1
0

Compare commits

...

3 Commits

6 changed files with 664 additions and 20 deletions

View File

@ -66,6 +66,28 @@ pub enum Statement {
body: Box<Node<Statement>>, body: Box<Node<Statement>>,
}, },
// Control flow
If {
condition: Box<Node<Statement>>,
body: Box<Node<Statement>>,
},
IfElse {
condition: Box<Node<Statement>>,
if_body: Box<Node<Statement>>,
else_body: Box<Node<Statement>>,
},
IfElseIf {
condition: Box<Node<Statement>>,
if_body: Box<Node<Statement>>,
else_ifs: Vec<(Node<Statement>, Node<Statement>)>,
},
IfElseIfElse {
condition: Box<Node<Statement>>,
if_body: Box<Node<Statement>>,
else_ifs: Vec<(Node<Statement>, Node<Statement>)>,
else_body: Box<Node<Statement>>,
},
// Identifier expression // Identifier expression
Identifier(Identifier), Identifier(Identifier),
@ -85,11 +107,7 @@ impl Statement {
pub fn expected_type(&self, context: &Context) -> Option<Type> { pub fn expected_type(&self, context: &Context) -> Option<Type> {
match self { match self {
Statement::Block(nodes) => nodes.last().unwrap().inner.expected_type(context), Statement::Block(nodes) => nodes.last().unwrap().inner.expected_type(context),
Statement::BinaryOperation { Statement::BinaryOperation { left, operator, .. } => match operator.inner {
left,
operator,
right,
} => match operator.inner {
BinaryOperator::Add BinaryOperator::Add
| BinaryOperator::Divide | BinaryOperator::Divide
| BinaryOperator::Modulo | BinaryOperator::Modulo
@ -110,6 +128,10 @@ impl Statement {
Statement::Constant(value) => Some(value.r#type(context)), Statement::Constant(value) => Some(value.r#type(context)),
Statement::FunctionCall { function, .. } => function.inner.expected_type(context), Statement::FunctionCall { function, .. } => function.inner.expected_type(context),
Statement::Identifier(identifier) => context.get_type(identifier).cloned(), 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) => { Statement::List(nodes) => {
let item_type = nodes.first().unwrap().inner.expected_type(context)?; let item_type = nodes.first().unwrap().inner.expected_type(context)?;
@ -231,6 +253,43 @@ impl Display for Statement {
write!(f, ")") write!(f, ")")
} }
Statement::Identifier(identifier) => write!(f, "{identifier}"), 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) => { Statement::List(nodes) => {
write!(f, "[")?; write!(f, "[")?;

View File

@ -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) => { Statement::List(statements) => {
for statement in statements { for statement in statements {
self.analyze_node(statement)?; self.analyze_node(statement)?;

View File

@ -395,14 +395,16 @@ impl Lexer {
let string = &source[start_pos..self.position]; let string = &source[start_pos..self.position];
let token = match string { let token = match string {
"true" => Token::Boolean("true"),
"false" => Token::Boolean("false"),
"Infinity" => Token::Float("Infinity"), "Infinity" => Token::Float("Infinity"),
"NaN" => Token::Float("NaN"),
"else" => Token::Else,
"false" => Token::Boolean("false"),
"if" => Token::If,
"is_even" => Token::IsEven, "is_even" => Token::IsEven,
"is_odd" => Token::IsOdd, "is_odd" => Token::IsOdd,
"length" => Token::Length, "length" => Token::Length,
"NaN" => Token::Float("NaN"),
"read_line" => Token::ReadLine, "read_line" => Token::ReadLine,
"true" => Token::Boolean("true"),
"while" => Token::While, "while" => Token::While,
"write_line" => Token::WriteLine, "write_line" => Token::WriteLine,
_ => Token::Identifier(string), _ => Token::Identifier(string),
@ -476,6 +478,31 @@ impl Display for LexError {
mod tests { mod tests {
use super::*; 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] #[test]
fn while_loop() { fn while_loop() {
let input = "while x < 10 { x += 1 }"; let input = "while x < 10 { x += 1 }";

View File

@ -202,6 +202,96 @@ impl<'src> Parser<'src> {
position, 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 {
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) => { (Token::String(string), position) => {
self.next_token()?; self.next_token()?;
@ -247,23 +337,57 @@ impl<'src> Parser<'src> {
statement.get_or_insert_with(|| Statement::Block(Vec::new())) statement.get_or_insert_with(|| Statement::Block(Vec::new()))
{ {
block.push(next_node); block.push(next_node);
continue;
} }
// If the next node is an assignment, this might be a map }
} else if let Statement::BinaryOperation {
// If the new statement is already a map, expect the next node to be an
// assignment
if statement
.as_ref()
.is_some_and(|statement| matches!(statement, Statement::Map(_)))
{
if let Statement::BinaryOperation {
left, left,
operator: operator:
Node { Node {
inner: BinaryOperator::Assign, inner: BinaryOperator::Assign,
position: operator_position, ..
}, },
right, right,
} = next_node.inner } = next_node.inner
{ {
// If the current token is a comma, or the new statement is already a map if let Statement::Map(map_properties) =
statement.get_or_insert_with(|| Statement::Map(Vec::new()))
{
map_properties.push((*left, *right));
}
if let Token::Comma = self.current.0 {
self.next_token()?;
}
continue;
} else {
return Err(ParseError::ExpectedAssignment { actual: next_node });
}
}
// If the next node is an assignment, this might be a map
if let Statement::BinaryOperation {
left,
operator:
Node {
inner: BinaryOperator::Assign,
..
},
right,
} = next_node.inner
{
// If the current token is a comma or closing brace
if self.current.0 == Token::Comma if self.current.0 == Token::Comma
|| statement || self.current.0 == Token::RightCurlyBrace
.as_ref()
.is_some_and(|statement| matches!(statement, Statement::Map(_)))
{ {
// The new statement is a map // The new statement is a map
if let Statement::Map(map_properties) = if let Statement::Map(map_properties) =
@ -282,14 +406,11 @@ impl<'src> Parser<'src> {
if let Statement::Block(statements) = if let Statement::Block(statements) =
statement.get_or_insert_with(|| Statement::Block(Vec::new())) statement.get_or_insert_with(|| Statement::Block(Vec::new()))
{ {
// Add the statement to the block // Add the assignment statement to the block
statements.push(Node::new( statements.push(Node::new(
Statement::BinaryOperation { Statement::BinaryOperation {
left, left,
operator: Node::new( operator: Node::new(BinaryOperator::Assign, left_position),
BinaryOperator::Assign,
operator_position,
),
right, right,
}, },
next_node.position, next_node.position,
@ -565,6 +686,12 @@ pub enum ParseError {
position: Span, position: Span,
}, },
LexError(LexError), LexError(LexError),
ExpectedAssignment {
actual: Node<Statement>,
},
ExpectedBlock {
actual: Node<Statement>,
},
ExpectedIdentifier { ExpectedIdentifier {
actual: TokenOwned, actual: TokenOwned,
position: Span, position: Span,
@ -598,6 +725,8 @@ impl ParseError {
pub fn position(&self) -> Span { pub fn position(&self) -> Span {
match self { match self {
ParseError::BooleanError { position, .. } => *position, ParseError::BooleanError { position, .. } => *position,
ParseError::ExpectedAssignment { actual } => actual.position,
ParseError::ExpectedBlock { actual } => actual.position,
ParseError::ExpectedIdentifier { position, .. } => *position, ParseError::ExpectedIdentifier { position, .. } => *position,
ParseError::ExpectedToken { position, .. } => *position, ParseError::ExpectedToken { position, .. } => *position,
ParseError::FloatError { position, .. } => *position, ParseError::FloatError { position, .. } => *position,
@ -621,6 +750,8 @@ impl Display for ParseError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self { match self {
Self::BooleanError { error, .. } => write!(f, "{}", error), Self::BooleanError { error, .. } => write!(f, "{}", error),
Self::ExpectedAssignment { .. } => write!(f, "Expected assignment"),
Self::ExpectedBlock { .. } => write!(f, "Expected block"),
Self::ExpectedIdentifier { actual, .. } => { Self::ExpectedIdentifier { actual, .. } => {
write!(f, "Expected identifier, found {actual}") write!(f, "Expected identifier, found {actual}")
} }
@ -641,6 +772,142 @@ mod tests {
use super::*; 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; }";
assert_eq!(
parse(input),
Err(ParseError::ExpectedAssignment {
actual: Node::new(
Statement::Nil(Box::new(Node::new(
Statement::BinaryOperation {
left: Box::new(Node::new(
Statement::Identifier(Identifier::new("z")),
(16, 17)
)),
operator: Node::new(BinaryOperator::Assign, (18, 19)),
right: Box::new(Node::new(
Statement::Constant(Value::integer(3)),
(20, 21)
)),
},
(16, 21)
))),
(16, 24)
)
})
);
}
#[test] #[test]
fn while_loop() { fn while_loop() {
let input = "while x < 10 { x += 1 }"; let input = "while x < 10 { x += 1 }";

View File

@ -17,6 +17,8 @@ pub enum Token<'src> {
String(&'src str), String(&'src str),
// Keywords // Keywords
Else,
If,
IsEven, IsEven,
IsOdd, IsOdd,
Length, Length,
@ -59,12 +61,14 @@ impl<'src> Token<'src> {
Token::DoubleAmpersand => TokenOwned::DoubleAmpersand, Token::DoubleAmpersand => TokenOwned::DoubleAmpersand,
Token::DoubleEqual => TokenOwned::DoubleEqual, Token::DoubleEqual => TokenOwned::DoubleEqual,
Token::DoublePipe => TokenOwned::DoublePipe, Token::DoublePipe => TokenOwned::DoublePipe,
Token::Else => TokenOwned::Else,
Token::Eof => TokenOwned::Eof, Token::Eof => TokenOwned::Eof,
Token::Equal => TokenOwned::Equal, Token::Equal => TokenOwned::Equal,
Token::Float(float) => TokenOwned::Float(float.to_string()), Token::Float(float) => TokenOwned::Float(float.to_string()),
Token::Greater => TokenOwned::Greater, Token::Greater => TokenOwned::Greater,
Token::GreaterEqual => TokenOwned::GreaterOrEqual, Token::GreaterEqual => TokenOwned::GreaterOrEqual,
Token::Identifier(text) => TokenOwned::Identifier(text.to_string()), Token::Identifier(text) => TokenOwned::Identifier(text.to_string()),
Token::If => TokenOwned::If,
Token::Integer(integer) => TokenOwned::Integer(integer.to_string()), Token::Integer(integer) => TokenOwned::Integer(integer.to_string()),
Token::IsEven => TokenOwned::IsEven, Token::IsEven => TokenOwned::IsEven,
Token::IsOdd => TokenOwned::IsOdd, Token::IsOdd => TokenOwned::IsOdd,
@ -96,16 +100,20 @@ impl<'src> Token<'src> {
Token::Boolean(boolean_text) => boolean_text, Token::Boolean(boolean_text) => boolean_text,
Token::Identifier(text) => text, Token::Identifier(text) => text,
Token::Integer(integer_text) => integer_text, Token::Integer(integer_text) => integer_text,
Token::String(text) => text,
Token::Comma => ",", Token::Comma => ",",
Token::Dot => ".", Token::Dot => ".",
Token::DoubleAmpersand => "&&", Token::DoubleAmpersand => "&&",
Token::DoubleEqual => "==", Token::DoubleEqual => "==",
Token::DoublePipe => "||", Token::DoublePipe => "||",
Token::Else => "else",
Token::Eof => "EOF", Token::Eof => "EOF",
Token::Equal => "=", Token::Equal => "=",
Token::Float(_) => "float", Token::Float(_) => "float",
Token::Greater => ">", Token::Greater => ">",
Token::GreaterEqual => ">=", Token::GreaterEqual => ">=",
Token::If => "if",
Token::IsEven => "is_even", Token::IsEven => "is_even",
Token::IsOdd => "is_odd", Token::IsOdd => "is_odd",
Token::LeftCurlyBrace => "{", Token::LeftCurlyBrace => "{",
@ -124,7 +132,6 @@ impl<'src> Token<'src> {
Token::RightSquareBrace => "]", Token::RightSquareBrace => "]",
Token::Semicolon => ";", Token::Semicolon => ";",
Token::Star => "*", Token::Star => "*",
Token::String(_) => "string",
Token::Slash => "/", Token::Slash => "/",
Token::While => "while", Token::While => "while",
Token::WriteLine => "write_line", Token::WriteLine => "write_line",
@ -181,12 +188,14 @@ impl<'src> PartialEq for Token<'src> {
(Token::DoubleAmpersand, Token::DoubleAmpersand) => true, (Token::DoubleAmpersand, Token::DoubleAmpersand) => true,
(Token::DoubleEqual, Token::DoubleEqual) => true, (Token::DoubleEqual, Token::DoubleEqual) => true,
(Token::DoublePipe, Token::DoublePipe) => true, (Token::DoublePipe, Token::DoublePipe) => true,
(Token::Else, Token::Else) => true,
(Token::Eof, Token::Eof) => true, (Token::Eof, Token::Eof) => true,
(Token::Equal, Token::Equal) => true, (Token::Equal, Token::Equal) => true,
(Token::Float(left), Token::Float(right)) => left == right, (Token::Float(left), Token::Float(right)) => left == right,
(Token::Greater, Token::Greater) => true, (Token::Greater, Token::Greater) => true,
(Token::GreaterEqual, Token::GreaterEqual) => true, (Token::GreaterEqual, Token::GreaterEqual) => true,
(Token::Identifier(left), Token::Identifier(right)) => left == right, (Token::Identifier(left), Token::Identifier(right)) => left == right,
(Token::If, Token::If) => true,
(Token::Integer(left), Token::Integer(right)) => left == right, (Token::Integer(left), Token::Integer(right)) => left == right,
(Token::IsEven, Token::IsEven) => true, (Token::IsEven, Token::IsEven) => true,
(Token::IsOdd, Token::IsOdd) => true, (Token::IsOdd, Token::IsOdd) => true,
@ -231,6 +240,8 @@ pub enum TokenOwned {
String(String), String(String),
// Keywords // Keywords
Else,
If,
IsEven, IsEven,
IsOdd, IsOdd,
Length, Length,
@ -273,12 +284,14 @@ impl Display for TokenOwned {
TokenOwned::DoubleAmpersand => Token::DoubleAmpersand.fmt(f), TokenOwned::DoubleAmpersand => Token::DoubleAmpersand.fmt(f),
TokenOwned::DoubleEqual => Token::DoubleEqual.fmt(f), TokenOwned::DoubleEqual => Token::DoubleEqual.fmt(f),
TokenOwned::DoublePipe => Token::DoublePipe.fmt(f), TokenOwned::DoublePipe => Token::DoublePipe.fmt(f),
TokenOwned::Else => write!(f, "else"),
TokenOwned::Eof => Token::Eof.fmt(f), TokenOwned::Eof => Token::Eof.fmt(f),
TokenOwned::Equal => Token::Equal.fmt(f), TokenOwned::Equal => Token::Equal.fmt(f),
TokenOwned::Float(float) => write!(f, "{float}"), TokenOwned::Float(float) => write!(f, "{float}"),
TokenOwned::Greater => Token::Greater.fmt(f), TokenOwned::Greater => Token::Greater.fmt(f),
TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f), TokenOwned::GreaterOrEqual => Token::GreaterEqual.fmt(f),
TokenOwned::Identifier(text) => write!(f, "{text}"), TokenOwned::Identifier(text) => write!(f, "{text}"),
TokenOwned::If => write!(f, "if"),
TokenOwned::Integer(integer) => write!(f, "{integer}"), TokenOwned::Integer(integer) => write!(f, "{integer}"),
TokenOwned::IsEven => Token::IsEven.fmt(f), TokenOwned::IsEven => Token::IsEven.fmt(f),
TokenOwned::IsOdd => Token::IsOdd.fmt(f), TokenOwned::IsOdd => Token::IsOdd.fmt(f),

View File

@ -245,6 +245,154 @@ 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,
});
};
let condition = if let Some(condition) = condition_value.as_boolean() {
condition
} else {
return Err(VmError::ExpectedBoolean {
position: condition_position,
});
};
if condition {
self.run_node(*body, context)?;
}
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,
});
};
let condition = if let Some(condition) = condition_value.as_boolean() {
condition
} else {
return Err(VmError::ExpectedBoolean {
position: condition_position,
});
};
if condition {
self.run_node(body, context)?;
}
}
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,
});
};
let condition = if let Some(condition) = condition_value.as_boolean() {
condition
} else {
return Err(VmError::ExpectedBoolean {
position: condition_position,
});
};
if condition {
return self.run_node(body, context);
}
}
self.run_node(*else_body, context)
}
} else {
Err(VmError::ExpectedBoolean {
position: condition_position,
})
}
}
Statement::List(nodes) => { Statement::List(nodes) => {
let values = nodes let values = nodes
.into_iter() .into_iter()
@ -512,6 +660,34 @@ impl Display for VmError {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn r#if() {
let input = "if true { 1 }";
assert_eq!(run(input, &mut Context::new()), Ok(None));
}
#[test]
fn if_else() {
let input = "if false { 1 } else { 2 }";
assert_eq!(run(input, &mut Context::new()), Ok(Some(Value::integer(2))));
}
#[test]
fn if_else_if() {
let input = "if false { 1 } else if true { 2 }";
assert_eq!(run(input, &mut Context::new()), Ok(None));
}
#[test]
fn if_else_if_else() {
let input = "if false { 1 } else if false { 2 } else { 3 }";
assert_eq!(run(input, &mut Context::new()), Ok(Some(Value::integer(3))));
}
#[test] #[test]
fn while_loop() { fn while_loop() {
let input = "x = 0; while x < 5 { x += 1; } x"; let input = "x = 0; while x < 5 { x += 1; } x";