diff --git a/src/ast.rs b/src/ast.rs index 3e2961e..fb03341 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -22,4 +22,27 @@ pub enum Expr { op: Token, right: Box, }, + Variable { + name: Token, + }, + Assign { + name: Token, + value: Box, + }, +} + +pub enum Stmt { + Block { + statements: Vec, + }, + Expression { + expression: Expr, + }, + Print { + expression: Expr, + }, + Var { + name: Token, + initializer: Option, + }, } diff --git a/src/environment.rs b/src/environment.rs new file mode 100644 index 0000000..6c26c20 --- /dev/null +++ b/src/environment.rs @@ -0,0 +1,65 @@ +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use crate::token::{LiteralType, Token}; + +pub struct Environment { + values: HashMap, + enclosing: Option>>, +} + +pub enum EnvironmentError { + AssignError, +} + +impl Environment { + pub fn new() -> Self { + Self { + values: HashMap::new(), + enclosing: None, + } + } + + pub fn with_enclosing(enclosing: &Rc>) -> Self { + Self { + values: HashMap::new(), + enclosing: Some(Rc::clone(enclosing)), + } + } + + pub fn define(&mut self, name: &str, val: LiteralType) { + // do not like this at all. String is allocated each time a variable is defined. Might be + // bad or might be good. I don't know :D + self.values.insert(name.to_string(), val); + } + + pub fn assign(&mut self, name: &Token, val: LiteralType) -> Result<(), EnvironmentError> { + let cloned = val.clone(); + let assigned = self + .values + .get_mut(&name.lexeme) + .map(|l| *l = val) + .ok_or(EnvironmentError::AssignError); + + if assigned.is_err() { + if let Some(enclosing) = &mut self.enclosing { + return enclosing.borrow_mut().assign(name, cloned); + } + } + + assigned + } + + pub fn get(&self, name: &Token) -> Option { + //self.values.get(&name.lexeme).cloned() + + let value = self.values.get(&name.lexeme); + + if value.is_none() { + if let Some(enclosing) = &self.enclosing { + return enclosing.borrow().get(name); + } + } + + value.cloned() + } +} diff --git a/src/interpreter.rs b/src/interpreter.rs index 302657b..187caf0 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,5 +1,8 @@ +use std::{cell::RefCell, rc::Rc}; + use crate::{ - ast::Expr, + ast::{Expr, Stmt}, + environment::Environment, token::{LiteralType, Token, TokenType}, }; @@ -18,27 +21,97 @@ impl RuntimeError { } } -pub fn interpret(expr: &Expr) -> Result { +pub fn interpret(statements: &Vec) -> Result<(), RuntimeError> { + let environment = Rc::new(RefCell::new(Environment::new())); + for statement in statements { + execute(statement, &environment)?; + } + + Ok(()) +} + +fn execute(statement: &Stmt, environment: &Rc>) -> Result<(), RuntimeError> { + match statement { + Stmt::Expression { expression } => { + evaluate(expression, &mut environment.borrow_mut())?; + } + Stmt::Print { expression } => { + let expr = evaluate(expression, &mut environment.borrow_mut())?; + println!("{expr}"); + } + Stmt::Var { name, initializer } => { + let value = if let Some(initializer) = initializer { + evaluate(initializer, &mut environment.borrow_mut())? + } else { + LiteralType::Nil + }; + environment.borrow_mut().define(&name.lexeme, value); + } + Stmt::Block { statements } => { + execute_block(statements, environment)?; + } + } + + Ok(()) +} + +fn execute_block( + statements: &Vec, + environment: &Rc>, +) -> Result<(), RuntimeError> { + let block_enviroment = Rc::new(RefCell::new(Environment::with_enclosing(environment))); + for stmt in statements { + execute(stmt, &block_enviroment)?; + } + + Ok(()) +} + +fn evaluate(expr: &Expr, environment: &mut Environment) -> Result { match expr { Expr::Ternary { first, second, third, .. - } => ternary(first, second, third), - Expr::Binary { left, op, right } => binary(&interpret(left)?, &interpret(right)?, op), - Expr::Grouping { expression } => interpret(expression), + } => ternary(first, second, third, environment), + Expr::Binary { left, op, right } => binary( + &evaluate(left, environment)?, + &evaluate(right, environment)?, + op, + ), + Expr::Grouping { expression } => evaluate(expression, environment), Expr::Literal { value } => Ok(value.clone()), - Expr::Unary { op, right } => Ok(unary(&interpret(right)?, op)), + Expr::Unary { op, right } => Ok(unary(&evaluate(right, environment)?, op)), + Expr::Variable { name } => environment.get(name).ok_or_else(|| RuntimeError { + token: name.clone(), + message: format!("Undefined variable {}.", name.lexeme), + }), + Expr::Assign { name, value } => { + let value = evaluate(value, environment)?; + environment + .assign(name, value.clone()) + .map_err(|_| RuntimeError { + token: name.clone(), + message: format!("Undefined variable {}.", name.lexeme), + })?; + + Ok(value) + } } } -fn ternary(first: &Expr, second: &Expr, third: &Expr) -> Result { - let first = interpret(first)?; +fn ternary( + first: &Expr, + second: &Expr, + third: &Expr, + environment: &mut Environment, +) -> Result { + let first = evaluate(first, environment)?; if is_truthy(&first) { - return interpret(second); + return evaluate(second, environment); } - interpret(third) + evaluate(third, environment) } fn binary( @@ -71,7 +144,7 @@ fn binary( (Greater | GreaterEqual | Less | LessEqual | Minus | Slash | Star, _, _) => Err(RuntimeError::new(op, "Operands must be numbers")), (Plus, _, _) => Err(RuntimeError::new(op, "Operands must be two numbers or two strings")), - _ => unreachable!("Shouldn't happen. Expr::Binary for interpret. Some case is a binary operation that wasn't matched") + _ => unreachable!("Shouldn't happen. Expr::Binary for evaluate. Some case is a binary operation that wasn't matched") } } @@ -79,7 +152,7 @@ fn unary(right: &LiteralType, op: &Token) -> LiteralType { match (op.t_type, &right) { (TokenType::Minus, LiteralType::Number(num)) => LiteralType::Number(-num), (TokenType::Bang, _) => LiteralType::Bool(!is_truthy(right)), - _ => unreachable!("Shouldn't happen. Expr::Unary for interpret"), + _ => unreachable!("Shouldn't happen. Expr::Unary for evaluate"), } } diff --git a/src/lib.rs b/src/lib.rs index 20220c8..7d270da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,11 +5,12 @@ use std::{ }; use interpreter::RuntimeError; -use parser::Parser; +use parser::{ParseError, Parser}; use scanner::Scanner; use token::{Token, TokenType}; mod ast; +mod environment; mod interpreter; mod parser; mod printer; @@ -22,6 +23,7 @@ pub enum RunError { FileReadError(io::Error), OtherError(Box), // to be added, RuntimeError(RuntimeError), + ParseError, } impl From for RunError { @@ -43,14 +45,28 @@ pub fn run(src: &str) -> Result<(), RunError> { let mut parser = Parser::new(tokens); - let expression = parser.parse().inspect_err(|e| error(&e.token, &e.msg))?; + let statements = parser.parse(); - let interpreted_value = interpreter::interpret(&expression) + // i don't want to collect the errors and allocate a vec + let mut p_error = false; + + for err in statements.iter().filter_map(|x| x.as_ref().err()) { + if !p_error { + p_error = true; + } + error(err); + } + + if p_error { + return Err(RunError::ParseError); + } + + let statements = statements.into_iter().flatten().collect(); + + interpreter::interpret(&statements) .inspect_err(runtime_error) .map_err(RunError::RuntimeError)?; - println!("{interpreted_value}"); - Ok(()) } @@ -76,10 +92,10 @@ pub fn report(line: usize, location: &str, message: &str) { eprintln!("[line {line}] Error {location}: {message}"); } -fn error(token: &Token, message: &str) { +fn error(ParseError { token, msg }: &ParseError) { match token.t_type { - TokenType::EOF => report(token.line, " at end", message), - _ => report(token.line, &format!(" at '{}'", token.lexeme), message), + TokenType::EOF => report(token.line, " at end", msg), + _ => report(token.line, &format!("at '{}'", token.lexeme), msg), } } diff --git a/src/main.rs b/src/main.rs index a291031..e1ac5f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,10 @@ fn main() -> ExitCode { if let Err(RunError::RuntimeError(r)) = result { return ExitCode::from(70); } + + if let Err(RunError::ParseError) = result { + return ExitCode::from(75); + } } else { let result = run_prompt(); diff --git a/src/parser.rs b/src/parser.rs index 6ef9667..2ea019b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use crate::{ - ast::Expr, + ast::{Expr, Stmt}, token::{LiteralType, Token, TokenType}, }; @@ -29,12 +29,111 @@ impl Parser<'_> { Parser { tokens, current: 0 } } - pub fn parse(&mut self) -> Result { - self.expression() + //pub fn parse(&mut self) -> Result { + // self.expression() + //} + + // maps to program rule in the grammar + pub fn parse(&mut self) -> Vec> { + let mut statements = Vec::new(); + + while !self.is_at_end() { + statements.push(self.declaration()); + } + + statements + } + + fn declaration(&mut self) -> Result { + let stmt = if self.match_token(&[TokenType::Var]) { + self.var_declaration() + } else { + self.statement() + }; + + stmt.inspect_err(|_| self.synchronize()) + } + + fn var_declaration(&mut self) -> Result { + let name = self.consume(TokenType::Identifier, "Expect variable name")?; + let initializer = if self.match_token(&[TokenType::Equal]) { + Some(self.expression()?) + } else { + None + }; + + self.consume( + TokenType::Semicolon, + "Expect ';' after variable declaration.", + )?; + + Ok(Stmt::Var { name, initializer }) + } + + fn statement(&mut self) -> Result { + if self.match_token(&[TokenType::Print]) { + return self.print_statement(); + } + + if self.match_token(&[TokenType::LeftBrace]) { + return Ok(Stmt::Block { + statements: self.block()?, + }); + } + + self.expression_statement() + } + + fn block(&mut self) -> Result, ParseError> { + let mut statements = Vec::new(); + + while !self.check(TokenType::RightBrace) && !self.is_at_end() { + statements.push(self.declaration()?); + } + + self.consume(TokenType::RightBrace, "Expect '}' after block.")?; + + Ok(statements) + } + + fn print_statement(&mut self) -> Result { + let expression = self.expression()?; + self.consume(TokenType::Semicolon, "Expect ';' after value.")?; + + Ok(Stmt::Print { expression }) + } + + fn expression_statement(&mut self) -> Result { + let expression = self.expression()?; + self.consume(TokenType::Semicolon, "Expect ';' after expression.")?; + + Ok(Stmt::Expression { expression }) } fn expression(&mut self) -> Result { - self.comma() + self.assignment() + } + + fn assignment(&mut self) -> Result { + let expr = self.comma()?; + + if self.match_token(&[TokenType::Equal]) { + let value = self.assignment()?; + let equals = self.previous(); + + if let Expr::Variable { name } = expr { + return Ok(Expr::Assign { + name, + value: Box::new(value), + }); + } + return Err(ParseError { + token: equals.clone(), + msg: "Invalid assignment target.".to_string(), + }); + } + + Ok(expr) } // Challenge #1. We're writing comma before equality, because it has the lowest precedence @@ -140,6 +239,12 @@ impl Parser<'_> { }); } + if self.match_token(&[Identifier]) { + return Ok(Expr::Variable { + name: self.previous().clone(), + }); + } + if self.match_token(&[Equal, BangEqual]) { let _ = self.equality(); return Err(ParseError { @@ -189,7 +294,6 @@ impl Parser<'_> { }) } - // will not be used for the time being (per the book) // used for error recovery fn synchronize(&mut self) { use TokenType::*; @@ -203,9 +307,8 @@ impl Parser<'_> { if let Class | Fun | Var | For | If | While | Print | Return = self.peek().t_type { return; } + self.advance(); } - - self.advance(); } fn left_association_binary( diff --git a/src/printer.rs b/src/printer.rs index e2e48cc..b20ab5e 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -16,6 +16,8 @@ pub fn pretty_print(expr: &Expr) -> String { second, third, } => parenthesize("?:", &[first, second, third]), + Expr::Variable { name } => name.lexeme.clone(), + Expr::Assign { name, value } => parenthesize(&name.lexeme, &[value]), } }