From de9f55842cd745a924bc4e6e3446c2d4f7e0af3d Mon Sep 17 00:00:00 2001 From: Shautvast Date: Sun, 2 Nov 2025 13:02:10 +0100 Subject: [PATCH] generic support for line numbers in error messages --- src/ast_compiler.rs | 104 ++++++++++++++++++++++----------------- src/bytecode_compiler.rs | 24 +++++---- src/compiler_tests.rs | 8 +-- src/errors.rs | 42 +++++++++++----- src/scanner.rs | 24 +++++---- src/vm.rs | 14 ------ 6 files changed, 118 insertions(+), 98 deletions(-) diff --git a/src/ast_compiler.rs b/src/ast_compiler.rs index 7df9d0f..6df9771 100644 --- a/src/ast_compiler.rs +++ b/src/ast_compiler.rs @@ -10,8 +10,9 @@ use crate::tokens::{Token, TokenType}; use crate::value::Value; use log::debug; use std::collections::HashMap; +use crate::errors::CompilerErrorAtLine; -pub fn compile(tokens: Vec) -> Result, CompilerError> { +pub fn compile(tokens: Vec) -> Result, CompilerErrorAtLine> { let mut compiler = AstCompiler::new(tokens); compiler.compile_tokens() } @@ -49,13 +50,16 @@ impl AstCompiler { self.current = 0; } - fn compile_tokens(&mut self) -> Result,CompilerError> { + fn compile_tokens(&mut self) -> Result,CompilerErrorAtLine> { self.collect_functions()?; self.reset(); self.compile() } - fn compile(&mut self) -> Result, CompilerError> { + + + fn compile(&mut self) -> Result, CompilerErrorAtLine> { + self.current_line(); if !self.had_error { let mut statements = vec![]; while !self.is_at_end() { @@ -68,11 +72,15 @@ impl AstCompiler { } Ok(statements) } else { - Err(CompilerError::Failure) + Err(self.raise(CompilerError::Failure)) } } - fn collect_functions(&mut self) -> Result<(), CompilerError> { + fn raise(&self, error: CompilerError) -> CompilerErrorAtLine { + CompilerErrorAtLine::raise(error, self.current_line()) + } + + fn collect_functions(&mut self) -> Result<(), CompilerErrorAtLine> { while !self.is_at_end() { if self.match_token(vec![Fn]) { let name_token = self.consume(Identifier, Expected("function name."))?; @@ -80,7 +88,7 @@ impl AstCompiler { let mut parameters = vec![]; while !self.check(RightParen) { if parameters.len() >= 25 { - return Err(TooManyParameters); + return Err(self.raise(TooManyParameters)); } let parm_name = self.consume(Identifier, Expected("a parameter name."))?; @@ -125,7 +133,7 @@ impl AstCompiler { Ok(()) } - fn indent(&mut self) -> Result, CompilerError> { + fn indent(&mut self) -> Result, CompilerErrorAtLine> { let expected_indent = *self.indent.last().unwrap(); // skip empty lines while self.check(Eol) { @@ -138,9 +146,9 @@ impl AstCompiler { indent_on_line += 1; } if indent_on_line > expected_indent { - Err(UnexpectedIndent( + Err(self.raise(UnexpectedIndent( indent_on_line, expected_indent - )) + ))) } else if indent_on_line < expected_indent { self.indent.pop(); return Ok(None); @@ -149,7 +157,7 @@ impl AstCompiler { } } - fn declaration(&mut self) -> Result { + fn declaration(&mut self) -> Result { if self.match_token(vec![Fn]) { self.function_declaration() } else if self.match_token(vec![Let]) { @@ -161,7 +169,7 @@ impl AstCompiler { } } - fn object_declaration(&mut self) -> Result { + fn object_declaration(&mut self) -> Result { let type_name = self.consume(Identifier, Expected("object name."))?; self.consume(Colon, Expected("':' after object name."))?; self.consume(Eol, Expected("end of line."))?; @@ -186,7 +194,7 @@ impl AstCompiler { if field_type.is_type() { self.advance(); } else { - Err(Expected("a type"))? + Err(self.raise(Expected("a type")))? } fields.push(Parameter { name: field_name, @@ -201,7 +209,7 @@ impl AstCompiler { }) } - fn function_declaration(&mut self) -> Result { + fn function_declaration(&mut self) -> Result { let name_token = self.consume(Identifier, Expected("function name."))?; self.consume(LeftParen, Expected("'(' after function name."))?; while !self.check(RightParen) { @@ -227,9 +235,9 @@ impl AstCompiler { Ok(function_stmt) } - fn let_declaration(&mut self) -> Result { + fn let_declaration(&mut self) -> Result { if self.peek().token_type.is_type(){ - return Err(CompilerError::KeywordNotAllowedAsIdentifier(self.peek().token_type)) + return Err(self.raise(CompilerError::KeywordNotAllowedAsIdentifier(self.peek().token_type))) } let name_token = self.consume(Identifier, Expected("variable name."))?; @@ -249,7 +257,7 @@ impl AstCompiler { Ok(var_type) => var_type, Err(e) => { self.had_error = true; - return Err(TypeError(name_token.line, Box::new(e))); + return Err(self.raise(TypeError(Box::new(e)))); } }; self.vars.push(Expression::Variable { @@ -263,11 +271,11 @@ impl AstCompiler { initializer, }) } else { - Err(UninitializedVariable)? + Err(self.raise(UninitializedVariable))? } } - fn statement(&mut self) -> Result { + fn statement(&mut self) -> Result { if self.match_token(vec![Print]) { self.print_statement() } else { @@ -275,68 +283,68 @@ impl AstCompiler { } } - fn print_statement(&mut self) -> Result { + fn print_statement(&mut self) -> Result { let expr = self.expression()?; self.consume(Eol, Expected("end of line after print statement."))?; Ok(Statement::PrintStmt { value: expr }) } - fn expr_statement(&mut self) -> Result { + fn expr_statement(&mut self) -> Result { let expr = self.expression()?; self.consume(Eol, Expected("end of line after expression."))?; Ok(Statement::ExpressionStmt { expression: expr }) } - fn expression(&mut self) -> Result { + fn expression(&mut self) -> Result { self.or() } - fn or(&mut self) -> Result { + fn or(&mut self) -> Result { let expr = self.and()?; self.binary(vec![TokenType::LogicalOr], expr) } - fn and(&mut self) -> Result { + fn and(&mut self) -> Result { let expr = self.bit_and()?; self.binary(vec![TokenType::LogicalAnd], expr) } - fn bit_and(&mut self) -> Result { + fn bit_and(&mut self) -> Result { let expr = self.bit_or()?; self.binary(vec![TokenType::BitAnd], expr) } - fn bit_or(&mut self) -> Result { + fn bit_or(&mut self) -> Result { let expr = self.bit_xor()?; self.binary(vec![TokenType::BitOr], expr) } - fn bit_xor(&mut self) -> Result { + fn bit_xor(&mut self) -> Result { let expr = self.equality()?; self.binary(vec![TokenType::BitXor], expr) } - fn equality(&mut self) -> Result { + fn equality(&mut self) -> Result { let expr = self.comparison()?; self.binary(vec![TokenType::EqualEqual, TokenType::BangEqual], expr) } - fn comparison(&mut self) -> Result { + fn comparison(&mut self) -> Result { let expr = self.bitshift()?; self.binary(vec![Greater, GreaterEqual, Less, LessEqual], expr) } - fn bitshift(&mut self) -> Result { + fn bitshift(&mut self) -> Result { let expr = self.term()?; self.binary(vec![GreaterGreater, LessLess], expr) } - fn term(&mut self) -> Result { + fn term(&mut self) -> Result { let expr = self.factor()?; self.binary(vec![Minus, Plus], expr) } - fn factor(&mut self) -> Result { + fn factor(&mut self) -> Result { let expr = self.unary()?; self.binary(vec![Slash, Star], expr) } @@ -345,7 +353,7 @@ impl AstCompiler { &mut self, types: Vec, mut expr: Expression, - ) -> Result { + ) -> Result { while self.match_token(types.clone()) { let operator = self.previous().clone(); let right = self.comparison()?; @@ -359,7 +367,7 @@ impl AstCompiler { Ok(expr) } - fn unary(&mut self) -> Result { + fn unary(&mut self) -> Result { if self.match_token(vec![Bang, Minus]) { let operator = self.previous().clone(); let right = self.unary()?; @@ -373,7 +381,7 @@ impl AstCompiler { } } - fn primary(&mut self) -> Result { + fn primary(&mut self) -> Result { debug!("primary {:?}", self.peek()); Ok(if self.match_token(vec![LeftBracket]) { self.list()? @@ -395,13 +403,13 @@ impl AstCompiler { Expression::Literal { line: self.peek().line, literaltype: Integer, - value: Value::I64(self.previous().lexeme.parse().map_err(|e|ParseError(format!("{:?}",e)))?), + value: Value::I64(self.previous().lexeme.parse().map_err(|e|self.raise(ParseError(format!("{:?}",e))))?), } } else if self.match_token(vec![FloatingPoint]) { Expression::Literal { line: self.peek().line, literaltype: FloatingPoint, - value: Value::F64(self.previous().lexeme.parse().map_err(|e|ParseError(format!("{:?}",e)))?), + value: Value::F64(self.previous().lexeme.parse().map_err(|e|self.raise(ParseError(format!("{:?}",e))))?), } } else if self.match_token(vec![StringType]) { Expression::Literal { @@ -433,7 +441,7 @@ impl AstCompiler { }) } - fn list(&mut self) -> Result { + fn list(&mut self) -> Result { let mut list = vec![]; while !self.match_token(vec![RightBracket]) { list.push(self.expression()?); @@ -451,7 +459,7 @@ impl AstCompiler { }) } - fn map(&mut self) -> Result { + fn map(&mut self) -> Result { let mut entries = vec![]; while !self.match_token(vec![RightBrace]) { let key = self.expression()?; @@ -472,7 +480,7 @@ impl AstCompiler { }) } - fn variable_lookup(&mut self, token: &Token) -> Result { + fn variable_lookup(&mut self, token: &Token) -> Result { let (var_name, var_type) = self .vars .iter() @@ -484,7 +492,7 @@ impl AstCompiler { } }) .find(|e| e.0 == &token.lexeme) - .ok_or_else(|| return CompilerError::UndeclaredVariable(token.clone()))?; + .ok_or_else(|| return self.raise(CompilerError::UndeclaredVariable(token.clone())))?; Ok(Expression::Variable { name: var_name.to_string(), var_type: var_type.clone(), @@ -492,22 +500,22 @@ impl AstCompiler { }) } - fn function_call(&mut self, name: String) -> Result { + fn function_call(&mut self, name: String) -> Result { let function_name = self.functions.get(&name).unwrap().name.lexeme.clone(); let function = self.functions.get(&function_name).unwrap().clone(); let mut arguments = vec![]; while !self.match_token(vec![RightParen]) { if arguments.len() >= 25 { - return Err(TooManyParameters); + return Err(self.raise(TooManyParameters)); } let arg = self.expression()?; let arg_type = arg.infer_type(); if arg_type != function.parameters[arguments.len()].var_type { - return Err(IncompatibleTypes( + return Err(self.raise(IncompatibleTypes( function.parameters[arguments.len()].var_type, arg_type - )); + ))); } arguments.push(arg); if self.peek().token_type == TokenType::Comma { @@ -526,7 +534,7 @@ impl AstCompiler { }) } - fn consume(&mut self, token_type: TokenType, message: CompilerError) -> Result { + fn consume(&mut self, token_type: TokenType, message: CompilerError) -> Result { if self.check(token_type) { self.advance(); } else { @@ -536,7 +544,7 @@ impl AstCompiler { // message.to_string(), // self.peek() // )); - return Err(message); + return Err(self.raise(message)); } Ok(self.previous().clone()) } @@ -577,6 +585,10 @@ impl AstCompiler { fn is_at_end(&self) -> bool { self.peek().token_type == Eof } + + fn current_line(&self) -> usize { + self.peek().line + } } fn calculate_type( diff --git a/src/bytecode_compiler.rs b/src/bytecode_compiler.rs index 044fcd6..9b2db7a 100644 --- a/src/bytecode_compiler.rs +++ b/src/bytecode_compiler.rs @@ -1,12 +1,11 @@ use crate::ast_compiler::{Expression, Function, Statement}; use crate::chunk::Chunk; -use crate::errors::CompilerError; +use crate::errors::{CompilerErrorAtLine}; use crate::tokens::TokenType; use crate::value::Value; use crate::vm::{ - OP_ADD, OP_AND, OP_ASSIGN, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CALL, OP_CONSTANT, OP_DEF_BOOL, - OP_DEF_CHAR, OP_DEF_DATE, OP_DEF_F32, OP_DEF_F64, OP_DEF_I32, OP_DEF_I64, OP_DEF_LIST, - OP_DEF_MAP, OP_DEF_STRING, OP_DEF_STRUCT, OP_DEF_U32, OP_DEFINE, OP_DIVIDE, OP_EQUAL, OP_GET, + OP_ADD, OP_AND, OP_ASSIGN, OP_BITAND, OP_BITOR, OP_BITXOR, OP_CALL, OP_CONSTANT, OP_DEF_LIST, + OP_DEF_MAP, OP_DIVIDE, OP_EQUAL, OP_GET, OP_GREATER, OP_GREATER_EQUAL, OP_LESS, OP_LESS_EQUAL, OP_MULTIPLY, OP_NEGATE, OP_NOT, OP_OR, OP_PRINT, OP_RETURN, OP_SHL, OP_SHR, OP_SUBTRACT, }; @@ -16,32 +15,31 @@ pub fn compile( namespace: Option<&str>, ast: &Vec, registry: &mut HashMap, -) -> Result<(), CompilerError> { - compile_name(ast, namespace, registry) +) -> Result<(), CompilerErrorAtLine> { + compile_in_namespace(ast, namespace, registry) } pub(crate) fn compile_function( function: &Function, registry: &mut HashMap, namespace: &str, -) -> Result { +) -> Result { let mut compiler = Compiler::new(&function.name.lexeme); for parm in &function.parameters { let name = parm.name.lexeme.clone(); let var_index = compiler.chunk.add_var(&parm.var_type, &parm.name.lexeme); compiler.vars.insert(name, var_index); - // compiler.emit_bytes(OP_DEFINE, name_index as u16); } Ok(compiler.compile(&function.body, registry, namespace)?) } -pub(crate) fn compile_name( +pub(crate) fn compile_in_namespace( ast: &Vec, namespace: Option<&str>, registry: &mut HashMap, -) -> Result<(), CompilerError> { +) -> Result<(), CompilerErrorAtLine> { let name = namespace.unwrap_or("main"); let compiler = Compiler::new(name); let chunk = compiler.compile(ast, registry, name)?; @@ -76,7 +74,7 @@ impl Compiler { ast: &Vec, registry: &mut HashMap, namespace: &str, - ) -> Result { + ) -> Result { for statement in ast { self.compile_statement(statement, registry, namespace)?; } @@ -90,7 +88,7 @@ impl Compiler { statement: &Statement, registry: &mut HashMap, namespace: &str, - ) -> Result<(), CompilerError> { + ) -> Result<(), CompilerErrorAtLine> { self.current_line = statement.line(); match statement { Statement::VarStmt { @@ -131,7 +129,7 @@ impl Compiler { namespace: &str, expression: &Expression, registry: &mut HashMap, - ) -> Result<(), CompilerError> { + ) -> Result<(), CompilerErrorAtLine> { match expression { Expression::FunctionCall { name, arguments, .. diff --git a/src/compiler_tests.rs b/src/compiler_tests.rs index 3fba686..e8da00e 100644 --- a/src/compiler_tests.rs +++ b/src/compiler_tests.rs @@ -56,7 +56,7 @@ a"#), if let Err(e) = &r { assert_eq!( e.to_string(), - "Type mismatch at line 1: Incompatible types. Expected u32, found i32/64" + "Compilation failed: error at line 1, Type mismatch: Expected u32, found i32/64" ); } } @@ -68,7 +68,7 @@ a"#), if let Err(e) = &r { assert_eq!( e.to_string(), - "Type mismatch at line 1: Incompatible types. Expected u64, found i32/64" + "Compilation failed: error at line 1, Type mismatch: Expected u64, found i32/64" ); } } @@ -80,7 +80,7 @@ a"#), if let Err(e) = &r { assert_eq!( e.to_string(), - "Type mismatch at line 1: Incompatible types. Expected u64, found string" + "Compilation failed: error at line 1, Type mismatch: Expected u64, found string" ); } } @@ -154,6 +154,6 @@ m"#); fn keyword_error(){ let result = run(r#"let map = {"name": "Dent"}"#); assert!(result.is_err()); - assert_eq!("'map' is a keyword. You cannot use it as an identifier",result.unwrap_err().to_string()); + assert_eq!("Compilation failed: error at line 1, 'map' is a keyword. You cannot use it as an identifier",result.unwrap_err().to_string()); } } diff --git a/src/errors.rs b/src/errors.rs index df13ea4..0c57725 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,16 +1,36 @@ +use std::fmt::Display; use crate::tokens::{Token, TokenType}; use thiserror::Error; #[derive(Error, Debug, PartialEq)] pub enum Error { - #[error(transparent)] - Compiler(#[from] CompilerError), + #[error("Compilation failed: {0}")] + Compiler(#[from] CompilerErrorAtLine), + #[error(transparent)] Runtime(#[from] RuntimeError), #[error("Platform error {0}")] Platform(String), } +#[derive(Error, Debug, PartialEq)] +pub struct CompilerErrorAtLine{ + pub error: CompilerError, + pub line: usize +} + +impl CompilerErrorAtLine { + pub(crate) fn raise(error:CompilerError, line: usize) -> Self{ + Self {error, line} + } +} + +impl Display for CompilerErrorAtLine { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "error at line {}, {}", self.line, self.error) + } +} + #[derive(Error, Debug, PartialEq)] pub enum CompilerError { #[error("Compilation failed")] @@ -21,22 +41,22 @@ pub enum CompilerError { Expected(&'static str), #[error("unexpected indent level {0} vs expected {1}")] UnexpectedIndent(usize, usize), - #[error("Type mismatch at line {0}: {1}")] - TypeError(usize, Box), + #[error("Type mismatch: {0}")] + TypeError(Box), #[error("Uninitialized variables are not allowed.")] UninitializedVariable, - #[error("Incompatible types. Expected {0}, found {1}")] + #[error("Expected {0}, found {1}")] IncompatibleTypes(TokenType, TokenType), #[error("Error parsing number {0}")] ParseError(String), #[error("Undeclared variable: {0:?}")] UndeclaredVariable(Token), - #[error("Unexpected identifier at line {0}")] - UnexpectedIdentifier(usize), - #[error("Unterminated {0} at line {1}")] - Unterminated(&'static str, usize), - #[error("Illegal char length for {0} at line {1}")] - IllegalCharLength(String, usize), + #[error("Unexpected identifier")] + UnexpectedIdentifier, + #[error("Unterminated {0}")] + Unterminated(&'static str), + #[error("Illegal char length for {0}")] + IllegalCharLength(String), #[error("Unexpected type {0}")] UnexpectedType(TokenType), #[error("'{0}' is a keyword. You cannot use it as an identifier")] diff --git a/src/scanner.rs b/src/scanner.rs index c99fbe0..7ce18ce 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -6,10 +6,10 @@ use crate::{ TokenType::{self}, }, }; -use crate::errors::CompilerError; +use crate::errors::{CompilerError, CompilerErrorAtLine}; use crate::errors::CompilerError::{IllegalCharLength, UnexpectedIdentifier, Unterminated}; -pub fn scan(source: &str) -> Result, CompilerError> { +pub fn scan(source: &str) -> Result, CompilerErrorAtLine> { let scanner = Scanner { chars: source.chars().collect(), current: 0, @@ -22,7 +22,7 @@ pub fn scan(source: &str) -> Result, CompilerError> { } impl Scanner { - fn scan(mut self) -> Result, CompilerError> { + fn scan(mut self) -> Result, CompilerErrorAtLine> { while !self.is_at_end() { self.start = self.current; self.scan_token()?; @@ -32,7 +32,7 @@ impl Scanner { Ok(self.tokens) } - fn scan_token(&mut self) -> Result<(),CompilerError> { + fn scan_token(&mut self) -> Result<(),CompilerErrorAtLine> { let c = self.advance(); if self.new_line && (c == ' ' || c == '\t') { self.add_token(TokenType::Indent); @@ -139,7 +139,7 @@ impl Scanner { } else if is_alpha(c) { self.identifier(); } else { - return Err(UnexpectedIdentifier(self.line)); + return Err(self.raise(UnexpectedIdentifier)); } } } @@ -174,13 +174,13 @@ impl Scanner { self.add_token_with_value(if has_dot { FloatingPoint } else { Integer }, value); } - fn char(&mut self) -> Result<(), CompilerError>{ + fn char(&mut self) -> Result<(), CompilerErrorAtLine> { while self.peek() != '\'' && !self.is_at_end() { self.advance(); } if self.is_at_end() { - return Err(Unterminated("char", self.line)) + return Err(CompilerErrorAtLine::raise(Unterminated("char"), self.line)) } self.advance(); @@ -189,13 +189,17 @@ impl Scanner { .iter() .collect(); if value.len() != 1 { - return Err(IllegalCharLength(value, self.line)) + return Err(self.raise(IllegalCharLength(value))); } self.add_token_with_value(TokenType::Char, value); Ok(()) } - fn string(&mut self) -> Result<(),CompilerError> { + fn raise(&self, error: CompilerError) -> CompilerErrorAtLine { + CompilerErrorAtLine::raise(error, self.line) + } + + fn string(&mut self) -> Result<(),CompilerErrorAtLine> { while self.peek() != '"' && !self.is_at_end() { if self.peek() == '\n' { self.line += 1; @@ -204,7 +208,7 @@ impl Scanner { } if self.is_at_end() { - return Err(Unterminated("string", self.line)) + return Err(self.raise(Unterminated("string"))); } self.advance(); diff --git a/src/vm.rs b/src/vm.rs index 519831d..26a9b27 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -6,20 +6,6 @@ use std::collections::HashMap; use tracing::debug; use crate::tokens::TokenType; -macro_rules! define_var { - ($self:ident, $variant:ident, $chunk:ident) => {{ - let name = $self.read_name($chunk); - let value = $self.pop(); - if let Value::$variant(_) = value { - $self.local_vars.insert(name, value); - } else { - return Err(RuntimeError::Expected( - stringify!($variant), stringify!(value), - )); - } - }}; -} - pub struct Vm<'a> { ip: usize, stack: Vec,