diff --git a/build.rs b/build.rs index 5e50bb0..533a106 100644 --- a/build.rs +++ b/build.rs @@ -40,6 +40,7 @@ fn do_ast_codegen(mut output: W) { writeln!(output).unwrap(); let expr_types = BTreeMap::from([ + ("Assign", "name: Token, value: Box"), ( "Binary", "left: Box, operator: Token, right: Box", diff --git a/src/eval.rs b/src/eval.rs index 72d2c39..80c1d24 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -178,6 +178,21 @@ impl ExprVisitor> for InterpreterRunner<'_> }) .cloned() } + + fn visit_assign(&mut self, name: &Token, value: &Expr) -> Result { + if self.interpreter.env.get_value(name.lexeme()).is_none() { + return Err(ScriptError { + line: name.line(), + location: String::new(), + message: format!("Cannot assign to undefined variable {}", name.lexeme()), + }); + } + + let value = self.visit_expr(value)?; + let inserted_value = self.interpreter.env.set_value(name.lexeme(), value); + + Ok(inserted_value.clone()) + } } impl StmtVisitor> for InterpreterRunner<'_> { diff --git a/src/eval/environment.rs b/src/eval/environment.rs index a5639cc..5901224 100644 --- a/src/eval/environment.rs +++ b/src/eval/environment.rs @@ -1,4 +1,7 @@ -use std::collections::{BTreeMap, HashMap}; +use std::{ + borrow::BorrowMut, + collections::{hash_map::Entry, BTreeMap, HashMap}, +}; use super::value::EvaluatedValue; @@ -75,8 +78,8 @@ impl Environment { None } - pub fn set_value(&mut self, name: &str, value: EvaluatedValue) { - self.current_scope_mut().set(name, value); + pub fn set_value(&mut self, name: &str, value: EvaluatedValue) -> &EvaluatedValue { + self.current_scope_mut().set(name, value) } fn current_scope(&self) -> &Scope { @@ -123,8 +126,15 @@ impl Scope { self.values.get(name) } - fn set>(&mut self, name: S, value: EvaluatedValue) { - self.values.insert(name.into(), value); + fn set>(&mut self, name: S, value: EvaluatedValue) -> &EvaluatedValue { + match self.values.entry(name.into()) { + Entry::Vacant(entry) => entry.insert(value), + Entry::Occupied(entry) => { + let item = entry.into_mut(); + *item = value; + item + } + } } } @@ -184,4 +194,14 @@ mod tests { assert_eq!(None, global.get_value(r"foo")); } + + #[test] + fn test_inserting_value_returns_reference_to_inserted_item() { + let mut global = Environment::new(); + + global.enter_scope(); + let inserted = global.set_value("foo", EvaluatedValue::Number(42_f64)); + + assert_eq!(&EvaluatedValue::Number(42_f64), inserted); + } } diff --git a/src/lib.rs b/src/lib.rs index dc6f719..8347761 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,6 +105,10 @@ impl ExprVisitor for ASTPrinter { fn visit_variable(&mut self, name: &lex::Token) -> String { name.lexeme().to_owned() } + + fn visit_assign(&mut self, name: &lex::Token, value: &Expr) -> String { + todo!() + } } #[cfg(test)] diff --git a/src/parse.rs b/src/parse.rs index b4b0b3d..8dfaa1c 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -155,7 +155,28 @@ fn parse_block_statement>( } fn parse_expression>(iter: &mut Peekable) -> Result { - parse_equality(iter) + parse_assignment(iter) +} + +fn parse_assignment>(iter: &mut Peekable) -> Result { + let expr = parse_equality(iter)?; + match (match_token_kind!(iter, TokenKind::Equal), expr) { + (Err(_), expr) => Ok(expr), + + (Ok(_), Expr::Variable { name }) => { + let rhs = parse_assignment(iter)?; + + Ok(Expr::Assign { + name, + value: Box::new(rhs), + }) + } + + (Ok(equals_token), _) => Err(ParseError { + message: "The left hand side of an assignment must be an identifier".to_string(), + line: equals_token.line().into(), + }), + } } fn parse_equality>(iter: &mut Peekable) -> Result {