mirror of
https://github.com/MorizzG/rlox.git
synced 2025-12-06 04:12:42 +00:00
updated error names and Display
This commit is contained in:
parent
b8741ea3cc
commit
f9fe77f1e2
7 changed files with 164 additions and 81 deletions
|
|
@ -4,13 +4,13 @@ use super::CodePos;
|
|||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum LexerError {
|
||||
#[error("LexerError: Unexpected character '{c}' at {code_pos}.")]
|
||||
#[error("unexpected character '{c}' at {code_pos}.")]
|
||||
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 },
|
||||
#[error("LexerError: Unterminated block comment starting at {code_pos}.")]
|
||||
#[error("unterminated block comment starting at {code_pos}.")]
|
||||
UnterminatedBlockComment { code_pos: CodePos },
|
||||
#[error("LexerError: Invalid number literal {lexeme} at {code_pos}: {msg}")]
|
||||
#[error("invalid number literal {lexeme} at {code_pos}: {msg}")]
|
||||
InvalidNumberLiteral {
|
||||
lexeme: String,
|
||||
msg: String,
|
||||
|
|
|
|||
|
|
@ -6,46 +6,46 @@ use super::Expr;
|
|||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ParserError {
|
||||
#[error("ParserError: Token stream ended unexpectedly.")]
|
||||
#[error("token stream ended unexpectedly.")]
|
||||
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 },
|
||||
#[error("ParserError: Missing semicolon at {code_pos}")]
|
||||
#[error("missing semicolon at {code_pos}")]
|
||||
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 },
|
||||
#[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 },
|
||||
#[error("ParserError: Missing closing curly brace at {code_pos}")]
|
||||
#[error("missing closing curly brace at {code_pos}")]
|
||||
MissingRightBrace { code_pos: CodePos },
|
||||
#[error("ParserError: Missing closing parenthesis at {code_pos}")]
|
||||
#[error("missing closing parenthesis at {code_pos}")]
|
||||
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 },
|
||||
#[error("ParserError: Missing parenthesis after while at {code_pos}")]
|
||||
#[error("missing parenthesis after while at {code_pos}")]
|
||||
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 },
|
||||
#[error("ParserError: Call at {code_pos} has too many arguments")]
|
||||
#[error("call at {code_pos} has too many arguments")]
|
||||
TooManyArguments { code_pos: CodePos },
|
||||
#[error("ParserError: {msg} at {code_pos}")]
|
||||
#[error("{msg} at {code_pos}")]
|
||||
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 },
|
||||
#[error("ParserError: Missing body to function declaration at {code_pos}")]
|
||||
#[error("missing body to function declaration at {code_pos}")]
|
||||
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 },
|
||||
#[error("ParserError: Return statement outside of function definition")]
|
||||
#[error("return statement outside of function definition")]
|
||||
ReturnOutsideFunction { code_pos: CodePos },
|
||||
#[error("ParserError: Break statement outside of loop")]
|
||||
#[error("break statement outside of loop")]
|
||||
InvalidBreak { code_pos: CodePos },
|
||||
#[error("ParserError: Missing class body at {code_pos}")]
|
||||
#[error("missing class body at {code_pos}")]
|
||||
MissingClassBody { code_pos: CodePos },
|
||||
#[error("ParserError: Return statement in init")]
|
||||
#[error("return statement in init")]
|
||||
ReturnInInit { code_pos: CodePos },
|
||||
#[error("ParserError: Missing method name after super")]
|
||||
#[error("missing method name after super")]
|
||||
MissingMethodAfterSuper { code_pos: CodePos },
|
||||
#[error("ParserError: duplicate parameter name at {code_pos}")]
|
||||
#[error("duplicate parameter name at {code_pos}")]
|
||||
DuplicateParameterName { code_pos: CodePos },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::fmt::Display;
|
||||
use std::rc::Rc;
|
||||
|
||||
use itertools::Itertools;
|
||||
use rlox2_frontend::lexer::LexerError;
|
||||
use rlox2_frontend::parser::{BinaryOp, ParserError, UnaryOp};
|
||||
use thiserror::Error;
|
||||
|
|
@ -10,89 +10,131 @@ use crate::{LoxClass, ResolverError};
|
|||
|
||||
#[derive(Error, Debug)]
|
||||
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 },
|
||||
#[error("RuntimeError: Binary operator {op} had invalid arguments {left} and {right}")]
|
||||
#[error("binary operator {op} had invalid arguments {left} and {right}")]
|
||||
BinaryOpInvalidArguments {
|
||||
left: Value,
|
||||
op: BinaryOp,
|
||||
right: Value,
|
||||
},
|
||||
#[error("RuntimeError: Division by zero")]
|
||||
#[error("division by zero")]
|
||||
DivisionByZero,
|
||||
#[error("RuntimeError: Local {name} is not defined")]
|
||||
#[error("local {name} is not defined")]
|
||||
NameNotDefined { name: String },
|
||||
#[error("RuntimeError: Global {name} is not defined")]
|
||||
#[error("global {name} is not defined")]
|
||||
GlobalNotDefined { name: String },
|
||||
#[error("RuntimeError: {callee} is not callable")]
|
||||
#[error("{callee} is not callable")]
|
||||
NotCallable { callee: Value },
|
||||
#[error("RuntimeError: {name}() takes {arity} args, but {given} were given.")]
|
||||
#[error("{name}() takes {arity} args, but {given} were given.")]
|
||||
WrongArity {
|
||||
name: String,
|
||||
arity: 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 },
|
||||
#[error("RuntimeError: Uncaught break statement")]
|
||||
#[error("uncaught break statement")]
|
||||
Break,
|
||||
#[error("RuntimeError: Uncaught return statement")]
|
||||
#[error("uncaught return statement")]
|
||||
Return { value: Value },
|
||||
#[error("RuntimeError: Exit with exit code {exit_code}")]
|
||||
#[error("exit with exit code {exit_code}")]
|
||||
Exit { exit_code: i32 },
|
||||
#[error("RuntimeError: Only objects have attributes")]
|
||||
#[error("only objects have attributes")]
|
||||
InvalidGetTarget,
|
||||
#[error("RuntimeError: Only objects have attributes")]
|
||||
#[error("only objects have attributes")]
|
||||
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> },
|
||||
#[error("RuntimeError: {value} is not a valid superclass")]
|
||||
#[error("{value} is not a valid superclass")]
|
||||
InvalidSuperclass { value: Value },
|
||||
#[error("RuntimeError: stack overflow")]
|
||||
#[error("stack overflow")]
|
||||
StackOverflow,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum LoxError {
|
||||
#[error("{0}", format_multiple_errors(inner))]
|
||||
LexerError { inner: Vec<LexerError> },
|
||||
#[error("{0}", format_multiple_errors(inner))]
|
||||
ParserError { inner: Vec<ParserError> },
|
||||
#[error("{inner}")]
|
||||
ResolverError { inner: ResolverError },
|
||||
#[error("{inner}")]
|
||||
RuntimeError { inner: RuntimeError },
|
||||
#[error("Called exit() with exit code {exit_code}")]
|
||||
LexerError { lexer_errors: Vec<LexerError> },
|
||||
ParserError { parser_errors: Vec<ParserError> },
|
||||
ResolverError { resolver_error: ResolverError },
|
||||
RuntimeError { runtime_error: RuntimeError },
|
||||
Exit { exit_code: i32 },
|
||||
}
|
||||
|
||||
fn format_multiple_errors(errs: &[impl std::error::Error]) -> String {
|
||||
let msg = if errs.len() == 1 {
|
||||
errs[0].to_string()
|
||||
} else {
|
||||
errs.iter().map(|err| err.to_string()).join("\n")
|
||||
};
|
||||
impl Display for LoxError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let plural = |len: usize| {
|
||||
if len > 1 {
|
||||
"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 {
|
||||
fn from(lexer_errs: Vec<LexerError>) -> Self {
|
||||
LoxError::LexerError { inner: lexer_errs }
|
||||
LoxError::LexerError {
|
||||
lexer_errors: lexer_errs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ParserError>> for LoxError {
|
||||
fn from(parser_errs: Vec<ParserError>) -> Self {
|
||||
LoxError::ParserError { inner: parser_errs }
|
||||
LoxError::ParserError {
|
||||
parser_errors: parser_errs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResolverError> for LoxError {
|
||||
fn from(resolver_err: ResolverError) -> Self {
|
||||
LoxError::ResolverError {
|
||||
inner: resolver_err,
|
||||
resolver_error: resolver_err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +143,9 @@ impl From<RuntimeError> for LoxError {
|
|||
fn from(runtime_err: RuntimeError) -> Self {
|
||||
match runtime_err {
|
||||
RuntimeError::Exit { exit_code } => LoxError::Exit { exit_code },
|
||||
_ => LoxError::RuntimeError { inner: runtime_err },
|
||||
_ => LoxError::RuntimeError {
|
||||
runtime_error: runtime_err,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -290,8 +290,6 @@ impl Eval for Stmt {
|
|||
env.exit_scope();
|
||||
}
|
||||
Stmt::ExprStmt { expr } => {
|
||||
// expr.eval(env)?;
|
||||
// Ok(Value::Nil)
|
||||
expr.eval(env)?;
|
||||
}
|
||||
Stmt::Break => return Err(RuntimeError::Break),
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ use crate::{LoxError, ResolverError};
|
|||
type ResolverScope = FxHashMap<Box<str>, ResolveStatus>;
|
||||
type ResolverFrame = Vec<ResolverScope>;
|
||||
|
||||
type ResolverResult<T> = Result<T, ResolverError>;
|
||||
|
||||
/*====================================================================================================================*/
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -81,14 +83,21 @@ impl Resolver {
|
|||
.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();
|
||||
|
||||
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);
|
||||
} else {
|
||||
// global variables can be declared multiple times
|
||||
self.global_scope.insert(name, ResolveStatus::Declared);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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 {
|
||||
Stmt::Print { expr } => self.resolve_expr(expr),
|
||||
Stmt::IfStmt {
|
||||
|
|
@ -161,29 +170,40 @@ impl Resolver {
|
|||
else_branch,
|
||||
} => {
|
||||
self.resolve_expr(condition)?;
|
||||
|
||||
self.resolve_stmt(then_branch)?;
|
||||
|
||||
if let Some(else_branch) = else_branch {
|
||||
self.resolve_stmt(else_branch)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Stmt::While { condition, body } => {
|
||||
self.resolve_expr(condition)?;
|
||||
|
||||
self.resolve_stmt(body)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Stmt::VarDecl { name, initializer } => {
|
||||
self.declare(name.clone());
|
||||
self.declare(name.clone())?;
|
||||
|
||||
self.resolve_expr(initializer)?;
|
||||
|
||||
self.define(name.clone());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Stmt::Block { statements } => {
|
||||
self.enter_scope();
|
||||
|
||||
for statement in statements.iter_mut() {
|
||||
self.resolve_stmt(statement)?;
|
||||
}
|
||||
|
||||
self.exit_scope();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
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 {
|
||||
Expr::Literal { literal: _ } => Ok(()),
|
||||
Expr::Unary { op: _, expr } => self.resolve_expr(expr),
|
||||
Expr::Binary { left, op: _, right } => {
|
||||
self.resolve_expr(left)?;
|
||||
|
||||
self.resolve_expr(right)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Expr::Logical { left, op: _, right } => {
|
||||
self.resolve_expr(left)?;
|
||||
|
||||
self.resolve_expr(right)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Expr::Grouping { expr } => self.resolve_expr(expr),
|
||||
|
|
@ -228,6 +252,7 @@ impl Resolver {
|
|||
self.resolve_expr(value)?;
|
||||
|
||||
let target = target.as_mut();
|
||||
|
||||
if let Expr::Variable { name } = target {
|
||||
*target = self.resolve_var(name)?;
|
||||
} else {
|
||||
|
|
@ -238,13 +263,16 @@ impl Resolver {
|
|||
}
|
||||
Expr::Call { callee, args } => {
|
||||
self.resolve_expr(callee)?;
|
||||
|
||||
for arg in args.iter_mut() {
|
||||
self.resolve_expr(arg)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Expr::Get { target, name: _ } => {
|
||||
self.resolve_expr(target)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Expr::Set {
|
||||
|
|
@ -253,7 +281,9 @@ impl Resolver {
|
|||
value,
|
||||
} => {
|
||||
self.resolve_expr(target)?;
|
||||
|
||||
self.resolve_expr(value)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Expr::Function {
|
||||
|
|
@ -266,11 +296,12 @@ impl Resolver {
|
|||
|
||||
self.push_frame();
|
||||
|
||||
self.declare(name.clone());
|
||||
self.declare(name.clone())?;
|
||||
|
||||
self.define(name.clone());
|
||||
|
||||
for param_name in param_names.iter() {
|
||||
self.declare(param_name.clone());
|
||||
self.declare(param_name.clone())?;
|
||||
self.define(param_name.clone());
|
||||
}
|
||||
|
||||
|
|
@ -291,7 +322,7 @@ impl Resolver {
|
|||
name,
|
||||
methods,
|
||||
} => {
|
||||
self.declare(name.clone());
|
||||
self.declare(name.clone())?;
|
||||
|
||||
if let Some(superclass) = superclass {
|
||||
self.resolve_expr(superclass)?;
|
||||
|
|
@ -301,10 +332,13 @@ impl Resolver {
|
|||
|
||||
// this is the scope "this" is defined in
|
||||
self.enter_scope();
|
||||
self.declare("this");
|
||||
|
||||
// this should never fail! we just made a new scope
|
||||
self.declare("this").unwrap();
|
||||
|
||||
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() {
|
||||
|
|
@ -325,6 +359,7 @@ impl Resolver {
|
|||
method: _,
|
||||
} => {
|
||||
self.resolve_expr(super_var)?;
|
||||
|
||||
self.resolve_expr(this_var)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,16 @@ use thiserror::Error;
|
|||
|
||||
#[derive(Error, Debug)]
|
||||
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> },
|
||||
#[error("ResolverError: Variable {name} not defined")]
|
||||
#[error("variable {name} not defined")]
|
||||
UnresolvableVariable { name: Box<str> },
|
||||
#[error("ResolverError: Return outside of function definition")]
|
||||
#[error("return outside of function definition")]
|
||||
ReturnOutsideFunction,
|
||||
#[error("ResolverError: this outside of method")]
|
||||
#[error("this outside of method")]
|
||||
ThisOutsideMethod,
|
||||
#[error("ResolverError: super outside of subclass method")]
|
||||
#[error("super outside of subclass method")]
|
||||
SuperOutsideMethod,
|
||||
#[error("local variable {name} declares multiple times")]
|
||||
LocalMulipleDeclarations { name: Box<str> },
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,11 @@ pub fn run_test(path: impl Into<PathBuf>) {
|
|||
.map(|s| s.to_owned())
|
||||
.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()) {
|
||||
assert_eq!(line, comment);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue