From 7961a92ad1cb7c280d7680d7746ceace789d3ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Wed, 4 Jan 2023 07:35:40 +0100 Subject: [PATCH] Further attempts at refactoring the error handling code * Everything's still broken though --- src/errors.rs | 66 ++++++++++++++++++++++++++------ src/interpreter/interpretable.rs | 41 +++++++++++--------- src/main.rs | 33 ++++++++-------- src/parser.rs | 12 ++---- src/resolver.rs | 2 +- src/scanner.rs | 4 +- 6 files changed, 99 insertions(+), 59 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index f117993..3766978 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -38,7 +38,7 @@ impl fmt::Display for ErrorKind { #[derive(Debug, Clone)] pub struct SloxError { kind: ErrorKind, - line: usize, + line: Option, pos: String, message: String, } @@ -51,10 +51,10 @@ impl SloxError { pub fn scanner_error(line: usize, ch: Option, message: String) -> Self { Self { kind: ErrorKind::Scan, - line, + line: Some(line), pos: match ch { - None => "at end of input".to_owned(), - Some(ch) => format!("near {:?}", ch) + None => " at end of input".to_owned(), + Some(ch) => format!(" near {:?}", ch), }, message, } @@ -64,25 +64,44 @@ impl SloxError { pub fn with_token(kind: ErrorKind, token: &Token, message: String) -> Self { Self { kind, - line: token.line, + line: Some(token.line), pos: if token.token_type == TokenType::Eof { - "at end of input".to_owned() + " at end of input".to_owned() } else { - format!("near '{}'", token.lexeme) + format!(" near '{}'", token.lexeme) }, message, } } + + /// Create an error indicating a stage failed. + fn stage_failed(kind: ErrorKind) -> Self { + Self { + kind, + line: None, + pos: "".to_owned(), + message: "exiting...".to_owned(), + } + } + + /// Return the type of error + pub fn kind(&self) -> &ErrorKind { + &self.kind + } } impl Error for SloxError {} impl Display for SloxError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let line = match self.line { + None => "".to_owned(), + Some(l) => format!("[line {}] ", l), + }; write!( f, - "[line {}] {} error {}: {}", - self.line, self.kind, self.pos, self.message + "{}{} error{}: {}", + line, self.kind, self.pos, self.message ) } } @@ -102,9 +121,32 @@ impl ErrorHandler { /// Report an error. pub fn report(&mut self, error: SloxError) { - if self.had_error.is_none() { - self.had_error = Some(error.kind); - } + self.had_error = Some(error.kind); println!("{error}"); } + + /// Transmit the last value returned by some component, or report its error + /// and generate the final error. + pub fn report_or_continue(&mut self, result: SloxResult) -> SloxResult { + match result { + Ok(v) => Ok(v), + Err(e) => { + self.report(e); + let fe = SloxError::stage_failed(e.kind); + println!("{fe}"); + Err(fe) + } + } + } + + /// Generate an error that corresponds to the last error encountered. + pub fn final_error(&self, result: T) -> SloxResult { + if let Some(err_kind) = self.had_error { + let err = SloxError::stage_failed(err_kind); + println!("{err}"); + Err(err) + } else { + Ok(result) + } + } } diff --git a/src/interpreter/interpretable.rs b/src/interpreter/interpretable.rs index 7844883..131f5c5 100644 --- a/src/interpreter/interpretable.rs +++ b/src/interpreter/interpretable.rs @@ -2,21 +2,26 @@ use std::{cell::RefCell, rc::Rc}; use crate::{ ast, - errors::{ErrorHandler, InterpreterError}, + errors::{ErrorHandler, SloxError}, interpreter::{Environment, EnvironmentRef, Value}, + resolver::ResolvedVariables, tokens::{Token, TokenType}, }; use super::functions::Function; /// Evaluate an interpretable, returning its value. -pub fn evaluate(err_hdl: &mut ErrorHandler, ast: &dyn Interpretable) -> Option { +pub fn evaluate( + err_hdl: &mut ErrorHandler, + ast: &dyn Interpretable, + vars: ResolvedVariables, +) -> Result { let env = Rc::new(RefCell::new(Environment::default())); match ast.interpret(&env) { - Ok(v) => Some(v.result()), + Ok(v) => Ok(v.result()), Err(e) => { - e.report(err_hdl); - None + err_hdl.report(e); + Err(e) } } } @@ -65,7 +70,7 @@ impl From for InterpreterFlowControl { } /// A result returned by some part of the interpreter. -pub type InterpreterResult = Result; +pub type InterpreterResult = Result; /// An Interpretable can be evaluated and will return a value. pub trait Interpretable { @@ -326,12 +331,12 @@ impl ast::ExprNode { TokenType::Plus => match (left_value, right_value) { (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b).into()), (Value::String(a), Value::String(b)) => Ok(Value::String(a + &b).into()), - _ => Err(InterpreterError::new(operator, "type error")), + _ => Err(SloxError::new(operator, "type error")), }, TokenType::Minus => match (left_value, right_value) { (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a - b).into()), - _ => Err(InterpreterError::new(operator, "type error")), + _ => Err(SloxError::new(operator, "type error")), }, TokenType::Star => match (left_value, right_value) { @@ -339,38 +344,38 @@ impl ast::ExprNode { (Value::String(a), Value::Number(b)) => { Ok(Value::String(a.repeat(b as usize)).into()) } - _ => Err(InterpreterError::new(operator, "type error")), + _ => Err(SloxError::new(operator, "type error")), }, TokenType::Slash => match (left_value, right_value) { (Value::Number(a), Value::Number(b)) => { if b == 0. { - Err(InterpreterError::new(operator, "division by zero")) + Err(SloxError::new(operator, "division by zero")) } else { Ok(Value::Number(a / b).into()) } } - _ => Err(InterpreterError::new(operator, "type error")), + _ => Err(SloxError::new(operator, "type error")), }, TokenType::Greater => match (left_value, right_value) { (Value::Number(a), Value::Number(b)) => Ok(Value::Boolean(a > b).into()), - _ => Err(InterpreterError::new(operator, "type error")), + _ => Err(SloxError::new(operator, "type error")), }, TokenType::GreaterEqual => match (left_value, right_value) { (Value::Number(a), Value::Number(b)) => Ok(Value::Boolean(a >= b).into()), - _ => Err(InterpreterError::new(operator, "type error")), + _ => Err(SloxError::new(operator, "type error")), }, TokenType::Less => match (left_value, right_value) { (Value::Number(a), Value::Number(b)) => Ok(Value::Boolean(a < b).into()), - _ => Err(InterpreterError::new(operator, "type error")), + _ => Err(SloxError::new(operator, "type error")), }, TokenType::LessEqual => match (left_value, right_value) { (Value::Number(a), Value::Number(b)) => Ok(Value::Boolean(a <= b).into()), - _ => Err(InterpreterError::new(operator, "type error")), + _ => Err(SloxError::new(operator, "type error")), }, TokenType::EqualEqual => Ok(Value::Boolean(left_value == right_value).into()), @@ -396,7 +401,7 @@ impl ast::ExprNode { if let Value::Number(n) = right_value { Ok(Value::Number(-n).into()) } else { - Err(InterpreterError::new(operator, "number expected")) + Err(SloxError::new(operator, "number expected")) } } @@ -441,7 +446,7 @@ impl ast::ExprNode { if let Value::Callable(callable_ref) = &callee { let callable = callable_ref.borrow(); if callable.arity() != arg_values.len() { - Err(InterpreterError::new( + Err(SloxError::new( right_paren, &format!( "expected {} arguments, found {}", @@ -453,7 +458,7 @@ impl ast::ExprNode { Ok(callable.call(environment, arg_values)?.into()) } } else { - Err(InterpreterError::new( + Err(SloxError::new( right_paren, "can only call functions and classes", )) diff --git a/src/main.rs b/src/main.rs index ce383e5..9cb5f4b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,34 +14,32 @@ use std::{ #[cfg(feature = "dump_ast")] use ast::AstDumper; -use errors::{ErrorHandler, ErrorType}; +use errors::{ErrorHandler, SloxResult}; use interpreter::evaluate; use parser::Parser; +use resolver::resolve_variables; use scanner::Scanner; /// Execute a script. -fn run(source: String) -> ErrorHandler { +fn run(source: String) -> SloxResult<()> { let mut error_handler = ErrorHandler::default(); let scanner = Scanner::new(source); - let tokens = scanner.scan_tokens(&mut error_handler); - + let tokens = scanner.scan_tokens(&mut error_handler)?; #[cfg(feature = "dump_tokens")] for token in tokens.iter() { println!("{:#?}", token); } let parser = Parser::new(tokens); - match parser.parse(&mut error_handler) { - None => (), - Some(ast) => { - #[cfg(feature = "dump_ast")] - println!("AST generated ! {}", ast.dump()); - evaluate(&mut error_handler, &ast); - } - } + let ast = parser.parse(&mut error_handler)?; + #[cfg(feature = "dump_ast")] + println!("AST generated ! {}", ast.dump()); - error_handler + let resolved_vars = error_handler.final_error(resolve_variables(&ast))?; + error_handler.final_error(evaluate(&ast, resolved_vars)); + + Ok(()) } /// Run the REPL. @@ -64,7 +62,7 @@ fn run_prompt() { } /// Load a file and run the script it contains. -fn run_file(file: &str) -> ErrorHandler { +fn run_file(file: &str) -> SloxResult<()> { let contents = fs::read_to_string(file).unwrap_or_else(|_| panic!("Could not load {}", file)); run(contents) } @@ -77,10 +75,9 @@ fn main() -> Result<(), ExitCode> { run_prompt(); Ok(()) } else if n_args == 1 { - match run_file(&args[0]).had_error() { - None => Ok(()), - Some(ErrorType::Parse) => Err(ExitCode::from(65)), - Some(ErrorType::Runtime) => Err(ExitCode::from(70)), + match run_file(&args[0]) { + Ok(()) => Ok(()), + Err(err) => Err(ExitCode::from(err.kind().exit_code())), } } else { println!("Usage: slox [script]"); diff --git a/src/parser.rs b/src/parser.rs index a16d804..6e66e6c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -74,11 +74,11 @@ impl Parser { /// Parse the tokens into an AST and return it, or return nothing if a /// parser error occurs. - pub fn parse(mut self, err_hdl: &mut ErrorHandler) -> Option { + pub fn parse(mut self, err_hdl: &mut ErrorHandler) -> SloxResult { self.loop_state.push(LoopParsingState::None); let result = self.parse_program(err_hdl); self.loop_state.pop(); - result + err_hdl.final_error(result) } /// Synchronize the parser after an error. @@ -111,7 +111,7 @@ impl Parser { /// ``` /// program := statement* /// ``` - fn parse_program(&mut self, err_hdl: &mut ErrorHandler) -> Option { + fn parse_program(&mut self, err_hdl: &mut ErrorHandler) -> ast::ProgramNode { let mut stmts: Vec = Vec::new(); while !self.is_at_end() { match self.parse_statement() { @@ -122,11 +122,7 @@ impl Parser { } } } - if err_hdl.had_error().is_none() { - Some(ast::ProgramNode(stmts)) - } else { - None - } + ast::ProgramNode(stmts) } /// Parse the following rule: diff --git a/src/resolver.rs b/src/resolver.rs index 34a022a..0e09ef5 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -8,7 +8,7 @@ use crate::{ pub type ResolvedVariables = HashMap<*const ast::ExprNode, usize>; -pub fn resolve_variables(program: &ast::ProgramNode) -> Result { +pub fn resolve_variables(program: &ast::ProgramNode) -> SloxResult { let mut state = ResolverState::default(); program.resolve(&mut state).map(|_| state.resolved) } diff --git a/src/scanner.rs b/src/scanner.rs index 48c6f8e..bd64dde 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -60,7 +60,7 @@ impl Scanner { /// Scan the source code, generating the list of tokens and returning it. /// The scanner itself is destroyed once the process is complete. - pub fn scan_tokens(mut self, err_hdl: &mut ErrorHandler) -> Vec { + pub fn scan_tokens(mut self, err_hdl: &mut ErrorHandler) -> SloxResult> { while !self.is_at_end() { self.start = self.current; if let Err(e) = self.scan_token() { @@ -72,7 +72,7 @@ impl Scanner { lexeme: String::from(""), line: self.line, }); - self.tokens + err_hdl.final_error(self.tokens) } /// Read the next token from the input