Compare commits
3 Commits
37e3e1116d
...
28c65b0715
Author | SHA1 | Date | |
---|---|---|---|
28c65b0715 | |||
0c73f80947 | |||
24e21aa0b5 |
@ -66,6 +66,28 @@ pub enum 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(Identifier),
|
||||
|
||||
@ -85,11 +107,7 @@ impl Statement {
|
||||
pub fn expected_type(&self, context: &Context) -> Option<Type> {
|
||||
match self {
|
||||
Statement::Block(nodes) => nodes.last().unwrap().inner.expected_type(context),
|
||||
Statement::BinaryOperation {
|
||||
left,
|
||||
operator,
|
||||
right,
|
||||
} => match operator.inner {
|
||||
Statement::BinaryOperation { left, operator, .. } => match operator.inner {
|
||||
BinaryOperator::Add
|
||||
| BinaryOperator::Divide
|
||||
| BinaryOperator::Modulo
|
||||
@ -110,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)?;
|
||||
|
||||
@ -231,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, "[")?;
|
||||
|
||||
|
@ -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)?;
|
||||
|
@ -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 }";
|
||||
|
@ -202,6 +202,96 @@ 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 {
|
||||
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()?;
|
||||
|
||||
@ -247,23 +337,57 @@ impl<'src> Parser<'src> {
|
||||
statement.get_or_insert_with(|| Statement::Block(Vec::new()))
|
||||
{
|
||||
block.push(next_node);
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
operator:
|
||||
Node {
|
||||
inner: BinaryOperator::Assign,
|
||||
..
|
||||
},
|
||||
right,
|
||||
} = next_node.inner
|
||||
{
|
||||
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
|
||||
} else if let Statement::BinaryOperation {
|
||||
if let Statement::BinaryOperation {
|
||||
left,
|
||||
operator:
|
||||
Node {
|
||||
inner: BinaryOperator::Assign,
|
||||
position: operator_position,
|
||||
..
|
||||
},
|
||||
right,
|
||||
} = next_node.inner
|
||||
{
|
||||
// If the current token is a comma, or the new statement is already a map
|
||||
// If the current token is a comma or closing brace
|
||||
if self.current.0 == Token::Comma
|
||||
|| statement
|
||||
.as_ref()
|
||||
.is_some_and(|statement| matches!(statement, Statement::Map(_)))
|
||||
|| self.current.0 == Token::RightCurlyBrace
|
||||
{
|
||||
// The new statement is a map
|
||||
if let Statement::Map(map_properties) =
|
||||
@ -282,14 +406,11 @@ impl<'src> Parser<'src> {
|
||||
if let Statement::Block(statements) =
|
||||
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(
|
||||
Statement::BinaryOperation {
|
||||
left,
|
||||
operator: Node::new(
|
||||
BinaryOperator::Assign,
|
||||
operator_position,
|
||||
),
|
||||
operator: Node::new(BinaryOperator::Assign, left_position),
|
||||
right,
|
||||
},
|
||||
next_node.position,
|
||||
@ -565,6 +686,12 @@ pub enum ParseError {
|
||||
position: Span,
|
||||
},
|
||||
LexError(LexError),
|
||||
ExpectedAssignment {
|
||||
actual: Node<Statement>,
|
||||
},
|
||||
ExpectedBlock {
|
||||
actual: Node<Statement>,
|
||||
},
|
||||
ExpectedIdentifier {
|
||||
actual: TokenOwned,
|
||||
position: Span,
|
||||
@ -598,6 +725,8 @@ impl ParseError {
|
||||
pub fn position(&self) -> Span {
|
||||
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,
|
||||
@ -621,6 +750,8 @@ impl Display for ParseError {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
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}")
|
||||
}
|
||||
@ -641,6 +772,142 @@ 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; }";
|
||||
|
||||
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]
|
||||
fn while_loop() {
|
||||
let input = "while x < 10 { x += 1 }";
|
||||
|
@ -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),
|
||||
|
@ -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) => {
|
||||
let values = nodes
|
||||
.into_iter()
|
||||
@ -512,6 +660,34 @@ impl Display for VmError {
|
||||
mod tests {
|
||||
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]
|
||||
fn while_loop() {
|
||||
let input = "x = 0; while x < 5 { x += 1; } x";
|
||||
|
Loading…
Reference in New Issue
Block a user