From 82fbf796f3ed8dabd85de598c3aa2b48cee4a6c3 Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 9 Aug 2024 18:14:46 -0400 Subject: [PATCH] Implement postfix parsing --- dust-lang/src/abstract_tree.rs | 28 ++--- dust-lang/src/analyzer.rs | 43 +++---- dust-lang/src/lex.rs | 25 +++- dust-lang/src/parse.rs | 207 ++++++++++++++++++--------------- dust-lang/src/token.rs | 10 +- dust-lang/src/vm.rs | 128 +++++++++++++------- 6 files changed, 263 insertions(+), 178 deletions(-) diff --git a/dust-lang/src/abstract_tree.rs b/dust-lang/src/abstract_tree.rs index 4101b16..957f79e 100644 --- a/dust-lang/src/abstract_tree.rs +++ b/dust-lang/src/abstract_tree.rs @@ -34,12 +34,6 @@ impl Display for Node { #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub enum Statement { - // Variable assignment - Assignment { - identifier: Node, - value_node: Box>, - }, - // A sequence of statements Block(Vec>), @@ -70,7 +64,7 @@ pub enum Statement { // Value collection expressions List(Vec>), - Map(Vec<(Node, Node)>), + Map(Vec<(Node, Node)>), // Hard-coded value Constant(Value), @@ -83,7 +77,6 @@ pub enum Statement { impl Statement { pub fn expected_type(&self, variables: &HashMap) -> Option { match self { - Statement::Assignment { .. } => None, Statement::Block(nodes) => nodes.last().unwrap().inner.expected_type(variables), Statement::BinaryOperation { left, .. } => left.inner.expected_type(variables), Statement::BuiltInFunctionCall { function, .. } => function.expected_return_type(), @@ -104,10 +97,9 @@ impl Statement { let mut types = BTreeMap::new(); for (identifier, item) in nodes { - types.insert( - identifier.inner.clone(), - item.inner.expected_type(variables)?, - ); + if let Statement::Identifier(identifier) = &identifier.inner { + types.insert(identifier.clone(), item.inner.expected_type(variables)?); + } } Some(Type::Map(types)) @@ -121,12 +113,6 @@ impl Statement { impl Display for Statement { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Statement::Assignment { - identifier, - value_node: value, - } => { - write!(f, "{identifier} = {value}") - } Statement::Block(statements) => { write!(f, "{{ ")?; @@ -270,12 +256,18 @@ pub enum BinaryOperator { // Logic And, Or, + + // Assignment + Assign, + AddAssign, } impl Display for BinaryOperator { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { BinaryOperator::Add => write!(f, "+"), + BinaryOperator::AddAssign => write!(f, "+="), + BinaryOperator::Assign => write!(f, "="), BinaryOperator::And => write!(f, "&&"), BinaryOperator::Divide => write!(f, "/"), BinaryOperator::Equal => write!(f, "=="), diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index 97a2792..4e3a7b7 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -77,18 +77,6 @@ impl<'a> Analyzer<'a> { fn analyze_node(&self, node: &Node) -> Result<(), AnalyzerError> { match &node.inner { - Statement::Assignment { - value_node: value, .. - } => { - self.analyze_node(value)?; - - if value.inner.expected_type(self.variables).is_none() { - return Err(AnalyzerError::ExpectedValue { - actual: value.as_ref().clone(), - position: value.position, - }); - } - } Statement::BinaryOperation { left, operator, @@ -97,6 +85,12 @@ impl<'a> Analyzer<'a> { self.analyze_node(left)?; self.analyze_node(right)?; + if let BinaryOperator::AddAssign | BinaryOperator::Assign = operator.inner { + if let Statement::Identifier(_) = left.inner { + return Ok(()); + } + } + let left_type = left.inner.expected_type(self.variables); let right_type = right.inner.expected_type(self.variables); @@ -157,11 +151,12 @@ impl<'a> Analyzer<'a> { }); } } - Statement::Identifier(_) => { - return Err(AnalyzerError::UnexpectedIdentifier { - identifier: node.clone(), - position: node.position, - }); + Statement::Identifier(identifier) => { + if !self.variables.contains_key(identifier) { + return Err(AnalyzerError::UndefinedVariable { + identifier: node.clone(), + }); + } } Statement::List(statements) => { for statement in statements { @@ -251,6 +246,9 @@ pub enum AnalyzerError { actual: Node, position: Span, }, + UndefinedVariable { + identifier: Node, + }, UnexpectedIdentifier { identifier: Node, position: Span, @@ -274,6 +272,7 @@ impl AnalyzerError { AnalyzerError::ExpectedIntegerFloatOrString { position, .. } => *position, AnalyzerError::ExpectedSameType { position, .. } => *position, AnalyzerError::ExpectedString { position, .. } => *position, + AnalyzerError::UndefinedVariable { identifier } => identifier.position, AnalyzerError::UnexpectedIdentifier { position, .. } => *position, AnalyzerError::UnexectedString { position, .. } => *position, } @@ -315,6 +314,9 @@ impl Display for AnalyzerError { AnalyzerError::ExpectedValue { actual, .. } => { write!(f, "Expected value, found {}", actual) } + AnalyzerError::UndefinedVariable { identifier } => { + write!(f, "Undefined variable {}", identifier) + } AnalyzerError::UnexpectedIdentifier { identifier, .. } => { write!(f, "Unexpected identifier {}", identifier) } @@ -443,7 +445,7 @@ mod tests { } #[test] - fn unexpected_identifier() { + fn undefined_variable() { let abstract_tree = AbstractSyntaxTree { nodes: [Node::new( Statement::Identifier(Identifier::new("x")), @@ -456,9 +458,8 @@ mod tests { assert_eq!( analyzer.analyze(), - Err(AnalyzerError::UnexpectedIdentifier { - identifier: Node::new(Statement::Identifier(Identifier::new("x")), (0, 1)), - position: (0, 1) + Err(AnalyzerError::UndefinedVariable { + identifier: Node::new(Statement::Identifier(Identifier::new("x")), (0, 1)) }) ) } diff --git a/dust-lang/src/lex.rs b/dust-lang/src/lex.rs index 1b7dc70..6880bdd 100644 --- a/dust-lang/src/lex.rs +++ b/dust-lang/src/lex.rs @@ -122,9 +122,15 @@ impl Lexer { '"' => self.lex_string('"', source)?, '\'' => self.lex_string('\'', source)?, '+' => { - self.position += 1; + if let Some('=') = self.peek_second_char(source) { + self.position += 2; - (Token::Plus, (self.position - 1, self.position)) + (Token::PlusEqual, (self.position - 2, self.position)) + } else { + self.position += 1; + + (Token::Plus, (self.position - 1, self.position)) + } } '*' => { self.position += 1; @@ -452,6 +458,21 @@ impl Display for LexError { mod tests { use super::*; + #[test] + fn add_assign() { + let input = "x += 42"; + + assert_eq!( + lex(input), + Ok(vec![ + (Token::Identifier("x"), (0, 1)), + (Token::PlusEqual, (2, 4)), + (Token::Integer("42"), (5, 7)), + (Token::Eof, (7, 7)), + ]) + ) + } + #[test] fn or() { let input = "true || false"; diff --git a/dust-lang/src/parse.rs b/dust-lang/src/parse.rs index cc3fdcc..348cd7d 100644 --- a/dust-lang/src/parse.rs +++ b/dust-lang/src/parse.rs @@ -202,25 +202,10 @@ impl<'src> Parser<'src> { (Token::Identifier(text), position) => { self.next_token()?; - if let (Token::Equal, _) = self.current { - self.next_token()?; - - let value_node = self.parse_node(0)?; - let right_end = value_node.position.1; - - Ok(Node::new( - Statement::Assignment { - identifier: Node::new(Identifier::new(text), position), - value_node: Box::new(value_node), - }, - (position.0, right_end), - )) - } else { - Ok(Node::new( - Statement::Identifier(Identifier::new(text)), - position, - )) - } + Ok(Node::new( + Statement::Identifier(Identifier::new(text)), + position, + )) } (Token::String(string), position) => { self.next_token()?; @@ -259,13 +244,17 @@ impl<'src> Parser<'src> { let next_node = self.parse_node(0)?; // If the next node is an assignment, this might be a map - if let Statement::Assignment { - identifier, - value_node, + if let Statement::BinaryOperation { + left, + operator: + Node { + inner: BinaryOperator::Assign, + .. + }, + right, } = next_node.inner { - // If the current token is a comma, right curly brace, or the new - // statement is already a map + // If the current token is a comma, or the new statement is already a map if self.current.0 == Token::Comma || statement .as_ref() @@ -281,38 +270,9 @@ impl<'src> Parser<'src> { } // Add the new property to the map - map_properties.push((identifier, *value_node)); - } - // Otherwise, the new statement is a block - } else if let Statement::Block(statements) = - statement.get_or_insert_with(|| Statement::Block(Vec::new())) - { - if self.current.0 == Token::Semicolon { - self.next_token()?; - - statements.push(Node::new( - Statement::Nil(Box::new(Node::new( - Statement::Assignment { - identifier, - value_node, - }, - next_node.position, - ))), - (next_node.position.0, self.current.1 .1), - )); - - continue; - } else { - statements.push(Node::new( - Statement::Assignment { - identifier, - value_node, - }, - next_node.position, - )); - - continue; + map_properties.push((*left, *right)); } + // Otherwise, the new statement is a block } } else if let Statement::Block(statements) = statement.get_or_insert_with(|| Statement::Block(Vec::new())) @@ -443,27 +403,42 @@ impl<'src> Parser<'src> { fn parse_infix(&mut self, left: Node) -> Result, ParseError> { let left_start = left.position.0; + // Postfix operations + if let Token::Semicolon = &self.current.0 { + self.next_token()?; + + let right_end = self.current.1 .1; + + return Ok(Node::new( + Statement::Nil(Box::new(left)), + (left_start, right_end), + )); + }; + + // Infix operations let binary_operator = match &self.current { (Token::Dot, _) => { self.next_token()?; - let right_node = self.parse_node(0)?; - let right_end = right_node.position.1; + let right = self.parse_node(0)?; + let right_end = right.position.1; return Ok(Node::new( - Statement::PropertyAccess(Box::new(left), Box::new(right_node)), + Statement::PropertyAccess(Box::new(left), Box::new(right)), (left_start, right_end), )); } (Token::DoubleAmpersand, _) => Node::new(BinaryOperator::And, self.current.1), (Token::DoubleEqual, _) => Node::new(BinaryOperator::Equal, self.current.1), (Token::DoublePipe, _) => Node::new(BinaryOperator::Or, self.current.1), + (Token::Equal, _) => Node::new(BinaryOperator::Assign, self.current.1), (Token::Greater, _) => Node::new(BinaryOperator::Greater, self.current.1), (Token::GreaterEqual, _) => Node::new(BinaryOperator::GreaterOrEqual, self.current.1), (Token::Less, _) => Node::new(BinaryOperator::Less, self.current.1), (Token::LessEqual, _) => Node::new(BinaryOperator::LessOrEqual, self.current.1), (Token::Minus, _) => Node::new(BinaryOperator::Subtract, self.current.1), (Token::Plus, _) => Node::new(BinaryOperator::Add, self.current.1), + (Token::PlusEqual, _) => Node::new(BinaryOperator::AddAssign, self.current.1), (Token::Star, _) => Node::new(BinaryOperator::Multiply, self.current.1), (Token::Slash, _) => Node::new(BinaryOperator::Divide, self.current.1), (Token::Percent, _) => Node::new(BinaryOperator::Modulo, self.current.1), @@ -479,6 +454,7 @@ impl<'src> Parser<'src> { self.next_token()?; + let left_start = left.position.0; let right = self.parse_node(0)?; let right_end = right.position.1; @@ -494,6 +470,8 @@ impl<'src> Parser<'src> { fn current_precedence(&self) -> u8 { match self.current.0 { + Token::Semicolon => 10, + Token::Equal | Token::PlusEqual => 8, Token::DoubleEqual => 7, Token::DoubleAmpersand | Token::DoublePipe => 6, Token::Greater | Token::GreaterEqual | Token::Less | Token::LessEqual => 5, @@ -506,6 +484,17 @@ impl<'src> Parser<'src> { _ => 0, } } + + fn peek_token(&mut self) -> Token { + self.lexer + .peek_token(self.source) + .map(|(token, _)| token) + .unwrap_or(Token::Eof) + } + + fn next_is_postfix(&mut self) -> bool { + matches!(self.peek_token(), Token::Semicolon) + } } #[derive(Debug, PartialEq, Clone)] @@ -588,6 +577,29 @@ mod tests { use super::*; + #[test] + fn add_assign() { + let input = "a += 1"; + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::BinaryOperation { + left: Box::new(Node::new( + Statement::Identifier(Identifier::new("a")), + (0, 1) + )), + operator: Node::new(BinaryOperator::AddAssign, (2, 4)), + right: Box::new(Node::new(Statement::Constant(Value::integer(1)), (5, 6))), + }, + (0, 6) + )] + .into() + }) + ); + } + #[test] fn or() { let input = "true || false"; @@ -667,37 +679,49 @@ mod tests { Statement::Block(vec![ Node::new( Statement::Nil(Box::new(Node::new( - Statement::Assignment { - identifier: Node::new(Identifier::new("foo"), (2, 5)), - value_node: Box::new(Node::new( + Statement::BinaryOperation { + left: Box::new(Node::new( + Statement::Identifier(Identifier::new("foo")), + (2, 5) + )), + operator: Node::new(BinaryOperator::Assign, (6, 8)), + right: Box::new(Node::new( Statement::Constant(Value::integer(42)), - (8, 10) - )) + (9, 11) + )), }, - (2, 10) + (2, 11) ),)), (2, 15) ), Node::new( Statement::Nil(Box::new(Node::new( - Statement::Assignment { - identifier: Node::new(Identifier::new("bar"), (12, 15)), - value_node: Box::new(Node::new( + Statement::BinaryOperation { + left: Box::new(Node::new( + Statement::Identifier(Identifier::new("bar")), + (16, 19) + )), + operator: Node::new(BinaryOperator::Assign, (20, 22)), + right: Box::new(Node::new( Statement::Constant(Value::integer(42)), - (18, 20) - )) + (23, 25) + )), }, (12, 20) ),)), (12, 25) ), Node::new( - Statement::Assignment { - identifier: Node::new(Identifier::new("baz"), (22, 25)), - value_node: Box::new(Node::new( + Statement::BinaryOperation { + left: Box::new(Node::new( + Statement::Identifier(Identifier::new("baz")), + (26, 29) + )), + operator: Node::new(BinaryOperator::Assign, (30, 32)), + right: Box::new(Node::new( Statement::Constant(Value::string("42")), - (28, 32) - )) + (22, 32) + )), }, (22, 32) ) @@ -731,15 +755,15 @@ mod tests { nodes: [Node::new( Statement::Map(vec![ ( - Node::new(Identifier::new("foo"), (2, 5)), + Node::new(Statement::Identifier(Identifier::new("foo")), (2, 5)), Node::new(Statement::Constant(Value::integer(42)), (8, 10)) ), ( - Node::new(Identifier::new("bar"), (12, 15)), + Node::new(Statement::Identifier(Identifier::new("bar")), (12, 15)), Node::new(Statement::Constant(Value::integer(42)), (18, 20)) ), ( - Node::new(Identifier::new("baz"), (22, 25)), + Node::new(Statement::Identifier(Identifier::new("baz")), (22, 25)), Node::new(Statement::Constant(Value::string("42")), (28, 32)) ), ]), @@ -760,11 +784,11 @@ mod tests { nodes: [Node::new( Statement::Map(vec![ ( - Node::new(Identifier::new("x"), (2, 3)), + Node::new(Statement::Identifier(Identifier::new("x")), (2, 3)), Node::new(Statement::Constant(Value::integer(42)), (6, 8)) ), ( - Node::new(Identifier::new("y"), (10, 11)), + Node::new(Statement::Identifier(Identifier::new("y")), (10, 11)), Node::new(Statement::Constant(Value::string("foobar")), (14, 22)) ) ]), @@ -784,7 +808,7 @@ mod tests { Ok(AbstractSyntaxTree { nodes: [Node::new( Statement::Map(vec![( - Node::new(Identifier::new("x"), (2, 3)), + Node::new(Statement::Identifier(Identifier::new("x")), (2, 3)), Node::new(Statement::Constant(Value::integer(42)), (6, 8)) )]), (0, 11) @@ -854,19 +878,6 @@ mod tests { ); } - #[test] - fn malformed_assignment() { - let input = "false = 1"; - - assert_eq!( - parse(input), - Err(ParseError::UnexpectedToken { - actual: TokenOwned::Equal, - position: (6, 7) - }) - ); - } - #[test] fn less_than() { let input = "1 < 2"; @@ -1278,9 +1289,13 @@ mod tests { parse(input), Ok(AbstractSyntaxTree { nodes: [Node::new( - Statement::Assignment { - identifier: Node::new(Identifier::new("a"), (0, 1)), - value_node: Box::new(Node::new( + Statement::BinaryOperation { + left: Box::new(Node::new( + Statement::Identifier(Identifier::new("a")), + (0, 1) + )), + operator: Node::new(BinaryOperator::Assign, (2, 3)), + right: Box::new(Node::new( Statement::BinaryOperation { left: Box::new(Node::new( Statement::Constant(Value::integer(1)), diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 2440dba..59ee7c6 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -6,13 +6,13 @@ use serde::{Deserialize, Serialize}; /// Source code token. #[derive(Debug, Serialize, Deserialize)] pub enum Token<'src> { + // End of file Eof, - Identifier(&'src str), - // Hard-coded values Boolean(&'src str), Float(&'src str), + Identifier(&'src str), Integer(&'src str), String(&'src str), @@ -40,6 +40,7 @@ pub enum Token<'src> { Minus, Percent, Plus, + PlusEqual, RightCurlyBrace, RightParenthesis, RightSquareBrace, @@ -75,6 +76,7 @@ impl<'src> Token<'src> { Token::Minus => TokenOwned::Minus, Token::Percent => TokenOwned::Percent, Token::Plus => TokenOwned::Plus, + Token::PlusEqual => TokenOwned::PlusEqual, Token::ReadLine => TokenOwned::ReadLine, Token::RightCurlyBrace => TokenOwned::RightCurlyBrace, Token::RightParenthesis => TokenOwned::RightParenthesis, @@ -113,6 +115,7 @@ impl<'src> Token<'src> { Token::Minus => "-", Token::Percent => "%", Token::Plus => "+", + Token::PlusEqual => "+=", Token::ReadLine => "read_line", Token::RightCurlyBrace => "}", Token::RightParenthesis => ")", @@ -159,6 +162,7 @@ impl<'src> PartialEq for Token<'src> { (Token::Minus, Token::Minus) => true, (Token::Percent, Token::Percent) => true, (Token::Plus, Token::Plus) => true, + (Token::PlusEqual, Token::PlusEqual) => true, (Token::ReadLine, Token::ReadLine) => true, (Token::RightCurlyBrace, Token::RightCurlyBrace) => true, (Token::RightParenthesis, Token::RightParenthesis) => true, @@ -212,6 +216,7 @@ pub enum TokenOwned { Minus, Percent, Plus, + PlusEqual, RightCurlyBrace, RightParenthesis, RightSquareBrace, @@ -247,6 +252,7 @@ impl Display for TokenOwned { TokenOwned::Minus => Token::Minus.fmt(f), TokenOwned::Percent => Token::Percent.fmt(f), TokenOwned::Plus => Token::Plus.fmt(f), + TokenOwned::PlusEqual => Token::PlusEqual.fmt(f), TokenOwned::ReadLine => Token::ReadLine.fmt(f), TokenOwned::RightCurlyBrace => Token::RightCurlyBrace.fmt(f), TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f), diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index ba67b19..dc4a5b9 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -52,28 +52,74 @@ impl Vm { variables: &mut HashMap, ) -> Result, VmError> { match node.inner { - Statement::Assignment { - identifier, - value_node, - } => { - let value_node_position = value_node.position; - let value = if let Some(value) = self.run_node(*value_node, variables)? { - value - } else { - return Err(VmError::ExpectedValue { - position: value_node_position, - }); - }; - - variables.insert(identifier.inner, value); - - Ok(None) - } Statement::BinaryOperation { left, operator, right, } => { + let right_position = right.position; + + if let BinaryOperator::Assign = operator.inner { + let identifier = if let Statement::Identifier(identifier) = left.inner { + identifier + } else { + return Err(VmError::ExpectedIdentifier { + position: left.position, + }); + }; + + let value = if let Some(value) = self.run_node(*right, variables)? { + value + } else { + return Err(VmError::ExpectedValue { + position: right_position, + }); + }; + + variables.insert(identifier, value.clone()); + + return Ok(Some(value)); + } + + if let BinaryOperator::AddAssign = operator.inner { + let identifier = if let Statement::Identifier(identifier) = left.inner { + identifier + } else { + return Err(VmError::ExpectedIdentifier { + position: left.position, + }); + }; + + let right_value = if let Some(value) = self.run_node(*right, variables)? { + value + } else { + return Err(VmError::ExpectedValue { + position: right_position, + }); + }; + + let left_value = + variables + .get(&identifier) + .ok_or_else(|| VmError::UndefinedVariable { + identifier: Node::new( + Statement::Identifier(identifier.clone()), + left.position, + ), + })?; + + let new_value = left_value.add(&right_value).map_err(|value_error| { + VmError::ValueError { + error: value_error, + position: right_position, + } + })?; + + variables.insert(identifier, new_value.clone()); + + return Ok(Some(new_value)); + } + let left_position = left.position; let left_value = if let Some(value) = self.run_node(*left, variables)? { value @@ -83,7 +129,6 @@ impl Vm { }); }; - let right_position = right.position; let right_value = if let Some(value) = self.run_node(*right, variables)? { value } else { @@ -92,7 +137,7 @@ impl Vm { }); }; - let result = match operator.inner { + match operator.inner { BinaryOperator::Add => left_value.add(&right_value), BinaryOperator::And => left_value.and(&right_value), BinaryOperator::Divide => left_value.divide(&right_value), @@ -107,13 +152,13 @@ impl Vm { BinaryOperator::Multiply => left_value.multiply(&right_value), BinaryOperator::Or => left_value.or(&right_value), BinaryOperator::Subtract => left_value.subtract(&right_value), + _ => unreachable!(), } + .map(Some) .map_err(|value_error| VmError::ValueError { error: value_error, position: node.position, - })?; - - Ok(Some(result)) + }) } Statement::Block(statements) => { let mut previous_value = None; @@ -206,9 +251,8 @@ impl Vm { if let Some(value) = variables.get(&identifier) { Ok(Some(value.clone())) } else { - Err(VmError::UndefinedIdentifier { - identifier, - position: node.position, + Err(VmError::UndefinedVariable { + identifier: Node::new(Statement::Identifier(identifier), node.position), }) } } @@ -231,6 +275,13 @@ impl Vm { let mut values = BTreeMap::new(); for (identifier, value_node) in nodes { + let identifier = if let Statement::Identifier(identifier) = identifier.inner { + identifier + } else { + return Err(VmError::ExpectedIdentifier { + position: identifier.position, + }); + }; let position = value_node.position; let value = if let Some(value) = self.run_node(value_node, variables)? { value @@ -238,7 +289,7 @@ impl Vm { return Err(VmError::ExpectedValue { position }); }; - values.insert(identifier.inner, value); + values.insert(identifier, value); } Ok(Some(Value::map(values))) @@ -347,9 +398,8 @@ pub enum VmError { ExpectedValue { position: Span, }, - UndefinedIdentifier { - identifier: Identifier, - position: Span, + UndefinedVariable { + identifier: Node, }, } @@ -366,7 +416,7 @@ impl VmError { Self::ExpectedFunction { position, .. } => *position, Self::ExpectedList { position } => *position, Self::ExpectedValue { position } => *position, - Self::UndefinedIdentifier { position, .. } => *position, + Self::UndefinedVariable { identifier } => identifier.position, } } } @@ -430,15 +480,8 @@ impl Display for VmError { Self::ExpectedValue { position } => { write!(f, "Expected a value at position: {:?}", position) } - Self::UndefinedIdentifier { - identifier, - position, - } => { - write!( - f, - "Undefined identifier: {} at position: {:?}", - identifier, position - ) + Self::UndefinedVariable { identifier } => { + write!(f, "Undefined identifier: {}", identifier) } } } @@ -448,6 +491,13 @@ impl Display for VmError { mod tests { use super::*; + #[test] + fn add_assign() { + let input = "x = 1; x += 1; x"; + + assert_eq!(run(input, &mut HashMap::new()), Ok(Some(Value::integer(2)))); + } + #[test] fn or() { let input = "true || false";