diff --git a/dom/src/environment.rs b/dom/src/environment.rs index f3f5125..81115ff 100644 --- a/dom/src/environment.rs +++ b/dom/src/environment.rs @@ -2,7 +2,7 @@ use thiserror::Error; -use std::collections::HashMap; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::ast::{Ident, Stmt}; @@ -14,7 +14,7 @@ pub enum EnvError { Declaration(String), } -pub trait CloneableFn: FnMut(Vec, &mut Env) -> Option { +pub trait CloneableFn: FnMut(Vec, Rc>) -> Option { fn clone_box<'a>(&self) -> Box where Self: 'a; @@ -22,7 +22,7 @@ pub trait CloneableFn: FnMut(Vec, &mut Env) -> Option { impl CloneableFn for F where - F: Fn(Vec, &mut Env) -> Option + Clone, + F: Fn(Vec, Rc>) -> Option + Clone, { fn clone_box<'a>(&self) -> Box where @@ -54,7 +54,7 @@ pub enum Val { ident: Ident, params: Vec, body: Vec, - env: Env, + env: Rc>, }, NativeFunc(Box), } @@ -76,19 +76,24 @@ impl std::fmt::Display for Val { #[derive(Debug, Clone, Default)] pub struct Env { /// The parent environment, if any. - parent: Option>, + parent: Option>>, /// The values stored in this environment. values: HashMap, } impl Env { + #[must_use] + pub fn new() -> Rc> { + Rc::new(RefCell::new(Self::default())) + } + /// Creates a new environment with the given parent environment. #[must_use] - pub fn with_parent(parent: Env) -> Self { - Self { - parent: Some(Box::new(parent)), + pub fn with_parent(parent: Rc>) -> Rc> { + Rc::new(RefCell::new(Self { + parent: Some(parent), values: HashMap::new(), - } + })) } /// Returns a reference to the values stored in this environment. @@ -98,6 +103,7 @@ impl Env { } /// Returns a mutable reference to the values stored in the environment. + #[must_use] pub fn values_mut(&mut self) -> &mut HashMap { &mut self.values } @@ -119,11 +125,11 @@ impl Env { /// Assigns a new value to the variable with the given name. /// /// Returns an error if no variable with the given name exists in this environment or its parents. - pub fn assign(&mut self, name: String, value: Val) -> Result { + pub fn assign(env: &Rc>, name: String, value: Val) -> Result { // Find the environment where the variable is declared. - let env = self.resolve_mut(&name)?; + let env = Self::resolve(env, &name)?; - env.values.insert(name, value.clone()); + env.borrow_mut().values.insert(name, value.clone()); Ok(value) } @@ -131,12 +137,11 @@ impl Env { /// Looks up the value of the variable with the given name. /// /// Returns an error if no variable with the given name exists in this environment or its parents. - pub fn lookup(&self, name: &str) -> Result { + pub fn lookup(env: &Rc>, name: &str) -> Result { // Find the environment where the variable is declared. - let env = self.resolve(name)?; - - let value = env - .values + let env = Self::resolve(env, name)?; + let values = &env.borrow().values; + let value = values .get(name) .expect("Environment should contain identifier"); @@ -144,31 +149,127 @@ impl Env { } /// Resolves the environment that contains the variable with the given name. - fn resolve(&self, name: &str) -> Result<&Env, EnvError> { - if self.values.contains_key(name) { - return Ok(self); + fn resolve(env: &Rc>, name: &str) -> Result>, EnvError> { + if env.borrow().values.contains_key(name) { + return Ok(Rc::clone(env)); } - if let Some(parent) = &self.parent { - return parent.resolve(name); + match &env.borrow().parent { + Some(parent) => Self::resolve(parent, name), + None => Err(EnvError::Declaration(name.to_string())), } - - Err(EnvError::Declaration(name.to_string())) } +} - /// Resolves the mutable environment that contains the variable with the given name. - fn resolve_mut(&mut self, name: &str) -> Result<&mut Env, EnvError> { - // If the variable is declared in this environment, return this environment. - if self.values.contains_key(name) { - return Ok(self); - } +#[cfg(test)] +mod tests { + use super::*; - // If there is a parent environment, recursively search for the variable there. - if let Some(parent) = &mut self.parent { - return parent.resolve_mut(name); + impl PartialEq for Val { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Val::Int(a), Val::Int(b)) => a == b, + _ => false, + } } + } + + #[test] + fn declare_and_lookup() { + let env = Env::new(); + + let name = "foo"; + let value = Val::Int(0); + + // Declare a variable in the environment + env.borrow_mut() + .declare(name.to_string(), value.clone()) + .expect("should be able to declare variable"); + + // Lookup the variable + let result = Env::lookup(&env, &name).expect("variable should exist"); + assert_eq!(result, value); + } + + #[test] + fn declare_error() { + let env = Env::new(); + + let name = "foo"; + let value = Val::Int(0); + + // Declare a variable in the environment + env.borrow_mut() + .declare(name.to_string(), value.clone()) + .expect("should be able to declare variable"); + + // Attempt to redeclare the same variable + let result = env.borrow_mut().declare(name.to_string(), value.clone()); + assert!(matches!(result, Err(EnvError::Duplicate(_)))); + } + + #[test] + fn lookup_error() { + let env = Env::new(); + + // Attempt to lookup a non-existent variable + let name = "foo"; + let result = Env::lookup(&env, &name); + assert!(matches!(result, Err(EnvError::Declaration(_)))); + } + + #[test] + fn assign_and_lookup() { + let env = Env::new(); + + let name = "foo"; + let value = Val::Int(0); + + // Declare a variable in the environment + env.borrow_mut() + .declare(name.to_string(), value.clone()) + .expect("should be able to declare variable"); + + // Assign a new value to the variable + let value = Val::Int(1); + Env::assign(&env, name.to_string(), value.clone()) + .expect("should be able to assign value to variable"); + + // Lookup the variable + let result = Env::lookup(&env, &name).expect("should be able to lookup variable"); + assert_eq!(result, value); + } + + #[test] + fn nested_environments() { + let parent_env = Env::new(); + + let name = "foo"; + let value = Val::Int(0); + + // Declare a variable in the parent environment + parent_env + .borrow_mut() + .declare(name.to_string(), value.clone()) + .expect("should be able to declare variable"); + + // Create a child environment with the parent environment + let child_env = Env::with_parent(Rc::clone(&parent_env)); + + // Lookup the variable from the child environment + let result = Env::lookup(&child_env, &name); + assert_eq!(result.unwrap(), value.clone()); + + // Declare a new variable in the parent environment + let name = "bar"; + let value = Val::Int(0); + parent_env + .borrow_mut() + .declare(name.to_string(), value.clone()) + .expect("should be able to declare variable"); - // If no environment contains the variable, return an error. - Err(EnvError::Declaration(name.to_string())) + // Lookup the new variable from the child environment + let result = Env::lookup(&child_env, &name).expect("should be able to lookup variable"); + assert_eq!(result, value); } } diff --git a/dom/src/interpreter.rs b/dom/src/interpreter.rs index de3a09d..d8beb40 100644 --- a/dom/src/interpreter.rs +++ b/dom/src/interpreter.rs @@ -1,3 +1,5 @@ +use std::{cell::RefCell, rc::Rc}; + use thiserror::Error; use crate::{ @@ -31,7 +33,7 @@ pub enum Exception { Return(Option>), } -pub fn eval(statement: impl Into, env: &mut Env) -> Result { +pub fn eval(statement: impl Into, env: &Rc>) -> Result { match statement.into() { Stmt::Program { body } => eval_body(body, env), Stmt::Cond(cond) => eval_cond(cond, env), @@ -59,14 +61,14 @@ pub fn eval(statement: impl Into, env: &mut Env) -> Result { } } -fn eval_body(body: Vec, env: &mut Env) -> Result { +fn eval_body(body: Vec, env: &Rc>) -> Result { body.into_iter() .map(|stmt| eval(stmt, env)) .last() .unwrap_or(Ok(Val::None)) } -fn eval_cond(cond: Cond, env: &mut Env) -> Result { +fn eval_cond(cond: Cond, env: &Rc>) -> Result { let Cond { condition, body } = cond; let Val::Bool(success) = eval(condition, env)? else { @@ -74,37 +76,40 @@ fn eval_cond(cond: Cond, env: &mut Env) -> Result { }; if success { - let result = eval_body(body, env)?; - + let env = Env::with_parent(Rc::clone(env)); + let result = eval_body(body, &env)?; return Ok(result); } Ok(Val::None) } -fn eval_func(ident: &Ident, params: Vec, body: Vec, env: &mut Env) -> Result { +fn eval_func( + ident: &Ident, + params: Vec, + body: Vec, + env: &Rc>, +) -> Result { let func = Val::Func { ident: ident.to_owned(), params, body, - env: Env::with_parent(env.clone()), + env: Env::with_parent(Rc::clone(env)), }; - let result = env.declare(ident.to_owned(), func); + let result = env.borrow_mut().declare(ident.to_owned(), func); Ok(result?) } -fn eval_loop(body: &Vec, env: &mut Env) -> Result { +fn eval_loop(body: &Vec, env: &Rc>) -> Result { let mut last = None; - // Used so that we can keep track of what variables existed before the loop - let stored_env = env.clone(); - let idents = stored_env.values().keys().collect::>(); - 'outer: loop { + let loop_env = Env::with_parent(Rc::clone(env)); + for stmt in body { - let result = eval(stmt.clone(), env); + let result = eval(stmt.clone(), &loop_env); match result { Ok(result) => last = Some(result), @@ -115,9 +120,6 @@ fn eval_loop(body: &Vec, env: &mut Env) -> Result { }, } } - - // Drop any values that were declared in an iteration - env.values_mut().retain(|ident, _| idents.contains(&ident)); } match last { @@ -126,52 +128,50 @@ fn eval_loop(body: &Vec, env: &mut Env) -> Result { } } -fn eval_var(ident: Ident, value: Stmt, env: &mut Env) -> Result { +fn eval_var(ident: Ident, value: Stmt, env: &Rc>) -> Result { let value = eval(value, env)?; - let result = env.declare(ident, value)?; + let result = env.borrow_mut().declare(ident, value)?; Ok(result) } -fn eval_assign(assignee: Expr, value: Expr, env: &mut Env) -> Result { +fn eval_assign(assignee: Expr, value: Expr, env: &Rc>) -> Result { let Expr::Ident(assignee) = assignee else { return Err(Box::new(InterpreterError::Assignment)); }; let value = eval(value, env)?; - let result = env.assign(assignee, value)?; + let result = Env::assign(env, assignee, value)?; Ok(result) } -fn eval_call(caller: Expr, args: Vec, env: &mut Env) -> Result { +fn eval_call(caller: Expr, args: Vec, env: &Rc>) -> Result { let Ok(args): Result> = args.into_iter().map(|arg| eval(arg, env)).collect() else { return Err(Box::new(InterpreterError::Args)); }; match eval(caller, env)? { - Val::NativeFunc(mut native_func) => match native_func(args, env) { + Val::NativeFunc(mut native_func) => match native_func(args, Rc::clone(env)) { Some(result) => Ok(result), None => Ok(Val::None), }, Val::Func { params, body, env, .. } => { - let mut env = env; - for (param, arg) in params.into_iter().zip(args.into_iter()) { - env.declare(param, arg)?; + env.borrow_mut().declare(param, arg)?; } let mut last = None; for stmt in body { - let result = eval(stmt, &mut env); + let result = eval(stmt, &env); match result { Ok(result) => last = Some(result), Err(kind) => match kind.downcast_ref() { Some(Exception::Return(value)) => { last = match value { - Some(value) => Some(eval(*value.clone(), &mut env)?), + Some(value) => Some(eval(*value.clone(), &env)?), None => None, }; break; @@ -190,7 +190,7 @@ fn eval_call(caller: Expr, args: Vec, env: &mut Env) -> Result { } } -fn eval_cmp_expr(left: Expr, right: Expr, op: CmpOp, env: &mut Env) -> Result { +fn eval_cmp_expr(left: Expr, right: Expr, op: CmpOp, env: &Rc>) -> Result { let lhs = eval(left, env)?; let rhs = eval(right, env)?; @@ -211,7 +211,7 @@ fn eval_cmp_expr(left: Expr, right: Expr, op: CmpOp, env: &mut Env) -> Result Result { +fn eval_binary_expr(left: Expr, right: Expr, op: BinaryOp, env: &Rc>) -> Result { let lhs = eval(left, env)?; let rhs = eval(right, env)?; @@ -261,7 +261,7 @@ fn eval_binary_expr(left: Expr, right: Expr, op: BinaryOp, env: &mut Env) -> Res Ok(result) } -fn eval_ident(ident: &Ident, env: &mut Env) -> Result { - let val = env.lookup(ident)?; +fn eval_ident(ident: &Ident, env: &Rc>) -> Result { + let val = Env::lookup(env, ident)?; Ok(val) } diff --git a/dom/src/main.rs b/dom/src/main.rs index ada37ec..17cde03 100644 --- a/dom/src/main.rs +++ b/dom/src/main.rs @@ -10,9 +10,11 @@ use interpreter::eval; use parser::Parser; use std::{ + cell::RefCell, fmt::Write as _, fs::read_to_string, io::{self, Write}, + rc::Rc, }; use clap::Parser as _; @@ -25,10 +27,10 @@ struct Args { fn main() { let args = Args::parse(); - let mut env = Env::default(); + let env = Rc::new(RefCell::new(Env::default())); // TODO: Refactor `Env` - let _ = env.declare( + let _ = env.borrow_mut().declare( "print".to_owned(), Val::NativeFunc(Box::new(|args, _| { let joined = args.iter().fold(String::new(), |mut output, arg| { @@ -42,13 +44,13 @@ fn main() { })), ); - let mut result = |contents: String| { + let result = |contents: String| { let mut parser = Parser::new(); let program = match parser.produce_ast(contents) { Ok(program) => program, Err(reason) => panic!("[L{}] {reason}", parser.line()), }; - eval(program, &mut env) + eval(program, &env) }; if let Some(path) = args.path { diff --git a/playground/dom-wasm/src/lib.rs b/playground/dom-wasm/src/lib.rs index 7ea0de8..1f0bebc 100644 --- a/playground/dom-wasm/src/lib.rs +++ b/playground/dom-wasm/src/lib.rs @@ -6,25 +6,27 @@ use web_sys::console; #[wasm_bindgen] pub fn interpret(contents: &str) -> String { - let mut env = Env::default(); + let env = Env::new(); - let _ = env.declare( - "print".to_owned(), - Val::NativeFunc(Box::new(|args, _| { - let joined = args.iter().fold(String::new(), |mut output, arg| { - let _ = write!(output, "{arg} "); - output - }); - console::log_1(&joined.into()); - None - })), - ); + env.borrow_mut() + .declare( + "print".to_owned(), + Val::NativeFunc(Box::new(|args, _| { + let joined = args.iter().fold(String::new(), |mut output, arg| { + let _ = write!(output, "{arg} "); + output + }); + console::log_1(&joined.into()); + None + })), + ) + .expect("should be able to declare `print` function"); let mut parser = Parser::new(); match parser.produce_ast(contents.to_string()) { Ok(program) => { let ast = format!("{:#?}", program); - let _ = eval(program, &mut env); + let _ = eval(program, &env); return ast; } Err(reason) => {