diff --git a/lox-examples/shadowing.lox b/lox-examples/shadowing.lox new file mode 100644 index 0000000..d563807 --- /dev/null +++ b/lox-examples/shadowing.lox @@ -0,0 +1,19 @@ +var a = "global a"; +var b = "global b"; +var c = "global c"; +{ + var a = "outer a"; + var b = "outer b"; + { + var a = "inner a"; + print a; + print b; + print c; + } + print a; + print b; + print c; +} +print a; +print b; +print c; diff --git a/src/environment.rs b/src/environment.rs index 7c91a52..ad468c3 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -1,26 +1,142 @@ +use std::{cell::RefCell, collections::hash_map::HashMap, rc::Rc}; + use crate::value::Value; -use std::collections::hash_map::Entry; -use std::collections::HashMap; -pub struct Env(HashMap); +#[derive(Debug, Clone)] +pub struct Env(Rc>); impl Env { - pub fn new() -> Self { - Self(HashMap::new()) + pub fn empty() -> Self { + Self(Rc::new(RefCell::new(Inner::empty()))) + } + + pub fn new_child(&self) -> Self { + let parent = self.clone(); + let inner = Inner::with_parent(parent); + Self(Rc::new(RefCell::new(inner))) + } + + /// Define or re-define a new variable in the current environment + pub fn define(&self, var: String, val: Value) { + self.0.borrow_mut().define(var, val); + } + + /// Assign a new value to an existing variable the current environment or a parent + #[must_use = "you should check the assignment was successful"] + pub fn assign(&self, var: String, new_val: Value) -> bool { + self.0.borrow_mut().assign(var, new_val) + } + + /// Get the value of a variable in the current environment or a parent + pub fn get(&self, var: &str) -> Option { + self.0.borrow().get(var) + } +} + +#[derive(Debug, Clone)] +pub struct Inner { + values: HashMap, + parent: Option, +} + +impl Inner { + fn empty() -> Self { + Self { + values: HashMap::new(), + parent: None, + } } + fn with_parent(parent: Env) -> Self { + Self { + values: HashMap::new(), + parent: Some(parent), + } + } + + /// Define or re-define a new variable in the current environment pub fn define(&mut self, var: String, val: Value) { - self.0.insert(var, val); + self.values.insert(var, val); } + /// Assign a new value to an existing variable the current environment or a parent pub fn assign(&mut self, var: String, new_val: Value) -> bool { - matches!( - self.0.entry(var).and_modify(|entry| *entry = new_val), - Entry::Occupied(_) - ) + match self.values.get_mut(&var) { + Some(entry) => { + *entry = new_val; + true + } + None => match self.parent.as_ref() { + Some(parent) => parent.assign(var, new_val), + None => false, + }, + } } + /// Get the value of a variable in the current environment or a parent pub fn get(&self, var: &str) -> Option { - self.0.get(var).cloned() + self.values + .get(var) + .cloned() + .or_else(|| self.parent.as_ref().and_then(|parent| parent.get(var))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use claims::assert_some_eq; + + #[test] + fn defines_value() { + let e = Env::empty(); + + e.define("foo".to_string(), Value::Number(1.0)); + + assert_some_eq!(e.get("foo"), Value::Number(1.0)); + } + + #[test] + fn reassigns_value() { + let e = Env::empty(); + + e.define("foo".to_string(), Value::Number(1.0)); + assert!(e.assign("foo".to_string(), Value::Bool(true))); + + assert_some_eq!(e.get("foo"), Value::Bool(true)); + } + + #[test] + fn gets_value_from_parent() { + let parent = Env::empty(); + let child = parent.new_child(); + + parent.define("foo".to_string(), Value::Number(1.0)); + + assert_some_eq!(child.get("foo"), Value::Number(1.0)); + } + + #[test] + fn shadows_in_child() { + let parent = Env::empty(); + let child = parent.new_child(); + + parent.define("foo".to_string(), Value::Number(1.0)); + child.define("foo".to_string(), Value::Bool(true)); + + assert_some_eq!(parent.get("foo"), Value::Number(1.0)); + assert_some_eq!(child.get("foo"), Value::Bool(true)); + } + + #[test] + fn assigns_in_parent_from_child() { + let parent = Env::empty(); + let child = parent.new_child(); + + parent.define("foo".to_string(), Value::Number(1.0)); + assert!(child.assign("foo".to_string(), Value::Bool(true))); + + assert_some_eq!(parent.get("foo"), Value::Bool(true)); + assert_some_eq!(child.get("foo"), Value::Bool(true)); } } diff --git a/src/interpreter.rs b/src/interpreter.rs index 068b32e..097437e 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -18,7 +18,7 @@ pub fn interpret(ast: &Ast) -> Result<()> { #[cfg(test)] pub fn interpret_expr(expr: &Spanned) -> Result { - Interpreter::new().interpret_expr(expr) + Interpreter::::interpret_expr(expr, &Env::empty()) } pub struct Interpreter { @@ -29,7 +29,7 @@ pub struct Interpreter { impl Interpreter { pub fn new() -> Self { Self { - env: Env::new(), + env: Env::empty(), output: std::io::stdout(), } } @@ -39,7 +39,7 @@ impl Interpreter { impl Interpreter { fn with_output(output: Output) -> Self { Self { - env: Env::new(), + env: Env::empty(), output, } } @@ -52,56 +52,60 @@ impl Interpreter { impl Interpreter { pub fn interpret(&mut self, ast: &Ast) -> Result<()> { for stmt in &ast.0 { - self.execute_stmt(stmt)?; + Self::execute_stmt(stmt, &mut self.output, &self.env)?; } Ok(()) } #[tracing::instrument(name = "stmt", skip_all)] - fn execute_stmt(&mut self, stmt: &Spanned) -> Result<()> { + fn execute_stmt(stmt: &Spanned, output: &mut Output, env: &Env) -> Result<()> { match stmt.as_ref() { Stmt::Expr(e) => { - self.interpret_expr(e)?; + Self::interpret_expr(e, env)?; } Stmt::Print(e) => { - let value = self.interpret_expr(e)?; - writeln!(&mut self.output, "{value}")?; + let value = Self::interpret_expr(e, env)?; + writeln!(output, "{value}")?; } Stmt::VarDecl { name, initializer } => { let val = initializer .as_ref() - .map(|e| self.interpret_expr(e)) + .map(|e| Self::interpret_expr(e, env)) .transpose()? .unwrap_or(Value::Nil); - self.env.define(name.clone(), val); + env.define(name.clone(), val); + } + Stmt::Block(stmts) => { + let child_env = env.new_child(); + for stmt in stmts { + Self::execute_stmt(stmt, output, &child_env)? + } } - Stmt::Block(_) => todo!("block execution"), } Ok(()) } #[tracing::instrument(name = "expr", skip_all)] - fn interpret_expr(&mut self, expr: &Spanned) -> Result { + fn interpret_expr(expr: &Spanned, env: &Env) -> Result { match expr.as_ref() { - Expr::Binary { left, op, right } => self.interpret_binary_op(left, op, right), - Expr::Grouping { inner } => self.interpret_grouping(inner), + Expr::Binary { left, op, right } => Self::interpret_binary_op(left, op, right, env), + Expr::Grouping { inner } => Self::interpret_grouping(inner, env), Expr::Literal { lit } => { tracing::trace!("resolving literal"); Ok(Value::from(lit.as_ref())) } - Expr::Unary { op, expr } => self.interpret_unary(op, expr), - Expr::Var { name } => self - .env + Expr::Unary { op, expr } => Self::interpret_unary(op, expr, env), + Expr::Var { name } => env .get(name) .ok_or_else(|| RuntimeError::undefined_var(name)), Expr::Assignment { target, value } => { let name = match target.as_ref() { Lval::Ident { name } => name.clone(), }; - let value = self.interpret_expr(value)?; - if !self.env.assign(name.clone(), value.clone()) { + let value = Self::interpret_expr(value, env)?; + if !env.assign(name.clone(), value.clone()) { Err(RuntimeError::undefined_var(&Spanned::new( name, target.span(), @@ -115,16 +119,16 @@ impl Interpreter { #[tracing::instrument(name = "binary", skip_all)] fn interpret_binary_op( - &mut self, left: &Spanned, op: &Spanned, right: &Spanned, + env: &Env, ) -> Result { tracing::trace!("evaluating lhs of binary expr"); - let left_val = self.interpret_expr(left)?; + let left_val = Self::interpret_expr(left, env)?; tracing::trace!("evaluating rhs of binary expr"); - let right_val = self.interpret_expr(right)?; + let right_val = Self::interpret_expr(right, env)?; match op.as_ref() { BinaryOp::Add => match (&left_val, &right_val) { @@ -219,16 +223,16 @@ impl Interpreter { } #[tracing::instrument(name = "grouping", skip_all)] - fn interpret_grouping(&mut self, inner: &Spanned) -> Result { + fn interpret_grouping(inner: &Spanned, env: &Env) -> Result { tracing::trace!("descending to group"); // TODO: investigate using more span features here - self.interpret_expr(inner) + Self::interpret_expr(inner, env) } #[tracing::instrument(name = "unary", skip_all)] - fn interpret_unary(&mut self, op: &Spanned, expr: &Spanned) -> Result { + fn interpret_unary(op: &Spanned, expr: &Spanned, env: &Env) -> Result { tracing::trace!("descending into unary"); - let val = self.interpret_expr(expr)?; + let val = Self::interpret_expr(expr, env)?; match op.as_ref() { UnaryOp::Negative => { tracing::trace!("negating value"); @@ -382,6 +386,13 @@ mod test { assert_some_eq!(e.get("foo"), Value::Bool(false)); } + #[test] + fn child_env_shadows_parent() { + let (e, out) = execute_stmts("var foo = 1; { print foo; } { var foo = 2; }"); + assert_some_eq!(e.get("foo"), Value::Number(1.0)); + assert_eq!(out, "1\n"); + } + #[test] fn interprets_arithmetic() { for (input, expected_value) in [ diff --git a/src/runners/file.rs b/src/runners/file.rs index 7319d0e..f48d66c 100644 --- a/src/runners/file.rs +++ b/src/runners/file.rs @@ -16,7 +16,6 @@ pub fn run_file(path: &Path) -> Result<()> { let tokens = scan(&contents).map_err(|e| Report::new(e).with_source_code(contents.clone()))?; let ast = parse(tokens).map_err(|e| Report::new(e).with_source_code(contents.clone()))?; - println!("{}", ast.print_rpn()); interpret(&ast).map_err(|e| Report::new(e).with_source_code(contents.clone()))?;