Merge pull request #921 from blockstack/review/smart-contract-021319

Code Review: Smart Contract VM 02/13/19
This commit is contained in:
Aaron Blankstein
2019-02-19 13:01:47 -06:00
committed by GitHub
28 changed files with 2349 additions and 723 deletions

View File

@@ -20,6 +20,7 @@ serde_json = "1.0"
rust-crypto = "0.2"
sha2 = "0.8.0"
dirs = "1.0.4"
regex = "1"
[dependencies.secp256k1]
version = "0.11.5"

View File

@@ -16,3 +16,320 @@
You should have received a copy of the GNU General Public License
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
*/
use super::Error;
use crypto::sha2::Sha256;
use crypto::digest::Digest;
const C32_CHARACTERS: &str = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
fn c32_encode(input_bytes: &[u8]) -> String {
let c32_chars: &[u8] = C32_CHARACTERS.as_bytes();
let mut result = vec![];
let mut carry = 0;
let mut carry_bits = 0;
for current_value in input_bytes.iter().rev() {
let low_bits_to_take = 5 - carry_bits;
let low_bits = current_value & ((1<<low_bits_to_take) - 1);
let c32_value = (low_bits << carry_bits) + carry;
result.push(c32_chars[c32_value as usize]);
carry_bits = (8 + carry_bits) - 5;
carry = current_value >> (8 - carry_bits);
if carry_bits >= 5 {
let c32_value = carry & ((1<<5) - 1);
result.push(c32_chars[c32_value as usize]);
carry_bits = carry_bits - 5;
carry = carry >> 5;
}
}
if carry_bits > 0 {
result.push(c32_chars[carry as usize]);
}
// remove leading zeros from c32 encoding
while let Some(v) = result.pop() {
if v != c32_chars[0] {
result.push(v);
break;
}
}
// add leading zeros from input.
for current_value in input_bytes.iter() {
if *current_value == 0 {
result.push(c32_chars[0]);
} else {
break;
}
}
let result: Vec<u8> = result.drain(..).rev().collect();
String::from_utf8(result).unwrap()
}
fn c32_decode(input_str: &str) -> Result<Vec<u8>, Error> {
let mut result = vec![];
let mut carry: u16 = 0;
let mut carry_bits = 0; // can be up to 5
let iter_c32_digits = input_str.chars().rev()
.map(|x| { C32_CHARACTERS.find(x) });
for current_result in iter_c32_digits {
let current_5bit = current_result.ok_or(Error::InvalidCrockford32)?;
carry += (current_5bit as u16) << carry_bits;
carry_bits += 5;
if carry_bits >= 8 {
result.push((carry & ((1<<8) - 1)) as u8);
carry_bits -= 8;
carry = carry >> 8;
}
}
if carry_bits > 0 {
result.push(carry as u8);
}
// remove leading zeros from Vec<u8> encoding
while let Some(v) = result.pop() {
if v != 0 {
result.push(v);
break;
}
}
// add leading zeros from input.
for current_value in input_str.chars() {
if current_value == '0' {
result.push(0);
} else {
break;
}
}
result.reverse();
Ok(result)
}
fn double_sha256_checksum(data: &[u8]) -> Vec<u8> {
let mut sha2 = Sha256::new();
let mut tmp = [0u8; 32];
sha2.input(data);
sha2.result(&mut tmp);
let mut sha2 = Sha256::new();
sha2.input(&tmp);
sha2.result(&mut tmp);
tmp[0..4].to_vec()
}
fn c32_check_encode(version: u8, data: &[u8]) -> Result<String, Error> {
if version >= 32 {
return Err(Error::InvalidVersion(version))
}
let mut check_data = vec![version];
check_data.extend_from_slice(data);
let checksum = double_sha256_checksum(&check_data);
let mut encoding_data = data.to_vec();
encoding_data.extend_from_slice(&checksum);
// working with ascii strings is awful.
let mut c32_string = c32_encode(&encoding_data).into_bytes();
let version_char = C32_CHARACTERS.as_bytes()[version as usize];
c32_string.insert(0, version_char);
Ok(String::from_utf8(c32_string).unwrap())
}
fn c32_check_decode(check_data: &str) -> Result<(u8, Vec<u8>), Error> {
if check_data.len() < 2 {
return Err(Error::InvalidCrockford32)
}
let (version, data) = check_data.split_at(1);
let data_sum_bytes = c32_decode(data)?;
if data_sum_bytes.len() < 5 {
return Err(Error::InvalidCrockford32)
}
let (data_bytes, expected_sum) = data_sum_bytes.split_at(data_sum_bytes.len() - 4);
let mut check_data = c32_decode(version)?;
check_data.extend_from_slice(data_bytes);
let computed_sum = double_sha256_checksum(&check_data);
if computed_sum != expected_sum {
return Err(Error::InvalidChecksum(computed_sum, expected_sum.to_vec()))
}
let version = check_data[0];
let data = data_bytes.to_vec();
Ok((version, data))
}
pub fn c32_address_decode(c32_address_str: &str) -> Result<(u8, Vec<u8>), Error> {
if c32_address_str.len() <= 5 {
Err(Error::InvalidCrockford32)
} else {
c32_check_decode(&c32_address_str[1..])
}
}
pub fn c32_address(version: u8, data: &[u8]) -> Result<String, Error> {
let c32_string = c32_check_encode(version, data)?;
Ok(format!("S{}", c32_string))
}
mod test {
use util::hash::hex_bytes;
use super::*;
#[test]
fn test_addresses() {
let hex_strs = [
"a46ff88886c2ef9762d970b4d2c63678835bd39d",
"0000000000000000000000000000000000000000",
"0000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000000"
];
let versions = [
22,
0,
31,
20,
26,
21
];
let c32_addrs = [
[
"SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7",
"SP000000000000000000002Q6VF78",
"SP00000000000000000005JA84HQ",
"SP80000000000000000000000000000004R0CMNV",
"SP800000000000000000000000000000033H8YKK"
],
[
"S02J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKPVKG2CE",
"S0000000000000000000002AA028H",
"S000000000000000000006EKBDDS",
"S080000000000000000000000000000007R1QC00",
"S080000000000000000000000000000003ENTGCQ"
],
[
"SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR",
"SZ000000000000000000002ZE1VMN",
"SZ00000000000000000005HZ3DVN",
"SZ80000000000000000000000000000004XBV6MS",
"SZ800000000000000000000000000000007VF5G0"
],
[
"SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G",
"SM0000000000000000000062QV6X",
"SM00000000000000000005VR75B2",
"SM80000000000000000000000000000004WBEWKC",
"SM80000000000000000000000000000000JGSYGV"
],
[
"ST2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQYAC0RQ",
"ST000000000000000000002AMW42H",
"ST000000000000000000042DB08Y",
"ST80000000000000000000000000000006BYJ4R4",
"ST80000000000000000000000000000002YBNPV3"
],
[
"SN2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKP6D2ZK9",
"SN000000000000000000003YDHWKJ",
"SN00000000000000000005341MC8",
"SN800000000000000000000000000000066KZWY0",
"SN800000000000000000000000000000006H75AK"
]
];
for i in 0..hex_strs.len() {
for j in 0..versions.len() {
let h = hex_strs[i];
let v = versions[j];
let b = hex_bytes(h).unwrap();
let z = c32_address(v, &b).unwrap();
assert_eq!(z, c32_addrs[j][i]);
let (decoded_version, decoded_bytes) = c32_address_decode(&z).unwrap();
assert_eq!(decoded_version, v);
assert_eq!(decoded_bytes, b);
}
}
}
#[test]
fn test_simple() {
let hex_strings = &[
"a46ff88886c2ef9762d970b4d2c63678835bd39d",
"",
"0000000000000000000000000000000000000000",
"0000000000000000000000000000000000000001",
"1000000000000000000000000000000000000001",
"1000000000000000000000000000000000000000",
"01",
"22",
"0001",
"000001",
"00000001",
"10",
"0100",
"1000",
"010000",
"100000",
"01000000",
"10000000",
"0100000000"
];
let c32_strs = [
"MHQZH246RBQSERPSE2TD5HHPF21NQMWX",
"",
"00000000000000000000",
"00000000000000000001",
"20000000000000000000000000000001",
"20000000000000000000000000000000",
"1",
"12",
"01",
"001",
"0001",
"G",
"80",
"400",
"2000",
"10000",
"G0000",
"800000",
"4000000"
];
let results: Vec<_> = hex_strings.iter().zip(c32_strs.iter())
.map(|(hex_str, expected)|
{
let bytes = hex_bytes(hex_str).unwrap();
let c32_encoded = c32_encode(&bytes);
let decoded_bytes = c32_decode(&c32_encoded).unwrap();
let result = (bytes, c32_encoded, decoded_bytes, expected);
println!("{:?}", result);
result
}).collect();
for (bytes, c32_encoded, decoded_bytes, expected_c32) in results.iter() {
assert_eq!(bytes, decoded_bytes);
assert_eq!(c32_encoded, *expected_c32);
}
}
}

View File

@@ -17,4 +17,27 @@
along with Blockstack. If not, see <http://www.gnu.org/licenses/>.
*/
use std::error;
use std::fmt;
pub mod c32;
#[derive(Debug)]
pub enum Error {
InvalidCrockford32,
InvalidVersion(u8),
InvalidChecksum(Vec<u8>, Vec<u8>),
EmptyData
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
None
}
}

View File

@@ -30,6 +30,7 @@ extern crate curve25519_dalek;
extern crate ed25519_dalek;
extern crate sha2;
extern crate dirs;
extern crate regex;
#[macro_use] extern crate serde_derive;
@@ -38,7 +39,9 @@ mod burnchains;
mod chainstate;
mod core;
mod vm;
mod address;
use std::fs;
use std::env;
use std::process;
@@ -82,6 +85,20 @@ fn main() {
}
}
if argv[1] == "exec_program" {
if argv.len() < 3 {
eprintln!("Usage: {} exec_program [program-file.scm]", argv[0]);
process::exit(1);
}
let program: String = fs::read_to_string(&argv[2])
.expect(&format!("Error reading file: {}", argv[2]));
match vm::execute(&program) {
Ok(result) => println!("{}", result),
Err(error) => println!("Program Execution Error: \n {}", error)
}
return
}
if argv.len() < 4 {
eprintln!("Usage: {} blockchain network working_dir", argv[0]);
process::exit(1);

108
src/vm/callables.rs Normal file
View File

@@ -0,0 +1,108 @@
use vm::errors::{InterpreterResult as Result, Error};
use vm::representations::SymbolicExpression;
use vm::types::TypeSignature;
use vm::{eval, Value, LocalContext, Environment};
pub enum CallableType <'a> {
UserFunction(DefinedFunction),
NativeFunction(&'a Fn(&[Value]) -> Result<Value>),
SpecialFunction(&'a Fn(&[SymbolicExpression], &mut Environment, &LocalContext) -> Result<Value>)
}
#[derive(Clone)]
pub enum DefinedFunction {
Public(PublicFunction),
Private(PrivateFunction)
}
#[derive(Clone)]
pub struct PublicFunction {
types: Vec<TypeSignature>,
pub arguments: Vec<String>,
pub body: SymbolicExpression
}
#[derive(Clone)]
pub struct PrivateFunction {
pub arguments: Vec<String>,
pub body: SymbolicExpression
}
#[derive(Clone,PartialEq,Eq,Hash)]
pub struct FunctionIdentifier {
pub arguments: Vec<String>,
pub body: SymbolicExpression
}
impl PublicFunction {
pub fn new(mut arguments: Vec<(String, TypeSignature)>, body: SymbolicExpression) -> DefinedFunction {
let (argument_names, types) = arguments.drain(..).unzip();
DefinedFunction::Public(PublicFunction {
arguments: argument_names,
body: body,
types: types
})
}
fn apply(&self, args: &[Value], env: &mut Environment) -> Result<Value> {
// since self is a malformed object.
let mut context = LocalContext::new();
let arg_iterator = self.arguments.iter().zip(self.types.iter()).zip(args.iter());
for ((arg, type_sig), value) in arg_iterator {
if !type_sig.admits(value) {
return Err(Error::TypeError(format!("{:?}", type_sig), value.clone()))
}
if let Some(_) = context.variables.insert(arg.clone(), value.clone()) {
return Err(Error::VariableDefinedMultipleTimes(arg.clone()))
}
}
eval(&self.body, env, &context)
}
}
impl PrivateFunction {
pub fn new(arguments: Vec<String>, body: SymbolicExpression) -> DefinedFunction {
DefinedFunction::Private(PrivateFunction {
arguments: arguments,
body: body,
})
}
fn apply(&self, args: &[Value], env: &mut Environment) -> Result<Value> {
let mut context = LocalContext::new();
let arg_iterator = self.arguments.iter().zip(args.iter());
for (arg, value) in arg_iterator {
if let Some(_) = context.variables.insert(arg.clone(), value.clone()) {
return Err(Error::VariableDefinedMultipleTimes(arg.clone()))
}
}
eval(&self.body, env, &context)
}
}
impl DefinedFunction {
pub fn apply(&self, args: &[Value], env: &mut Environment) -> Result<Value> {
match self {
DefinedFunction::Private(f) => f.apply(args, env),
DefinedFunction::Public(f) => f.apply(args, env),
}
}
pub fn is_public(&self) -> bool {
match self {
DefinedFunction::Public(_) => true,
DefinedFunction::Private(_) => false
}
}
pub fn get_identifier(&self) -> FunctionIdentifier {
let (body, arguments) = match self {
DefinedFunction::Private(f) => (f.body.clone(), f.arguments.clone()),
DefinedFunction::Public(f) => (f.body.clone(), f.arguments.clone())
};
return FunctionIdentifier {
body: body,
arguments: arguments }
}
}

View File

@@ -1,18 +1,25 @@
use std::collections::HashMap;
use std::collections::HashSet;
use vm::types::{DefinedFunction, FunctionIdentifier, Value};
use vm::errors::{Error, InterpreterResult as Result};
use vm::types::Value;
use vm::callables::{DefinedFunction, FunctionIdentifier};
use vm::database::ContractDatabase;
const MAX_CONTEXT_DEPTH: u16 = 256;
pub struct Environment <'a> {
pub global_context: Context <'a>,
pub global_context: &'a GlobalContext,
pub call_stack: CallStack,
pub database: Box<ContractDatabase>
pub database: &'a mut ContractDatabase
}
impl <'a> Environment <'a> {
pub fn new(database: Box<ContractDatabase>) -> Environment<'a> {
let global_context = Context::new();
// Environments pack a reference to the global context, a mutable reference to a contract db,
// together with a call stack. Generally, the environment structure is intended to be reconstructed
// for every transaction.
pub fn new(global_context: &'a GlobalContext,
database: &'a mut ContractDatabase) -> Environment<'a> {
Environment {
global_context: global_context,
call_stack: CallStack::new(),
@@ -21,22 +28,20 @@ impl <'a> Environment <'a> {
}
}
pub struct Context <'a> {
pub parent: Option< &'a Context<'a>>,
pub struct GlobalContext {
pub variables: HashMap<String, Value>,
pub functions: HashMap<String, Box<DefinedFunction>>,
pub functions: HashMap<String, DefinedFunction>,
}
impl <'a> Context <'a> {
pub fn new() -> Context<'a> {
Context { parent: Option::None,
variables: HashMap::new(),
functions: HashMap::new() }
}
pub fn extend(&'a self) -> Context<'a> {
Context {
parent: Some(self),
pub struct LocalContext <'a> {
pub parent: Option< &'a LocalContext<'a>>,
pub variables: HashMap<String, Value>,
depth: u16
}
impl GlobalContext {
pub fn new() -> GlobalContext {
GlobalContext {
variables: HashMap::new(),
functions: HashMap::new()
}
@@ -44,24 +49,46 @@ impl <'a> Context <'a> {
pub fn lookup_variable(&self, name: &str) -> Option<Value> {
match self.variables.get(name) {
Some(value) => Option::Some((*value).clone()),
None => {
match self.parent {
Some(parent) => parent.lookup_variable(name),
None => Option::None
}
}
Some(value) => Option::Some(value.clone()),
None => Option::None
}
}
pub fn lookup_function(&self, name: &str) -> Option<Box<DefinedFunction>> {
pub fn lookup_function(&self, name: &str) -> Option<DefinedFunction> {
match self.functions.get(name) {
Some(value) => {
Option::Some(Box::new(*value.clone()))
},
Some(value) => Option::Some(value.clone()),
None => Option::None
}
}
}
impl <'a> LocalContext <'a> {
pub fn new() -> LocalContext<'a> {
LocalContext {
depth: 0,
parent: Option::None,
variables: HashMap::new(),
}
}
pub fn extend(&'a self) -> Result<LocalContext<'a>> {
if self.depth >= MAX_CONTEXT_DEPTH {
Err(Error::MaxContextDepthReached)
} else {
Ok(LocalContext {
parent: Some(self),
variables: HashMap::new(),
depth: self.depth + 1
})
}
}
pub fn lookup_variable(&self, name: &str) -> Option<Value> {
match self.variables.get(name) {
Some(value) => Option::Some(value.clone()),
None => {
match self.parent {
Some(parent) => parent.lookup_function(name),
Some(parent) => parent.lookup_variable(name),
None => Option::None
}
}
@@ -81,6 +108,10 @@ impl CallStack {
}
}
pub fn depth(&self) -> usize {
self.stack.len()
}
pub fn contains(&self, user_function: &FunctionIdentifier) -> bool {
self.stack.contains(user_function)
}

57
src/vm/contracts.rs Normal file
View File

@@ -0,0 +1,57 @@
use vm::{SymbolicExpression, Value, apply, eval_all};
use vm::errors::{Error, InterpreterResult as Result};
use vm::callables::CallableType;
use vm::contexts::{Environment, LocalContext, GlobalContext};
use vm::database::{MemoryContractDatabase, ContractDatabase};
use vm::parser;
use vm::variables;
pub struct Contract {
db: Box<ContractDatabase>,
global_context: GlobalContext
}
impl Contract {
pub fn make_in_memory_contract(contract: &str) -> Result<Contract> {
let parsed: Vec<_> = parser::parse(contract)?;
let mut global_context = GlobalContext::new();
let mut db_instance = Box::new(MemoryContractDatabase::new());
let result = eval_all(&parsed, &mut *db_instance, &mut global_context)?;
match result {
Value::Void => {},
_ => return Err(Error::Generic("Contract instantiation should return null.".to_string()))
}
Ok(Contract { db: db_instance,
global_context: global_context })
}
// Todo: add principal value type, check for sender to be one.
pub fn execute_transaction(&mut self, sender: &Value, tx_name: &str,
args: &[SymbolicExpression]) -> Result<Value> {
let func = self.global_context.lookup_function(tx_name)
.ok_or(Error::Undefined(format!("No such function in contract: {}", tx_name)))?;
if !func.is_public() {
return Err(Error::Undefined("Attempt to call non-public function".to_string()))
}
if let Value::Principal(_, _) = sender {
let mut env = Environment::new(&self.global_context, &mut *self.db);
for arg in args {
match arg {
SymbolicExpression::AtomValue(ref _v) => {},
_ => return Err(Error::InterpreterError("Passed non-value expression to exec_tx!".to_string()))
}
}
let mut local_context = LocalContext::new();
local_context.variables.insert(variables::TX_SENDER.to_string(), sender.clone());
apply(&CallableType::UserFunction(func), args, &mut env, &local_context)
} else {
Err(Error::BadSender(sender.clone()))
}
}
}

View File

@@ -1,18 +1,18 @@
use std::collections::HashMap;
use vm::errors::Error;
use vm::InterpreterResult;
use vm::errors::{Error, InterpreterResult as Result};
use vm::types::{Value, TypeSignature, TupleTypeSignature, AtomTypeIdentifier};
pub trait DataMap {
fn fetch_entry(&self, key: &Value) -> InterpreterResult;
fn set_entry(&mut self, key: Value, value: Value) -> Result<(), Error>;
fn insert_entry(&mut self, key: Value, value: Value) -> InterpreterResult;
fn delete_entry(&mut self, key: &Value) -> InterpreterResult;
fn fetch_entry(&self, key: &Value) -> Result<Value>;
fn set_entry(&mut self, key: Value, value: Value) -> Result<()>;
fn insert_entry(&mut self, key: Value, value: Value) -> Result<Value>;
fn delete_entry(&mut self, key: &Value) -> Result<Value>;
}
pub trait ContractDatabase {
fn get_data_map(&mut self, map_name: &str) -> Option<&mut DataMap>;
fn get_data_map(&mut self, map_name: &str) -> Option<&DataMap>;
fn get_mut_data_map(&mut self, map_name: &str) -> Option<&mut DataMap>;
fn create_map(&mut self, map_name: &str, key_type: TupleTypeSignature, value_type: TupleTypeSignature);
}
@@ -31,10 +31,8 @@ impl MemoryDataMap {
value_type: TupleTypeSignature) -> MemoryDataMap {
MemoryDataMap {
map: HashMap::new(),
key_type: TypeSignature::new(
AtomTypeIdentifier::TupleType(key_type), 0),
value_type: TypeSignature::new(
AtomTypeIdentifier::TupleType(value_type), 0)
key_type: TypeSignature::new_atom(AtomTypeIdentifier::TupleType(key_type)),
value_type: TypeSignature::new_atom(AtomTypeIdentifier::TupleType(value_type))
}
}
}
@@ -46,7 +44,7 @@ impl MemoryContractDatabase {
}
impl ContractDatabase for MemoryContractDatabase {
fn get_data_map(&mut self, map_name: &str) -> Option<&mut DataMap> {
fn get_mut_data_map(&mut self, map_name: &str) -> Option<&mut DataMap> {
if let Some(data_map) = self.maps.get_mut(map_name) {
Some(data_map)
} else {
@@ -54,6 +52,14 @@ impl ContractDatabase for MemoryContractDatabase {
}
}
fn get_data_map(&mut self, map_name: &str) -> Option<&DataMap> {
if let Some(data_map) = self.maps.get(map_name) {
Some(data_map)
} else {
None
}
}
fn create_map(&mut self, map_name: &str, key_type: TupleTypeSignature, value_type: TupleTypeSignature) {
let new_map = MemoryDataMap::new(key_type, value_type);
self.maps.insert(map_name.to_string(), new_map);
@@ -65,9 +71,8 @@ impl DataMap for MemoryDataMap {
// however, they should really be specified in the functions/database.rs file, whereas
// this file should really just be speccing out the database connection/requirement.
fn fetch_entry(&self, key: &Value) -> InterpreterResult {
let key_type = TypeSignature::type_of(key);
if self.key_type != key_type {
fn fetch_entry(&self, key: &Value) -> Result<Value> {
if !self.key_type.admits(key) {
return Err(Error::TypeError(format!("{:?}", self.key_type), (*key).clone()))
}
if let Some(value) = self.map.get(key) {
@@ -77,26 +82,22 @@ impl DataMap for MemoryDataMap {
}
}
fn set_entry(&mut self, key: Value, value: Value) -> Result<(), Error> {
let key_type = TypeSignature::type_of(&key);
if self.key_type != key_type {
fn set_entry(&mut self, key: Value, value: Value) -> Result<()> {
if !self.key_type.admits(&key) {
return Err(Error::TypeError(format!("{:?}", self.key_type), key))
}
let value_type = TypeSignature::type_of(&value);
if self.value_type != value_type {
if !self.value_type.admits(&value) {
return Err(Error::TypeError(format!("{:?}", self.value_type), value))
}
self.map.insert(key, value);
Ok(())
}
fn insert_entry(&mut self, key: Value, value: Value) -> InterpreterResult {
let key_type = TypeSignature::type_of(&key);
if self.key_type != key_type {
fn insert_entry(&mut self, key: Value, value: Value) -> Result<Value> {
if !self.key_type.admits(&key) {
return Err(Error::TypeError(format!("{:?}", self.key_type), key))
}
let value_type = TypeSignature::type_of(&value);
if self.value_type != value_type {
if !self.value_type.admits(&value) {
return Err(Error::TypeError(format!("{:?}", self.value_type), value))
}
if self.map.contains_key(&key) {
@@ -107,9 +108,8 @@ impl DataMap for MemoryDataMap {
}
}
fn delete_entry(&mut self, key: &Value) -> InterpreterResult {
let key_type = TypeSignature::type_of(key);
if self.key_type != key_type {
fn delete_entry(&mut self, key: &Value) -> Result<Value> {
if !self.key_type.admits(key) {
return Err(Error::TypeError(format!("{:?}", self.key_type), (*key).clone()))
}
if let Some(_value) = self.map.remove(key) {

View File

@@ -1,6 +1,6 @@
use std::fmt;
use std::error;
use super::types::Value;
use vm::types::Value;
#[derive(Debug,PartialEq)]
pub enum Error {
@@ -13,15 +13,30 @@ pub enum Error {
Arithmetic(String),
ParseError(String),
RecursionDetected,
ExpectedListPairs
MaxStackDepthReached,
MaxContextDepthReached,
ListDimensionTooHigh,
ListTooLarge,
BadTypeConstruction,
BufferTooLarge,
ValueTooLarge,
ExpectedListPairs,
InvalidTypeDescription,
BadSender(Value),
BadSymbolicRepresentation(String),
ReservedName(String),
InterpreterError(String),
VariableDefinedMultipleTimes(String)
}
pub type InterpreterResult <R> = Result<R, Error>;
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::RecursionDetected => write!(f, "Illegal operation: attempted recursion detected."),
Error::TryEvalToFunction => write!(f, "Illegal operation: attempt to evaluate to function."),
Error::TypeError(ref expected, ref found) => write!(f, "TypeError: Expected {}, found {:?}.", expected, found),
Error::TypeError(ref expected, ref found) => write!(f, "TypeError: Expected {}, found {}.", expected, found),
_ => write!(f, "{:?}", self)
}
}
@@ -29,8 +44,18 @@ impl fmt::Display for Error {
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
_ => None
}
None
}
}
#[test]
fn error_formats() {
assert_eq!(format!("{}", Error::RecursionDetected),
"Illegal operation: attempted recursion detected.");
assert_eq!(format!("{}", Error::TryEvalToFunction),
"Illegal operation: attempt to evaluate to function.");
assert_eq!(format!("{}", Error::TypeError("Test".to_string(), Value::Void)),
"TypeError: Expected Test, found null.");
assert_eq!(format!("{}", Error::NotImplemented),
"NotImplemented");
}

View File

@@ -1,15 +1,14 @@
use super::super::types::Value;
use super::super::errors::Error;
use super::super::InterpreterResult;
use vm::types::Value;
use vm::errors::{Error, InterpreterResult as Result};
fn type_force_integer(value: &Value) -> Result<i128, Error> {
fn type_force_integer(value: &Value) -> Result<i128> {
match *value {
Value::Int(int) => Ok(int),
_ => Err(Error::TypeError("IntType".to_string(), value.clone()))
}
}
fn binary_comparison<F>(args: &[Value], function: &F) -> InterpreterResult
fn binary_comparison<F>(args: &[Value], function: &F) -> Result<Value>
where F: Fn(i128, i128) -> bool {
if args.len() == 2 {
let arg1 = type_force_integer(&args[0])?;
@@ -20,21 +19,21 @@ where F: Fn(i128, i128) -> bool {
}
}
pub fn native_geq(args: &[Value]) -> InterpreterResult {
pub fn native_geq(args: &[Value]) -> Result<Value> {
binary_comparison(args, &|x, y| x >= y)
}
pub fn native_leq(args: &[Value]) -> InterpreterResult {
pub fn native_leq(args: &[Value]) -> Result<Value> {
binary_comparison(args, &|x, y| x <= y)
}
pub fn native_ge(args: &[Value]) -> InterpreterResult {
pub fn native_ge(args: &[Value]) -> Result<Value> {
binary_comparison(args, &|x, y| x > y)
}
pub fn native_le(args: &[Value]) -> InterpreterResult {
pub fn native_le(args: &[Value]) -> Result<Value> {
binary_comparison(args, &|x, y| x < y)
}
pub fn native_add(args: &[Value]) -> InterpreterResult {
let typed_args: Result<Vec<_>, Error> = args.iter().map(|x| type_force_integer(x)).collect();
pub fn native_add(args: &[Value]) -> Result<Value> {
let typed_args: Result<Vec<_>> = args.iter().map(|x| type_force_integer(x)).collect();
let parsed_args = typed_args?;
let checked_result = parsed_args.iter().fold(Some(0), |acc: Option<i128>, x| {
match acc {
@@ -48,8 +47,8 @@ pub fn native_add(args: &[Value]) -> InterpreterResult {
}
}
pub fn native_sub(args: &[Value]) -> InterpreterResult {
let typed_args: Result<Vec<_>, Error> = args.iter().map(|x| type_force_integer(x)).collect();
pub fn native_sub(args: &[Value]) -> Result<Value> {
let typed_args: Result<Vec<_>> = args.iter().map(|x| type_force_integer(x)).collect();
let parsed_args = typed_args?;
if let Some((first, rest)) = parsed_args.split_first() {
if rest.len() == 0 { // return negation
@@ -71,8 +70,8 @@ pub fn native_sub(args: &[Value]) -> InterpreterResult {
}
}
pub fn native_mul(args: &[Value]) -> InterpreterResult {
let typed_args: Result<Vec<_>, Error> = args.iter().map(|x| type_force_integer(x)).collect();
pub fn native_mul(args: &[Value]) -> Result<Value> {
let typed_args: Result<Vec<_>> = args.iter().map(|x| type_force_integer(x)).collect();
let parsed_args = typed_args?;
let checked_result = parsed_args.iter().fold(Some(1), |acc: Option<i128>, x| {
match acc {
@@ -86,8 +85,8 @@ pub fn native_mul(args: &[Value]) -> InterpreterResult {
}
}
pub fn native_div(args: &[Value]) -> InterpreterResult {
let typed_args: Result<Vec<_>, Error> = args.iter().map(|x| type_force_integer(x)).collect();
pub fn native_div(args: &[Value]) -> Result<Value> {
let typed_args: Result<Vec<_>> = args.iter().map(|x| type_force_integer(x)).collect();
let parsed_args = typed_args?;
if let Some((first, rest)) = parsed_args.split_first() {
let checked_result = rest.iter().fold(Some(*first), |acc, x| {
@@ -105,6 +104,8 @@ pub fn native_div(args: &[Value]) -> InterpreterResult {
}
}
// AARON: Note -- this was pulled straight for rustlang's nightly @ 1.34
// -- this _should be_ deleted once 1.34 ships.
fn checked_pow(mut base: i128, mut exp: u32) -> Option<i128> {
let mut acc: i128 = 1;
@@ -126,7 +127,7 @@ fn checked_pow(mut base: i128, mut exp: u32) -> Option<i128> {
Some(acc)
}
pub fn native_pow(args: &[Value]) -> InterpreterResult {
pub fn native_pow(args: &[Value]) -> Result<Value> {
if args.len() == 2 {
let base = type_force_integer(&args[0])?;
let power_i128 = type_force_integer(&args[1])?;
@@ -148,7 +149,7 @@ pub fn native_pow(args: &[Value]) -> InterpreterResult {
}
pub fn native_mod(args: &[Value]) -> InterpreterResult {
pub fn native_mod(args: &[Value]) -> Result<Value> {
if args.len() == 2 {
let numerator = type_force_integer(&args[0])?;
let denominator = type_force_integer(&args[1])?;

View File

@@ -1,16 +1,16 @@
use super::super::types::Value;
use super::super::errors::Error;
use super::super::representations::SymbolicExpression;
use super::super::{Context,Environment,eval,InterpreterResult};
use vm::types::Value;
use vm::errors::{Error, InterpreterResult as Result};
use vm::representations::SymbolicExpression;
use vm::{LocalContext, Environment, eval};
fn type_force_bool(value: &Value) -> Result<bool, Error> {
fn type_force_bool(value: &Value) -> Result<bool> {
match *value {
Value::Bool(boolean) => Ok(boolean),
_ => Err(Error::TypeError("BoolType".to_string(), value.clone()))
}
}
pub fn special_or(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> InterpreterResult {
pub fn special_or(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result<Value> {
if args.len() < 1 {
return Err(Error::InvalidArguments("(or ...) requires at least 1 argument".to_string()))
}
@@ -26,7 +26,7 @@ pub fn special_or(args: &[SymbolicExpression], env: &mut Environment, context: &
Ok(Value::Bool(false))
}
pub fn special_and(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> InterpreterResult {
pub fn special_and(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result<Value> {
if args.len() < 1 {
return Err(Error::InvalidArguments("(and ...) requires at least 1 argument".to_string()))
}
@@ -42,7 +42,7 @@ pub fn special_and(args: &[SymbolicExpression], env: &mut Environment, context:
Ok(Value::Bool(true))
}
pub fn native_not(args: &[Value]) -> InterpreterResult {
pub fn native_not(args: &[Value]) -> Result<Value> {
if args.len() != 1 {
return Err(Error::InvalidArguments("(not ...) requires exactly 1 argument".to_string()))
}

View File

@@ -1,15 +1,15 @@
use super::super::types::{Value};
use super::super::representations::SymbolicExpression;
use super::super::{InterpreterResult,eval,Context,Environment};
use super::super::errors::Error;
use super::super::database::DataMap;
use vm::types::{Value};
use vm::representations::SymbolicExpression;
use vm::errors::{Error, InterpreterResult as Result};
use vm::database::DataMap;
use vm::{eval, LocalContext, Environment};
fn obtain_map <'a> (map_arg: &SymbolicExpression, env: &'a mut Environment) -> Result<&'a mut DataMap, Error> {
fn obtain_map <'a> (map_arg: &SymbolicExpression, env: &'a mut Environment) -> Result<&'a mut DataMap> {
let map_name = match map_arg {
SymbolicExpression::Atom(value) => Ok(value),
_ => Err(Error::InvalidArguments("First argument in data functions must be the map name".to_string()))
}?;
match env.database.get_data_map(map_name) {
match env.database.get_mut_data_map(map_name) {
Some(map) => Ok(map),
None => Err(Error::Undefined(format!("No such map named: {}", map_name)))
}
@@ -17,7 +17,7 @@ fn obtain_map <'a> (map_arg: &SymbolicExpression, env: &'a mut Environment) -> R
pub fn special_fetch_entry(args: &[SymbolicExpression],
env: &mut Environment,
context: &Context) -> InterpreterResult {
context: &LocalContext) -> Result<Value> {
// arg0 -> map name
// arg1 -> key
if args.len() != 2 {
@@ -26,14 +26,22 @@ pub fn special_fetch_entry(args: &[SymbolicExpression],
let key = eval(&args[1], env, context)?;
let map = obtain_map(&args[0], env)?;
let map_name = match &args[0] {
SymbolicExpression::Atom(value) => Ok(value),
_ => Err(Error::InvalidArguments("First argument in data functions must be the map name".to_string()))
}?;
let map = match env.database.get_data_map(&map_name) {
Some(map) => Ok(map),
None => Err(Error::Undefined(format!("No such map named: {}", map_name)))
}?;
map.fetch_entry(&key)
}
pub fn special_set_entry(args: &[SymbolicExpression],
env: &mut Environment,
context: &Context) -> InterpreterResult {
context: &LocalContext) -> Result<Value> {
// arg0 -> map name
// arg1 -> key
// arg2 -> value
@@ -54,7 +62,7 @@ pub fn special_set_entry(args: &[SymbolicExpression],
pub fn special_insert_entry(args: &[SymbolicExpression],
env: &mut Environment,
context: &Context) -> InterpreterResult {
context: &LocalContext) -> Result<Value> {
// arg0 -> map name
// arg1 -> key
// arg2 -> value
@@ -72,7 +80,7 @@ pub fn special_insert_entry(args: &[SymbolicExpression],
pub fn special_delete_entry(args: &[SymbolicExpression],
env: &mut Environment,
context: &Context) -> InterpreterResult {
context: &LocalContext) -> Result<Value> {
// arg0 -> map name
// arg1 -> key
if args.len() != 2 {

View File

@@ -1,8 +1,10 @@
use super::super::types::{Value, DefinedFunction, TupleTypeSignature, TypeSignature};
use super::super::representations::SymbolicExpression;
use super::super::representations::SymbolicExpression::{Atom,AtomValue,List,NamedParameter};
use super::super::{Context,Environment,eval};
use super::super::errors::Error;
use vm::types::{Value, TupleTypeSignature, parse_name_type_pairs};
use vm::callables::{DefinedFunction, PublicFunction, PrivateFunction};
use vm::representations::SymbolicExpression;
use vm::representations::SymbolicExpression::{Atom, AtomValue, List, NamedParameter};
use vm::errors::{Error, InterpreterResult as Result};
use vm::contexts::{GlobalContext, LocalContext, Environment};
use vm::eval;
pub enum DefineResult {
Variable(String, Value),
@@ -11,14 +13,30 @@ pub enum DefineResult {
NoDefine
}
pub fn handle_define_variable(variable: &String, expression: &SymbolicExpression, env: &mut Environment) -> Result<DefineResult, Error> {
let context = Context::new();
fn check_legal_define(name: &str, global_context: &GlobalContext) -> Result<()> {
use vm::is_reserved;
if is_reserved(name) {
Err(Error::ReservedName(name.to_string()))
} else if global_context.variables.contains_key(name) || global_context.functions.contains_key(name) {
Err(Error::VariableDefinedMultipleTimes(name.to_string()))
} else {
Ok(())
}
}
fn handle_define_variable(variable: &String, expression: &SymbolicExpression, env: &mut Environment) -> Result<DefineResult> {
// is the variable name legal?
check_legal_define(variable, &env.global_context)?;
let context = LocalContext::new();
let value = eval(expression, env, &context)?;
Ok(DefineResult::Variable(variable.clone(), value))
}
pub fn handle_define_function(signature: &[SymbolicExpression], expression: &SymbolicExpression) -> Result<DefineResult, Error> {
let coerced_atoms: Result<Vec<_>, _> = signature.iter().map(|x| {
fn handle_define_private_function(signature: &[SymbolicExpression],
expression: &SymbolicExpression,
env: &Environment) -> Result<DefineResult> {
let coerced_atoms: Result<Vec<_>> = signature.iter().map(|x| {
if let Atom(name) = x {
Ok(name)
} else {
@@ -27,78 +45,57 @@ pub fn handle_define_function(signature: &[SymbolicExpression], expression: &Sym
}).collect();
let names = coerced_atoms?;
if let Some((function_name, arg_names)) = names.split_first() {
let function = DefinedFunction {
arguments: arg_names.iter().map(|x| (*x).clone()).collect(),
body: expression.clone()
};
Ok(DefineResult::Function((*function_name).clone(), function))
} else {
Err(Error::InvalidArguments("Must supply atleast a name argument to define a function".to_string()))
}
let (function_name, arg_names) = names.split_first()
.ok_or(Error::InvalidArguments("Must supply atleast a name argument to define a function".to_string()))?;
check_legal_define(&function_name, &env.global_context)?;
let function = PrivateFunction::new(
arg_names.iter().map(|x| (*x).clone()).collect(),
expression.clone());
Ok(DefineResult::Function((*function_name).clone(), function))
}
fn parse_name_type_pair_list(type_def: &SymbolicExpression) -> Result<TupleTypeSignature, Error> {
fn handle_define_public_function(signature: &[SymbolicExpression],
expression: &SymbolicExpression,
env: &Environment) -> Result<DefineResult> {
let (function_symbol, arg_symbols) = signature.split_first()
.ok_or(Error::InvalidArguments("Must supply atleast a name argument to define a function".to_string()))?;
// this is a pretty deep nesting here, but what we're trying to do is pick out the values of
// the form:
// ((name1 type1) (name2 type2) (name3 type3) ...)
// which is a list of 2-length lists of atoms.
let mapped_key_types = match type_def {
List(ref key_vec) => {
// step 1: parse it into a vec of symbolicexpression pairs.
let as_pairs: Result<Vec<_>, Error> =
key_vec.iter().map(
|key_type_pair| {
if let List(ref as_vec) = key_type_pair {
if as_vec.len() != 2 {
Err(Error::ExpectedListPairs)
} else {
Ok((&as_vec[0], &as_vec[1]))
}
} else {
Err(Error::ExpectedListPairs)
}
}).collect();
// step 2: turn into a vec of (name, typesignature) pairs.
let key_types: Result<Vec<_>, Error> =
(as_pairs?).iter().map(|(name_symbol, type_symbol)| {
let name = match name_symbol {
Atom(ref var) => Ok(var.clone()),
_ => Err(Error::ExpectedListPairs)
}?;
let type_info = match type_symbol {
Atom(ref type_description) => TypeSignature::parse_type_str(type_description),
_ => Err(Error::ExpectedListPairs)
}?;
Ok((name, type_info))
}).collect();
key_types
},
_ => Err(Error::ExpectedListPairs)
let function_name = match function_symbol {
Atom(name) => Ok(name),
_ => Err(Error::InvalidArguments(format!("Invalid function name {:?}", function_symbol)))
}?;
TupleTypeSignature::new(mapped_key_types)
check_legal_define(&function_name, &env.global_context)?;
let arguments = parse_name_type_pairs(arg_symbols)?;
let function = PublicFunction::new(arguments,
expression.clone());
Ok(DefineResult::Function((*function_name).clone(), function))
}
fn handle_define_map(map_name: &SymbolicExpression,
key_type: &SymbolicExpression,
value_type: &SymbolicExpression) -> Result<DefineResult, Error> {
value_type: &SymbolicExpression,
env: &Environment) -> Result<DefineResult> {
let map_str = match map_name {
Atom(ref map_name) => Ok(map_name.clone()),
_ => Err(Error::InvalidArguments("Non-name argument to define-map".to_string()))
}?;
let key_type_signature = parse_name_type_pair_list(key_type)?;
let value_type_signature = parse_name_type_pair_list(value_type)?;
check_legal_define(&map_str, &env.global_context)?;
let key_type_signature = TupleTypeSignature::parse_name_type_pair_list(key_type)?;
let value_type_signature = TupleTypeSignature::parse_name_type_pair_list(value_type)?;
Ok(DefineResult::Map(map_str, key_type_signature, value_type_signature))
}
pub fn evaluate_define(expression: &SymbolicExpression, env: &mut Environment) -> Result<DefineResult, Error> {
pub fn evaluate_define(expression: &SymbolicExpression, env: &mut Environment) -> Result<DefineResult> {
if let SymbolicExpression::List(elements) = expression {
if let Some(Atom(func_name)) = elements.get(0) {
return match func_name.as_str() {
@@ -112,7 +109,20 @@ pub fn evaluate_define(expression: &SymbolicExpression, env: &mut Environment) -
"Illegal operation: attempted to re-define a value type.".to_string())),
NamedParameter(ref _value) => Err(Error::InvalidArguments(
"Illegal operation: attempted to re-define a named parameter.".to_string())),
List(ref function_signature) => handle_define_function(&function_signature, &elements[2])
List(ref function_signature) =>
handle_define_private_function(&function_signature, &elements[2], env)
}
}
},
"define-public" => {
if elements.len() != 3 {
Err(Error::InvalidArguments("(define-public ...) requires 2 arguments".to_string()))
} else {
if let List(ref function_signature) = elements[1] {
handle_define_public_function(&function_signature, &elements[2], env)
} else {
Err(Error::InvalidArguments(
"Illegal operation: attempted to define-public a non-function.".to_string()))
}
}
},
@@ -120,7 +130,7 @@ pub fn evaluate_define(expression: &SymbolicExpression, env: &mut Environment) -
if elements.len() != 4 {
Err(Error::InvalidArguments("(define-map ...) requires 3 arguments".to_string()))
} else {
handle_define_map(&elements[1], &elements[2], &elements[3])
handle_define_map(&elements[1], &elements[2], &elements[3], env)
}
}
_ => Ok(DefineResult::NoDefine)

View File

@@ -1,31 +1,14 @@
use super::InterpreterResult;
use super::super::errors::Error;
use super::super::types::{Value, TypeSignature};
use super::super::types::Value::{List};
use super::super::representations::SymbolicExpression;
use super::super::representations::SymbolicExpression::{AtomValue};
use super::super::{Context,Environment,eval,apply,lookup_function};
use vm::errors::{Error, InterpreterResult as Result};
use vm::types::Value;
use vm::representations::SymbolicExpression;
use vm::representations::SymbolicExpression::{AtomValue};
use vm::{LocalContext, Environment, eval, apply, lookup_function};
pub fn list_cons(args: &[Value]) -> InterpreterResult {
if let Some((first, _rest)) = args.split_first() {
let list_type = TypeSignature::get_list_type_for(first)?;
let list_result: Result<Vec<_>, Error> = args.iter().map(|x| {
let x_type = TypeSignature::get_list_type_for(x)?;
if x_type == list_type {
Ok(x.clone())
} else {
Err(Error::InvalidArguments("List must be composed of a single type".to_string()))
}
}).collect();
let list_contents = list_result?;
Ok(List(list_contents, list_type))
} else {
Ok(List(Vec::new(), TypeSignature::get_empty_list_type()))
}
pub fn list_cons(args: &[Value]) -> Result<Value> {
Value::new_list(args)
}
pub fn list_fold(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> InterpreterResult {
pub fn list_fold(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result<Value> {
if args.len() != 3 {
return Err(Error::InvalidArguments(format!("Wrong number of arguments ({}) to fold", args.len())))
}
@@ -34,7 +17,7 @@ pub fn list_fold(args: &[SymbolicExpression], env: &mut Environment, context: &C
let list = eval(&args[1], env, context)?;
let initial = eval(&args[2], env, context)?;
match list {
List(vector, _) => vector.iter().try_fold(
Value::List(list_data) => list_data.data.iter().try_fold(
initial,
|acc, x| {
let argument = [ AtomValue(x.clone()), AtomValue(acc) ];
@@ -47,7 +30,7 @@ pub fn list_fold(args: &[SymbolicExpression], env: &mut Environment, context: &C
}
}
pub fn list_map(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> InterpreterResult {
pub fn list_map(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result<Value> {
if args.len() != 2 {
return Err(Error::InvalidArguments(format!("Wrong number of arguments ({}) to map", args.len())))
}
@@ -56,28 +39,13 @@ pub fn list_map(args: &[SymbolicExpression], env: &mut Environment, context: &Co
let list = eval(&args[1], env, context)?;
match list {
List(vector, _) => {
let mut result_value_type: Option<TypeSignature> = None;
let result: Result<Vec<_>, Error> = vector.iter().map(|x| {
let argument = [ SymbolicExpression::AtomValue(x.clone()) ];
let value = apply(&function, &argument, env, context)?;
let value_type = TypeSignature::get_list_type_for(&value)?;
if let Some(ref all_type) = result_value_type {
if *all_type == value_type {
Ok(value)
} else {
Err(Error::InvalidArguments("Results of map must all be of a single type".to_string()))
}
} else {
result_value_type = Some(value_type);
Ok(value)
}
Value::List(list_data) => {
let result: Result<Vec<_>> = list_data.data.iter().map(|x| {
let argument = [ AtomValue(x.clone()) ];
apply(&function, &argument, env, context)
}).collect();
let vec = result?;
match result_value_type {
Some(value_type) => Ok(List(vec, value_type)),
None => Ok(List(Vec::new(), TypeSignature::get_empty_list_type()))
}
let as_vec = result?;
Value::list_from(as_vec)
},
_ => Err(Error::TypeError("List".to_string(), list))
}

View File

@@ -5,15 +5,14 @@ mod boolean;
mod database;
mod tuples;
use vm::types::{Value, CallableType};
use vm::errors::{Error, InterpreterResult as Result};
use vm::types::Value;
use vm::callables::CallableType;
use vm::representations::SymbolicExpression;
use vm::{Context,Environment};
use vm::InterpreterResult;
use vm::errors::Error;
use vm::eval;
use vm::{LocalContext, Environment, eval};
fn native_eq(args: &[Value]) -> InterpreterResult {
fn native_eq(args: &[Value]) -> Result<Value> {
// TODO: this currently uses the derived equality checks of Value,
// however, that's probably not how we want to implement equality
// checks on the ::ListTypes
@@ -26,14 +25,14 @@ fn native_eq(args: &[Value]) -> InterpreterResult {
}
}
fn native_begin(args: &[Value]) -> InterpreterResult {
fn native_begin(args: &[Value]) -> Result<Value> {
match args.last() {
Some(v) => Ok(v.clone()),
None => Ok(Value::Void)
}
}
fn special_if(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> InterpreterResult {
fn special_if(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result<Value> {
if !(args.len() == 2 || args.len() == 3) {
return Err(Error::InvalidArguments("Wrong number of arguments to if (expect 2 or 3)".to_string()))
}
@@ -55,7 +54,9 @@ fn special_if(args: &[SymbolicExpression], env: &mut Environment, context: &Cont
}
}
fn special_let(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> InterpreterResult {
fn special_let(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result<Value> {
use vm::is_reserved;
// (let ((x 1) (y 2)) (+ x y)) -> 3
// arg0 => binding list
// arg1 => body
@@ -63,37 +64,35 @@ fn special_let(args: &[SymbolicExpression], env: &mut Environment, context: &Con
return Err(Error::InvalidArguments("Wrong number of arguments to let (expect 2)".to_string()))
}
// create a new context.
let mut inner_context = context.extend();
let mut inner_context = context.extend()?;
if let SymbolicExpression::List(ref bindings) = args[0] {
let bind_result = bindings.iter().try_for_each(|binding| {
for binding in bindings.iter() {
if let SymbolicExpression::List(ref binding_exps) = *binding {
if binding_exps.len() != 2 {
Err(Error::Generic("Passed non 2-length list as binding in let expression".to_string()))
return Err(Error::Generic("Passed non 2-length list as binding in let expression".to_string()))
} else {
if let SymbolicExpression::Atom(ref var_name) = binding_exps[0] {
if is_reserved(var_name) {
return Err(Error::ReservedName(var_name.to_string()))
}
let value = eval(&binding_exps[1], env, context)?;
match inner_context.variables.insert((*var_name).clone(), value) {
Some(_val) => Err(Error::Generic("Multiply defined binding in let expression".to_string())),
_ => Ok(())
Some(_val) => return Err(Error::VariableDefinedMultipleTimes(var_name.to_string())),
_ => continue
}
} else {
Err(Error::Generic("Passed non-atomic variable name to let expression binding".to_string()))
return Err(Error::InvalidArguments("Passed non-atomic variable name to let expression binding".to_string()))
}
}
} else {
Err(Error::Generic("Passed non-list as binding in let expression.".to_string()))
return Err(Error::InvalidArguments("Passed non-list as binding in let expression.".to_string()))
}
});
// if there was an error during binding, return error.
if let Err(e) = bind_result {
Err(e)
} else {
// otherwise, evaluate the let-body
eval(&args[1], env, &inner_context)
}
// evaluate the let-body
eval(&args[1], env, &inner_context)
} else {
Err(Error::Generic("Passed non-list as second argument to let expression.".to_string()))
Err(Error::InvalidArguments("Passed non-list as second argument to let expression.".to_string()))
}
}

View File

@@ -1,11 +1,10 @@
use super::InterpreterResult;
use super::super::errors::Error;
use super::super::types::{Value, TupleData};
use super::super::representations::SymbolicExpression;
use super::super::representations::SymbolicExpression::{NamedParameter};
use super::super::{Context,Environment,eval};
use vm::errors::{Error, InterpreterResult as Result};
use vm::types::{Value};
use vm::representations::SymbolicExpression;
use vm::representations::SymbolicExpression::{NamedParameter};
use vm::{LocalContext, Environment, eval};
pub fn tuple_cons(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> InterpreterResult {
pub fn tuple_cons(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result<Value> {
// (tuple #arg-name value
// #arg-name value ...)
if args.len() % 2 != 0 {
@@ -13,9 +12,9 @@ pub fn tuple_cons(args: &[SymbolicExpression], env: &mut Environment, context: &
}
let num_pairs = args.len() / 2;
// turn list into pairs.
let eval_result: Result<Vec<_>, Error> = (0..num_pairs).map(|i| {
let eval_result: Result<Vec<_>> = (0..num_pairs).map(|i| {
let arg_name = match args[i * 2] {
NamedParameter(ref name) => Ok(name.as_str()),
NamedParameter(ref name) => Ok(name.clone()),
_ => Err(Error::InvalidArguments("Named arguments must be supplied as #name-arg".to_string()))
}?;
let value = eval(&args[i * 2 + 1], env, context)?;
@@ -24,11 +23,10 @@ pub fn tuple_cons(args: &[SymbolicExpression], env: &mut Environment, context: &
let evaled_pairs = eval_result?;
let tuple_data = TupleData::from_data(&evaled_pairs)?;
Ok(Value::Tuple(tuple_data))
Value::tuple_from_data(evaled_pairs)
}
pub fn tuple_get(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> InterpreterResult {
pub fn tuple_get(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result<Value> {
// (get arg-name (tuple ...))
// if the tuple argument is 'null, then return 'null.
// NOTE: a tuple field value itself may _never_ be 'null

View File

@@ -1,37 +1,33 @@
pub mod types;
pub mod representations;
pub mod parser;
pub mod contexts;
extern crate regex;
pub mod errors;
pub mod database;
pub mod types;
pub mod contracts;
mod representations;
mod parser;
mod contexts;
mod database;
mod functions;
mod variables;
mod callables;
mod tests;
use vm::types::{Value, CallableType};
use vm::types::Value;
use vm::callables::CallableType;
use vm::representations::SymbolicExpression;
use vm::contexts::{Context, Environment};
use vm::contexts::{GlobalContext, LocalContext, Environment};
use vm::database::ContractDatabase;
use vm::functions::define::DefineResult;
use vm::errors::Error;
use vm::errors::{Error, InterpreterResult as Result};
type InterpreterResult = Result<Value, Error>;
const MAX_CALL_STACK_DEPTH: usize = 256;
fn lookup_variable(name: &str, context: &Context, env: &Environment) -> InterpreterResult {
// first off, are we talking about a constant?
if name.starts_with(char::is_numeric) {
match i128::from_str_radix(name, 10) {
Ok(parsed) => Ok(Value::Int(parsed)),
Err(_e) => Err(Error::Generic("Failed to parse native int!".to_string()))
}
} else if name.starts_with('\'') {
// Quoted! true or false?
match &name as &str {
"'null" => Ok(Value::Void),
"'true" => Ok(Value::Bool(true)),
"'false" => Ok(Value::Bool(false)),
_ => Err(Error::NotImplemented)
}
fn lookup_variable(name: &str, context: &LocalContext, env: &Environment) -> Result<Value> {
if name.starts_with(char::is_numeric) || name.starts_with('\'') {
Err(Error::BadSymbolicRepresentation(format!("Unexpected variable name: {}", name)))
} else {
if let Some(value) = context.lookup_variable(name) {
Ok(value)
@@ -43,24 +39,24 @@ fn lookup_variable(name: &str, context: &Context, env: &Environment) -> Interpre
}
}
pub fn lookup_function<'a> (name: &str, env: &Environment)-> Result<CallableType<'a>, Error> {
// Aaron:: todo -- now that global_context is an immutable reference when it's used here,
// I am pretty sure we can return a reference with lifetime 'a here.
pub fn lookup_function<'a> (name: &str, env: &Environment)-> Result<CallableType<'a>> {
if let Some(result) = functions::lookup_reserved_functions(name) {
Ok(result)
} else {
if let Some(func) = env.global_context.lookup_function(name) {
Ok(CallableType::UserFunction(func))
} else {
Err(Error::Undefined(format!("No such function found in context: {}", name)))
}
let user_function = env.global_context.lookup_function(name).ok_or(
Error::Undefined(format!("No such function found in context: {}", name)))?;
Ok(CallableType::UserFunction(user_function))
}
}
pub fn apply(function: &CallableType, args: &[SymbolicExpression],
env: &mut Environment, context: &Context) -> InterpreterResult {
env: &mut Environment, context: &LocalContext) -> Result<Value> {
if let CallableType::SpecialFunction(function) = function {
function(&args, env, context)
} else {
let eval_tried: Result<Vec<Value>, errors::Error> =
let eval_tried: Result<Vec<Value>> =
args.iter().map(|x| eval(x, env, context)).collect();
match eval_tried {
Ok(evaluated_args) => {
@@ -72,6 +68,8 @@ pub fn apply(function: &CallableType, args: &[SymbolicExpression],
let identifier = function.get_identifier();
if env.call_stack.contains(&identifier) {
Err(Error::RecursionDetected)
} else if env.call_stack.depth() >= MAX_CALL_STACK_DEPTH {
Err(Error::MaxStackDepthReached)
} else {
env.call_stack.insert(&identifier);
let resp = function.apply(&evaluated_args, env);
@@ -87,7 +85,7 @@ pub fn apply(function: &CallableType, args: &[SymbolicExpression],
}
}
pub fn eval <'a> (exp: &SymbolicExpression, env: &'a mut Environment, context: &Context) -> InterpreterResult {
pub fn eval <'a> (exp: &SymbolicExpression, env: &'a mut Environment, context: &LocalContext) -> Result<Value> {
match exp {
&SymbolicExpression::AtomValue(ref value) => Ok(value.clone()),
&SymbolicExpression::Atom(ref value) => lookup_variable(&value, context, env),
@@ -110,33 +108,47 @@ pub fn eval <'a> (exp: &SymbolicExpression, env: &'a mut Environment, context: &
}
pub fn is_reserved(name: &str) -> bool {
if let Some(_result) = functions::lookup_reserved_functions(name) {
true
} else if variables::is_reserved_variable(name) {
true
} else {
false
}
}
/* This function evaluates a list of expressions, sharing a global context.
* It returns the final evaluated result.
*/
pub fn eval_all(expressions: &[SymbolicExpression],
contract_db: Option<Box<database::ContractDatabase>>) -> InterpreterResult {
let db_instance = match contract_db {
Some(db) => db,
None => Box::new(database::MemoryContractDatabase::new())
};
let mut env = Environment::new(db_instance);
fn eval_all(expressions: &[SymbolicExpression],
database: &mut ContractDatabase,
global_context: &mut GlobalContext) -> Result<Value> {
let mut last_executed = None;
let context = Context::new();
let context = LocalContext::new();
for exp in expressions {
let try_define = functions::define::evaluate_define(exp, &mut env)?;
let try_define = {
let mut env = Environment::new(
global_context, database);
functions::define::evaluate_define(exp, &mut env)
}?;
match try_define {
DefineResult::Variable(name, value) => {
env.global_context.variables.insert(name, value);
global_context.variables.insert(name, value);
},
DefineResult::Function(name, value) => {
env.global_context.functions.insert(name, Box::new(value));
global_context.functions.insert(name, value);
},
DefineResult::Map(name, key_type, value_type) => {
env.database.create_map(&name, key_type, value_type);
database.create_map(&name, key_type, value_type);
},
DefineResult::NoDefine => {
// not a define function, evaluate normally.
let mut env = Environment::new(
global_context, database);
last_executed = Some(eval(exp, &mut env, &context));
}
}
@@ -145,11 +157,57 @@ pub fn eval_all(expressions: &[SymbolicExpression],
if let Some(result) = last_executed {
result
} else {
Err(Error::Generic("Failed to get response from eval()".to_string()))
Ok(Value::Void)
}
}
pub fn execute(program: &str) -> InterpreterResult {
/* Run provided program in a brand new environment, with a transient, empty
* database.
*/
pub fn execute(program: &str) -> Result<Value> {
let mut global_context = GlobalContext::new();
let mut db_instance = Box::new(database::MemoryContractDatabase::new());
let parsed = parser::parse(program)?;
eval_all(&parsed, None)
eval_all(&parsed, &mut *db_instance, &mut global_context)
}
mod test {
use vm::database::MemoryContractDatabase;
use vm::errors::Error;
use vm::{Value, LocalContext, GlobalContext, Environment, SymbolicExpression};
use vm::callables::PrivateFunction;
use vm::eval;
#[test]
fn test_simple_user_function() {
//
// test program:
// (define (do_work x) (+ 5 x))
// (define a 59)
// (do_work a)
//
let content = [ SymbolicExpression::List(
Box::new([ SymbolicExpression::Atom("do_work".to_string()),
SymbolicExpression::Atom("a".to_string()) ])) ];
let func_body = SymbolicExpression::List(
Box::new([ SymbolicExpression::Atom("+".to_string()),
SymbolicExpression::AtomValue(Value::Int(5)),
SymbolicExpression::Atom("x".to_string())]));
let func_args = vec!["x".to_string()];
let user_function = PrivateFunction::new(func_args, func_body);
let context = LocalContext::new();
let mut global_context = GlobalContext::new();
let mut db = MemoryContractDatabase::new();
global_context.variables.insert("a".to_string(), Value::Int(59));
global_context.functions.insert("do_work".to_string(), user_function);
let mut env = Environment::new(&global_context, &mut db);
assert_eq!(Ok(Value::Int(64)), eval(&content[0], &mut env, &context));
}
}

View File

@@ -1,103 +1,172 @@
use vm::errors::Error;
use regex::{Regex, Captures};
use address::c32::c32_address_decode;
use vm::errors::{Error, InterpreterResult as Result};
use vm::representations::SymbolicExpression;
use vm::types::Value;
#[derive(Debug)]
pub enum LexItem {
LeftParen,
RightParen,
NameParameter(String),
Atom(String)
NamedParameter(String),
LiteralValue(Value),
Variable(String),
Whitespace
}
fn finish_atom(current: &mut Option<String>) -> Option<LexItem> {
let resp = match current {
&mut None => {
None
},
&mut Some(ref value) => {
if value.starts_with('#') {
Some(LexItem::NameParameter(value[1..].to_string()))
} else {
Some(LexItem::Atom(value.clone()))
}
},
};
*current = None;
resp
#[derive(Debug)]
enum TokenType {
LParens, RParens, Whitespace,
StringLiteral, HexStringLiteral,
IntLiteral, QuoteLiteral,
Variable, NamedParameter, PrincipalLiteral
}
pub fn lex(input: &str) -> Result<Vec<LexItem>, Error> {
struct LexMatcher {
matcher: Regex,
handler: TokenType
}
impl LexMatcher {
fn new(regex_str: &str, handles: TokenType) -> LexMatcher {
LexMatcher {
matcher: Regex::new(&format!("^{}", regex_str)).unwrap(),
handler: handles
}
}
}
fn get_value_or_err(input: &str, captures: Captures) -> Result<String> {
let matched = captures.name("value").ok_or(
Error::ParseError("Failed to capture value from input".to_string()))?;
Ok(input[matched.start()..matched.end()].to_string())
}
pub fn lex(input: &str) -> Result<Vec<LexItem>> {
// Aaron: I'd like these to be static, but that'd require using
// lazy_static (or just hand implementing that), and I'm not convinced
// it's worth either (1) an extern macro, or (2) the complexity of hand implementing.
let lex_matchers: &[LexMatcher] = &[
LexMatcher::new(r##""(?P<value>((\\")|([[:print:]&&[^"\n\r\t]]))*)""##, TokenType::StringLiteral),
LexMatcher::new(";;[[:print:]&&[^\n\r\t]]*", TokenType::Whitespace), // ;; comments.
LexMatcher::new("[(]", TokenType::LParens),
LexMatcher::new("[)]", TokenType::RParens),
LexMatcher::new("[ \n\t\r]+", TokenType::Whitespace),
LexMatcher::new("(?P<value>[[:digit:]]+)", TokenType::IntLiteral),
LexMatcher::new("'(?P<value>true|false|null)", TokenType::QuoteLiteral),
LexMatcher::new("'(?P<value>[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{40,41})", TokenType::PrincipalLiteral),
LexMatcher::new("0x(?P<value>[[:xdigit:]])", TokenType::HexStringLiteral),
LexMatcher::new("#(?P<value>([[:word:]]|[-#!?+<>=/*])+)", TokenType::NamedParameter),
LexMatcher::new("(?P<value>([[:word:]]|[-#!?+<>=/*])+)", TokenType::Variable),
];
let mut result = Vec::new();
let current = &mut None;
for c in input.chars() {
match c {
'(' => {
if let Some(value) = finish_atom(current) {
result.push(value);
}
result.push(LexItem::LeftParen)
},
')' => {
if let Some(value) = finish_atom(current) {
result.push(value);
}
result.push(LexItem::RightParen)
},
'#' => {
if let Some(ref _value) = *current {
return Err(Error::ParseError("You may not use # in the middle of an atom.".to_string()))
} else {
*current = Some(c.to_string())
}
},
' '|'\t'|'\n'|'\r' => {
if let Some(value) = finish_atom(current) {
result.push(value);
}
},
_ => {
match *current {
None => {
*current = Some(c.to_string());
let mut munch_index = 0;
let mut did_match = true;
while did_match && munch_index < input.len() {
did_match = false;
let current_slice = &input[munch_index..];
for matcher in lex_matchers.iter() {
if let Some(captures) = matcher.matcher.captures(current_slice) {
let whole_match = captures.get(0).unwrap();
assert_eq!(whole_match.start(), 0);
munch_index += whole_match.end();
let token = match matcher.handler {
TokenType::LParens => Ok(LexItem::LeftParen),
TokenType::RParens => Ok(LexItem::RightParen),
TokenType::Whitespace => Ok(LexItem::Whitespace),
TokenType::NamedParameter => {
let value = get_value_or_err(current_slice, captures)?;
if value.contains("#") {
return Err(Error::ParseError(format!("Illegal variable name: '{}'", value)))
}
Ok(LexItem::NamedParameter(value))
},
Some(ref mut value) => {
value.push(c);
TokenType::Variable => {
let value = get_value_or_err(current_slice, captures)?;
if value.contains("#") {
return Err(Error::ParseError(format!("Illegal variable name: '{}'", value)))
}
Ok(LexItem::Variable(value))
},
TokenType::QuoteLiteral => {
let str_value = get_value_or_err(current_slice, captures)?;
let value = match str_value.as_str() {
"null" => Ok(Value::Void),
"true" => Ok(Value::Bool(true)),
"false" => Ok(Value::Bool(false)),
_ => Err(Error::ParseError(format!("Unknown 'quoted value '{}'", str_value)))
}?;
Ok(LexItem::LiteralValue(value))
},
TokenType::IntLiteral => {
let str_value = get_value_or_err(current_slice, captures)?;
let value = match i128::from_str_radix(&str_value, 10) {
Ok(parsed) => Ok(Value::Int(parsed)),
Err(_e) => Err(Error::ParseError(format!("Failed to parse int literal '{}'", str_value)))
}?;
Ok(LexItem::LiteralValue(value))
},
TokenType::PrincipalLiteral => {
let str_value = get_value_or_err(current_slice, captures)?;
let (version, data) = c32_address_decode(&str_value)
.map_err(|x| { Error::ParseError(format!("Invalid principal literal: {}", x)) })?;
if data.len() != 20 {
Err(Error::ParseError("Invalid principal literal: Expected 20 data bytes.".to_string()))
} else {
let mut fixed_data = [0; 20];
fixed_data.copy_from_slice(&data[..20]);
Ok(LexItem::LiteralValue(Value::Principal(version, fixed_data)))
}
},
TokenType::HexStringLiteral => {
panic!("Not implemented")
},
TokenType::StringLiteral => {
let str_value = get_value_or_err(current_slice, captures)?;
let quote_unescaped = str_value.replace("\\\"","\"");
let slash_unescaped = quote_unescaped.replace("\\\\","\\");
let byte_vec = slash_unescaped.as_bytes().to_vec();
let value = Value::buff_from(byte_vec)?;
Ok(LexItem::LiteralValue(value))
}
}
}?;
result.push(token);
did_match = true;
break;
}
};
}
}
if let Some(value) = finish_atom(current) {
result.push(value);
if munch_index == input.len() {
Ok(result)
} else {
Err(Error::ParseError(format!("Failed to lex input remainder: {}", &input[munch_index..])))
}
Ok(result)
}
pub fn parse_lexed(input: &Vec<LexItem>) -> Result<Vec<SymbolicExpression>, Error> {
pub fn parse_lexed(mut input: Vec<LexItem>) -> Result<Vec<SymbolicExpression>> {
let mut parse_stack = Vec::new();
let mut output_list = Vec::new();
// TODO: we don't need to be cloning here, we can just seize item ownership from the
// input iterator by popping.
let _result = input.iter().try_for_each(|item| {
match *item {
for item in input.drain(..) {
match item {
LexItem::LeftParen => {
// start new list.
let new_list = Vec::new();
parse_stack.push(new_list);
Ok(())
},
LexItem::NameParameter(ref value) => {
let symbol_out = SymbolicExpression::NamedParameter(value.clone());
LexItem::NamedParameter(value) => {
let symbol_out = SymbolicExpression::NamedParameter(value);
match parse_stack.last_mut() {
None => output_list.push(symbol_out),
Some(ref mut list) => list.push(symbol_out)
};
Ok(())
},
LexItem::RightParen => {
// end current list.
@@ -112,20 +181,25 @@ pub fn parse_lexed(input: &Vec<LexItem>) -> Result<Vec<SymbolicExpression>, Erro
list.push(expression);
}
};
Ok(())
} else {
Err(Error::ParseError("Tried to close list which isn't open.".to_string()))
return Err(Error::ParseError("Tried to close list which isn't open.".to_string()))
}
},
LexItem::Atom(ref value) => {
LexItem::Variable(value) => {
match parse_stack.last_mut() {
None => output_list.push(SymbolicExpression::Atom(value.clone())),
Some(ref mut list) => list.push(SymbolicExpression::Atom(value.clone()))
None => output_list.push(SymbolicExpression::Atom(value)),
Some(ref mut list) => list.push(SymbolicExpression::Atom(value))
};
Ok(())
}
}
})?;
},
LexItem::LiteralValue(value) => {
match parse_stack.last_mut() {
None => output_list.push(SymbolicExpression::AtomValue(value)),
Some(ref mut list) => list.push(SymbolicExpression::AtomValue(value))
};
},
LexItem::Whitespace => ()
};
}
// check unfinished stack:
if parse_stack.len() > 0 {
@@ -135,7 +209,90 @@ pub fn parse_lexed(input: &Vec<LexItem>) -> Result<Vec<SymbolicExpression>, Erro
}
}
pub fn parse(input: &str) -> Result<Vec<SymbolicExpression>, Error> {
pub fn parse(input: &str) -> Result<Vec<SymbolicExpression>> {
let lexed = lex(input)?;
parse_lexed(&lexed)
parse_lexed(lexed)
}
mod test {
use vm::representations::SymbolicExpression;
use vm::errors::Error;
use vm::types::Value;
use vm::parser;
#[test]
fn test_parse_let_expression() {
let input = "z (let((x 1) (y 2))
(+ x ;; \"comments section?\"
;; this is also a comment!
(let ((x 3)) ;; more commentary
(+ x y))
x)) x y";
let program = vec![
SymbolicExpression::Atom("z".to_string()),
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("let".to_string()),
SymbolicExpression::List(Box::new([
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("x".to_string()),
SymbolicExpression::AtomValue(Value::Int(1))])),
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("y".to_string()),
SymbolicExpression::AtomValue(Value::Int(2))]))])),
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("+".to_string()),
SymbolicExpression::Atom("x".to_string()),
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("let".to_string()),
SymbolicExpression::List(Box::new([
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("x".to_string()),
SymbolicExpression::AtomValue(Value::Int(3))]))])),
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("+".to_string()),
SymbolicExpression::Atom("x".to_string()),
SymbolicExpression::Atom("y".to_string())]))])),
SymbolicExpression::Atom("x".to_string())]))])),
SymbolicExpression::Atom("x".to_string()),
SymbolicExpression::Atom("y".to_string()),
];
let parsed = parser::parse(&input);
assert_eq!(Ok(program), parsed, "Should match expected symbolic expression");
}
#[test]
fn test_parse_failures() {
let too_much_closure = "(let ((x 1) (y 2))))";
let not_enough_closure = "(let ((x 1) (y 2))";
let middle_hash = "(let ((x 1) (y#not 2)) x)";
let unicode = "(let ((x🎶 1)) (eq x🎶 1))";
assert!(match parser::parse(&too_much_closure) {
Err(Error::ParseError(_)) => true,
_ => false
}, "Should have failed to parse with too many right parens");
assert!(match parser::parse(&not_enough_closure) {
Err(Error::ParseError(_)) => true,
_ => false
}, "Should have failed to parse with too few right parens");
let x = parser::parse(&middle_hash);
assert!(match x {
Err(Error::ParseError(_)) => true,
_ => {
println!("Expected parser error. Unexpected value is:\n {:?}", x);
false
}
}, "Should have failed to parse with a middle hash");
assert!(match parser::parse(&unicode) {
Err(Error::ParseError(_)) => true,
_ => false
}, "Should have failed to parse a unicode variable name");
}
}

View File

@@ -12,7 +12,3 @@ pub enum SymbolicExpression {
List(Box<[SymbolicExpression]>),
NamedParameter(String)
}
pub struct Contract {
pub content: Box<[SymbolicExpression]>
}

95
src/vm/tests/contracts.rs Normal file
View File

@@ -0,0 +1,95 @@
use vm::errors::Error;
use vm::types::{Value};
use vm::representations::SymbolicExpression;
use vm::contracts::Contract;
fn symbols_from_values(mut vec: Vec<Value>) -> Vec<SymbolicExpression> {
vec.drain(..).map(|value| SymbolicExpression::AtomValue(value)).collect()
}
#[test]
fn test_factorial_contract() {
let contract_defn =
"(define-map factorials ((id int)) ((current int) (index int)))
(define (init-factorial id factorial)
(insert-entry! factorials (tuple #id id) (tuple #current 1 #index factorial)))
(define-public (compute (id int))
(let ((entry (fetch-entry factorials (tuple #id id))))
(if (eq? entry 'null)
0
(let ((current (get current entry))
(index (get index entry)))
(if (<= index 1)
current
(begin
(set-entry! factorials (tuple #id id)
(tuple #current (* current index)
#index (- index 1)))
0))))))
(begin (init-factorial 1337 3)
(init-factorial 8008 5)
'null)
";
let mut contract = Contract::make_in_memory_contract(contract_defn).unwrap();
let tx_name = "compute";
let arguments_to_test = [symbols_from_values(vec![Value::Int(1337)]),
symbols_from_values(vec![Value::Int(1337)]),
symbols_from_values(vec![Value::Int(1337)]),
symbols_from_values(vec![Value::Int(1337)]),
symbols_from_values(vec![Value::Int(1337)]),
symbols_from_values(vec![Value::Int(8008)]),
symbols_from_values(vec![Value::Int(8008)]),
symbols_from_values(vec![Value::Int(8008)]),
symbols_from_values(vec![Value::Int(8008)]),
symbols_from_values(vec![Value::Int(8008)]),
symbols_from_values(vec![Value::Int(8008)])];
let expected = vec![
Value::Int(0),
Value::Int(0),
Value::Int(6),
Value::Int(6),
Value::Int(6),
Value::Int(0),
Value::Int(0),
Value::Int(0),
Value::Int(0),
Value::Int(120),
Value::Int(120),
];
let sender = Value::Principal(1, [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]);
arguments_to_test.iter().zip(expected.iter())
.for_each(|(arguments, expectation)| assert_eq!(Ok(expectation.clone()),
contract.execute_transaction(
&sender,
&tx_name,
arguments)));
let err_result = contract.execute_transaction(&sender, &"init-factorial",
&symbols_from_values(vec![Value::Int(9000),
Value::Int(15)]));
match err_result {
Err(Error::Undefined(_)) => {},
_ => {
println!("{:?}", err_result);
assert!(false, "Attempt to call init-factorial should fail!")
}
}
let err_result = contract.execute_transaction(&sender, &"compute",
&symbols_from_values(vec![Value::Void]));
match err_result {
Err(Error::TypeError(_, _)) => {},
_ => {
println!("{:?}", err_result);
assert!(false, "Attempt to call compute with void type should fail!")
}
}
}

View File

@@ -1,4 +1,6 @@
use vm::errors::Error;
use vm::types::{Value, TypeSignature, AtomTypeIdentifier};
use vm::execute;
#[test]
@@ -32,23 +34,73 @@ fn test_simple_tea_shop() {
(consume 3))
";
let expected = Value::List(
vec![
Value::Bool(true),
Value::Bool(true),
Value::Bool(true),
Value::Bool(true),
Value::Bool(true),
Value::Bool(true),
Value::Bool(false),
Value::Bool(true),
Value::Bool(true),
Value::Bool(false),
Value::Bool(false),
Value::Bool(false)],
TypeSignature::new(AtomTypeIdentifier::BoolType, 1));
let expected = Value::list_from(vec![
Value::Bool(true),
Value::Bool(true),
Value::Bool(true),
Value::Bool(true),
Value::Bool(true),
Value::Bool(true),
Value::Bool(false),
Value::Bool(true),
Value::Bool(true),
Value::Bool(false),
Value::Bool(false),
Value::Bool(false)],
);
assert_eq!(Ok(expected), execute(test1));
assert_eq!(expected, execute(test1));
}
#[test]
fn test_factorial_contract() {
let test1 =
"(define-map factorials ((id int)) ((current int) (index int)))
(define (init-factorial id factorial)
(insert-entry! factorials (tuple #id id) (tuple #current 1 #index factorial)))
(define (compute id)
(let ((entry (fetch-entry factorials (tuple #id id))))
(if (eq? entry 'null)
0
(let ((current (get current entry))
(index (get index entry)))
(if (<= index 1)
current
(begin
(set-entry! factorials (tuple #id id)
(tuple #current (* current index)
#index (- index 1)))
0))))))
(init-factorial 1337 3)
(init-factorial 8008 5)
(list (compute 1337)
(compute 1337)
(compute 1337)
(compute 1337)
(compute 1337)
(compute 8008)
(compute 8008)
(compute 8008)
(compute 8008)
(compute 8008)
(compute 8008))
";
let expected = Value::list_from(vec![
Value::Int(0),
Value::Int(0),
Value::Int(6),
Value::Int(6),
Value::Int(6),
Value::Int(0),
Value::Int(0),
Value::Int(0),
Value::Int(0),
Value::Int(120),
Value::Int(120),
]);
assert_eq!(expected, execute(test1));
}
#[test]
@@ -78,20 +130,199 @@ fn silly_naming_system() {
(who-owns? 1))
";
let expected = Value::List(
vec![
Value::Int(1),
Value::Int(0),
Value::Int(1),
Value::Int(0),
Value::Int(0),
Value::Int(1),
Value::Int(0),
Value::Int(1),
Value::Int(0),
Value::Int(-1),
],
TypeSignature::new(AtomTypeIdentifier::IntType, 1));
assert_eq!(Ok(expected), execute(test1));
let expected = Value::list_from(vec![
Value::Int(1),
Value::Int(0),
Value::Int(1),
Value::Int(0),
Value::Int(0),
Value::Int(1),
Value::Int(0),
Value::Int(1),
Value::Int(0),
Value::Int(-1),
]);
assert_eq!(expected, execute(test1));
}
#[test]
fn datamap_errors() {
let tests = [
"(fetch-entry non-existent (tuple #name 1))",
"(delete-entry! non-existent (tuple #name 1))",
];
let expected = [
Err(Error::Undefined("No such map named: non-existent".to_string())),
Err(Error::Undefined("No such map named: non-existent".to_string())),
];
for (program, expectation) in tests.iter().zip(expected.iter()) {
assert_eq!(*expectation, execute(program));
}
}
#[test]
fn lists_system() {
let test1 =
"(define-map lists ((name int)) ((contents (list 5 1 int))))
(define (add-list name content)
(insert-entry! lists (tuple #name name)
(tuple #contents content)))
(define (get-list name)
(get contents (fetch-entry lists (tuple #name name))))
(add-list 0 (list 1 2 3 4 5))
(add-list 1 (list 1 2 3))
(list (get-list 0)
(get-list 1))
";
let mut test_list_too_big = test1.to_string();
test_list_too_big.push_str("(add-list 2 (list 1 2 3 4 5 6))");
let mut test_bad_tuple_1 = test1.to_string();
test_bad_tuple_1.push_str("(insert-entry! lists (tuple #name 1) (tuple #contentious (list 1 2 6)))");
let mut test_bad_tuple_2 = test1.to_string();
test_bad_tuple_2.push_str("(insert-entry! lists (tuple #name 1) (tuple #contents (list 1 2 6) #discontents 1))");
let mut test_bad_tuple_3 = test1.to_string();
test_bad_tuple_3.push_str("(insert-entry! lists (tuple #name 1) (tuple #contents (list 'false 'true 'false)))");
let mut test_bad_tuple_4 = test1.to_string();
test_bad_tuple_4.push_str("(insert-entry! lists (tuple #name (list 1)) (tuple #contents (list 1 2 3)))");
let expected = || {
let list1 = Value::list_from(vec![
Value::Int(1),
Value::Int(2),
Value::Int(3),
Value::Int(4),
Value::Int(5)])?;
let list2 = Value::list_from(vec![
Value::Int(1),
Value::Int(2),
Value::Int(3)])?;
Value::list_from(vec![list1, list2])
};
assert_eq!(expected(), execute(test1));
for test in [test_list_too_big, test_bad_tuple_1, test_bad_tuple_2,
test_bad_tuple_3, test_bad_tuple_4].iter() {
let expected_type_error = match execute(test) {
Err(Error::TypeError(_,_)) => true,
_ => {
println!("{:?}", execute(test));
false
}
};
assert!(expected_type_error);
}
}
#[test]
fn tuples_system() {
let test1 =
"(define-map tuples ((name int))
((contents (tuple ((name (buff 5))
(owner (buff 5)))))))
(define (add-tuple name content)
(insert-entry! tuples (tuple #name name)
(tuple #contents
(tuple #name content
#owner content))))
(define (get-tuple name)
(get name (get contents (fetch-entry tuples (tuple #name name)))))
(add-tuple 0 \"abcde\")
(add-tuple 1 \"abcd\")
(list (get-tuple 0)
(get-tuple 1))
";
let mut test_list_too_big = test1.to_string();
test_list_too_big.push_str("(add-tuple 2 \"abcdef\")");
let mut test_bad_tuple_1 = test1.to_string();
test_bad_tuple_1.push_str("(insert-entry! tuples (tuple #name 1) (tuple #contents (tuple #name \"abcde\" #owner \"abcdef\")))");
let expected = || {
let buff1 = Value::buff_from("abcde".to_string().into_bytes())?;
let buff2 = Value::buff_from("abcd".to_string().into_bytes())?;
Value::list_from(vec![buff1, buff2])
};
assert_eq!(expected(), execute(test1));
for test in [test_list_too_big, test_bad_tuple_1].iter() {
let expected_type_error = match execute(test) {
Err(Error::TypeError(_,_)) => true,
_ => {
println!("{:?}", execute(test));
false
}
};
assert!(expected_type_error);
}
}
#[test]
fn bad_define_maps() {
let test_list_pairs = [
"(define-map lists ((name int)) ((contents int bool)))",
"(define-map lists ((name int)) (contents bool))",
"(define-map lists ((#name int)) (contents bool))",
"(define-map lists ((name int)) contents)"];
let test_define_args = [
"(define-map (lists) ((name #int)) contents)",
"(define-map lists ((name #int)) contents 5)"];
let test_bad_type = [
"(define-map lists ((name int)) ((contents (list 5 0 int))))",
"(define-map lists ((name #int)) (contents bool))"];
for test in test_list_pairs.iter() {
println!("Test: {:?}", test);
assert_eq!(Err(Error::ExpectedListPairs), execute(test));
}
for test in test_define_args.iter() {
assert!(match execute(test) {
Err(Error::InvalidArguments(_)) => true,
_ => false
})
}
for test in test_bad_type.iter() {
assert_eq!(Err(Error::InvalidTypeDescription), execute(test));
}
}
#[test]
fn bad_tuples() {
let tests = ["(tuple #name 1 #name 3)",
"(tuple #name 'null)",
"(tuple name 1)",
"(tuple #name 1 #blame)",
"(get value (tuple #name 1))",
"(get name five (tuple #name 1))",
"(get 1234 (tuple #name 1))"];
for test in tests.iter() {
let outcome = execute(test);
match outcome {
Err(Error::InvalidArguments(_)) => continue,
_ => {
println!("Expected InvalidArguments Error, but found {:?}", outcome);
assert!(false)
}
}
}
}

View File

@@ -12,6 +12,56 @@ fn test_defines() {
(f 3 1)";
assert_eq!(Ok(Value::Int(29)), execute(&tests));
let tests =
"1";
assert_eq!(Ok(Value::Int(1)), execute(&tests));
}
#[test]
fn test_bad_define_names() {
let test0 =
"(define tx-sender 1)
(+ tx-sender tx-sender)";
let test1 =
"(define * 1)
(+ * *)";
let test2 =
"(define 1 1)
(+ 1 1)";
let test3 =
"(define foo 1)
(define foo 2)
(+ foo foo)";
assert_eq!(Err(Error::ReservedName("tx-sender".to_string())), execute(&test0));
assert_eq!(Err(Error::ReservedName("*".to_string())), execute(&test1));
assert_eq!(Err(Error::InvalidArguments("Illegal operation: attempted to re-define a value type.".to_string())),
execute(&test2));
assert_eq!(Err(Error::VariableDefinedMultipleTimes("foo".to_string())),
execute(&test3));
}
#[test]
fn test_stack_depth() {
let mut function_defines = Vec::new();
function_defines.push("(define (foo-0 x) (+ 1 x))".to_string());
for i in 1..257 {
function_defines.push(
format!("(define (foo-{} x) (foo-{} (+ 1 x)))",
i, i-1));
}
function_defines.push(
format!("(foo-255 1)"));
let test0 = function_defines.join("\n");
function_defines.push(
format!("(foo-256 2)"));
let test1 = function_defines.join("\n");
assert_eq!(Ok(Value::Int(257)), execute(&test0));
assert_eq!(Err(Error::MaxStackDepthReached), execute(&test1));
}
#[test]
@@ -26,10 +76,34 @@ fn test_recursive_panic() {
assert_eq!(Err(Error::RecursionDetected), execute(&tests));
}
#[test]
fn test_bad_variables() {
let test0 = "(+ a 1)";
let expected = Err(Error::Undefined("No such variable found in context: a".to_string()));
assert_eq!(expected, execute(&test0));
let test1 = "(foo 2 1)";
let expected = Err(Error::Undefined("No such function found in context: foo".to_string()));
assert_eq!(expected, execute(&test1));
let test2 = "((lambda (x y) 1) 2 1)";
let expected = Err(Error::TryEvalToFunction);
assert_eq!(expected, execute(&test2));
let test3 = "#foo";
let expected = Err(Error::InvalidArguments("Cannot eval a named parameter".to_string()));
assert_eq!(expected, execute(&test3));
let test4 = "()";
let expected = Ok(Value::Void);
assert_eq!(expected, execute(&test4));
}
#[test]
fn test_define_parse_panic() {
let tests = "(define () 1)";
let expected = Err(Error::InvalidArguments("Must supply atleast a name argument to define a function".to_string()));
assert_eq!(expected, execute(&tests));
}

View File

@@ -1,4 +1,4 @@
use vm::types::{Value, TypeSignature, AtomTypeIdentifier};
use vm::types::{Value, TypeSignature};
use vm::execute;
use vm::errors::Error;
@@ -9,22 +9,60 @@ fn test_simple_map() {
"(define (square x) (* x x))
(map square (list 1 2 3 4))";
let expected = Value::List(
vec![
Value::Int(1),
Value::Int(4),
Value::Int(9),
Value::Int(16)],
TypeSignature::new(AtomTypeIdentifier::IntType, 1));
let expected = Value::list_from(vec![
Value::Int(1),
Value::Int(4),
Value::Int(9),
Value::Int(16)]);
assert_eq!(Ok(expected.clone()), execute(test1));
assert_eq!(expected, execute(test1));
// let's test lists of lists.
let test2 = "(define (multiply x acc) (* x acc))
(define (multiply-all x) (fold multiply x 1))
(map multiply-all (list (list 1 1 1) (list 2 2 1) (list 3 3) (list 2 2 2 2)))";
assert_eq!(Ok(expected), execute(test2));
assert_eq!(expected, execute(test2));
// let's test empty lists.
let test2 = "(define (double x) (* x 2))
(map double (list))";
assert_eq!(Value::list_from(vec![]), execute(test2));
}
#[test]
fn test_list_tuple_admission() {
let test =
"(define (bufferize x) (if (eq? x 1) \"abc\" \"ab\"))
(define (tuplize x)
(tuple #value (bufferize x)))
(map tuplize (list 0 1 0 1 0 1))";
let expected_type =
"(list (tuple #value \"012\")
(tuple #value \"012\")
(tuple #value \"012\")
(tuple #value \"012\")
(tuple #value \"012\")
(tuple #value \"012\"))";
let not_expected_type =
"(list (tuple #value \"01\")
(tuple #value \"02\")
(tuple #value \"12\")
(tuple #value \"12\")
(tuple #value \"01\")
(tuple #value \"02\"))";
let result_type = TypeSignature::type_of(&execute(test).unwrap());
let expected_type = TypeSignature::type_of(&execute(expected_type).unwrap());
let testing_value = &execute(not_expected_type).unwrap();
let not_expected_type = TypeSignature::type_of(testing_value);
assert_eq!(expected_type, result_type);
assert!(not_expected_type != result_type);
assert!(result_type.admits(&testing_value));
}
#[test]
@@ -41,13 +79,36 @@ fn test_simple_folds() {
#[test]
fn test_construct_bad_list() {
let test1 = "(list 1 2 3 'true)";
assert_eq!(Err(Error::InvalidArguments("List must be composed of a single type".to_string())),
execute(test1));
assert!(
match execute(test1) {
Err(Error::BadTypeConstruction) => true,
_ => false
});
let test2 = "(define (bad-function x) (if (eq? x 1) 'true x))
(map bad-function (list 0 1 2 3))";
assert_eq!(Err(Error::InvalidArguments("Results of map must all be of a single type".to_string())),
execute(test2));
assert!(
match execute(test2) {
Err(Error::BadTypeConstruction) => true,
_ => false
});
let bad_2d_list = "(list (list 1 2 3) (list 'true 'false 'true))";
let bad_high_order_list = "(list (list 1 2 3) (list (list 1 2 3)))";
let expected_err_1 = match execute(bad_2d_list) {
Err(Error::BadTypeConstruction) => true,
_ => false
};
assert!(expected_err_1);
let expected_err_2 = match execute(bad_high_order_list) {
Err(Error::BadTypeConstruction) => true,
_ => false
};
assert!(expected_err_2);
}
#[test]

View File

@@ -1,5 +1,5 @@
mod lists;
mod defines;
mod parser_lexer;
mod simple_apply_eval;
mod datamaps;
mod contracts;

View File

@@ -1,60 +0,0 @@
use vm::representations::SymbolicExpression;
use vm::parser;
#[test]
fn test_parse_let_expression() {
let input = "z (let ((x 1) (y 2))
(+ x
(let ((x 3))
(+ x y))
x)) x y";
let program = vec![
SymbolicExpression::Atom("z".to_string()),
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("let".to_string()),
SymbolicExpression::List(Box::new([
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("x".to_string()),
SymbolicExpression::Atom("1".to_string())])),
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("y".to_string()),
SymbolicExpression::Atom("2".to_string())]))])),
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("+".to_string()),
SymbolicExpression::Atom("x".to_string()),
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("let".to_string()),
SymbolicExpression::List(Box::new([
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("x".to_string()),
SymbolicExpression::Atom("3".to_string())]))])),
SymbolicExpression::List(Box::new([
SymbolicExpression::Atom("+".to_string()),
SymbolicExpression::Atom("x".to_string()),
SymbolicExpression::Atom("y".to_string())]))])),
SymbolicExpression::Atom("x".to_string())]))])),
SymbolicExpression::Atom("x".to_string()),
SymbolicExpression::Atom("y".to_string()),
];
if let Ok(parsed) = parser::parse(&input) {
assert_eq!(program, parsed, "Should match expected symbolic expression");
} else {
assert!(false, "Failed to lex and parse input");
}
}
#[test]
fn test_parse_failures() {
let too_much_closure = "(let ((x 1) (y 2))))";
let not_enough_closure = "(let ((x 1) (y 2))";
match parser::parse(&too_much_closure) {
Ok(_parsed) => assert!(false, "Should have failed to parse with too many right parens"),
Err(_s) => assert!(true, "Should have failed to parse with too many right parens")
}
match parser::parse(&not_enough_closure) {
Ok(_parsed) => assert!(false, "Should have failed to parse with too few right parens"),
Err(_s) => assert!(true, "Should have failed to parse with too few right parens")
}
}

View File

@@ -1,41 +1,10 @@
use vm::eval;
use vm::{eval, execute};
use vm::database::MemoryContractDatabase;
use vm::errors::Error;
use vm::contexts::{Context, Environment};
use vm::types::{Value, DefinedFunction};
use vm::representations::SymbolicExpression;
use vm::{Value, LocalContext, GlobalContext, Environment};
use vm::callables::PrivateFunction;
use vm::parser::parse;
#[test]
fn test_simple_user_function() {
//
// test program:
// (define (do_work x) (+ 5 x))
// (define a 59)
// (do_work a)
//
let content = [ SymbolicExpression::List(
Box::new([ SymbolicExpression::Atom("do_work".to_string()),
SymbolicExpression::Atom("a".to_string()) ])) ];
let func_body = SymbolicExpression::List(
Box::new([ SymbolicExpression::Atom("+".to_string()),
SymbolicExpression::Atom("5".to_string()),
SymbolicExpression::Atom("x".to_string())]));
let func_args = vec!["x".to_string()];
let user_function = Box::new(DefinedFunction::new(func_body, func_args));
let context = Context::new();
let mut env = Environment::new(Box::new(MemoryContractDatabase::new()));
env.global_context.variables.insert("a".to_string(), Value::Int(59));
env.global_context.functions.insert("do_work".to_string(), user_function);
assert_eq!(Ok(Value::Int(64)), eval(&content[0], &mut env, &context));
}
#[test]
fn test_simple_let() {
/*
@@ -54,8 +23,10 @@ fn test_simple_let() {
x))";
if let Ok(parsed_program) = parse(&program) {
let context = Context::new();
let mut env = Environment::new(Box::new(MemoryContractDatabase::new()));
let context = LocalContext::new();
let global_context = GlobalContext::new();
let mut db = MemoryContractDatabase::new();
let mut env = Environment::new(&global_context, &mut db);
assert_eq!(Ok(Value::Int(7)), eval(&parsed_program[0], &mut env, &context));
} else {
@@ -64,6 +35,37 @@ fn test_simple_let() {
}
#[test]
fn test_buffer_equality() {
let tests = [
"(eq? \"a b c\" \"a b c\")",
"(eq? \"\\\" a b d\"
\"\\\" a b d\")",
"(not (eq? \"\\\" a b d\"
\" a b d\"))"];
let expectations = [
Value::Bool(true),
Value::Bool(true),
Value::Bool(true)];
tests.iter().zip(expectations.iter())
.for_each(|(program, expectation)| assert_eq!(Ok(expectation.clone()), execute(program)));
}
#[test]
fn test_principal_equality() {
let tests = [
"(eq? 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR)",
"(not (eq? 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR
'SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G))"];
let expectations = [
Value::Bool(true),
Value::Bool(true)];
tests.iter().zip(expectations.iter())
.for_each(|(program, expectation)| assert_eq!(Ok(expectation.clone()), execute(program)));
}
#[test]
fn test_simple_if_functions() {
//
@@ -85,15 +87,17 @@ fn test_simple_if_functions() {
if let Ok(parsed_bodies) = function_bodies {
let func_args1 = vec!["x".to_string()];
let func_args2 = vec!["x".to_string()];
let user_function1 = Box::new(DefinedFunction::new(parsed_bodies[0].clone(),
func_args1));
let user_function2 = Box::new(DefinedFunction::new(parsed_bodies[1].clone(),
func_args2));
let mut context = Context::new();
let mut env = Environment::new(Box::new(MemoryContractDatabase::new()));
let user_function1 = PrivateFunction::new(func_args1, parsed_bodies[0].clone());
let user_function2 = PrivateFunction::new(func_args2, parsed_bodies[1].clone());
env.global_context.functions.insert("with_else".to_string(), user_function1);
env.global_context.functions.insert("without_else".to_string(), user_function2);
let context = LocalContext::new();
let mut global_context = GlobalContext::new();
let mut db = MemoryContractDatabase::new();
global_context.functions.insert("with_else".to_string(), user_function1);
global_context.functions.insert("without_else".to_string(), user_function2);
let mut env = Environment::new(&global_context, &mut db);
if let Ok(tests) = evals {
assert_eq!(Ok(Value::Int(1)), eval(&tests[0], &mut env, &context));
@@ -109,25 +113,24 @@ fn test_simple_if_functions() {
#[test]
fn test_simple_arithmetic_functions() {
let tests = parse(&
"(* 52314 414)
(/ 52314 414)
(* 2 3 4 5)
(/ 10 13)
(mod 51 2)
(- 5 4 1)
(+ 5 4 1)
(eq? (* 2 3)
(+ 2 2 2))
(> 1 2)
(< 1 2)
(<= 1 1)
(>= 2 1)
(>= 1 1)
(pow 2 16)
(pow 2 32)
(- (pow 2 32))
");
let tests = [
"(* 52314 414)",
"(/ 52314 414)",
"(* 2 3 4 5)",
"(/ 10 13)",
"(mod 51 2)",
"(- 5 4 1)",
"(+ 5 4 1)",
"(eq? (* 2 3)
(+ 2 2 2))",
"(> 1 2)",
"(< 1 2)",
"(<= 1 1)",
"(>= 2 1)",
"(>= 1 1)",
"(pow 2 16)",
"(pow 2 32)",
"(- (pow 2 32))"];
let expectations = [
Value::Int(21657996),
@@ -146,33 +149,29 @@ fn test_simple_arithmetic_functions() {
Value::Int(65536),
Value::Int(u32::max_value() as i128 + 1),
Value::Int(-1 * (u32::max_value() as i128 + 1)),
];
];
if let Ok(to_eval) = tests {
let context = Context::new();
let mut env = Environment::new(Box::new(MemoryContractDatabase::new()));
to_eval.iter().zip(expectations.iter())
.for_each(|(program, expectation)| assert_eq!(Ok(expectation.clone()), eval(program, &mut env, &context)));
} else {
assert!(false, "Failed to parse function bodies.");
}
tests.iter().zip(expectations.iter())
.for_each(|(program, expectation)| assert_eq!(Ok(expectation.clone()), execute(program)));
}
#[test]
fn test_arithmetic_errors() {
let tests = parse(&
"(>= 1)
(+ 1 'true)
(/ 10 0)
(mod 10 0)
(pow 2 128)
(* 10 (pow 2 126))
(+ (pow 2 126) (pow 2 126))
(- 0 (pow 2 126) (pow 2 126) 1)
(-) (/) (mod 1) (pow 1)
(pow 2 (pow 2 32))
(pow 2 (- 1))
");
let tests = [
"(>= 1)",
"(+ 1 'true)",
"(/ 10 0)",
"(mod 10 0)",
"(pow 2 128)",
"(* 10 (pow 2 126))",
"(+ (pow 2 126) (pow 2 126))",
"(- 0 (pow 2 126) (pow 2 126) 1)",
"(-)",
"(/)",
"(mod 1)",
"(pow 1)",
"(pow 2 (pow 2 32))",
"(pow 2 (- 1))"];
let expectations = [
Err(Error::InvalidArguments("Binary comparison must be called with exactly 2 arguments".to_string())),
@@ -191,28 +190,24 @@ fn test_arithmetic_errors() {
Err(Error::Arithmetic("Power argument to (pow ...) must be a u32 integer".to_string()))
];
if let Ok(to_eval) = tests {
let context = Context::new();
let mut env = Environment::new(Box::new(MemoryContractDatabase::new()));
for (program, expectation) in to_eval.iter().zip(expectations.iter()) {
assert_eq!(*expectation, eval(program, &mut env, &context));
}
} else {
assert!(false, "Failed to parse function bodies.");
for (program, expectation) in tests.iter().zip(expectations.iter()) {
assert_eq!(*expectation, execute(program));
}
}
#[test]
fn test_bool_functions() {
let tests = parse(&
"(and 'true 'true 'true)
(and 'false 'true 'true)
(and 'false (> 1 (/ 10 0)))
(or 'true (> 1 (/ 10 0)))
(or 'false 'false 'false)
(not 'true)");
let tests = [
"'true",
"(and 'true 'true 'true)",
"(and 'false 'true 'true)",
"(and 'false (> 1 (/ 10 0)))",
"(or 'true (> 1 (/ 10 0)))",
"(or 'false 'false 'false)",
"(not 'true)"];
let expectations = [
Value::Bool(true),
Value::Bool(true),
Value::Bool(false),
Value::Bool(false),
@@ -220,12 +215,22 @@ fn test_bool_functions() {
Value::Bool(false),
Value::Bool(false)];
if let Ok(to_eval) = tests {
let context = Context::new();
let mut env = Environment::new(Box::new(MemoryContractDatabase::new()));
to_eval.iter().zip(expectations.iter())
.for_each(|(program, expectation)| assert_eq!(Ok(expectation.clone()), eval(program, &mut env, &context)));
} else {
assert!(false, "Failed to parse function bodies.");
}
tests.iter().zip(expectations.iter())
.for_each(|(program, expectation)| assert_eq!(Ok(expectation.clone()), execute(program)));
}
#[test]
fn test_bad_lets() {
let tests = [
"(let ((tx-sender 1)) (+ tx-sender tx-sender))",
"(let ((* 1)) (+ * *))",
"(let ((a 1) (a 2)) (+ a a))"];
let expectations: &[Result<Value, Error>] = &[
Err(Error::ReservedName("tx-sender".to_string())),
Err(Error::ReservedName("*".to_string())),
Err(Error::VariableDefinedMultipleTimes("a".to_string()))];
tests.iter().zip(expectations.iter())
.for_each(|(program, expectation)| assert_eq!(*expectation, execute(program)));
}

View File

@@ -1,66 +1,236 @@
use std::hash::{Hash, Hasher};
use std::fmt;
use std::collections::BTreeMap;
use vm::InterpreterResult;
use vm::errors::Error;
use address::c32;
use vm::representations::SymbolicExpression;
use vm::{eval, Context, Environment};
use vm::errors::{Error, InterpreterResult as Result};
use util::hash;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum AtomTypeIdentifier {
VoidType,
IntType,
BoolType,
BufferType,
TupleType(TupleTypeSignature)
}
const MAX_VALUE_SIZE: i128 = 1024 * 1024; // 1MB
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TypeSignature {
atomic_type: AtomTypeIdentifier,
dimension: u8
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TupleTypeSignature {
type_map: BTreeMap<String, TypeSignature>
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AtomTypeIdentifier {
VoidType,
IntType,
BoolType,
BufferType(u32),
PrincipalType,
TupleType(TupleTypeSignature)
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ListTypeData {
max_len: u32,
dimension: u8
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeSignature {
atomic_type: AtomTypeIdentifier,
list_dimensions: Option<ListTypeData>,
// NOTE: for the purposes of type-checks and cost computations, list size = dimension * max_length!
// high dimensional lists are _expensive_ --- use lists of tuples!
}
#[derive(Debug, Clone, Eq)]
pub struct TupleData {
pub type_signature: TupleTypeSignature,
type_signature: TupleTypeSignature,
data_map: BTreeMap<String, Value>
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct BuffData {
data: Vec<u8>,
}
#[derive(Debug, Clone, Eq)]
pub struct ListData {
pub data: Vec<Value>,
type_signature: TypeSignature
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Value {
Void,
Int(i128),
Bool(bool),
Buffer(Box<[char]>),
List(Vec<Value>, TypeSignature),
Buffer(BuffData),
List(ListData),
Principal(u8, [u8; 20]), // a principal is a version byte + hash160 (20 bytes)
Tuple(TupleData)
}
pub enum CallableType <'a> {
UserFunction(Box<DefinedFunction>),
NativeFunction(&'a Fn(&[Value]) -> InterpreterResult),
SpecialFunction(&'a Fn(&[SymbolicExpression], &mut Environment, &Context) -> InterpreterResult)
impl PartialEq for ListData {
fn eq(&self, other: &ListData) -> bool {
self.data == other.data
}
}
#[derive(Clone)]
pub struct DefinedFunction {
pub arguments: Vec<String>,
pub body: SymbolicExpression
impl Hash for ListData {
fn hash<H: Hasher>(&self, state: &mut H) {
self.data.hash(state);
}
}
#[derive(Clone,PartialEq,Eq,Hash)]
pub struct FunctionIdentifier {
pub arguments: Vec<String>,
pub body: SymbolicExpression
impl PartialEq for TupleData {
fn eq(&self, other: &TupleData) -> bool {
self.data_map == other.data_map
}
}
impl Hash for TupleData {
fn hash<H: Hasher>(&self, state: &mut H) {
self.data_map.hash(state);
}
}
impl Value {
pub fn new_list(list_data: &[Value]) -> Result<Value> {
let vec_data = Vec::from(list_data);
Value::list_from(vec_data)
}
pub fn list_from(list_data: Vec<Value>) -> Result<Value> {
let type_sig = TypeSignature::construct_parent_list_type(&list_data)?;
// Aaron: at this point, we've _already_ allocated memory for this type.
// (e.g., from a (map...) call, or a (list...) call.
// this is a problem _if_ the static analyzer cannot already prevent
// this case. This applies to all the constructor size checks.
if type_sig.size()? > MAX_VALUE_SIZE {
return Err(Error::ValueTooLarge)
}
Ok(Value::List(ListData { data: list_data, type_signature: type_sig }))
}
pub fn buff_from(buff_data: Vec<u8>) -> Result<Value> {
if buff_data.len() > u32::max_value() as usize {
Err(Error::BufferTooLarge)
} else if buff_data.len() as i128 > MAX_VALUE_SIZE {
Err(Error::ValueTooLarge)
} else {
Ok(Value::Buffer(BuffData { data: buff_data }))
}
}
pub fn tuple_from_data(paired_tuple_data: Vec<(String, Value)>) -> Result<Value> {
let tuple_data = TupleData::from_data(paired_tuple_data)?;
if tuple_data.size()? > MAX_VALUE_SIZE {
return Err(Error::ValueTooLarge)
}
Ok(Value::Tuple(tuple_data))
}
pub fn size(&self) -> Result<i128> {
match self {
Value::Void => AtomTypeIdentifier::VoidType.size(),
Value::Int(_i) => AtomTypeIdentifier::IntType.size(),
Value::Bool(_i) => AtomTypeIdentifier::BoolType.size(),
Value::Principal(_,_) => AtomTypeIdentifier::PrincipalType.size(),
Value::Buffer(ref buff_data) => Ok(buff_data.data.len() as i128),
Value::Tuple(ref tuple_data) => tuple_data.size(),
Value::List(ref list_data) => list_data.type_signature.size()
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Value::Void => write!(f, "null"),
Value::Int(int) => write!(f, "{}", int),
Value::Bool(boolean) => write!(f, "{}", boolean),
Value::Buffer(vec_bytes) => write!(f, "0x{}", hash::to_hex(&vec_bytes.data)),
Value::Tuple(data) => write!(f, "{}", data),
Value::Principal(version, vec_bytes) => {
let c32_str = match c32::c32_address(*version, &vec_bytes[..]) {
Ok(val) => val,
Err(_) => "INVALID_C32_ADDR".to_string()
};
write!(f, "{}", c32_str)
},
Value::List(list_data) => {
write!(f, "( ")?;
for v in list_data.data.iter() {
write!(f, "{} ", v)?;
}
write!(f, ")")
}
}
}
}
impl AtomTypeIdentifier {
pub fn size(&self) -> Result<i128> {
match self {
AtomTypeIdentifier::VoidType => Ok(1),
AtomTypeIdentifier::IntType => Ok(16),
AtomTypeIdentifier::BoolType => Ok(1),
AtomTypeIdentifier::PrincipalType => Ok(21),
AtomTypeIdentifier::BufferType(len) => Ok(*len as i128),
AtomTypeIdentifier::TupleType(tuple_sig) => tuple_sig.size()
}
}
fn expand_to_admit(&mut self, other: &AtomTypeIdentifier) -> Result<()> {
match self {
AtomTypeIdentifier::BufferType(ref mut my_len) => {
if let AtomTypeIdentifier::BufferType(ref other_len) = other {
if other_len > my_len {
*my_len = *other_len
}
Ok(())
} else {
Err(Error::BadTypeConstruction)
}
},
AtomTypeIdentifier::TupleType(ref mut tuple_sig) => {
if let AtomTypeIdentifier::TupleType(ref other_tuple_sig) = other {
tuple_sig.expand_to_admit(other_tuple_sig)
} else {
Err(Error::BadTypeConstruction)
}
},
_ => {
if other == self {
Ok(())
} else {
Err(Error::BadTypeConstruction)
}
}
}
}
fn admits(&self, other: &AtomTypeIdentifier) -> bool {
match self {
AtomTypeIdentifier::BufferType(ref my_len) => {
if let AtomTypeIdentifier::BufferType(ref other_len) = other {
my_len >= other_len
} else {
false
}
},
AtomTypeIdentifier::TupleType(ref tuple_sig) => {
if let AtomTypeIdentifier::TupleType(ref other_tuple_sig) = other {
tuple_sig.admits(other_tuple_sig)
} else {
false
}
},
_ => other == self
}
}
}
impl TupleTypeSignature {
pub fn new(type_data: Vec<(String, TypeSignature)>) -> Result<TupleTypeSignature, Error> {
pub fn new(type_data: Vec<(String, TypeSignature)>) -> Result<TupleTypeSignature> {
let mut type_map = BTreeMap::new();
for (name, type_info) in type_data {
if let Some(_v) = type_map.insert(name, type_info) {
@@ -70,35 +240,84 @@ impl TupleTypeSignature {
Ok(TupleTypeSignature { type_map: type_map })
}
pub fn check_valid(&self, name: &str, value: &Value) -> bool {
if let Some(expected_type) = self.type_map.get(name) {
*expected_type == TypeSignature::type_of(value)
pub fn admits(&self, other: &TupleTypeSignature) -> bool {
if self.type_map.len() != other.type_map.len() {
return false
}
for (name, my_type_sig) in self.type_map.iter() {
if let Some(other_type_sig) = other.type_map.get(name) {
if !my_type_sig.admits_type(other_type_sig) {
return false
}
} else {
return false
}
}
return true
}
pub fn size(&self) -> Result<i128> {
let mut name_size: i128 = 0;
let mut value_size: i128 = 0;
for (name, type_signature) in self.type_map.iter() {
// we only accept ascii names, so 1 char = 1 byte.
name_size = name_size.checked_add(name.len() as i128)
.ok_or(Error::ValueTooLarge)?;
value_size = value_size.checked_add(type_signature.size()? as i128)
.ok_or(Error::ValueTooLarge)?;
}
let name_total_size = name_size.checked_mul(2)
.ok_or(Error::ValueTooLarge)?;
value_size.checked_add(name_total_size)
.ok_or(Error::ValueTooLarge)
}
// NOTE: this function mutates self _even if it returns an error_.
fn expand_to_admit(&mut self, other: &TupleTypeSignature) -> Result<()> {
if self.type_map.len() != other.type_map.len() {
return Err(Error::BadTypeConstruction)
}
for (name, my_type_sig) in self.type_map.iter_mut() {
let other_type_sig = other.type_map.get(name).ok_or(Error::BadTypeConstruction)?;
my_type_sig.expand_to_admit(other_type_sig)?;
}
Ok(())
}
pub fn parse_name_type_pair_list(type_def: &SymbolicExpression) -> Result<TupleTypeSignature> {
if let SymbolicExpression::List(ref name_type_pairs) = type_def {
let mapped_key_types = parse_name_type_pairs(name_type_pairs)?;
TupleTypeSignature::new(mapped_key_types)
} else {
false
Err(Error::ExpectedListPairs)
}
}
}
impl TupleData {
pub fn from_data(data: &[(&str, Value)]) -> Result<TupleData, Error> {
fn from_data(mut data: Vec<(String, Value)>) -> Result<TupleData> {
let mut type_map = BTreeMap::new();
let mut data_map = BTreeMap::new();
for (name, value) in data {
let type_info = TypeSignature::type_of(value);
for (name, value) in data.drain(..) {
let type_info = TypeSignature::type_of(&value);
if type_info.atomic_type == AtomTypeIdentifier::VoidType {
return Err(Error::InvalidArguments("Cannot use VoidTypes in tuples.".to_string()))
}
if let Some(_v) = type_map.insert(name.to_string(), type_info) {
return Err(Error::InvalidArguments("Cannot use named argument twice in tuple construction.".to_string()))
}
data_map.insert(name.to_string(), (*value).clone());
data_map.insert(name.to_string(), value);
}
Ok(TupleData { type_signature: TupleTypeSignature { type_map: type_map },
data_map: data_map })
}
pub fn get(&self, name: &str) -> InterpreterResult {
pub fn get(&self, name: &str) -> Result<Value> {
if let Some(value) = self.data_map.get(name) {
Ok(value.clone())
} else {
@@ -106,107 +325,321 @@ impl TupleData {
}
}
pub fn size(&self) -> Result<i128> {
self.type_signature.size()
}
}
impl fmt::Display for TupleData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut first = true;
write!(f, "(")?;
for (name, value) in self.data_map.iter() {
if !first {
write!(f, ", ")?;
}
first = false;
write!(f, "{}: {}", name, value)?;
}
write!(f, ")")
}
}
impl TypeSignature {
pub fn new(atomic_type: AtomTypeIdentifier, dimension: u8) -> TypeSignature {
pub fn new_atom(atomic_type: AtomTypeIdentifier) -> TypeSignature {
TypeSignature { atomic_type: atomic_type,
dimension: dimension }
list_dimensions: None }
}
pub fn type_of(x: &Value) -> TypeSignature {
match x {
Value::Void => TypeSignature::new(AtomTypeIdentifier::VoidType, 0),
Value::Int(_v) => TypeSignature::new(AtomTypeIdentifier::IntType, 0),
Value::Bool(_v) => TypeSignature::new(AtomTypeIdentifier::BoolType, 0),
Value::Buffer(_v) => TypeSignature::new(AtomTypeIdentifier::BufferType, 0),
Value::List(_v, type_signature) => type_signature.clone(),
Value::Tuple(v) => TypeSignature::new(AtomTypeIdentifier::TupleType(
v.type_signature.clone()), 0)
}
}
pub fn get_list_type_for(x: &Value) -> Result<TypeSignature, Error> {
match x {
Value::Void => Err(Error::InvalidArguments("Cannot construct list of void types".to_string())),
Value::Tuple(_a) => Err(Error::InvalidArguments("Cannot construct list of tuple types".to_string())),
_ => {
let mut base_type = TypeSignature::type_of(x);
base_type.dimension += 1;
Ok(base_type)
pub fn new_list(atomic_type: AtomTypeIdentifier, max_len: i128, dimension: i128) -> Result<TypeSignature> {
if dimension == 0 {
Err(Error::InvalidTypeDescription)
} else if max_len > u32::max_value() as i128 || dimension > u8::max_value() as i128 {
Err(Error::ListTooLarge)
} else {
let list_dimensions = Some(ListTypeData { max_len: max_len as u32,
dimension: dimension as u8 });
let type_sig = TypeSignature { atomic_type: atomic_type,
list_dimensions: list_dimensions };
if type_sig.size()? > MAX_VALUE_SIZE {
Err(Error::ValueTooLarge)
} else {
Ok(type_sig)
}
}
}
fn get_atom_type(typename: &str) -> Result<AtomTypeIdentifier, Error> {
fn new_atom_checked(atom_type: AtomTypeIdentifier) -> Result<TypeSignature> {
if atom_type.size()? > MAX_VALUE_SIZE {
Err(Error::ValueTooLarge)
} else {
Ok(TypeSignature::new_atom(atom_type))
}
}
fn new_tuple(tuple_type_sig: TupleTypeSignature) -> Result<TypeSignature> {
TypeSignature::new_atom_checked(AtomTypeIdentifier::TupleType(tuple_type_sig))
}
fn new_buffer(buff_len: i128) -> Result<TypeSignature> {
if buff_len > u32::max_value() as i128 {
Err(Error::BufferTooLarge)
} else {
let atom_type = AtomTypeIdentifier::BufferType(buff_len as u32);
TypeSignature::new_atom_checked(atom_type)
}
}
pub fn get_empty_list_type() -> TypeSignature {
TypeSignature { atomic_type: AtomTypeIdentifier::IntType,
list_dimensions: Some(ListTypeData { max_len: 0,
dimension: 1 })}
}
pub fn size(&self) -> Result<i128> {
let list_multiplier = match self.list_dimensions {
Some(ref list_data) => (list_data.max_len as i128).checked_mul(list_data.dimension as i128)
.ok_or(Error::ValueTooLarge),
None => Ok(1)
}?;
list_multiplier.checked_mul(self.atomic_type.size()?)
.ok_or(Error::ValueTooLarge)
}
pub fn type_of(x: &Value) -> TypeSignature {
if let Value::List(list_data) = x {
list_data.type_signature.clone()
} else {
let atom = match x {
Value::Void => AtomTypeIdentifier::VoidType,
Value::Principal(_,_) => AtomTypeIdentifier::PrincipalType,
Value::Int(_v) => AtomTypeIdentifier::IntType,
Value::Bool(_v) => AtomTypeIdentifier::BoolType,
Value::Buffer(buff_data) => AtomTypeIdentifier::BufferType(buff_data.data.len() as u32),
Value::Tuple(v) => AtomTypeIdentifier::TupleType(
v.type_signature.clone()),
Value::List(_) => panic!("Unreachable code")
};
TypeSignature::new_atom(atom)
}
}
fn expand_to_admit(&mut self, x_type: &TypeSignature) -> Result<()> {
if let Some(ref mut my_list_dimensions) = self.list_dimensions {
if let Some(ref x_list_dimensions) = x_type.list_dimensions {
if my_list_dimensions.dimension != x_list_dimensions.dimension {
return Err(Error::BadTypeConstruction)
}
if my_list_dimensions.max_len < x_list_dimensions.max_len {
my_list_dimensions.max_len = x_list_dimensions.max_len;
}
} else {
return Err(Error::BadTypeConstruction)
}
} else {
if let Some(_) = x_type.list_dimensions {
return Err(Error::BadTypeConstruction)
}
}
self.atomic_type.expand_to_admit(& x_type.atomic_type)
}
// Checks if resulting type signature is of valid size.
// Aaron:
// currently, this does "loose admission" for higher-order lists --
// but should it do the same for buffers and tuples or is it better
// like it is now, where it requires an exact type match on those?
// e.g.: (list "abcd" "abc") will currently error because one etry is
// if type (buffer 4) and the other is of type (buffer 3)
// my feeling is that this should probably be allowed, and the resulting
// type should be (list 2 (buffer 4))
fn construct_parent_list_type(args: &[Value]) -> Result<TypeSignature> {
if let Some((first, rest)) = args.split_first() {
// children must be all of identical types, though we're a little more permissive about
// children which are _lists_: we don't care about their max_len, we just take the max()
let mut child_type = TypeSignature::type_of(first);
for x in rest {
let x_type_signature = TypeSignature::type_of(x);
child_type.expand_to_admit(&x_type_signature)?;
}
let mut parent_max_len = {
let args_len = args.len();
if args_len > (u32::max_value() as usize) {
Err(Error::ListTooLarge)
} else {
Ok(args_len as u32)
}
}?;
let parent_dimension = match child_type.list_dimensions {
Some(ref type_data) => {
if type_data.max_len > parent_max_len {
parent_max_len = type_data.max_len
}
type_data.dimension.checked_add(1)
.ok_or(Error::ListDimensionTooHigh)
},
None => {
Ok(1)
}
}?;
TypeSignature::new_list(child_type.atomic_type,
parent_max_len as i128, parent_dimension as i128)
} else {
Ok(TypeSignature::get_empty_list_type())
}
}
pub fn admits(&self, x: &Value) -> bool {
let x_type = TypeSignature::type_of(x);
self.admits_type(&x_type)
}
pub fn admits_type(&self, x_type: &TypeSignature) -> bool {
if let Some(ref x_type_data) = x_type.list_dimensions {
if let Some(ref my_type_data) = self.list_dimensions {
if my_type_data.dimension == x_type_data.dimension && my_type_data.max_len >= x_type_data.max_len {
self.atomic_type.admits(&x_type.atomic_type)
} else {
false
}
} else {
false
}
} else {
self.atomic_type.admits(&x_type.atomic_type)
}
}
fn parse_atom_type(typename: &str) -> Result<AtomTypeIdentifier> {
match typename {
"int" => Ok(AtomTypeIdentifier::IntType),
"void" => Ok(AtomTypeIdentifier::VoidType),
"bool" => Ok(AtomTypeIdentifier::BoolType),
"buff" => Ok(AtomTypeIdentifier::BufferType),
"principal" => Ok(AtomTypeIdentifier::PrincipalType),
_ => Err(Error::ParseError(format!("Unknown type name: '{:?}'", typename)))
}
}
fn get_list_type(prefix: &str, typename: &str, dimension: &str) -> Result<TypeSignature, Error> {
if prefix != "list" {
let message = format!("Unknown type name: '{}-{}-{}'", prefix, typename, dimension);
return Err(Error::ParseError(message))
// Parses list type signatures ->
// (list maximum-length dimension atomic-type) or
// (list maximum-length atomic-type) -> denotes list of dimension 1
fn parse_list_type_repr(type_args: &[SymbolicExpression]) -> Result<TypeSignature> {
if type_args.len() != 2 && type_args.len() != 3 {
return Err(Error::InvalidTypeDescription);
}
let atom_type = TypeSignature::get_atom_type(typename)?;
let dimension = match u8::from_str_radix(dimension, 10) {
Ok(parsed) => Ok(parsed),
Err(_e) => Err(Error::ParseError(
format!("Failed to parse dimension of type: '{}-{}-{}'",
prefix, typename, dimension)))
}?;
Ok(TypeSignature::new(atom_type, dimension))
}
pub fn parse_type_str(x: &str) -> Result<TypeSignature, Error> {
let components: Vec<_> = x.split('-').collect();
match components.len() {
1 => {
let atom_type = TypeSignature::get_atom_type(components[0])?;
Ok(TypeSignature::new(atom_type, 0))
},
3 => TypeSignature::get_list_type(components[0], components[1], components[2]),
_ => Err(Error::ParseError(
format!("Unknown type name: '{}'", x)))
}
}
pub fn get_empty_list_type() -> TypeSignature {
TypeSignature::new(AtomTypeIdentifier::IntType, 0)
}
}
impl DefinedFunction {
pub fn new(body: SymbolicExpression, arguments: Vec<String>) -> DefinedFunction {
DefinedFunction {
body: body,
arguments: arguments,
}
}
pub fn apply(&self, args: &[Value], env: &mut Environment) -> InterpreterResult {
let mut context = Context::new();
let mut arg_iterator = self.arguments.iter().zip(args.iter());
let _result = arg_iterator.try_for_each(|(arg, value)| {
match context.variables.insert((*arg).clone(), (*value).clone()) {
Some(_val) => Err(Error::InvalidArguments("Multiply defined function argument".to_string())),
_ => Ok(())
let dimension = {
if type_args.len() == 2 {
Ok(1)
} else {
if let SymbolicExpression::AtomValue(Value::Int(dimension)) = &type_args[1] {
Ok(*dimension)
} else {
Err(Error::InvalidTypeDescription)
}
}
})?;
eval(&self.body, env, &context)
}?;
if let SymbolicExpression::AtomValue(Value::Int(max_len)) = &type_args[0] {
let atomic_type_arg = &type_args[type_args.len()-1];
let atomic_type = TypeSignature::parse_type_repr(atomic_type_arg, false)?;
TypeSignature::new_list(atomic_type.atomic_type, *max_len, dimension)
} else {
Err(Error::InvalidTypeDescription)
}
}
pub fn get_identifier(&self) -> FunctionIdentifier {
return FunctionIdentifier {
body: self.body.clone(),
arguments: self.arguments.clone() }
// Parses type signatures of the following form:
// (tuple ((key-name-0 value-type-0) (key-name-1 value-type-1)))
fn parse_tuple_type_repr(type_args: &[SymbolicExpression]) -> Result<TypeSignature> {
if type_args.len() != 1 {
return Err(Error::InvalidTypeDescription)
}
let tuple_type_signature = TupleTypeSignature::parse_name_type_pair_list(&type_args[0])?;
TypeSignature::new_tuple(tuple_type_signature)
}
// Parses type signatures of the form:
// (buff 10)
fn parse_buff_type_repr(type_args: &[SymbolicExpression]) -> Result<TypeSignature> {
if type_args.len() != 1 {
return Err(Error::InvalidTypeDescription)
}
if let SymbolicExpression::AtomValue(Value::Int(buff_len)) = &type_args[0] {
TypeSignature::new_buffer(*buff_len)
} else {
Err(Error::InvalidTypeDescription)
}
}
fn parse_type_repr(x: &SymbolicExpression, allow_list: bool) -> Result<TypeSignature> {
match x {
SymbolicExpression::Atom(atom_type_str) => {
let atomic_type = TypeSignature::parse_atom_type(atom_type_str)?;
Ok(TypeSignature::new_atom(atomic_type))
},
SymbolicExpression::List(list_contents) => {
let (compound_type, rest) = list_contents.split_first()
.ok_or(Error::InvalidTypeDescription)?;
if let SymbolicExpression::Atom(compound_type) = compound_type {
match compound_type.as_str() {
"list" =>
if !allow_list {
Err(Error::InvalidTypeDescription)
} else {
TypeSignature::parse_list_type_repr(rest)
},
"buff" => TypeSignature::parse_buff_type_repr(rest),
"tuple" => TypeSignature::parse_tuple_type_repr(rest),
_ => Err(Error::InvalidTypeDescription)
}
} else {
Err(Error::InvalidTypeDescription)
}
},
_ => Err(Error::InvalidTypeDescription)
}
}
}
pub fn parse_name_type_pairs(name_type_pairs: &[SymbolicExpression]) -> Result<Vec<(String, TypeSignature)>> {
// this is a pretty deep nesting here, but what we're trying to do is pick out the values of
// the form:
// ((name1 type1) (name2 type2) (name3 type3) ...)
// which is a list of 2-length lists of atoms.
use vm::representations::SymbolicExpression::{List, Atom};
// step 1: parse it into a vec of symbolicexpression pairs.
let as_pairs: Result<Vec<_>> =
name_type_pairs.iter().map(
|key_type_pair| {
if let List(ref as_vec) = key_type_pair {
if as_vec.len() != 2 {
Err(Error::ExpectedListPairs)
} else {
Ok((&as_vec[0], &as_vec[1]))
}
} else {
Err(Error::ExpectedListPairs)
}
}).collect();
// step 2: turn into a vec of (name, typesignature) pairs.
let key_types: Result<Vec<_>> =
(as_pairs?).iter().map(|(name_symbol, type_symbol)| {
let name = match name_symbol {
Atom(ref var) => Ok(var.clone()),
_ => Err(Error::ExpectedListPairs)
}?;
let type_info = TypeSignature::parse_type_repr(type_symbol, true)?;
Ok((name, type_info))
}).collect();
key_types
}

13
src/vm/variables.rs Normal file
View File

@@ -0,0 +1,13 @@
// This currently is just serving as a place-holder for reserved variable
// names.
pub const TX_SENDER: &str = "tx-sender";
static RESERVED_VARIABLES: &[&str] =
&[TX_SENDER,
"block-height",
"burn-block-height"];
pub fn is_reserved_variable(name: &str) -> bool {
RESERVED_VARIABLES.contains(&name)
}