diff --git a/dust-lang/src/abstract_tree.rs b/dust-lang/src/abstract_tree.rs index 8658b0b..e853dbd 100644 --- a/dust-lang/src/abstract_tree.rs +++ b/dust-lang/src/abstract_tree.rs @@ -57,8 +57,15 @@ pub enum Statement { }, // Property access expression + // TODO: This should be a binary operation PropertyAccess(Box>, Box>), + // Loops + While { + condition: Box>, + body: Box>, + }, + // Identifier expression Identifier(Identifier), @@ -78,7 +85,27 @@ impl Statement { pub fn expected_type(&self, context: &Context) -> Option { match self { Statement::Block(nodes) => nodes.last().unwrap().inner.expected_type(context), - Statement::BinaryOperation { left, .. } => left.inner.expected_type(context), + Statement::BinaryOperation { + left, + operator, + right, + } => match operator.inner { + BinaryOperator::Add + | BinaryOperator::Divide + | BinaryOperator::Modulo + | BinaryOperator::Multiply + | BinaryOperator::Subtract => Some(left.inner.expected_type(context)?), + + BinaryOperator::Equal + | BinaryOperator::Greater + | BinaryOperator::GreaterOrEqual + | BinaryOperator::Less + | BinaryOperator::LessOrEqual + | BinaryOperator::And + | BinaryOperator::Or => Some(Type::Boolean), + + BinaryOperator::Assign | BinaryOperator::AddAssign => None, + }, Statement::BuiltInFunctionCall { function, .. } => function.expected_return_type(), Statement::Constant(value) => Some(value.r#type(context)), Statement::FunctionCall { function, .. } => function.inner.expected_type(context), @@ -102,8 +129,9 @@ impl Statement { Some(Type::Map(types)) } - Statement::PropertyAccess(_, _) => None, Statement::Nil(_) => None, + Statement::PropertyAccess(_, _) => None, + Statement::While { .. } => None, } } } @@ -231,6 +259,9 @@ impl Display for Statement { } Statement::Nil(node) => write!(f, "{node};"), Statement::PropertyAccess(left, right) => write!(f, "{left}.{right}"), + Statement::While { condition, body } => { + write!(f, "while {condition} {body}") + } } } } diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index 4eb0ea0..f2dbb2a 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -178,6 +178,9 @@ impl<'a> Analyzer<'a> { self.analyze_node(value_node)?; } } + Statement::Nil(node) => { + self.analyze_node(node)?; + } Statement::PropertyAccess(left, right) => { if let Statement::Identifier(_) | Statement::Constant(_) | Statement::List(_) = &left.inner @@ -204,8 +207,17 @@ impl<'a> Analyzer<'a> { self.analyze_node(right)?; } - Statement::Nil(node) => { - self.analyze_node(node)?; + Statement::While { condition, body } => { + self.analyze_node(condition)?; + self.analyze_node(body)?; + + if let Some(Type::Boolean) = condition.inner.expected_type(self.context) { + } else { + return Err(AnalyzerError::ExpectedBoolean { + actual: condition.as_ref().clone(), + position: condition.position, + }); + } } } diff --git a/dust-lang/src/lex.rs b/dust-lang/src/lex.rs index 8e90b63..cd1a16d 100644 --- a/dust-lang/src/lex.rs +++ b/dust-lang/src/lex.rs @@ -403,6 +403,7 @@ impl Lexer { "length" => Token::Length, "NaN" => Token::Float("NaN"), "read_line" => Token::ReadLine, + "while" => Token::While, "write_line" => Token::WriteLine, _ => Token::Identifier(string), }; @@ -475,6 +476,27 @@ impl Display for LexError { mod tests { use super::*; + #[test] + fn while_loop() { + let input = "while x < 10 { x += 1 }"; + + assert_eq!( + lex(input), + Ok(vec![ + (Token::While, (0, 5)), + (Token::Identifier("x"), (6, 7)), + (Token::Less, (8, 9)), + (Token::Integer("10"), (10, 12)), + (Token::LeftCurlyBrace, (13, 14)), + (Token::Identifier("x"), (15, 16)), + (Token::PlusEqual, (17, 19)), + (Token::Integer("1"), (20, 21)), + (Token::RightCurlyBrace, (22, 23)), + (Token::Eof, (23, 23)), + ]) + ) + } + #[test] fn add_assign() { let input = "x += 42"; diff --git a/dust-lang/src/parse.rs b/dust-lang/src/parse.rs index f4c26d0..00a030c 100644 --- a/dust-lang/src/parse.rs +++ b/dust-lang/src/parse.rs @@ -416,6 +416,31 @@ impl<'src> Parser<'src> { left_position, )) } + (Token::While, left_position) => { + self.next_token()?; + + let condition = self.parse_statement(0)?; + + if let Token::LeftCurlyBrace = self.current.0 { + } else { + return Err(ParseError::ExpectedToken { + expected: TokenOwned::LeftCurlyBrace, + actual: self.current.0.to_owned(), + position: self.current.1, + }); + } + + let body = self.parse_block()?; + let body_end = body.position.1; + + Ok(Node::new( + Statement::While { + condition: Box::new(condition), + body: Box::new(body), + }, + (left_position.0, body_end), + )) + } _ => Err(ParseError::UnexpectedToken { actual: self.current.0.to_owned(), position: self.current.1, @@ -498,6 +523,39 @@ impl<'src> Parser<'src> { Ok(left) } } + + fn parse_block(&mut self) -> Result, ParseError> { + let left_start = self.current.1 .0; + + if let Token::LeftCurlyBrace = self.current.0 { + self.next_token()?; + } else { + return Err(ParseError::ExpectedToken { + expected: TokenOwned::LeftCurlyBrace, + actual: self.current.0.to_owned(), + position: self.current.1, + }); + } + + let mut statements = Vec::new(); + + loop { + if let Token::RightCurlyBrace = self.current.0 { + let right_end = self.current.1 .1; + + self.next_token()?; + + return Ok(Node::new( + Statement::Block(statements), + (left_start, right_end), + )); + } + + let statement = self.parse_statement(0)?; + + statements.push(statement); + } + } } #[derive(Debug, PartialEq, Clone)] @@ -583,6 +641,54 @@ mod tests { use super::*; + #[test] + fn while_loop() { + let input = "while x < 10 { x += 1 }"; + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::While { + condition: Box::new(Node::new( + Statement::BinaryOperation { + left: Box::new(Node::new( + Statement::Identifier(Identifier::new("x")), + (6, 7) + )), + operator: Node::new(BinaryOperator::Less, (8, 9)), + right: Box::new(Node::new( + Statement::Constant(Value::integer(10)), + (10, 12) + )), + }, + (6, 12) + )), + body: Box::new(Node::new( + Statement::Block(vec![Node::new( + Statement::BinaryOperation { + left: Box::new(Node::new( + Statement::Identifier(Identifier::new("x")), + (15, 16) + )), + operator: Node::new(BinaryOperator::AddAssign, (17, 19)), + right: Box::new(Node::new( + Statement::Constant(Value::integer(1)), + (20, 21) + )), + }, + (15, 21) + )]), + (13, 23) + )), + }, + (0, 23) + )] + .into() + }) + ); + } + #[test] fn add_assign() { let input = "a += 1"; diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index 8592890..cdb34c0 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -21,6 +21,7 @@ pub enum Token<'src> { IsOdd, Length, ReadLine, + While, WriteLine, // Symbols @@ -85,6 +86,7 @@ impl<'src> Token<'src> { Token::Star => TokenOwned::Star, Token::Slash => TokenOwned::Slash, Token::String(text) => TokenOwned::String(text.to_string()), + Token::While => TokenOwned::While, Token::WriteLine => TokenOwned::WriteLine, } } @@ -124,6 +126,7 @@ impl<'src> Token<'src> { Token::Star => "*", Token::String(_) => "string", Token::Slash => "/", + Token::While => "while", Token::WriteLine => "write_line", } } @@ -205,6 +208,7 @@ impl<'src> PartialEq for Token<'src> { (Token::Star, Token::Star) => true, (Token::Slash, Token::Slash) => true, (Token::String(left), Token::String(right)) => left == right, + (Token::While, Token::While) => true, (Token::WriteLine, Token::WriteLine) => true, _ => false, } @@ -231,6 +235,7 @@ pub enum TokenOwned { IsOdd, Length, ReadLine, + While, WriteLine, // Symbols @@ -295,6 +300,7 @@ impl Display for TokenOwned { TokenOwned::Star => Token::Star.fmt(f), TokenOwned::Slash => Token::Slash.fmt(f), TokenOwned::String(string) => write!(f, "{string}"), + TokenOwned::While => Token::While.fmt(f), TokenOwned::WriteLine => Token::WriteLine.fmt(f), } } diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 2de4b9d..1c0c932 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -6,8 +6,9 @@ use std::{ }; use crate::{ - abstract_tree::BinaryOperator, parse, AbstractSyntaxTree, Analyzer, AnalyzerError, - BuiltInFunctionError, Context, Node, ParseError, Span, Statement, Value, ValueError, + abstract_tree::BinaryOperator, parse, value::ValueInner, AbstractSyntaxTree, Analyzer, + AnalyzerError, BuiltInFunctionError, Context, Node, ParseError, Span, Statement, Value, + ValueError, }; pub fn run(input: &str, context: &mut Context) -> Result, VmError> { @@ -348,6 +349,31 @@ impl Vm { position: right_span, }) } + Statement::While { condition, body } => { + let mut return_value = None; + + let condition_position = condition.position; + + while let Some(condition_value) = self.run_node(*condition.clone(), context)? { + if let ValueInner::Boolean(condition_value) = condition_value.inner().as_ref() { + if !condition_value { + break; + } + } else { + return Err(VmError::ExpectedBoolean { + position: condition_position, + }); + } + + return_value = self.run_node(*body.clone(), context)?; + + if return_value.is_some() { + break; + } + } + + Ok(return_value) + } } } } @@ -367,6 +393,9 @@ pub enum VmError { error: BuiltInFunctionError, position: Span, }, + ExpectedBoolean { + position: Span, + }, ExpectedIdentifier { position: Span, }, @@ -398,6 +427,7 @@ impl VmError { Self::ParseError(parse_error) => parse_error.position(), Self::ValueError { position, .. } => *position, Self::BuiltInFunctionError { position, .. } => *position, + Self::ExpectedBoolean { position } => *position, Self::ExpectedIdentifier { position } => *position, Self::ExpectedIdentifierOrInteger { position } => *position, Self::ExpectedInteger { position } => *position, @@ -442,6 +472,9 @@ impl Display for VmError { Self::BuiltInFunctionError { error, .. } => { write!(f, "{}", error) } + Self::ExpectedBoolean { position } => { + write!(f, "Expected a boolean at position: {:?}", position) + } Self::ExpectedFunction { actual, position } => { write!( f, @@ -479,6 +512,13 @@ impl Display for VmError { mod tests { use super::*; + #[test] + fn while_loop() { + let input = "x = 0; while x < 5 { x += 1; } x"; + + assert_eq!(run(input, &mut Context::new()), Ok(Some(Value::integer(5)))); + } + #[test] fn add_assign() { let input = "x = 1; x += 1; x";