From 64a3ce4cd3455c7f372f4b1afc0f912ee3f28f89 Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 13 Aug 2024 21:24:56 -0400 Subject: [PATCH] Implement subtraction assignment --- dust-lang/src/abstract_tree.rs | 14 ++++++ dust-lang/src/lexer.rs | 35 ++++++++++++++- dust-lang/src/parser.rs | 80 ++++++++++++++++++++++++++++++++-- dust-lang/src/token.rs | 20 +++++---- dust-lang/src/vm.rs | 32 +++++++++++++- 5 files changed, 168 insertions(+), 13 deletions(-) diff --git a/dust-lang/src/abstract_tree.rs b/dust-lang/src/abstract_tree.rs index b27fa5d..c2343d0 100644 --- a/dust-lang/src/abstract_tree.rs +++ b/dust-lang/src/abstract_tree.rs @@ -14,6 +14,20 @@ pub struct AbstractSyntaxTree { pub nodes: VecDeque>, } +impl AbstractSyntaxTree { + pub fn new() -> Self { + Self { + nodes: VecDeque::new(), + } + } +} + +impl Default for AbstractSyntaxTree { + fn default() -> Self { + Self::new() + } +} + #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub struct Node { pub inner: T, diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index fae4a07..bb83dfc 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -103,7 +103,13 @@ impl Lexer { match c { '0'..='9' => self.lex_number(source)?, '-' => { - if let Some('0'..='9') = self.peek_second_char(source) { + let second_char = self.peek_second_char(source); + + if let Some('=') = second_char { + self.position += 2; + + (Token::MinusEqual, (self.position - 2, self.position)) + } else if let Some('0'..='9') = second_char { self.lex_number(source)? } else if "-Infinity" == self.peek_chars(source, 9) { self.position += 9; @@ -499,6 +505,33 @@ impl Display for LexError { mod tests { use super::*; + #[test] + fn all_keywords() { + let input = "bool else false float if int is_even is_odd length read_line struct to_string true while write_line"; + + assert_eq!( + lex(input), + Ok(vec![ + (Token::Bool, (0, 4)), + (Token::Else, (5, 9)), + (Token::Boolean("false"), (10, 15)), + (Token::FloatKeyword, (16, 21)), + (Token::If, (22, 24)), + (Token::Int, (25, 28)), + (Token::IsEven, (29, 36)), + (Token::IsOdd, (37, 43)), + (Token::Length, (44, 50)), + (Token::ReadLine, (51, 60)), + (Token::Struct, (61, 67)), + (Token::ToString, (68, 77)), + (Token::Boolean("true"), (78, 82)), + (Token::While, (83, 88)), + (Token::WriteLine, (89, 99)), + (Token::Eof, (99, 99)), + ]) + ); + } + #[test] fn unit_struct() { let input = "struct Foo"; diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index d08224c..358846d 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -72,6 +72,31 @@ pub fn parse(source: &str) -> Result { Ok(AbstractSyntaxTree { nodes }) } +pub fn parse_into<'src>( + source: &'src str, + tree: &mut AbstractSyntaxTree, +) -> Result<(), DustError<'src>> { + let lexer = Lexer::new(); + let mut parser = Parser::new(source, lexer); + + loop { + let node = parser + .parse() + .map_err(|parse_error| DustError::ParseError { + parse_error, + source, + })?; + + tree.nodes.push_back(node); + + if let Token::Eof = parser.current.0 { + break; + } + } + + Ok(()) +} + /// Low-level tool for parsing the input a statement at a time. /// /// # Examples @@ -133,8 +158,12 @@ impl<'src> Parser<'src> { fn parse_statement(&mut self, mut precedence: u8) -> Result, ParseError> { // Parse a statement starting from the current node. let mut left = if self.current.0.is_prefix() { + log::trace!("Parsing {} as prefix operator", self.current.0); + self.parse_prefix()? } else { + log::trace!("Parsing {} as primary", self.current.0); + self.parse_primary()? }; @@ -142,6 +171,8 @@ impl<'src> Parser<'src> { while precedence < self.current.0.precedence() { // Give precedence to postfix operations left = if self.current.0.is_postfix() { + log::trace!("Parsing {} as postfix operator", self.current.0); + // Replace the left-hand side with the postfix operation let statement = self.parse_postfix(left)?; @@ -149,6 +180,8 @@ impl<'src> Parser<'src> { statement } else { + log::trace!("Parsing {} as infix operator", self.current.0); + // Replace the left-hand side with the infix operation self.parse_infix(left)? }; @@ -735,13 +768,13 @@ impl<'src> Parser<'src> { let left_start = left.position.0; if let Token::Equal | Token::PlusEqual | Token::MinusEqual = &self.current.0 { - let operator_position = self.current.1; let operator = match self.current.0 { Token::Equal => AssignmentOperator::Assign, Token::PlusEqual => AssignmentOperator::AddAssign, Token::MinusEqual => AssignmentOperator::SubtractAssign, _ => unreachable!(), }; + let operator_position = self.current.1; self.next_token()?; @@ -891,10 +924,10 @@ impl<'src> Parser<'src> { } } - let right_end = self.current.1 .1; - self.next_token()?; + let right_end = self.current.1 .1; + Node::new( Statement::Invokation { invokee: Box::new(left), @@ -1147,6 +1180,47 @@ mod tests { use super::*; + #[test] + fn tuple_struct_access() { + let input = "Foo(42, 'bar').0"; + let mut tree = AbstractSyntaxTree::new(); + + if parse_into(input, &mut tree).is_err() { + println!("{:?}", tree); + } + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::BinaryOperation { + left: Box::new(Node::new( + Statement::Invokation { + invokee: Box::new(Node::new( + Statement::Identifier(Identifier::new("Foo")), + (0, 3) + )), + type_arguments: None, + value_arguments: Some(vec![ + Node::new(Statement::Constant(Value::integer(42)), (4, 6)), + Node::new(Statement::Constant(Value::string("bar")), (8, 11)) + ]), + }, + (0, 12) + )), + operator: Node::new(BinaryOperator::FieldAccess, (13, 14)), + right: Box::new(Node::new( + Statement::Constant(Value::integer(0)), + (15, 16) + )) + }, + (0, 16) + )] + .into() + }) + ); + } + #[test] fn fields_struct_instantiation() { let input = "Foo { a = 42, b = 4.0 }"; diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 3206e74..f916219 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -237,7 +237,7 @@ impl<'src> Token<'src> { | Token::GreaterEqual => 5, Token::DoubleAmpersand => 4, Token::DoublePipe => 3, - Token::Equal | Token::PlusEqual => 2, + Token::Equal | Token::MinusEqual | Token::PlusEqual => 2, Token::DoubleDot | Token::Semicolon => 1, _ => 0, } @@ -507,12 +507,13 @@ impl Display for TokenKind { } #[cfg(test)] -mod tests { +pub(crate) mod tests { use super::*; - fn all_tokens<'src>() -> [Token<'src>; 42] { + pub fn all_tokens<'src>() -> [Token<'src>; 46] { [ Token::Bang, + Token::Bool, Token::Boolean("true"), Token::Colon, Token::Comma, @@ -524,12 +525,14 @@ mod tests { Token::Else, Token::Eof, Token::Equal, - Token::Float("42.0"), + Token::Float("0.0"), + Token::FloatKeyword, Token::Greater, Token::GreaterEqual, - Token::Identifier("foobar"), + Token::Identifier(""), Token::If, - Token::Integer("42"), + Token::Int, + Token::Integer("0"), Token::IsEven, Token::IsOdd, Token::LeftCurlyBrace, @@ -539,6 +542,7 @@ mod tests { Token::Less, Token::LessEqual, Token::Minus, + Token::MinusEqual, Token::Percent, Token::Plus, Token::PlusEqual, @@ -548,8 +552,8 @@ mod tests { Token::RightSquareBrace, Token::Semicolon, Token::Star, - Token::Slash, - Token::String("foobar"), + Token::Str, + Token::String(""), Token::Struct, Token::ToString, Token::While, diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 8475ba2..8f246b2 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -147,7 +147,30 @@ impl Vm { Ok(None) } AssignmentOperator::SubtractAssign => { - todo!() + let left_value = if let Some(value) = self.context.get_value(&identifier.inner) + { + value + } else { + return Err(VmError::UndefinedVariable { identifier }); + }; + let value_position = value.position; + let right_value = if let Some(value) = self.run_statement(*value)? { + value + } else { + return Err(VmError::ExpectedValue { + position: value_position, + }); + }; + let new_value = left_value.subtract(&right_value).map_err(|value_error| { + VmError::ValueError { + error: value_error, + position: (identifier.position.0, value_position.1), + } + })?; + + self.context.set_value(identifier.inner, new_value); + + Ok(None) } }, Statement::BinaryOperation { @@ -1032,6 +1055,13 @@ mod tests { assert_eq!(run(input), Ok(Some(Value::integer(5)))); } + #[test] + fn subtract_assign() { + let input = "x = 1; x -= 1; x"; + + assert_eq!(run(input), Ok(Some(Value::integer(0)))); + } + #[test] fn add_assign() { let input = "x = 1; x += 1; x";