Compare commits
3 Commits
37e3e1116d
...
28c65b0715
Author | SHA1 | Date | |
---|---|---|---|
28c65b0715 | |||
0c73f80947 | |||
24e21aa0b5 |
@ -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, "[")?;
|
||||||
|
|
||||||
|
@ -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)?;
|
||||||
|
@ -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 }";
|
||||||
|
@ -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 }";
|
||||||
|
@ -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),
|
||||||
|
@ -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";
|
||||||
|
Loading…
Reference in New Issue
Block a user