From 60bd8f535238b57a95c491f20b133979542e6898 Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 8 Aug 2024 23:28:47 -0400 Subject: [PATCH] Implement subtraction and multiplication --- dust-lang/src/abstract_tree.rs | 5 ++- dust-lang/src/analyzer.rs | 36 ++++++++++++++--- dust-lang/src/lex.rs | 64 +++++++++++++++++++++++++++++++ dust-lang/src/lib.rs | 4 -- dust-lang/src/parse.rs | 33 +++++++++++++++- dust-lang/src/token.rs | 5 +++ dust-lang/src/value.rs | 30 ++++++++++++++- dust-lang/src/vm.rs | 70 +++++++++++++++++++++++++++++++++- 8 files changed, 233 insertions(+), 14 deletions(-) diff --git a/dust-lang/src/abstract_tree.rs b/dust-lang/src/abstract_tree.rs index 7eefe80..d0c98b7 100644 --- a/dust-lang/src/abstract_tree.rs +++ b/dust-lang/src/abstract_tree.rs @@ -53,6 +53,7 @@ pub enum Statement { value_arguments: Option>, }, PropertyAccess(Box, Box), + Subtract(Box, Box), List(Vec), Multiply(Box, Box), @@ -75,6 +76,7 @@ impl Statement { Statement::List(_) => None, Statement::Multiply(left, _) => left.statement.expected_type(variables), Statement::PropertyAccess(_, _) => None, + Statement::Subtract(left, _) => left.statement.expected_type(variables), } } } @@ -154,7 +156,6 @@ impl Display for Statement { write!(f, ")") } - Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"), Statement::List(nodes) => { write!(f, "[")?; for (i, node) in nodes.iter().enumerate() { @@ -165,9 +166,11 @@ impl Display for Statement { } write!(f, "]") } + Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"), Statement::Multiply(left, right) => write!(f, "{left} * {right}"), Statement::Constant(value) => write!(f, "{value}"), Statement::Identifier(identifier) => write!(f, "{identifier}"), + Statement::Subtract(left, right) => write!(f, "{left} - {right}"), } } } diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index 0adcb35..a6685fb 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -75,6 +75,9 @@ impl<'a> Analyzer<'a> { fn analyze_node(&self, node: &Node) -> Result<(), AnalyzerError> { match &node.statement { Statement::Add(left, right) => { + self.analyze_node(left)?; + self.analyze_node(right)?; + let left_type = left.statement.expected_type(self.variables); let right_type = right.statement.expected_type(self.variables); @@ -95,9 +98,6 @@ impl<'a> Analyzer<'a> { }); } } - - self.analyze_node(left)?; - self.analyze_node(right)?; } Statement::Assign(left, right) => { if let Statement::Identifier(_) = &left.statement { @@ -135,6 +135,9 @@ impl<'a> Analyzer<'a> { } } Statement::Multiply(left, right) => { + self.analyze_node(left)?; + self.analyze_node(right)?; + if let Some(Type::Integer) | Some(Type::Float) = left.statement.expected_type(self.variables) { @@ -154,9 +157,6 @@ impl<'a> Analyzer<'a> { position: right.position, }); } - - self.analyze_node(left)?; - self.analyze_node(right)?; } Statement::PropertyAccess(left, right) => { if let Statement::Identifier(_) | Statement::Constant(_) | Statement::List(_) = @@ -184,6 +184,30 @@ impl<'a> Analyzer<'a> { self.analyze_node(right)?; } + Statement::Subtract(left, right) => { + self.analyze_node(left)?; + self.analyze_node(right)?; + + let left_type = left.statement.expected_type(self.variables); + let right_type = right.statement.expected_type(self.variables); + + match (left_type, right_type) { + (Some(Type::Integer), Some(Type::Integer)) => {} + (Some(Type::Float), Some(Type::Float)) => {} + (Some(Type::Integer), _) | (Some(Type::Float), _) => { + return Err(AnalyzerError::ExpectedIntegerOrFloat { + actual: right.as_ref().clone(), + position: right.position, + }); + } + _ => { + return Err(AnalyzerError::ExpectedIntegerOrFloat { + actual: left.as_ref().clone(), + position: left.position, + }); + } + } + } } Ok(()) diff --git a/dust-lang/src/lex.rs b/dust-lang/src/lex.rs index 9a6de23..6faa209 100644 --- a/dust-lang/src/lex.rs +++ b/dust-lang/src/lex.rs @@ -103,6 +103,14 @@ impl Lexer { let (token, span) = if let Some(c) = self.peek_char(source) { match c { '0'..='9' => self.lex_number(source)?, + '-' => { + if let Some('0'..='9') = self.peek_second_char(source) { + self.lex_number(source)? + } else { + self.position += 1; + (Token::Minus, (self.position - 1, self.position)) + } + } 'a'..='z' | 'A'..='Z' => self.lex_alphabetical(source)?, '"' => self.lex_string('"', source)?, '\'' => self.lex_string('\'', source)?, @@ -185,6 +193,10 @@ impl Lexer { let start_pos = self.position; let mut is_float = false; + if let Some('-') = self.peek_char(source) { + self.next_char(source); + } + while let Some(c) = self.peek_char(source) { if c == '.' { if let Some('0'..='9') = self.peek_second_char(source) { @@ -324,8 +336,60 @@ impl From for LexError { #[cfg(test)] mod tests { + use super::*; + #[test] + fn max_integer() { + let input = "9223372036854775807"; + + assert_eq!( + lex(input), + Ok(vec![ + (Token::Integer(i64::MAX), (0, 19)), + (Token::Eof, (19, 19)), + ]) + ) + } + + #[test] + fn min_integer() { + let input = "-9223372036854775808"; + + assert_eq!( + lex(input), + Ok(vec![ + (Token::Integer(i64::MIN), (0, 20)), + (Token::Eof, (20, 20)), + ]) + ) + } + + #[test] + fn subtract_negative_integers() { + let input = "-42 - -42"; + + assert_eq!( + lex(input), + Ok(vec![ + (Token::Integer(-42), (0, 3)), + (Token::Minus, (4, 5)), + (Token::Integer(-42), (6, 9)), + (Token::Eof, (9, 9)), + ]) + ) + } + + #[test] + fn negative_integer() { + let input = "-42"; + + assert_eq!( + lex(input), + Ok(vec![(Token::Integer(-42), (0, 3)), (Token::Eof, (3, 3))]) + ) + } + #[test] fn read_line() { let input = "read_line()"; diff --git a/dust-lang/src/lib.rs b/dust-lang/src/lib.rs index 093a186..81e6369 100644 --- a/dust-lang/src/lib.rs +++ b/dust-lang/src/lib.rs @@ -1,10 +1,6 @@ //! The Dust programming language. //! //! Dust is a statically typed, interpreted programming language. -//! -//! The [interpreter] module contains the `Interpreter` struct, which is used to lex, parse and/or -//! interpret Dust code. The `interpret` function is a convenience function that creates a new -//! `Interpreter` and runs the given source code. pub mod abstract_tree; pub mod analyzer; pub mod built_in_function; diff --git a/dust-lang/src/parse.rs b/dust-lang/src/parse.rs index d2c28ff..e5b604a 100644 --- a/dust-lang/src/parse.rs +++ b/dust-lang/src/parse.rs @@ -183,6 +183,17 @@ impl<'src> Parser<'src> { (left_start, right_end), )); } + (Token::Minus, _) => { + self.next_token()?; + + let right_node = self.parse_node(self.current_precedence())?; + let right_end = right_node.position.1; + + return Ok(Node::new( + Statement::Subtract(Box::new(left_node), Box::new(right_node)), + (left_start, right_end), + )); + } _ => {} } } @@ -338,8 +349,9 @@ impl<'src> Parser<'src> { match self.current.0 { Token::Dot => 4, Token::Equal => 3, - Token::Plus => 1, Token::Star => 2, + Token::Plus => 1, + Token::Minus => 1, _ => 0, } } @@ -394,6 +406,25 @@ mod tests { use super::*; + #[test] + fn subtract_negative_integers() { + let input = "-1 - -2"; + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::Subtract( + Box::new(Node::new(Statement::Constant(Value::integer(-1)), (0, 2))), + Box::new(Node::new(Statement::Constant(Value::integer(-2)), (5, 7))) + ), + (0, 7) + )] + .into() + }) + ); + } + #[test] fn string_concatenation() { let input = "\"Hello, \" + \"World!\""; diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 50239d6..565a439 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -29,6 +29,7 @@ pub enum Token<'src> { Equal, LeftParenthesis, LeftSquareBrace, + Minus, Plus, RightParenthesis, RightSquareBrace, @@ -58,6 +59,7 @@ impl<'src> Token<'src> { Token::RightParenthesis => TokenOwned::RightParenthesis, Token::LeftSquareBrace => TokenOwned::LeftSquareBrace, Token::RightSquareBrace => TokenOwned::RightSquareBrace, + Token::Minus => TokenOwned::Minus, } } } @@ -85,6 +87,7 @@ impl<'src> Display for Token<'src> { Token::RightParenthesis => write!(f, ")"), Token::LeftSquareBrace => write!(f, "["), Token::RightSquareBrace => write!(f, "]"), + Token::Minus => write!(f, "-"), } } } @@ -117,6 +120,7 @@ pub enum TokenOwned { Equal, LeftParenthesis, LeftSquareBrace, + Minus, Plus, RightParenthesis, RightSquareBrace, @@ -146,6 +150,7 @@ impl Display for TokenOwned { TokenOwned::RightParenthesis => write!(f, ")"), TokenOwned::LeftSquareBrace => write!(f, "["), TokenOwned::RightSquareBrace => write!(f, "]"), + TokenOwned::Minus => write!(f, "-"), } } } diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index 1fea438..cf53d50 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -129,11 +129,31 @@ impl Value { match (self.inner().as_ref(), other.inner().as_ref()) { (ValueInner::Float(left), ValueInner::Float(right)) => Ok(Value::float(left + right)), (ValueInner::Integer(left), ValueInner::Integer(right)) => { - Ok(Value::integer(left + right)) + Ok(Value::integer(left.saturating_add(*right))) } _ => Err(ValueError::CannotAdd(self.clone(), other.clone())), } } + + pub fn subtract(&self, other: &Value) -> Result { + match (self.inner().as_ref(), other.inner().as_ref()) { + (ValueInner::Float(left), ValueInner::Float(right)) => Ok(Value::float(left - right)), + (ValueInner::Integer(left), ValueInner::Integer(right)) => { + Ok(Value::integer(left.saturating_sub(*right))) + } + _ => Err(ValueError::CannotSubtract(self.clone(), other.clone())), + } + } + + pub fn multiply(&self, other: &Value) -> Result { + match (self.inner().as_ref(), other.inner().as_ref()) { + (ValueInner::Float(left), ValueInner::Float(right)) => Ok(Value::float(left * right)), + (ValueInner::Integer(left), ValueInner::Integer(right)) => { + Ok(Value::integer(left * right)) + } + _ => Err(ValueError::CannotMultiply(self.clone(), other.clone())), + } + } } impl Display for Value { @@ -653,6 +673,8 @@ impl Display for Function { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ValueError { CannotAdd(Value, Value), + CannotMultiply(Value, Value), + CannotSubtract(Value, Value), PropertyNotFound { value: Value, property: Identifier }, IndexOutOfBounds { value: Value, index: i64 }, ExpectedList(Value), @@ -664,6 +686,12 @@ impl Display for ValueError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { ValueError::CannotAdd(left, right) => write!(f, "Cannot add {} and {}", left, right), + ValueError::CannotMultiply(left, right) => { + write!(f, "Cannot multiply {} and {}", left, right) + } + ValueError::CannotSubtract(left, right) => { + write!(f, "Cannot subtract {} and {}", left, right) + } ValueError::PropertyNotFound { value, property } => { write!(f, "{} does not have a property named {}", value, property) } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index c9f51aa..76e8be9 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -182,7 +182,27 @@ impl Vm { Ok(Some(Value::list(values))) } - Statement::Multiply(_, _) => todo!(), + Statement::Multiply(left, right) => { + let left_span = left.position; + let left = if let Some(value) = self.run_node(*left, variables)? { + value + } else { + return Err(VmError::ExpectedValue { + position: left_span, + }); + }; + let right_span = right.position; + let right = if let Some(value) = self.run_node(*right, variables)? { + value + } else { + return Err(VmError::ExpectedValue { + position: right_span, + }); + }; + let product = left.multiply(&right)?; + + Ok(Some(product)) + } Statement::PropertyAccess(left, right) => { let left_span = left.position; let left_value = if let Some(value) = self.run_node(*left, variables)? { @@ -239,6 +259,27 @@ impl Vm { position: right_span, }) } + Statement::Subtract(left, right) => { + let left_span = left.position; + let left = if let Some(value) = self.run_node(*left, variables)? { + value + } else { + return Err(VmError::ExpectedValue { + position: left_span, + }); + }; + let right_span = right.position; + let right = if let Some(value) = self.run_node(*right, variables)? { + value + } else { + return Err(VmError::ExpectedValue { + position: right_span, + }); + }; + let difference = left.subtract(&right)?; + + Ok(Some(difference)) + } } } } @@ -341,6 +382,33 @@ impl Display for VmError { mod tests { use super::*; + #[test] + fn integer_saturating_add() { + let input = "9223372036854775807 + 1"; + + assert_eq!( + run(input, &mut HashMap::new()), + Ok(Some(Value::integer(i64::MAX))) + ); + } + + #[test] + fn integer_saturating_sub() { + let input = "-9223372036854775808 - 1"; + + assert_eq!( + run(input, &mut HashMap::new()), + Ok(Some(Value::integer(i64::MIN))) + ); + } + + #[test] + fn multiply() { + let input = "2 * 3"; + + assert_eq!(run(input, &mut HashMap::new()), Ok(Some(Value::integer(6)))); + } + #[test] fn boolean() { let input = "true";