diff --git a/dust-lang/src/abstract_tree.rs b/dust-lang/src/abstract_tree.rs index d0c98b7..a910896 100644 --- a/dust-lang/src/abstract_tree.rs +++ b/dust-lang/src/abstract_tree.rs @@ -11,51 +11,61 @@ use crate::{BuiltInFunction, Identifier, Span, Type, Value}; /// In-memory representation of a Dust program. #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub struct AbstractSyntaxTree { - pub nodes: VecDeque, + pub nodes: VecDeque>, } #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct Node { - pub statement: Statement, +pub struct Node { + pub inner: T, pub position: Span, } -impl Node { - pub fn new(operation: Statement, position: Span) -> Self { - Self { - statement: operation, - position, - } +impl Node { + pub fn new(inner: T, position: Span) -> Self { + Self { inner, position } } } -impl Display for Node { +impl Display for Node { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", self.statement) + write!(f, "{}", self.inner) } } #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] pub enum Statement { // Top-level statements - Assign(Box, Box), + Assign(Box>, Box>), - // Expressions - Add(Box, Box), + // Math expressions + Add(Box>, Box>), + Subtract(Box>, Box>), + Multiply(Box>, Box>), + + // Function calls BuiltInFunctionCall { function: BuiltInFunction, - type_arguments: Option>, - value_arguments: Option>, + type_arguments: Option>>, + value_arguments: Option>>, }, FunctionCall { - function: Box, - type_arguments: Option>, - value_arguments: Option>, + function: Box>, + type_arguments: Option>>, + value_arguments: Option>>, }, - PropertyAccess(Box, Box), - Subtract(Box, Box), - List(Vec), - Multiply(Box, Box), + + // Comparison expressions + Comparison( + Box>, + Node, + Box>, + ), + + // Property access + PropertyAccess(Box>, Box>), + + // Value collections + List(Vec>), // Hard-coded values Constant(Value), @@ -65,18 +75,22 @@ pub enum Statement { impl Statement { pub fn expected_type(&self, variables: &HashMap) -> Option { match self { - Statement::Add(left, _) => left.statement.expected_type(variables), + Statement::Add(left, _) => left.inner.expected_type(variables), Statement::Assign(_, _) => None, Statement::BuiltInFunctionCall { function, .. } => function.expected_type(), + Statement::Comparison(_, _, _) => Some(Type::Boolean), Statement::Constant(value) => Some(value.r#type(variables)), - Statement::FunctionCall { function, .. } => function.statement.expected_type(variables), + Statement::FunctionCall { function, .. } => function.inner.expected_type(variables), Statement::Identifier(identifier) => variables .get(identifier) .map(|value| value.r#type(variables)), - Statement::List(_) => None, - Statement::Multiply(left, _) => left.statement.expected_type(variables), + Statement::List(nodes) => nodes + .first() + .map(|node| node.inner.expected_type(variables)) + .flatten(), + Statement::Multiply(left, _) => left.inner.expected_type(variables), Statement::PropertyAccess(_, _) => None, - Statement::Subtract(left, _) => left.statement.expected_type(variables), + Statement::Subtract(left, _) => left.inner.expected_type(variables), } } } @@ -121,6 +135,8 @@ impl Display for Statement { write!(f, ")") } + Statement::Comparison(left, operator, right) => write!(f, "{left} {operator} {right}"), + Statement::Constant(value) => write!(f, "{value}"), Statement::FunctionCall { function, type_arguments: type_parameters, @@ -156,6 +172,7 @@ impl Display for Statement { write!(f, ")") } + Statement::Identifier(identifier) => write!(f, "{identifier}"), Statement::List(nodes) => { write!(f, "[")?; for (i, node) in nodes.iter().enumerate() { @@ -166,11 +183,28 @@ 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::PropertyAccess(left, right) => write!(f, "{left}.{right}"), Statement::Subtract(left, right) => write!(f, "{left} - {right}"), } } } + +#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] +pub enum ComparisonOperator { + GreaterThan, + GreaterThanOrEqual, + LessThan, + LessThanOrEqual, +} + +impl Display for ComparisonOperator { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + ComparisonOperator::GreaterThan => write!(f, ">"), + ComparisonOperator::GreaterThanOrEqual => write!(f, ">="), + ComparisonOperator::LessThan => write!(f, "<"), + ComparisonOperator::LessThanOrEqual => write!(f, "<="), + } + } +} diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index 90f634f..a9d05f0 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -72,14 +72,14 @@ impl<'a> Analyzer<'a> { Ok(()) } - fn analyze_node(&self, node: &Node) -> Result<(), AnalyzerError> { - match &node.statement { + fn analyze_node(&self, node: &Node) -> Result<(), AnalyzerError> { + match &node.inner { 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); + let left_type = left.inner.expected_type(self.variables); + let right_type = right.inner.expected_type(self.variables); match (left_type, right_type) { (Some(Type::Integer), Some(Type::Integer)) => {} @@ -100,7 +100,7 @@ impl<'a> Analyzer<'a> { } } Statement::Assign(left, right) => { - if let Statement::Identifier(_) = &left.statement { + if let Statement::Identifier(_) = &left.inner { // Identifier is in the correct position } else { return Err(AnalyzerError::ExpectedIdentifier { @@ -112,9 +112,33 @@ impl<'a> Analyzer<'a> { self.analyze_node(right)?; } Statement::BuiltInFunctionCall { .. } => {} + Statement::Comparison(left, _, right) => { + self.analyze_node(left)?; + self.analyze_node(right)?; + + if let Some(Type::Integer) | Some(Type::Float) = + left.inner.expected_type(self.variables) + { + } else { + return Err(AnalyzerError::ExpectedIntegerOrFloat { + actual: left.as_ref().clone(), + position: left.position, + }); + } + + if let Some(Type::Integer) | Some(Type::Float) = + right.inner.expected_type(self.variables) + { + } else { + return Err(AnalyzerError::ExpectedIntegerOrFloat { + actual: right.as_ref().clone(), + position: right.position, + }); + } + } Statement::Constant(_) => {} Statement::FunctionCall { function, .. } => { - if let Statement::Identifier(_) = &function.statement { + if let Statement::Identifier(_) = &function.inner { // Function is in the correct position } else { return Err(AnalyzerError::ExpectedIdentifier { @@ -139,7 +163,7 @@ impl<'a> Analyzer<'a> { self.analyze_node(right)?; if let Some(Type::Integer) | Some(Type::Float) = - left.statement.expected_type(self.variables) + left.inner.expected_type(self.variables) { } else { return Err(AnalyzerError::ExpectedIntegerOrFloat { @@ -149,7 +173,7 @@ impl<'a> Analyzer<'a> { } if let Some(Type::Integer) | Some(Type::Float) = - right.statement.expected_type(self.variables) + right.inner.expected_type(self.variables) { } else { return Err(AnalyzerError::ExpectedIntegerOrFloat { @@ -160,7 +184,7 @@ impl<'a> Analyzer<'a> { } Statement::PropertyAccess(left, right) => { if let Statement::Identifier(_) | Statement::Constant(_) | Statement::List(_) = - &left.statement + &left.inner { // Left side is valid } else { @@ -170,9 +194,9 @@ impl<'a> Analyzer<'a> { }); } - if let Statement::BuiltInFunctionCall { function, .. } = &right.statement { + if let Statement::BuiltInFunctionCall { function, .. } = &right.inner { if function == &BuiltInFunction::IsEven || function == &BuiltInFunction::IsOdd { - if let Some(Type::Integer) = left.statement.expected_type(self.variables) { + if let Some(Type::Integer) = left.inner.expected_type(self.variables) { } else { return Err(AnalyzerError::ExpectedIntegerOrFloat { actual: left.as_ref().clone(), @@ -188,8 +212,8 @@ impl<'a> Analyzer<'a> { 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); + let left_type = left.inner.expected_type(self.variables); + let right_type = right.inner.expected_type(self.variables); match (left_type, right_type) { (Some(Type::Integer), Some(Type::Integer)) => {} @@ -216,13 +240,34 @@ impl<'a> Analyzer<'a> { #[derive(Clone, Debug, PartialEq)] pub enum AnalyzerError { - ExpectedBoolean { actual: Node, position: Span }, - ExpectedFunction { actual: Node, position: Span }, - ExpectedIdentifier { actual: Node, position: Span }, - ExpectedIdentifierOrValue { actual: Node, position: Span }, - ExpectedIntegerOrFloat { actual: Node, position: Span }, - ExpectedIntegerFloatOrString { actual: Node, position: Span }, - UnexpectedIdentifier { identifier: Node, position: Span }, + ExpectedBoolean { + actual: Node, + position: Span, + }, + ExpectedFunction { + actual: Node, + position: Span, + }, + ExpectedIdentifier { + actual: Node, + position: Span, + }, + ExpectedIdentifierOrValue { + actual: Node, + position: Span, + }, + ExpectedIntegerOrFloat { + actual: Node, + position: Span, + }, + ExpectedIntegerFloatOrString { + actual: Node, + position: Span, + }, + UnexpectedIdentifier { + identifier: Node, + position: Span, + }, } impl AnalyzerError { diff --git a/dust-lang/src/lex.rs b/dust-lang/src/lex.rs index c543042..1c03adf 100644 --- a/dust-lang/src/lex.rs +++ b/dust-lang/src/lex.rs @@ -167,6 +167,28 @@ impl Lexer { (Token::Dot, (self.position - 1, self.position)) } + '>' => { + if let Some('=') = self.peek_second_char(source) { + self.position += 2; + + (Token::GreaterEqual, (self.position - 2, self.position)) + } else { + self.position += 1; + + (Token::Greater, (self.position - 1, self.position)) + } + } + '<' => { + if let Some('=') = self.peek_second_char(source) { + self.position += 2; + + (Token::LessEqual, (self.position - 2, self.position)) + } else { + self.position += 1; + + (Token::Less, (self.position - 1, self.position)) + } + } _ => { self.position += 1; @@ -393,6 +415,46 @@ impl From for LexError { mod tests { use super::*; + #[test] + fn greater_than() { + let input = ">"; + + assert_eq!( + lex(input), + Ok(vec![(Token::Greater, (0, 1)), (Token::Eof, (1, 1))]) + ) + } + + #[test] + fn greater_than_or_equal() { + let input = ">="; + + assert_eq!( + lex(input), + Ok(vec![(Token::GreaterEqual, (0, 2)), (Token::Eof, (2, 2))]) + ) + } + + #[test] + fn less_than() { + let input = "<"; + + assert_eq!( + lex(input), + Ok(vec![(Token::Less, (0, 1)), (Token::Eof, (1, 1))]) + ) + } + + #[test] + fn less_than_or_equal() { + let input = "<="; + + assert_eq!( + lex(input), + Ok(vec![(Token::LessEqual, (0, 2)), (Token::Eof, (2, 2))]) + ) + } + #[test] fn infinity() { let input = "Infinity"; diff --git a/dust-lang/src/parse.rs b/dust-lang/src/parse.rs index eea2505..e2c1b6f 100644 --- a/dust-lang/src/parse.rs +++ b/dust-lang/src/parse.rs @@ -10,8 +10,8 @@ use std::{ }; use crate::{ - built_in_function::BuiltInFunction, token::TokenOwned, AbstractSyntaxTree, Identifier, - LexError, Lexer, Node, Span, Statement, Token, Value, + abstract_tree::ComparisonOperator, built_in_function::BuiltInFunction, token::TokenOwned, + AbstractSyntaxTree, Identifier, LexError, Lexer, Node, Span, Statement, Token, Value, }; /// Parses the input into an abstract syntax tree. @@ -27,13 +27,13 @@ use crate::{ /// Ok(AbstractSyntaxTree { /// nodes: [ /// Node { -/// statement: Statement::Assign( +/// inner: Statement::Assign( /// Box::new(Node { -/// statement: Statement::Identifier("x".into()), +/// inner: Statement::Identifier("x".into()), /// position: (0, 1), /// }), /// Box::new(Node { -/// statement: Statement::Constant(Value::integer(42)), +/// inner: Statement::Constant(Value::integer(42)), /// position: (4, 6), /// }) /// ), @@ -84,15 +84,15 @@ pub fn parse(input: &str) -> Result { /// /// assert_eq!( /// nodes, -/// Into::>::into([ +/// Into::>>::into([ /// Node { -/// statement: Statement::Assign( +/// inner: Statement::Assign( /// Box::new(Node { -/// statement: Statement::Identifier("x".into()), +/// inner: Statement::Identifier("x".into()), /// position: (0, 1), /// }), /// Box::new(Node { -/// statement: Statement::Constant(Value::integer(42)), +/// inner: Statement::Constant(Value::integer(42)), /// position: (4, 6), /// }) /// ), @@ -119,7 +119,7 @@ impl<'src> Parser<'src> { } } - pub fn parse(&mut self) -> Result { + pub fn parse(&mut self) -> Result, ParseError> { self.parse_node(0) } @@ -149,12 +149,113 @@ impl<'src> Parser<'src> { Ok(()) } - fn parse_node(&mut self, precedence: u8) -> Result { + fn parse_node(&mut self, precedence: u8) -> Result, ParseError> { let left_node = self.parse_primary()?; let left_start = left_node.position.0; if precedence < self.current_precedence() { match &self.current { + (Token::Dot, _) => { + self.next_token()?; + + let right_node = self.parse_node(self.current_precedence())?; + let right_end = right_node.position.1; + + return Ok(Node::new( + Statement::PropertyAccess(Box::new(left_node), Box::new(right_node)), + (left_start, right_end), + )); + } + (Token::Equal, _) => { + self.next_token()?; + + let right_node = self.parse_node(self.current_precedence())?; + let right_end = right_node.position.1; + + return Ok(Node::new( + Statement::Assign(Box::new(left_node), Box::new(right_node)), + (left_start, right_end), + )); + } + (Token::Greater, _) => { + let operator_position = self.current.1; + + self.next_token()?; + + let right_node = self.parse_node(self.current_precedence())?; + let right_end = right_node.position.1; + + return Ok(Node::new( + Statement::Comparison( + Box::new(left_node), + Node::new(ComparisonOperator::GreaterThan, operator_position), + Box::new(right_node), + ), + (left_start, right_end), + )); + } + (Token::GreaterEqual, _) => { + let operator_position = self.current.1; + + self.next_token()?; + + let right_node = self.parse_node(self.current_precedence())?; + let right_end = right_node.position.1; + + return Ok(Node::new( + Statement::Comparison( + Box::new(left_node), + Node::new(ComparisonOperator::GreaterThanOrEqual, operator_position), + Box::new(right_node), + ), + (left_start, right_end), + )); + } + (Token::Less, _) => { + let operator_position = self.current.1; + + self.next_token()?; + + let right_node = self.parse_node(self.current_precedence())?; + let right_end = right_node.position.1; + + return Ok(Node::new( + Statement::Comparison( + Box::new(left_node), + Node::new(ComparisonOperator::LessThan, operator_position), + Box::new(right_node), + ), + (left_start, right_end), + )); + } + (Token::LessEqual, _) => { + let operator_position = self.current.1; + + self.next_token()?; + + let right_node = self.parse_node(self.current_precedence())?; + let right_end = right_node.position.1; + + return Ok(Node::new( + Statement::Comparison( + Box::new(left_node), + Node::new(ComparisonOperator::LessThanOrEqual, operator_position), + Box::new(right_node), + ), + (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), + )); + } (Token::Plus, _) => { self.next_token()?; @@ -177,39 +278,6 @@ impl<'src> Parser<'src> { (left_start, right_end), )); } - (Token::Equal, _) => { - self.next_token()?; - - let right_node = self.parse_node(self.current_precedence())?; - let right_end = right_node.position.1; - - return Ok(Node::new( - Statement::Assign(Box::new(left_node), Box::new(right_node)), - (left_start, right_end), - )); - } - (Token::Dot, _) => { - self.next_token()?; - - let right_node = self.parse_node(self.current_precedence())?; - let right_end = right_node.position.1; - - return Ok(Node::new( - Statement::PropertyAccess(Box::new(left_node), Box::new(right_node)), - (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), - )); - } _ => {} } } @@ -217,7 +285,7 @@ impl<'src> Parser<'src> { Ok(left_node) } - fn parse_primary(&mut self) -> Result { + fn parse_primary(&mut self) -> Result, ParseError> { match self.current { (Token::Boolean(boolean), span) => { self.next_token()?; @@ -258,7 +326,7 @@ impl<'src> Parser<'src> { if let (Token::RightParenthesis, right_span) = self.current { self.next_token()?; - Ok(Node::new(node.statement, (left_span.0, right_span.1))) + Ok(Node::new(node.inner, (left_span.0, right_span.1))) } else { Err(ParseError::ExpectedClosingParenthesis { actual: self.current.0.to_owned(), @@ -321,7 +389,7 @@ impl<'src> Parser<'src> { }); } - let mut value_arguments: Option> = None; + let mut value_arguments: Option>> = None; loop { if let (Token::RightParenthesis, _) = self.current { @@ -366,6 +434,7 @@ impl<'src> Parser<'src> { fn current_precedence(&self) -> u8 { match self.current.0 { + Token::Greater | Token::GreaterEqual | Token::Less | Token::LessEqual => 5, Token::Dot => 4, Token::Equal => 3, Token::Star => 2, @@ -427,10 +496,90 @@ impl Display for ParseError { #[cfg(test)] mod tests { - use crate::Identifier; + use crate::{abstract_tree::ComparisonOperator, Identifier}; use super::*; + #[test] + fn less_than() { + let input = "1 < 2"; + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::Comparison( + Box::new(Node::new(Statement::Constant(Value::integer(1)), (0, 1))), + Node::new(ComparisonOperator::LessThan, (2, 3)), + Box::new(Node::new(Statement::Constant(Value::integer(2)), (4, 5))) + ), + (0, 5) + )] + .into() + }) + ); + } + + #[test] + fn less_than_or_equal() { + let input = "1 <= 2"; + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::Comparison( + Box::new(Node::new(Statement::Constant(Value::integer(1)), (0, 1))), + Node::new(ComparisonOperator::LessThanOrEqual, (2, 4)), + Box::new(Node::new(Statement::Constant(Value::integer(2)), (5, 6))) + ), + (0, 6) + )] + .into() + }) + ); + } + + #[test] + fn greater_than_or_equal() { + let input = "1 >= 2"; + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::Comparison( + Box::new(Node::new(Statement::Constant(Value::integer(1)), (0, 1))), + Node::new(ComparisonOperator::GreaterThanOrEqual, (2, 4)), + Box::new(Node::new(Statement::Constant(Value::integer(2)), (5, 6))) + ), + (0, 6) + )] + .into() + }) + ); + } + + #[test] + fn greater_than() { + let input = "1 > 2"; + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::Comparison( + Box::new(Node::new(Statement::Constant(Value::integer(1)), (0, 1))), + Node::new(ComparisonOperator::GreaterThan, (2, 3)), + Box::new(Node::new(Statement::Constant(Value::integer(2)), (4, 5))) + ), + (0, 5) + )] + .into() + }) + ); + } + #[test] fn subtract_negative_integers() { let input = "-1 - -2"; diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index db24fcd..a7708a6 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -27,8 +27,12 @@ pub enum Token<'src> { Comma, Dot, Equal, + Greater, + GreaterEqual, LeftParenthesis, LeftSquareBrace, + Less, + LessEqual, Minus, Plus, RightParenthesis, @@ -39,53 +43,61 @@ pub enum Token<'src> { impl<'src> Token<'src> { pub fn to_owned(&self) -> TokenOwned { match self { - Token::Eof => TokenOwned::Eof, - Token::Identifier(text) => TokenOwned::Identifier(text.to_string()), Token::Boolean(boolean) => TokenOwned::Boolean(*boolean), - Token::Float(float) => TokenOwned::Float(*float), - Token::Integer(integer) => TokenOwned::Integer(*integer), - Token::String(text) => TokenOwned::String(text.to_string()), - Token::IsEven => TokenOwned::IsEven, - Token::IsOdd => TokenOwned::IsOdd, - Token::Length => TokenOwned::Length, - Token::ReadLine => TokenOwned::ReadLine, - Token::WriteLine => TokenOwned::WriteLine, Token::Comma => TokenOwned::Comma, Token::Dot => TokenOwned::Dot, + Token::Eof => TokenOwned::Eof, Token::Equal => TokenOwned::Equal, - Token::Plus => TokenOwned::Plus, - Token::Star => TokenOwned::Star, + Token::Float(float) => TokenOwned::Float(*float), + Token::Greater => TokenOwned::Greater, + Token::GreaterEqual => TokenOwned::GreaterOrEqual, + Token::Identifier(text) => TokenOwned::Identifier(text.to_string()), + Token::Integer(integer) => TokenOwned::Integer(*integer), + Token::IsEven => TokenOwned::IsEven, + Token::IsOdd => TokenOwned::IsOdd, Token::LeftParenthesis => TokenOwned::LeftParenthesis, - Token::RightParenthesis => TokenOwned::RightParenthesis, Token::LeftSquareBrace => TokenOwned::LeftSquareBrace, - Token::RightSquareBrace => TokenOwned::RightSquareBrace, + Token::Length => TokenOwned::Length, + Token::Less => TokenOwned::Less, + Token::LessEqual => TokenOwned::LessOrEqual, Token::Minus => TokenOwned::Minus, + Token::Plus => TokenOwned::Plus, + Token::ReadLine => TokenOwned::ReadLine, + Token::RightParenthesis => TokenOwned::RightParenthesis, + Token::RightSquareBrace => TokenOwned::RightSquareBrace, + Token::Star => TokenOwned::Star, + Token::String(text) => TokenOwned::String(text.to_string()), + Token::WriteLine => TokenOwned::WriteLine, } } pub fn as_str(&self) -> &'static str { match self { - Token::Eof => "EOF", - Token::Identifier(_) => "identifier", Token::Boolean(_) => "boolean", - Token::Float(_) => "float", - Token::Integer(_) => "integer", - Token::String(_) => "string", - Token::IsEven => "is_even", - Token::IsOdd => "is_odd", - Token::Length => "length", - Token::ReadLine => "read_line", - Token::WriteLine => "write_line", Token::Comma => ",", Token::Dot => ".", + Token::Eof => "EOF", Token::Equal => "=", - Token::Plus => "+", - Token::Star => "*", + Token::Float(_) => "float", + Token::Greater => ">", + Token::GreaterEqual => ">=", + Token::Identifier(_) => "identifier", + Token::Integer(_) => "integer", + Token::IsEven => "is_even", + Token::IsOdd => "is_odd", Token::LeftParenthesis => "(", - Token::RightParenthesis => ")", Token::LeftSquareBrace => "[", - Token::RightSquareBrace => "]", + Token::Length => "length", + Token::Less => "<", + Token::LessEqual => "<=", Token::Minus => "-", + Token::Plus => "+", + Token::ReadLine => "read_line", + Token::RightParenthesis => ")", + Token::RightSquareBrace => "]", + Token::Star => "*", + Token::String(_) => "string", + Token::WriteLine => "write_line", } } } @@ -103,26 +115,30 @@ impl<'src> PartialEq for Token<'src> { (Token::Float(left), Token::Float(right)) => left.to_bits() == right.to_bits(), // Compare all other variants normally. - (Token::Eof, Token::Eof) => true, - (Token::Identifier(left), Token::Identifier(right)) => left == right, (Token::Boolean(left), Token::Boolean(right)) => left == right, - (Token::Integer(left), Token::Integer(right)) => left == right, - (Token::String(left), Token::String(right)) => left == right, - (Token::IsEven, Token::IsEven) => true, - (Token::IsOdd, Token::IsOdd) => true, - (Token::Length, Token::Length) => true, - (Token::ReadLine, Token::ReadLine) => true, - (Token::WriteLine, Token::WriteLine) => true, (Token::Comma, Token::Comma) => true, (Token::Dot, Token::Dot) => true, + (Token::Eof, Token::Eof) => true, (Token::Equal, Token::Equal) => true, - (Token::Plus, Token::Plus) => true, - (Token::Star, Token::Star) => true, + (Token::Greater, Token::Greater) => true, + (Token::GreaterEqual, Token::GreaterEqual) => true, + (Token::Identifier(left), Token::Identifier(right)) => left == right, + (Token::Integer(left), Token::Integer(right)) => left == right, + (Token::IsEven, Token::IsEven) => true, + (Token::IsOdd, Token::IsOdd) => true, (Token::LeftParenthesis, Token::LeftParenthesis) => true, - (Token::RightParenthesis, Token::RightParenthesis) => true, (Token::LeftSquareBrace, Token::LeftSquareBrace) => true, - (Token::RightSquareBrace, Token::RightSquareBrace) => true, + (Token::Length, Token::Length) => true, + (Token::Less, Token::Less) => true, + (Token::LessEqual, Token::LessEqual) => true, (Token::Minus, Token::Minus) => true, + (Token::Plus, Token::Plus) => true, + (Token::ReadLine, Token::ReadLine) => true, + (Token::RightParenthesis, Token::RightParenthesis) => true, + (Token::RightSquareBrace, Token::RightSquareBrace) => true, + (Token::Star, Token::Star) => true, + (Token::String(left), Token::String(right)) => left == right, + (Token::WriteLine, Token::WriteLine) => true, _ => false, } } @@ -154,8 +170,12 @@ pub enum TokenOwned { Comma, Dot, Equal, + Greater, + GreaterOrEqual, LeftParenthesis, LeftSquareBrace, + Less, + LessOrEqual, Minus, Plus, RightParenthesis, @@ -166,27 +186,31 @@ pub enum TokenOwned { impl Display for TokenOwned { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - TokenOwned::Eof => Token::Eof.fmt(f), - TokenOwned::Identifier(text) => write!(f, "{text}"), TokenOwned::Boolean(boolean) => write!(f, "{boolean}"), - TokenOwned::Float(float) => write!(f, "{float}"), - TokenOwned::Integer(integer) => write!(f, "{integer}"), - TokenOwned::String(string) => write!(f, "{string}"), - TokenOwned::IsEven => Token::IsEven.fmt(f), - TokenOwned::IsOdd => Token::IsOdd.fmt(f), - TokenOwned::Length => Token::Length.fmt(f), - TokenOwned::ReadLine => Token::ReadLine.fmt(f), - TokenOwned::WriteLine => Token::WriteLine.fmt(f), TokenOwned::Comma => Token::Comma.fmt(f), TokenOwned::Dot => Token::Dot.fmt(f), + TokenOwned::Eof => Token::Eof.fmt(f), TokenOwned::Equal => Token::Equal.fmt(f), - TokenOwned::Plus => Token::Plus.fmt(f), - TokenOwned::Star => Token::Star.fmt(f), + TokenOwned::Float(float) => write!(f, "{float}"), + TokenOwned::Greater => Token::Greater.fmt(f), + TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f), + TokenOwned::Identifier(text) => write!(f, "{text}"), + TokenOwned::Integer(integer) => write!(f, "{integer}"), + TokenOwned::IsEven => Token::IsEven.fmt(f), + TokenOwned::IsOdd => Token::IsOdd.fmt(f), TokenOwned::LeftParenthesis => Token::LeftParenthesis.fmt(f), - TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f), TokenOwned::LeftSquareBrace => Token::LeftSquareBrace.fmt(f), - TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f), + TokenOwned::Length => Token::Length.fmt(f), + TokenOwned::Less => Token::Less.fmt(f), + TokenOwned::LessOrEqual => Token::LessEqual.fmt(f), TokenOwned::Minus => Token::Minus.fmt(f), + TokenOwned::Plus => Token::Plus.fmt(f), + TokenOwned::ReadLine => Token::ReadLine.fmt(f), + TokenOwned::RightParenthesis => Token::RightParenthesis.fmt(f), + TokenOwned::RightSquareBrace => Token::RightSquareBrace.fmt(f), + TokenOwned::Star => Token::Star.fmt(f), + TokenOwned::String(string) => write!(f, "{string}"), + TokenOwned::WriteLine => Token::WriteLine.fmt(f), } } } diff --git a/dust-lang/src/value.rs b/dust-lang/src/value.rs index 90d7f13..7aaf1ab 100644 --- a/dust-lang/src/value.rs +++ b/dust-lang/src/value.rs @@ -157,6 +157,56 @@ impl Value { _ => Err(ValueError::CannotMultiply(self.clone(), other.clone())), } } + + pub fn less_than(&self, other: &Value) -> Result { + match (self.inner().as_ref(), other.inner().as_ref()) { + (ValueInner::Float(left), ValueInner::Float(right)) => Ok(Value::boolean(left < right)), + (ValueInner::Integer(left), ValueInner::Integer(right)) => { + Ok(Value::boolean(left < right)) + } + _ => Err(ValueError::CannotLessThan(self.clone(), other.clone())), + } + } + + pub fn less_than_or_equal(&self, other: &Value) -> Result { + match (self.inner().as_ref(), other.inner().as_ref()) { + (ValueInner::Float(left), ValueInner::Float(right)) => { + Ok(Value::boolean(left <= right)) + } + (ValueInner::Integer(left), ValueInner::Integer(right)) => { + Ok(Value::boolean(left <= right)) + } + _ => Err(ValueError::CannotLessThanOrEqual( + self.clone(), + other.clone(), + )), + } + } + + pub fn greater_than(&self, other: &Value) -> Result { + match (self.inner().as_ref(), other.inner().as_ref()) { + (ValueInner::Float(left), ValueInner::Float(right)) => Ok(Value::boolean(left > right)), + (ValueInner::Integer(left), ValueInner::Integer(right)) => { + Ok(Value::boolean(left > right)) + } + _ => Err(ValueError::CannotGreaterThan(self.clone(), other.clone())), + } + } + + pub fn greater_than_or_equal(&self, other: &Value) -> Result { + match (self.inner().as_ref(), other.inner().as_ref()) { + (ValueInner::Float(left), ValueInner::Float(right)) => { + Ok(Value::boolean(left >= right)) + } + (ValueInner::Integer(left), ValueInner::Integer(right)) => { + Ok(Value::boolean(left >= right)) + } + _ => Err(ValueError::CannotGreaterThanOrEqual( + self.clone(), + other.clone(), + )), + } + } } impl Display for Value { @@ -636,7 +686,7 @@ impl Function { .iter() .last() .unwrap() - .statement + .inner .expected_type(variables) } } @@ -686,9 +736,13 @@ pub enum ValueError { CannotAdd(Value, Value), CannotMultiply(Value, Value), CannotSubtract(Value, Value), - PropertyNotFound { value: Value, property: Identifier }, - IndexOutOfBounds { value: Value, index: i64 }, + CannotLessThan(Value, Value), + CannotLessThanOrEqual(Value, Value), + CannotGreaterThan(Value, Value), + CannotGreaterThanOrEqual(Value, Value), ExpectedList(Value), + IndexOutOfBounds { value: Value, index: i64 }, + PropertyNotFound { value: Value, property: Identifier }, } impl Error for ValueError {} @@ -703,6 +757,12 @@ impl Display for ValueError { ValueError::CannotSubtract(left, right) => { write!(f, "Cannot subtract {} and {}", left, right) } + ValueError::CannotLessThan(left, right) + | ValueError::CannotLessThanOrEqual(left, right) + | ValueError::CannotGreaterThan(left, right) + | ValueError::CannotGreaterThanOrEqual(left, right) => { + write!(f, "Cannot compare {} 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 66ac663..77c9ee6 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -6,8 +6,8 @@ use std::{ }; use crate::{ - parse, AbstractSyntaxTree, Analyzer, AnalyzerError, BuiltInFunctionError, Identifier, Node, - ParseError, Span, Statement, Value, ValueError, + abstract_tree::ComparisonOperator, parse, AbstractSyntaxTree, Analyzer, AnalyzerError, + BuiltInFunctionError, Identifier, Node, ParseError, Span, Statement, Value, ValueError, }; pub fn run( @@ -48,10 +48,10 @@ impl Vm { fn run_node( &self, - node: Node, + node: Node, variables: &mut HashMap, ) -> Result, VmError> { - match node.statement { + match node.inner { Statement::Add(left, right) => { let left_span = left.position; let left = if let Some(value) = self.run_node(*left, variables)? { @@ -79,7 +79,7 @@ impl Vm { Ok(Some(sum)) } Statement::Assign(left, right) => { - let identifier = if let Statement::Identifier(identifier) = &left.statement { + let identifier = if let Statement::Identifier(identifier) = &left.inner { identifier } else { return Err(VmError::ExpectedIdentifier { @@ -132,6 +132,54 @@ impl Vm { Ok(function_call_return) } + Statement::Comparison(left, operator, 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 comparison = match operator.inner { + ComparisonOperator::GreaterThan => { + left.greater_than(&right) + .map_err(|value_error| VmError::ValueError { + error: value_error, + position: (left_span.0, right_span.1), + })? + } + ComparisonOperator::GreaterThanOrEqual => left + .greater_than_or_equal(&right) + .map_err(|value_error| VmError::ValueError { + error: value_error, + position: (left_span.0, right_span.1), + })?, + ComparisonOperator::LessThan => { + left.less_than(&right) + .map_err(|value_error| VmError::ValueError { + error: value_error, + position: (left_span.0, right_span.1), + })? + } + ComparisonOperator::LessThanOrEqual => left + .less_than_or_equal(&right) + .map_err(|value_error| VmError::ValueError { + error: value_error, + position: (left_span.0, right_span.1), + })?, + }; + + Ok(Some(comparison)) + } Statement::Constant(value) => Ok(Some(value.clone())), Statement::FunctionCall { function: function_node, @@ -240,7 +288,7 @@ impl Vm { let right_span = right.position; if let (Some(list), Statement::Constant(value)) = - (left_value.as_list(), &right.statement) + (left_value.as_list(), &right.inner) { if let Some(index) = value.as_integer() { let value = list.get(index as usize).cloned(); @@ -256,7 +304,7 @@ impl Vm { type_arguments: _, value_arguments: value_argument_nodes, }, - ) = (left_value, right.statement) + ) = (left_value, right.inner) { let mut value_arguments = Vec::new(); @@ -454,6 +502,46 @@ impl Display for VmError { mod tests { use super::*; + #[test] + fn less_than() { + let input = "2 < 3"; + + assert_eq!( + run(input, &mut HashMap::new()), + Ok(Some(Value::boolean(true))) + ); + } + + #[test] + fn less_than_or_equal() { + let input = "42 <= 42"; + + assert_eq!( + run(input, &mut HashMap::new()), + Ok(Some(Value::boolean(true))) + ); + } + + #[test] + fn greater_than() { + let input = "2 > 3"; + + assert_eq!( + run(input, &mut HashMap::new()), + Ok(Some(Value::boolean(false))) + ); + } + + #[test] + fn greater_than_or_equal() { + let input = "42 >= 42"; + + assert_eq!( + run(input, &mut HashMap::new()), + Ok(Some(Value::boolean(true))) + ); + } + #[test] fn integer_saturating_add() { let input = "9223372036854775807 + 1";