Further attempts at refactoring the error handling code
* Everything's still broken though
This commit is contained in:
parent
0443754007
commit
7961a92ad1
6 changed files with 99 additions and 59 deletions
|
@ -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);
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
))
|
||||
|
|
33
src/main.rs
33
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]");
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue