updated error names and Display

This commit is contained in:
Moritz Gmeiner 2024-09-01 23:53:04 +02:00
commit f9fe77f1e2
7 changed files with 164 additions and 81 deletions

View file

@ -4,13 +4,13 @@ use super::CodePos;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum LexerError { pub enum LexerError {
#[error("LexerError: Unexpected character '{c}' at {code_pos}.")] #[error("unexpected character '{c}' at {code_pos}.")]
UnexpectedCharacter { c: char, code_pos: CodePos }, UnexpectedCharacter { c: char, code_pos: CodePos },
#[error("LexerError: Unterminated string literal starting at {code_pos}.")] #[error("unterminated string literal starting at {code_pos}.")]
UnterminatedStringLiteral { code_pos: CodePos }, UnterminatedStringLiteral { code_pos: CodePos },
#[error("LexerError: Unterminated block comment starting at {code_pos}.")] #[error("unterminated block comment starting at {code_pos}.")]
UnterminatedBlockComment { code_pos: CodePos }, UnterminatedBlockComment { code_pos: CodePos },
#[error("LexerError: Invalid number literal {lexeme} at {code_pos}: {msg}")] #[error("invalid number literal {lexeme} at {code_pos}: {msg}")]
InvalidNumberLiteral { InvalidNumberLiteral {
lexeme: String, lexeme: String,
msg: String, msg: String,

View file

@ -6,46 +6,46 @@ use super::Expr;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ParserError { pub enum ParserError {
#[error("ParserError: Token stream ended unexpectedly.")] #[error("token stream ended unexpectedly.")]
TokenStreamEnded, TokenStreamEnded,
#[error("ParserError: Expected a primary expression, but found a {token} token instead at {0}.", token.code_pos)] #[error("expected a primary expression, but found a {token} token instead at {0}.", token.code_pos)]
ExpectedPrimary { token: Token }, ExpectedPrimary { token: Token },
#[error("ParserError: Missing semicolon at {code_pos}")] #[error("missing semicolon at {code_pos}")]
MissingSemicolon { code_pos: CodePos }, MissingSemicolon { code_pos: CodePos },
#[error("ParserError: Expected variable name at {0}, got {token} instead", token.code_pos)] #[error("expected variable name at {0}, got {token} instead", token.code_pos)]
ExpectedVarName { token: Token }, ExpectedVarName { token: Token },
#[error("ParserError: Can't assign to {expr} at {code_pos}")] #[error("can't assign to {expr} at {code_pos}")]
InvalidAssignment { expr: Expr, code_pos: CodePos }, InvalidAssignment { expr: Expr, code_pos: CodePos },
#[error("ParserError: Missing closing curly brace at {code_pos}")] #[error("missing closing curly brace at {code_pos}")]
MissingRightBrace { code_pos: CodePos }, MissingRightBrace { code_pos: CodePos },
#[error("ParserError: Missing closing parenthesis at {code_pos}")] #[error("missing closing parenthesis at {code_pos}")]
MissingRightParen { code_pos: CodePos }, MissingRightParen { code_pos: CodePos },
#[error("ParserError: Missing parenthesis after if at {code_pos}")] #[error("missing parenthesis after if at {code_pos}")]
MissingParenAfterIf { code_pos: CodePos }, MissingParenAfterIf { code_pos: CodePos },
#[error("ParserError: Missing parenthesis after while at {code_pos}")] #[error("missing parenthesis after while at {code_pos}")]
MissingParenAfterWhile { code_pos: CodePos }, MissingParenAfterWhile { code_pos: CodePos },
#[error("ParserError: Missing parenthesis after for at {code_pos}")] #[error("missing parenthesis after for at {code_pos}")]
MissingParenAfterFor { code_pos: CodePos }, MissingParenAfterFor { code_pos: CodePos },
#[error("ParserError: Call at {code_pos} has too many arguments")] #[error("call at {code_pos} has too many arguments")]
TooManyArguments { code_pos: CodePos }, TooManyArguments { code_pos: CodePos },
#[error("ParserError: {msg} at {code_pos}")] #[error("{msg} at {code_pos}")]
MissingIdentifier { msg: String, code_pos: CodePos }, MissingIdentifier { msg: String, code_pos: CodePos },
#[error("ParserError: Missing arguments to function declaration at {code_pos}")] #[error("missing arguments to function declaration at {code_pos}")]
MissingFunctionArgs { code_pos: CodePos }, MissingFunctionArgs { code_pos: CodePos },
#[error("ParserError: Missing body to function declaration at {code_pos}")] #[error("missing body to function declaration at {code_pos}")]
MissingFunctionBody { code_pos: CodePos }, MissingFunctionBody { code_pos: CodePos },
#[error("ParserError: Function declaration at {code_pos} has too many parameters")] #[error("function declaration at {code_pos} has too many parameters")]
TooManyParams { code_pos: CodePos }, TooManyParams { code_pos: CodePos },
#[error("ParserError: Return statement outside of function definition")] #[error("return statement outside of function definition")]
ReturnOutsideFunction { code_pos: CodePos }, ReturnOutsideFunction { code_pos: CodePos },
#[error("ParserError: Break statement outside of loop")] #[error("break statement outside of loop")]
InvalidBreak { code_pos: CodePos }, InvalidBreak { code_pos: CodePos },
#[error("ParserError: Missing class body at {code_pos}")] #[error("missing class body at {code_pos}")]
MissingClassBody { code_pos: CodePos }, MissingClassBody { code_pos: CodePos },
#[error("ParserError: Return statement in init")] #[error("return statement in init")]
ReturnInInit { code_pos: CodePos }, ReturnInInit { code_pos: CodePos },
#[error("ParserError: Missing method name after super")] #[error("missing method name after super")]
MissingMethodAfterSuper { code_pos: CodePos }, MissingMethodAfterSuper { code_pos: CodePos },
#[error("ParserError: duplicate parameter name at {code_pos}")] #[error("duplicate parameter name at {code_pos}")]
DuplicateParameterName { code_pos: CodePos }, DuplicateParameterName { code_pos: CodePos },
} }

View file

@ -1,6 +1,6 @@
use std::fmt::Display;
use std::rc::Rc; use std::rc::Rc;
use itertools::Itertools;
use rlox2_frontend::lexer::LexerError; use rlox2_frontend::lexer::LexerError;
use rlox2_frontend::parser::{BinaryOp, ParserError, UnaryOp}; use rlox2_frontend::parser::{BinaryOp, ParserError, UnaryOp};
use thiserror::Error; use thiserror::Error;
@ -10,89 +10,131 @@ use crate::{LoxClass, ResolverError};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum RuntimeError { pub enum RuntimeError {
#[error("RuntimeError: Unary operator {op} had invalid argument {arg}")] #[error("unary operator {op} had invalid argument {arg}")]
UnaryOpInvalidArgument { op: UnaryOp, arg: Value }, UnaryOpInvalidArgument { op: UnaryOp, arg: Value },
#[error("RuntimeError: Binary operator {op} had invalid arguments {left} and {right}")] #[error("binary operator {op} had invalid arguments {left} and {right}")]
BinaryOpInvalidArguments { BinaryOpInvalidArguments {
left: Value, left: Value,
op: BinaryOp, op: BinaryOp,
right: Value, right: Value,
}, },
#[error("RuntimeError: Division by zero")] #[error("division by zero")]
DivisionByZero, DivisionByZero,
#[error("RuntimeError: Local {name} is not defined")] #[error("local {name} is not defined")]
NameNotDefined { name: String }, NameNotDefined { name: String },
#[error("RuntimeError: Global {name} is not defined")] #[error("global {name} is not defined")]
GlobalNotDefined { name: String }, GlobalNotDefined { name: String },
#[error("RuntimeError: {callee} is not callable")] #[error("{callee} is not callable")]
NotCallable { callee: Value }, NotCallable { callee: Value },
#[error("RuntimeError: {name}() takes {arity} args, but {given} were given.")] #[error("{name}() takes {arity} args, but {given} were given.")]
WrongArity { WrongArity {
name: String, name: String,
arity: usize, arity: usize,
given: usize, given: usize,
}, },
#[error("RuntimeError: Extern function call to {name} failed: {msg}")] #[error("extern function call to {name} failed: {msg}")]
ExtFunCallFailed { name: String, msg: String }, ExtFunCallFailed { name: String, msg: String },
#[error("RuntimeError: Uncaught break statement")] #[error("uncaught break statement")]
Break, Break,
#[error("RuntimeError: Uncaught return statement")] #[error("uncaught return statement")]
Return { value: Value }, Return { value: Value },
#[error("RuntimeError: Exit with exit code {exit_code}")] #[error("exit with exit code {exit_code}")]
Exit { exit_code: i32 }, Exit { exit_code: i32 },
#[error("RuntimeError: Only objects have attributes")] #[error("only objects have attributes")]
InvalidGetTarget, InvalidGetTarget,
#[error("RuntimeError: Only objects have attributes")] #[error("only objects have attributes")]
InvalidSetTarget, InvalidSetTarget,
#[error("RuntimeError: Class {0} has no property {name}", class.name())] #[error("class {0} has no property {name}", class.name())]
UndefinedAttribute { class: Rc<LoxClass>, name: Box<str> }, UndefinedAttribute { class: Rc<LoxClass>, name: Box<str> },
#[error("RuntimeError: {value} is not a valid superclass")] #[error("{value} is not a valid superclass")]
InvalidSuperclass { value: Value }, InvalidSuperclass { value: Value },
#[error("RuntimeError: stack overflow")] #[error("stack overflow")]
StackOverflow, StackOverflow,
} }
#[derive(Error, Debug)] #[derive(Debug)]
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
pub enum LoxError { pub enum LoxError {
#[error("{0}", format_multiple_errors(inner))] LexerError { lexer_errors: Vec<LexerError> },
LexerError { inner: Vec<LexerError> }, ParserError { parser_errors: Vec<ParserError> },
#[error("{0}", format_multiple_errors(inner))] ResolverError { resolver_error: ResolverError },
ParserError { inner: Vec<ParserError> }, RuntimeError { runtime_error: RuntimeError },
#[error("{inner}")]
ResolverError { inner: ResolverError },
#[error("{inner}")]
RuntimeError { inner: RuntimeError },
#[error("Called exit() with exit code {exit_code}")]
Exit { exit_code: i32 }, Exit { exit_code: i32 },
} }
fn format_multiple_errors(errs: &[impl std::error::Error]) -> String { impl Display for LoxError {
let msg = if errs.len() == 1 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
errs[0].to_string() let plural = |len: usize| {
} else { if len > 1 {
errs.iter().map(|err| err.to_string()).join("\n") "s"
}; } else {
""
}
};
msg match self {
LoxError::LexerError { lexer_errors } => {
let len = lexer_errors.len();
writeln!(f, "{len} lexer error{}:", plural(len))?;
if len > 1 {
for lexer_error in lexer_errors[..len - 2].iter() {
writeln!(f, "{lexer_error}")?;
}
}
write!(f, "{}", lexer_errors[len - 1])
}
LoxError::ParserError { parser_errors } => {
let len = parser_errors.len();
writeln!(f, "{len} parser error{}:", plural(len))?;
if len > 1 {
for lexer_error in parser_errors[..len - 2].iter() {
writeln!(f, "{lexer_error}")?;
}
}
write!(f, "{}", parser_errors[len - 1])
}
LoxError::ResolverError { resolver_error } => {
write!(f, "resolver error: {resolver_error}")
}
LoxError::RuntimeError { runtime_error } => {
write!(f, "runtime error: {runtime_error}")
}
LoxError::Exit { exit_code } => {
write!(f, "exited with code {exit_code}")
}
}
}
} }
impl std::error::Error for LoxError {}
impl From<Vec<LexerError>> for LoxError { impl From<Vec<LexerError>> for LoxError {
fn from(lexer_errs: Vec<LexerError>) -> Self { fn from(lexer_errs: Vec<LexerError>) -> Self {
LoxError::LexerError { inner: lexer_errs } LoxError::LexerError {
lexer_errors: lexer_errs,
}
} }
} }
impl From<Vec<ParserError>> for LoxError { impl From<Vec<ParserError>> for LoxError {
fn from(parser_errs: Vec<ParserError>) -> Self { fn from(parser_errs: Vec<ParserError>) -> Self {
LoxError::ParserError { inner: parser_errs } LoxError::ParserError {
parser_errors: parser_errs,
}
} }
} }
impl From<ResolverError> for LoxError { impl From<ResolverError> for LoxError {
fn from(resolver_err: ResolverError) -> Self { fn from(resolver_err: ResolverError) -> Self {
LoxError::ResolverError { LoxError::ResolverError {
inner: resolver_err, resolver_error: resolver_err,
} }
} }
} }
@ -101,7 +143,9 @@ impl From<RuntimeError> for LoxError {
fn from(runtime_err: RuntimeError) -> Self { fn from(runtime_err: RuntimeError) -> Self {
match runtime_err { match runtime_err {
RuntimeError::Exit { exit_code } => LoxError::Exit { exit_code }, RuntimeError::Exit { exit_code } => LoxError::Exit { exit_code },
_ => LoxError::RuntimeError { inner: runtime_err }, _ => LoxError::RuntimeError {
runtime_error: runtime_err,
},
} }
} }
} }

View file

@ -290,8 +290,6 @@ impl Eval for Stmt {
env.exit_scope(); env.exit_scope();
} }
Stmt::ExprStmt { expr } => { Stmt::ExprStmt { expr } => {
// expr.eval(env)?;
// Ok(Value::Nil)
expr.eval(env)?; expr.eval(env)?;
} }
Stmt::Break => return Err(RuntimeError::Break), Stmt::Break => return Err(RuntimeError::Break),

View file

@ -9,6 +9,8 @@ use crate::{LoxError, ResolverError};
type ResolverScope = FxHashMap<Box<str>, ResolveStatus>; type ResolverScope = FxHashMap<Box<str>, ResolveStatus>;
type ResolverFrame = Vec<ResolverScope>; type ResolverFrame = Vec<ResolverScope>;
type ResolverResult<T> = Result<T, ResolverError>;
/*====================================================================================================================*/ /*====================================================================================================================*/
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -81,14 +83,21 @@ impl Resolver {
.expect("Tried to pop non-existant ResolverFrame"); .expect("Tried to pop non-existant ResolverFrame");
} }
fn declare(&mut self, name: impl Into<Box<str>>) { fn declare(&mut self, name: impl Into<Box<str>>) -> ResolverResult<()> {
let name = name.into(); let name = name.into();
if let Some(local_scope) = self.local_scopes_mut().last_mut() { if let Some(local_scope) = self.local_scopes_mut().last_mut() {
if local_scope.get(&name).is_some() {
return Err(ResolverError::LocalMulipleDeclarations { name });
}
local_scope.insert(name, ResolveStatus::Declared); local_scope.insert(name, ResolveStatus::Declared);
} else { } else {
// global variables can be declared multiple times
self.global_scope.insert(name, ResolveStatus::Declared); self.global_scope.insert(name, ResolveStatus::Declared);
} }
Ok(())
} }
fn define(&mut self, name: impl Into<Box<str>>) { fn define(&mut self, name: impl Into<Box<str>>) {
@ -109,7 +118,7 @@ impl Resolver {
} }
} }
fn resolve_var(&mut self, name: &str) -> Result<Expr, ResolverError> { fn resolve_var(&mut self, name: &str) -> ResolverResult<Expr> {
let mut level = 0; let mut level = 0;
for scope in self.local_scopes().iter().rev() { for scope in self.local_scopes().iter().rev() {
@ -152,7 +161,7 @@ impl Resolver {
} }
} }
fn resolve_stmt(&mut self, stmt: &mut Stmt) -> Result<(), ResolverError> { fn resolve_stmt(&mut self, stmt: &mut Stmt) -> ResolverResult<()> {
match stmt { match stmt {
Stmt::Print { expr } => self.resolve_expr(expr), Stmt::Print { expr } => self.resolve_expr(expr),
Stmt::IfStmt { Stmt::IfStmt {
@ -161,29 +170,40 @@ impl Resolver {
else_branch, else_branch,
} => { } => {
self.resolve_expr(condition)?; self.resolve_expr(condition)?;
self.resolve_stmt(then_branch)?; self.resolve_stmt(then_branch)?;
if let Some(else_branch) = else_branch { if let Some(else_branch) = else_branch {
self.resolve_stmt(else_branch)?; self.resolve_stmt(else_branch)?;
} }
Ok(()) Ok(())
} }
Stmt::While { condition, body } => { Stmt::While { condition, body } => {
self.resolve_expr(condition)?; self.resolve_expr(condition)?;
self.resolve_stmt(body)?; self.resolve_stmt(body)?;
Ok(()) Ok(())
} }
Stmt::VarDecl { name, initializer } => { Stmt::VarDecl { name, initializer } => {
self.declare(name.clone()); self.declare(name.clone())?;
self.resolve_expr(initializer)?; self.resolve_expr(initializer)?;
self.define(name.clone()); self.define(name.clone());
Ok(()) Ok(())
} }
Stmt::Block { statements } => { Stmt::Block { statements } => {
self.enter_scope(); self.enter_scope();
for statement in statements.iter_mut() { for statement in statements.iter_mut() {
self.resolve_stmt(statement)?; self.resolve_stmt(statement)?;
} }
self.exit_scope(); self.exit_scope();
Ok(()) Ok(())
} }
Stmt::ExprStmt { expr } => self.resolve_expr(expr), Stmt::ExprStmt { expr } => self.resolve_expr(expr),
@ -192,18 +212,22 @@ impl Resolver {
} }
} }
fn resolve_expr(&mut self, expr: &mut Expr) -> Result<(), ResolverError> { fn resolve_expr(&mut self, expr: &mut Expr) -> ResolverResult<()> {
match expr { match expr {
Expr::Literal { literal: _ } => Ok(()), Expr::Literal { literal: _ } => Ok(()),
Expr::Unary { op: _, expr } => self.resolve_expr(expr), Expr::Unary { op: _, expr } => self.resolve_expr(expr),
Expr::Binary { left, op: _, right } => { Expr::Binary { left, op: _, right } => {
self.resolve_expr(left)?; self.resolve_expr(left)?;
self.resolve_expr(right)?; self.resolve_expr(right)?;
Ok(()) Ok(())
} }
Expr::Logical { left, op: _, right } => { Expr::Logical { left, op: _, right } => {
self.resolve_expr(left)?; self.resolve_expr(left)?;
self.resolve_expr(right)?; self.resolve_expr(right)?;
Ok(()) Ok(())
} }
Expr::Grouping { expr } => self.resolve_expr(expr), Expr::Grouping { expr } => self.resolve_expr(expr),
@ -228,6 +252,7 @@ impl Resolver {
self.resolve_expr(value)?; self.resolve_expr(value)?;
let target = target.as_mut(); let target = target.as_mut();
if let Expr::Variable { name } = target { if let Expr::Variable { name } = target {
*target = self.resolve_var(name)?; *target = self.resolve_var(name)?;
} else { } else {
@ -238,13 +263,16 @@ impl Resolver {
} }
Expr::Call { callee, args } => { Expr::Call { callee, args } => {
self.resolve_expr(callee)?; self.resolve_expr(callee)?;
for arg in args.iter_mut() { for arg in args.iter_mut() {
self.resolve_expr(arg)?; self.resolve_expr(arg)?;
} }
Ok(()) Ok(())
} }
Expr::Get { target, name: _ } => { Expr::Get { target, name: _ } => {
self.resolve_expr(target)?; self.resolve_expr(target)?;
Ok(()) Ok(())
} }
Expr::Set { Expr::Set {
@ -253,7 +281,9 @@ impl Resolver {
value, value,
} => { } => {
self.resolve_expr(target)?; self.resolve_expr(target)?;
self.resolve_expr(value)?; self.resolve_expr(value)?;
Ok(()) Ok(())
} }
Expr::Function { Expr::Function {
@ -266,11 +296,12 @@ impl Resolver {
self.push_frame(); self.push_frame();
self.declare(name.clone()); self.declare(name.clone())?;
self.define(name.clone()); self.define(name.clone());
for param_name in param_names.iter() { for param_name in param_names.iter() {
self.declare(param_name.clone()); self.declare(param_name.clone())?;
self.define(param_name.clone()); self.define(param_name.clone());
} }
@ -291,7 +322,7 @@ impl Resolver {
name, name,
methods, methods,
} => { } => {
self.declare(name.clone()); self.declare(name.clone())?;
if let Some(superclass) = superclass { if let Some(superclass) = superclass {
self.resolve_expr(superclass)?; self.resolve_expr(superclass)?;
@ -301,10 +332,13 @@ impl Resolver {
// this is the scope "this" is defined in // this is the scope "this" is defined in
self.enter_scope(); self.enter_scope();
self.declare("this");
// this should never fail! we just made a new scope
self.declare("this").unwrap();
if superclass.is_some() { if superclass.is_some() {
self.declare("super"); // this should never fail either! same as `this`
self.declare("super").unwrap();
} }
for method in methods.iter_mut() { for method in methods.iter_mut() {
@ -325,6 +359,7 @@ impl Resolver {
method: _, method: _,
} => { } => {
self.resolve_expr(super_var)?; self.resolve_expr(super_var)?;
self.resolve_expr(this_var) self.resolve_expr(this_var)
} }
} }

View file

@ -2,14 +2,16 @@ use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ResolverError { pub enum ResolverError {
#[error("ResolverError: Can't read variable {name} in its own initializer.")] #[error("can't read variable {name} in its own initializer.")]
VarInOwnInitializer { name: Box<str> }, VarInOwnInitializer { name: Box<str> },
#[error("ResolverError: Variable {name} not defined")] #[error("variable {name} not defined")]
UnresolvableVariable { name: Box<str> }, UnresolvableVariable { name: Box<str> },
#[error("ResolverError: Return outside of function definition")] #[error("return outside of function definition")]
ReturnOutsideFunction, ReturnOutsideFunction,
#[error("ResolverError: this outside of method")] #[error("this outside of method")]
ThisOutsideMethod, ThisOutsideMethod,
#[error("ResolverError: super outside of subclass method")] #[error("super outside of subclass method")]
SuperOutsideMethod, SuperOutsideMethod,
#[error("local variable {name} declares multiple times")]
LocalMulipleDeclarations { name: Box<str> },
} }

View file

@ -74,7 +74,11 @@ pub fn run_test(path: impl Into<PathBuf>) {
.map(|s| s.to_owned()) .map(|s| s.to_owned())
.collect(); .collect();
assert_eq!(lines.len(), comments.len()); assert_eq!(
lines.len(),
comments.len(),
"Didn't get as many outputs as expected"
);
for (line, comment) in std::iter::zip(lines.into_iter(), comments.into_iter()) { for (line, comment) in std::iter::zip(lines.into_iter(), comments.into_iter()) {
assert_eq!(line, comment); assert_eq!(line, comment);