diff --git a/src/ast.rs b/src/ast.rs index ad8aca3..326b00d 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -40,6 +40,7 @@ pub enum Stmt { Block { statements: Vec, }, + Break, Expression { expression: Expr, }, diff --git a/src/interpreter.rs b/src/interpreter.rs index 71629f2..3ead726 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,8 +1,9 @@ +use core::panic; use std::{cell::RefCell, rc::Rc}; use crate::{ ast::{Expr, Stmt}, - environment::{self, Environment}, + environment::Environment, token::{LiteralType, Token, TokenType}, }; @@ -21,10 +22,30 @@ impl RuntimeError { } } +pub enum InterpreterError { + RuntimeError(RuntimeError), + BreakSignal, +} + +impl From for InterpreterError { + fn from(value: RuntimeError) -> Self { + Self::RuntimeError(value) + } +} + +impl From for RuntimeError { + fn from(value: InterpreterError) -> Self { + match value { + InterpreterError::RuntimeError(runtime_error) => runtime_error, + InterpreterError::BreakSignal => panic!("Not a runtime error"), + } + } +} + pub fn interpret( statements: &Vec, environment: &Rc>, -) -> Result<(), RuntimeError> { +) -> Result<(), InterpreterError> { for statement in statements { execute(statement, environment)?; } @@ -32,7 +53,10 @@ pub fn interpret( Ok(()) } -fn execute(statement: &Stmt, environment: &Rc>) -> Result<(), RuntimeError> { +fn execute( + statement: &Stmt, + environment: &Rc>, +) -> Result<(), InterpreterError> { match statement { Stmt::Expression { expression } => { evaluate(expression, &mut environment.borrow_mut())?; @@ -65,9 +89,13 @@ fn execute(statement: &Stmt, environment: &Rc>) -> Result<( } Stmt::While { condition, body } => { while is_truthy(&evaluate(condition, &mut environment.borrow_mut())?) { - execute(body, environment)?; + let result = execute(body, environment); + if result.is_err() { + break; + } } } + Stmt::Break => Err(InterpreterError::BreakSignal)?, } Ok(()) @@ -76,7 +104,7 @@ fn execute(statement: &Stmt, environment: &Rc>) -> Result<( fn execute_block( statements: &Vec, environment: &Rc>, -) -> Result<(), RuntimeError> { +) -> Result<(), InterpreterError> { let block_enviroment = Rc::new(RefCell::new(Environment::with_enclosing(environment))); for stmt in statements { execute(stmt, &block_enviroment)?; @@ -85,7 +113,7 @@ fn execute_block( Ok(()) } -fn evaluate(expr: &Expr, environment: &mut Environment) -> Result { +fn evaluate(expr: &Expr, environment: &mut Environment) -> Result { match expr { Expr::Ternary { first, @@ -112,7 +140,8 @@ fn evaluate(expr: &Expr, environment: &mut Environment) -> Result { let value = evaluate(value, environment)?; environment @@ -145,7 +174,7 @@ fn ternary( second: &Expr, third: &Expr, environment: &mut Environment, -) -> Result { +) -> Result { let first = evaluate(first, environment)?; if is_truthy(&first) { return evaluate(second, environment); @@ -157,7 +186,7 @@ fn binary( left: &LiteralType, right: &LiteralType, op: &Token, -) -> Result { +) -> Result { use LiteralType::{Bool, Number, String}; use TokenType::{ BangEqual, Comma, EqualEqual, Greater, GreaterEqual, Less, LessEqual, Minus, Plus, Slash, @@ -180,8 +209,8 @@ fn binary( (Star, Number(left), Number(right)) => Ok(Number(left * right)), /* comma operator discard the left operand, so we just return the evaluation of the right operand */ (Comma, _,_) => Ok(right.clone()), - (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")), + (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 evaluate. Some case is a binary operation that wasn't matched") } diff --git a/src/lib.rs b/src/lib.rs index 6b95fa5..dbd2e9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ use environment::Environment; use interpreter::RuntimeError; use parser::{ParseError, Parser}; use scanner::Scanner; -use token::{Token, TokenType}; +use token::TokenType; mod ast; mod environment; @@ -68,6 +68,7 @@ pub fn run(src: &str, environment: &Rc>) -> Result<(), RunE let statements = statements.into_iter().flatten().collect(); interpreter::interpret(&statements, environment) + .map_err(|x| x.into()) .inspect_err(runtime_error) .map_err(RunError::RuntimeError)?; diff --git a/src/parser.rs b/src/parser.rs index 84288b2..53d383d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,11 +3,13 @@ use std::fmt::Display; use crate::{ ast::{Expr, Stmt}, token::{LiteralType, Token, TokenType}, + utils::{defer, expr, ScopeCall}, }; pub struct Parser<'a> { tokens: &'a Vec, current: usize, + loop_depth: u32, } #[derive(Debug)] @@ -26,7 +28,15 @@ impl std::error::Error for ParseError {} impl Parser<'_> { pub fn new(tokens: &Vec) -> Parser<'_> { - Parser { tokens, current: 0 } + Parser { + tokens, + current: 0, + loop_depth: 0, + } + } + + pub fn loop_depth(&mut self) -> &mut u32 { + &mut self.loop_depth } //pub fn parse(&mut self) -> Result { @@ -84,6 +94,10 @@ impl Parser<'_> { return self.while_statement(); } + if self.match_token(&[TokenType::Break]) { + return self.break_statement(); + } + if self.match_token(&[TokenType::LeftBrace]) { return Ok(Stmt::Block { statements: self.block()?, @@ -105,6 +119,18 @@ impl Parser<'_> { Ok(statements) } + fn break_statement(&mut self) -> Result { + if *self.loop_depth() == 0 { + return Err(ParseError { + token: self.previous().clone(), + msg: "Must be inside a loop to use 'break'".to_string(), + }); + } + self.consume(TokenType::Semicolon, "Expect ';' after 'break'")?; + + Ok(Stmt::Break) + } + fn if_statement(&mut self) -> Result { self.consume(TokenType::LeftParen, "Expect '(' after 'if'.")?; let condition = self.expression()?; @@ -125,15 +151,20 @@ impl Parser<'_> { } fn while_statement(&mut self) -> Result { + self.loop_depth += 1; self.consume(TokenType::LeftParen, "Expect '(' after 'while'.")?; let condition = self.expression()?; self.consume(TokenType::RightParen, "Expect ')' after while condition.")?; let body = Box::new(self.statement()?); + defer! { + *self.loop_depth() += 1; + } Ok(Stmt::While { condition, body }) } fn for_statement(&mut self) -> Result { + self.loop_depth += 1; self.consume(TokenType::LeftParen, "Expect '(' after 'for'.")?; let initializer = if self.match_token(&[TokenType::Semicolon]) { None @@ -182,6 +213,10 @@ impl Parser<'_> { None => body, }; + defer! { + *self.loop_depth() -= 1; + } + Ok(body) } diff --git a/src/scanner.rs b/src/scanner.rs index 0accbe7..f8dadff 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -266,6 +266,7 @@ fn get_identified_keyword(identifier: &str) -> Option { "true" => Some(TokenType::True), "var" => Some(TokenType::Var), "while" => Some(TokenType::While), + "break" => Some(TokenType::Break), _ => None, } } diff --git a/src/token.rs b/src/token.rs index 5c2cae9..696d88f 100644 --- a/src/token.rs +++ b/src/token.rs @@ -30,6 +30,7 @@ pub enum TokenType { Number, And, + Break, Class, Else, False, diff --git a/src/utils.rs b/src/utils.rs index 199cc08..7971c41 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -50,3 +50,30 @@ impl StringUtils for String { self.substring(start, len) } } + +pub struct ScopeCall { + pub c: Option, +} + +impl Drop for ScopeCall { + fn drop(&mut self) { + self.c.take().unwrap()() + } +} + +macro_rules! expr { + ($e: expr) => { + $e + }; +} + +macro_rules! defer { + ($($data: tt)*) => ( + let _scope_call = ScopeCall { + c: Some(|| -> () { expr!({ $($data)* }) }) + }; + ) +} + +pub(crate) use defer; +pub(crate) use expr;