generic support for line numbers in error messages

This commit is contained in:
Shautvast 2025-11-02 13:02:10 +01:00
parent 8234e9d50a
commit de9f55842c
6 changed files with 118 additions and 98 deletions

View file

@ -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<Token>) -> Result<Vec<Statement>, CompilerError> {
pub fn compile(tokens: Vec<Token>) -> Result<Vec<Statement>, 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<Vec<Statement>,CompilerError> {
fn compile_tokens(&mut self) -> Result<Vec<Statement>,CompilerErrorAtLine> {
self.collect_functions()?;
self.reset();
self.compile()
}
fn compile(&mut self) -> Result<Vec<Statement>, CompilerError> {
fn compile(&mut self) -> Result<Vec<Statement>, 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<Option<Statement>, CompilerError> {
fn indent(&mut self) -> Result<Option<Statement>, 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<Statement, CompilerError> {
fn declaration(&mut self) -> Result<Statement, CompilerErrorAtLine> {
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<Statement, CompilerError> {
fn object_declaration(&mut self) -> Result<Statement, CompilerErrorAtLine> {
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<Statement, CompilerError> {
fn function_declaration(&mut self) -> Result<Statement, CompilerErrorAtLine> {
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<Statement, CompilerError> {
fn let_declaration(&mut self) -> Result<Statement, CompilerErrorAtLine> {
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<Statement, CompilerError> {
fn statement(&mut self) -> Result<Statement, CompilerErrorAtLine> {
if self.match_token(vec![Print]) {
self.print_statement()
} else {
@ -275,68 +283,68 @@ impl AstCompiler {
}
}
fn print_statement(&mut self) -> Result<Statement, CompilerError> {
fn print_statement(&mut self) -> Result<Statement, CompilerErrorAtLine> {
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<Statement, CompilerError> {
fn expr_statement(&mut self) -> Result<Statement, CompilerErrorAtLine> {
let expr = self.expression()?;
self.consume(Eol, Expected("end of line after expression."))?;
Ok(Statement::ExpressionStmt { expression: expr })
}
fn expression(&mut self) -> Result<Expression, CompilerError> {
fn expression(&mut self) -> Result<Expression, CompilerErrorAtLine> {
self.or()
}
fn or(&mut self) -> Result<Expression, CompilerError> {
fn or(&mut self) -> Result<Expression, CompilerErrorAtLine> {
let expr = self.and()?;
self.binary(vec![TokenType::LogicalOr], expr)
}
fn and(&mut self) -> Result<Expression, CompilerError> {
fn and(&mut self) -> Result<Expression, CompilerErrorAtLine> {
let expr = self.bit_and()?;
self.binary(vec![TokenType::LogicalAnd], expr)
}
fn bit_and(&mut self) -> Result<Expression, CompilerError> {
fn bit_and(&mut self) -> Result<Expression, CompilerErrorAtLine> {
let expr = self.bit_or()?;
self.binary(vec![TokenType::BitAnd], expr)
}
fn bit_or(&mut self) -> Result<Expression, CompilerError> {
fn bit_or(&mut self) -> Result<Expression, CompilerErrorAtLine> {
let expr = self.bit_xor()?;
self.binary(vec![TokenType::BitOr], expr)
}
fn bit_xor(&mut self) -> Result<Expression, CompilerError> {
fn bit_xor(&mut self) -> Result<Expression, CompilerErrorAtLine> {
let expr = self.equality()?;
self.binary(vec![TokenType::BitXor], expr)
}
fn equality(&mut self) -> Result<Expression, CompilerError> {
fn equality(&mut self) -> Result<Expression, CompilerErrorAtLine> {
let expr = self.comparison()?;
self.binary(vec![TokenType::EqualEqual, TokenType::BangEqual], expr)
}
fn comparison(&mut self) -> Result<Expression, CompilerError> {
fn comparison(&mut self) -> Result<Expression, CompilerErrorAtLine> {
let expr = self.bitshift()?;
self.binary(vec![Greater, GreaterEqual, Less, LessEqual], expr)
}
fn bitshift(&mut self) -> Result<Expression, CompilerError> {
fn bitshift(&mut self) -> Result<Expression, CompilerErrorAtLine> {
let expr = self.term()?;
self.binary(vec![GreaterGreater, LessLess], expr)
}
fn term(&mut self) -> Result<Expression, CompilerError> {
fn term(&mut self) -> Result<Expression, CompilerErrorAtLine> {
let expr = self.factor()?;
self.binary(vec![Minus, Plus], expr)
}
fn factor(&mut self) -> Result<Expression, CompilerError> {
fn factor(&mut self) -> Result<Expression, CompilerErrorAtLine> {
let expr = self.unary()?;
self.binary(vec![Slash, Star], expr)
}
@ -345,7 +353,7 @@ impl AstCompiler {
&mut self,
types: Vec<TokenType>,
mut expr: Expression,
) -> Result<Expression, CompilerError> {
) -> Result<Expression, CompilerErrorAtLine> {
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<Expression, CompilerError> {
fn unary(&mut self) -> Result<Expression, CompilerErrorAtLine> {
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<Expression, CompilerError> {
fn primary(&mut self) -> Result<Expression, CompilerErrorAtLine> {
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<Expression, CompilerError> {
fn list(&mut self) -> Result<Expression, CompilerErrorAtLine> {
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<Expression, CompilerError> {
fn map(&mut self) -> Result<Expression, CompilerErrorAtLine> {
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<Expression, CompilerError> {
fn variable_lookup(&mut self, token: &Token) -> Result<Expression, CompilerErrorAtLine> {
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<Expression, CompilerError> {
fn function_call(&mut self, name: String) -> Result<Expression, CompilerErrorAtLine> {
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<Token, CompilerError> {
fn consume(&mut self, token_type: TokenType, message: CompilerError) -> Result<Token, CompilerErrorAtLine> {
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(

View file

@ -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<Statement>,
registry: &mut HashMap<String, Chunk>,
) -> 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<String, Chunk>,
namespace: &str,
) -> Result<Chunk, CompilerError> {
) -> Result<Chunk, CompilerErrorAtLine> {
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<Statement>,
namespace: Option<&str>,
registry: &mut HashMap<String, Chunk>,
) -> 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<Statement>,
registry: &mut HashMap<String, Chunk>,
namespace: &str,
) -> Result<Chunk, CompilerError> {
) -> Result<Chunk, CompilerErrorAtLine> {
for statement in ast {
self.compile_statement(statement, registry, namespace)?;
}
@ -90,7 +88,7 @@ impl Compiler {
statement: &Statement,
registry: &mut HashMap<String, Chunk>,
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<String, Chunk>,
) -> Result<(), CompilerError> {
) -> Result<(), CompilerErrorAtLine> {
match expression {
Expression::FunctionCall {
name, arguments, ..

View file

@ -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());
}
}

View file

@ -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<CompilerError>),
#[error("Type mismatch: {0}")]
TypeError(Box<CompilerError>),
#[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")]

View file

@ -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<Vec<Token>, CompilerError> {
pub fn scan(source: &str) -> Result<Vec<Token>, CompilerErrorAtLine> {
let scanner = Scanner {
chars: source.chars().collect(),
current: 0,
@ -22,7 +22,7 @@ pub fn scan(source: &str) -> Result<Vec<Token>, CompilerError> {
}
impl Scanner {
fn scan(mut self) -> Result<Vec<Token>, CompilerError> {
fn scan(mut self) -> Result<Vec<Token>, 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();

View file

@ -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<Value>,