statements in rust
This commit is contained in:
parent
15b331f447
commit
a629ddca05
6 changed files with 434 additions and 194 deletions
|
@ -3,6 +3,7 @@ use tracing::error;
|
|||
|
||||
use crate::{
|
||||
expression::Expression,
|
||||
statement::Statement,
|
||||
token::{
|
||||
self, Token,
|
||||
TokenType::{self, *},
|
||||
|
@ -13,14 +14,24 @@ use crate::{
|
|||
pub enum ParserError {
|
||||
#[error("empty token stream")]
|
||||
NoTokens,
|
||||
#[error("line {0}: expected expression")]
|
||||
#[error("[line {0}] expected expression")]
|
||||
ExpressionExpected(usize),
|
||||
#[error("line {0}: expected ')' after expression.")]
|
||||
#[error("[line {0}] expected ')' after expression.")]
|
||||
ParenAfterExpression(usize),
|
||||
#[error("Out of bounds access at index {0}.")]
|
||||
#[error("[Out of bounds access at index {0}.")]
|
||||
OutOfBoundsAccess(usize),
|
||||
#[error("line {0}: literal expected.")]
|
||||
#[error("[line {0}] literal expected.")]
|
||||
LiteralExpected(usize),
|
||||
#[error("[line {0}] expected ';' after value.")]
|
||||
SemicolonAfterValueExpected(usize),
|
||||
#[error("[line {0}] expected ';' after expression.")]
|
||||
SemicolonAfterExpressionExpected(usize),
|
||||
#[error("[line {0}] expected variable name.")]
|
||||
VariableNameExpected(usize),
|
||||
#[error("[line {0}] invalid assignment target.")]
|
||||
InvalidAssignmentTarget(usize),
|
||||
#[error("[line {0}] expected '}}' after block.")]
|
||||
RightBraceAfterBlockExpected(usize),
|
||||
}
|
||||
|
||||
/// Parse the Lox language.
|
||||
|
@ -43,6 +54,23 @@ impl Parser {
|
|||
})
|
||||
}
|
||||
|
||||
/// Parse all tokens to a list of statements for execution.
|
||||
fn run(&mut self) -> Result<Vec<Statement>, ParserError> {
|
||||
let mut statements = Vec::new();
|
||||
|
||||
while !self.is_at_end() {
|
||||
match self.declaration() {
|
||||
Ok(x) => statements.push(x),
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
self.synchronize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(statements)
|
||||
}
|
||||
|
||||
/// Check if any of the provided types match the type of the current token.
|
||||
///
|
||||
/// If so, advance the current token.
|
||||
|
@ -117,7 +145,99 @@ impl Parser {
|
|||
|
||||
/// expression -> equality ;
|
||||
fn expression(&mut self) -> Result<Expression, ParserError> {
|
||||
self.equality()
|
||||
self.assignment()
|
||||
}
|
||||
|
||||
fn declaration(&mut self) -> Result<Statement, ParserError> {
|
||||
if self.matches(&[Var]) {
|
||||
self.var_declaration()
|
||||
} else {
|
||||
self.statement()
|
||||
}
|
||||
}
|
||||
|
||||
fn statement(&mut self) -> Result<Statement, ParserError> {
|
||||
if self.matches(&[Print]) {
|
||||
self.print_statement()
|
||||
} else if self.matches(&[LeftBrace]) {
|
||||
Ok(Statement::Block(self.block()?))
|
||||
} else {
|
||||
self.expression_statement()
|
||||
}
|
||||
}
|
||||
|
||||
fn print_statement(&mut self) -> Result<Statement, ParserError> {
|
||||
let value = self.expression()?;
|
||||
let line = self.current_token.line;
|
||||
self.consume(&Semicolon)
|
||||
.ok_or(ParserError::SemicolonAfterValueExpected(line))?;
|
||||
|
||||
Ok(Statement::Print(value))
|
||||
}
|
||||
|
||||
fn var_declaration(&mut self) -> Result<Statement, ParserError> {
|
||||
let line = self.current_token.line;
|
||||
let name = self
|
||||
.consume(&Identifier)
|
||||
.ok_or(ParserError::VariableNameExpected(line))?
|
||||
.clone();
|
||||
|
||||
let initializer = if self.matches(&[Equal]) {
|
||||
Some(self.expression()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.consume(&Semicolon)
|
||||
.ok_or(ParserError::SemicolonAfterExpressionExpected(line))?;
|
||||
|
||||
Ok(Statement::Var {
|
||||
name,
|
||||
initializer: Box::new(initializer),
|
||||
})
|
||||
}
|
||||
|
||||
fn expression_statement(&mut self) -> Result<Statement, ParserError> {
|
||||
let expr = self.expression()?;
|
||||
let line = self.current_token.line;
|
||||
self.consume(&Semicolon)
|
||||
.ok_or(ParserError::SemicolonAfterExpressionExpected(line))?;
|
||||
|
||||
Ok(Statement::Expression(expr))
|
||||
}
|
||||
|
||||
fn block(&mut self) -> Result<Vec<Statement>, ParserError> {
|
||||
let mut statements = Vec::new();
|
||||
|
||||
while !self.check(&RightBrace) && !self.is_at_end() {
|
||||
statements.push(self.declaration()?);
|
||||
}
|
||||
|
||||
let line = self.previous()?.line;
|
||||
self.consume(&RightBrace)
|
||||
.ok_or(ParserError::RightBraceAfterBlockExpected(line))?;
|
||||
|
||||
Ok(statements)
|
||||
}
|
||||
|
||||
fn assignment(&mut self) -> Result<Expression, ParserError> {
|
||||
let expr = self.equality()?;
|
||||
|
||||
if self.matches(&[Equal]) {
|
||||
let equals = self.previous()?.clone();
|
||||
let value = self.assignment()?;
|
||||
|
||||
if let Expression::Variable { name } = expr {
|
||||
Ok(Expression::Assign {
|
||||
name,
|
||||
value: Box::new(value),
|
||||
})
|
||||
} else {
|
||||
Err(ParserError::InvalidAssignmentTarget(equals.line))
|
||||
}
|
||||
} else {
|
||||
Ok(expr)
|
||||
}
|
||||
}
|
||||
|
||||
/// equality -> comparison ( ( "!=" | "==" ) comparison )* ;
|
||||
|
@ -169,6 +289,9 @@ impl Parser {
|
|||
Ok(Expression::Literal {
|
||||
value: token::Literal::Nil,
|
||||
})
|
||||
} else if self.matches(&[Identifier]) {
|
||||
let prev = self.previous()?.clone();
|
||||
Ok(Expression::Variable { name: prev })
|
||||
} else if self.matches(&[Number, String]) {
|
||||
let prev = self.previous()?;
|
||||
let value = prev
|
||||
|
@ -205,106 +328,7 @@ impl Parser {
|
|||
}
|
||||
|
||||
/// Try to parse the provided tokens into an AST.
|
||||
pub fn generate_ast(tokens: Vec<Token>) -> Result<Expression, ParserError> {
|
||||
pub fn ast(tokens: Vec<Token>) -> Result<Vec<Statement>, ParserError> {
|
||||
let mut parser = Parser::new(tokens)?;
|
||||
parser.expression()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
expression::Expression,
|
||||
token::{Literal, Token, TokenType},
|
||||
};
|
||||
|
||||
use super::generate_ast;
|
||||
|
||||
#[test]
|
||||
fn simple_expression() {
|
||||
let ast = generate_ast(vec![
|
||||
Token {
|
||||
token_type: TokenType::Number,
|
||||
lexeme: "3".into(),
|
||||
literal: Some(Literal::Number(3.0)),
|
||||
line: 1,
|
||||
},
|
||||
Token {
|
||||
token_type: TokenType::Star,
|
||||
lexeme: "*".into(),
|
||||
literal: None,
|
||||
line: 1,
|
||||
},
|
||||
Token {
|
||||
token_type: TokenType::Number,
|
||||
lexeme: "4".into(),
|
||||
literal: Some(Literal::Number(4.0)),
|
||||
line: 1,
|
||||
},
|
||||
Token {
|
||||
token_type: TokenType::Plus,
|
||||
lexeme: "+".into(),
|
||||
literal: None,
|
||||
line: 1,
|
||||
},
|
||||
Token {
|
||||
token_type: TokenType::Number,
|
||||
lexeme: "2".into(),
|
||||
literal: Some(Literal::Number(2.0)),
|
||||
line: 1,
|
||||
},
|
||||
Token {
|
||||
token_type: TokenType::Star,
|
||||
lexeme: "*".into(),
|
||||
literal: None,
|
||||
line: 1,
|
||||
},
|
||||
Token {
|
||||
token_type: TokenType::Number,
|
||||
lexeme: "6".into(),
|
||||
literal: Some(Literal::Number(6.0)),
|
||||
line: 1,
|
||||
},
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
ast,
|
||||
Expression::Binary {
|
||||
left: Box::new(Expression::Binary {
|
||||
left: Box::new(Expression::Literal {
|
||||
value: Literal::Number(3.0)
|
||||
}),
|
||||
operator: Token {
|
||||
token_type: TokenType::Star,
|
||||
lexeme: "*".into(),
|
||||
literal: None,
|
||||
line: 1
|
||||
},
|
||||
right: Box::new(Expression::Literal {
|
||||
value: Literal::Number(4.0)
|
||||
})
|
||||
}),
|
||||
operator: Token {
|
||||
token_type: TokenType::Plus,
|
||||
lexeme: "+".into(),
|
||||
literal: None,
|
||||
line: 1
|
||||
},
|
||||
right: Box::new(Expression::Binary {
|
||||
left: Box::new(Expression::Literal {
|
||||
value: Literal::Number(2.0)
|
||||
}),
|
||||
operator: Token {
|
||||
token_type: TokenType::Star,
|
||||
lexeme: "*".into(),
|
||||
literal: None,
|
||||
line: 1
|
||||
},
|
||||
right: Box::new(Expression::Literal {
|
||||
value: Literal::Number(6.0)
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
parser.run()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue