separate two kinds of contexts (local and global) into two types

This commit is contained in:
Aaron Blankstein
2019-02-18 12:29:17 -06:00
parent 8791851bbf
commit 241b3cc49f
13 changed files with 171 additions and 149 deletions

View File

@@ -1,12 +1,12 @@
use vm::errors::{InterpreterResult as Result, Error};
use vm::representations::SymbolicExpression;
use vm::types::TypeSignature;
use vm::{eval, Value, Context, Environment};
use vm::{eval, Value, LocalContext, Environment};
pub enum CallableType <'a> {
UserFunction(DefinedFunction),
NativeFunction(&'a Fn(&[Value]) -> Result<Value>),
SpecialFunction(&'a Fn(&[SymbolicExpression], &mut Environment, &Context) -> Result<Value>)
SpecialFunction(&'a Fn(&[SymbolicExpression], &mut Environment, &LocalContext) -> Result<Value>)
}
#[derive(Clone)]
@@ -47,7 +47,7 @@ impl PublicFunction {
fn apply(&self, args: &[Value], env: &mut Environment) -> Result<Value> {
// since self is a malformed object.
let mut context = Context::new();
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) {
@@ -70,7 +70,7 @@ impl PrivateFunction {
}
fn apply(&self, args: &[Value], env: &mut Environment) -> Result<Value> {
let mut context = Context::new();
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()) {

View File

@@ -9,7 +9,7 @@ use vm::database::ContractDatabase;
const MAX_CONTEXT_DEPTH: u16 = 256;
pub struct Environment <'a> {
pub global_context: &'a Context <'a>,
pub global_context: &'a GlobalContext,
pub call_stack: CallStack,
pub database: &'a mut ContractDatabase
}
@@ -18,7 +18,7 @@ impl <'a> Environment <'a> {
// 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 Context<'a>,
pub fn new(global_context: &'a GlobalContext,
database: &'a mut ContractDatabase) -> Environment<'a> {
Environment {
global_context: global_context,
@@ -31,31 +31,56 @@ impl <'a> Environment <'a> {
// Aaron: note -- only the global context will ever have DefinedFunctions
// so it is probably worthwhile to separate into 2 types.
pub struct Context <'a> {
pub parent: Option< &'a Context<'a>>,
pub struct GlobalContext {
pub variables: HashMap<String, Value>,
pub functions: HashMap<String, DefinedFunction>,
}
pub struct LocalContext <'a> {
pub parent: Option< &'a LocalContext<'a>>,
pub variables: HashMap<String, Value>,
depth: u16
}
impl <'a> Context <'a> {
pub fn new() -> Context<'a> {
Context {
depth: 0,
parent: Option::None,
impl GlobalContext {
pub fn new() -> GlobalContext {
GlobalContext {
variables: HashMap::new(),
functions: HashMap::new()
}
}
pub fn lookup_variable(&self, name: &str) -> Option<Value> {
match self.variables.get(name) {
Some(value) => Option::Some(value.clone()),
None => Option::None
}
}
pub fn lookup_function(&self, name: &str) -> Option<DefinedFunction> {
match self.functions.get(name) {
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<Context<'a>> {
pub fn extend(&'a self) -> Result<LocalContext<'a>> {
if self.depth >= MAX_CONTEXT_DEPTH {
Err(Error::MaxContextDepthReached)
} else {
Ok(Context {
Ok(LocalContext {
parent: Some(self),
variables: HashMap::new(),
functions: HashMap::new(),
depth: self.depth + 1
})
}
@@ -63,7 +88,7 @@ impl <'a> Context <'a> {
pub fn lookup_variable(&self, name: &str) -> Option<Value> {
match self.variables.get(name) {
Some(value) => Option::Some((*value).clone()),
Some(value) => Option::Some(value.clone()),
None => {
match self.parent {
Some(parent) => parent.lookup_variable(name),
@@ -72,20 +97,6 @@ impl <'a> Context <'a> {
}
}
}
pub fn lookup_function(&self, name: &str) -> Option<DefinedFunction> {
match self.functions.get(name) {
Some(value) => {
Option::Some(value.clone())
},
None => {
match self.parent {
Some(parent) => parent.lookup_function(name),
None => Option::None
}
}
}
}
}
pub struct CallStack {

View File

@@ -1,19 +1,20 @@
use vm::{SymbolicExpression, Value, apply, eval_all};
use vm::errors::{Error, InterpreterResult as Result};
use vm::callables::CallableType;
use vm::{Context, SymbolicExpression, Value, Environment, apply, eval_all};
use vm::contexts::{Environment, LocalContext, GlobalContext};
use vm::database::{MemoryContractDatabase, ContractDatabase};
use vm::parser;
use vm::variables;
pub struct Contract <'a> {
pub struct Contract {
db: Box<ContractDatabase>,
global_context: Context<'a>
global_context: GlobalContext
}
impl <'a> Contract <'a> {
pub fn make_in_memory_contract(contract: &str) -> Result<Contract<'a>> {
impl Contract {
pub fn make_in_memory_contract(contract: &str) -> Result<Contract> {
let parsed: Vec<_> = parser::parse(contract)?;
let mut global_context = Context::new();
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)?;
@@ -45,7 +46,7 @@ impl <'a> Contract <'a> {
}
}
let mut local_context = Context::new();
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)

View File

@@ -1,7 +1,7 @@
use vm::types::Value;
use vm::errors::{Error, InterpreterResult as Result};
use vm::representations::SymbolicExpression;
use vm::{Context,Environment,eval};
use vm::{LocalContext, Environment, eval};
fn type_force_bool(value: &Value) -> Result<bool> {
match *value {
@@ -10,7 +10,7 @@ fn type_force_bool(value: &Value) -> Result<bool> {
}
}
pub fn special_or(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> Result<Value> {
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) -> Result<Value> {
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()))
}

View File

@@ -2,7 +2,7 @@ use vm::types::{Value};
use vm::representations::SymbolicExpression;
use vm::errors::{Error, InterpreterResult as Result};
use vm::database::DataMap;
use vm::{eval,Context,Environment};
use vm::{eval, LocalContext, Environment};
fn obtain_map <'a> (map_arg: &SymbolicExpression, env: &'a mut Environment) -> Result<&'a mut DataMap> {
let map_name = match map_arg {
@@ -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) -> Result<Value> {
context: &LocalContext) -> Result<Value> {
// arg0 -> map name
// arg1 -> key
if args.len() != 2 {
@@ -41,7 +41,7 @@ pub fn special_fetch_entry(args: &[SymbolicExpression],
pub fn special_set_entry(args: &[SymbolicExpression],
env: &mut Environment,
context: &Context) -> Result<Value> {
context: &LocalContext) -> Result<Value> {
// arg0 -> map name
// arg1 -> key
// arg2 -> value
@@ -62,7 +62,7 @@ pub fn special_set_entry(args: &[SymbolicExpression],
pub fn special_insert_entry(args: &[SymbolicExpression],
env: &mut Environment,
context: &Context) -> Result<Value> {
context: &LocalContext) -> Result<Value> {
// arg0 -> map name
// arg1 -> key
// arg2 -> value
@@ -80,7 +80,7 @@ pub fn special_insert_entry(args: &[SymbolicExpression],
pub fn special_delete_entry(args: &[SymbolicExpression],
env: &mut Environment,
context: &Context) -> Result<Value> {
context: &LocalContext) -> Result<Value> {
// arg0 -> map name
// arg1 -> key
if args.len() != 2 {

View File

@@ -3,7 +3,8 @@ 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::{ Context, Environment, eval };
use vm::contexts::{GlobalContext, LocalContext, Environment};
use vm::eval;
pub enum DefineResult {
Variable(String, Value),
@@ -12,7 +13,7 @@ pub enum DefineResult {
NoDefine
}
fn check_legal_define(name: &str, global_context: &Context) -> Result<()> {
fn check_legal_define(name: &str, global_context: &GlobalContext) -> Result<()> {
use vm::is_reserved;
if is_reserved(name) {
@@ -27,7 +28,7 @@ fn check_legal_define(name: &str, global_context: &Context) -> Result<()> {
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 = Context::new();
let context = LocalContext::new();
let value = eval(expression, env, &context)?;
Ok(DefineResult::Variable(variable.clone(), value))
}

View File

@@ -2,13 +2,13 @@ use vm::errors::{Error, InterpreterResult as Result};
use vm::types::Value;
use vm::representations::SymbolicExpression;
use vm::representations::SymbolicExpression::{AtomValue};
use vm::{Context,Environment,eval,apply,lookup_function};
use vm::{LocalContext, Environment, eval, apply, lookup_function};
pub fn list_cons(args: &[Value]) -> Result<Value> {
Value::new_list(args)
}
pub fn list_fold(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> Result<Value> {
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())))
}
@@ -30,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) -> Result<Value> {
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())))
}

View File

@@ -9,7 +9,7 @@ use vm::errors::{Error, InterpreterResult as Result};
use vm::types::Value;
use vm::callables::CallableType;
use vm::representations::SymbolicExpression;
use vm::{Context, Environment, eval};
use vm::{LocalContext, Environment, eval};
fn native_eq(args: &[Value]) -> Result<Value> {
@@ -32,7 +32,7 @@ fn native_begin(args: &[Value]) -> Result<Value> {
}
}
fn special_if(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> Result<Value> {
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()))
}
@@ -54,7 +54,7 @@ fn special_if(args: &[SymbolicExpression], env: &mut Environment, context: &Cont
}
}
fn special_let(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> Result<Value> {
fn special_let(args: &[SymbolicExpression], env: &mut Environment, context: &LocalContext) -> Result<Value> {
use vm::is_reserved;
// (let ((x 1) (y 2)) (+ x y)) -> 3

View File

@@ -2,9 +2,9 @@ use vm::errors::{Error, InterpreterResult as Result};
use vm::types::{Value};
use vm::representations::SymbolicExpression;
use vm::representations::SymbolicExpression::{NamedParameter};
use vm::{Context, Environment, eval};
use vm::{LocalContext, Environment, eval};
pub fn tuple_cons(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> Result<Value> {
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 {
@@ -26,7 +26,7 @@ pub fn tuple_cons(args: &[SymbolicExpression], env: &mut Environment, context: &
Value::tuple_from_data(evaled_pairs)
}
pub fn tuple_get(args: &[SymbolicExpression], env: &mut Environment, context: &Context) -> Result<Value> {
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

@@ -18,14 +18,14 @@ mod tests;
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, InterpreterResult as Result};
const MAX_CALL_STACK_DEPTH: usize = 256;
fn lookup_variable(name: &str, context: &Context, env: &Environment) -> Result<Value> {
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 {
@@ -45,16 +45,14 @@ pub fn lookup_function<'a> (name: &str, env: &Environment)-> Result<CallableType
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) -> Result<Value> {
env: &mut Environment, context: &LocalContext) -> Result<Value> {
if let CallableType::SpecialFunction(function) = function {
function(&args, env, context)
} else {
@@ -87,7 +85,7 @@ pub fn apply(function: &CallableType, args: &[SymbolicExpression],
}
}
pub fn eval <'a> (exp: &SymbolicExpression, env: &'a mut Environment, context: &Context) -> Result<Value> {
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),
@@ -125,10 +123,10 @@ pub fn is_reserved(name: &str) -> bool {
*/
fn eval_all(expressions: &[SymbolicExpression],
database: &mut ContractDatabase,
global_context: &mut Context) -> Result<Value> {
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 = {
@@ -167,7 +165,7 @@ fn eval_all(expressions: &[SymbolicExpression],
* database.
*/
pub fn execute(program: &str) -> Result<Value> {
let mut global_context = Context::new();
let mut global_context = GlobalContext::new();
let mut db_instance = Box::new(database::MemoryContractDatabase::new());
let parsed = parser::parse(program)?;

View File

@@ -213,3 +213,86 @@ pub fn parse(input: &str) -> Result<Vec<SymbolicExpression>> {
let lexed = lex(input)?;
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

@@ -1,72 +0,0 @@
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)";
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");
}

View File

@@ -1,7 +1,7 @@
use vm::{eval, execute};
use vm::database::MemoryContractDatabase;
use vm::errors::Error;
use vm::{Value, Context, Environment};
use vm::{Value, LocalContext, GlobalContext, Environment};
use vm::callables::PrivateFunction;
use vm::representations::SymbolicExpression;
use vm::parser::parse;
@@ -27,8 +27,8 @@ fn test_simple_user_function() {
let func_args = vec!["x".to_string()];
let user_function = PrivateFunction::new(func_args, func_body);
let context = Context::new();
let mut global_context = Context::new();
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));
@@ -56,8 +56,8 @@ fn test_simple_let() {
x))";
if let Ok(parsed_program) = parse(&program) {
let context = Context::new();
let global_context = Context::new();
let context = LocalContext::new();
let global_context = GlobalContext::new();
let mut db = MemoryContractDatabase::new();
let mut env = Environment::new(&global_context, &mut db);
@@ -123,8 +123,8 @@ fn test_simple_if_functions() {
let user_function1 = PrivateFunction::new(func_args1, parsed_bodies[0].clone());
let user_function2 = PrivateFunction::new(func_args2, parsed_bodies[1].clone());
let context = Context::new();
let mut global_context = Context::new();
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);