From 41253e932a0c5dc65485f899cba3ad4a72109ce7 Mon Sep 17 00:00:00 2001 From: Giulio Agostini Date: Sat, 4 Oct 2025 19:02:33 +0200 Subject: [PATCH] Add source location tracking to AST nodes The commit expands the source location tracking through the AST by: 1. Switching from raw Expr/Stmt nodes to AstNode wrappers containing source slices 2. Updating interpreter and parser to preserve location info through node traversal 3. Enhancing error reporting with richer source context information --- src/backend/interpreter.rs | 126 +++++++--- src/frontend/ast.rs | 139 ++++++----- src/frontend/lexer.rs | 1 + src/frontend/parser.rs | 457 +++++++++++++++++++++++------------ src/frontend/tokens.rs | 2 + src/logging/display_ast.rs | 40 --- src/logging/display_token.rs | 2 + src/main.rs | 13 +- src/result.rs | 423 +++++++++++++++++++++++++++----- test_error.lox | 9 + test_source_tracking.lox | 26 ++ 11 files changed, 886 insertions(+), 352 deletions(-) create mode 100644 test_error.lox create mode 100644 test_source_tracking.lox diff --git a/src/backend/interpreter.rs b/src/backend/interpreter.rs index 779760f..d5ddccb 100644 --- a/src/backend/interpreter.rs +++ b/src/backend/interpreter.rs @@ -19,6 +19,13 @@ fn error(message: String) -> LoxError { } } +fn error_at(source_slice: SourceSlice, message: String) -> LoxError { + LoxError::RuntimeError { + source_slice, + message, + } +} + impl Not for LiteralValue { type Output = LiteralValue; @@ -68,6 +75,25 @@ impl Add for LiteralValue { } } +impl LiteralValue { + pub fn add_with_source( + self, + other: LiteralValue, + source_slice: SourceSlice, + ) -> LoxResult { + match (self, other) { + (LiteralValue::Number(a), LiteralValue::Number(b)) => Ok(LiteralValue::Number(a + b)), + (LiteralValue::String(a), LiteralValue::String(b)) => { + Ok(LiteralValue::String(format!("{}{}", a, b))) + } + _ => Err(error_at( + source_slice, + "Cannot add non-numeric values".to_string(), + )), + } + } +} + impl Sub for LiteralValue { type Output = LoxResult; @@ -154,15 +180,16 @@ impl<'a> Interpreter<'a> { fn interpret_binary( &mut self, - left: Expr, + left: AstNode, operator: TokenType, - right: Expr, + right: AstNode, + source_slice: SourceSlice, ) -> LoxResult { let left_value = self.interpret(left)?; let right_value = self.interpret(right)?; match operator { TokenType::Minus => left_value - right_value, - TokenType::Plus => left_value + right_value, + TokenType::Plus => left_value.add_with_source(right_value, source_slice.clone()), TokenType::Slash => left_value / right_value, TokenType::Star => left_value * right_value, TokenType::EqualEqual => Ok(LiteralValue::Boolean(left_value == right_value)), @@ -207,31 +234,76 @@ where } } +// Direct Expr evaluation to avoid infinite recursion impl<'a> EvaluateInterpreter for Interpreter<'a> { - fn interpret(&mut self, stmt: Expr) -> LoxResult { - match stmt { - Expr::Literal { value } => Ok(value.clone()), + fn interpret(&mut self, expr: Expr) -> LoxResult { + match expr { + Expr::Literal { value } => Ok(value), + Expr::Variable { name } => self.enviorment.get(&name), Expr::Binary { left, operator, right, - } => self.interpret_binary(*left, operator, *right), + } => { + let left_val = self.interpret(*left)?; + let right_val = self.interpret(*right)?; + self.evaluate_binary(left_val, operator, right_val) + } Expr::Unary { operator, operand } => { - let right = self.interpret(*operand)?; - match operator { - TokenType::Minus => Ok((-right)?), - TokenType::Bang => Ok(!right), - _ => Err(error("Unsupported unary operator".to_string())), - } + let operand_val = self.interpret(*operand)?; + self.evaluate_unary(operator, operand_val) } Expr::Grouping { expression } => self.interpret(*expression), - Expr::Variable { name } => self.enviorment.get(&name), } } } -impl<'a> EvaluateInterpreter for Interpreter<'a> { - fn interpret(&mut self, stmt: Stmt) -> LoxResult { +impl<'a> EvaluateInterpreter> for Interpreter<'a> { + fn interpret(&mut self, node: AstNode) -> LoxResult { + let stmt = node.node; + let _source_slice = node.source_slice; + self.interpret_stmt_inner(stmt) + } +} + +impl<'a> Interpreter<'a> { + fn evaluate_binary( + &mut self, + left: LiteralValue, + operator: TokenType, + right: LiteralValue, + ) -> LoxResult { + match operator { + TokenType::Plus => left + right, + TokenType::Minus => left - right, + TokenType::Star => left * right, + TokenType::Slash => left / right, + TokenType::Greater => Ok(LiteralValue::Boolean(left > right)), + TokenType::GreaterEqual => Ok(LiteralValue::Boolean(left >= right)), + TokenType::Less => Ok(LiteralValue::Boolean(left < right)), + TokenType::LessEqual => Ok(LiteralValue::Boolean(left <= right)), + TokenType::EqualEqual => Ok(LiteralValue::Boolean(left == right)), + TokenType::BangEqual => Ok(LiteralValue::Boolean(left != right)), + _ => Err(error(format!( + "Unsupported binary operator: {:?}", + operator + ))), + } + } + + fn evaluate_unary( + &mut self, + operator: TokenType, + operand: LiteralValue, + ) -> LoxResult { + match operator { + TokenType::Minus => -operand, + TokenType::Bang => Ok(!operand), + _ => Err(error(format!("Unsupported unary operator: {:?}", operator))), + } + } + + fn interpret_stmt_inner(&mut self, stmt: Stmt) -> LoxResult { match stmt { Stmt::Expression { expression } => self.interpret(*expression), Stmt::Print { expression } => { @@ -241,11 +313,12 @@ impl<'a> EvaluateInterpreter for Interpreter<'a> { } Stmt::Block { statements } => { let (elements, final_expr) = match statements.split_last() { - Some((Stmt::Expression { expression }, body)) => (body, Some(expression)), - Some((Stmt::Return { expression }, body)) => (body, Some(expression)), - Some((_last_stmt, _body)) => { - (statements.as_slice(), None) // Non è un'Expression finale - } + Some((stmt, body)) => match &stmt.node { + Stmt::Expression { expression } => (body, Some(expression)), + Stmt::Return { expression } => (body, Some(expression)), + _ => (statements.as_slice(), None), + }, + None => { (&[][..], None) // Blocco vuoto } @@ -258,14 +331,11 @@ impl<'a> EvaluateInterpreter for Interpreter<'a> { // Gestisci l'espressione finale se presente match final_expr { - Some(expr) => self.interpret((**expr).clone()), + Some(expr) => self.interpret(*expr.clone()), None => Ok(LiteralValue::Nil), } } - Stmt::Stmt { expression } => { - let _ = self.interpret(*expression); - Ok(LiteralValue::Nil) - } + Stmt::Stmt { expression } => self.interpret(*expression.clone()), Stmt::Return { expression } => self.interpret(*expression), Stmt::Var { name, initializer } => { let value = if let Some(expr) = initializer { @@ -277,8 +347,8 @@ impl<'a> EvaluateInterpreter for Interpreter<'a> { return Ok(LiteralValue::Nil); } Stmt::Assign { name, value } => { - let value = self.interpret(*value)?; - self.enviorment.set(name.clone(), value); + let result = self.interpret(*value)?; + self.enviorment.set(name.clone(), result); Ok(LiteralValue::Nil) } Stmt::If { diff --git a/src/frontend/ast.rs b/src/frontend/ast.rs index 1ac797b..1378009 100644 --- a/src/frontend/ast.rs +++ b/src/frontend/ast.rs @@ -38,16 +38,16 @@ pub enum Expr { value: LiteralValue, }, Binary { - left: Box, + left: Box>, operator: TokenType, - right: Box, + right: Box>, }, Unary { operator: TokenType, - operand: Box, + operand: Box>, }, Grouping { - expression: Box, + expression: Box>, }, Variable { name: String, @@ -67,13 +67,13 @@ impl ExprPrint for Expr { operator, right, } => { - format!("Binary ({} {} {})", left, operator, right) + format!("Binary ({} {} {})", left.node, operator, right.node) } Expr::Unary { operator, operand } => { - format!("Unary ({} {})", operator, operand) + format!("Unary ({} {})", operator, operand.node) } Expr::Grouping { expression } => { - format!("Grouping (group {})", expression) + format!("Grouping (group {})", expression.node) } Expr::Variable { name } => { format!("Variable (variable {})", name) @@ -91,9 +91,9 @@ impl std::fmt::Display for Expr { left, operator, right, - } => write!(f, "Binary ({} {} {})", left, operator, right), - Expr::Unary { operator, operand } => write!(f, "Unary ({} {})", operator, operand), - Expr::Grouping { expression } => write!(f, "Grouping (group {})", expression), + } => write!(f, "Binary ({} {} {})", left.node, operator, right.node), + Expr::Unary { operator, operand } => write!(f, "Unary ({} {})", operator, operand.node), + Expr::Grouping { expression } => write!(f, "Grouping (group {})", expression.node), Expr::Variable { name } => write!(f, "Variable (variable {})", name), } } @@ -107,9 +107,9 @@ impl Debug for Expr { left, operator, right, - } => write!(f, "({:?} {:?} {:?})", operator, left, right), - Expr::Unary { operator, operand } => write!(f, "({:?} {:?})", operator, operand), - Expr::Grouping { expression } => write!(f, "(group {:?})", expression), + } => write!(f, "({:?} {:?} {:?})", operator, left.node, right.node), + Expr::Unary { operator, operand } => write!(f, "({:?} {:?})", operator, operand.node), + Expr::Grouping { expression } => write!(f, "(group {:?})", expression.node), Expr::Variable { name } => write!(f, "(variable {:?})", name), } } @@ -118,86 +118,96 @@ impl Debug for Expr { #[derive(Clone)] pub enum Stmt { Expression { - expression: Box, + expression: Box>, }, Print { - expression: Box, + expression: Box>, }, Stmt { - expression: Box, + expression: Box>, }, Var { name: String, - initializer: Option>, + initializer: Option>>, }, Assign { name: String, - value: Box, + value: Box>, }, Return { - expression: Box, + expression: Box>, }, Block { - statements: Box>, + statements: Box>>, }, If { - condition: Box, - then_branch: Box, - elif_branch: Vec<(Box, Box)>, - else_branch: Option>, + condition: Box>, + then_branch: Box>, + elif_branch: Vec<(Box>, Box>)>, + else_branch: Option>>, }, While { - condition: Box, - body: Box, + condition: Box>, + body: Box>, }, For { - variable: Box, - iterable: Box, - body: Box, + variable: Box>, + iterable: Box>, + body: Box>, }, } impl Display for Stmt { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Stmt::Expression { expression } => write!(f, "Expression ({})", expression), + Stmt::Expression { expression } => write!(f, "Expression ({})", expression.node), Stmt::If { condition, then_branch, elif_branch, else_branch, } => { - let mut result = format!("IF ({}) {{\n{}\n}}", condition, then_branch); + let mut result = format!("IF ({}) {{\n{}\n}}", condition.node, then_branch.node); for (condition, branch) in elif_branch { - result.push_str(&format!(" ELIF ({}) {{\n{}\n}}", condition, branch)); + result.push_str(&format!( + " ELIF ({}) {{\n{}\n}}", + condition.node, branch.node + )); } if let Some(else_branch) = else_branch { - result.push_str(&format!(" ELSE {{\n{}\n}}", else_branch)); + result.push_str(&format!(" ELSE {{\n{}\n}}", else_branch.node)); } write!(f, "IfStmt {}", result) } - Stmt::Print { expression } => write!(f, "Print({});", expression), - Stmt::Stmt { expression } => write!(f, "Stmt({});", expression), - Stmt::Var { name, initializer } => { - write!(f, "Var({} = {:?});", name, initializer) - } - Stmt::Assign { name, value } => write!(f, "Assign({} = {});", name, value), - Stmt::Return { expression } => write!(f, "Return({});", expression), + Stmt::Print { expression } => write!(f, "Print({});", expression.node), + Stmt::Stmt { expression } => write!(f, "Stmt({});", expression.node), + Stmt::Var { name, initializer } => match initializer { + Some(init) => write!(f, "Var({} = {});", name, init.node), + None => write!(f, "Var({});", name), + }, + Stmt::Assign { name, value } => write!(f, "Assign({} = {});", name, value.node), + Stmt::Return { expression } => write!(f, "Return({});", expression.node), Stmt::Block { statements } => write!( f, "Block([\n{}\n])", statements .iter() - .map(|stmt| format!("\t \t{}", stmt)) + .map(|stmt| format!("\t \t{}", stmt.node)) .collect::>() .join("\n") ), - Stmt::While { condition, body } => todo!(), + Stmt::While { condition, body } => { + write!(f, "While({}) {{\n{}\n}}", condition.node, body.node) + } Stmt::For { variable, iterable, body, - } => todo!(), + } => write!( + f, + "For({} in {}) {{\n{}\n}}", + variable.node, iterable.node, body.node + ), } } } @@ -205,44 +215,55 @@ impl Display for Stmt { impl Debug for Stmt { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Stmt::Expression { expression } => write!(f, " Expression ({:?})", expression), + Stmt::Expression { expression } => write!(f, " Expression ({:?})", expression.node), Stmt::If { condition, then_branch, elif_branch, else_branch, } => { - let mut result = format!("IF ({:?}) {{\n{:?}\n}}", condition, then_branch); + let mut result = + format!("IF ({:?}) {{\n{:?}\n}}", condition.node, then_branch.node); for (condition, branch) in elif_branch { - result.push_str(&format!(" ELIF ({:?}) {{\n{:?}\n}}", condition, branch)); + result.push_str(&format!( + " ELIF ({:?}) {{\n{:?}\n}}", + condition.node, branch.node + )); } if let Some(else_branch) = else_branch { - result.push_str(&format!(" ELSE {{\n{:?}\n}}", else_branch)); + result.push_str(&format!(" ELSE {{\n{:?}\n}}", else_branch.node)); } write!(f, "IfStmt {:?}", result) } - Stmt::Print { expression } => write!(f, "Print({:?});", expression), - Stmt::Stmt { expression } => write!(f, "Stmt({:?});", expression), - Stmt::Var { name, initializer } => { - write!(f, "Var({:?} = {:?});", name, initializer) - } - Stmt::Assign { name, value } => write!(f, "Assign({:?} = {:?});", name, value), - Stmt::Return { expression } => write!(f, "Return({:?});", expression), + Stmt::Print { expression } => write!(f, "Print({:?});", expression.node), + Stmt::Stmt { expression } => write!(f, "Stmt({:?});", expression.node), + Stmt::Var { name, initializer } => match initializer { + Some(init) => write!(f, "Var({:?} = {:?});", name, init.node), + None => write!(f, "Var({:?});", name), + }, + Stmt::Assign { name, value } => write!(f, "Assign({:?} = {:?});", name, value.node), + Stmt::Return { expression } => write!(f, "Return({:?});", expression.node), Stmt::Block { statements } => write!( f, "Block([\n{:?}\n])", statements .iter() - .map(|stmt| format!("{:?}", stmt)) + .map(|stmt| format!("{:?}", stmt.node)) .collect::>() .join("\t\t\n") ), - Stmt::While { condition, body } => todo!(), + Stmt::While { condition, body } => { + write!(f, "While({:?}) {{\n{:?}\n}}", condition.node, body.node) + } Stmt::For { variable, iterable, body, - } => todo!(), + } => write!( + f, + "For({:?} in {:?}) {{\n{:?}\n}}", + variable.node, iterable.node, body.node + ), } } } @@ -289,12 +310,12 @@ impl AstNodeKind for Stmt { } => "if statement", Stmt::Print { expression: _ } => "print statement", Stmt::Stmt { expression: _ } => "statement", - Stmt::While { condition, body } => todo!(), + Stmt::While { condition, body } => "while statement", Stmt::For { variable, iterable, body, - } => todo!(), + } => "for statement", } } } diff --git a/src/frontend/lexer.rs b/src/frontend/lexer.rs index 45901af..d18b110 100644 --- a/src/frontend/lexer.rs +++ b/src/frontend/lexer.rs @@ -22,6 +22,7 @@ fn get_keyword_token(word: &str) -> Option { "for" => Some(TokenType::For), "fun" => Some(TokenType::Fun), "if" => Some(TokenType::If), + "then" => Some(TokenType::Then), "elif" => Some(TokenType::Elif), "else" => Some(TokenType::Else), "or" => Some(TokenType::Or), diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs index 611ed83..381205d 100644 --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -1,9 +1,10 @@ use crate::{ frontend::{ ast::{AstNode, Expr, Stmt}, + source_registry::SourceSlice, tokens::{LiteralValue, Token, TokenType}, }, - logging::display_ast::{pretty_print_with_config, PrettyConfig, PrettyPrint}, + logging::display_ast::{pretty_print_with_config, PrettyConfig}, result::{LoxError, LoxResult}, }; @@ -57,193 +58,284 @@ impl Parser { fn statement(&mut self) -> LoxResult> { match (&self.peek().token_type, &self.peek_next().token_type) { - (TokenType::Var, _) => Ok(AstNode::new( - self.var_statement()?, - self.peek().source_slice.clone(), - )), - (TokenType::Print, _) => Ok(AstNode::new( - self.print_statement()?, - self.peek().source_slice.clone(), - )), - (TokenType::Return, _) => Ok(AstNode::new( - self.return_statement()?, - self.peek().source_slice.clone(), - )), - (TokenType::StartBlock, _) => Ok(AstNode::new( - self.block_statement()?, - self.peek().source_slice.clone(), - )), - (TokenType::Identifier, TokenType::Equal) => Ok(AstNode::new( - self.assignment_statement()?, - self.peek().source_slice.clone(), - )), - (TokenType::If, _) => Ok(AstNode::new( - self.if_statement()?, - self.peek().source_slice.clone(), - )), - (TokenType::While, _) => Ok(AstNode::new( - self.while_statement()?, - self.peek().source_slice.clone(), - )), - _ => Ok(AstNode::new( - self.expression_statement()?, - self.peek().source_slice.clone(), - )), + (TokenType::Var, _) => self.var_statement(), + (TokenType::Print, _) => self.print_statement(), + (TokenType::Return, _) => self.return_statement(), + (TokenType::StartBlock, _) => self.block_statement(), + (TokenType::Identifier, TokenType::Equal) => self.assignment_statement(), + (TokenType::If, _) => self.if_statement(), + (TokenType::While, _) => self.while_statement(), + _ => self.expression_statement(), } } - fn while_statement(&mut self) -> LoxResult { + fn while_statement(&mut self) -> LoxResult> { + let start_slice = self.peek().source_slice.clone(); self.advance(); let condition = self.expression()?; - let body = self.block_statement()?; - Ok(Stmt::While { - condition: Box::new(condition), - body: Box::new(body), - }) + let body = self.statement()?; + let end_slice = body.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + Ok(AstNode::new( + Stmt::While { + condition: Box::new(condition), + body: Box::new(body), + }, + combined_slice, + )) } - fn if_statement(&mut self) -> LoxResult { - self.advance(); + fn if_statement(&mut self) -> LoxResult> { + let start_slice = self.peek().source_slice.clone(); + self.advance(); // consume 'if' let condition = self.expression()?; + self.consume(TokenType::Then, "Expect 'then' after if condition.")?; let then_branch = self.statement()?; let mut elif_branches = Vec::new(); + let mut last_slice = then_branch.source_slice.clone(); + while self.peek().token_type == TokenType::Elif { - self.advance(); - let condition = self.expression()?; - let then_branch = self.statement()?; - elif_branches.push((Box::new(condition), Box::new(then_branch.node))); + self.advance(); // consume 'elif' + let elif_condition = self.expression()?; + self.consume(TokenType::Then, "Expect 'then' after elif condition.")?; + let elif_branch = self.statement()?; + last_slice = elif_branch.source_slice.clone(); + elif_branches.push((Box::new(elif_condition), Box::new(elif_branch))); } + let else_branch = if self.peek().token_type == TokenType::Else { - self.advance(); - Some(Box::new(self.statement()?.node)) + self.advance(); // consume 'else' + let else_stmt = self.statement()?; + last_slice = else_stmt.source_slice.clone(); + Some(Box::new(else_stmt)) } else { None }; - Ok(Stmt::If { - condition: Box::new(condition), - then_branch: Box::new(then_branch.node), - elif_branch: elif_branches, - else_branch: else_branch, - }) + + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + last_slice.end_position, + ); + + Ok(AstNode::new( + Stmt::If { + condition: Box::new(condition), + then_branch: Box::new(then_branch), + elif_branch: elif_branches, + else_branch: else_branch, + }, + combined_slice, + )) } - fn var_statement(&mut self) -> LoxResult { + fn var_statement(&mut self) -> LoxResult> { + let start_slice = self.peek().source_slice.clone(); self.advance(); let name = self.consume(TokenType::Identifier, "Expect variable name.")?; let name_lexeme = name.lexeme.clone(); self.consume(TokenType::Equal, "Expect '=' after variable name.")?; let value = self.expression()?; - self.consume( + let semicolon = self.consume( TokenType::Semicolon, "Expect ';' after variable declaration.", )?; - Ok(Stmt::Var { - name: name_lexeme, - initializer: Some(Box::new(value)), - }) + let end_slice = semicolon.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + Ok(AstNode::new( + Stmt::Var { + name: name_lexeme, + initializer: Some(Box::new(value)), + }, + combined_slice, + )) } - fn assignment_statement(&mut self) -> LoxResult { + fn assignment_statement(&mut self) -> LoxResult> { + let start_slice = self.peek().source_slice.clone(); let name = self.consume(TokenType::Identifier, "Expect variable name.")?; let name_lexeme = name.lexeme.clone(); self.consume(TokenType::Equal, "Expect '=' after variable name.")?; let value = self.expression()?; - self.consume( + let semicolon = self.consume( TokenType::Semicolon, "Expect ';' after variable declaration.", )?; - Ok(Stmt::Assign { - name: name_lexeme, - value: Box::new(value), - }) + let end_slice = semicolon.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + Ok(AstNode::new( + Stmt::Assign { + name: name_lexeme, + value: Box::new(value), + }, + combined_slice, + )) } - fn print_statement(&mut self) -> LoxResult { + fn print_statement(&mut self) -> LoxResult> { // consume the print keyword + let start_slice = self.peek().source_slice.clone(); self.advance(); let expr = self.expression()?; - self.consume(TokenType::Semicolon, "Expect ';' after value.")?; - Ok(Stmt::Print { - expression: Box::new(expr), - }) + let semicolon = self.consume(TokenType::Semicolon, "Expect ';' after value.")?; + let end_slice = semicolon.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + Ok(AstNode::new( + Stmt::Print { + expression: Box::new(expr), + }, + combined_slice, + )) } - fn block_statement(&mut self) -> LoxResult { + fn block_statement(&mut self) -> LoxResult> { + let start_slice = self.peek().source_slice.clone(); self.advance(); let mut statements = Vec::new(); while self.peek().token_type != TokenType::EndBlock && !self.is_at_end() { let stmt = self.statement()?; - statements.push(stmt.node.clone()); - if let Stmt::Expression { .. } = stmt.node { + let should_break = matches!(stmt.node, Stmt::Expression { .. }); + statements.push(stmt); + if should_break { break; } } - self.consume(TokenType::EndBlock, "Expect 'end' after block.")?; - Ok(Stmt::Block { - statements: Box::new(statements), - }) + let end_token = self.consume(TokenType::EndBlock, "Expect 'end' after block.")?; + let end_slice = end_token.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + Ok(AstNode::new( + Stmt::Block { + statements: Box::new(statements), + }, + combined_slice, + )) } - fn expression_statement(&mut self) -> LoxResult { + fn expression_statement(&mut self) -> LoxResult> { + let start_slice = self.peek().source_slice.clone(); let expr = self.expression()?; if self.peek().token_type == TokenType::Semicolon { - self.advance(); - return Ok(Stmt::Stmt { - expression: Box::new(expr), - }); + let semicolon = self.advance(); + let end_slice = semicolon.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + return Ok(AstNode::new( + Stmt::Stmt { + expression: Box::new(expr), + }, + combined_slice, + )); } - Ok(Stmt::Expression { - expression: Box::new(expr), - }) + // Use the expression's source slice for expression statements without semicolon + let expr_slice = expr.source_slice.clone(); + Ok(AstNode::new( + Stmt::Expression { + expression: Box::new(expr), + }, + expr_slice, + )) } - fn return_statement(&mut self) -> LoxResult { + fn return_statement(&mut self) -> LoxResult> { + let start_slice = self.peek().source_slice.clone(); self.advance(); let expr = self.expression()?; - self.consume(TokenType::Semicolon, "Expect ';' after return value.")?; - Ok(Stmt::Return { - expression: Box::new(expr), - }) + let semicolon = self.consume(TokenType::Semicolon, "Expect ';' after return value.")?; + let end_slice = semicolon.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + Ok(AstNode::new( + Stmt::Return { + expression: Box::new(expr), + }, + combined_slice, + )) } - fn expression(&mut self) -> LoxResult { + fn expression(&mut self) -> LoxResult> { self.or_and() } - fn or_and(&mut self) -> LoxResult { + fn or_and(&mut self) -> LoxResult> { let mut expr = self.equality()?; while [TokenType::Or, TokenType::And].contains(&self.peek().token_type) { + let start_slice = expr.source_slice.clone(); let operator = self.peek().token_type.clone(); self.advance(); let right = self.equality()?; - expr = Expr::Binary { - left: Box::new(expr), - operator, - right: Box::new(right), - }; + let end_slice = right.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + expr = AstNode::new( + Expr::Binary { + left: Box::new(expr), + operator, + right: Box::new(right), + }, + combined_slice, + ); } Ok(expr) } - fn equality(&mut self) -> LoxResult { + fn equality(&mut self) -> LoxResult> { let mut expr = self.comparison()?; while [TokenType::BangEqual, TokenType::EqualEqual].contains(&self.peek().token_type) { + let start_slice = expr.source_slice.clone(); let operator = self.peek().token_type.clone(); self.advance(); let right = self.comparison()?; - expr = Expr::Binary { - left: Box::new(expr), - operator, - right: Box::new(right), - }; + let end_slice = right.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + expr = AstNode::new( + Expr::Binary { + left: Box::new(expr), + operator, + right: Box::new(right), + }, + combined_slice, + ); } Ok(expr) } - fn comparison(&mut self) -> LoxResult { + fn comparison(&mut self) -> LoxResult> { let mut expr = self.term()?; while [ @@ -254,121 +346,174 @@ impl Parser { ] .contains(&self.peek().token_type) { + let start_slice = expr.source_slice.clone(); let operator = self.peek().token_type.clone(); self.advance(); let right = self.term()?; - expr = Expr::Binary { - left: Box::new(expr), - operator, - right: Box::new(right), - }; + let end_slice = right.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + expr = AstNode::new( + Expr::Binary { + left: Box::new(expr), + operator, + right: Box::new(right), + }, + combined_slice, + ); } Ok(expr) } - fn term(&mut self) -> LoxResult { + fn term(&mut self) -> LoxResult> { let mut expr = self.factor()?; while [TokenType::Plus, TokenType::Minus].contains(&self.peek().token_type) { + let start_slice = expr.source_slice.clone(); let operator = self.peek().token_type.clone(); self.advance(); let right = self.factor()?; - expr = Expr::Binary { - left: Box::new(expr), - operator, - right: Box::new(right), - }; + let end_slice = right.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + expr = AstNode::new( + Expr::Binary { + left: Box::new(expr), + operator, + right: Box::new(right), + }, + combined_slice, + ); } Ok(expr) } - fn factor(&mut self) -> LoxResult { + fn factor(&mut self) -> LoxResult> { let mut expr = self.unary()?; while [TokenType::Slash, TokenType::Star].contains(&self.peek().token_type) { + let start_slice = expr.source_slice.clone(); let operator = self.peek().token_type.clone(); self.advance(); let right = self.unary()?; - expr = Expr::Binary { - left: Box::new(expr), - operator, - right: Box::new(right), - }; + let end_slice = right.source_slice.clone(); + let combined_slice = SourceSlice::from_positions( + start_slice.source_id, + start_slice.start_position, + end_slice.end_position, + ); + expr = AstNode::new( + Expr::Binary { + left: Box::new(expr), + operator, + right: Box::new(right), + }, + combined_slice, + ); } Ok(expr) } - fn unary(&mut self) -> LoxResult { + fn unary(&mut self) -> LoxResult> { if [TokenType::Bang, TokenType::Minus].contains(&self.peek().token_type) { let operator = self.peek().token_type.clone(); + let source_slice = self.peek().source_slice.clone(); self.advance(); let right = self.unary()?; - return Ok(Expr::Unary { - operator, - operand: Box::new(right), - }); + return Ok(AstNode::new( + Expr::Unary { + operator, + operand: Box::new(right), + }, + source_slice, + )); } self.primary() } - fn primary(&mut self) -> LoxResult { + fn primary(&mut self) -> LoxResult> { match self.peek().token_type { TokenType::False => { + let source_slice = self.peek().source_slice.clone(); self.advance(); - Ok(Expr::Literal { - value: LiteralValue::Boolean(false), - }) + Ok(AstNode::new( + Expr::Literal { + value: LiteralValue::Boolean(false), + }, + source_slice, + )) } TokenType::True => { + let source_slice = self.peek().source_slice.clone(); self.advance(); - Ok(Expr::Literal { - value: LiteralValue::Boolean(true), - }) + Ok(AstNode::new( + Expr::Literal { + value: LiteralValue::Boolean(true), + }, + source_slice, + )) } TokenType::Nil => { + let source_slice = self.peek().source_slice.clone(); self.advance(); - Ok(Expr::Literal { - value: LiteralValue::Nil, - }) + Ok(AstNode::new( + Expr::Literal { + value: LiteralValue::Nil, + }, + source_slice, + )) } TokenType::Number => { + let source_slice = self.peek().source_slice.clone(); + let literal = self.peek().literal.clone(); self.advance(); - let token = self.previous(); - if let Some(literal) = &token.literal { - Ok(Expr::Literal { - value: literal.clone(), - }) + if let Some(literal) = literal { + Ok(AstNode::new(Expr::Literal { value: literal }, source_slice)) } else { - Err(self.error(token, "Expected number literal").into()) + Err(self + .error(self.previous(), "Expected number literal") + .into()) } } TokenType::String => { + let source_slice = self.peek().source_slice.clone(); + let literal = self.peek().literal.clone(); self.advance(); - let token = self.previous(); - if let Some(literal) = &token.literal { - Ok(Expr::Literal { - value: literal.clone(), - }) + if let Some(literal) = literal { + Ok(AstNode::new(Expr::Literal { value: literal }, source_slice)) } else { - Err(self.error(token, "Expected string literal").into()) + Err(self + .error(self.previous(), "Expected string literal") + .into()) } } TokenType::LeftParen => { + let source_slice = self.peek().source_slice.clone(); self.advance(); let expr = self.expression()?; self.consume(TokenType::RightParen, "Expect ')' after expression.")?; - Ok(Expr::Grouping { - expression: Box::new(expr), - }) + Ok(AstNode::new( + Expr::Grouping { + expression: Box::new(expr), + }, + source_slice, + )) } TokenType::Identifier => { let name = self.peek().lexeme.clone(); + let source_slice = self.peek().source_slice.clone(); self.advance(); - Ok(Expr::Variable { name: name }) + Ok(AstNode::new(Expr::Variable { name }, source_slice)) } _ => Err(self.error(self.peek(), "Expect expression.").into()), } @@ -386,7 +531,7 @@ impl Parser { if !self.is_at_end() { self.current += 1; } - self.peek() + self.previous() } fn peek(&self) -> &Token { @@ -406,7 +551,11 @@ impl Parser { } fn previous(&self) -> &Token { - &self.tokens[self.current - 1] + if self.current == 0 { + &self.tokens[0] + } else { + &self.tokens[self.current - 1] + } } fn consume(&mut self, token_type: TokenType, message: &str) -> LoxResult<&Token> { diff --git a/src/frontend/tokens.rs b/src/frontend/tokens.rs index 6a7d81f..b863337 100644 --- a/src/frontend/tokens.rs +++ b/src/frontend/tokens.rs @@ -67,6 +67,7 @@ pub enum TokenType { For, While, If, + Then, Elif, Else, Nil, @@ -103,6 +104,7 @@ impl fmt::Display for TokenType { TokenType::Fun => write!(f, "fun"), TokenType::For => write!(f, "for"), TokenType::If => write!(f, "if"), + TokenType::Then => write!(f, "then"), TokenType::Nil => write!(f, "nil"), TokenType::Or => write!(f, "or"), TokenType::Print => write!(f, "print"), diff --git a/src/logging/display_ast.rs b/src/logging/display_ast.rs index d300d54..1e648b1 100644 --- a/src/logging/display_ast.rs +++ b/src/logging/display_ast.rs @@ -552,43 +552,3 @@ pub fn pretty_stmt_compact(stmt: &Stmt) -> String { pub fn pretty_tree(item: &T) -> String { pretty_print_with_config(item, &PrettyConfig::tree()) } - -#[cfg(test)] -mod tests { - use super::*; - use crate::frontend::{ - source_registry::{SourceId, SourcePosition, SourceSlice}, - tokens::{LiteralValue, TokenType}, - }; - - #[test] - fn test_pretty_expr_literal() { - let expr = Expr::Literal { - value: LiteralValue::Number(42.0), - }; - - let compact = expr.pretty_compact(); - assert!(compact.contains("42")); - - let full = expr.pretty(); - assert!(full.contains("Literal")); - assert!(full.contains("42")); - } - - #[test] - fn test_pretty_expr_binary() { - let expr = Expr::Binary { - left: Box::new(Expr::Literal { - value: LiteralValue::Number(1.0), - }), - operator: TokenType::Plus, - right: Box::new(Expr::Literal { - value: LiteralValue::Number(2.0), - }), - }; - - let compact = expr.pretty_compact(); - assert!(compact.contains("1")); - assert!(compact.contains("2")); - } -} diff --git a/src/logging/display_token.rs b/src/logging/display_token.rs index 87fc1be..bba5446 100644 --- a/src/logging/display_token.rs +++ b/src/logging/display_token.rs @@ -187,6 +187,7 @@ impl Token { TokenType::For => "FOR", TokenType::While => "WHILE", TokenType::If => "IF", + TokenType::Then => "THEN", TokenType::Elif => "ELIF", TokenType::Else => "ELSE", TokenType::Nil => "NIL", @@ -227,6 +228,7 @@ impl Token { | TokenType::For | TokenType::While | TokenType::If + | TokenType::Then | TokenType::Elif | TokenType::Else | TokenType::Nil diff --git a/src/main.rs b/src/main.rs index 6b526ed..da8eb2e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,16 +24,16 @@ fn main() -> LoxResult<()> { let args: Vec = env::args().collect(); let mut lox = LoxInterpreter::new(); - if args.len() > 2 { + let _ = if args.len() > 2 { eprintln!("Usage: {} [script]", args[0]); std::process::exit(64); } else if args.len() == 2 { // Esegui file - lox.run_file(&args[1])?; + lox.run_file(&args[1]) } else { // Modalità interattiva - lox.run_prompt()?; - } + lox.run_prompt() + }; Ok(()) } @@ -86,8 +86,9 @@ impl LoxInterpreter { let mut env = Environment::new(); let mut interpreter = Interpreter::new(&mut env); let mut result = None; - for stmt in stmts { - result = Some(interpreter.interpret(stmt)?); + for (index, stmt) in stmts.iter().enumerate() { + println!("executing stmt {}: {:?}", index, stmt); + result = Some(interpreter.interpret(stmt.clone())?); } match result { Some(res) => Ok(res), diff --git a/src/result.rs b/src/result.rs index 0b62ee3..8365ae4 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,4 +1,5 @@ -use crate::frontend::source_registry::{SourcePosition, SourceRegistry, SourceSlice}; +use crate::frontend::source_registry::{SourceRegistry, SourceSlice}; +use std::fmt; #[derive(Debug, Clone)] pub enum LoxError { @@ -24,6 +25,50 @@ pub enum LoxError { }, } +/// Configuration for error display formatting +#[derive(Clone, Debug)] +pub struct ErrorDisplayConfig { + /// Show colored output (for terminals that support it) + pub colored: bool, + /// Number of context lines to show before and after the error + pub context_lines: usize, + /// Show line numbers + pub show_line_numbers: bool, + /// Use Unicode characters for arrows and decorations + pub use_unicode: bool, +} + +impl Default for ErrorDisplayConfig { + fn default() -> Self { + Self { + colored: true, + context_lines: 2, + show_line_numbers: true, + use_unicode: true, + } + } +} + +impl ErrorDisplayConfig { + pub fn simple() -> Self { + Self { + colored: false, + context_lines: 1, + show_line_numbers: false, + use_unicode: false, + } + } + + pub fn verbose() -> Self { + Self { + colored: true, + context_lines: 3, + show_line_numbers: true, + use_unicode: true, + } + } +} + impl LoxError { pub fn get_message(&self) -> String { match self { @@ -49,88 +94,289 @@ impl LoxError { } } - pub fn print_with_context(&self, source_registry: &SourceRegistry) { - match self.get_source_slice() { - Some(source_slice) => { - let source_code = source_registry.get_source_code(&source_slice); - println!( - "\n =============================================== \n DATA:{} \n CODE \n {} | {}", - self, source_slice.start_position.line ,source_code - ); + pub fn error_kind(&self) -> &'static str { + match self { + LoxError::LexicalError { .. } => "lexical error", + LoxError::RuntimeError { .. } => "runtime error", + LoxError::ParseError { .. } => "parse error", + LoxError::IoError { .. } => "io error", + LoxError::TypeMismatch { .. } => "type error", + } + } + + /// Display error with source code context using default configuration + pub fn display_with_source(&self, source_registry: &SourceRegistry) -> String { + self.display_with_source_and_config(source_registry, &ErrorDisplayConfig::default()) + } + + /// Display error with source code context using custom configuration + pub fn display_with_source_and_config( + &self, + source_registry: &SourceRegistry, + config: &ErrorDisplayConfig, + ) -> String { + let mut output = String::new(); + + // Error header + let header = if config.colored { + format!( + "\x1b[1;31merror\x1b[0m: [{}] {}", + self.error_kind(), + self.get_message() + ) + } else { + format!("error: [{}] {}", self.error_kind(), self.get_message()) + }; + output.push_str(&header); + output.push('\n'); + + // If we have source location, show it + if let Some(source_slice) = self.get_source_slice() { + let source_info = source_registry.get_by_id(source_slice.source_id); + + // File location line + let location = if config.colored { + format!( + " \x1b[34m-->\x1b[0m {}:{}:{}", + source_info.file_name, + source_slice.start_position.line + 1, + source_slice.start_position.column + 1 + ) + } else { + format!( + " --> {}:{}:{}", + source_info.file_name, + source_slice.start_position.line + 1, + source_slice.start_position.column + 1 + ) + }; + output.push_str(&location); + output.push('\n'); + + // Source code context + if let Some(source_context) = + self.get_source_context(source_registry, &source_slice, config) + { + output.push_str(&source_context); } - None => println!("{}", self), } + + output + } + + fn get_source_context( + &self, + source_registry: &SourceRegistry, + source_slice: &SourceSlice, + config: &ErrorDisplayConfig, + ) -> Option { + let source_info = source_registry.get_by_id(source_slice.source_id); + let lines: Vec<&str> = source_info.content.lines().collect(); + + if lines.is_empty() || source_slice.start_position.line >= lines.len() { + return None; + } + + let mut output = String::new(); + let error_line = source_slice.start_position.line; + let start_line = error_line.saturating_sub(config.context_lines); + let end_line = (error_line + config.context_lines + 1).min(lines.len()); + + // Calculate the width needed for line numbers + let line_number_width = if config.show_line_numbers { + (end_line + 1).to_string().len() + } else { + 0 + }; + + // Show context lines + for line_idx in start_line..end_line { + let line_content = lines[line_idx]; + let line_number = line_idx + 1; + + if config.show_line_numbers { + let line_num_str = if config.colored { + if line_idx == error_line { + format!( + "\x1b[1;34m{:width$}\x1b[0m", + line_number, + width = line_number_width + ) + } else { + format!( + "\x1b[34m{:width$}\x1b[0m", + line_number, + width = line_number_width + ) + } + } else { + format!("{:width$}", line_number, width = line_number_width) + }; + + let separator = if config.colored { + if line_idx == error_line { + "\x1b[1;34m |\x1b[0m " + } else { + "\x1b[34m |\x1b[0m " + } + } else { + " | " + }; + + output.push_str(&format!( + " {}{}{}\n", + line_num_str, separator, line_content + )); + } else { + output.push_str(&format!(" {}\n", line_content)); + } + + // Add error indicator on the error line + if line_idx == error_line { + let spaces_before = + " ".repeat(3 + line_number_width + 3 + source_slice.start_position.column); + + let error_length = if source_slice.start_position.line + == source_slice.end_position.line + { + (source_slice.end_position.column - source_slice.start_position.column).max(1) + } else { + line_content.len() - source_slice.start_position.column + }; + + let indicator = if config.use_unicode { + "^".repeat(error_length) + } else { + "^".repeat(error_length) + }; + + let colored_indicator = if config.colored { + format!("\x1b[1;31m{}\x1b[0m", indicator) + } else { + indicator + }; + + // Add the indicator line + if config.show_line_numbers { + let empty_line_number = " ".repeat(line_number_width); + let separator = if config.colored { + "\x1b[34m |\x1b[0m " + } else { + " | " + }; + output.push_str(&format!( + " {}{}{}{}\n", + empty_line_number, + separator, + spaces_before[3 + line_number_width + 3..].to_string(), + colored_indicator + )); + } else { + output.push_str(&format!( + " {}{}\n", + spaces_before[3..].to_string(), + colored_indicator + )); + } + + // Add error type and additional context + let error_note = match self { + LoxError::TypeMismatch { + expected, found, .. + } => Some(format!("expected `{}`, found `{}`", expected, found)), + _ => None, + }; + + if let Some(note) = error_note { + let note_prefix = if config.show_line_numbers { + format!( + " {} {} ", + " ".repeat(line_number_width), + if config.colored { + "\x1b[34m|\x1b[0m" + } else { + "|" + } + ) + } else { + " ".to_string() + }; + + let colored_note = if config.colored { + format!("\x1b[1;36mnote\x1b[0m: {}", note) + } else { + format!("note: {}", note) + }; + + output.push_str(&format!("{}{}\n", note_prefix, colored_note)); + } + } + } + + Some(output) + } + + /// Print error with source context to stdout + pub fn print_with_source(&self, source_registry: &SourceRegistry) { + println!("{}", self.display_with_source(source_registry)); + } + + /// Print error with source context and custom config to stdout + pub fn print_with_source_and_config( + &self, + source_registry: &SourceRegistry, + config: &ErrorDisplayConfig, + ) { + println!( + "{}", + self.display_with_source_and_config(source_registry, config) + ); + } + + /// Legacy method for backwards compatibility + pub fn print_with_context(&self, source_registry: &SourceRegistry) { + self.print_with_source(source_registry); } } -fn fetch_source_code(path: &str, start: SourcePosition, end: SourcePosition) -> LoxResult { - let source_code = std::fs::read_to_string(path).map_err(|e| LoxError::IoError { - message: format!("Failed to read file: {}", e), - })?; - - let lines: Vec<&str> = source_code.split('\n').collect(); - - // Verifica bounds - if start.line >= lines.len() || end.line >= lines.len() { - return Err(LoxError::IoError { - message: "Line number out of bounds".to_string(), - }); - } - - let mut result = String::new(); - - if start.line == end.line { - // Caso speciale: stessa riga - let line = lines[start.line]; - if end.column <= line.len() && start.column <= end.column { - result.push_str(&line[start.column..end.column]); - } - } else { - // Caso generale: righe multiple - - // Prima riga: da start.column alla fine della riga - let first_line = lines[start.line]; - if start.column < first_line.len() { - result.push_str(&first_line[start.column..]); - } - result.push('\n'); - - // Righe intermedie: complete - for i in (start.line + 1)..end.line { - result.push_str(lines[i]); - result.push('\n'); - } - - // Ultima riga: dall'inizio fino a end.column - let last_line = lines[end.line]; - if end.column <= last_line.len() { - result.push_str(&last_line[..end.column]); - } - } - - Ok(result) -} - -impl std::fmt::Display for LoxError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for LoxError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { LoxError::LexicalError { source_slice, message, } => { - write!(f, "Lexical error on {:?}: \n {}", source_slice, message) + write!( + f, + "Lexical error at {}:{}: {}", + source_slice.start_position.line + 1, + source_slice.start_position.column + 1, + message + ) } LoxError::RuntimeError { source_slice, message, } => { - write!(f, "Runtime error on {:?}: \n{}", source_slice, message) + write!( + f, + "Runtime error at {}:{}: {}", + source_slice.start_position.line + 1, + source_slice.start_position.column + 1, + message + ) } LoxError::ParseError { source_slice, message, } => { - write!(f, "Parse error on {:?}: \n{}", source_slice, message) + write!( + f, + "Parse error at {}:{}: {}", + source_slice.start_position.line + 1, + source_slice.start_position.column + 1, + message + ) } LoxError::IoError { message } => write!(f, "IO error: {}", message), LoxError::TypeMismatch { @@ -140,8 +386,11 @@ impl std::fmt::Display for LoxError { } => { write!( f, - "Type mismatch on {:?}: \n expected {}, found {}", - source_slice, expected, found + "Type mismatch at {}:{}: expected `{}`, found `{}`", + source_slice.start_position.line + 1, + source_slice.start_position.column + 1, + expected, + found ) } } @@ -151,3 +400,47 @@ impl std::fmt::Display for LoxError { impl std::error::Error for LoxError {} pub type LoxResult = Result; + +/// Helper function to create a lexical error +pub fn lexical_error(source_slice: SourceSlice, message: impl Into) -> LoxError { + LoxError::LexicalError { + source_slice, + message: message.into(), + } +} + +/// Helper function to create a parse error +pub fn parse_error(source_slice: SourceSlice, message: impl Into) -> LoxError { + LoxError::ParseError { + source_slice, + message: message.into(), + } +} + +/// Helper function to create a runtime error +pub fn runtime_error(source_slice: SourceSlice, message: impl Into) -> LoxError { + LoxError::RuntimeError { + source_slice, + message: message.into(), + } +} + +/// Helper function to create a type mismatch error +pub fn type_mismatch_error( + source_slice: SourceSlice, + expected: impl Into, + found: impl Into, +) -> LoxError { + LoxError::TypeMismatch { + source_slice, + expected: expected.into(), + found: found.into(), + } +} + +/// Helper function to create an IO error +pub fn io_error(message: impl Into) -> LoxError { + LoxError::IoError { + message: message.into(), + } +} diff --git a/test_error.lox b/test_error.lox new file mode 100644 index 0000000..698ea42 --- /dev/null +++ b/test_error.lox @@ -0,0 +1,9 @@ +// Test script to verify error positioning +var a = 5; +var b = "hello"; +print 0; +var result = a + b; // This should cause a type error +print 1; +43 + "hello"; // This should cause a type error +print result; +print 46 + "world"; // This should cause a type error diff --git a/test_source_tracking.lox b/test_source_tracking.lox new file mode 100644 index 0000000..5fad933 --- /dev/null +++ b/test_source_tracking.lox @@ -0,0 +1,26 @@ +// Test file for source slice tracking +var x = 42; +print x; + +if x > 0 then do + print "positive"; + var y = x + 1; +end +elif x == 0 then do + print "zero"; +end +else do + print "negative"; +end + +while x > 0 do + x = x - 1; + print x; +end + +do + var z = 10; + print z * 2; +end + +return x + 5;