feat(tazjin/rlox): Add support for statements
First part of https://craftinginterpreters.com/statements-and-state.html Supports print statements, as well as evaluation for the sake of it (i.e. future side-effects). Change-Id: Ic6653b568f98d6cfe3f297615b7113c0ba1d9a70 Reviewed-on: https://cl.tvl.fyi/c/depot/+/2287 Reviewed-by: tazjin <mail@tazj.in> Tested-by: BuildkiteCI
This commit is contained in:
parent
c3bbb861b8
commit
75ae25daa9
3 changed files with 73 additions and 35 deletions
|
@ -4,6 +4,7 @@ pub enum ErrorKind {
|
|||
UnterminatedString,
|
||||
UnmatchedParens,
|
||||
ExpectedExpression(String),
|
||||
ExpectedSemicolon,
|
||||
TypeError(String),
|
||||
}
|
||||
|
||||
|
|
|
@ -1,33 +1,25 @@
|
|||
use crate::errors::{report, Error, ErrorKind};
|
||||
use crate::parser::{self, Expr, Literal};
|
||||
use crate::scanner::{self, Token, TokenKind};
|
||||
use crate::parser::{self, Expr, Literal, Program, Statement};
|
||||
use crate::scanner::{self, TokenKind};
|
||||
|
||||
// Run some Lox code and print it to stdout
|
||||
pub fn run(code: &str) {
|
||||
let chars: Vec<char> = code.chars().collect();
|
||||
|
||||
match scanner::scan(&chars) {
|
||||
Ok(tokens) => {
|
||||
print_tokens(&tokens);
|
||||
match parser::parse(tokens) {
|
||||
Ok(expr) => {
|
||||
println!("Expression:\n{:?}", expr);
|
||||
println!("Result: {:?}", eval(&expr));
|
||||
Ok(tokens) => match parser::parse(tokens) {
|
||||
Ok(program) => {
|
||||
println!("Program:\n{:?}", program);
|
||||
if let Err(err) = run_program(&program) {
|
||||
println!("Error in program: {:?}", err);
|
||||
}
|
||||
Err(errors) => report_errors(errors),
|
||||
}
|
||||
}
|
||||
Err(errors) => report_errors(errors),
|
||||
},
|
||||
Err(errors) => report_errors(errors),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_tokens<'a>(tokens: &Vec<Token<'a>>) {
|
||||
println!("Tokens:");
|
||||
for token in tokens {
|
||||
println!("{:?}", token);
|
||||
}
|
||||
}
|
||||
|
||||
fn report_errors(errors: Vec<Error>) {
|
||||
for error in errors {
|
||||
report(&error);
|
||||
|
@ -111,3 +103,19 @@ fn eval<'a>(expr: &Expr<'a>) -> Result<Literal, Error> {
|
|||
Expr::Binary(binary) => eval_binary(binary),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_program<'a>(program: &Program<'a>) -> Result<(), Error> {
|
||||
for stmt in program {
|
||||
match stmt {
|
||||
Statement::Expr(expr) => {
|
||||
eval(expr)?;
|
||||
}
|
||||
Statement::Print(expr) => {
|
||||
let result = eval(expr)?;
|
||||
println!("{:?}", result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -42,9 +42,25 @@ pub enum Expr<'a> {
|
|||
Unary(Unary<'a>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Statement<'a> {
|
||||
Expr(Expr<'a>),
|
||||
Print(Expr<'a>),
|
||||
}
|
||||
|
||||
pub type Program<'a> = Vec<Statement<'a>>;
|
||||
|
||||
// Parser
|
||||
|
||||
/*
|
||||
program → statement* EOF ;
|
||||
|
||||
statement → exprStmt
|
||||
| printStmt ;
|
||||
|
||||
exprStmt → expression ";" ;
|
||||
printStmt → "print" expression ";" ;
|
||||
|
||||
expression → equality ;
|
||||
equality → comparison ( ( "!=" | "==" ) comparison )* ;
|
||||
comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
|
||||
|
@ -62,10 +78,31 @@ struct Parser<'a> {
|
|||
}
|
||||
|
||||
type ExprResult<'a> = Result<Expr<'a>, Error>;
|
||||
type StmtResult<'a> = Result<Statement<'a>, Error>;
|
||||
|
||||
impl<'a> Parser<'a> {
|
||||
// recursive-descent parser functions
|
||||
|
||||
fn statement(&mut self) -> StmtResult<'a> {
|
||||
if self.match_token(&[TokenKind::Print]) {
|
||||
self.print_statement()
|
||||
} else {
|
||||
self.expr_statement()
|
||||
}
|
||||
}
|
||||
|
||||
fn print_statement(&mut self) -> StmtResult<'a> {
|
||||
let expr = self.expression()?;
|
||||
self.consume(&TokenKind::Semicolon, ErrorKind::ExpectedSemicolon)?;
|
||||
Ok(Statement::Print(expr))
|
||||
}
|
||||
|
||||
fn expr_statement(&mut self) -> StmtResult<'a> {
|
||||
let expr = self.expression()?;
|
||||
self.consume(&TokenKind::Semicolon, ErrorKind::ExpectedSemicolon)?;
|
||||
Ok(Statement::Expr(expr))
|
||||
}
|
||||
|
||||
fn expression(&mut self) -> ExprResult<'a> {
|
||||
self.equality()
|
||||
}
|
||||
|
@ -231,34 +268,26 @@ impl<'a> Parser<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse<'a>(tokens: Vec<Token<'a>>) -> Result<Expr<'a>, Vec<Error>> {
|
||||
pub fn parse<'a>(tokens: Vec<Token<'a>>) -> Result<Program<'a>, Vec<Error>> {
|
||||
let mut parser = Parser { tokens, current: 0 };
|
||||
let mut program: Program<'a> = vec![];
|
||||
let mut errors: Vec<Error> = vec![];
|
||||
|
||||
while !parser.is_at_end() {
|
||||
match parser.expression() {
|
||||
match parser.statement() {
|
||||
Err(err) => {
|
||||
errors.push(err);
|
||||
parser.synchronise();
|
||||
}
|
||||
Ok(expr) => {
|
||||
if !parser.is_at_end() {
|
||||
// TODO(tazjin): This isn't a functional language
|
||||
// - multiple statements should be allowed, at
|
||||
// some point.
|
||||
let current = &parser.tokens[parser.current];
|
||||
errors.push(Error {
|
||||
line: current.line,
|
||||
kind: ErrorKind::UnexpectedChar(current.lexeme[0]),
|
||||
});
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
return Ok(expr);
|
||||
}
|
||||
Ok(stmt) => {
|
||||
program.push(stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Err(errors);
|
||||
if errors.is_empty() {
|
||||
Ok(program)
|
||||
} else {
|
||||
Err(errors)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue