From f9fe77f1e2fe42289dd05891197480ab992a00d1 Mon Sep 17 00:00:00 2001 From: Moritz Gmeiner Date: Sun, 1 Sep 2024 23:53:04 +0200 Subject: [PATCH] updated error names and Display --- frontend/src/lexer/error.rs | 8 +- frontend/src/parser/error.rs | 42 ++++----- interpreter/src/error.rs | 120 ++++++++++++++++++-------- interpreter/src/interpreter.rs | 2 - interpreter/src/resolver/_resolver.rs | 55 +++++++++--- interpreter/src/resolver/error.rs | 12 +-- interpreter/tests/common/mod.rs | 6 +- 7 files changed, 164 insertions(+), 81 deletions(-) diff --git a/frontend/src/lexer/error.rs b/frontend/src/lexer/error.rs index 1993732..1f382fc 100644 --- a/frontend/src/lexer/error.rs +++ b/frontend/src/lexer/error.rs @@ -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, diff --git a/frontend/src/parser/error.rs b/frontend/src/parser/error.rs index 49ab93b..daec9fc 100644 --- a/frontend/src/parser/error.rs +++ b/frontend/src/parser/error.rs @@ -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 }, } diff --git a/interpreter/src/error.rs b/interpreter/src/error.rs index f17213f..9244b31 100644 --- a/interpreter/src/error.rs +++ b/interpreter/src/error.rs @@ -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, name: Box }, - #[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 }, - #[error("{0}", format_multiple_errors(inner))] - ParserError { inner: Vec }, - #[error("{inner}")] - ResolverError { inner: ResolverError }, - #[error("{inner}")] - RuntimeError { inner: RuntimeError }, - #[error("Called exit() with exit code {exit_code}")] + LexerError { lexer_errors: Vec }, + ParserError { parser_errors: Vec }, + 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> for LoxError { fn from(lexer_errs: Vec) -> Self { - LoxError::LexerError { inner: lexer_errs } + LoxError::LexerError { + lexer_errors: lexer_errs, + } } } impl From> for LoxError { fn from(parser_errs: Vec) -> Self { - LoxError::ParserError { inner: parser_errs } + LoxError::ParserError { + parser_errors: parser_errs, + } } } impl From for LoxError { fn from(resolver_err: ResolverError) -> Self { LoxError::ResolverError { - inner: resolver_err, + resolver_error: resolver_err, } } } @@ -101,7 +143,9 @@ impl From 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, + }, } } } diff --git a/interpreter/src/interpreter.rs b/interpreter/src/interpreter.rs index d023f60..bea32f2 100644 --- a/interpreter/src/interpreter.rs +++ b/interpreter/src/interpreter.rs @@ -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), diff --git a/interpreter/src/resolver/_resolver.rs b/interpreter/src/resolver/_resolver.rs index 48c98f8..a9de27e 100644 --- a/interpreter/src/resolver/_resolver.rs +++ b/interpreter/src/resolver/_resolver.rs @@ -9,6 +9,8 @@ use crate::{LoxError, ResolverError}; type ResolverScope = FxHashMap, ResolveStatus>; type ResolverFrame = Vec; +type ResolverResult = Result; + /*====================================================================================================================*/ #[derive(Debug, Clone, Copy)] @@ -81,14 +83,21 @@ impl Resolver { .expect("Tried to pop non-existant ResolverFrame"); } - fn declare(&mut self, name: impl Into>) { + fn declare(&mut self, name: impl Into>) -> 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>) { @@ -109,7 +118,7 @@ impl Resolver { } } - fn resolve_var(&mut self, name: &str) -> Result { + fn resolve_var(&mut self, name: &str) -> ResolverResult { 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) } } diff --git a/interpreter/src/resolver/error.rs b/interpreter/src/resolver/error.rs index fe4143a..9c36710 100644 --- a/interpreter/src/resolver/error.rs +++ b/interpreter/src/resolver/error.rs @@ -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 }, - #[error("ResolverError: Variable {name} not defined")] + #[error("variable {name} not defined")] UnresolvableVariable { name: Box }, - #[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 }, } diff --git a/interpreter/tests/common/mod.rs b/interpreter/tests/common/mod.rs index ad7a1e6..b57f97c 100644 --- a/interpreter/tests/common/mod.rs +++ b/interpreter/tests/common/mod.rs @@ -74,7 +74,11 @@ pub fn run_test(path: impl Into) { .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);