Implment control flow lexing and parsing

This commit is contained in:
Jeff 2024-08-11 14:35:33 -04:00
parent 24e21aa0b5
commit 0c73f80947
6 changed files with 559 additions and 4 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),
@ -106,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)?;
@ -227,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,98 @@ 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 {
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) => { (Token::String(string), position) => {
self.next_token()?; self.next_token()?;
@ -599,6 +691,9 @@ pub enum ParseError {
ExpectedAssignment { ExpectedAssignment {
actual: Node<Statement>, actual: Node<Statement>,
}, },
ExpectedBlock {
actual: Node<Statement>,
},
ExpectedIdentifier { ExpectedIdentifier {
actual: TokenOwned, actual: TokenOwned,
position: Span, position: Span,
@ -633,6 +728,7 @@ impl ParseError {
match self { match self {
ParseError::BooleanError { position, .. } => *position, ParseError::BooleanError { position, .. } => *position,
ParseError::ExpectedAssignment { actual } => actual.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,
@ -657,6 +753,7 @@ impl Display for ParseError {
match self { match self {
Self::BooleanError { error, .. } => write!(f, "{}", error), Self::BooleanError { error, .. } => write!(f, "{}", error),
Self::ExpectedAssignment { .. } => write!(f, "Expected assignment"), 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}")
} }
@ -677,6 +774,114 @@ 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] #[test]
fn malformed_map() { fn malformed_map() {
let input = "{ x = 1, y = 2, z = 3; }"; let input = "{ x = 1, y = 2, z = 3; }";

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,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) => { Statement::List(nodes) => {
let values = nodes let values = nodes
.into_iter() .into_iter()