diff options
| -rw-r--r-- | rust/src/lambda.rs | 56 | ||||
| -rw-r--r-- | rust/tests/interpret_test.rs | 1 |
2 files changed, 46 insertions, 11 deletions
diff --git a/rust/src/lambda.rs b/rust/src/lambda.rs index 9d810f9..97b6f08 100644 --- a/rust/src/lambda.rs +++ b/rust/src/lambda.rs @@ -1,5 +1,5 @@ use rand::Rng; -use std::fs::read_to_string; +use std::{collections::HashMap, fs::read_to_string}; mod ast; use ast::*; @@ -7,20 +7,45 @@ use ast::*; mod parser; use parser::*; -struct Environment { - bindings: std::collections::HashMap<String, Value>, +#[derive(Debug, PartialEq)] +struct Environment<'a> { + parent: Box<Option<&'a Environment<'a>>>, + bindings: HashMap<String, Value>, } -impl Environment { +impl<'a> Environment<'a> { fn new() -> Self { Environment { - bindings: std::collections::HashMap::new(), + parent: Box::new(None), + bindings: HashMap::new(), } } fn bind(&mut self, var: &str, value: &Value) { self.bindings.insert(var.to_string(), value.clone()); } + + fn extends(&'a self) -> Self { + Environment { + parent: Box::new(Some(self)), + bindings: HashMap::new(), + } + } + + fn lookup(&self, var: &str) -> Option<&Value> { + self.bindings.get(var).or_else(|| match *self.parent { + Some(parent) => parent.lookup(var), + None => None, + }) + } + + fn binds(&self, s: &str) -> bool { + self.bindings.contains_key(s) + || match *self.parent { + Some(parent) => parent.binds(s), + None => false, + } + } } pub fn eval_file(file_name: &str) -> String { @@ -45,28 +70,31 @@ fn eval(arg: &Value, env: &mut Environment) -> Value { Value::Bool(true) } Value::Let(var, value, expr) => { - env.bind(var, value); - eval(expr, env) + let mut newenv = env.extends(); + newenv.bind(var, value); + eval(expr, &mut newenv) } Value::App(l, r) => { let result = apply(&eval(l, env), r); - if is_reducible(&result) { + if is_reducible(&result, env) { eval(&result, env) } else { result } } - Value::Sym(var) => env.bindings.get(var).unwrap_or(arg).clone(), + Value::Sym(var) => env.lookup(var).unwrap_or(arg).clone(), other => other.clone(), } } -fn is_reducible(result: &Value) -> bool { +fn is_reducible(result: &Value, env: &Environment) -> bool { match result { Value::App(l, r) => match **l { Value::Lam(_, _) => true, - _ => is_reducible(l) || is_reducible(r), + _ => is_reducible(l, env) || is_reducible(r, env), }, + Value::Let(_, _, _) => true, + Value::Sym(s) => env.binds(s), Value::Lam(_, _) => false, _ => false, } @@ -176,4 +204,10 @@ mod lambda_test { let values = parse("(let (foo (lam x x)) (foo 12))"); assert_eq!(vec![Value::Num(12)], eval_all(&values)); } + + #[test] + fn let_expressions_introduce_new_scope_for_bindings() { + let values = parse("(let (foo (lam x x)) ((let (foo foo) foo) 13))"); + assert_eq!(vec![Value::Num(13)], eval_all(&values)); + } } diff --git a/rust/tests/interpret_test.rs b/rust/tests/interpret_test.rs index 3fa357d..61b0712 100644 --- a/rust/tests/interpret_test.rs +++ b/rust/tests/interpret_test.rs @@ -5,4 +5,5 @@ fn interpreter_can_read_and_interpret_file() { assert_eq!("12 foo true (x x)", eval_file("sample/test.txt")); assert_eq!("true", eval_file("sample/test_full.txt")); assert_eq!("1", eval_file("sample/test_normal.txt")); + assert_eq!("13", eval_file("sample/test_let.txt")); } |
