summaryrefslogtreecommitdiff
path: root/rust
diff options
context:
space:
mode:
authorArnaud Bailly <arnaud.bailly@iohk.io>2024-09-25 14:11:41 +0200
committerArnaud Bailly <arnaud.bailly@iohk.io>2024-09-25 14:11:41 +0200
commit778881eda5b0955fb4c845fb29097f048491f007 (patch)
treea476547ca3286fc5e20a83198d658da7b1b18540 /rust
parent90dfe422f9c7058bebde0abf7eb3c7675c8fbf66 (diff)
downloadlambda-nantes-778881eda5b0955fb4c845fb29097f048491f007.tar.gz
Let creates a new environment
Diffstat (limited to 'rust')
-rw-r--r--rust/src/lambda.rs56
-rw-r--r--rust/tests/interpret_test.rs1
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"));
}