diff --git a/dust-lang/src/abstract_tree.rs b/dust-lang/src/abstract_tree.rs index 4de4045..fc5bba3 100644 --- a/dust-lang/src/abstract_tree.rs +++ b/dust-lang/src/abstract_tree.rs @@ -66,6 +66,28 @@ pub enum Statement { body: Box>, }, + // Control flow + If { + condition: Box>, + body: Box>, + }, + IfElse { + condition: Box>, + if_body: Box>, + else_body: Box>, + }, + IfElseIf { + condition: Box>, + if_body: Box>, + else_ifs: Vec<(Node, Node)>, + }, + IfElseIfElse { + condition: Box>, + if_body: Box>, + else_ifs: Vec<(Node, Node)>, + else_body: Box>, + }, + // Identifier expression Identifier(Identifier), @@ -106,6 +128,10 @@ impl Statement { Statement::Constant(value) => Some(value.r#type(context)), Statement::FunctionCall { function, .. } => function.inner.expected_type(context), Statement::Identifier(identifier) => context.get_type(identifier).cloned(), + Statement::If { .. } => None, + Statement::IfElse { if_body, .. } => if_body.inner.expected_type(context), + Statement::IfElseIf { .. } => None, + Statement::IfElseIfElse { if_body, .. } => if_body.inner.expected_type(context), Statement::List(nodes) => { let item_type = nodes.first().unwrap().inner.expected_type(context)?; @@ -227,6 +253,43 @@ impl Display for Statement { write!(f, ")") } Statement::Identifier(identifier) => write!(f, "{identifier}"), + Statement::If { condition, body } => { + write!(f, "if {condition} {body}") + } + Statement::IfElse { + condition, + if_body, + else_body, + } => { + write!(f, "if {condition} {if_body} else {else_body}") + } + Statement::IfElseIf { + condition, + if_body, + else_ifs, + } => { + write!(f, "if {condition} {if_body}")?; + + for (condition, body) in else_ifs { + write!(f, " else if {condition} {body}")?; + } + + Ok(()) + } + Statement::IfElseIfElse { + condition, + if_body, + else_ifs, + else_body, + } => { + write!(f, "if {condition} {if_body}")?; + + for (condition, body) in else_ifs { + write!(f, " else if {condition} {body}")?; + } + + write!(f, " else {else_body}") + } Statement::List(nodes) => { write!(f, "[")?; diff --git a/dust-lang/src/analyzer.rs b/dust-lang/src/analyzer.rs index f2dbb2a..9a4c73c 100644 --- a/dust-lang/src/analyzer.rs +++ b/dust-lang/src/analyzer.rs @@ -168,6 +168,108 @@ impl<'a> Analyzer<'a> { }); } } + Statement::If { condition, body } => { + self.analyze_node(condition)?; + + if let Some(Type::Boolean) = condition.inner.expected_type(self.context) { + // Condition is valid + } else { + return Err(AnalyzerError::ExpectedBoolean { + actual: condition.as_ref().clone(), + position: condition.position, + }); + } + + self.analyze_node(body)?; + } + Statement::IfElse { + condition, + if_body, + else_body, + } => { + self.analyze_node(condition)?; + + if let Some(Type::Boolean) = condition.inner.expected_type(self.context) { + // Condition is valid + } else { + return Err(AnalyzerError::ExpectedBoolean { + actual: condition.as_ref().clone(), + position: condition.position, + }); + } + + self.analyze_node(if_body)?; + self.analyze_node(else_body)?; + } + Statement::IfElseIf { + condition, + if_body, + else_ifs, + } => { + self.analyze_node(condition)?; + + if let Some(Type::Boolean) = condition.inner.expected_type(self.context) { + // Condition is valid + } else { + return Err(AnalyzerError::ExpectedBoolean { + actual: condition.as_ref().clone(), + position: condition.position, + }); + } + + self.analyze_node(if_body)?; + + for (condition, body) in else_ifs { + self.analyze_node(condition)?; + + if let Some(Type::Boolean) = condition.inner.expected_type(self.context) { + // Condition is valid + } else { + return Err(AnalyzerError::ExpectedBoolean { + actual: condition.clone(), + position: condition.position, + }); + } + + self.analyze_node(body)?; + } + } + Statement::IfElseIfElse { + condition, + if_body, + else_ifs, + else_body, + } => { + self.analyze_node(condition)?; + + if let Some(Type::Boolean) = condition.inner.expected_type(self.context) { + // Condition is valid + } else { + return Err(AnalyzerError::ExpectedBoolean { + actual: condition.as_ref().clone(), + position: condition.position, + }); + } + + self.analyze_node(if_body)?; + + for (condition, body) in else_ifs { + self.analyze_node(condition)?; + + if let Some(Type::Boolean) = condition.inner.expected_type(self.context) { + // Condition is valid + } else { + return Err(AnalyzerError::ExpectedBoolean { + actual: condition.clone(), + position: condition.position, + }); + } + + self.analyze_node(body)?; + } + + self.analyze_node(else_body)?; + } Statement::List(statements) => { for statement in statements { self.analyze_node(statement)?; diff --git a/dust-lang/src/lex.rs b/dust-lang/src/lex.rs index cd1a16d..a4ea50c 100644 --- a/dust-lang/src/lex.rs +++ b/dust-lang/src/lex.rs @@ -395,14 +395,16 @@ impl Lexer { let string = &source[start_pos..self.position]; let token = match string { - "true" => Token::Boolean("true"), - "false" => Token::Boolean("false"), "Infinity" => Token::Float("Infinity"), + "NaN" => Token::Float("NaN"), + "else" => Token::Else, + "false" => Token::Boolean("false"), + "if" => Token::If, "is_even" => Token::IsEven, "is_odd" => Token::IsOdd, "length" => Token::Length, - "NaN" => Token::Float("NaN"), "read_line" => Token::ReadLine, + "true" => Token::Boolean("true"), "while" => Token::While, "write_line" => Token::WriteLine, _ => Token::Identifier(string), @@ -476,6 +478,31 @@ impl Display for LexError { mod tests { use super::*; + #[test] + fn if_else() { + let input = "if x < 10 { x + 1 } else { x }"; + + assert_eq!( + lex(input), + Ok(vec![ + (Token::If, (0, 2)), + (Token::Identifier("x"), (3, 4)), + (Token::Less, (5, 6)), + (Token::Integer("10"), (7, 9)), + (Token::LeftCurlyBrace, (10, 11)), + (Token::Identifier("x"), (12, 13)), + (Token::Plus, (14, 15)), + (Token::Integer("1"), (16, 17)), + (Token::RightCurlyBrace, (18, 19)), + (Token::Else, (20, 24)), + (Token::LeftCurlyBrace, (25, 26)), + (Token::Identifier("x"), (27, 28)), + (Token::RightCurlyBrace, (29, 30)), + (Token::Eof, (30, 30)), + ]) + ) + } + #[test] fn while_loop() { let input = "while x < 10 { x += 1 }"; diff --git a/dust-lang/src/parse.rs b/dust-lang/src/parse.rs index 49ae185..ce32261 100644 --- a/dust-lang/src/parse.rs +++ b/dust-lang/src/parse.rs @@ -202,6 +202,98 @@ impl<'src> Parser<'src> { position, )) } + (Token::If, position) => { + self.next_token()?; + + let condition = Box::new(self.parse_statement(0)?); + let if_body = Box::new(self.parse_statement(0)?); + + if let Statement::Block(_) = if_body.inner { + } else { + return Err(ParseError::ExpectedBlock { actual: *if_body }); + } + + if let Token::Else = self.current.0 { + self.next_token()?; + + if let Token::If = self.current.0 { + self.next_token()?; + + let first_else_if = (self.parse_statement(0)?, self.parse_statement(0)?); + let mut else_ifs = vec![first_else_if]; + + loop { + if let Token::Else = self.current.0 { + self.next_token()?; + } else { + let else_if_end = self.current.1; + + return Ok(Node::new( + Statement::IfElseIf { + condition, + if_body, + else_ifs, + }, + position, + )); + } + + if let Token::If = self.current.0 { + self.next_token()?; + + let else_if = (self.parse_statement(0)?, self.parse_statement(0)?); + + else_ifs.push(else_if); + } else { + let else_body = Box::new(self.parse_statement(0)?); + let else_end = else_body.position.1; + + if let Statement::Block(_) = else_body.inner { + } else { + return Err(ParseError::ExpectedBlock { actual: *else_body }); + } + + return Ok(Node::new( + Statement::IfElseIfElse { + condition, + if_body, + else_ifs, + else_body, + }, + (position.0, else_end), + )); + } + } + } else { + let else_body = Box::new(self.parse_statement(0)?); + let else_end = else_body.position.1; + + if let Statement::Block(_) = else_body.inner { + } else { + return Err(ParseError::ExpectedBlock { actual: *else_body }); + } + + Ok(Node::new( + Statement::IfElse { + condition, + if_body, + else_body, + }, + (position.0, else_end), + )) + } + } else { + let if_end = if_body.position.1; + + Ok(Node::new( + Statement::If { + condition, + body: if_body, + }, + (position.0, if_end), + )) + } + } (Token::String(string), position) => { self.next_token()?; @@ -599,6 +691,9 @@ pub enum ParseError { ExpectedAssignment { actual: Node, }, + ExpectedBlock { + actual: Node, + }, ExpectedIdentifier { actual: TokenOwned, position: Span, @@ -633,6 +728,7 @@ impl ParseError { match self { ParseError::BooleanError { position, .. } => *position, ParseError::ExpectedAssignment { actual } => actual.position, + ParseError::ExpectedBlock { actual } => actual.position, ParseError::ExpectedIdentifier { position, .. } => *position, ParseError::ExpectedToken { position, .. } => *position, ParseError::FloatError { position, .. } => *position, @@ -657,6 +753,7 @@ impl Display for ParseError { match self { Self::BooleanError { error, .. } => write!(f, "{}", error), Self::ExpectedAssignment { .. } => write!(f, "Expected assignment"), + Self::ExpectedBlock { .. } => write!(f, "Expected block"), Self::ExpectedIdentifier { actual, .. } => { write!(f, "Expected identifier, found {actual}") } @@ -677,6 +774,114 @@ mod tests { use super::*; + #[test] + fn r#if() { + let input = "if x { y }"; + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::If { + condition: Box::new(Node::new( + Statement::Identifier(Identifier::new("x")), + (3, 4) + )), + body: Box::new(Node::new( + Statement::Block(vec![Node::new( + Statement::Identifier(Identifier::new("y")), + (7, 8) + )]), + (5, 10) + )), + }, + (0, 10) + )] + .into() + }) + ); + } + + #[test] + fn if_else() { + let input = "if x { y } else { z }"; + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::IfElse { + condition: Box::new(Node::new( + Statement::Identifier(Identifier::new("x")), + (3, 4) + )), + if_body: Box::new(Node::new( + Statement::Block(vec![Node::new( + Statement::Identifier(Identifier::new("y")), + (7, 8) + )]), + (5, 10) + )), + else_body: Box::new(Node::new( + Statement::Block(vec![Node::new( + Statement::Identifier(Identifier::new("z")), + (18, 19) + )]), + (16, 21) + )), + }, + (0, 21) + )] + .into() + }) + ); + } + + #[test] + fn if_else_if_else() { + let input = "if x { y } else if z { a } else { b }"; + + assert_eq!( + parse(input), + Ok(AbstractSyntaxTree { + nodes: [Node::new( + Statement::IfElseIfElse { + condition: Box::new(Node::new( + Statement::Identifier(Identifier::new("x")), + (3, 4) + )), + if_body: Box::new(Node::new( + Statement::Block(vec![Node::new( + Statement::Identifier(Identifier::new("y")), + (7, 8) + )]), + (5, 10) + )), + else_ifs: vec![( + Node::new(Statement::Identifier(Identifier::new("z")), (19, 20)), + Node::new( + Statement::Block(vec![Node::new( + Statement::Identifier(Identifier::new("a")), + (23, 24) + )]), + (21, 26) + ), + )], + else_body: Box::new(Node::new( + Statement::Block(vec![Node::new( + Statement::Identifier(Identifier::new("b")), + (34, 35) + )]), + (32, 37) + )), + }, + (0, 37) + )] + .into() + }) + ); + } + #[test] fn malformed_map() { let input = "{ x = 1, y = 2, z = 3; }"; diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index cdb34c0..eef143e 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -17,6 +17,8 @@ pub enum Token<'src> { String(&'src str), // Keywords + Else, + If, IsEven, IsOdd, Length, @@ -59,12 +61,14 @@ impl<'src> Token<'src> { Token::DoubleAmpersand => TokenOwned::DoubleAmpersand, Token::DoubleEqual => TokenOwned::DoubleEqual, Token::DoublePipe => TokenOwned::DoublePipe, + Token::Else => TokenOwned::Else, Token::Eof => TokenOwned::Eof, Token::Equal => TokenOwned::Equal, Token::Float(float) => TokenOwned::Float(float.to_string()), Token::Greater => TokenOwned::Greater, Token::GreaterEqual => TokenOwned::GreaterOrEqual, Token::Identifier(text) => TokenOwned::Identifier(text.to_string()), + Token::If => TokenOwned::If, Token::Integer(integer) => TokenOwned::Integer(integer.to_string()), Token::IsEven => TokenOwned::IsEven, Token::IsOdd => TokenOwned::IsOdd, @@ -96,16 +100,20 @@ impl<'src> Token<'src> { Token::Boolean(boolean_text) => boolean_text, Token::Identifier(text) => text, Token::Integer(integer_text) => integer_text, + Token::String(text) => text, + Token::Comma => ",", Token::Dot => ".", Token::DoubleAmpersand => "&&", Token::DoubleEqual => "==", Token::DoublePipe => "||", + Token::Else => "else", Token::Eof => "EOF", Token::Equal => "=", Token::Float(_) => "float", Token::Greater => ">", Token::GreaterEqual => ">=", + Token::If => "if", Token::IsEven => "is_even", Token::IsOdd => "is_odd", Token::LeftCurlyBrace => "{", @@ -124,7 +132,6 @@ impl<'src> Token<'src> { Token::RightSquareBrace => "]", Token::Semicolon => ";", Token::Star => "*", - Token::String(_) => "string", Token::Slash => "/", Token::While => "while", Token::WriteLine => "write_line", @@ -181,12 +188,14 @@ impl<'src> PartialEq for Token<'src> { (Token::DoubleAmpersand, Token::DoubleAmpersand) => true, (Token::DoubleEqual, Token::DoubleEqual) => true, (Token::DoublePipe, Token::DoublePipe) => true, + (Token::Else, Token::Else) => true, (Token::Eof, Token::Eof) => true, (Token::Equal, Token::Equal) => true, (Token::Float(left), Token::Float(right)) => left == right, (Token::Greater, Token::Greater) => true, (Token::GreaterEqual, Token::GreaterEqual) => true, (Token::Identifier(left), Token::Identifier(right)) => left == right, + (Token::If, Token::If) => true, (Token::Integer(left), Token::Integer(right)) => left == right, (Token::IsEven, Token::IsEven) => true, (Token::IsOdd, Token::IsOdd) => true, @@ -231,6 +240,8 @@ pub enum TokenOwned { String(String), // Keywords + Else, + If, IsEven, IsOdd, Length, @@ -273,12 +284,14 @@ impl Display for TokenOwned { TokenOwned::DoubleAmpersand => Token::DoubleAmpersand.fmt(f), TokenOwned::DoubleEqual => Token::DoubleEqual.fmt(f), TokenOwned::DoublePipe => Token::DoublePipe.fmt(f), + TokenOwned::Else => write!(f, "else"), TokenOwned::Eof => Token::Eof.fmt(f), TokenOwned::Equal => Token::Equal.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::If => write!(f, "if"), TokenOwned::Integer(integer) => write!(f, "{integer}"), TokenOwned::IsEven => Token::IsEven.fmt(f), TokenOwned::IsOdd => Token::IsOdd.fmt(f), diff --git a/dust-lang/src/vm.rs b/dust-lang/src/vm.rs index 1c0c932..a20699c 100644 --- a/dust-lang/src/vm.rs +++ b/dust-lang/src/vm.rs @@ -245,6 +245,151 @@ impl Vm { }) } } + Statement::If { condition, body } => { + let condition_position = condition.position; + let condition_value = if let Some(value) = self.run_node(*condition, context)? { + value + } else { + return Err(VmError::ExpectedValue { + position: condition_position, + }); + }; + + if let Some(condition) = condition_value.as_boolean() { + if condition { + return self.run_node(*body, context); + } + } else { + return Err(VmError::ExpectedBoolean { + position: condition_position, + }); + } + + Ok(None) + } + Statement::IfElse { + condition, + if_body, + else_body, + } => { + let condition_position = condition.position; + let condition_value = if let Some(value) = self.run_node(*condition, context)? { + value + } else { + return Err(VmError::ExpectedValue { + position: condition_position, + }); + }; + + if let Some(condition) = condition_value.as_boolean() { + if condition { + self.run_node(*if_body, context) + } else { + self.run_node(*else_body, context) + } + } else { + Err(VmError::ExpectedBoolean { + position: condition_position, + }) + } + } + Statement::IfElseIf { + condition, + if_body, + else_ifs, + } => { + let condition_position = condition.position; + let condition_value = if let Some(value) = self.run_node(*condition, context)? { + value + } else { + return Err(VmError::ExpectedValue { + position: condition_position, + }); + }; + + if let Some(condition) = condition_value.as_boolean() { + if condition { + self.run_node(*if_body, context) + } else { + for (condition, body) in else_ifs { + let condition_position = condition.position; + let condition_value = + if let Some(value) = self.run_node(condition, context)? { + value + } else { + return Err(VmError::ExpectedValue { + position: condition_position, + }); + }; + + if let Some(condition) = condition_value.as_boolean() { + if condition { + return self.run_node(body, context); + } + } else { + return Err(VmError::ExpectedBoolean { + position: condition_position, + }); + } + } + + Ok(None) + } + } else { + Err(VmError::ExpectedBoolean { + position: condition_position, + }) + } + } + Statement::IfElseIfElse { + condition, + if_body, + else_ifs, + else_body, + } => { + let condition_position = condition.position; + let condition_value = if let Some(value) = self.run_node(*condition, context)? { + value + } else { + return Err(VmError::ExpectedValue { + position: condition_position, + }); + }; + + if let Some(condition) = condition_value.as_boolean() { + if condition { + self.run_node(*if_body, context) + } else { + for (condition, body) in else_ifs { + let condition_position = condition.position; + let condition_value = + if let Some(value) = self.run_node(condition, context)? { + value + } else { + return Err(VmError::ExpectedValue { + position: condition_position, + }); + }; + + if let Some(condition) = condition_value.as_boolean() { + if condition { + return self.run_node(body, context); + } + } else { + return Err(VmError::ExpectedBoolean { + position: condition_position, + }); + } + } + + self.run_node(*else_body, context) + } + } else { + Err(VmError::ExpectedBoolean { + position: condition_position, + }) + } + } Statement::List(nodes) => { let values = nodes .into_iter()