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)]
|
#[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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
))
|
))
|
||||||
|
|
33
src/main.rs
33
src/main.rs
|
@ -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 => (),
|
#[cfg(feature = "dump_ast")]
|
||||||
Some(ast) => {
|
println!("AST generated ! {}", ast.dump());
|
||||||
#[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.
|
/// 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]");
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue