diff options
author | Vincent Ambo <mail@tazj.in> | 2021-01-14T20·16+0300 |
---|---|---|
committer | tazjin <mail@tazj.in> | 2021-01-14T20·26+0000 |
commit | 740a9a3565b3c30d8b667ff159b15d9f455e94b9 (patch) | |
tree | ed144ed7adf28143b121d9cc14cc8df53cb8015b | |
parent | 39439d59e8e9ddb1e2b7802f3aff092d77de7acf (diff) |
feat(tazjin/rlox): Implement support for closures r/2109
Change-Id: I0ffc810807a1a6ec90455a4f2d2bd977833005bd Reviewed-on: https://cl.tvl.fyi/c/depot/+/2396 Reviewed-by: tazjin <mail@tazj.in> Tested-by: BuildkiteCI
-rw-r--r-- | users/tazjin/rlox/src/interpreter.rs | 59 | ||||
-rw-r--r-- | users/tazjin/rlox/src/interpreter/tests.rs | 22 |
2 files changed, 57 insertions, 24 deletions
diff --git a/users/tazjin/rlox/src/interpreter.rs b/users/tazjin/rlox/src/interpreter.rs index 5fdde9adac68..a1d246ba2eb2 100644 --- a/users/tazjin/rlox/src/interpreter.rs +++ b/users/tazjin/rlox/src/interpreter.rs @@ -18,14 +18,17 @@ mod tests; #[derive(Clone, Debug)] pub enum Callable { Builtin(&'static dyn builtins::Builtin), - Function(Rc<parser::Function>), + Function { + func: Rc<parser::Function>, + closure: Rc<RwLock<Environment>>, + }, } impl Callable { fn arity(&self) -> usize { match self { Callable::Builtin(builtin) => builtin.arity(), - Callable::Function(func) => func.params.len(), + Callable::Function { func, .. } => func.params.len(), } } @@ -33,14 +36,15 @@ impl Callable { match self { Callable::Builtin(builtin) => builtin.call(args), - Callable::Function(func) => { + Callable::Function { func, closure } => { let mut fn_env: Environment = Default::default(); + fn_env.enclosing = Some(closure.clone()); for (param, value) in func.params.iter().zip(args.into_iter()) { fn_env.define(param, value)?; } - let result = lox.interpret_block(Rc::new(RwLock::new(fn_env)), &func.body); + let result = lox.interpret_block(Some(Rc::new(RwLock::new(fn_env))), &func.body); match result { // extract returned values if applicable @@ -90,7 +94,7 @@ impl Value { } #[derive(Debug, Default)] -struct Environment { +pub struct Environment { enclosing: Option<Rc<RwLock<Environment>>>, values: HashMap<String, Value>, } @@ -200,13 +204,6 @@ impl Interpreter { .get(var) } - fn set_enclosing(&mut self, parent: Rc<RwLock<Environment>>) { - self.env - .write() - .expect("environment lock is poisoned") - .enclosing = Some(parent); - } - // Interpreter itself pub fn interpret(&mut self, program: &Block) -> Result<Value, Error> { let mut value = Value::Literal(Literal::Nil); @@ -228,7 +225,7 @@ impl Interpreter { Value::Literal(Literal::String(output)) } Statement::Var(var) => return self.interpret_var(var), - Statement::Block(block) => return self.interpret_block(Default::default(), block), + Statement::Block(block) => return self.interpret_block(None, block), Statement::If(if_stmt) => return self.interpret_if(if_stmt), Statement::While(while_stmt) => return self.interpret_while(while_stmt), Statement::Function(func) => return self.interpret_function(func.clone()), @@ -253,19 +250,24 @@ impl Interpreter { Ok(value) } + /// Interpret the block in the supplied environment. If no + /// environment is supplied, a new one is created using the + /// current one as its parent. fn interpret_block( &mut self, - env: Rc<RwLock<Environment>>, + env: Option<Rc<RwLock<Environment>>>, block: &parser::Block, ) -> Result<Value, Error> { - // Initialise a new environment and point it at the parent - // (this is a bit tedious because we need to wrap it in and - // out of the Rc). - // - // TODO(tazjin): Refactor this to use Rc on the interpreter itself. - let previous = std::mem::replace(&mut self.env, env); - self.set_enclosing(previous.clone()); + let env = match env { + Some(env) => env, + None => { + let env: Rc<RwLock<Environment>> = Default::default(); + set_enclosing_env(&env, self.env.clone()); + env + } + }; + let previous = std::mem::replace(&mut self.env, env); let result = self.interpret(block); // Swap it back, discarding the child env. @@ -295,9 +297,12 @@ impl Interpreter { Ok(value) } - fn interpret_function(&mut self, stmt: Rc<parser::Function>) -> Result<Value, Error> { - let name = stmt.name.clone(); - let value = Value::Callable(Callable::Function(stmt)); + fn interpret_function(&mut self, func: Rc<parser::Function>) -> Result<Value, Error> { + let name = func.name.clone(); + let value = Value::Callable(Callable::Function { + func, + closure: self.env.clone(), + }); self.define_var(&name, value.clone())?; Ok(value) } @@ -442,3 +447,9 @@ fn eval_truthy(lit: &Value) -> bool { false } } + +fn set_enclosing_env(this: &RwLock<Environment>, parent: Rc<RwLock<Environment>>) { + this.write() + .expect("environment lock is poisoned") + .enclosing = Some(parent); +} diff --git a/users/tazjin/rlox/src/interpreter/tests.rs b/users/tazjin/rlox/src/interpreter/tests.rs index 5bc9f0a0a475..875116593e44 100644 --- a/users/tazjin/rlox/src/interpreter/tests.rs +++ b/users/tazjin/rlox/src/interpreter/tests.rs @@ -76,3 +76,25 @@ add(1, 2, 3); assert_eq!(Value::Literal(Literal::Number(6.0)), result); } + +#[test] +fn test_closure() { + let result = parse_eval( + r#" +fun makeCounter() { + var i = 0; + fun count() { + i = i + 1; + } + + return count; +} + +var counter = makeCounter(); +counter(); // "1". +counter(); // "2". +"#, + ); + + assert_eq!(Value::Literal(Literal::Number(2.0)), result); +} |