Further attempts at refactoring the error handling code

* Everything's still broken though
This commit is contained in:
Emmanuel BENOîT 2023-01-04 07:35:40 +01:00
parent 0443754007
commit 7961a92ad1
6 changed files with 99 additions and 59 deletions

View file

@ -38,7 +38,7 @@ impl fmt::Display for ErrorKind {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SloxError { pub struct SloxError {
kind: ErrorKind, kind: ErrorKind,
line: usize, line: Option<usize>,
pos: String, pos: String,
message: String, message: String,
} }
@ -51,10 +51,10 @@ impl SloxError {
pub fn scanner_error(line: usize, ch: Option<char>, message: String) -> Self { pub fn scanner_error(line: usize, ch: Option<char>, message: String) -> Self {
Self { Self {
kind: ErrorKind::Scan, kind: ErrorKind::Scan,
line, line: Some(line),
pos: match ch { pos: match ch {
None => "at end of input".to_owned(), None => " at end of input".to_owned(),
Some(ch) => format!("near {:?}", ch) Some(ch) => format!(" near {:?}", ch),
}, },
message, message,
} }
@ -64,25 +64,44 @@ impl SloxError {
pub fn with_token(kind: ErrorKind, token: &Token, message: String) -> Self { pub fn with_token(kind: ErrorKind, token: &Token, message: String) -> Self {
Self { Self {
kind, kind,
line: token.line, line: Some(token.line),
pos: if token.token_type == TokenType::Eof { pos: if token.token_type == TokenType::Eof {
"at end of input".to_owned() " at end of input".to_owned()
} else { } else {
format!("near '{}'", token.lexeme) format!(" near '{}'", token.lexeme)
}, },
message, 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 Error for SloxError {}
impl Display for SloxError { impl Display for SloxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let line = match self.line {
None => "".to_owned(),
Some(l) => format!("[line {}] ", l),
};
write!( write!(
f, f,
"[line {}] {} error {}: {}", "{}{} error{}: {}",
self.line, self.kind, self.pos, self.message line, self.kind, self.pos, self.message
) )
} }
} }
@ -102,9 +121,32 @@ impl ErrorHandler {
/// Report an error. /// Report an error.
pub fn report(&mut self, error: SloxError) { 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}"); println!("{error}");
} }
/// Transmit the last value returned by some component, or report its error
/// and generate the final error.
pub fn report_or_continue<T>(&mut self, result: SloxResult<T>) -> SloxResult<T> {
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<T>(&self, result: T) -> SloxResult<T> {
if let Some(err_kind) = self.had_error {
let err = SloxError::stage_failed(err_kind);
println!("{err}");
Err(err)
} else {
Ok(result)
}
}
} }

View file

@ -2,21 +2,26 @@ use std::{cell::RefCell, rc::Rc};
use crate::{ use crate::{
ast, ast,
errors::{ErrorHandler, InterpreterError}, errors::{ErrorHandler, SloxError},
interpreter::{Environment, EnvironmentRef, Value}, interpreter::{Environment, EnvironmentRef, Value},
resolver::ResolvedVariables,
tokens::{Token, TokenType}, tokens::{Token, TokenType},
}; };
use super::functions::Function; use super::functions::Function;
/// Evaluate an interpretable, returning its value. /// Evaluate an interpretable, returning its value.
pub fn evaluate(err_hdl: &mut ErrorHandler, ast: &dyn Interpretable) -> Option<Value> { pub fn evaluate(
err_hdl: &mut ErrorHandler,
ast: &dyn Interpretable,
vars: ResolvedVariables,
) -> Result<Value, SloxError> {
let env = Rc::new(RefCell::new(Environment::default())); let env = Rc::new(RefCell::new(Environment::default()));
match ast.interpret(&env) { match ast.interpret(&env) {
Ok(v) => Some(v.result()), Ok(v) => Ok(v.result()),
Err(e) => { Err(e) => {
e.report(err_hdl); err_hdl.report(e);
None Err(e)
} }
} }
} }
@ -65,7 +70,7 @@ impl From<Value> for InterpreterFlowControl {
} }
/// A result returned by some part of the interpreter. /// A result returned by some part of the interpreter.
pub type InterpreterResult = Result<InterpreterFlowControl, InterpreterError>; pub type InterpreterResult = Result<InterpreterFlowControl, SloxError>;
/// An Interpretable can be evaluated and will return a value. /// An Interpretable can be evaluated and will return a value.
pub trait Interpretable { pub trait Interpretable {
@ -326,12 +331,12 @@ impl ast::ExprNode {
TokenType::Plus => match (left_value, right_value) { TokenType::Plus => match (left_value, right_value) {
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b).into()), (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b).into()),
(Value::String(a), Value::String(b)) => Ok(Value::String(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) { TokenType::Minus => match (left_value, right_value) {
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(a - b).into()), (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) { TokenType::Star => match (left_value, right_value) {
@ -339,38 +344,38 @@ impl ast::ExprNode {
(Value::String(a), Value::Number(b)) => { (Value::String(a), Value::Number(b)) => {
Ok(Value::String(a.repeat(b as usize)).into()) 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) { TokenType::Slash => match (left_value, right_value) {
(Value::Number(a), Value::Number(b)) => { (Value::Number(a), Value::Number(b)) => {
if b == 0. { if b == 0. {
Err(InterpreterError::new(operator, "division by zero")) Err(SloxError::new(operator, "division by zero"))
} else { } else {
Ok(Value::Number(a / b).into()) 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) { TokenType::Greater => match (left_value, right_value) {
(Value::Number(a), Value::Number(b)) => Ok(Value::Boolean(a > b).into()), (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) { TokenType::GreaterEqual => match (left_value, right_value) {
(Value::Number(a), Value::Number(b)) => Ok(Value::Boolean(a >= b).into()), (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) { TokenType::Less => match (left_value, right_value) {
(Value::Number(a), Value::Number(b)) => Ok(Value::Boolean(a < b).into()), (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) { TokenType::LessEqual => match (left_value, right_value) {
(Value::Number(a), Value::Number(b)) => Ok(Value::Boolean(a <= b).into()), (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()), TokenType::EqualEqual => Ok(Value::Boolean(left_value == right_value).into()),
@ -396,7 +401,7 @@ impl ast::ExprNode {
if let Value::Number(n) = right_value { if let Value::Number(n) = right_value {
Ok(Value::Number(-n).into()) Ok(Value::Number(-n).into())
} else { } 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 { if let Value::Callable(callable_ref) = &callee {
let callable = callable_ref.borrow(); let callable = callable_ref.borrow();
if callable.arity() != arg_values.len() { if callable.arity() != arg_values.len() {
Err(InterpreterError::new( Err(SloxError::new(
right_paren, right_paren,
&format!( &format!(
"expected {} arguments, found {}", "expected {} arguments, found {}",
@ -453,7 +458,7 @@ impl ast::ExprNode {
Ok(callable.call(environment, arg_values)?.into()) Ok(callable.call(environment, arg_values)?.into())
} }
} else { } else {
Err(InterpreterError::new( Err(SloxError::new(
right_paren, right_paren,
"can only call functions and classes", "can only call functions and classes",
)) ))

View file

@ -14,34 +14,32 @@ use std::{
#[cfg(feature = "dump_ast")] #[cfg(feature = "dump_ast")]
use ast::AstDumper; use ast::AstDumper;
use errors::{ErrorHandler, ErrorType}; use errors::{ErrorHandler, SloxResult};
use interpreter::evaluate; use interpreter::evaluate;
use parser::Parser; use parser::Parser;
use resolver::resolve_variables;
use scanner::Scanner; use scanner::Scanner;
/// Execute a script. /// Execute a script.
fn run(source: String) -> ErrorHandler { fn run(source: String) -> SloxResult<()> {
let mut error_handler = ErrorHandler::default(); let mut error_handler = ErrorHandler::default();
let scanner = Scanner::new(source); 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")] #[cfg(feature = "dump_tokens")]
for token in tokens.iter() { for token in tokens.iter() {
println!("{:#?}", token); println!("{:#?}", token);
} }
let parser = Parser::new(tokens); let parser = Parser::new(tokens);
match parser.parse(&mut error_handler) { let ast = parser.parse(&mut error_handler)?;
None => (),
Some(ast) => {
#[cfg(feature = "dump_ast")] #[cfg(feature = "dump_ast")]
println!("AST generated ! {}", ast.dump()); println!("AST generated ! {}", ast.dump());
evaluate(&mut error_handler, &ast);
}
}
error_handler let resolved_vars = error_handler.final_error(resolve_variables(&ast))?;
error_handler.final_error(evaluate(&ast, resolved_vars));
Ok(())
} }
/// Run the REPL. /// Run the REPL.
@ -64,7 +62,7 @@ fn run_prompt() {
} }
/// Load a file and run the script it contains. /// 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)); let contents = fs::read_to_string(file).unwrap_or_else(|_| panic!("Could not load {}", file));
run(contents) run(contents)
} }
@ -77,10 +75,9 @@ fn main() -> Result<(), ExitCode> {
run_prompt(); run_prompt();
Ok(()) Ok(())
} else if n_args == 1 { } else if n_args == 1 {
match run_file(&args[0]).had_error() { match run_file(&args[0]) {
None => Ok(()), Ok(()) => Ok(()),
Some(ErrorType::Parse) => Err(ExitCode::from(65)), Err(err) => Err(ExitCode::from(err.kind().exit_code())),
Some(ErrorType::Runtime) => Err(ExitCode::from(70)),
} }
} else { } else {
println!("Usage: slox [script]"); println!("Usage: slox [script]");

View file

@ -74,11 +74,11 @@ impl Parser {
/// Parse the tokens into an AST and return it, or return nothing if a /// Parse the tokens into an AST and return it, or return nothing if a
/// parser error occurs. /// parser error occurs.
pub fn parse(mut self, err_hdl: &mut ErrorHandler) -> Option<ast::ProgramNode> { pub fn parse(mut self, err_hdl: &mut ErrorHandler) -> SloxResult<ast::ProgramNode> {
self.loop_state.push(LoopParsingState::None); self.loop_state.push(LoopParsingState::None);
let result = self.parse_program(err_hdl); let result = self.parse_program(err_hdl);
self.loop_state.pop(); self.loop_state.pop();
result err_hdl.final_error(result)
} }
/// Synchronize the parser after an error. /// Synchronize the parser after an error.
@ -111,7 +111,7 @@ impl Parser {
/// ``` /// ```
/// program := statement* /// program := statement*
/// ``` /// ```
fn parse_program(&mut self, err_hdl: &mut ErrorHandler) -> Option<ast::ProgramNode> { fn parse_program(&mut self, err_hdl: &mut ErrorHandler) -> ast::ProgramNode {
let mut stmts: Vec<ast::StmtNode> = Vec::new(); let mut stmts: Vec<ast::StmtNode> = Vec::new();
while !self.is_at_end() { while !self.is_at_end() {
match self.parse_statement() { match self.parse_statement() {
@ -122,11 +122,7 @@ impl Parser {
} }
} }
} }
if err_hdl.had_error().is_none() { ast::ProgramNode(stmts)
Some(ast::ProgramNode(stmts))
} else {
None
}
} }
/// Parse the following rule: /// Parse the following rule:

View file

@ -8,7 +8,7 @@ use crate::{
pub type ResolvedVariables = HashMap<*const ast::ExprNode, usize>; pub type ResolvedVariables = HashMap<*const ast::ExprNode, usize>;
pub fn resolve_variables(program: &ast::ProgramNode) -> Result<ResolvedVariables, SloxError> { pub fn resolve_variables(program: &ast::ProgramNode) -> SloxResult<ResolvedVariables> {
let mut state = ResolverState::default(); let mut state = ResolverState::default();
program.resolve(&mut state).map(|_| state.resolved) program.resolve(&mut state).map(|_| state.resolved)
} }

View file

@ -60,7 +60,7 @@ impl Scanner {
/// Scan the source code, generating the list of tokens and returning it. /// Scan the source code, generating the list of tokens and returning it.
/// The scanner itself is destroyed once the process is complete. /// The scanner itself is destroyed once the process is complete.
pub fn scan_tokens(mut self, err_hdl: &mut ErrorHandler) -> Vec<Token> { pub fn scan_tokens(mut self, err_hdl: &mut ErrorHandler) -> SloxResult<Vec<Token>> {
while !self.is_at_end() { while !self.is_at_end() {
self.start = self.current; self.start = self.current;
if let Err(e) = self.scan_token() { if let Err(e) = self.scan_token() {
@ -72,7 +72,7 @@ impl Scanner {
lexeme: String::from(""), lexeme: String::from(""),
line: self.line, line: self.line,
}); });
self.tokens err_hdl.final_error(self.tokens)
} }
/// Read the next token from the input /// Read the next token from the input