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)]
pub struct SloxError {
kind: ErrorKind,
line: usize,
line: Option<usize>,
pos: String,
message: String,
}
@ -51,10 +51,10 @@ impl SloxError {
pub fn scanner_error(line: usize, ch: Option<char>, 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);
}
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::{
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<Value> {
pub fn evaluate(
err_hdl: &mut ErrorHandler,
ast: &dyn Interpretable,
vars: ResolvedVariables,
) -> Result<Value, SloxError> {
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<Value> for InterpreterFlowControl {
}
/// 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.
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",
))

View file

@ -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) => {
let ast = parser.parse(&mut error_handler)?;
#[cfg(feature = "dump_ast")]
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.
@ -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]");

View file

@ -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<ast::ProgramNode> {
pub fn parse(mut self, err_hdl: &mut ErrorHandler) -> SloxResult<ast::ProgramNode> {
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<ast::ProgramNode> {
fn parse_program(&mut self, err_hdl: &mut ErrorHandler) -> ast::ProgramNode {
let mut stmts: Vec<ast::StmtNode> = 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:

View file

@ -8,7 +8,7 @@ use crate::{
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();
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.
/// 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() {
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