From 07cedc27bfc83951063f94bceb39b4a35faec69a Mon Sep 17 00:00:00 2001 From: Giulio Agostini Date: Fri, 10 Oct 2025 10:18:02 +0200 Subject: [PATCH 1/9] Add .idea to gitignore and prep return value types Add middleend module for AST return value tracking The changes introduce support for tracking return values and labels across AST nodes. The key changes include: - Adding ReturnValue type to BaseValue - Adding return_type field to LoxFunction - Adding label field to Block statements - Creating middleend crawler module for AST traversal Add return value types to AST This accurately captures both changes: adding `.idea/` to gitignore and adding return value types to the AST structure. The changes include adding return value fields to statement nodes and adjusting the interpreter logic accordingly. --- .gitignore | 2 + src/backend/interpreter.rs | 25 +++--- src/common/ast.rs | 180 ++++++++++++++++++++++++++++--------- src/common/base_value.rs | 10 +++ src/frontend/parser.rs | 15 +++- src/logging/display_ast.rs | 46 +++++++--- src/main.rs | 1 + src/middleend/crawler.rs | 115 ++++++++++++++++++++++++ src/middleend/mod.rs | 1 + tests/test_example.rs | 0 10 files changed, 331 insertions(+), 64 deletions(-) create mode 100644 src/middleend/crawler.rs create mode 100644 src/middleend/mod.rs create mode 100644 tests/test_example.rs diff --git a/.gitignore b/.gitignore index 4495404..91a8d77 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ rust-project.json # End of https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer .zed/ + +.idea/ diff --git a/src/backend/interpreter.rs b/src/backend/interpreter.rs index 235467f..3e811db 100644 --- a/src/backend/interpreter.rs +++ b/src/backend/interpreter.rs @@ -182,16 +182,17 @@ impl Interpreter { fn interpret_stmt_inner(&mut self, stmt: Stmt) -> LoxResult { match stmt { - Stmt::Expression { expression } => self.evaluate(*expression), - Stmt::Print { expression } => { + Stmt::Expression { expression, .. } => self.evaluate(*expression), + Stmt::Print { expression, .. } => { let value = self.evaluate(*expression)?; println!("{}", value); Ok(BaseValue::Nil) } - Stmt::Block { statements } => self.evaluate_block(*statements), - Stmt::Stmt { expression } => self.evaluate(*expression.clone()), - Stmt::Return { expression } => self.evaluate(*expression), - Stmt::VarDeclaration { name, initializer } => { + Stmt::Block { statements, .. } => self.evaluate_block(*statements), + Stmt::Return { expression, .. } => self.evaluate(*expression), + Stmt::VarDeclaration { + name, initializer, .. + } => { let value = match initializer { Some(expr_node) => match &expr_node.node { Expr::Literal { value } => { @@ -208,7 +209,7 @@ impl Interpreter { self.enviorment.declare(name.clone(), value) } - Stmt::VarAssigment { name, value } => { + Stmt::VarAssigment { name, value, .. } => { let result = self.evaluate(*value)?; self.enviorment.set(name.clone(), result) } @@ -217,8 +218,11 @@ impl Interpreter { then_branch, elif_branch, else_branch, + .. } => self.evaluate_if(condition, then_branch, elif_branch, else_branch), - Stmt::While { condition, body } => { + Stmt::While { + condition, body, .. + } => { while self.evaluate(*condition.clone())?.is_truthy() { self.evaluate(*body.clone())?; } @@ -229,6 +233,7 @@ impl Interpreter { condition, increment, body, + .. } => { self.enviorment.push_new_scope(); let source_slice = variable.source_slice.clone(); @@ -291,8 +296,8 @@ impl Interpreter { self.enviorment.push_new_scope(); let (elements, final_expr) = match statements.split_last() { Some((stmt, body)) => match &stmt.node { - Stmt::Expression { expression } => (body, Some(expression)), - Stmt::Return { expression } => (body, Some(expression)), + Stmt::Expression { expression, .. } => (body, Some(expression)), + Stmt::Return { expression, .. } => (body, Some(expression)), _ => (statements.as_slice(), None), }, diff --git a/src/common/ast.rs b/src/common/ast.rs index 0127b79..adc10b5 100644 --- a/src/common/ast.rs +++ b/src/common/ast.rs @@ -120,96 +120,142 @@ impl Debug for Expr { pub enum Stmt { Expression { expression: Box>, + return_value: Box, }, Print { expression: Box>, - }, - Stmt { - expression: Box>, + return_value: Box, }, VarDeclaration { name: String, initializer: Option>>, + return_value: Box, }, VarAssigment { name: String, value: Box>, + return_value: Box, }, Return { expression: Box>, + return_value: Box, }, Block { statements: Box>>, + label: String, + return_value: Box, }, If { condition: Box>, then_branch: Box>, elif_branch: Vec<(Box>, Box>)>, else_branch: Option>>, + return_value: Box, }, While { condition: Box>, body: Box>, + return_value: Box, }, For { variable: Box>, condition: Box>, increment: Box>, body: Box>, + return_value: 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.node), + Stmt::Expression { + expression, + return_value, + } => write!(f, "Expression ({}) -> {:?}", expression.node, return_value), Stmt::If { condition, then_branch, elif_branch, else_branch, + return_value, } => { 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.node, branch.node + " ELIF ({}) {{\n{}\n}} -> {:?}", + condition.node, branch.node, return_value )); } if let Some(else_branch) = else_branch { - result.push_str(&format!(" ELSE {{\n{}\n}}", else_branch.node)); + result.push_str(&format!( + " ELSE {{\n{}\n}} -> {:?}", + else_branch.node, return_value + )); } write!(f, "IfStmt {}", result) } - Stmt::Print { expression } => write!(f, "Print({});", expression.node), - Stmt::Stmt { expression } => write!(f, "Stmt({});", expression.node), - Stmt::VarDeclaration { name, initializer } => match initializer { - Some(init) => write!(f, "Var({} = {});", name, init.node), - None => write!(f, "Var({});", name), + Stmt::Print { + expression, + return_value, + } => write!(f, "Print({}) -> {:?};", expression.node, return_value), + Stmt::VarDeclaration { + name, + initializer, + return_value, + } => match initializer { + Some(init) => write!(f, "Var({} = {}) -> {:?};", name, init.node, return_value), + None => write!(f, "Var({}) -> {:?};", name, return_value), }, - Stmt::VarAssigment { name, value } => write!(f, "Assign({} = {});", name, value.node), - Stmt::Return { expression } => write!(f, "Return({});", expression.node), - Stmt::Block { statements } => write!( + Stmt::VarAssigment { + name, + value, + return_value, + } => write!( f, - "Block([\n{}\n])", + "Assign({} = {}) -> {:?};", + name, value.node, return_value + ), + Stmt::Return { + expression, + return_value, + } => write!(f, "Return({}) -> {:?};", expression.node, return_value), + Stmt::Block { + statements, + label, + return_value, + } => write!( + f, + "Block [{}] ([\n{}\n]) -> {:?}", + label, statements .iter() .map(|stmt| format!("\t \t{}", stmt.node)) .collect::>() - .join("\n") + .join("\n"), + return_value ), - Stmt::While { condition, body } => { - write!(f, "While({}) {{\n{}\n}}", condition.node, body.node) + Stmt::While { + condition, + body, + return_value, + } => { + write!( + f, + "While({}) {{\n{}\n}} -> {:?}", + condition.node, body.node, return_value + ) } Stmt::For { variable, condition, increment, body, + return_value, } => write!( f, - "For({} = {} in {}) {{\n{}\n}}", - variable.node, condition.node, increment.node, body.node + "For({} = {} in {}) {{\n{}\n}} -> {:?}", + variable.node, condition.node, increment.node, body.node, return_value ), } } @@ -218,57 +264,106 @@ 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.node), + Stmt::Expression { + expression, + return_value, + } => write!( + f, + " Expression ({:?}) -> {:?}", + expression.node, return_value + ), Stmt::If { condition, then_branch, elif_branch, else_branch, + return_value, } => { - let mut result = - format!("IF ({:?}) {{\n{:?}\n}}", condition.node, then_branch.node); + let mut result = format!( + "IF ({:?}) {{\n{:?}\n}} -> {:?} ", + condition.node, then_branch.node, return_value + ); for (condition, branch) in elif_branch { result.push_str(&format!( - " ELIF ({:?}) {{\n{:?}\n}}", - condition.node, branch.node + " ELIF ({:?}) {{\n{:?}\n}} -> {:?} ", + condition.node, branch.node, return_value )); } if let Some(else_branch) = else_branch { - result.push_str(&format!(" ELSE {{\n{:?}\n}}", else_branch.node)); + result.push_str(&format!( + " ELSE {{\n{:?}\n}} -> {:?}", + else_branch.node, return_value + )); } write!(f, "IfStmt {:?}", result) } - Stmt::Print { expression } => write!(f, "Print({:?});", expression.node), - Stmt::Stmt { expression } => write!(f, "Stmt({:?});", expression.node), - Stmt::VarDeclaration { name, initializer } => match initializer { - Some(init) => write!(f, "Var({:?} = {:?});", name, init.node), - None => write!(f, "Var({:?});", name), + Stmt::Print { + expression, + return_value, + } => write!(f, "Print({:?}) -> {:?};", expression.node, return_value), + Stmt::VarDeclaration { + name, + initializer, + return_value, + } => match initializer { + Some(init) => write!( + f, + "Var({:?} = {:?}) -> {:?};", + name, init.node, return_value + ), + None => write!(f, "Var({:?}) -> {:?};", name, return_value), }, - Stmt::VarAssigment { name, value } => { - write!(f, "Assign({:?} = {:?});", name, value.node) + Stmt::VarAssigment { + name, + value, + return_value, + } => { + write!( + f, + "Assign({:?} = {:?}) -> {:?};", + name, value.node, return_value + ) } - Stmt::Return { expression } => write!(f, "Return({:?});", expression.node), - Stmt::Block { statements } => write!( + Stmt::Return { + expression, + return_value, + } => write!(f, "Return({:?}) -> {:?};", expression.node, return_value), + Stmt::Block { + label, + statements, + return_value, + } => write!( f, - "Block([\n{:?}\n])", + "Block [{}] ([\n{:?}\n]) -> {:?}", + label, statements .iter() .map(|stmt| format!("{:?}", stmt.node)) .collect::>() - .join("\t\t\n") + .join("\t\t\n"), + return_value ), - Stmt::While { condition, body } => { - write!(f, "While({:?}) {{\n{:?}\n}}", condition.node, body.node) + Stmt::While { + condition, + body, + return_value, + } => { + write!( + f, + "While({:?}) {{\n{:?}\n}} -> {:?}", + condition.node, body.node, return_value + ) } Stmt::For { variable, condition, increment, body, + return_value, } => write!( f, - "For({:?} = {:?} in {:?}) {{\n{:?}\n}}", - variable.node, condition.node, increment.node, body.node + "For({:?} = {:?} in {:?}) {{\n{:?}\n}} -> {:?}", + variable.node, condition.node, increment.node, body.node, return_value ), } } @@ -305,7 +400,6 @@ impl AstNodeKind for Stmt { Stmt::Block { .. } => "block statement", Stmt::If { .. } => "if statement", Stmt::Print { .. } => "print statement", - Stmt::Stmt { .. } => "statement", Stmt::While { .. } => "while statement", Stmt::For { .. } => "for statement", } diff --git a/src/common/base_value.rs b/src/common/base_value.rs index 9ae8024..98191f9 100644 --- a/src/common/base_value.rs +++ b/src/common/base_value.rs @@ -271,6 +271,10 @@ pub enum BaseValue { Nil, Function(LoxFunction), NativeFunction(NativeFunction), + Return { + value: Box, + labels: Vec, + }, } impl BaseValue { @@ -292,6 +296,9 @@ impl Display for BaseValue { BaseValue::Nil => write!(f, "nil"), BaseValue::Function(..) => write!(f, ""), BaseValue::NativeFunction(..) => write!(f, ""), + BaseValue::Return { labels, .. } => { + write!(f, "", labels.join(":")) + } } } } @@ -299,6 +306,7 @@ impl Display for BaseValue { #[derive(Debug, Clone, PartialEq)] pub struct LoxFunction { pub parameters: Vec<(String, String)>, + pub return_type: Option, pub body: AstNode, pub closure: Option, pub guard: Option>, @@ -313,6 +321,7 @@ impl LoxFunction { ) -> Self { LoxFunction { parameters, + return_type: None, body, closure, guard, @@ -322,6 +331,7 @@ impl LoxFunction { pub fn anonymous_function(parameters: Vec<(String, String)>, body: AstNode) -> Self { LoxFunction { parameters, + return_type: None, body, closure: None, guard: None, diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs index bb705f8..5dee948 100644 --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -87,6 +87,7 @@ impl Parser { condition: Box::new(condition), increment: Box::new(increment), body: Box::new(body), + return_value: Box::new(BaseValue::Nil), }, combined_slice, )) @@ -149,6 +150,7 @@ impl Parser { Stmt::While { condition: Box::new(condition), body: Box::new(body), + return_value: Box::new(BaseValue::Nil), }, combined_slice, )) @@ -193,6 +195,7 @@ impl Parser { then_branch: Box::new(then_branch), elif_branch: elif_branches, else_branch: else_branch, + return_value: Box::new(BaseValue::Nil), }, combined_slice, )) @@ -261,6 +264,7 @@ impl Parser { node: Expr::Literal { value: BaseValue::Function(LoxFunction { parameters, + return_type: None, body, closure: None, guard, @@ -272,6 +276,7 @@ impl Parser { Stmt::VarDeclaration { name: name_lexeme, initializer: Some(Box::new(node)), + return_value: Box::new(BaseValue::Nil), }, combine_position.clone(), )) @@ -313,6 +318,7 @@ impl Parser { Stmt::VarDeclaration { name: name_lexeme, initializer: Some(Box::new(value)), + return_value: Box::new(BaseValue::Nil), }, combined_slice, )) @@ -337,6 +343,7 @@ impl Parser { Stmt::VarAssigment { name: name_lexeme, value: Box::new(value), + return_value: Box::new(BaseValue::Nil), }, combined_slice, )) @@ -357,6 +364,7 @@ impl Parser { Ok(AstNode::new( Stmt::Print { expression: Box::new(expr), + return_value: Box::new(BaseValue::Nil), }, combined_slice, )) @@ -384,6 +392,8 @@ impl Parser { Ok(AstNode::new( Stmt::Block { statements: Box::new(statements), + label: String::default(), + return_value: Box::new(BaseValue::Nil), }, combined_slice, )) @@ -401,8 +411,9 @@ impl Parser { end_slice.end_position, ); return Ok(AstNode::new( - Stmt::Stmt { + Stmt::Expression { expression: Box::new(expr), + return_value: Box::new(BaseValue::Nil), }, combined_slice, )); @@ -412,6 +423,7 @@ impl Parser { Ok(AstNode::new( Stmt::Expression { expression: Box::new(expr), + return_value: Box::new(BaseValue::Nil), }, expr_slice, )) @@ -431,6 +443,7 @@ impl Parser { Ok(AstNode::new( Stmt::Return { expression: Box::new(expr), + return_value: Box::new(BaseValue::Nil), }, combined_slice, )) diff --git a/src/logging/display_ast.rs b/src/logging/display_ast.rs index da837e1..8f93d95 100644 --- a/src/logging/display_ast.rs +++ b/src/logging/display_ast.rs @@ -263,7 +263,10 @@ impl PrettyPrint for Stmt { let indent = ctx.indent(); match self { - Stmt::Expression { expression } => { + Stmt::Expression { + expression, + return_value, + } => { if ctx.config.compact { let expr_str = pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); @@ -276,7 +279,10 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Print { expression } => { + Stmt::Print { + expression, + return_value, + } => { if ctx.config.compact { let expr_str = pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); @@ -289,7 +295,11 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::VarDeclaration { name, initializer } => { + Stmt::VarDeclaration { + name, + initializer, + return_value, + } => { if ctx.config.compact { match initializer { Some(init) => { @@ -315,7 +325,11 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::VarAssigment { name, value } => { + Stmt::VarAssigment { + name, + value, + return_value, + } => { if ctx.config.compact { let value_str = pretty_print_with_config(value.as_ref(), &PrettyConfig::compact()); @@ -329,7 +343,10 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Return { expression } => { + Stmt::Return { + expression, + return_value, + } => { if ctx.config.compact { let expr_str = pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); @@ -342,11 +359,15 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Block { statements } => { + Stmt::Block { + statements, + label, + return_value, + } => { if ctx.config.compact { - write!(f, "{{ ... ({} statements) }}", statements.len()) + write!(f, "{{ ... [{}] ({} statements) }}", label, statements.len()) } else { - writeln!(f, "{}Block {{", indent)?; + writeln!(f, "{}Block [{}] {{", label, indent)?; writeln!(f, "{}statements: [", ctx.child_context(false).indent())?; for (i, stmt) in statements.iter().enumerate() { let is_last = i == statements.len() - 1; @@ -366,6 +387,7 @@ impl PrettyPrint for Stmt { then_branch, elif_branch, else_branch, + return_value, } => { if ctx.config.compact { let cond_str = @@ -415,8 +437,11 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Stmt { expression } => expression.pretty_print(ctx, f), - Stmt::While { condition, body } => { + Stmt::While { + condition, + body, + return_value, + } => { write!(f, "{}while (", ctx.child_context(true).indent())?; condition.pretty_print(&ctx.child_context(true), f)?; writeln!(f, ") {{")?; @@ -428,6 +453,7 @@ impl PrettyPrint for Stmt { condition, increment, body, + return_value, } => { write!(f, "{}for (", ctx.child_context(true).indent())?; variable.pretty_print(&ctx.child_context(true), f)?; diff --git a/src/main.rs b/src/main.rs index 98e2653..d10766d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod backend; mod common; mod frontend; mod logging; +mod middleend; use crate::{ backend::interpreter::{EvaluateInterpreter, Interpreter}, diff --git a/src/middleend/crawler.rs b/src/middleend/crawler.rs new file mode 100644 index 0000000..8feaa2f --- /dev/null +++ b/src/middleend/crawler.rs @@ -0,0 +1,115 @@ +use crate::common::{ + ast::{AstNode, Expr, Stmt}, + lox_result::LoxResult, +}; + +// Enum per tutti i possibili target di operazioni +enum OperationTarget<'a> { + Stmt(&'a mut Stmt), + Expr(&'a mut Expr), + AstStmt(&'a mut AstNode), + AstExpr(&'a mut AstNode), +} + +// Trait per le operazioni +trait Operation: Send + Sync { + fn execute(&self, target: OperationTarget) -> LoxResult<()>; +} + +// Operazione concreta per set_label +struct SetLabelOperation { + new_label: String, +} + +impl SetLabelOperation { + fn new(label: String) -> Self { + Self { new_label: label } + } +} + +impl Operation for SetLabelOperation { + fn execute(&self, target: OperationTarget) -> LoxResult<()> { + match target { + OperationTarget::Stmt(stmt) => { + if let Stmt::Block { label, .. } = stmt { + *label = self.new_label.clone(); + } + } + OperationTarget::AstStmt(node) => { + if let Stmt::Block { label, .. } = &mut node.node { + *label = self.new_label.clone(); + } + } + _ => {} // Expr non hanno label + } + Ok(()) + } +} + +// Trait per convertire tipi in OperationTarget +trait AsOperationTarget { + fn as_target(&mut self) -> OperationTarget; +} + +impl AsOperationTarget for Stmt { + fn as_target(&mut self) -> OperationTarget { + OperationTarget::Stmt(self) + } +} + +impl AsOperationTarget for Expr { + fn as_target(&mut self) -> OperationTarget { + OperationTarget::Expr(self) + } +} + +impl AsOperationTarget for AstNode { + fn as_target(&mut self) -> OperationTarget { + OperationTarget::AstStmt(self) + } +} + +impl AsOperationTarget for AstNode { + fn as_target(&mut self) -> OperationTarget { + OperationTarget::AstExpr(self) + } +} + +// Crawler generico con Command Pattern +struct Crawler { + node: T, + operations: Vec>, +} + +impl Crawler { + fn new(node: T) -> Self { + Self { + node, + operations: vec![], + } + } + + fn add_operation(&mut self, op: Box) { + self.operations.push(op); + } + + fn execute_operations(&mut self) -> LoxResult<()> { + for op in &self.operations { + op.execute(self.node.as_target())?; + } + Ok(()) + } + + fn with_operation(mut self, op: Box) -> Self { + self.operations.push(op); + self + } + + fn get_node(&self) -> &T { + &self.node + } + + fn get_node_mut(&mut self) -> &mut T { + &mut self.node + } +} diff --git a/src/middleend/mod.rs b/src/middleend/mod.rs new file mode 100644 index 0000000..145a637 --- /dev/null +++ b/src/middleend/mod.rs @@ -0,0 +1 @@ +pub mod crawler; diff --git a/tests/test_example.rs b/tests/test_example.rs new file mode 100644 index 0000000..e69de29 From ee279425f2272006b49951af7b06bb566ceb7a30 Mon Sep 17 00:00:00 2001 From: Giulio Agostini Date: Wed, 12 Nov 2025 20:45:35 +0100 Subject: [PATCH 2/9] wip --- examples/{test_error.lox => fail_error.lox} | 6 +- examples/{example.lox => test_example.lox} | 0 ...nction_call.lox => test_function_call.lox} | 0 examples/test_source_tracking.lox | 6 +- src/common/base_value.rs | 18 +- src/common/lox_result.rs | 9 +- src/frontend/source_registry.rs | 2 +- src/logging/display_ast.rs | 37 +--- tests/test_example.rs | 207 ++++++++++++++++++ 9 files changed, 240 insertions(+), 45 deletions(-) rename examples/{test_error.lox => fail_error.lox} (67%) rename examples/{example.lox => test_example.lox} (100%) rename examples/{function_call.lox => test_function_call.lox} (100%) diff --git a/examples/test_error.lox b/examples/fail_error.lox similarity index 67% rename from examples/test_error.lox rename to examples/fail_error.lox index 698ea42..101823b 100644 --- a/examples/test_error.lox +++ b/examples/fail_error.lox @@ -1,8 +1,8 @@ // Test script to verify error positioning -var a = 5; -var b = "hello"; +var a := 5; +var b := "hello"; print 0; -var result = a + b; // This should cause a type error +var result := a + b; // This should cause a type error print 1; 43 + "hello"; // This should cause a type error print result; diff --git a/examples/example.lox b/examples/test_example.lox similarity index 100% rename from examples/example.lox rename to examples/test_example.lox diff --git a/examples/function_call.lox b/examples/test_function_call.lox similarity index 100% rename from examples/function_call.lox rename to examples/test_function_call.lox diff --git a/examples/test_source_tracking.lox b/examples/test_source_tracking.lox index 5fad933..5f879de 100644 --- a/examples/test_source_tracking.lox +++ b/examples/test_source_tracking.lox @@ -1,10 +1,10 @@ // Test file for source slice tracking -var x = 42; +var x := 42; print x; if x > 0 then do print "positive"; - var y = x + 1; + var y := x + 1; end elif x == 0 then do print "zero"; @@ -19,7 +19,7 @@ while x > 0 do end do - var z = 10; + var z := 10; print z * 2; end diff --git a/src/common/base_value.rs b/src/common/base_value.rs index 98191f9..6eb2976 100644 --- a/src/common/base_value.rs +++ b/src/common/base_value.rs @@ -271,10 +271,17 @@ pub enum BaseValue { Nil, Function(LoxFunction), NativeFunction(NativeFunction), - Return { - value: Box, - labels: Vec, - }, +} + +struct ReturnValue { + label: Vec, + value: BaseValue, +} + +impl ReturnValue { + pub fn new(label: Vec, value: BaseValue) -> Self { + Self { label, value } + } } impl BaseValue { @@ -296,9 +303,6 @@ impl Display for BaseValue { BaseValue::Nil => write!(f, "nil"), BaseValue::Function(..) => write!(f, ""), BaseValue::NativeFunction(..) => write!(f, ""), - BaseValue::Return { labels, .. } => { - write!(f, "", labels.join(":")) - } } } } diff --git a/src/common/lox_result.rs b/src/common/lox_result.rs index 9f0ec6f..8ed8f7d 100644 --- a/src/common/lox_result.rs +++ b/src/common/lox_result.rs @@ -1,5 +1,8 @@ -use crate::frontend::source_registry::{SourceRegistry, SourceSlice}; -use std::fmt; +use crate::{ + common::base_value::BaseValue, + frontend::source_registry::{SourceRegistry, SourceSlice}, +}; +use std::{error::Error, fmt}; #[derive(Debug, Clone)] pub enum LoxError { @@ -397,7 +400,7 @@ impl fmt::Display for LoxError { } } -impl std::error::Error for LoxError {} +impl Error for LoxError {} pub type LoxResult = Result; diff --git a/src/frontend/source_registry.rs b/src/frontend/source_registry.rs index 3bdaa25..27ae1ef 100644 --- a/src/frontend/source_registry.rs +++ b/src/frontend/source_registry.rs @@ -4,7 +4,7 @@ use std::{ io::ErrorKind, }; -use crate::common::lox_result::{io_error, LoxError, LoxResult}; +use crate::common::lox_result::{io_error, LoxResult}; pub struct SourceRegistry { pub sources: Vec, diff --git a/src/logging/display_ast.rs b/src/logging/display_ast.rs index 8f93d95..1418c50 100644 --- a/src/logging/display_ast.rs +++ b/src/logging/display_ast.rs @@ -263,10 +263,7 @@ impl PrettyPrint for Stmt { let indent = ctx.indent(); match self { - Stmt::Expression { - expression, - return_value, - } => { + Stmt::Expression { expression, .. } => { if ctx.config.compact { let expr_str = pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); @@ -279,10 +276,7 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Print { - expression, - return_value, - } => { + Stmt::Print { expression, .. } => { if ctx.config.compact { let expr_str = pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); @@ -296,9 +290,7 @@ impl PrettyPrint for Stmt { } } Stmt::VarDeclaration { - name, - initializer, - return_value, + name, initializer, .. } => { if ctx.config.compact { match initializer { @@ -325,11 +317,7 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::VarAssigment { - name, - value, - return_value, - } => { + Stmt::VarAssigment { name, value, .. } => { if ctx.config.compact { let value_str = pretty_print_with_config(value.as_ref(), &PrettyConfig::compact()); @@ -343,10 +331,7 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Return { - expression, - return_value, - } => { + Stmt::Return { expression, .. } => { if ctx.config.compact { let expr_str = pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); @@ -360,9 +345,7 @@ impl PrettyPrint for Stmt { } } Stmt::Block { - statements, - label, - return_value, + statements, label, .. } => { if ctx.config.compact { write!(f, "{{ ... [{}] ({} statements) }}", label, statements.len()) @@ -387,7 +370,7 @@ impl PrettyPrint for Stmt { then_branch, elif_branch, else_branch, - return_value, + .. } => { if ctx.config.compact { let cond_str = @@ -438,9 +421,7 @@ impl PrettyPrint for Stmt { } } Stmt::While { - condition, - body, - return_value, + condition, body, .. } => { write!(f, "{}while (", ctx.child_context(true).indent())?; condition.pretty_print(&ctx.child_context(true), f)?; @@ -453,7 +434,7 @@ impl PrettyPrint for Stmt { condition, increment, body, - return_value, + .. } => { write!(f, "{}for (", ctx.child_context(true).indent())?; variable.pretty_print(&ctx.child_context(true), f)?; diff --git a/tests/test_example.rs b/tests/test_example.rs index e69de29..8dc261f 100644 --- a/tests/test_example.rs +++ b/tests/test_example.rs @@ -0,0 +1,207 @@ +use rlox::backend::interpreter::{EvaluateInterpreter, Interpreter}; +use rlox::frontend::lexer::Lexer; +use rlox::frontend::parser::Parser; +use rlox::frontend::source_registry::SourceRegistry; +use std::fs; +use std::path::Path; + +#[test] +fn test_all_examples() { + let examples_dir = Path::new("examples"); + + // Trova tutti i file .lox nella directory examples + let entries = fs::read_dir(examples_dir).expect("Failed to read examples directory"); + + let mut test_files_passed = Vec::new(); + let mut test_files_failed = Vec::new(); + let mut fail_files_passed = Vec::new(); + let mut fail_files_failed = Vec::new(); + + for entry in entries { + let entry = entry.expect("Failed to read directory entry"); + let path = entry.path(); + + // Controlla se รจ un file .lox + if path.extension().and_then(|s| s.to_str()) == Some("lox") { + let file_name = path.file_name().unwrap().to_str().unwrap().to_string(); + + // Ignora i file che non iniziano con test_ o fail_ + if !file_name.starts_with("test_") && !file_name.starts_with("fail_") { + continue; + } + + // Leggi il contenuto del file + let source = match fs::read_to_string(&path) { + Ok(content) => content, + Err(e) => { + if file_name.starts_with("test_") { + test_files_failed.push((file_name, format!("Failed to read file: {}", e))); + } else { + fail_files_failed.push((file_name, format!("Failed to read file: {}", e))); + } + continue; + } + }; + + // Esegui il file e categorizza il risultato + match run_lox_file(source, &file_name) { + Ok(_) => { + if file_name.starts_with("test_") { + test_files_passed.push(file_name); + } else { + fail_files_passed.push(file_name); + } + } + Err(error) => { + if file_name.starts_with("test_") { + test_files_failed.push((file_name, error)); + } else { + fail_files_failed.push((file_name, error)); + } + } + } + } + } + + // Stampa i risultati + println!("\n========== TEST RESULTS =========="); + + // File test_ (dovrebbero passare) + if !test_files_passed.is_empty() || !test_files_failed.is_empty() { + println!("\n๐Ÿ“ TEST FILES (should pass):"); + if !test_files_passed.is_empty() { + println!(" โœ… Passed ({}):", test_files_passed.len()); + for file in &test_files_passed { + println!(" - {}", file); + } + } + if !test_files_failed.is_empty() { + println!(" โŒ Failed ({}):", test_files_failed.len()); + for (file, error) in &test_files_failed { + println!("\n - {}", file); + println!("{}", error); + } + } + } + + // File fail_ (dovrebbero fallire) + if !fail_files_failed.is_empty() || !fail_files_passed.is_empty() { + println!("\n๐Ÿšซ FAIL FILES (should fail):"); + if !fail_files_failed.is_empty() { + println!(" โœ… Failed as expected ({}):", fail_files_failed.len()); + for (file, error) in &fail_files_failed { + println!("\n - {}", file); + println!("{}", error); + } + } + if !fail_files_passed.is_empty() { + println!(" โŒ Passed unexpectedly ({}):", fail_files_passed.len()); + for file in &fail_files_passed { + println!(" - {} (should have failed but passed!)", file); + } + } + } + + println!("\n=================================="); + + // Riepilogo + let total_test_files = test_files_passed.len() + test_files_failed.len(); + let total_fail_files = fail_files_passed.len() + fail_files_failed.len(); + let total_files = total_test_files + total_fail_files; + + println!("Summary:"); + if total_test_files > 0 { + println!( + " test_ files: {}/{} passed", + test_files_passed.len(), + total_test_files + ); + } + if total_fail_files > 0 { + println!( + " fail_ files: {}/{} failed (as expected)", + fail_files_failed.len(), + total_fail_files + ); + } + println!(" Total files tested: {}", total_files); + + // Assicurati che almeno un file sia stato testato + assert!( + total_files > 0, + "No test_ or fail_ .lox files found in examples directory" + ); + + // Asserzioni per far fallire il test se le aspettative non sono rispettate + if !test_files_failed.is_empty() { + println!("\nโŒ TEST FAILURE: The following test_ files failed but should have passed:"); + for (file, _) in &test_files_failed { + println!(" - {}", file); + } + panic!( + "{} test_ files failed unexpectedly", + test_files_failed.len() + ); + } + + if !fail_files_passed.is_empty() { + println!("\nโŒ TEST FAILURE: The following fail_ files passed but should have failed:"); + for file in &fail_files_passed { + println!(" - {}", file); + } + panic!( + "{} fail_ files passed unexpectedly", + fail_files_passed.len() + ); + } + + println!( + "\nโœ… All {} test/fail files behaved as expected!", + total_files + ); +} + +fn run_lox_file(source: String, _filename: &str) -> Result<(), String> { + // Crea un source registry e mantienilo per il pretty printing degli errori + let mut source_registry = SourceRegistry::new(); + + // Aggiungi il source con il nome del file corretto + let source_id = match source_registry.add_source_string(source.clone()) { + Ok(id) => id, + Err(e) => return Err(format!("Failed to add source: {}", e)), + }; + + // Tokenizza + let source_content = source_registry.get_by_id(source_id).content.clone(); + let mut lexer = Lexer::new(source_content, source_id); + let tokens = match lexer.scans_tokens() { + Ok(tokens) => tokens, + Err(err) => { + // Usa il pretty printing con source context + return Err(err.display_with_source(&source_registry)); + } + }; + + // Parsa + let ast = match Parser::new(tokens).parse() { + Ok(ast) => ast, + Err(err) => { + // Usa il pretty printing con source context + return Err(err.display_with_source(&source_registry)); + } + }; + + // Esegui + let mut interpreter = Interpreter::new(); + for stmt in ast.iter() { + match interpreter.evaluate(stmt.clone()) { + Ok(_) => {} + Err(err) => { + // Usa il pretty printing con source context + return Err(err.display_with_source(&source_registry)); + } + } + } + + Ok(()) +} From 4e2645e12dfde7bf5ebcca0b4c997951902132b7 Mon Sep 17 00:00:00 2001 From: Giulio Agostini Date: Wed, 11 Feb 2026 16:35:05 +0100 Subject: [PATCH 3/9] closure --- README.md | 0 examples/fail_error.lox | 1 + examples/test_closure.lox | 12 ++++ examples/test_example.lox | 21 +++--- examples/test_function_call.lox | 14 +++- src/backend/interpreter.rs | 123 +++++++++++++++++++------------- src/common/ast.rs | 15 +++- src/common/lox_result.rs | 32 +++++++++ src/frontend/lexer.rs | 1 + src/frontend/parser.rs | 72 +++++++++++++------ src/frontend/tokens.rs | 2 + src/logging/display_token.rs | 2 + src/main.rs | 110 +++++----------------------- 13 files changed, 229 insertions(+), 176 deletions(-) create mode 100644 README.md create mode 100644 examples/test_closure.lox diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/examples/fail_error.lox b/examples/fail_error.lox index 101823b..4bd04c0 100644 --- a/examples/fail_error.lox +++ b/examples/fail_error.lox @@ -1,4 +1,5 @@ // Test script to verify error positioning +"no"(); var a := 5; var b := "hello"; print 0; diff --git a/examples/test_closure.lox b/examples/test_closure.lox new file mode 100644 index 0000000..2e288c0 --- /dev/null +++ b/examples/test_closure.lox @@ -0,0 +1,12 @@ + +closure :: fn() do + value := "Closure"; + internal :: fn() do + print value; + return value; + end + return internal; +end + +internal := closure(); +internal(); diff --git a/examples/test_example.lox b/examples/test_example.lox index b9a3c8b..a1e87e2 100644 --- a/examples/test_example.lox +++ b/examples/test_example.lox @@ -6,23 +6,26 @@ //func_name :: fn(param: Number, param2: String) {param is Int} do func_name :: fn(param: Int, param2: String) do print param; - cavallo := 5; - print cavallo; - while cavallo < 10 do - cavallo = cavallo + 1; - print cavallo; + print param2; + while param < param2 do + param = param + 1; + print param; + print param2; + //break; end - if True then do + if False then do print "this shoul be stop"; - return False; + return "Hahahaha"; end print "this shoud be un other stop"; - return 42; + //return 42; for i := 11; i <= 21; i = i + 1; do print i; + print "hello"; + break; end return False or True; end -func_name(1,2) +func_name(1,20) diff --git a/examples/test_function_call.lox b/examples/test_function_call.lox index 663d520..aa045ee 100644 --- a/examples/test_function_call.lox +++ b/examples/test_function_call.lox @@ -11,10 +11,20 @@ end function_fatcoty_factory :: fn() do print "Function Factory Factory"; - function_factory + return function_factory; end //function(); //function_factory()(); -function_fatcoty_factory()()(); +//function_fatcoty_factory()()(); clock() + +closure :: fn() do + value = "Closure"; + internal :: fn() do + print value; + end + return internal; +end + +closure() diff --git a/src/backend/interpreter.rs b/src/backend/interpreter.rs index 3e811db..360d8ec 100644 --- a/src/backend/interpreter.rs +++ b/src/backend/interpreter.rs @@ -33,7 +33,15 @@ where impl EvaluateInterpreter for Interpreter { fn evaluate(&mut self, expr: Expr) -> LoxResult { match expr { - Expr::Literal { value } => Ok(value), + Expr::Literal { value } => match value { + BaseValue::Function(mut func) => { + func.closure = Some(self.enviorment.clone()); + println!("Closure created"); + println!("current enviorment: {:?}", self.enviorment); + Ok(BaseValue::Function(func)) + } + _ => Ok(value), + }, Expr::Identifier { name } => self.enviorment.get(&name), Expr::Binary { left, @@ -120,15 +128,34 @@ impl Interpreter { match function { BaseValue::NativeFunction(func) => Ok((func.function)(&evaluated_arguments)), BaseValue::Function(func) => { - func.parameters - .iter() - .enumerate() - .map(|(index, par)| { - let value = evaluated_arguments.get(index).unwrap(); - self.enviorment.declare(par.0.clone(), value.clone()) - }) - .collect::>>()?; - self.evaluate(func.body) + // Save the current environment + let saved_env = self.enviorment.clone(); + + // If the function captured a closure, use it as the base environment + if let Some(closure_env) = func.closure { + self.enviorment = closure_env; + } + + // Push a new scope for the function's parameters + self.enviorment.push_new_scope(); + + // Declare parameters in the new scope + for (index, par) in func.parameters.iter().enumerate() { + let value = evaluated_arguments.get(index).unwrap(); + self.enviorment.declare(par.0.clone(), value.clone())?; + } + + // Execute the function body + let result = match self.evaluate(func.body) { + Ok(value) => Ok(value), + Err(LoxError::Return { value, .. }) => Ok(value), + Err(err) => Err(err), + }; + + // Restore the original environment (cleanup is automatic) + self.enviorment = saved_env; + + result } _ => runtime_error( source_slice.clone(), @@ -194,21 +221,11 @@ impl Interpreter { name, initializer, .. } => { let value = match initializer { - Some(expr_node) => match &expr_node.node { - Expr::Literal { value } => { - if value.is_callable() { - value.clone() - } else { - value.clone() - } - } - _ => self.evaluate(*expr_node)?, - }, + Some(expr_node) => self.evaluate(*expr_node)?, None => BaseValue::Nil, }; self.enviorment.declare(name.clone(), value) } - Stmt::VarAssigment { name, value, .. } => { let result = self.evaluate(*value)?; self.enviorment.set(name.clone(), result) @@ -223,10 +240,18 @@ impl Interpreter { Stmt::While { condition, body, .. } => { + let mut ret = BaseValue::Nil; while self.evaluate(*condition.clone())?.is_truthy() { - self.evaluate(*body.clone())?; + match self.evaluate(*body.clone()) { + Ok(val) => ret = val, + Err(LoxError::Return { value, .. }) => { + ret = value; + break; + } + Err(err) => return Err(err), + }; } - Ok(BaseValue::Nil) + Ok(ret) } Stmt::For { variable, @@ -235,7 +260,6 @@ impl Interpreter { body, .. } => { - self.enviorment.push_new_scope(); let source_slice = variable.source_slice.clone(); let val = self.evaluate(*variable)?; if !matches!(val, BaseValue::Number(..)) { @@ -243,7 +267,14 @@ impl Interpreter { } let mut ret = BaseValue::Nil; while self.evaluate(*condition.clone())?.is_truthy() { - ret = self.evaluate(*body.clone())?; + match self.evaluate(*body.clone()) { + Ok(val) => ret = val, + Err(LoxError::Return { value, .. }) => { + ret = value; + break; + } + Err(err) => return Err(err), + }; self.evaluate(*increment.clone())?; } @@ -294,34 +325,26 @@ impl Interpreter { fn evaluate_block(&mut self, statements: Vec>) -> LoxResult { self.enviorment.push_new_scope(); - let (elements, final_expr) = match statements.split_last() { - 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 - } - }; // Ora elements รจ sempre disponibile - for statement in elements.iter() { - self.evaluate((*statement).clone())?; - } + let mut result = Ok(BaseValue::Nil); + for statement in statements.iter() { + let node = statement.node.clone(); + match node { + Stmt::Return { expression, .. } => { + let value = self.evaluate(*expression)?; - // Gestisci l'espressione finale se presente - match final_expr { - Some(expr) => { - let res = self.evaluate(*expr.clone()); - self.enviorment.pop_scope(); - res - } - None => { - self.enviorment.pop_scope(); - Ok(BaseValue::Nil) - } + result = Err(LoxError::Return { + source_slice: statement.source_slice.clone(), + value: value, + return_label: "Hi".to_string(), + }); + break; + } + _ => result = self.evaluate((*statement).clone()), + }; } + self.enviorment.pop_scope(); + result } } diff --git a/src/common/ast.rs b/src/common/ast.rs index adc10b5..a796a28 100644 --- a/src/common/ast.rs +++ b/src/common/ast.rs @@ -138,6 +138,7 @@ pub enum Stmt { }, Return { expression: Box>, + label: String, return_value: Box, }, Block { @@ -219,7 +220,12 @@ impl Display for Stmt { Stmt::Return { expression, return_value, - } => write!(f, "Return({}) -> {:?};", expression.node, return_value), + label, + } => write!( + f, + "Return({}, {}) -> {:?};", + expression.node, label, return_value + ), Stmt::Block { statements, label, @@ -327,7 +333,12 @@ impl Debug for Stmt { Stmt::Return { expression, return_value, - } => write!(f, "Return({:?}) -> {:?};", expression.node, return_value), + label, + } => write!( + f, + "Return({:?}, {}) -> {:?};", + expression.node, label, return_value + ), Stmt::Block { label, statements, diff --git a/src/common/lox_result.rs b/src/common/lox_result.rs index 8ed8f7d..dce78e0 100644 --- a/src/common/lox_result.rs +++ b/src/common/lox_result.rs @@ -26,6 +26,11 @@ pub enum LoxError { expected: String, found: String, }, + Return { + source_slice: SourceSlice, + value: BaseValue, + return_label: String, + }, } /// Configuration for error display formatting @@ -84,6 +89,13 @@ impl LoxError { } => { format!("expected {}, found {}", expected, found) } + LoxError::Return { + value, + return_label, + .. + } => { + format!("return value: {} and label: {}", value, return_label) + } } } @@ -94,6 +106,11 @@ impl LoxError { LoxError::ParseError { source_slice, .. } => Some(source_slice.clone()), LoxError::IoError { .. } => None, LoxError::TypeMismatch { source_slice, .. } => Some(source_slice.clone()), + LoxError::Return { + value, + return_label, + .. + } => None, } } @@ -104,6 +121,7 @@ impl LoxError { LoxError::ParseError { .. } => "parse error", LoxError::IoError { .. } => "io error", LoxError::TypeMismatch { .. } => "type error", + LoxError::Return { .. } => "return error", } } @@ -396,6 +414,20 @@ impl fmt::Display for LoxError { found ) } + LoxError::Return { + value, + return_label, + source_slice, + } => { + write!( + f, + "Return error at {}:{}: value `{}`, label `{}`", + source_slice.start_position.line + 1, + source_slice.start_position.column + 1, + value, + return_label + ) + } } } } diff --git a/src/frontend/lexer.rs b/src/frontend/lexer.rs index b34d5ec..6751c21 100644 --- a/src/frontend/lexer.rs +++ b/src/frontend/lexer.rs @@ -29,6 +29,7 @@ fn get_keyword_token(word: &str) -> Option { "in" => Some(TokenType::In), "print" => Some(TokenType::Print), "return" => Some(TokenType::Return), + "break" => Some(TokenType::Break), "super" => Some(TokenType::Super), "this" => Some(TokenType::This), "true" => Some(TokenType::True), diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs index 5dee948..29e8dad 100644 --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -2,7 +2,7 @@ use crate::{ common::{ ast::{AstNode, Expr, Stmt}, base_value::{BaseValue, LoxFunction}, - lox_result::{parse_error, runtime_error, LoxResult}, + lox_result::{parse_error, runtime_error, LoxError, LoxResult}, }, frontend::{ source_registry::SourceSlice, @@ -24,7 +24,7 @@ impl Parser { let mut statements = Vec::new(); let mut errors = Vec::new(); while !self.is_at_end() { - match self.statement() { + match self.statement(None) { Ok(stmt) => { statements.push(stmt.clone()); } @@ -42,11 +42,12 @@ impl Parser { Ok(statements) } - fn statement(&mut self) -> LoxResult> { + fn statement(&mut self, label: Option) -> LoxResult> { match (&self.peek().token_type, &self.peek_next().token_type) { (TokenType::Print, _) => self.print_statement(), - (TokenType::Return, _) => self.return_statement(), - (TokenType::StartBlock, _) => self.block_statement(), + (TokenType::Return, _) => self.return_statement(label), + (TokenType::Break, _) => self.return_statement(Some("loop".to_string())), + (TokenType::StartBlock, _) => self.block_statement(label), (TokenType::Var, TokenType::Identifier) => { self.advance(); self.var_statement() @@ -68,11 +69,7 @@ impl Parser { let condition = self.expression()?; self.consume(TokenType::Semicolon, "Expected ';' after for condition")?; let increment = self.assignment_statement()?; - - self.consume(TokenType::StartBlock, "Expected 'do' after for range")?; - - let body = self.statement()?; - self.consume(TokenType::EndBlock, "Expected 'end' after for body")?; + let body = self.statement(Some("loop".to_string()))?; let end_slice = self.previous().source_slice.clone(); let combined_slice = SourceSlice::from_positions( @@ -118,7 +115,7 @@ impl Parser { // self.advance(); // self.consume(TokenType::In, "Expect 'in' after for variable.")?; // let iterable = self.expression()?; - // let body = self.statement()?; + // let body = self.statement(None)?; // let end_slice = body.source_slice.clone(); // let combined_slice = SourceSlice::from_positions( // start_slice.source_id, @@ -139,7 +136,7 @@ impl Parser { let start_slice = self.peek().source_slice.clone(); self.advance(); let condition = self.expression()?; - let body = self.statement()?; + let body = self.statement(Some("loop".to_string()))?; let end_slice = body.source_slice.clone(); let combined_slice = SourceSlice::from_positions( start_slice.source_id, @@ -161,7 +158,7 @@ impl Parser { self.advance(); // consume 'if' let condition = self.expression()?; self.consume(TokenType::Then, "Expect 'then' after if condition.")?; - let then_branch = self.statement()?; + let then_branch = self.statement(None)?; let mut elif_branches = Vec::new(); let mut last_slice = then_branch.source_slice.clone(); @@ -169,14 +166,14 @@ impl Parser { self.advance(); // consume 'elif' let elif_condition = self.expression()?; self.consume(TokenType::Then, "Expect 'then' after elif condition.")?; - let elif_branch = self.statement()?; + let elif_branch = self.statement(None)?; 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(); // consume 'else' - let else_stmt = self.statement()?; + let else_stmt = self.statement(None)?; last_slice = else_stmt.source_slice.clone(); Some(Box::new(else_stmt)) } else { @@ -259,7 +256,7 @@ impl Parser { self.consume(TokenType::RightBrace, "Expected '}' after guard expression")?; } - let body = self.statement()?; + let body = self.statement(None)?; let node = AstNode { node: Expr::Literal { value: BaseValue::Function(LoxFunction { @@ -370,12 +367,13 @@ impl Parser { )) } - fn block_statement(&mut self) -> LoxResult> { + fn block_statement(&mut self, label: Option) -> 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()?; + let stmt = self.statement(label.clone())?; let should_break = matches!(stmt.node, Stmt::Expression { .. }); statements.push(stmt); if should_break { @@ -389,10 +387,15 @@ impl Parser { start_slice.start_position, end_slice.end_position, ); + let label_str = if let Some(label) = label { + label + } else { + String::default() + }; Ok(AstNode::new( Stmt::Block { statements: Box::new(statements), - label: String::default(), + label: label_str, return_value: Box::new(BaseValue::Nil), }, combined_slice, @@ -429,10 +432,31 @@ impl Parser { )) } - fn return_statement(&mut self) -> LoxResult> { + fn return_statement(&mut self, label: Option) -> LoxResult> { let start_slice = self.peek().source_slice.clone(); self.advance(); - let expr = self.expression()?; + let expr = match self.expression() { + Ok(expr) => expr, + Err(LoxError::ParseError { + message, + source_slice, + }) => { + if message == "Expect expression." { + AstNode { + node: Expr::Literal { + value: BaseValue::Nil, + }, + source_slice: start_slice.clone(), + } + } else { + return Err(LoxError::ParseError { + message, + source_slice, + }); + } + } + Err(err) => return Err(err), + }; let semicolon = self.consume(TokenType::Semicolon, "Expect ';' after return value.")?; let end_slice = semicolon.source_slice.clone(); let combined_slice = SourceSlice::from_positions( @@ -440,10 +464,16 @@ impl Parser { start_slice.start_position, end_slice.end_position, ); + let label_str = if let Some(label) = label { + label + } else { + String::default() + }; Ok(AstNode::new( Stmt::Return { expression: Box::new(expr), return_value: Box::new(BaseValue::Nil), + label: label_str, }, combined_slice, )) diff --git a/src/frontend/tokens.rs b/src/frontend/tokens.rs index cdad154..d822670 100644 --- a/src/frontend/tokens.rs +++ b/src/frontend/tokens.rs @@ -57,6 +57,7 @@ pub enum TokenType { Is, Print, Return, + Break, Super, This, Var, @@ -92,6 +93,7 @@ impl fmt::Display for TokenType { TokenType::Or => write!(f, "or"), TokenType::Print => write!(f, "print"), TokenType::Return => write!(f, "return"), + TokenType::Break => write!(f, "break"), TokenType::Super => write!(f, "super"), TokenType::This => write!(f, "this"), TokenType::Var => write!(f, "var"), diff --git a/src/logging/display_token.rs b/src/logging/display_token.rs index f3b645f..5ce6980 100644 --- a/src/logging/display_token.rs +++ b/src/logging/display_token.rs @@ -190,6 +190,7 @@ impl Token { TokenType::Or => "OR", TokenType::Print => "PRINT", TokenType::Return => "RETURN", + TokenType::Break => "BREAK", TokenType::Super => "SUPER", TokenType::This => "THIS", TokenType::Var => "VAR", @@ -232,6 +233,7 @@ impl Token { | TokenType::Or | TokenType::Print | TokenType::Return + | TokenType::Break | TokenType::Super | TokenType::This | TokenType::Var diff --git a/src/main.rs b/src/main.rs index d10766d..f79d8f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,109 +30,35 @@ fn main() -> LoxResult<()> { let args: Vec = env::args().collect(); let (stage, file_path, debug) = parse_args(&args); + println!("running {file_path:?}"); let mut lox = LoxInterpreter::new(debug); - let _ = match file_path { + let res = match file_path { Some(path) => lox.run_file(&path, stage), None => lox.run_prompt(stage), }; - Ok(()) + res } fn parse_args(args: &[String]) -> (ExecutionStage, Option, bool) { - if args.len() == 1 { - // Solo il nome del programma: modalitร  interattiva completa - (ExecutionStage::Full, None, false) - } else if args.len() == 2 { - // Un argomento: potrebbe essere file o flag - let arg = &args[1]; - if arg == "--tokens" || arg == "--ast" || arg == "--full" || arg == "--debug" { - // Flag senza file: modalitร  interattiva - let stage = match arg.as_str() { - "--tokens" => ExecutionStage::Tokens, - "--ast" => ExecutionStage::Ast, - "--full" => ExecutionStage::Full, - "--debug" => ExecutionStage::Full, - _ => ExecutionStage::Full, - }; - let debug = arg == "--debug"; - (stage, None, debug) - } else { - // File senza flag: esecuzione completa del file - (ExecutionStage::Full, Some(arg.clone()), false) + println!("{args:?}"); + + let mut stage = ExecutionStage::Full; + let mut file_path = None; + let mut debug = false; + + for arg in args.iter().skip(1) { + match arg.as_str() { + "-t" | "--tokens" => stage = ExecutionStage::Tokens, + "-a" | "--ast" => stage = ExecutionStage::Ast, + "-f" | "--full" => stage = ExecutionStage::Full, + "-d" | "--debug" => debug = true, + _ => file_path = Some(arg.clone()), } - } else if args.len() == 3 { - // Due argomenti: potrebbero essere flag + file o due flag - let mut debug = false; - let mut stage = ExecutionStage::Full; - let mut file_path = None; - - for arg in &args[1..3] { - if arg == "--debug" { - debug = true; - } else if arg == "--tokens" || arg == "--ast" || arg == "--full" { - stage = match arg.as_str() { - "--tokens" => ExecutionStage::Tokens, - "--ast" => ExecutionStage::Ast, - "--full" => ExecutionStage::Full, - _ => ExecutionStage::Full, - }; - } else if !arg.starts_with("--") { - file_path = Some(arg.clone()); - } else { - eprintln!( - "Unknown flag: {}. Use --tokens, --ast, --full, or --debug", - arg - ); - eprintln!( - "Usage: {} [--tokens|--ast|--full] [--debug] [script]", - args[0] - ); - std::process::exit(64); - } - } - - (stage, file_path, debug) - } else if args.len() == 4 { - // Tre argomenti: flag + flag + file - let mut debug = false; - let mut stage = ExecutionStage::Full; - let mut file_path = None; - - for arg in &args[1..4] { - if arg == "--debug" { - debug = true; - } else if arg == "--tokens" || arg == "--ast" || arg == "--full" { - stage = match arg.as_str() { - "--tokens" => ExecutionStage::Tokens, - "--ast" => ExecutionStage::Ast, - "--full" => ExecutionStage::Full, - _ => ExecutionStage::Full, - }; - } else if !arg.starts_with("--") { - file_path = Some(arg.clone()); - } else { - eprintln!( - "Unknown flag: {}. Use --tokens, --ast, --full, or --debug", - arg - ); - eprintln!( - "Usage: {} [--tokens|--ast|--full] [--debug] [script]", - args[0] - ); - std::process::exit(64); - } - } - - (stage, file_path, debug) - } else { - eprintln!( - "Usage: {} [--tokens|--ast|--full] [--debug] [script]", - args[0] - ); - std::process::exit(64); } + + (stage, file_path, debug) } struct LoxInterpreter { From 29c86d278ddd8af39bfc763beb73d70445980e25 Mon Sep 17 00:00:00 2001 From: Giulio Agostini Date: Mon, 29 Jun 2026 20:47:59 +0200 Subject: [PATCH 4/9] Make environment generic and adapt closures Added unit test --- .gitignore | 0 Cargo.toml | 0 README.md | 0 examples/fail_error.lox | 0 examples/test_closure.lox | 15 +++ examples/test_example.lox | 0 examples/test_function_call.lox | 0 examples/test_source_tracking.lox | 0 src/backend/environment.rs | 124 ++++++++++++++++-- src/backend/interpreter.rs | 148 ++++++++++++++++++++- src/backend/mod.rs | 1 + src/backend/variable_resolution.rs | 204 +++++++++++++++++++++++++++++ src/common/ast.rs | 0 src/common/base_value.rs | 5 +- src/common/lox_result.rs | 6 +- src/common/mod.rs | 0 src/frontend/lexer.rs | 188 ++++++++++++++++++++++++++ src/frontend/mod.rs | 0 src/frontend/parser.rs | 159 +++++++++++++++++++++- src/frontend/source_registry.rs | 0 src/frontend/tokens.rs | 0 src/lib.rs | 0 src/logging/display_ast.rs | 0 src/logging/display_token.rs | 0 src/logging/mod.rs | 0 src/main.rs | 0 src/middleend/crawler.rs | 0 src/middleend/mod.rs | 0 tests/number_operations.rs | 0 tests/test_example.rs | 0 30 files changed, 820 insertions(+), 30 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 Cargo.toml mode change 100644 => 100755 README.md mode change 100644 => 100755 examples/fail_error.lox mode change 100644 => 100755 examples/test_closure.lox mode change 100644 => 100755 examples/test_example.lox mode change 100644 => 100755 examples/test_function_call.lox mode change 100644 => 100755 examples/test_source_tracking.lox mode change 100644 => 100755 src/backend/environment.rs mode change 100644 => 100755 src/backend/interpreter.rs mode change 100644 => 100755 src/backend/mod.rs create mode 100755 src/backend/variable_resolution.rs mode change 100644 => 100755 src/common/ast.rs mode change 100644 => 100755 src/common/base_value.rs mode change 100644 => 100755 src/common/lox_result.rs mode change 100644 => 100755 src/common/mod.rs mode change 100644 => 100755 src/frontend/lexer.rs mode change 100644 => 100755 src/frontend/mod.rs mode change 100644 => 100755 src/frontend/parser.rs mode change 100644 => 100755 src/frontend/source_registry.rs mode change 100644 => 100755 src/frontend/tokens.rs mode change 100644 => 100755 src/lib.rs mode change 100644 => 100755 src/logging/display_ast.rs mode change 100644 => 100755 src/logging/display_token.rs mode change 100644 => 100755 src/logging/mod.rs mode change 100644 => 100755 src/main.rs mode change 100644 => 100755 src/middleend/crawler.rs mode change 100644 => 100755 src/middleend/mod.rs mode change 100644 => 100755 tests/number_operations.rs mode change 100644 => 100755 tests/test_example.rs diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/Cargo.toml b/Cargo.toml old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/examples/fail_error.lox b/examples/fail_error.lox old mode 100644 new mode 100755 diff --git a/examples/test_closure.lox b/examples/test_closure.lox old mode 100644 new mode 100755 index 2e288c0..719f093 --- a/examples/test_closure.lox +++ b/examples/test_closure.lox @@ -10,3 +10,18 @@ end internal := closure(); internal(); + +a := "Global"; +b := a; +c := b; +c = b = a; +d := c = b = a; +do + showA :: fn() do + print a; + end + + showA(); + a := "Local"; + showA(); +end diff --git a/examples/test_example.lox b/examples/test_example.lox old mode 100644 new mode 100755 diff --git a/examples/test_function_call.lox b/examples/test_function_call.lox old mode 100644 new mode 100755 diff --git a/examples/test_source_tracking.lox b/examples/test_source_tracking.lox old mode 100644 new mode 100755 diff --git a/src/backend/environment.rs b/src/backend/environment.rs old mode 100644 new mode 100755 index 97166ec..640a45d --- a/src/backend/environment.rs +++ b/src/backend/environment.rs @@ -1,34 +1,45 @@ -use std::collections::HashMap; - use crate::{ - common::{ - base_value::BaseValue, - lox_result::{runtime_error, LoxResult}, - }, + common::lox_result::{runtime_error, LoxResult}, frontend::source_registry::SourceSlice, }; +use std::collections::HashMap; +use std::fmt::Debug; -#[derive(Debug, Clone, PartialEq)] -pub struct EnvironmentStack { - stack: Vec>, +pub type Environment = HashMap; + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct EnvironmentStack { + stack: Vec>, } -impl EnvironmentStack { +impl EnvironmentStack { pub fn new() -> Self { EnvironmentStack { stack: vec![HashMap::new()], } } + pub fn is_empty(&self) -> bool { + self.stack.is_empty() + } + pub fn push_new_scope(&mut self) { self.stack.push(HashMap::new()); } + pub fn push_existing_scope(&mut self, scope: HashMap) { + self.stack.push(scope); + } + pub fn pop_scope(&mut self) { self.stack.pop(); } - pub fn get(&self, name: &str) -> LoxResult { + pub fn clone_last_scope(&mut self) -> HashMap { + self.stack.last().unwrap().clone() + } + + pub fn get(&self, name: &str) -> LoxResult { let size = self.stack.len(); for i in (0..size).rev() { @@ -42,12 +53,12 @@ impl EnvironmentStack { ) } - pub fn declare(&mut self, name: String, value: BaseValue) -> LoxResult { + pub fn declare(&mut self, name: String, value: T) -> LoxResult { self.stack.last_mut().unwrap().insert(name, value.clone()); Ok(value) } - pub fn set(&mut self, name: String, value: BaseValue) -> LoxResult { + pub fn set(&mut self, name: String, value: T) -> LoxResult { let size = self.stack.len(); for i in (0..size).rev() { @@ -69,3 +80,90 @@ impl EnvironmentStack { ) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn declare_and_get() { + let mut env: EnvironmentStack = EnvironmentStack::new(); + env.declare("a".to_string(), 1).unwrap(); + assert_eq!(env.get("a").unwrap(), 1); + } + + #[test] + fn get_undefined_is_error() { + let env: EnvironmentStack = EnvironmentStack::new(); + assert!(env.get("nope").is_err()); + } + + #[test] + fn declare_overwrites_in_same_scope() { + let mut env: EnvironmentStack = EnvironmentStack::new(); + env.declare("a".to_string(), 1).unwrap(); + env.declare("a".to_string(), 2).unwrap(); + assert_eq!(env.get("a").unwrap(), 2); + } + + #[test] + fn inner_scope_shadows_outer_and_unshadows_on_pop() { + let mut env: EnvironmentStack = EnvironmentStack::new(); + env.declare("a".to_string(), 1).unwrap(); + env.push_new_scope(); + env.declare("a".to_string(), 2).unwrap(); + assert_eq!(env.get("a").unwrap(), 2); + env.pop_scope(); + assert_eq!(env.get("a").unwrap(), 1); + } + + #[test] + fn get_falls_through_to_outer_scope() { + let mut env: EnvironmentStack = EnvironmentStack::new(); + env.declare("a".to_string(), 1).unwrap(); + env.push_new_scope(); + assert_eq!(env.get("a").unwrap(), 1); + } + + #[test] + fn set_updates_existing_value_in_outer_scope() { + let mut env: EnvironmentStack = EnvironmentStack::new(); + env.declare("a".to_string(), 1).unwrap(); + env.push_new_scope(); + env.set("a".to_string(), 9).unwrap(); + env.pop_scope(); + assert_eq!(env.get("a").unwrap(), 9); + } + + #[test] + fn set_undefined_is_error() { + let mut env: EnvironmentStack = EnvironmentStack::new(); + assert!(env.set("a".to_string(), 1).is_err()); + } + + #[test] + fn pop_scope_discards_inner_declarations() { + let mut env: EnvironmentStack = EnvironmentStack::new(); + env.push_new_scope(); + env.declare("temp".to_string(), 5).unwrap(); + env.pop_scope(); + assert!(env.get("temp").is_err()); + } + + #[test] + fn push_existing_scope_makes_values_visible() { + let mut scope = HashMap::new(); + scope.insert("k".to_string(), 7); + let mut env: EnvironmentStack = EnvironmentStack::new(); + env.push_existing_scope(scope); + assert_eq!(env.get("k").unwrap(), 7); + } + + #[test] + fn clone_last_scope_returns_top_scope() { + let mut env: EnvironmentStack = EnvironmentStack::new(); + env.declare("a".to_string(), 1).unwrap(); + let scope = env.clone_last_scope(); + assert_eq!(scope.get("a"), Some(&1)); + } +} diff --git a/src/backend/interpreter.rs b/src/backend/interpreter.rs old mode 100644 new mode 100755 index 360d8ec..74b39de --- a/src/backend/interpreter.rs +++ b/src/backend/interpreter.rs @@ -10,14 +10,14 @@ use crate::{ use std::fmt::{Debug, Display}; pub struct Interpreter { - enviorment: EnvironmentStack, + enviorment: EnvironmentStack, } pub trait EvaluateInterpreter { fn evaluate(&mut self, stmt: T) -> LoxResult; } -impl<'a, R: AstNodeKind + Clone + Debug + Display> EvaluateInterpreter> for Interpreter +impl EvaluateInterpreter> for Interpreter where Interpreter: EvaluateInterpreter, { @@ -36,8 +36,6 @@ impl EvaluateInterpreter for Interpreter { Expr::Literal { value } => match value { BaseValue::Function(mut func) => { func.closure = Some(self.enviorment.clone()); - println!("Closure created"); - println!("current enviorment: {:?}", self.enviorment); Ok(BaseValue::Function(func)) } _ => Ok(value), @@ -128,9 +126,7 @@ impl Interpreter { match function { BaseValue::NativeFunction(func) => Ok((func.function)(&evaluated_arguments)), BaseValue::Function(func) => { - // Save the current environment let saved_env = self.enviorment.clone(); - // If the function captured a closure, use it as the base environment if let Some(closure_env) = func.closure { self.enviorment = closure_env; @@ -348,3 +344,143 @@ impl Interpreter { result } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::frontend::lexer::Lexer; + use crate::frontend::parser::Parser; + + /// Lex, parse and interpret `src`, returning the value of the last statement. + fn eval(src: &str) -> LoxResult { + let tokens = Lexer::new(src.to_string(), 0) + .scans_tokens() + .expect("source should lex without errors"); + let statements = Parser::new(tokens) + .parse() + .expect("source should parse without errors"); + let mut interpreter = Interpreter::new(); + let mut last = BaseValue::Nil; + for stmt in statements { + last = interpreter.evaluate(stmt)?; + } + Ok(last) + } + + #[test] + fn evaluates_arithmetic_with_precedence() { + assert_eq!(eval("1 + 2 * 3;").unwrap().to_string(), "7"); + } + + #[test] + fn grouping_changes_precedence() { + assert_eq!(eval("(1 + 2) * 3;").unwrap().to_string(), "9"); + } + + #[test] + fn division_and_subtraction() { + assert_eq!(eval("10 - 4 / 2;").unwrap().to_string(), "8"); + } + + #[test] + fn comparison_yields_boolean() { + assert_eq!(eval("1 < 2;").unwrap(), BaseValue::Boolean(true)); + assert_eq!(eval("2 < 1;").unwrap(), BaseValue::Boolean(false)); + } + + #[test] + fn equality_operators() { + assert_eq!(eval("1 == 1;").unwrap(), BaseValue::Boolean(true)); + assert_eq!(eval("1 != 2;").unwrap(), BaseValue::Boolean(true)); + } + + #[test] + fn unary_negation() { + assert_eq!(eval("-5;").unwrap().to_string(), "-5"); + } + + #[test] + fn logical_not() { + assert_eq!(eval("!true;").unwrap(), BaseValue::Boolean(false)); + } + + #[test] + fn concatenates_strings() { + match eval("\"a\" + \"b\";").unwrap() { + BaseValue::String(s) => assert!(s.contains('a') && s.contains('b')), + other => panic!("expected string, got {:?}", other), + } + } + + #[test] + fn declares_and_reads_variable() { + assert_eq!(eval("var x: Int = 10; x + 5;").unwrap().to_string(), "15"); + } + + #[test] + fn assignment_updates_variable() { + assert_eq!( + eval("var x: Int = 1; x = 42; x;").unwrap().to_string(), + "42" + ); + } + + #[test] + fn if_takes_then_branch_when_true() { + assert_eq!( + eval("var x: Int = 0; if true then x = 1; x;") + .unwrap() + .to_string(), + "1" + ); + } + + #[test] + fn if_takes_else_branch_when_false() { + assert_eq!( + eval("var x: Int = 0; if false then x = 1; else x = 2; x;") + .unwrap() + .to_string(), + "2" + ); + } + + #[test] + fn while_loop_runs_until_condition_false() { + let src = "var i: Int = 0; while i < 3 do i = i + 1; end i;"; + assert_eq!(eval(src).unwrap().to_string(), "3"); + } + + #[test] + fn block_scope_does_not_leak_outer_assignment() { + // `set` walks outer scopes, so the outer `x` is updated from inside the block. + let src = "var x: Int = 1; do x = 5; end x;"; + assert_eq!(eval(src).unwrap().to_string(), "5"); + } + + #[test] + fn defines_and_calls_a_function() { + let src = "add :: fn (a, b) do return a + b; end add(2, 3);"; + assert_eq!(eval(src).unwrap().to_string(), "5"); + } + + #[test] + fn clock_native_function_returns_a_number() { + assert!(matches!(eval("clock();").unwrap(), BaseValue::Number(..))); + } + + #[test] + fn undefined_variable_is_runtime_error() { + assert!(eval("missing;").is_err()); + } + + #[test] + fn calling_a_non_function_is_runtime_error() { + assert!(eval("var x: Int = 5; x();").is_err()); + } + + #[test] + fn adding_incompatible_types_is_runtime_error() { + assert!(eval("1 + true;").is_err()); + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs old mode 100644 new mode 100755 index ad68c27..a6f3b79 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,2 +1,3 @@ pub mod environment; pub mod interpreter; +pub mod variable_resolution; diff --git a/src/backend/variable_resolution.rs b/src/backend/variable_resolution.rs new file mode 100755 index 0000000..1c9d515 --- /dev/null +++ b/src/backend/variable_resolution.rs @@ -0,0 +1,204 @@ +use crate::{ + backend::environment::EnvironmentStack, + common::{ + ast::{AstNode, AstNodeKind, Expr, Stmt}, + lox_result::{runtime_error, LoxError, LoxResult}, + }, + frontend::source_registry::SourceSlice, +}; + +use std::fmt::{Debug, Display}; +struct Resolver { + scopes: EnvironmentStack, +} + +impl Resolver { + pub fn new() -> Self { + Resolver { + scopes: EnvironmentStack::new(), + } + } + + fn declare(&mut self, name: &String) { + if self.scopes.is_empty() { + return; + } + let _ = self.scopes.set(name.clone(), false); + } + + fn define(&mut self, name: &String) { + if self.scopes.is_empty() { + return; + } + let _ = self.scopes.set(name.clone(), true); + } +} + +pub trait StaticAnalyzer { + fn resolve(&mut self, node: &T) -> LoxResult; +} + +impl StaticAnalyzer> for Resolver +where + Resolver: StaticAnalyzer, +{ + fn resolve(&mut self, node: &AstNode) -> LoxResult { + self.resolve(&node.node) + } +} + +impl StaticAnalyzer for Resolver { + fn resolve(&mut self, node: &Stmt) -> LoxResult { + match node { + Stmt::Expression { expression, .. } => { + // Visit the expression + self.resolve(expression.as_ref()) + } + Stmt::Print { expression, .. } => { + // Visit the expression + self.resolve(expression.as_ref()) + } + Stmt::VarDeclaration { + initializer, name, .. + } => { + self.declare(name); + // Visit the initializer if present + if let Some(init) = initializer { + self.resolve(init.as_ref())?; + } + self.define(name); + Ok(true) + } + Stmt::VarAssigment { value, name, .. } => { + match self.scopes.get(name) { + Ok(true) => (), + Ok(false) => { + return Err(LoxError::RuntimeError { + source_slice: SourceSlice::default(), + message: "Cant read loac variable in it own lintilizer".to_string(), + }) + } + Err(err) => return Err(err), + } + self.resolve(value.as_ref()) + } + Stmt::Return { expression, .. } => { + // Visit the return expression + self.resolve(expression.as_ref()) + } + Stmt::Block { statements, .. } => { + self.scopes.push_new_scope(); + // Visit all statements in the block + for stmt in statements.iter() { + self.resolve(stmt)?; + } + self.scopes.pop_scope(); + Ok(true) + } + Stmt::If { + condition, + then_branch, + elif_branch, + else_branch, + .. + } => { + // Visit the condition + self.resolve(condition.as_ref())?; + + // Visit the then branch + self.resolve(then_branch.as_ref())?; + + // Visit all elif branches + for (elif_condition, elif_stmt) in elif_branch.iter() { + self.resolve(elif_condition.as_ref())?; + self.resolve(elif_stmt.as_ref())?; + } + + // Visit the else branch if present + if let Some(else_stmt) = else_branch { + self.resolve(else_stmt.as_ref())?; + } + + Ok(true) + } + Stmt::While { + condition, body, .. + } => { + // Visit the condition + self.resolve(condition.as_ref())?; + + // Visit the body + self.resolve(body.as_ref())?; + Ok(true) + } + Stmt::For { + variable, + condition, + increment, + body, + .. + } => { + // Visit the variable initialization + self.resolve(variable.as_ref())?; + + // Visit the condition + self.resolve(condition.as_ref())?; + + // Visit the increment + self.resolve(increment.as_ref())?; + + // Visit the body + self.resolve(body.as_ref())?; + Ok(true) + } + } + } +} + +impl StaticAnalyzer for Resolver { + fn resolve(&mut self, node: &Expr) -> LoxResult { + match node { + Expr::Literal { .. } => { + // Leaf node - no children to visit + Ok(true) + } + Expr::Binary { left, right, .. } => { + // Visit left operand + self.resolve(left.as_ref())?; + + // Visit right operand + self.resolve(right.as_ref()) + } + Expr::Unary { operand, .. } => { + // Visit the operand + self.resolve(operand.as_ref()) + } + Expr::Grouping { expression } => { + // Visit the grouped expression + self.resolve(expression.as_ref()) + } + Expr::Identifier { name, .. } => { + if !self.scopes.is_empty() && self.scopes.get(name).is_ok() { + return Err(LoxError::ParseError { + source_slice: SourceSlice::default(), + message: "Cant read local varialbe in it own initializer".to_string(), + }); + } + Ok(true) + } + Expr::Call { + callee, arguments, .. + } => { + // Visit the callee + self.resolve(callee.as_ref())?; + + // Visit all arguments + for arg in arguments.iter() { + self.resolve(arg)?; + } + + Ok(true) + } + } + } +} diff --git a/src/common/ast.rs b/src/common/ast.rs old mode 100644 new mode 100755 diff --git a/src/common/base_value.rs b/src/common/base_value.rs old mode 100644 new mode 100755 index 6eb2976..1929a4d --- a/src/common/base_value.rs +++ b/src/common/base_value.rs @@ -2,6 +2,7 @@ use core::fmt; use std::fmt::Display; use std::ops::{Add, Div, Mul, Not, Rem, Sub}; +use crate::backend::environment::Environment; use crate::common::lox_result::{runtime_error, LoxResult}; use crate::{ backend::environment::EnvironmentStack, @@ -312,7 +313,7 @@ pub struct LoxFunction { pub parameters: Vec<(String, String)>, pub return_type: Option, pub body: AstNode, - pub closure: Option, + pub closure: Option>, pub guard: Option>, } @@ -320,7 +321,7 @@ impl LoxFunction { pub fn new( parameters: Vec<(String, String)>, body: AstNode, - closure: Option, + closure: Option>, guard: Option>, ) -> Self { LoxFunction { diff --git a/src/common/lox_result.rs b/src/common/lox_result.rs old mode 100644 new mode 100755 index dce78e0..63327b4 --- a/src/common/lox_result.rs +++ b/src/common/lox_result.rs @@ -106,11 +106,7 @@ impl LoxError { LoxError::ParseError { source_slice, .. } => Some(source_slice.clone()), LoxError::IoError { .. } => None, LoxError::TypeMismatch { source_slice, .. } => Some(source_slice.clone()), - LoxError::Return { - value, - return_label, - .. - } => None, + LoxError::Return { .. } => None, } } diff --git a/src/common/mod.rs b/src/common/mod.rs old mode 100644 new mode 100755 diff --git a/src/frontend/lexer.rs b/src/frontend/lexer.rs old mode 100644 new mode 100755 index 6751c21..5befaad --- a/src/frontend/lexer.rs +++ b/src/frontend/lexer.rs @@ -322,3 +322,191 @@ impl Lexer { } } } + +#[cfg(test)] +mod tests { + use std::f64; + + use super::*; + + /// Lex `input` and return the resulting tokens, panicking on lexical errors. + fn lex(input: &str) -> Vec { + Lexer::new(input.to_string(), 0) + .scans_tokens() + .expect("expected input to lex without errors") + } + + /// Lex `input` and collect just the token types (including the trailing EOF). + fn token_types(input: &str) -> Vec { + lex(input).into_iter().map(|t| t.token_type).collect() + } + + #[test] + fn scans_single_character_tokens() { + assert_eq!( + token_types("(){}[],.-+;:*%"), + vec![ + TokenType::LeftParen, + TokenType::RightParen, + TokenType::LeftBrace, + TokenType::RightBrace, + TokenType::LeftBracket, + TokenType::RightBracket, + TokenType::Comma, + TokenType::Dot, + TokenType::Minus, + TokenType::Plus, + TokenType::Semicolon, + TokenType::Colon, + TokenType::Star, + TokenType::Percent, + TokenType::Eof, + ] + ); + } + + #[test] + fn scans_one_and_two_char_operators() { + assert_eq!( + token_types("! != = == < <= > >= /"), + vec![ + TokenType::Bang, + TokenType::BangEqual, + TokenType::Equal, + TokenType::EqualEqual, + TokenType::Less, + TokenType::LessEqual, + TokenType::Greater, + TokenType::GreaterEqual, + TokenType::Slash, + TokenType::Eof, + ] + ); + } + + #[test] + fn keyword_lookup_matches_known_words() { + assert_eq!(get_keyword_token("and"), Some(TokenType::And)); + assert_eq!(get_keyword_token("if"), Some(TokenType::If)); + assert_eq!(get_keyword_token("then"), Some(TokenType::Then)); + assert_eq!(get_keyword_token("while"), Some(TokenType::While)); + assert_eq!(get_keyword_token("do"), Some(TokenType::StartBlock)); + assert_eq!(get_keyword_token("end"), Some(TokenType::EndBlock)); + assert_eq!(get_keyword_token("not_a_keyword"), None); + } + + #[test] + fn scans_keywords_in_a_stream() { + assert_eq!( + token_types("if then else while print return"), + vec![ + TokenType::If, + TokenType::Then, + TokenType::Else, + TokenType::While, + TokenType::Print, + TokenType::Return, + TokenType::Eof, + ] + ); + } + + #[test] + fn scans_integer_number() { + let tokens = lex("42"); + assert_eq!(tokens[0].token_type, TokenType::Number); + assert_eq!(tokens[0].literal, Some(BaseValue::Number(Number::I32(42)))); + } + + #[test] + fn scans_float_number() { + let tokens = lex("3.141592653589793"); + assert_eq!( + tokens[0].literal, + Some(BaseValue::Number(Number::F64(f64::consts::PI))) + ); + } + + #[test] + fn scans_number_with_float_suffix() { + let tokens = lex("5f"); + assert_eq!(tokens[0].literal, Some(BaseValue::Number(Number::F64(5.0)))); + } + + #[test] + fn scans_number_with_unsigned_suffix() { + let tokens = lex("7u"); + assert_eq!(tokens[0].literal, Some(BaseValue::Number(Number::U128(7)))); + } + + #[test] + fn scans_string_literal_including_quotes() { + // The lexer slices from the opening quote through the closing quote, + // so the literal currently retains the surrounding quotes. + let tokens = lex("\"hello\""); + assert_eq!(tokens[0].token_type, TokenType::String); + assert_eq!( + tokens[0].literal, + Some(BaseValue::String("\"hello\"".to_string())) + ); + } + + #[test] + fn scans_identifier() { + let tokens = lex("foo_bar"); + assert_eq!(tokens[0].token_type, TokenType::Identifier); + assert_eq!( + tokens[0].literal, + Some(BaseValue::String("foo_bar".to_string())) + ); + } + + #[test] + fn scans_boolean_and_nil_literals() { + let tokens = lex("true false Nil"); + assert_eq!(tokens[0].literal, Some(BaseValue::Boolean(true))); + assert_eq!(tokens[1].literal, Some(BaseValue::Boolean(false))); + assert_eq!(tokens[2].literal, Some(BaseValue::Nil)); + } + + #[test] + fn ignores_line_comments() { + assert_eq!( + token_types("1 // a comment\n2"), + vec![TokenType::Number, TokenType::Number, TokenType::Eof] + ); + } + + #[test] + fn ignores_block_comments() { + assert_eq!( + token_types("1 /* multi\nline */ 2"), + vec![TokenType::Number, TokenType::Number, TokenType::Eof] + ); + } + + #[test] + fn always_appends_eof_even_for_empty_input() { + let tokens = lex(""); + assert_eq!(tokens.len(), 1); + assert_eq!(tokens[0].token_type, TokenType::Eof); + } + + #[test] + fn unterminated_string_is_a_lexical_error() { + let result = Lexer::new("\"oops".to_string(), 0).scans_tokens(); + assert!(matches!(result, Err(LoxError::LexicalError { .. }))); + } + + #[test] + fn unterminated_block_comment_is_a_lexical_error() { + let result = Lexer::new("/* never ends".to_string(), 0).scans_tokens(); + assert!(matches!(result, Err(LoxError::LexicalError { .. }))); + } + + #[test] + fn unexpected_character_is_a_lexical_error() { + let result = Lexer::new("@".to_string(), 0).scans_tokens(); + assert!(result.is_err()); + } +} diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs old mode 100644 new mode 100755 diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs old mode 100644 new mode 100755 index 29e8dad..1cc73be --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -205,6 +205,10 @@ impl Parser { let mut _type_annotation = BaseValue::Nil; self.consume(TokenType::Colon, "Expect column")?; + if self.peek().token_type == TokenType::Identifier { + // TODO manage type annotation + self.consume(TokenType::Identifier, "Expect type name.")?; + } match self.peek().token_type { TokenType::Equal | TokenType::Identifier => { @@ -374,11 +378,7 @@ impl Parser { while self.peek().token_type != TokenType::EndBlock && !self.is_at_end() { let stmt = self.statement(label.clone())?; - let should_break = matches!(stmt.node, Stmt::Expression { .. }); statements.push(stmt); - if should_break { - break; - } } let end_token = self.consume(TokenType::EndBlock, "Expect 'end' after block.")?; let end_slice = end_token.source_slice.clone(); @@ -864,3 +864,154 @@ impl Parser { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::common::base_value::Number; + use crate::frontend::lexer::Lexer; + + /// Lex and parse `src`, returning the parser's result. + fn parse_source(src: &str) -> LoxResult>> { + let tokens = Lexer::new(src.to_string(), 0) + .scans_tokens() + .expect("source should lex without errors"); + Parser::new(tokens).parse() + } + + /// Parse `src`, panicking if parsing fails. + fn parse_ok(src: &str) -> Vec> { + parse_source(src).expect("expected source to parse") + } + + /// Extract the inner expression of an expression statement. + fn expression_of(stmt: &AstNode) -> &AstNode { + match &stmt.node { + Stmt::Expression { expression, .. } => expression, + other => panic!("expected expression statement, got {:?}", other), + } + } + + #[test] + fn parses_number_literal_expression() { + let stmts = parse_ok("42;"); + assert_eq!(stmts.len(), 1); + match &expression_of(&stmts[0]).node { + Expr::Literal { value } => { + assert_eq!(*value, BaseValue::Number(Number::I32(42))); + } + other => panic!("expected literal, got {:?}", other), + } + } + + #[test] + fn respects_multiplication_precedence_over_addition() { + // 1 + 2 * 3 should parse as 1 + (2 * 3) + let stmts = parse_ok("1 + 2 * 3;"); + match &expression_of(&stmts[0]).node { + Expr::Binary { + operator, right, .. + } => { + assert_eq!(*operator, TokenType::Plus); + match &right.node { + Expr::Binary { operator, .. } => { + assert_eq!(*operator, TokenType::Star) + } + other => panic!("expected nested binary, got {:?}", other), + } + } + other => panic!("expected binary expression, got {:?}", other), + } + } + + #[test] + fn parses_unary_negation() { + let stmts = parse_ok("-5;"); + match &expression_of(&stmts[0]).node { + Expr::Unary { operator, .. } => assert_eq!(*operator, TokenType::Minus), + other => panic!("expected unary expression, got {:?}", other), + } + } + + #[test] + fn parses_grouping_to_change_precedence() { + // (1 + 2) * 3 should have a grouping on the left of the multiply. + let stmts = parse_ok("(1 + 2) * 3;"); + match &expression_of(&stmts[0]).node { + Expr::Binary { operator, left, .. } => { + assert_eq!(*operator, TokenType::Star); + assert!(matches!(left.node, Expr::Grouping { .. })); + } + other => panic!("expected binary expression, got {:?}", other), + } + } + + #[test] + fn parses_comparison_expression() { + let stmts = parse_ok("1 < 2;"); + match &expression_of(&stmts[0]).node { + Expr::Binary { operator, .. } => assert_eq!(*operator, TokenType::Less), + other => panic!("expected binary expression, got {:?}", other), + } + } + + #[test] + fn parses_print_statement() { + let stmts = parse_ok("print 1;"); + assert!(matches!(stmts[0].node, Stmt::Print { .. })); + } + + #[test] + fn parses_var_declaration_with_initializer() { + let stmts = parse_ok("var x: Int = 5;"); + match &stmts[0].node { + Stmt::VarDeclaration { + name, initializer, .. + } => { + assert_eq!(name, "x"); + assert!(initializer.is_some()); + } + other => panic!("expected var declaration, got {:?}", other), + } + } + + #[test] + fn parses_assignment_statement() { + let stmts = parse_ok("x = 5;"); + match &stmts[0].node { + Stmt::VarAssigment { name, .. } => assert_eq!(name, "x"), + other => panic!("expected assignment statement, got {:?}", other), + } + } + + #[test] + fn parses_block_statement() { + let stmts = parse_ok("do print 1; print 2; end"); + match &stmts[0].node { + Stmt::Block { statements, .. } => assert_eq!(statements.len(), 2), + other => panic!("expected block statement, got {:?}", other), + } + } + + #[test] + fn parses_if_statement() { + let stmts = parse_ok("if true then print 1;"); + assert!(matches!(stmts[0].node, Stmt::If { .. })); + } + + #[test] + fn parses_while_statement() { + let stmts = parse_ok("while true do print 1; end"); + assert!(matches!(stmts[0].node, Stmt::While { .. })); + } + + #[test] + fn errors_on_missing_semicolon_after_print() { + assert!(parse_source("print 1").is_err()); + } + + #[test] + fn errors_on_unclosed_grouping() { + assert!(parse_source("(1 + 2;").is_err()); + } +} diff --git a/src/frontend/source_registry.rs b/src/frontend/source_registry.rs old mode 100644 new mode 100755 diff --git a/src/frontend/tokens.rs b/src/frontend/tokens.rs old mode 100644 new mode 100755 diff --git a/src/lib.rs b/src/lib.rs old mode 100644 new mode 100755 diff --git a/src/logging/display_ast.rs b/src/logging/display_ast.rs old mode 100644 new mode 100755 diff --git a/src/logging/display_token.rs b/src/logging/display_token.rs old mode 100644 new mode 100755 diff --git a/src/logging/mod.rs b/src/logging/mod.rs old mode 100644 new mode 100755 diff --git a/src/main.rs b/src/main.rs old mode 100644 new mode 100755 diff --git a/src/middleend/crawler.rs b/src/middleend/crawler.rs old mode 100644 new mode 100755 diff --git a/src/middleend/mod.rs b/src/middleend/mod.rs old mode 100644 new mode 100755 diff --git a/tests/number_operations.rs b/tests/number_operations.rs old mode 100644 new mode 100755 diff --git a/tests/test_example.rs b/tests/test_example.rs old mode 100644 new mode 100755 From ef8abda04867953aa96af1fb851a2d20f790719e Mon Sep 17 00:00:00 2001 From: Giulio Agostini Date: Tue, 30 Jun 2026 09:47:22 +0200 Subject: [PATCH 5/9] Move variable resolution to middleend - Introduce AST-based visitor and resolver in middleend - Update guard field and parser to use AstNode - Remove backend variable_resolution and adjust exports - Expose middleend in the library --- src/backend/environment.rs | 7 + src/backend/mod.rs | 1 - src/backend/variable_resolution.rs | 204 ---------------------- src/common/base_value.rs | 4 +- src/frontend/parser.rs | 5 +- src/lib.rs | 1 + src/middleend/crawler.rs | 115 ------------- src/middleend/mod.rs | 3 +- src/middleend/variable_resolution.rs | 99 +++++++++++ src/middleend/visit_ast.rs | 248 +++++++++++++++++++++++++++ 10 files changed, 361 insertions(+), 326 deletions(-) delete mode 100755 src/backend/variable_resolution.rs delete mode 100755 src/middleend/crawler.rs create mode 100755 src/middleend/variable_resolution.rs create mode 100644 src/middleend/visit_ast.rs diff --git a/src/backend/environment.rs b/src/backend/environment.rs index 640a45d..b161b57 100755 --- a/src/backend/environment.rs +++ b/src/backend/environment.rs @@ -79,6 +79,13 @@ impl EnvironmentStack { format!("Undefined variable '{}'", name), ) } + + pub fn depth(&self) -> usize { + self.stack.len() + } + pub fn scope_contains(&self, index: usize, name: &str) -> bool { + self.stack[index].contains_key(name) + } } #[cfg(test)] diff --git a/src/backend/mod.rs b/src/backend/mod.rs index a6f3b79..ad68c27 100755 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,3 +1,2 @@ pub mod environment; pub mod interpreter; -pub mod variable_resolution; diff --git a/src/backend/variable_resolution.rs b/src/backend/variable_resolution.rs deleted file mode 100755 index 1c9d515..0000000 --- a/src/backend/variable_resolution.rs +++ /dev/null @@ -1,204 +0,0 @@ -use crate::{ - backend::environment::EnvironmentStack, - common::{ - ast::{AstNode, AstNodeKind, Expr, Stmt}, - lox_result::{runtime_error, LoxError, LoxResult}, - }, - frontend::source_registry::SourceSlice, -}; - -use std::fmt::{Debug, Display}; -struct Resolver { - scopes: EnvironmentStack, -} - -impl Resolver { - pub fn new() -> Self { - Resolver { - scopes: EnvironmentStack::new(), - } - } - - fn declare(&mut self, name: &String) { - if self.scopes.is_empty() { - return; - } - let _ = self.scopes.set(name.clone(), false); - } - - fn define(&mut self, name: &String) { - if self.scopes.is_empty() { - return; - } - let _ = self.scopes.set(name.clone(), true); - } -} - -pub trait StaticAnalyzer { - fn resolve(&mut self, node: &T) -> LoxResult; -} - -impl StaticAnalyzer> for Resolver -where - Resolver: StaticAnalyzer, -{ - fn resolve(&mut self, node: &AstNode) -> LoxResult { - self.resolve(&node.node) - } -} - -impl StaticAnalyzer for Resolver { - fn resolve(&mut self, node: &Stmt) -> LoxResult { - match node { - Stmt::Expression { expression, .. } => { - // Visit the expression - self.resolve(expression.as_ref()) - } - Stmt::Print { expression, .. } => { - // Visit the expression - self.resolve(expression.as_ref()) - } - Stmt::VarDeclaration { - initializer, name, .. - } => { - self.declare(name); - // Visit the initializer if present - if let Some(init) = initializer { - self.resolve(init.as_ref())?; - } - self.define(name); - Ok(true) - } - Stmt::VarAssigment { value, name, .. } => { - match self.scopes.get(name) { - Ok(true) => (), - Ok(false) => { - return Err(LoxError::RuntimeError { - source_slice: SourceSlice::default(), - message: "Cant read loac variable in it own lintilizer".to_string(), - }) - } - Err(err) => return Err(err), - } - self.resolve(value.as_ref()) - } - Stmt::Return { expression, .. } => { - // Visit the return expression - self.resolve(expression.as_ref()) - } - Stmt::Block { statements, .. } => { - self.scopes.push_new_scope(); - // Visit all statements in the block - for stmt in statements.iter() { - self.resolve(stmt)?; - } - self.scopes.pop_scope(); - Ok(true) - } - Stmt::If { - condition, - then_branch, - elif_branch, - else_branch, - .. - } => { - // Visit the condition - self.resolve(condition.as_ref())?; - - // Visit the then branch - self.resolve(then_branch.as_ref())?; - - // Visit all elif branches - for (elif_condition, elif_stmt) in elif_branch.iter() { - self.resolve(elif_condition.as_ref())?; - self.resolve(elif_stmt.as_ref())?; - } - - // Visit the else branch if present - if let Some(else_stmt) = else_branch { - self.resolve(else_stmt.as_ref())?; - } - - Ok(true) - } - Stmt::While { - condition, body, .. - } => { - // Visit the condition - self.resolve(condition.as_ref())?; - - // Visit the body - self.resolve(body.as_ref())?; - Ok(true) - } - Stmt::For { - variable, - condition, - increment, - body, - .. - } => { - // Visit the variable initialization - self.resolve(variable.as_ref())?; - - // Visit the condition - self.resolve(condition.as_ref())?; - - // Visit the increment - self.resolve(increment.as_ref())?; - - // Visit the body - self.resolve(body.as_ref())?; - Ok(true) - } - } - } -} - -impl StaticAnalyzer for Resolver { - fn resolve(&mut self, node: &Expr) -> LoxResult { - match node { - Expr::Literal { .. } => { - // Leaf node - no children to visit - Ok(true) - } - Expr::Binary { left, right, .. } => { - // Visit left operand - self.resolve(left.as_ref())?; - - // Visit right operand - self.resolve(right.as_ref()) - } - Expr::Unary { operand, .. } => { - // Visit the operand - self.resolve(operand.as_ref()) - } - Expr::Grouping { expression } => { - // Visit the grouped expression - self.resolve(expression.as_ref()) - } - Expr::Identifier { name, .. } => { - if !self.scopes.is_empty() && self.scopes.get(name).is_ok() { - return Err(LoxError::ParseError { - source_slice: SourceSlice::default(), - message: "Cant read local varialbe in it own initializer".to_string(), - }); - } - Ok(true) - } - Expr::Call { - callee, arguments, .. - } => { - // Visit the callee - self.resolve(callee.as_ref())?; - - // Visit all arguments - for arg in arguments.iter() { - self.resolve(arg)?; - } - - Ok(true) - } - } - } -} diff --git a/src/common/base_value.rs b/src/common/base_value.rs index 1929a4d..36e996f 100755 --- a/src/common/base_value.rs +++ b/src/common/base_value.rs @@ -314,7 +314,7 @@ pub struct LoxFunction { pub return_type: Option, pub body: AstNode, pub closure: Option>, - pub guard: Option>, + pub guard: Option>>, } impl LoxFunction { @@ -322,7 +322,7 @@ impl LoxFunction { parameters: Vec<(String, String)>, body: AstNode, closure: Option>, - guard: Option>, + guard: Option>>, ) -> Self { LoxFunction { parameters, diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs index 1cc73be..a7a047f 100755 --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -252,11 +252,10 @@ impl Parser { end_position: end_position.end_position, }; - let mut guard: Option> = None; + let mut guard: Option>> = None; if self.peek().token_type == TokenType::LeftBrace { self.consume(TokenType::LeftBrace, "Expected '{' after guard expression")?; - println!("ho trovato una guradia"); - guard = Some(Box::new(self.expression()?.node.clone())); + guard = Some(Box::new(self.expression()?)); self.consume(TokenType::RightBrace, "Expected '}' after guard expression")?; } diff --git a/src/lib.rs b/src/lib.rs index 2d494f2..f030b0c 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ pub mod backend; pub mod common; pub mod frontend; +pub mod middleend; diff --git a/src/middleend/crawler.rs b/src/middleend/crawler.rs deleted file mode 100755 index 8feaa2f..0000000 --- a/src/middleend/crawler.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::common::{ - ast::{AstNode, Expr, Stmt}, - lox_result::LoxResult, -}; - -// Enum per tutti i possibili target di operazioni -enum OperationTarget<'a> { - Stmt(&'a mut Stmt), - Expr(&'a mut Expr), - AstStmt(&'a mut AstNode), - AstExpr(&'a mut AstNode), -} - -// Trait per le operazioni -trait Operation: Send + Sync { - fn execute(&self, target: OperationTarget) -> LoxResult<()>; -} - -// Operazione concreta per set_label -struct SetLabelOperation { - new_label: String, -} - -impl SetLabelOperation { - fn new(label: String) -> Self { - Self { new_label: label } - } -} - -impl Operation for SetLabelOperation { - fn execute(&self, target: OperationTarget) -> LoxResult<()> { - match target { - OperationTarget::Stmt(stmt) => { - if let Stmt::Block { label, .. } = stmt { - *label = self.new_label.clone(); - } - } - OperationTarget::AstStmt(node) => { - if let Stmt::Block { label, .. } = &mut node.node { - *label = self.new_label.clone(); - } - } - _ => {} // Expr non hanno label - } - Ok(()) - } -} - -// Trait per convertire tipi in OperationTarget -trait AsOperationTarget { - fn as_target(&mut self) -> OperationTarget; -} - -impl AsOperationTarget for Stmt { - fn as_target(&mut self) -> OperationTarget { - OperationTarget::Stmt(self) - } -} - -impl AsOperationTarget for Expr { - fn as_target(&mut self) -> OperationTarget { - OperationTarget::Expr(self) - } -} - -impl AsOperationTarget for AstNode { - fn as_target(&mut self) -> OperationTarget { - OperationTarget::AstStmt(self) - } -} - -impl AsOperationTarget for AstNode { - fn as_target(&mut self) -> OperationTarget { - OperationTarget::AstExpr(self) - } -} - -// Crawler generico con Command Pattern -struct Crawler { - node: T, - operations: Vec>, -} - -impl Crawler { - fn new(node: T) -> Self { - Self { - node, - operations: vec![], - } - } - - fn add_operation(&mut self, op: Box) { - self.operations.push(op); - } - - fn execute_operations(&mut self) -> LoxResult<()> { - for op in &self.operations { - op.execute(self.node.as_target())?; - } - Ok(()) - } - - fn with_operation(mut self, op: Box) -> Self { - self.operations.push(op); - self - } - - fn get_node(&self) -> &T { - &self.node - } - - fn get_node_mut(&mut self) -> &mut T { - &mut self.node - } -} diff --git a/src/middleend/mod.rs b/src/middleend/mod.rs index 145a637..05d6ea6 100755 --- a/src/middleend/mod.rs +++ b/src/middleend/mod.rs @@ -1 +1,2 @@ -pub mod crawler; +pub mod variable_resolution; +pub mod visit_ast; diff --git a/src/middleend/variable_resolution.rs b/src/middleend/variable_resolution.rs new file mode 100755 index 0000000..71cfb6c --- /dev/null +++ b/src/middleend/variable_resolution.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; + +use crate::{ + backend::environment::EnvironmentStack, + common::{ + ast::{AstNode, Expr, Stmt}, + lox_result::{LoxError, LoxResult}, + }, + frontend::source_registry::SourceSlice, + middleend::visit_ast::{walk_expr, walk_stmt, Visitor}, +}; + +struct Resolver { + scopes: EnvironmentStack, + locals: HashMap, +} + +impl Resolver { + pub fn new() -> Self { + Resolver { + scopes: EnvironmentStack::new(), + locals: HashMap::new(), + } + } + + fn declare(&mut self, name: &String) { + if self.scopes.is_empty() { + return; + } + let _ = self.scopes.set(name.clone(), false); + } + + fn define(&mut self, name: &String) { + if self.scopes.is_empty() { + return; + } + let _ = self.scopes.set(name.clone(), true); + } + + fn resolve_local(&mut self, name: &String) { + let depth = self.scopes.depth(); + for i in (0..depth).rev() { + if self.scopes.scope_contains(i, name) { + self.locals.insert(, i); + } + } + } +} + +impl Visitor for Resolver { + fn visit_stmt(&mut self, stmt: &AstNode) -> LoxResult<()> { + match &stmt.node { + Stmt::VarDeclaration { name, .. } => { + self.declare(name); + // `walk_stmt` resolves the initializer (if present). + walk_stmt(self, stmt)?; + self.define(name); + Ok(()) + } + Stmt::VarAssigment { name, .. } => { + match self.scopes.get(name) { + Ok(true) => (), + Ok(false) => { + return Err(LoxError::RuntimeError { + source_slice: SourceSlice::default(), + message: "Cant read local variable in it own lintilizer".to_string(), + }) + } + Err(err) => return Err(err), + } + // `walk_stmt` resolves the assigned value. + walk_stmt(self, stmt) + } + Stmt::Block { .. } => { + self.scopes.push_new_scope(); + walk_stmt(self, stmt)?; + self.scopes.pop_scope(); + Ok(()) + } + // Expression, Print, Return, If, While, For: default traversal. + _ => walk_stmt(self, stmt), + } + } + + fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { + match &expr.node { + Expr::Identifier { name, .. } => { + if !self.scopes.is_empty() && self.scopes.get(name).is_ok() { + return Err(LoxError::ParseError { + source_slice: SourceSlice::default(), + message: "Cant read local varialbe in it own initializer".to_string(), + }); + } + Ok(()) + } + _ => walk_expr(self, expr), + } + } +} diff --git a/src/middleend/visit_ast.rs b/src/middleend/visit_ast.rs new file mode 100644 index 0000000..1b1594f --- /dev/null +++ b/src/middleend/visit_ast.rs @@ -0,0 +1,248 @@ +//! A reusable, read-only AST visitor with default traversal. +//! +//! The traversal is split into two layers so that many different static +//! analyses can share a single definition of "how to walk the tree": +//! +//! * The `walk_*` free functions contain the canonical recursion. For each +//! node they call back into the visitor on every child. This is the only +//! place that needs to know the shape of the AST. +//! * The [`Visitor`] trait's `visit_*` methods are the overridable hooks. Each +//! one defaults to calling the matching `walk_*` function, so a visitor that +//! overrides nothing still performs a complete traversal. +//! +//! To implement an analysis, implement [`Visitor`] and override only the hooks +//! you care about. Inside an override, call the corresponding `walk_*` function +//! whenever you want the default "descend into children" behaviour to happen. +//! Accumulate results in your own struct fields (errors, scope tables, type +//! information, ...) rather than through the return value, which is only used +//! to short-circuit on error. +//! +//! # Example +//! +//! ```ignore +//! struct IdentifierCounter { count: usize } +//! +//! impl Visitor for IdentifierCounter { +//! fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { +//! if let Expr::Identifier { .. } = &expr.node { +//! self.count += 1; +//! } +//! walk_expr(self, expr) // keep descending into children +//! } +//! } +//! ``` + +use crate::common::{ + ast::{AstNode, Expr, Stmt}, + base_value::{BaseValue, LoxFunction}, + lox_result::LoxResult, +}; + +/// A read-only visitor over the AST. +/// +/// Every hook has a default implementation that performs the standard +/// recursive traversal, so implementors only override the cases they need. +pub trait Visitor: Sized { + /// Visit a statement node. Defaults to [`walk_stmt`]. + fn visit_stmt(&mut self, stmt: &AstNode) -> LoxResult<()> { + walk_stmt(self, stmt) + } + + /// Visit an expression node. Defaults to [`walk_expr`]. + fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { + walk_expr(self, expr) + } + + /// Visit a function literal (parameters, optional guard and body). + /// + /// Defaults to [`walk_function`], which walks the guard (if any) and the + /// body. Override this to manage a parameter scope before descending. + fn visit_function(&mut self, function: &LoxFunction) -> LoxResult<()> { + walk_function(self, function) + } +} + +/// Recurse into the children of `stmt`, calling back into `visitor`. +pub fn walk_stmt(visitor: &mut V, stmt: &AstNode) -> LoxResult<()> { + match &stmt.node { + Stmt::Expression { expression, .. } => visitor.visit_expr(expression), + Stmt::Print { expression, .. } => visitor.visit_expr(expression), + Stmt::VarDeclaration { initializer, .. } => { + if let Some(initializer) = initializer { + visitor.visit_expr(initializer)?; + } + Ok(()) + } + Stmt::VarAssigment { value, .. } => visitor.visit_expr(value), + Stmt::Return { expression, .. } => visitor.visit_expr(expression), + Stmt::Block { statements, .. } => { + for statement in statements.iter() { + visitor.visit_stmt(statement)?; + } + Ok(()) + } + Stmt::If { + condition, + then_branch, + elif_branch, + else_branch, + .. + } => { + visitor.visit_expr(condition)?; + visitor.visit_stmt(then_branch)?; + for (elif_condition, elif_body) in elif_branch.iter() { + visitor.visit_expr(elif_condition)?; + visitor.visit_stmt(elif_body)?; + } + if let Some(else_body) = else_branch { + visitor.visit_stmt(else_body)?; + } + Ok(()) + } + Stmt::While { + condition, body, .. + } => { + visitor.visit_expr(condition)?; + visitor.visit_stmt(body) + } + Stmt::For { + variable, + condition, + increment, + body, + .. + } => { + visitor.visit_stmt(variable)?; + visitor.visit_expr(condition)?; + visitor.visit_stmt(increment)?; + visitor.visit_stmt(body) + } + } +} + +/// Recurse into the children of `expr`, calling back into `visitor`. +pub fn walk_expr(visitor: &mut V, expr: &AstNode) -> LoxResult<()> { + match &expr.node { + // A function literal carries an entire sub-tree (its body), so it is + // not a leaf: hand it to the dedicated function hook. + Expr::Literal { value } => { + if let BaseValue::Function(function) = value { + visitor.visit_function(function)?; + } + Ok(()) + } + Expr::Identifier { .. } => Ok(()), + Expr::Binary { left, right, .. } => { + visitor.visit_expr(left)?; + visitor.visit_expr(right) + } + Expr::Unary { operand, .. } => visitor.visit_expr(operand), + Expr::Grouping { expression } => visitor.visit_expr(expression), + Expr::Call { + callee, arguments, .. + } => { + visitor.visit_expr(callee)?; + for argument in arguments.iter() { + visitor.visit_expr(argument)?; + } + Ok(()) + } + } +} + +/// Walk the guard (if present) and body of a function literal. +pub fn walk_function(visitor: &mut V, function: &LoxFunction) -> LoxResult<()> { + if let Some(guard) = &function.guard { + visitor.visit_expr(guard)?; + } + visitor.visit_stmt(&function.body) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::common::lox_result::runtime_error; + use crate::frontend::lexer::Lexer; + use crate::frontend::parser::Parser; + + fn parse(src: &str) -> Vec> { + let tokens = Lexer::new(src.to_string(), 0) + .scans_tokens() + .expect("source should lex"); + Parser::new(tokens).parse().expect("source should parse") + } + + /// A visitor that relies entirely on the default traversal and just counts + /// how many statement and expression nodes it sees. + #[derive(Default)] + struct Counter { + stmts: usize, + exprs: usize, + } + + impl Visitor for Counter { + fn visit_stmt(&mut self, stmt: &AstNode) -> LoxResult<()> { + self.stmts += 1; + walk_stmt(self, stmt) + } + + fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { + self.exprs += 1; + walk_expr(self, expr) + } + } + + fn count(src: &str) -> Counter { + let mut counter = Counter::default(); + for stmt in parse(src).iter() { + counter.visit_stmt(stmt).unwrap(); + } + counter + } + + #[test] + fn counts_every_node_via_default_traversal() { + // 1 + 2 * 3 => Binary(+){ Literal, Binary(*){ Literal, Literal } } + let counter = count("1 + 2 * 3;"); + assert_eq!(counter.stmts, 1); + assert_eq!(counter.exprs, 5); + } + + #[test] + fn descends_into_function_bodies() { + // The function body must be traversed through `visit_function`, so the + // `return a;` inside it should contribute to the counts. + let counter = count("f :: fn (a) do return a; end"); + // VarDeclaration + Block + Return + assert_eq!(counter.stmts, 3); + // Function literal + identifier `a` + assert_eq!(counter.exprs, 2); + } + + /// A visitor that aborts as soon as it sees an identifier, used to check + /// that errors short-circuit the traversal. + struct FailOnIdentifier; + + impl Visitor for FailOnIdentifier { + fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { + if let Expr::Identifier { .. } = &expr.node { + return runtime_error(expr.source_slice.clone(), "found an identifier"); + } + walk_expr(self, expr) + } + } + + #[test] + fn errors_propagate_through_traversal() { + let stmts = parse("var x: Int = 1; x;"); + let mut visitor = FailOnIdentifier; + let mut result = Ok(()); + for stmt in stmts.iter() { + result = visitor.visit_stmt(stmt); + if result.is_err() { + break; + } + } + assert!(result.is_err()); + } +} From d40fe2a55090789e21b094e26f760cd800976e09 Mon Sep 17 00:00:00 2001 From: Giulio Agostini Date: Tue, 30 Jun 2026 14:05:46 +0200 Subject: [PATCH 6/9] Introduce NodeId for AST and synthetic slices --- src/backend/environment.rs | 4 +- src/backend/interpreter.rs | 23 +-- src/common/ast.rs | 72 ++++--- src/common/base_value.rs | 14 +- src/frontend/lexer.rs | 85 ++++---- src/frontend/parser.rs | 88 ++++---- src/frontend/source_registry.rs | 23 ++- src/logging/display_ast.rs | 29 +-- src/middleend/variable_resolution.rs | 36 ++-- src/middleend/visit_ast.rs | 2 +- tests/frontend.rs | 298 +++++++++++++++++++++++++++ 11 files changed, 504 insertions(+), 170 deletions(-) create mode 100644 tests/frontend.rs diff --git a/src/backend/environment.rs b/src/backend/environment.rs index b161b57..f6ed87b 100755 --- a/src/backend/environment.rs +++ b/src/backend/environment.rs @@ -48,7 +48,7 @@ impl EnvironmentStack { } } runtime_error( - SourceSlice::default(), // todo change this to the actual source slice + SourceSlice::synthetic(), // todo change this to the actual source slice format!("Undefined variable '{}'", name), ) } @@ -75,7 +75,7 @@ impl EnvironmentStack { } } runtime_error( - SourceSlice::default(), // todo change this to the actual source slice + SourceSlice::synthetic(), // todo change this to the actual source slice format!("Undefined variable '{}'", name), ) } diff --git a/src/backend/interpreter.rs b/src/backend/interpreter.rs index 74b39de..bc6bb1a 100755 --- a/src/backend/interpreter.rs +++ b/src/backend/interpreter.rs @@ -56,6 +56,10 @@ impl EvaluateInterpreter for Interpreter { } Expr::Grouping { expression } => self.evaluate(*expression), Expr::Call { callee, arguments } => self.evaluate_call(callee, arguments), + Expr::Assign { name, value } => { + let value = self.evaluate(*value)?; + self.enviorment.set(name, value) + } } } } @@ -69,9 +73,7 @@ impl EvaluateInterpreter> for Interpreter { Err(LoxError::RuntimeError { message, source_slice, - }) if source_slice == SourceSlice::default() => { - runtime_error(node.source_slice.clone(), message) - } + }) if source_slice.is_synthetic() => runtime_error(node.source_slice.clone(), message), Err(err) => Err(err), } } @@ -180,7 +182,7 @@ impl Interpreter { TokenType::And => Ok(BaseValue::Boolean(left.is_truthy() && right.is_truthy())), TokenType::Or => Ok(BaseValue::Boolean(left.is_truthy() || right.is_truthy())), _ => Err(LoxError::RuntimeError { - source_slice: SourceSlice::default(), + source_slice: SourceSlice::synthetic(), message: format!("Unsupported binary operator: {:?}", operator), }), } @@ -191,13 +193,13 @@ impl Interpreter { TokenType::Minus => match operand { BaseValue::Number(n) => Ok(BaseValue::Number(n.neg())), _ => Err(LoxError::RuntimeError { - source_slice: SourceSlice::default(), + source_slice: SourceSlice::synthetic(), message: "Cannot negate non-numeric value".to_string(), }), }, TokenType::Bang => Ok(!operand), _ => Err(LoxError::RuntimeError { - source_slice: SourceSlice::default(), + source_slice: SourceSlice::synthetic(), message: format!("Unsupported unary operator: {:?}", operator), }), } @@ -222,10 +224,7 @@ impl Interpreter { }; self.enviorment.declare(name.clone(), value) } - Stmt::VarAssigment { name, value, .. } => { - let result = self.evaluate(*value)?; - self.enviorment.set(name.clone(), result) - } + Stmt::If { condition, then_branch, @@ -298,7 +297,7 @@ impl Interpreter { BaseValue::Boolean(false) => continue, _ => { return Err(LoxError::TypeMismatch { - source_slice: SourceSlice::default(), // todo change this to the actual source slice + source_slice: SourceSlice::synthetic(), // todo change this to the actual source slice expected: "boolean".to_string(), found: condition.to_string(), }); @@ -312,7 +311,7 @@ impl Interpreter { } } _ => Err(LoxError::TypeMismatch { - source_slice: SourceSlice::default(), // todo change this to the actual source slice + source_slice: SourceSlice::synthetic(), // todo change this to the actual source slice expected: "boolean".to_string(), found: condition.to_string(), }), diff --git a/src/common/ast.rs b/src/common/ast.rs index a796a28..48c6381 100755 --- a/src/common/ast.rs +++ b/src/common/ast.rs @@ -3,6 +3,25 @@ use crate::{ frontend::{source_registry::SourceSlice, tokens::TokenType}, }; use std::fmt::{Debug, Display}; +use std::sync::atomic::{AtomicUsize, Ordering}; + +/// A unique identity for an AST node, assigned once at construction. +/// +/// Unlike a [`SourceSlice`] (which describes *where* a node is, for +/// diagnostics), a `NodeId` describes *which* node it is. It is stable across +/// clones, so analyses such as the resolver can key per-reference data on it +/// without relying on source positions being unique. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct NodeId(pub usize); + +static NEXT_NODE_ID: AtomicUsize = AtomicUsize::new(0); + +impl NodeId { + /// Allocate the next globally-unique node id. + pub fn next() -> Self { + NodeId(NEXT_NODE_ID.fetch_add(1, Ordering::Relaxed)) + } +} /* * grammar: * program -> statement* EOF @@ -10,7 +29,6 @@ use std::fmt::{Debug, Display}; * | print_statement * | var_statement * | block_statement - * | assignment_statement * | if_statement * * expression_statement -> expression ";" @@ -18,7 +36,6 @@ use std::fmt::{Debug, Display}; * var_statement -> ("var"|"dyn"|"mut")? IDENTIFIER (":" IDENTIFIER)? (("=" expression)? ";")| function_declaration * function_declaration -> "::" "(" parameters? ")" ("->" IDENTIFIER)? block_statement * parameters -> ( IDENTIFIER (":" IDENTIFIER)? ("," IDENTIFIER (":" IDENTIFIER)? )* ) - * assignment_statement -> IDENTIFIER "=" expression ";" * block_statement -> "do" statement* "end" * if_statement -> "if" expression "then" statement ("elif" expression "then" statement)* ("else" statement)? "end" * while_statement -> "while" expression "do" statement "end" @@ -61,6 +78,10 @@ pub enum Expr { callee: Box>, arguments: Vec>, }, + Assign { + name: String, + value: Box>, + }, } // Implementazione Display per Expr (per debugging) @@ -86,6 +107,7 @@ impl std::fmt::Display for Expr { .collect::>() .join(", ") ), + Expr::Assign { name, value } => write!(f, "Assign ({} = {})", name, value.node), } } } @@ -112,6 +134,7 @@ impl Debug for Expr { .collect::>() .join(", ") ), + Expr::Assign { name, value } => write!(f, "(assign {:?} {:?})", name, value.node), } } } @@ -131,11 +154,6 @@ pub enum Stmt { initializer: Option>>, return_value: Box, }, - VarAssigment { - name: String, - value: Box>, - return_value: Box, - }, Return { expression: Box>, label: String, @@ -208,15 +226,6 @@ impl Display for Stmt { Some(init) => write!(f, "Var({} = {}) -> {:?};", name, init.node, return_value), None => write!(f, "Var({}) -> {:?};", name, return_value), }, - Stmt::VarAssigment { - name, - value, - return_value, - } => write!( - f, - "Assign({} = {}) -> {:?};", - name, value.node, return_value - ), Stmt::Return { expression, return_value, @@ -319,17 +328,6 @@ impl Debug for Stmt { ), None => write!(f, "Var({:?}) -> {:?};", name, return_value), }, - Stmt::VarAssigment { - name, - value, - return_value, - } => { - write!( - f, - "Assign({:?} = {:?}) -> {:?};", - name, value.node, return_value - ) - } Stmt::Return { expression, return_value, @@ -397,6 +395,7 @@ impl AstNodeKind for Expr { Expr::Grouping { .. } => "grouping expression", Expr::Identifier { .. } => "variable expression", Expr::Call { .. } => "call expression", + Expr::Assign { .. } => "assignment expression", } } } @@ -406,7 +405,6 @@ impl AstNodeKind for Stmt { match self { Stmt::Expression { .. } => "expression", Stmt::VarDeclaration { .. } => "variable declaration", - Stmt::VarAssigment { .. } => "assignment", Stmt::Return { .. } => "return statement", Stmt::Block { .. } => "block statement", Stmt::If { .. } => "if statement", @@ -417,12 +415,22 @@ impl AstNodeKind for Stmt { } } -#[derive(Clone, PartialEq, Default)] +#[derive(Clone)] pub struct AstNode { + /// Stable identity, assigned at construction and preserved across clones. + pub id: NodeId, pub node: T, pub source_slice: SourceSlice, } +// Identity (`id`) deliberately does not participate in equality: two nodes are +// equal when their content and location match, regardless of node id. +impl PartialEq for AstNode { + fn eq(&self, other: &Self) -> bool { + self.node == other.node && self.source_slice == other.source_slice + } +} + impl Display for AstNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( @@ -445,6 +453,10 @@ impl Debug for AstNode { impl AstNode { pub fn new(node: T, source_slice: SourceSlice) -> Self { - AstNode { node, source_slice } + AstNode { + id: NodeId::next(), + node, + source_slice, + } } } diff --git a/src/common/base_value.rs b/src/common/base_value.rs index 36e996f..bc6ed9c 100755 --- a/src/common/base_value.rs +++ b/src/common/base_value.rs @@ -398,7 +398,7 @@ impl Add for BaseValue { Ok(BaseValue::String(format!("{}{}", a, b))) } _ => runtime_error( - SourceSlice::default(), + SourceSlice::synthetic(), "Cannot add non-numeric values".to_string(), ), } @@ -428,7 +428,7 @@ impl Sub for BaseValue { match (self, other) { (BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.sub(b))), _ => runtime_error( - SourceSlice::default(), + SourceSlice::synthetic(), "Cannot subtract non-numeric values".to_string(), ), } @@ -442,10 +442,10 @@ impl Div for BaseValue { match (self, other) { (BaseValue::Number(a), BaseValue::Number(b)) => match a.div(b) { Some(result) => Ok(BaseValue::Number(result)), - None => runtime_error(SourceSlice::default(), "Division by zero".to_string()), + None => runtime_error(SourceSlice::synthetic(), "Division by zero".to_string()), }, _ => runtime_error( - SourceSlice::default(), + SourceSlice::synthetic(), "Cannot divide non-numeric values".to_string(), ), } @@ -459,7 +459,7 @@ impl Mul for BaseValue { match (self, other) { (BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.mul(b))), _ => runtime_error( - SourceSlice::default(), + SourceSlice::synthetic(), "Cannot multiply non-numeric values".to_string(), ), } @@ -473,10 +473,10 @@ impl Rem for BaseValue { match (self, other) { (BaseValue::Number(a), BaseValue::Number(b)) => match a.rem(b) { Some(result) => Ok(BaseValue::Number(result)), - None => runtime_error(SourceSlice::default(), "Division by zero".to_string()), + None => runtime_error(SourceSlice::synthetic(), "Division by zero".to_string()), }, _ => runtime_error( - SourceSlice::default(), + SourceSlice::synthetic(), "Cannot divide non-numeric values".to_string(), ), } diff --git a/src/frontend/lexer.rs b/src/frontend/lexer.rs index 5befaad..5c65137 100755 --- a/src/frontend/lexer.rs +++ b/src/frontend/lexer.rs @@ -5,8 +5,9 @@ use crate::frontend::tokens::{Token, TokenType}; pub struct Lexer { input: String, - start_char: usize, - current_char: usize, + // Byte offsets into `input` (not char counts) so slicing is UTF-8 correct. + start: usize, + current: usize, start_pos: SourcePosition, end_pos: SourcePosition, source_id: SourceId, @@ -48,28 +49,18 @@ impl Lexer { pub fn new(input: String, source_id: SourceId) -> Lexer { Lexer { input, - start_char: 0, - current_char: 0, + start: 0, + current: 0, start_pos: SourcePosition::default(), end_pos: SourcePosition::default(), source_id, } } - fn advance_column(&mut self) { - self.current_char += 1; - self.end_pos.column += 1; - } - - fn advance_line(&mut self) { - self.end_pos.line += 1; - self.end_pos.column = 0; - } - pub fn scans_tokens(&mut self) -> LoxResult> { let mut tokens = Vec::new(); while !self.is_at_end() { - self.start_char = self.current_char; + self.start = self.current; self.start_pos = self.end_pos.clone(); match self.scan_token() { Ok(Some(token)) => tokens.push(token), @@ -82,32 +73,31 @@ impl Lexer { } fn is_at_end(&self) -> bool { - self.current_char >= self.input.len() + self.current >= self.input.len() } fn advance(&mut self) -> char { - self.advance_column(); - self.input.chars().nth(self.current_char - 1).unwrap() + let c = self.input[self.current..].chars().next().unwrap(); + self.current += c.len_utf8(); + if c == '\n' { + self.end_pos.line += 1; + self.end_pos.column = 0; + } else { + self.end_pos.column += 1; + } + c } fn peek(&self) -> char { - if self.is_at_end() { - '\0' - } else { - self.input.chars().nth(self.current_char).unwrap() - } + self.input[self.current..].chars().next().unwrap_or('\0') } fn peek_next(&self) -> char { - if self.current_char + 1 >= self.input.len() { - '\0' - } else { - self.input.chars().nth(self.current_char + 1).unwrap() - } + self.input[self.current..].chars().nth(1).unwrap_or('\0') } fn make_token(&self, token_type: TokenType) -> Token { - let text = self.input[self.start_char..self.current_char].to_string(); + let text = self.input[self.start..self.current].to_string(); Token::new( token_type, text, @@ -119,7 +109,7 @@ impl Lexer { ) } fn make_token_with_literal(&self, token_type: TokenType, literal: BaseValue) -> Token { - let text = self.input[self.start_char..self.current_char].to_string(); + let text = self.input[self.start..self.current].to_string(); Token::new_complete( token_type, text, @@ -179,9 +169,6 @@ impl Lexer { ('/', '*') => { // Commento multi-line while (self.peek() != '*' || self.peek_next() != '/') && !self.is_at_end() { - if self.peek() == '\n' { - self.advance_line(); - } self.advance(); } if self.is_at_end() { @@ -201,10 +188,7 @@ impl Lexer { } ('/', _) => Ok(Some(self.make_token(TokenType::Slash))), (' ', _) | ('\r', _) | ('\t', _) => Ok(None), - ('\n', _) => { - self.advance_line(); - Ok(None) - } + ('\n', _) => Ok(None), ('"', _) => self.string(), (c, _) if c.is_digit(10) => self.number(), (c, _) if c.is_alphanumeric() || c == '_' => self.identifier(), @@ -236,7 +220,7 @@ impl Lexer { self.advance(); Ok(Some(self.make_token_with_literal( TokenType::String, - BaseValue::String(self.input[self.start_char..self.current_char].to_string()), + BaseValue::String(self.input[self.start..self.current].to_string()), ))) } @@ -266,7 +250,7 @@ impl Lexer { None }; - let num_str = &self.input[self.start_char..self.current_char]; + let num_str = &self.input[self.start..self.current]; let num_str_without_suffix = if suffix.is_some() { &num_str[..num_str.len() - 1] } else { @@ -303,7 +287,7 @@ impl Lexer { while self.peek().is_alphanumeric() || self.peek() == '_' { self.advance(); } - let text = self.input[self.start_char..self.current_char].to_string(); + let text = self.input[self.start..self.current].to_string(); match get_keyword_token(&text) { Some(TokenType::True) => Ok(Some( self.make_token_with_literal(TokenType::True, BaseValue::Boolean(true)), @@ -509,4 +493,25 @@ mod tests { let result = Lexer::new("@".to_string(), 0).scans_tokens(); assert!(result.is_err()); } + + #[test] + fn handles_multibyte_identifier() { + // `รฉ` is two UTF-8 bytes; the old char-counted slicing would panic or + // slice mid-codepoint here. Byte offsets make this correct. + let tokens = lex("cafรฉ"); + assert_eq!(tokens[0].token_type, TokenType::Identifier); + assert_eq!( + tokens[0].literal, + Some(BaseValue::String("cafรฉ".to_string())) + ); + } + + #[test] + fn tracks_line_and_column_across_newlines() { + // "1\n22": the second token sits at the start of line 1. + let tokens = lex("1\n22"); + assert_eq!(tokens[1].lexeme, "22"); + assert_eq!(tokens[1].source_slice.start_position.line, 1); + assert_eq!(tokens[1].source_slice.start_position.column, 0); + } } diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs index a7a047f..1a279c0 100755 --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -53,7 +53,6 @@ impl Parser { self.var_statement() } (TokenType::Identifier, TokenType::Colon) => self.var_statement(), - (TokenType::Identifier, TokenType::Equal) => self.assignment_statement(), (TokenType::If, _) => self.if_statement(), (TokenType::While, _) => self.while_statement(), (TokenType::For, _) => self.for_statement(), @@ -68,7 +67,7 @@ impl Parser { let variable = self.var_statement()?; let condition = self.expression()?; self.consume(TokenType::Semicolon, "Expected ';' after for condition")?; - let increment = self.assignment_statement()?; + let increment = self.expression_statement()?; let body = self.statement(Some("loop".to_string()))?; let end_slice = self.previous().source_slice.clone(); @@ -260,8 +259,8 @@ impl Parser { } let body = self.statement(None)?; - let node = AstNode { - node: Expr::Literal { + let node = AstNode::new( + Expr::Literal { value: BaseValue::Function(LoxFunction { parameters, return_type: None, @@ -270,8 +269,8 @@ impl Parser { guard, }), }, - source_slice: combine_position.clone(), - }; + combine_position.clone(), + ); Ok(AstNode::new( Stmt::VarDeclaration { name: name_lexeme, @@ -294,12 +293,12 @@ impl Parser { } // todo: make type annotation } - let mut value = AstNode { - node: Expr::Literal { + let mut value = AstNode::new( + Expr::Literal { value: BaseValue::Nil, }, - source_slice: start_slice.clone(), - }; + start_slice.clone(), + ); if self.peek().token_type == TokenType::Equal { self.advance(); value = self.expression()?; @@ -323,31 +322,6 @@ impl Parser { combined_slice, )) } - 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()?; - let semicolon = self.consume( - TokenType::Semicolon, - "Expect ';' after variable declaration.", - )?; - 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::VarAssigment { - name: name_lexeme, - value: Box::new(value), - return_value: Box::new(BaseValue::Nil), - }, - combined_slice, - )) - } fn print_statement(&mut self) -> LoxResult> { // consume the print keyword @@ -441,12 +415,12 @@ impl Parser { source_slice, }) => { if message == "Expect expression." { - AstNode { - node: Expr::Literal { + AstNode::new( + Expr::Literal { value: BaseValue::Nil, }, - source_slice: start_slice.clone(), - } + start_slice.clone(), + ) } else { return Err(LoxError::ParseError { message, @@ -479,7 +453,34 @@ impl Parser { } fn expression(&mut self) -> LoxResult> { - self.or_and() + self.assignment() + } + + fn assignment(&mut self) -> LoxResult> { + let expr = self.or_and()?; + if self.peek().token_type == TokenType::Equal { + self.advance(); // consume '=' + // Right-associative: `a = b = c` parses as `a = (b = c)`. + let value = self.assignment()?; + let combined_slice = SourceSlice::from_positions( + expr.source_slice.source_id, + expr.source_slice.start_position.clone(), + value.source_slice.end_position.clone(), + ); + let target_slice = expr.source_slice.clone(); + match expr.node { + Expr::Identifier { name } => Ok(AstNode::new( + Expr::Assign { + name, + value: Box::new(value), + }, + combined_slice, + )), + _ => parse_error(target_slice, "Invalid assignment target."), + } + } else { + Ok(expr) + } } fn or_and(&mut self) -> LoxResult> { @@ -978,8 +979,11 @@ mod tests { fn parses_assignment_statement() { let stmts = parse_ok("x = 5;"); match &stmts[0].node { - Stmt::VarAssigment { name, .. } => assert_eq!(name, "x"), - other => panic!("expected assignment statement, got {:?}", other), + Stmt::Expression { expression, .. } => match &expression.node { + Expr::Assign { name, .. } => assert_eq!(name, "x"), + other => panic!("expected assign expression, got {:?}", other), + }, + other => panic!("expected expression statement, got {:?}", other), } } diff --git a/src/frontend/source_registry.rs b/src/frontend/source_registry.rs index 27ae1ef..7c245fa 100755 --- a/src/frontend/source_registry.rs +++ b/src/frontend/source_registry.rs @@ -51,7 +51,7 @@ impl SourceFile { } } -#[derive(Debug, Clone, PartialEq, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] pub struct SourcePosition { pub line: usize, pub column: usize, @@ -63,7 +63,7 @@ impl Display for SourcePosition { } } -#[derive(Clone, PartialEq, Default)] +#[derive(Clone, PartialEq, Eq, Hash)] pub struct SourceSlice { pub source_id: SourceId, pub start_position: SourcePosition, @@ -91,6 +91,25 @@ impl Display for SourceSlice { } impl SourceSlice { + /// An explicit, non-located placeholder span. + /// + /// Use this only when a real span is genuinely unavailable. Unlike the old + /// `Default` impl, it is greppable and obviously intentional, so it can't be + /// produced by accident. + pub fn synthetic() -> Self { + Self { + source_id: 0, + start_position: SourcePosition::default(), + end_position: SourcePosition::default(), + } + } + + /// Whether this span is the non-located placeholder produced by + /// [`SourceSlice::synthetic`]. + pub fn is_synthetic(&self) -> bool { + *self == Self::synthetic() + } + pub fn from_positions( source_id: SourceId, start_position: SourcePosition, diff --git a/src/logging/display_ast.rs b/src/logging/display_ast.rs index 1418c50..04775c0 100755 --- a/src/logging/display_ast.rs +++ b/src/logging/display_ast.rs @@ -234,6 +234,20 @@ impl PrettyPrint for Expr { write!(f, "{}Identifier {{ name: {:?} }}", indent, name) } } + Expr::Assign { name, value } => { + if ctx.config.compact { + let value_str = + pretty_print_with_config(value.as_ref(), &PrettyConfig::compact()); + write!(f, "{} = {}", name, value_str) + } else { + writeln!(f, "{}Assign {{", indent)?; + writeln!(f, "{}name: {:?},", ctx.child_context(false).indent(), name)?; + write!(f, "{}value: ", ctx.child_context(true).indent())?; + value.pretty_print(&ctx.child_context(true), f)?; + writeln!(f)?; + write!(f, "{}}}", indent) + } + } Expr::Call { callee, arguments } => { if ctx.config.compact { write!(f, "{}", callee) @@ -317,20 +331,7 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::VarAssigment { name, value, .. } => { - if ctx.config.compact { - let value_str = - pretty_print_with_config(value.as_ref(), &PrettyConfig::compact()); - write!(f, "{} = {};", name, value_str) - } else { - writeln!(f, "{}Assign {{", indent)?; - writeln!(f, "{}name: {:?},", ctx.child_context(false).indent(), name)?; - write!(f, "{}value: ", ctx.child_context(true).indent())?; - value.pretty_print(&ctx.child_context(true), f)?; - writeln!(f)?; - write!(f, "{}}}", indent) - } - } + Stmt::Return { expression, .. } => { if ctx.config.compact { let expr_str = diff --git a/src/middleend/variable_resolution.rs b/src/middleend/variable_resolution.rs index 71cfb6c..32467b5 100755 --- a/src/middleend/variable_resolution.rs +++ b/src/middleend/variable_resolution.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use crate::{ backend::environment::EnvironmentStack, common::{ - ast::{AstNode, Expr, Stmt}, + ast::{AstNode, Expr, NodeId, Stmt}, lox_result::{LoxError, LoxResult}, }, frontend::source_registry::SourceSlice, @@ -12,7 +12,7 @@ use crate::{ struct Resolver { scopes: EnvironmentStack, - locals: HashMap, + locals: HashMap, } impl Resolver { @@ -27,6 +27,7 @@ impl Resolver { if self.scopes.is_empty() { return; } + let scope = self.scopes.peek(); let _ = self.scopes.set(name.clone(), false); } @@ -37,13 +38,16 @@ impl Resolver { let _ = self.scopes.set(name.clone(), true); } - fn resolve_local(&mut self, name: &String) { + fn resolve_local(&mut self, id: NodeId, name: &String) { let depth = self.scopes.depth(); for i in (0..depth).rev() { if self.scopes.scope_contains(i, name) { - self.locals.insert(, i); + // Distance = how many scopes up from the innermost the binding lives. + self.locals.insert(id, depth - 1 - i); + return; } } + // Not found in any tracked scope: assume global, record nothing. } } @@ -57,20 +61,8 @@ impl Visitor for Resolver { self.define(name); Ok(()) } - Stmt::VarAssigment { name, .. } => { - match self.scopes.get(name) { - Ok(true) => (), - Ok(false) => { - return Err(LoxError::RuntimeError { - source_slice: SourceSlice::default(), - message: "Cant read local variable in it own lintilizer".to_string(), - }) - } - Err(err) => return Err(err), - } - // `walk_stmt` resolves the assigned value. - walk_stmt(self, stmt) - } + // NOTE: assignment is now an `Expr::Assign`, not a statement. + // Add an `Expr::Assign` arm to `visit_expr` to resolve assignments. Stmt::Block { .. } => { self.scopes.push_new_scope(); walk_stmt(self, stmt)?; @@ -87,11 +79,15 @@ impl Visitor for Resolver { Expr::Identifier { name, .. } => { if !self.scopes.is_empty() && self.scopes.get(name).is_ok() { return Err(LoxError::ParseError { - source_slice: SourceSlice::default(), + source_slice: SourceSlice::synthetic(), message: "Cant read local varialbe in it own initializer".to_string(), }); } - Ok(()) + walk_expr(self, expr) + } + Expr::Assign { name, .. } => { + self.resolve_local(expr.id, name); + walk_expr(self, expr) } _ => walk_expr(self, expr), } diff --git a/src/middleend/visit_ast.rs b/src/middleend/visit_ast.rs index 1b1594f..35471d6 100644 --- a/src/middleend/visit_ast.rs +++ b/src/middleend/visit_ast.rs @@ -73,7 +73,6 @@ pub fn walk_stmt(visitor: &mut V, stmt: &AstNode) -> LoxResult } Ok(()) } - Stmt::VarAssigment { value, .. } => visitor.visit_expr(value), Stmt::Return { expression, .. } => visitor.visit_expr(expression), Stmt::Block { statements, .. } => { for statement in statements.iter() { @@ -137,6 +136,7 @@ pub fn walk_expr(visitor: &mut V, expr: &AstNode) -> LoxResult visitor.visit_expr(right) } Expr::Unary { operand, .. } => visitor.visit_expr(operand), + Expr::Assign { value, .. } => visitor.visit_expr(value), Expr::Grouping { expression } => visitor.visit_expr(expression), Expr::Call { callee, arguments, .. diff --git a/tests/frontend.rs b/tests/frontend.rs new file mode 100644 index 0000000..5ee5103 --- /dev/null +++ b/tests/frontend.rs @@ -0,0 +1,298 @@ +//! End-to-end frontend tests: source text -> lexer -> parser -> AST. +//! +//! These exercise the lexer and parser together through the public API and +//! assert on the resulting AST, complementing the per-module unit tests inside +//! `src/frontend/{lexer,parser}.rs`. + +use rlox::common::ast::{AstNode, Expr, Stmt}; +use rlox::common::base_value::{BaseValue, Number}; +use rlox::frontend::lexer::Lexer; +use rlox::frontend::parser::Parser; +use rlox::frontend::tokens::TokenType; + +/// Run the full frontend pipeline on `src`. +fn parse(src: &str) -> Result>, String> { + let tokens = Lexer::new(src.to_string(), 0) + .scans_tokens() + .map_err(|e| format!("lex error: {e}"))?; + Parser::new(tokens) + .parse() + .map_err(|e| format!("parse error: {e}")) +} + +/// Parse `src`, panicking if the frontend reports any error. +fn parse_ok(src: &str) -> Vec> { + parse(src).expect("expected source to lex and parse") +} + +/// Parse `src` and return the single statement it should produce. +fn single_stmt(src: &str) -> Stmt { + let mut stmts = parse_ok(src); + assert_eq!(stmts.len(), 1, "expected exactly one statement for {src:?}"); + stmts.remove(0).node +} + +/// Parse `src` and return the expression of its single expression-statement. +fn single_expr(src: &str) -> Expr { + match single_stmt(src) { + Stmt::Expression { expression, .. } => expression.node, + other => panic!("expected expression statement, got {other:?}"), + } +} + +// --------------------------------------------------------------------------- +// Expressions +// --------------------------------------------------------------------------- + +#[test] +fn lexes_and_parses_number_literal() { + match single_expr("42;") { + Expr::Literal { value } => assert_eq!(value, BaseValue::Number(Number::I32(42))), + other => panic!("expected literal, got {other:?}"), + } +} + +#[test] +fn parses_string_and_boolean_literals() { + assert!(matches!(single_expr("true;"), Expr::Literal { .. })); + assert!(matches!(single_expr("\"hi\";"), Expr::Literal { .. })); +} + +#[test] +fn multiplication_binds_tighter_than_addition() { + // 1 + 2 * 3 => (+ 1 (* 2 3)) + match single_expr("1 + 2 * 3;") { + Expr::Binary { + operator, right, .. + } => { + assert_eq!(operator, TokenType::Plus); + assert!(matches!( + right.node, + Expr::Binary { + operator: TokenType::Star, + .. + } + )); + } + other => panic!("expected binary, got {other:?}"), + } +} + +#[test] +fn grouping_overrides_precedence() { + // (1 + 2) * 3 => (* (group (+ 1 2)) 3) + match single_expr("(1 + 2) * 3;") { + Expr::Binary { operator, left, .. } => { + assert_eq!(operator, TokenType::Star); + assert!(matches!(left.node, Expr::Grouping { .. })); + } + other => panic!("expected binary, got {other:?}"), + } +} + +#[test] +fn parses_unary_operators() { + assert!(matches!( + single_expr("-5;"), + Expr::Unary { + operator: TokenType::Minus, + .. + } + )); + assert!(matches!( + single_expr("!true;"), + Expr::Unary { + operator: TokenType::Bang, + .. + } + )); +} + +#[test] +fn parses_comparison_and_equality() { + assert!(matches!( + single_expr("1 < 2;"), + Expr::Binary { + operator: TokenType::Less, + .. + } + )); + assert!(matches!( + single_expr("1 == 2;"), + Expr::Binary { + operator: TokenType::EqualEqual, + .. + } + )); +} + +#[test] +fn parses_logical_operators() { + assert!(matches!( + single_expr("true or false;"), + Expr::Binary { + operator: TokenType::Or, + .. + } + )); + assert!(matches!( + single_expr("true and false;"), + Expr::Binary { + operator: TokenType::And, + .. + } + )); +} + +#[test] +fn parses_call_with_arguments() { + match single_expr("add(1, 2);") { + Expr::Call { callee, arguments } => { + assert!(matches!(callee.node, Expr::Identifier { .. })); + assert_eq!(arguments.len(), 2); + } + other => panic!("expected call, got {other:?}"), + } +} + +// --------------------------------------------------------------------------- +// Assignment (now an expression) +// --------------------------------------------------------------------------- + +#[test] +fn assignment_is_an_expression_statement() { + match single_expr("x = 5;") { + Expr::Assign { name, .. } => assert_eq!(name, "x"), + other => panic!("expected assign, got {other:?}"), + } +} + +#[test] +fn assignment_is_right_associative() { + // a = b = c => (assign a (assign b c)) + match single_expr("a = b = c;") { + Expr::Assign { name, value } => { + assert_eq!(name, "a"); + match value.node { + Expr::Assign { name, .. } => assert_eq!(name, "b"), + other => panic!("expected nested assign, got {other:?}"), + } + } + other => panic!("expected assign, got {other:?}"), + } +} + +#[test] +fn assignment_to_non_identifier_is_an_error() { + assert!(parse("1 = 2;").is_err()); +} + +// --------------------------------------------------------------------------- +// Statements +// --------------------------------------------------------------------------- + +#[test] +fn parses_var_declaration() { + match single_stmt("x := 5;") { + Stmt::VarDeclaration { + name, initializer, .. + } => { + assert_eq!(name, "x"); + assert!(initializer.is_some()); + } + other => panic!("expected var declaration, got {other:?}"), + } +} + +#[test] +fn parses_print_statement() { + assert!(matches!(single_stmt("print 1;"), Stmt::Print { .. })); +} + +#[test] +fn parses_block_with_multiple_statements() { + match single_stmt("do print 1; print 2; end") { + Stmt::Block { statements, .. } => assert_eq!(statements.len(), 2), + other => panic!("expected block, got {other:?}"), + } +} + +#[test] +fn parses_if_else() { + match single_stmt("if true then print 1; else print 2;") { + Stmt::If { + else_branch: Some(_), + .. + } => {} + other => panic!("expected if/else, got {other:?}"), + } +} + +#[test] +fn parses_while_loop() { + assert!(matches!( + single_stmt("while true do print 1; end"), + Stmt::While { .. } + )); +} + +#[test] +fn parses_for_loop_with_assignment_increment() { + match single_stmt("for i := 0; i <= 2; i = i + 1; do print i; end") { + Stmt::For { increment, .. } => match increment.node { + // The increment desugars to an expression statement holding an assignment. + Stmt::Expression { expression, .. } => { + assert!(matches!(expression.node, Expr::Assign { .. })) + } + other => panic!("expected expression-statement increment, got {other:?}"), + }, + other => panic!("expected for loop, got {other:?}"), + } +} + +#[test] +fn parses_function_declaration() { + match single_stmt("add :: fn (a, b) do return a + b; end") { + Stmt::VarDeclaration { + name, initializer, .. + } => { + assert_eq!(name, "add"); + match initializer { + Some(init) => assert!(matches!( + init.node, + Expr::Literal { + value: BaseValue::Function(_) + } + )), + None => panic!("expected a function initializer"), + } + } + other => panic!("expected function declaration, got {other:?}"), + } +} + +// --------------------------------------------------------------------------- +// Whole-program / error handling +// --------------------------------------------------------------------------- + +#[test] +fn parses_a_multi_statement_program() { + let stmts = parse_ok("x := 1; y := 2; print x + y;"); + assert_eq!(stmts.len(), 3); +} + +#[test] +fn lexer_errors_surface_through_the_frontend() { + // Unterminated string is a lexical error reported by the lexer stage. + assert!(parse("\"unterminated;").is_err()); +} + +#[test] +fn missing_semicolon_is_a_parse_error() { + assert!(parse("print 1").is_err()); +} + +#[test] +fn unclosed_grouping_is_a_parse_error() { + assert!(parse("(1 + 2;").is_err()); +} From 9f15a00b98bf7751c63ee248201775467c549576 Mon Sep 17 00:00:00 2001 From: Giulio Agostini Date: Tue, 30 Jun 2026 15:05:34 +0200 Subject: [PATCH 7/9] Add static variable resolution with scope tracking - Add distance-based get_at on EnvironmentStack - Add distance-based assign_at on EnvironmentStack - Introduce ErrorSink to accumulate diagnostics across passes - Implement Resolver with a ScopeStack and per-node distance map - Extend interpreter to store locals distances for runtime lookup - Add tests for resolution behavior and error accumulation Add static variable resolution with scope tracking --- src/backend/environment.rs | 32 ++++ src/backend/interpreter.rs | 171 ++++++++++-------- src/common/lox_result.rs | 87 +++++++++ src/main.rs | 22 ++- src/middleend/mod.rs | 1 + src/middleend/scope_stack.rs | 146 +++++++++++++++ src/middleend/variable_resolution.rs | 254 ++++++++++++++++++++++----- tests/resolution.rs | 69 ++++++++ 8 files changed, 666 insertions(+), 116 deletions(-) create mode 100644 src/middleend/scope_stack.rs create mode 100644 tests/resolution.rs diff --git a/src/backend/environment.rs b/src/backend/environment.rs index f6ed87b..2ba5832 100755 --- a/src/backend/environment.rs +++ b/src/backend/environment.rs @@ -58,6 +58,38 @@ impl EnvironmentStack { Ok(value) } + /// Read a variable at a known scope `distance` from the innermost scope + /// (0 = innermost). Used with resolver-computed distances. + pub fn get_at(&self, distance: usize, name: &str) -> LoxResult { + let index = self.stack.len().checked_sub(distance + 1); + match index + .and_then(|i| self.stack.get(i)) + .and_then(|s| s.get(name)) + { + Some(value) => Ok(value.clone()), + None => runtime_error( + SourceSlice::synthetic(), + format!("Undefined variable '{}'", name), + ), + } + } + + /// Assign to a variable at a known scope `distance` from the innermost + /// scope (0 = innermost). + pub fn assign_at(&mut self, distance: usize, name: String, value: T) -> LoxResult { + let index = self.stack.len().checked_sub(distance + 1); + match index { + Some(i) if i < self.stack.len() => { + self.stack[i].insert(name, value.clone()); + Ok(value) + } + _ => runtime_error( + SourceSlice::synthetic(), + format!("Undefined variable '{}'", name), + ), + } + } + pub fn set(&mut self, name: String, value: T) -> LoxResult { let size = self.stack.len(); diff --git a/src/backend/interpreter.rs b/src/backend/interpreter.rs index bc6bb1a..e73f6c5 100755 --- a/src/backend/interpreter.rs +++ b/src/backend/interpreter.rs @@ -1,69 +1,26 @@ use crate::{ backend::environment::EnvironmentStack, common::{ - ast::{AstNode, AstNodeKind, Expr, Stmt}, + ast::{AstNode, Expr, NodeId, Stmt}, base_value::{BaseValue, NativeFunction, Number, Truthy}, lox_result::{runtime_error, LoxError, LoxResult}, }, frontend::{source_registry::SourceSlice, tokens::TokenType}, }; -use std::fmt::{Debug, Display}; +use std::collections::HashMap; pub struct Interpreter { enviorment: EnvironmentStack, + /// Per-reference scope distances from the resolver. Empty means "no + /// resolution pass was run", in which case lookups fall back to a dynamic + /// search of the scope chain. + locals: HashMap, } pub trait EvaluateInterpreter { fn evaluate(&mut self, stmt: T) -> LoxResult; } -impl EvaluateInterpreter> for Interpreter -where - Interpreter: EvaluateInterpreter, -{ - fn evaluate(&mut self, stmt: AstNode) -> LoxResult { - match self.evaluate(stmt.node.clone()) { - Ok(value) => Ok(value), - Err(err) => runtime_error(stmt.source_slice, err.get_message()), - } - } -} - -// Direct Expr evaluation to avoid infinite recursion -impl EvaluateInterpreter for Interpreter { - fn evaluate(&mut self, expr: Expr) -> LoxResult { - match expr { - Expr::Literal { value } => match value { - BaseValue::Function(mut func) => { - func.closure = Some(self.enviorment.clone()); - Ok(BaseValue::Function(func)) - } - _ => Ok(value), - }, - Expr::Identifier { name } => self.enviorment.get(&name), - Expr::Binary { - left, - operator, - right, - } => { - let left_val = self.evaluate(*left)?; - let right_val = self.evaluate(*right)?; - self.evaluate_binary(left_val, operator, right_val) - } - Expr::Unary { operator, operand } => { - let operand_val = self.evaluate(*operand)?; - self.evaluate_unary(operator, operand_val) - } - Expr::Grouping { expression } => self.evaluate(*expression), - Expr::Call { callee, arguments } => self.evaluate_call(callee, arguments), - Expr::Assign { name, value } => { - let value = self.evaluate(*value)?; - self.enviorment.set(name, value) - } - } - } -} - impl EvaluateInterpreter> for Interpreter { fn evaluate(&mut self, node: AstNode) -> LoxResult { let stmt = node.node; @@ -93,27 +50,93 @@ impl Interpreter { )) })), ); - Self { enviorment: env } + Self { + enviorment: env, + locals: HashMap::new(), + } } + + /// Install the resolver's per-reference scope distances. + pub fn set_locals(&mut self, locals: HashMap) { + self.locals = locals; + } + + /// Evaluate an expression node, threading its `NodeId` so resolved + /// variables and assignments can use their precomputed scope distance. + fn eval_expr(&mut self, node: &AstNode) -> LoxResult { + let result = match &node.node { + Expr::Literal { value } => match value { + BaseValue::Function(func) => { + let mut func = func.clone(); + func.closure = Some(self.enviorment.clone()); + Ok(BaseValue::Function(func)) + } + other => Ok(other.clone()), + }, + Expr::Identifier { name } => self.look_up_variable(name, node.id), + Expr::Binary { + left, + operator, + right, + } => { + let left_val = self.eval_expr(left)?; + let right_val = self.eval_expr(right)?; + self.evaluate_binary(left_val, operator.clone(), right_val) + } + Expr::Unary { operator, operand } => { + let operand_val = self.eval_expr(operand)?; + self.evaluate_unary(operator.clone(), operand_val) + } + Expr::Grouping { expression } => self.eval_expr(expression), + Expr::Call { callee, arguments } => self.evaluate_call(callee, arguments), + Expr::Assign { name, value } => { + let value = self.eval_expr(value)?; + self.assign_variable(name, node.id, value) + } + }; + // Give location-less runtime errors this node's span. + match result { + Err(LoxError::RuntimeError { + message, + source_slice, + }) if source_slice.is_synthetic() => runtime_error(node.source_slice.clone(), message), + other => other, + } + } + + /// Look up a variable: by resolved distance if known, else dynamically. + fn look_up_variable(&self, name: &str, id: NodeId) -> LoxResult { + match self.locals.get(&id) { + Some(&distance) => self.enviorment.get_at(distance, name), + None => self.enviorment.get(name), + } + } + + /// Assign a variable: at its resolved distance if known, else dynamically. + fn assign_variable( + &mut self, + name: &str, + id: NodeId, + value: BaseValue, + ) -> LoxResult { + match self.locals.get(&id) { + Some(&distance) => self.enviorment.assign_at(distance, name.to_string(), value), + None => self.enviorment.set(name.to_string(), value), + } + } + fn evaluate_call( &mut self, - callee: Box>, - arguments: Vec>, + callee: &AstNode, + arguments: &[AstNode], ) -> LoxResult { let source_slice = callee.source_slice.clone(); - // Estrai il nome della variabile se il callee รจ un identificatore - let function_name = match &callee.node { - Expr::Identifier { name } => Some(name.clone()), - _ => None, - }; - - let function = if let Some(name) = function_name { - // Se abbiamo un nome di variabile, ottieni la funzione dall'ambiente - self.enviorment.get(&name)? - } else { - // Altrimenti valuta l'espressione callee (per casi piรน complessi) - self.evaluate(*callee)? + // A bare identifier callee resolves through `locals`; anything else is + // evaluated as a general expression. + let function = match &callee.node { + Expr::Identifier { name } => self.look_up_variable(name, callee.id)?, + _ => self.eval_expr(callee)?, }; if !function.is_callable() { @@ -122,7 +145,7 @@ impl Interpreter { let evaluated_arguments = arguments .iter() - .map(|arg| self.evaluate(arg.clone())) + .map(|arg| self.eval_expr(arg)) .collect::, LoxError>>()?; match function { @@ -207,19 +230,19 @@ impl Interpreter { fn interpret_stmt_inner(&mut self, stmt: Stmt) -> LoxResult { match stmt { - Stmt::Expression { expression, .. } => self.evaluate(*expression), + Stmt::Expression { expression, .. } => self.eval_expr(&expression), Stmt::Print { expression, .. } => { - let value = self.evaluate(*expression)?; + let value = self.eval_expr(&expression)?; println!("{}", value); Ok(BaseValue::Nil) } Stmt::Block { statements, .. } => self.evaluate_block(*statements), - Stmt::Return { expression, .. } => self.evaluate(*expression), + Stmt::Return { expression, .. } => self.eval_expr(&expression), Stmt::VarDeclaration { name, initializer, .. } => { let value = match initializer { - Some(expr_node) => self.evaluate(*expr_node)?, + Some(expr_node) => self.eval_expr(&expr_node)?, None => BaseValue::Nil, }; self.enviorment.declare(name.clone(), value) @@ -236,7 +259,7 @@ impl Interpreter { condition, body, .. } => { let mut ret = BaseValue::Nil; - while self.evaluate(*condition.clone())?.is_truthy() { + while self.eval_expr(&condition)?.is_truthy() { match self.evaluate(*body.clone()) { Ok(val) => ret = val, Err(LoxError::Return { value, .. }) => { @@ -261,7 +284,7 @@ impl Interpreter { return runtime_error(source_slice, "Expected number literal"); } let mut ret = BaseValue::Nil; - while self.evaluate(*condition.clone())?.is_truthy() { + while self.eval_expr(&condition)?.is_truthy() { match self.evaluate(*body.clone()) { Ok(val) => ret = val, Err(LoxError::Return { value, .. }) => { @@ -284,12 +307,12 @@ impl Interpreter { elif_branch: Vec<(Box>, Box>)>, else_branch: Option>>, ) -> LoxResult { - let condition = self.evaluate(*condition)?; + let condition = self.eval_expr(&condition)?; match condition { BaseValue::Boolean(true) => self.evaluate(*then_branch), BaseValue::Boolean(false) => { for (elif_condition, elif_then_branch) in elif_branch { - let condition = self.evaluate(*elif_condition)?; + let condition = self.eval_expr(&elif_condition)?; match condition { BaseValue::Boolean(true) => { return self.evaluate(*elif_then_branch); @@ -327,7 +350,7 @@ impl Interpreter { let node = statement.node.clone(); match node { Stmt::Return { expression, .. } => { - let value = self.evaluate(*expression)?; + let value = self.eval_expr(&expression)?; result = Err(LoxError::Return { source_slice: statement.source_slice.clone(), diff --git a/src/common/lox_result.rs b/src/common/lox_result.rs index 63327b4..602d7c3 100755 --- a/src/common/lox_result.rs +++ b/src/common/lox_result.rs @@ -479,3 +479,90 @@ pub fn io_error(message: impl Into) -> LoxResult { message: message.into(), }) } + +/// Accumulates diagnostics during a pass instead of bailing on the first one. +/// +/// Static analyses (e.g. the resolver) `report` problems and keep traversing so +/// the user sees every error at once, then surface them at the end. +#[derive(Debug, Default)] +pub struct ErrorSink { + errors: Vec, +} + +impl ErrorSink { + pub fn new() -> Self { + Self::default() + } + + /// Record a diagnostic and keep going. + pub fn report(&mut self, error: LoxError) { + self.errors.push(error); + } + + pub fn has_errors(&self) -> bool { + !self.errors.is_empty() + } + + pub fn errors(&self) -> &[LoxError] { + &self.errors + } + + /// Merge another sink's errors into this one (e.g. from a separate pass). + pub fn extend(&mut self, other: ErrorSink) { + self.errors.extend(other.errors); + } + + pub fn into_errors(self) -> Vec { + self.errors + } + + /// Bridge to the single-error [`LoxResult`] API: `Ok` if empty, otherwise + /// the first reported error. + pub fn into_result(self) -> LoxResult<()> { + match self.errors.into_iter().next() { + Some(error) => Err(error), + None => Ok(()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::frontend::source_registry::SourceSlice; + + fn parse_err(message: &str) -> LoxError { + LoxError::ParseError { + source_slice: SourceSlice::synthetic(), + message: message.to_string(), + } + } + + #[test] + fn empty_sink_is_ok() { + let sink = ErrorSink::new(); + assert!(!sink.has_errors()); + assert!(sink.into_result().is_ok()); + } + + #[test] + fn accumulates_multiple_errors() { + let mut sink = ErrorSink::new(); + sink.report(parse_err("first")); + sink.report(parse_err("second")); + assert!(sink.has_errors()); + assert_eq!(sink.errors().len(), 2); + assert!(sink.into_result().is_err()); + } + + #[test] + fn extend_merges_sinks() { + let mut a = ErrorSink::new(); + a.report(parse_err("a")); + let mut b = ErrorSink::new(); + b.report(parse_err("b1")); + b.report(parse_err("b2")); + a.extend(b); + assert_eq!(a.into_errors().len(), 3); + } +} diff --git a/src/main.rs b/src/main.rs index f79d8f8..e60e051 100755 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ use crate::{ parser::Parser, source_registry::{SourceId, SourceRegistry}, }, + middleend::variable_resolution::Resolver, }; use std::env; use std::fs; @@ -185,7 +186,11 @@ impl LoxInterpreter { // Funzione per eseguire l'AST fn execute_ast(&self, ast: Vec>, debug: bool) -> LoxResult { + // Static resolution pass: compute variable scope distances and report + // any resolution errors before executing. + let locals = self.resolve(&ast)?; let mut interpreter = Interpreter::new(); + interpreter.set_locals(locals); let mut result = None; for (index, stmt) in ast.iter().enumerate() { @@ -204,6 +209,19 @@ impl LoxInterpreter { } } + // Static variable resolution; prints and returns the first error, if any. + fn resolve( + &self, + ast: &[AstNode], + ) -> LoxResult> { + Resolver::resolve_program(ast).map_err(|errors| { + for err in &errors { + err.print_with_context(&self.source_registry); + } + errors.into_iter().next().unwrap() + }) + } + fn process_source( &self, source_id: SourceId, @@ -245,8 +263,10 @@ impl LoxInterpreter { return Ok(format_ast(&ast)); } - // Stage 3: Interpretation + // Stage 3: Resolution + Interpretation + let locals = self.resolve(&ast)?; let mut interpreter = Interpreter::new(); + interpreter.set_locals(locals); let mut result = None; for (index, stmt) in ast.iter().enumerate() { diff --git a/src/middleend/mod.rs b/src/middleend/mod.rs index 05d6ea6..b361e3d 100755 --- a/src/middleend/mod.rs +++ b/src/middleend/mod.rs @@ -1,2 +1,3 @@ +pub mod scope_stack; pub mod variable_resolution; pub mod visit_ast; diff --git a/src/middleend/scope_stack.rs b/src/middleend/scope_stack.rs new file mode 100644 index 0000000..c19689a --- /dev/null +++ b/src/middleend/scope_stack.rs @@ -0,0 +1,146 @@ +//! A reusable lexical-scope stack for static analyses. +//! +//! Unlike [`EnvironmentStack`](crate::backend::environment::EnvironmentStack), +//! which stores runtime *values*, this stores per-binding *analysis state* +//! (for the resolver, a `bool` meaning "defined yet?"). It is generic over that +//! state so other passes (a type checker, an unused-variable lint, ...) can +//! reuse the same machinery. +//! +//! It starts **empty**: the global scope is intentionally untracked, so +//! [`ScopeStack::resolve`] returning `None` means "not local โ€” assume global". + +use std::collections::HashMap; + +#[derive(Debug)] +pub struct ScopeStack { + scopes: Vec>, +} + +impl Default for ScopeStack { + fn default() -> Self { + Self::new() + } +} + +impl ScopeStack { + pub fn new() -> Self { + ScopeStack { scopes: Vec::new() } + } + + pub fn begin_scope(&mut self) { + self.scopes.push(HashMap::new()); + } + + pub fn end_scope(&mut self) { + self.scopes.pop(); + } + + pub fn is_empty(&self) -> bool { + self.scopes.is_empty() + } + + pub fn depth(&self) -> usize { + self.scopes.len() + } + + /// Insert `name` with `state` into the innermost scope (no-op at global). + pub fn declare(&mut self, name: impl Into, state: T) { + if let Some(scope) = self.scopes.last_mut() { + scope.insert(name.into(), state); + } + } + + /// Update an existing binding in the innermost scope (no-op if absent). + pub fn set_local(&mut self, name: &str, state: T) { + if let Some(scope) = self.scopes.last_mut() { + if let Some(slot) = scope.get_mut(name) { + *slot = state; + } + } + } + + /// Look up `name` in the innermost scope only. + pub fn get_local(&self, name: &str) -> Option<&T> { + self.scopes.last().and_then(|scope| scope.get(name)) + } + + /// Whether the innermost scope already declares `name`. + pub fn declared_in_current(&self, name: &str) -> bool { + self.scopes + .last() + .map_or(false, |scope| scope.contains_key(name)) + } + + /// Distance (in scopes) from the innermost scope to the one declaring + /// `name`, or `None` if it isn't in any scope (i.e. global). + pub fn resolve(&self, name: &str) -> Option { + self.scopes + .iter() + .rev() + .enumerate() + .find_map(|(distance, scope)| scope.contains_key(name).then_some(distance)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn declare_and_get_local() { + let mut scopes: ScopeStack = ScopeStack::new(); + scopes.begin_scope(); + scopes.declare("a", false); + assert_eq!(scopes.get_local("a"), Some(&false)); + scopes.set_local("a", true); + assert_eq!(scopes.get_local("a"), Some(&true)); + assert_eq!(scopes.get_local("missing"), None); + } + + #[test] + fn resolve_returns_distance_from_innermost() { + let mut scopes: ScopeStack = ScopeStack::new(); + scopes.begin_scope(); + scopes.declare("a", true); + scopes.begin_scope(); + scopes.declare("b", true); + assert_eq!(scopes.resolve("b"), Some(0)); + assert_eq!(scopes.resolve("a"), Some(1)); + assert_eq!(scopes.resolve("missing"), None); + } + + #[test] + fn shadowing_and_end_scope() { + let mut scopes: ScopeStack = ScopeStack::new(); + scopes.begin_scope(); + scopes.declare("a", true); + scopes.begin_scope(); + scopes.declare("a", false); + assert_eq!(scopes.get_local("a"), Some(&false)); + assert_eq!(scopes.resolve("a"), Some(0)); + scopes.end_scope(); + assert_eq!(scopes.get_local("a"), Some(&true)); + assert_eq!(scopes.resolve("a"), Some(0)); + } + + #[test] + fn declared_in_current_checks_only_innermost() { + let mut scopes: ScopeStack = ScopeStack::new(); + scopes.begin_scope(); + scopes.declare("a", true); + scopes.begin_scope(); + assert!(!scopes.declared_in_current("a")); + scopes.declare("a", true); + assert!(scopes.declared_in_current("a")); + } + + #[test] + fn global_scope_is_untracked() { + let mut scopes: ScopeStack = ScopeStack::new(); + // No scope pushed: declarations are no-ops and nothing resolves locally. + scopes.declare("a", true); + assert!(scopes.is_empty()); + assert_eq!(scopes.resolve("a"), None); + assert_eq!(scopes.get_local("a"), None); + } +} diff --git a/src/middleend/variable_resolution.rs b/src/middleend/variable_resolution.rs index 32467b5..c2be7e5 100755 --- a/src/middleend/variable_resolution.rs +++ b/src/middleend/variable_resolution.rs @@ -1,95 +1,267 @@ +//! Static variable resolution (Crafting Interpreters, chapter 11). +//! +//! Walks the AST with the shared [`Visitor`] traversal, tracking lexical scopes +//! in a [`ScopeStack`], and records for every variable *reference* how many +//! scopes up its declaration lives (`locals: NodeId -> distance`). It also +//! reports the resolution errors from chapter 11: +//! +//! * reading a local variable in its own initializer (`var a = a;`), +//! * declaring two variables with the same name in one local scope, +//! * `return` outside of any function. +//! +//! Diagnostics accumulate in an [`ErrorSink`] so a single pass surfaces them +//! all. The produced `locals` map is keyed by [`NodeId`] (stable identity), +//! ready for the interpreter to consume via distance-based lookup. + use std::collections::HashMap; use crate::{ - backend::environment::EnvironmentStack, common::{ ast::{AstNode, Expr, NodeId, Stmt}, - lox_result::{LoxError, LoxResult}, + base_value::{BaseValue, LoxFunction}, + lox_result::{ErrorSink, LoxError, LoxResult}, + }, + middleend::{ + scope_stack::ScopeStack, + visit_ast::{walk_expr, walk_function, walk_stmt, Visitor}, }, - frontend::source_registry::SourceSlice, - middleend::visit_ast::{walk_expr, walk_stmt, Visitor}, }; -struct Resolver { - scopes: EnvironmentStack, +/// Tracks whether resolution is currently inside a function body, so a +/// top-level `return` can be reported. +#[derive(Clone, Copy, PartialEq)] +enum FunctionType { + None, + Function, +} + +pub struct Resolver { + scopes: ScopeStack, locals: HashMap, + errors: ErrorSink, + current_function: FunctionType, } impl Resolver { pub fn new() -> Self { Resolver { - scopes: EnvironmentStack::new(), + scopes: ScopeStack::new(), locals: HashMap::new(), + errors: ErrorSink::new(), + current_function: FunctionType::None, } } - fn declare(&mut self, name: &String) { + /// Resolve a whole program, returning the per-reference scope distances or + /// the accumulated resolution errors. + pub fn resolve_program( + statements: &[AstNode], + ) -> Result, Vec> { + let mut resolver = Resolver::new(); + for statement in statements { + // Visiting only fails for fatal/internal errors, which this pass + // never produces; user-facing diagnostics go to `errors`. + let _ = resolver.visit_stmt(statement); + } + if resolver.errors.has_errors() { + Err(resolver.errors.into_errors()) + } else { + Ok(resolver.locals) + } + } + + fn declare(&mut self, name: &str, slice: &crate::frontend::source_registry::SourceSlice) { + if self.scopes.is_empty() { + return; // global scope is untracked + } + if self.scopes.declared_in_current(name) { + self.errors.report(LoxError::ParseError { + source_slice: slice.clone(), + message: format!("Already a variable named '{}' in this scope.", name), + }); + } + self.scopes.declare(name.to_string(), false); + } + + fn define(&mut self, name: &str) { if self.scopes.is_empty() { return; } - let scope = self.scopes.peek(); - let _ = self.scopes.set(name.clone(), false); + self.scopes.set_local(name, true); } - fn define(&mut self, name: &String) { - if self.scopes.is_empty() { - return; + fn resolve_local(&mut self, id: NodeId, name: &str) { + if let Some(distance) = self.scopes.resolve(name) { + self.locals.insert(id, distance); } - let _ = self.scopes.set(name.clone(), true); + // Not found locally: assume global, record nothing. } +} - fn resolve_local(&mut self, id: NodeId, name: &String) { - let depth = self.scopes.depth(); - for i in (0..depth).rev() { - if self.scopes.scope_contains(i, name) { - // Distance = how many scopes up from the innermost the binding lives. - self.locals.insert(id, depth - 1 - i); - return; - } - } - // Not found in any tracked scope: assume global, record nothing. +impl Default for Resolver { + fn default() -> Self { + Self::new() } } impl Visitor for Resolver { fn visit_stmt(&mut self, stmt: &AstNode) -> LoxResult<()> { match &stmt.node { - Stmt::VarDeclaration { name, .. } => { - self.declare(name); - // `walk_stmt` resolves the initializer (if present). - walk_stmt(self, stmt)?; - self.define(name); + Stmt::VarDeclaration { + name, initializer, .. + } => { + // A function declaration is a var bound to a function literal. + // Define its name *before* resolving the body so it can recurse; + // a plain variable is defined *after* its initializer so that + // `var a = a;` is caught. + let is_function = matches!( + initializer.as_deref().map(|node| &node.node), + Some(Expr::Literal { + value: BaseValue::Function(_) + }) + ); + self.declare(name, &stmt.source_slice); + if is_function { + self.define(name); + walk_stmt(self, stmt)?; + } else { + walk_stmt(self, stmt)?; // resolves the initializer, if any + self.define(name); + } Ok(()) } - // NOTE: assignment is now an `Expr::Assign`, not a statement. - // Add an `Expr::Assign` arm to `visit_expr` to resolve assignments. Stmt::Block { .. } => { - self.scopes.push_new_scope(); + self.scopes.begin_scope(); walk_stmt(self, stmt)?; - self.scopes.pop_scope(); + self.scopes.end_scope(); Ok(()) } - // Expression, Print, Return, If, While, For: default traversal. + Stmt::Return { .. } => { + if self.current_function == FunctionType::None { + self.errors.report(LoxError::ParseError { + source_slice: stmt.source_slice.clone(), + message: "Can't return from top-level code.".to_string(), + }); + } + walk_stmt(self, stmt) // resolve the returned expression + } + // Expression, Print, If, While, For: default traversal. _ => walk_stmt(self, stmt), } } fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { match &expr.node { - Expr::Identifier { name, .. } => { - if !self.scopes.is_empty() && self.scopes.get(name).is_ok() { - return Err(LoxError::ParseError { - source_slice: SourceSlice::synthetic(), - message: "Cant read local varialbe in it own initializer".to_string(), + Expr::Identifier { name } => { + if self.scopes.get_local(name) == Some(&false) { + self.errors.report(LoxError::ParseError { + source_slice: expr.source_slice.clone(), + message: "Can't read local variable in its own initializer.".to_string(), }); } - walk_expr(self, expr) + self.resolve_local(expr.id, name); + Ok(()) } Expr::Assign { name, .. } => { + walk_expr(self, expr)?; // resolve the assigned value first self.resolve_local(expr.id, name); - walk_expr(self, expr) + Ok(()) } _ => walk_expr(self, expr), } } + + fn visit_function(&mut self, function: &LoxFunction) -> LoxResult<()> { + let enclosing = std::mem::replace(&mut self.current_function, FunctionType::Function); + self.scopes.begin_scope(); + // Parameters carry no source slice of their own; use the body's. + let param_slice = function.body.source_slice.clone(); + for (param, _ty) in &function.parameters { + self.declare(param, ¶m_slice); + self.define(param); + } + walk_function(self, function)?; // resolves guard + body + self.scopes.end_scope(); + self.current_function = enclosing; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::frontend::lexer::Lexer; + use crate::frontend::parser::Parser; + + fn parse(src: &str) -> Vec> { + let tokens = Lexer::new(src.to_string(), 0) + .scans_tokens() + .expect("source should lex"); + Parser::new(tokens).parse().expect("source should parse") + } + + fn resolve(src: &str) -> Result, Vec> { + Resolver::resolve_program(&parse(src)) + } + + #[test] + fn global_variables_are_not_resolved() { + // Top-level (global) scope is untracked, so nothing is recorded. + let locals = resolve("x := 1; print x;").expect("should resolve"); + assert!(locals.is_empty()); + } + + #[test] + fn local_read_resolves_to_distance_zero() { + let locals = resolve("do x := 1; print x; end").expect("should resolve"); + assert_eq!(locals.len(), 1); + assert_eq!(*locals.values().next().unwrap(), 0); + } + + #[test] + fn nested_scope_resolves_to_outer_distance() { + let src = "do x := 1; do print x; end end"; + let locals = resolve(src).expect("should resolve"); + assert_eq!(locals.len(), 1); + // `x` is read one scope above where it is read from. + assert_eq!(*locals.values().next().unwrap(), 1); + } + + #[test] + fn reading_a_variable_in_its_own_initializer_is_an_error() { + let errors = resolve("do x := x; end").expect_err("should fail"); + assert!(errors + .iter() + .any(|e| e.get_message().contains("its own initializer"))); + } + + #[test] + fn duplicate_declaration_in_same_scope_is_an_error() { + let errors = resolve("do x := 1; x := 2; end").expect_err("should fail"); + assert!(errors + .iter() + .any(|e| e.get_message().contains("Already a variable"))); + } + + #[test] + fn return_at_top_level_is_an_error() { + let errors = resolve("return 1;").expect_err("should fail"); + assert!(errors.iter().any(|e| e.get_message().contains("top-level"))); + } + + #[test] + fn return_inside_a_function_is_allowed() { + let locals = resolve("f :: fn (n) do return n; end").expect("should resolve"); + // `n` resolves from the body block up to the parameter scope. + assert_eq!(locals.len(), 1); + assert_eq!(*locals.values().next().unwrap(), 1); + } + + #[test] + fn assignment_target_is_resolved() { + let locals = resolve("do x := 1; x = 2; end").expect("should resolve"); + // Both the assignment target and... only the target is a reference here. + assert!(locals.values().all(|&d| d == 0)); + assert!(!locals.is_empty()); + } } diff --git a/tests/resolution.rs b/tests/resolution.rs new file mode 100644 index 0000000..e099415 --- /dev/null +++ b/tests/resolution.rs @@ -0,0 +1,69 @@ +//! End-to-end pipeline tests: lexer -> parser -> resolver -> interpreter. +//! +//! These verify chapter-11 resolution wired into execution: a variable's +//! resolved scope distance is used for lookup (`get_at`), and resolution errors +//! abort before running. + +use rlox::backend::interpreter::{EvaluateInterpreter, Interpreter}; +use rlox::common::base_value::BaseValue; +use rlox::frontend::lexer::Lexer; +use rlox::frontend::parser::Parser; +use rlox::middleend::variable_resolution::Resolver; + +/// Run the whole pipeline, returning the value of the last statement. +fn run(src: &str) -> Result { + let tokens = Lexer::new(src.to_string(), 0) + .scans_tokens() + .map_err(|e| e.to_string())?; + let ast = Parser::new(tokens).parse().map_err(|e| e.to_string())?; + let locals = Resolver::resolve_program(&ast) + .map_err(|errors| errors.into_iter().next().unwrap().to_string())?; + let mut interpreter = Interpreter::new(); + interpreter.set_locals(locals); + let mut last = BaseValue::Nil; + for stmt in ast { + last = interpreter.evaluate(stmt).map_err(|e| e.to_string())?; + } + Ok(last) +} + +#[test] +fn block_local_variables_evaluate() { + assert_eq!(run("do x := 10; x + 5; end").unwrap().to_string(), "15"); +} + +#[test] +fn resolved_nested_closure_reads_captured_local() { + // `count` is read from inside a nested function, two scopes up; the + // resolver records distance 2 and the interpreter uses `get_at(2, ..)`. + let src = "\ +make :: fn () do + count := 10; + get :: fn () do + return count; + end + return get; +end +g := make(); +g(); +"; + assert_eq!(run(src).unwrap().to_string(), "10"); +} + +#[test] +fn use_before_initializer_is_rejected() { + let err = run("do x := x; end").expect_err("should be a resolution error"); + assert!(err.contains("its own initializer")); +} + +#[test] +fn top_level_return_is_rejected() { + let err = run("return 1;").expect_err("should be a resolution error"); + assert!(err.contains("top-level")); +} + +#[test] +fn duplicate_declaration_in_block_is_rejected() { + let err = run("do x := 1; x := 2; end").expect_err("should be a resolution error"); + assert!(err.contains("Already a variable")); +} From 842216729b31cac3950f82cc219ab364f146b499 Mon Sep 17 00:00:00 2001 From: Giulio Agostini Date: Mon, 6 Jul 2026 10:43:17 +0200 Subject: [PATCH 8/9] wip: Add type system and refator for having only epression --- README.md => .rules | 0 README.org | 36 ++ examples/test_closure.lox | 12 +- examples/test_example.lox | 6 +- examples/test_function_call.lox | 28 +- grammar.org | 57 +++ src/backend/interpreter.rs | 196 +++++----- src/common/ast.rs | 460 ++++++++--------------- src/common/base_value.rs | 45 ++- src/common/mod.rs | 1 + src/common/types.rs | 70 ++++ src/frontend/lexer.rs | 30 +- src/frontend/parser.rs | 534 +++++++++++++-------------- src/frontend/source_registry.rs | 8 + src/frontend/tokens.rs | 6 +- src/lib.rs | 1 + src/logging/display_ast.rs | 83 +---- src/logging/display_token.rs | 6 +- src/main.rs | 21 +- src/middleend/variable_resolution.rs | 95 +++-- src/middleend/visit_ast.rs | 160 ++++---- tests/frontend.rs | 56 ++- tests/resolution.rs | 8 +- 23 files changed, 940 insertions(+), 979 deletions(-) rename README.md => .rules (100%) mode change 100755 => 100644 create mode 100755 README.org create mode 100644 grammar.org create mode 100644 src/common/types.rs diff --git a/README.md b/.rules old mode 100755 new mode 100644 similarity index 100% rename from README.md rename to .rules diff --git a/README.org b/README.org new file mode 100755 index 0000000..4dce23c --- /dev/null +++ b/README.org @@ -0,0 +1,36 @@ +#+title: README + +* Syntax example: +#+begin_src +funzione :: fn(parametro1: Number, parametro2: String): String do + +end +#+end_src + +* Planning: +allora quello che devo fare รจ: +** TODO Controllare se ho finito la questione del retourn:labe +** TODO implement struct: +#+begin_src rlox +Cosa :: struct{ + field1: Number +} +#+end_src +*** TODO update lexert +*** TODO update ast +*** TODO update parser +** TODO implement partial function ... +#+begin_src rlox +cosa := make(Cosa, feld1: 42) +function :: fn(self:Cosa, b: Number) -> Number +res := function(cosa,44) +#+end_src +*** TODO ... to undericlty create method +#+begin_src rlox +res := cosa.function(44) +#+end_src +*** TODO ... to make pipeline +#+begin_src rlox +res := cosa |> function(44) +#+end_src +** TODO Understand what is a type system diff --git a/examples/test_closure.lox b/examples/test_closure.lox index 719f093..7b0586c 100755 --- a/examples/test_closure.lox +++ b/examples/test_closure.lox @@ -1,12 +1,12 @@ -closure :: fn() do +closure :: fn(): Any do value := "Closure"; - internal :: fn() do + internal :: fn(): String do print value; return value; - end + end; return internal; -end +end; internal := closure(); internal(); @@ -17,9 +17,9 @@ c := b; c = b = a; d := c = b = a; do - showA :: fn() do + showA :: fn(): Nil do print a; - end + end; showA(); a := "Local"; diff --git a/examples/test_example.lox b/examples/test_example.lox index a1e87e2..b439140 100755 --- a/examples/test_example.lox +++ b/examples/test_example.lox @@ -4,7 +4,7 @@ // address: String, // } //func_name :: fn(param: Number, param2: String) {param is Int} do -func_name :: fn(param: Int, param2: String) do +func_name :: fn(param: Int, param2: String): Any do print param; print param2; while param < param2 do @@ -26,6 +26,6 @@ func_name :: fn(param: Int, param2: String) do end return False or True; -end +end; -func_name(1,20) +func_name(1,20); diff --git a/examples/test_function_call.lox b/examples/test_function_call.lox index aa045ee..013d673 100755 --- a/examples/test_function_call.lox +++ b/examples/test_function_call.lox @@ -1,30 +1,30 @@ -function :: fn() do +function :: fn(): Nil do print "Function"; -end +end; -function_factory :: fn() do +function_factory :: fn(): Any do print "Function Factory"; - function -end + function; +end; -function_fatcoty_factory :: fn() do +function_fatcoty_factory :: fn(): Any do print "Function Factory Factory"; return function_factory; -end +end; //function(); //function_factory()(); //function_fatcoty_factory()()(); -clock() +clock(); -closure :: fn() do - value = "Closure"; - internal :: fn() do +closure :: fn(): Any do + value := "Closure"; + internal :: fn(): Nil do print value; - end + end; return internal; -end +end; -closure() +closure(); diff --git a/grammar.org b/grammar.org new file mode 100644 index 0000000..aecd0a5 --- /dev/null +++ b/grammar.org @@ -0,0 +1,57 @@ + +statement -> print_statement + | return_statement + | break_statement + | block_statement + | var_statement + | if_statement + | while_statement + | for_statement + | expression_statement + +print_statement -> "print" expression ";" +return_statement -> "return" expression? ";" +break_statement -> "break" ";" +block_statement -> "do" statement* "end" +expression_statement -> expression ";"? + +; leading "var" optional; the ":" is required +var_statement -> "var"? IDENTIFIER ":" IDENTIFIER? + ( variable_declaration + | function_declaration + | struct_declaration ) ; struct = WIP +variable_declaration -> ( "=" expression )? ";" +function_declaration -> ":" "fn" "(" parameters? ")" ( "{" expression "}" )? statement +parameters -> IDENTIFIER ( ":" IDENTIFIER )? + ( "," IDENTIFIER ( ":" IDENTIFIER )? )* + +if_statement -> "if" expression "then" statement + ( "elif" expression "then" statement )* + ( "else" statement )? +while_statement -> "while" expression statement +for_statement -> "for" var_statement expression ";" expression_statement statement + +; ---------- expressions (lowest -> highest precedence) ---------- +expression -> assignment +assignment -> IDENTIFIER "=" assignment ; right-associative + | pipe +pipe -> logic ( "|>" logic )* ; left-assoc; x |> f(a) == f(x, a) +logic -> is ( ( "or" | "and" ) is )* +is -> equality ( "is" equality )* +equality -> comparison ( ( "==" | "!=" ) comparison )* +comparison -> term ( ( ">" | ">=" | "<" | "<=" ) term )* +term -> factor ( ( "+" | "-" ) factor )* +factor -> unary ( ( "*" | "/" ) unary )* +unary -> ( "!" | "-" ) unary | call +call -> primary ( "(" arguments? ")" )* +arguments -> expression ( "," expression )* +primary -> NUMBER | STRING | "true" | "false" | "nil" + | "(" expression ")" | IDENTIFIER + | dictionary | list | ATOM ; WIP + +; ---------- work in progress (lexed; parser support being added) ---------- +struct_declaration -> "struct" "{" field* "}" +field -> IDENTIFIER ( ":" IDENTIFIER )? ";" +dictionary -> "dict" "(" IDENTIFIER ":" expression + ( "," IDENTIFIER ":" expression )* ")" +list -> "list" "(" expression ( "," expression )* ")" diff --git a/src/backend/interpreter.rs b/src/backend/interpreter.rs index e73f6c5..00969ef 100755 --- a/src/backend/interpreter.rs +++ b/src/backend/interpreter.rs @@ -1,7 +1,7 @@ use crate::{ backend::environment::EnvironmentStack, common::{ - ast::{AstNode, Expr, NodeId, Stmt}, + ast::{AstNode, Expr, NodeId}, base_value::{BaseValue, NativeFunction, Number, Truthy}, lox_result::{runtime_error, LoxError, LoxResult}, }, @@ -21,18 +21,9 @@ pub trait EvaluateInterpreter { fn evaluate(&mut self, stmt: T) -> LoxResult; } -impl EvaluateInterpreter> for Interpreter { - fn evaluate(&mut self, node: AstNode) -> LoxResult { - let stmt = node.node; - let result = self.interpret_stmt_inner(stmt); - match result { - Ok(value) => Ok(value), - Err(LoxError::RuntimeError { - message, - source_slice, - }) if source_slice.is_synthetic() => runtime_error(node.source_slice.clone(), message), - Err(err) => Err(err), - } +impl EvaluateInterpreter for Interpreter { + fn evaluate(&mut self, node: AstNode) -> LoxResult { + self.eval_expr(&node) } } @@ -63,9 +54,9 @@ impl Interpreter { /// Evaluate an expression node, threading its `NodeId` so resolved /// variables and assignments can use their precomputed scope distance. - fn eval_expr(&mut self, node: &AstNode) -> LoxResult { + fn eval_expr(&mut self, node: &AstNode) -> LoxResult { let result = match &node.node { - Expr::Literal { value } => match value { + Expr::Literal { value } => match &**value { BaseValue::Function(func) => { let mut func = func.clone(); func.closure = Some(self.enviorment.clone()); @@ -93,6 +84,67 @@ impl Interpreter { let value = self.eval_expr(value)?; self.assign_variable(name, node.id, value) } + Expr::Print { expression } => { + let value = self.eval_expr(expression)?; + println!("{}", value); + Ok(BaseValue::Nil) + } + Expr::VarDeclaration { + name, initializer, .. + } => { + let value = match initializer { + Some(expr_node) => self.eval_expr(expr_node)?, + None => BaseValue::Nil, + }; + self.enviorment.declare(name.clone(), value) + } + Expr::Return { expression, .. } => self.eval_expr(expression), + Expr::Block { statements, .. } => self.evaluate_block(statements), + Expr::If { + condition, + then_branch, + elif_branches, + else_branch, + } => self.evaluate_if(condition, then_branch, elif_branches, else_branch), + Expr::While { condition, body } => { + let mut ret = BaseValue::Nil; + while self.eval_expr(condition)?.is_truthy() { + match self.eval_expr(body) { + Ok(val) => ret = val, + Err(LoxError::Return { value, .. }) => { + ret = value; + break; + } + Err(err) => return Err(err), + }; + } + Ok(ret) + } + Expr::For { + variable, + condition, + increment, + body, + } => { + let source_slice = variable.source_slice.clone(); + let val = self.eval_expr(variable)?; + if !matches!(val, BaseValue::Number(..)) { + return runtime_error(source_slice, "Expected number literal"); + } + let mut ret = BaseValue::Nil; + while self.eval_expr(condition)?.is_truthy() { + match self.eval_expr(body) { + Ok(val) => ret = val, + Err(LoxError::Return { value, .. }) => { + ret = value; + break; + } + Err(err) => return Err(err), + }; + self.eval_expr(increment)?; + } + Ok(ret) + } }; // Give location-less runtime errors this node's span. match result { @@ -125,11 +177,7 @@ impl Interpreter { } } - fn evaluate_call( - &mut self, - callee: &AstNode, - arguments: &[AstNode], - ) -> LoxResult { + fn evaluate_call(&mut self, callee: &AstNode, arguments: &[AstNode]) -> LoxResult { let source_slice = callee.source_slice.clone(); // A bare identifier callee resolves through `locals`; anything else is @@ -167,7 +215,7 @@ impl Interpreter { } // Execute the function body - let result = match self.evaluate(func.body) { + let result = match self.eval_expr(&func.body) { Ok(value) => Ok(value), Err(LoxError::Return { value, .. }) => Ok(value), Err(err) => Err(err), @@ -228,94 +276,22 @@ impl Interpreter { } } - fn interpret_stmt_inner(&mut self, stmt: Stmt) -> LoxResult { - match stmt { - Stmt::Expression { expression, .. } => self.eval_expr(&expression), - Stmt::Print { expression, .. } => { - let value = self.eval_expr(&expression)?; - println!("{}", value); - Ok(BaseValue::Nil) - } - Stmt::Block { statements, .. } => self.evaluate_block(*statements), - Stmt::Return { expression, .. } => self.eval_expr(&expression), - Stmt::VarDeclaration { - name, initializer, .. - } => { - let value = match initializer { - Some(expr_node) => self.eval_expr(&expr_node)?, - None => BaseValue::Nil, - }; - self.enviorment.declare(name.clone(), value) - } - - Stmt::If { - condition, - then_branch, - elif_branch, - else_branch, - .. - } => self.evaluate_if(condition, then_branch, elif_branch, else_branch), - Stmt::While { - condition, body, .. - } => { - let mut ret = BaseValue::Nil; - while self.eval_expr(&condition)?.is_truthy() { - match self.evaluate(*body.clone()) { - Ok(val) => ret = val, - Err(LoxError::Return { value, .. }) => { - ret = value; - break; - } - Err(err) => return Err(err), - }; - } - Ok(ret) - } - Stmt::For { - variable, - condition, - increment, - body, - .. - } => { - let source_slice = variable.source_slice.clone(); - let val = self.evaluate(*variable)?; - if !matches!(val, BaseValue::Number(..)) { - return runtime_error(source_slice, "Expected number literal"); - } - let mut ret = BaseValue::Nil; - while self.eval_expr(&condition)?.is_truthy() { - match self.evaluate(*body.clone()) { - Ok(val) => ret = val, - Err(LoxError::Return { value, .. }) => { - ret = value; - break; - } - Err(err) => return Err(err), - }; - self.evaluate(*increment.clone())?; - } - - Ok(ret) - } - } - } fn evaluate_if( &mut self, - condition: Box>, - then_branch: Box>, - elif_branch: Vec<(Box>, Box>)>, - else_branch: Option>>, + condition: &AstNode, + then_branch: &AstNode, + elif_branch: &[(Box, Box)], + else_branch: &Option>, ) -> LoxResult { - let condition = self.eval_expr(&condition)?; + let condition = self.eval_expr(condition)?; match condition { - BaseValue::Boolean(true) => self.evaluate(*then_branch), + BaseValue::Boolean(true) => self.eval_expr(then_branch), BaseValue::Boolean(false) => { for (elif_condition, elif_then_branch) in elif_branch { - let condition = self.eval_expr(&elif_condition)?; + let condition = self.eval_expr(elif_condition)?; match condition { BaseValue::Boolean(true) => { - return self.evaluate(*elif_then_branch); + return self.eval_expr(elif_then_branch); } BaseValue::Boolean(false) => continue, _ => { @@ -328,7 +304,7 @@ impl Interpreter { }; } if let Some(else_block) = else_branch { - self.evaluate(*else_block) + self.eval_expr(else_block) } else { Ok(BaseValue::Nil) } @@ -341,25 +317,23 @@ impl Interpreter { } } - fn evaluate_block(&mut self, statements: Vec>) -> LoxResult { + fn evaluate_block(&mut self, statements: &[AstNode]) -> LoxResult { self.enviorment.push_new_scope(); - // Ora elements รจ sempre disponibile let mut result = Ok(BaseValue::Nil); for statement in statements.iter() { - let node = statement.node.clone(); - match node { - Stmt::Return { expression, .. } => { - let value = self.eval_expr(&expression)?; + match &statement.node { + Expr::Return { expression, .. } => { + let value = self.eval_expr(expression)?; result = Err(LoxError::Return { source_slice: statement.source_slice.clone(), - value: value, + value, return_label: "Hi".to_string(), }); break; } - _ => result = self.evaluate((*statement).clone()), + _ => result = self.eval_expr(statement), }; } self.enviorment.pop_scope(); @@ -482,7 +456,7 @@ mod tests { #[test] fn defines_and_calls_a_function() { - let src = "add :: fn (a, b) do return a + b; end add(2, 3);"; + let src = "add :: fn (a, b): Number do return a + b; end; add(2, 3);"; assert_eq!(eval(src).unwrap().to_string(), "5"); } diff --git a/src/common/ast.rs b/src/common/ast.rs index 48c6381..fdfab03 100755 --- a/src/common/ast.rs +++ b/src/common/ast.rs @@ -1,5 +1,5 @@ use crate::{ - common::base_value::BaseValue, + common::{base_value::BaseValue, types::Type}, frontend::{source_registry::SourceSlice, tokens::TokenType}, }; use std::fmt::{Debug, Display}; @@ -22,65 +22,66 @@ impl NodeId { NodeId(NEXT_NODE_ID.fetch_add(1, Ordering::Relaxed)) } } -/* - * grammar: - * program -> statement* EOF - * statement -> expression_statement - * | print_statement - * | var_statement - * | block_statement - * | if_statement - * - * expression_statement -> expression ";" - * print_statement -> "print" expression ";" - * var_statement -> ("var"|"dyn"|"mut")? IDENTIFIER (":" IDENTIFIER)? (("=" expression)? ";")| function_declaration - * function_declaration -> "::" "(" parameters? ")" ("->" IDENTIFIER)? block_statement - * parameters -> ( IDENTIFIER (":" IDENTIFIER)? ("," IDENTIFIER (":" IDENTIFIER)? )* ) - * block_statement -> "do" statement* "end" - * if_statement -> "if" expression "then" statement ("elif" expression "then" statement)* ("else" statement)? "end" - * while_statement -> "while" expression "do" statement "end" - * - * expression -> assignment - * assignment -> IDENTIFIER "=" assignment | logical_or - * logical_or -> logical_and (("or" logical_and)* - * logical_and -> logical_is (("and" logical_is)* - * logical_is -> equality (("is" equality)* - * equality -> comparison (("==" | "!=") comparison)* - * comparison -> term ((">" | ">=" | "<" | "<=") term)* - * term -> factor (("+" | "-") factor)* - * factor -> unary (("*" | "/") unary)* - * unary -> ("!" | "-") unary | call - * call -> primary ("(" arguments ")")* - * arguments -> expression ("," expression)* - * primary -> NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" | IDENTIFIER - */ + #[derive(Clone, PartialEq)] pub enum Expr { Literal { - value: BaseValue, + value: Box, }, Binary { - left: Box>, + left: Box, operator: TokenType, - right: Box>, + right: Box, }, Unary { operator: TokenType, - operand: Box>, + operand: Box, }, Grouping { - expression: Box>, + expression: Box, }, Identifier { name: String, }, Call { - callee: Box>, - arguments: Vec>, + callee: Box, + arguments: Vec, }, Assign { name: String, - value: Box>, + value: Box, + }, + Print { + expression: Box, + }, + VarDeclaration { + name: String, + var_type: Type, + initializer: Option>, + }, + Return { + expression: Box, + label: String, + }, + Block { + statements: Box>, + label: String, + }, + If { + condition: Box, + then_branch: Box, + elif_branches: Vec<(Box, Box)>, + else_branch: Option>, + }, + While { + condition: Box, + body: Box, + }, + For { + variable: Box, + condition: Box, + increment: Box, + body: Box, }, } @@ -95,8 +96,10 @@ impl std::fmt::Display for Expr { right, } => 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::Identifier { name } => write!(f, "Identifier (variable {})", name), + Expr::Grouping { expression } => write!(f, "Grouping (group {})", expression.node,), + Expr::Identifier { name } => { + write!(f, "Identifier (variable {})", name) + } Expr::Call { callee, arguments } => write!( f, "Call ({}({}))", @@ -108,6 +111,59 @@ impl std::fmt::Display for Expr { .join(", ") ), Expr::Assign { name, value } => write!(f, "Assign ({} = {})", name, value.node), + Expr::If { + condition, + then_branch, + elif_branches, + else_branch, + } => { + let mut result = format!("IF ({}) {{\n{}\n}}", condition.node, then_branch.node); + for (condition, branch) in elif_branches { + 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.node)); + } + write!(f, "IfStmt {}", result) + } + Expr::Print { expression } => write!(f, "Print({})", expression.node), + Expr::VarDeclaration { + name, + initializer, + var_type, + } => match initializer { + Some(init) => write!(f, "Var({} : {:?} = {})", name, var_type, init.node), + None => write!(f, "Var({} : {:?})", name, var_type), + }, + Expr::Return { expression, label } => { + write!(f, "Return({}, {})", expression.node, label) + } + Expr::Block { statements, label } => write!( + f, + "Block [{}] ([\n{}\n])", + label, + statements + .iter() + .map(|stmt| format!("\t \t{}", stmt.node)) + .collect::>() + .join("\n"), + ), + Expr::While { condition, body } => { + write!(f, "While({}) {{\n{}\n}}", condition.node, body.node) + } + Expr::For { + variable, + condition, + increment, + body, + } => write!( + f, + "For({} = {} in {}) {{\n{}\n}}", + variable.node, condition.node, increment.node, body.node + ), } } } @@ -115,15 +171,21 @@ impl std::fmt::Display for Expr { impl Debug for Expr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Expr::Literal { value } => write!(f, "{:?}", value), + Expr::Literal { value } => write!(f, "{:?} ", value,), Expr::Binary { left, operator, right, - } => 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::Identifier { name } => write!(f, "(variable {:?})", name), + } => write!(f, "({:?} {:?} {:?}) ", operator, left.node, right.node,), + Expr::Unary { operator, operand } => { + write!(f, "(Unary {:?} {:?}) ", operator, operand.node,) + } + Expr::Grouping { expression } => { + write!(f, "(Grouping {:?})", expression.node,) + } + Expr::Identifier { name } => { + write!(f, "(variable {:?})", name,) + } Expr::Call { callee, arguments } => write!( f, "Call ({}({}))", @@ -132,306 +194,90 @@ impl Debug for Expr { .iter() .map(|arg| arg.node.to_string()) .collect::>() - .join(", ") + .join(", "), ), - Expr::Assign { name, value } => write!(f, "(assign {:?} {:?})", name, value.node), - } - } -} - -#[derive(Clone, PartialEq)] -pub enum Stmt { - Expression { - expression: Box>, - return_value: Box, - }, - Print { - expression: Box>, - return_value: Box, - }, - VarDeclaration { - name: String, - initializer: Option>>, - return_value: Box, - }, - Return { - expression: Box>, - label: String, - return_value: Box, - }, - Block { - statements: Box>>, - label: String, - return_value: Box, - }, - If { - condition: Box>, - then_branch: Box>, - elif_branch: Vec<(Box>, Box>)>, - else_branch: Option>>, - return_value: Box, - }, - While { - condition: Box>, - body: Box>, - return_value: Box, - }, - For { - variable: Box>, - condition: Box>, - increment: Box>, - body: Box>, - return_value: Box, - }, -} - -impl Display for Stmt { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Stmt::Expression { - expression, - return_value, - } => write!(f, "Expression ({}) -> {:?}", expression.node, return_value), - Stmt::If { + Expr::Assign { name, value } => { + write!(f, "(assign {:?} {:?})", name, value.node,) + } + Expr::If { condition, then_branch, - elif_branch, + elif_branches, else_branch, - return_value, } => { - let mut result = format!("IF ({}) {{\n{}\n}}", condition.node, then_branch.node); - for (condition, branch) in elif_branch { + let mut result = + format!("IF ({:?}) {{\n{:?}\n}}", condition.node, then_branch.node,); + for (condition, branch) in elif_branches { result.push_str(&format!( - " ELIF ({}) {{\n{}\n}} -> {:?}", - condition.node, branch.node, return_value + " ELIF ({:?}) {{\n{:?}\n}}", + condition.node, branch.node, )); } if let Some(else_branch) = else_branch { - result.push_str(&format!( - " ELSE {{\n{}\n}} -> {:?}", - else_branch.node, return_value - )); - } - write!(f, "IfStmt {}", result) - } - Stmt::Print { - expression, - return_value, - } => write!(f, "Print({}) -> {:?};", expression.node, return_value), - Stmt::VarDeclaration { - name, - initializer, - return_value, - } => match initializer { - Some(init) => write!(f, "Var({} = {}) -> {:?};", name, init.node, return_value), - None => write!(f, "Var({}) -> {:?};", name, return_value), - }, - Stmt::Return { - expression, - return_value, - label, - } => write!( - f, - "Return({}, {}) -> {:?};", - expression.node, label, return_value - ), - Stmt::Block { - statements, - label, - return_value, - } => write!( - f, - "Block [{}] ([\n{}\n]) -> {:?}", - label, - statements - .iter() - .map(|stmt| format!("\t \t{}", stmt.node)) - .collect::>() - .join("\n"), - return_value - ), - Stmt::While { - condition, - body, - return_value, - } => { - write!( - f, - "While({}) {{\n{}\n}} -> {:?}", - condition.node, body.node, return_value - ) - } - Stmt::For { - variable, - condition, - increment, - body, - return_value, - } => write!( - f, - "For({} = {} in {}) {{\n{}\n}} -> {:?}", - variable.node, condition.node, increment.node, body.node, return_value - ), - } - } -} - -impl Debug for Stmt { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Stmt::Expression { - expression, - return_value, - } => write!( - f, - " Expression ({:?}) -> {:?}", - expression.node, return_value - ), - Stmt::If { - condition, - then_branch, - elif_branch, - else_branch, - return_value, - } => { - let mut result = format!( - "IF ({:?}) {{\n{:?}\n}} -> {:?} ", - condition.node, then_branch.node, return_value - ); - for (condition, branch) in elif_branch { - result.push_str(&format!( - " ELIF ({:?}) {{\n{:?}\n}} -> {:?} ", - condition.node, branch.node, return_value - )); - } - if let Some(else_branch) = else_branch { - result.push_str(&format!( - " ELSE {{\n{:?}\n}} -> {:?}", - else_branch.node, return_value - )); + result.push_str(&format!(" ELSE {{\n{:?}\n}}", else_branch.node,)); } write!(f, "IfStmt {:?}", result) } - Stmt::Print { - expression, - return_value, - } => write!(f, "Print({:?}) -> {:?};", expression.node, return_value), - Stmt::VarDeclaration { + Expr::Print { expression } => { + write!(f, "Print({:?});", expression.node,) + } + Expr::VarDeclaration { name, initializer, - return_value, + var_type, } => match initializer { - Some(init) => write!( - f, - "Var({:?} = {:?}) -> {:?};", - name, init.node, return_value - ), - None => write!(f, "Var({:?}) -> {:?};", name, return_value), + Some(init) => write!(f, "Var({:?}: {:?} = {:?});", name, var_type, init.node,), + None => write!(f, "Var({:?}: {:?});", name, var_type,), }, - Stmt::Return { - expression, - return_value, - label, - } => write!( + Expr::Return { expression, label } => { + write!(f, "Return({:?}, {}) ;", expression.node, label,) + } + Expr::Block { label, statements } => write!( f, - "Return({:?}, {}) -> {:?};", - expression.node, label, return_value - ), - Stmt::Block { - label, - statements, - return_value, - } => write!( - f, - "Block [{}] ([\n{:?}\n]) -> {:?}", + "Block [{}] ([\n{:?}\n])", label, statements .iter() .map(|stmt| format!("{:?}", stmt.node)) .collect::>() .join("\t\t\n"), - return_value ), - Stmt::While { - condition, - body, - return_value, - } => { - write!( - f, - "While({:?}) {{\n{:?}\n}} -> {:?}", - condition.node, body.node, return_value - ) + Expr::While { condition, body } => { + write!(f, "While({:?}) {{\n{:?}\n}}", condition.node, body.node,) } - Stmt::For { + Expr::For { variable, condition, increment, body, - return_value, } => write!( f, - "For({:?} = {:?} in {:?}) {{\n{:?}\n}} -> {:?}", - variable.node, condition.node, increment.node, body.node, return_value + "For({:?} = {:?} in {:?}) {{\n{:?}\n}} ", + variable.node, condition.node, increment.node, body.node, ), } } } -pub trait AstNodeKind { - fn kind(&self) -> &'static str; -} - -impl AstNodeKind for Expr { - fn kind(&self) -> &'static str { - match self { - Expr::Literal { value: _ } => "literal", - Expr::Binary { - left: _, - operator: _, - right: _, - } => "binary expression", - Expr::Unary { .. } => "unary expression", - Expr::Grouping { .. } => "grouping expression", - Expr::Identifier { .. } => "variable expression", - Expr::Call { .. } => "call expression", - Expr::Assign { .. } => "assignment expression", - } - } -} - -impl AstNodeKind for Stmt { - fn kind(&self) -> &'static str { - match self { - Stmt::Expression { .. } => "expression", - Stmt::VarDeclaration { .. } => "variable declaration", - Stmt::Return { .. } => "return statement", - Stmt::Block { .. } => "block statement", - Stmt::If { .. } => "if statement", - Stmt::Print { .. } => "print statement", - Stmt::While { .. } => "while statement", - Stmt::For { .. } => "for statement", - } - } -} - #[derive(Clone)] -pub struct AstNode { +pub struct AstNode { /// Stable identity, assigned at construction and preserved across clones. pub id: NodeId, - pub node: T, + pub node: Expr, + pub is_statement: bool, pub source_slice: SourceSlice, + pub return_type: Type, } // Identity (`id`) deliberately does not participate in equality: two nodes are // equal when their content and location match, regardless of node id. -impl PartialEq for AstNode { +impl PartialEq for AstNode { fn eq(&self, other: &Self) -> bool { self.node == other.node && self.source_slice == other.source_slice } } -impl Display for AstNode { +impl Display for AstNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, @@ -441,7 +287,7 @@ impl Display for AstNode { } } -impl Debug for AstNode { +impl Debug for AstNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, @@ -451,12 +297,22 @@ impl Debug for AstNode { } } -impl AstNode { - pub fn new(node: T, source_slice: SourceSlice) -> Self { +impl AstNode { + fn new(node: Expr, source_slice: SourceSlice, type_name: String, is_statement: bool) -> Self { AstNode { id: NodeId::next(), node, + is_statement, source_slice, + return_type: Type::Unresolved(type_name), } } + + pub fn new_statement(node: Expr, source_slice: SourceSlice) -> Self { + Self::new(node, source_slice, "Nil".to_string(), true) + } + + pub fn new_expression(node: Expr, source_slice: SourceSlice, type_name: String) -> Self { + Self::new(node, source_slice, type_name, false) + } } diff --git a/src/common/base_value.rs b/src/common/base_value.rs index bc6ed9c..9d1862d 100755 --- a/src/common/base_value.rs +++ b/src/common/base_value.rs @@ -1,12 +1,12 @@ use core::fmt; +use std::collections::HashMap; use std::fmt::Display; use std::ops::{Add, Div, Mul, Not, Rem, Sub}; -use crate::backend::environment::Environment; use crate::common::lox_result::{runtime_error, LoxResult}; +use crate::common::types::Type; use crate::{ - backend::environment::EnvironmentStack, - common::ast::{AstNode, Expr, Stmt}, + backend::environment::EnvironmentStack, common::ast::AstNode, frontend::source_registry::SourceSlice, }; #[derive(Debug, Clone, PartialEq)] @@ -272,15 +272,20 @@ pub enum BaseValue { Nil, Function(LoxFunction), NativeFunction(NativeFunction), + Struct(Struct), +} + +struct Dict { + map: HashMap, } struct ReturnValue { label: Vec, - value: BaseValue, + value: Type, } impl ReturnValue { - pub fn new(label: Vec, value: BaseValue) -> Self { + pub fn new(label: Vec, value: Type) -> Self { Self { label, value } } } @@ -304,39 +309,40 @@ impl Display for BaseValue { BaseValue::Nil => write!(f, "nil"), BaseValue::Function(..) => write!(f, ""), BaseValue::NativeFunction(..) => write!(f, ""), + BaseValue::Struct(_) => write!(f, ""), } } } #[derive(Debug, Clone, PartialEq)] pub struct LoxFunction { - pub parameters: Vec<(String, String)>, - pub return_type: Option, - pub body: AstNode, + pub parameters: Vec<(String, Type)>, + pub return_type: Type, + pub body: AstNode, pub closure: Option>, - pub guard: Option>>, + pub guard: Option>, } impl LoxFunction { pub fn new( - parameters: Vec<(String, String)>, - body: AstNode, + parameters: Vec<(String, Type)>, + body: AstNode, closure: Option>, - guard: Option>>, + guard: Option>, ) -> Self { LoxFunction { parameters, - return_type: None, + return_type: Type::Any, body, closure, guard, } } - pub fn anonymous_function(parameters: Vec<(String, String)>, body: AstNode) -> Self { + pub fn anonymous_function(parameters: Vec<(String, Type)>, body: AstNode) -> Self { LoxFunction { parameters, - return_type: None, + return_type: Type::Any, body, closure: None, guard: None, @@ -346,12 +352,12 @@ impl LoxFunction { #[derive(Debug, Clone, PartialEq)] pub struct NativeFunction { - pub parameters: Vec<(String, String)>, + pub parameters: Vec<(String, Type)>, pub function: fn(&[BaseValue]) -> BaseValue, } impl NativeFunction { - pub fn new(parameters: Vec<(String, String)>, function: fn(&[BaseValue]) -> BaseValue) -> Self { + pub fn new(parameters: Vec<(String, Type)>, function: fn(&[BaseValue]) -> BaseValue) -> Self { NativeFunction { parameters, function, @@ -359,6 +365,11 @@ impl NativeFunction { } } +#[derive(Debug, Clone, PartialEq)] +pub struct Struct { + pub fields: Vec<(String, BaseValue)>, +} + // Trait implementations for BaseValue pub trait Truthy { diff --git a/src/common/mod.rs b/src/common/mod.rs index 736f74d..ccbfcd9 100755 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,3 +1,4 @@ pub mod ast; pub mod base_value; pub mod lox_result; +pub mod types; diff --git a/src/common/types.rs b/src/common/types.rs new file mode 100644 index 0000000..d5a3dd7 --- /dev/null +++ b/src/common/types.rs @@ -0,0 +1,70 @@ +use std::{fmt::Display, rc::Rc}; + +#[derive(Debug, Clone, PartialEq)] +pub enum Type { + Any, + Number, + String, + Boolean, + Nil, + Function(FunctionType), + Struct(Rc), + Unresolved(String), +} + +impl Display for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Type::Any => write!(f, "any"), + Type::Number => write!(f, "number"), + Type::String => write!(f, "string"), + Type::Boolean => write!(f, "boolean"), + Type::Nil => write!(f, "nil"), + Type::Function(fun) => write!(f, "{}", fun), + Type::Struct(struct_) => write!(f, "{}", struct_), + Type::Unresolved(name) => write!(f, "{}", name), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct FunctionType { + pub params: Vec<(String, Box)>, + pub return_type: Box, +} + +impl Display for FunctionType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let rendered_params = self + .params + .iter() + .map(|(name, ty)| format!("{}: {}", name, ty)) + .collect::>() + .join(", "); + write!(f, "fn({}) -> {}", rendered_params, self.return_type) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct StructType { + pub fields: Vec<(String, Box)>, + pub methods: Vec<(String, FunctionType)>, +} + +impl Display for StructType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let rendered_field = self + .fields + .iter() + .map(|(name, ty)| format!("{}: {}", name, ty)) + .collect::>() + .join(", "); + let rendered_method = self + .methods + .iter() + .map(|(name, ty)| format!("{}: {}", name, ty)) + .collect::>() + .join(", "); + write!(f, "struct {{ {}, {} }}", rendered_field, rendered_method) + } +} diff --git a/src/frontend/lexer.rs b/src/frontend/lexer.rs index 5c65137..5a61faf 100755 --- a/src/frontend/lexer.rs +++ b/src/frontend/lexer.rs @@ -16,7 +16,7 @@ pub struct Lexer { fn get_keyword_token(word: &str) -> Option { match word { "and" => Some(TokenType::And), - "class" => Some(TokenType::Class), + "struct" => Some(TokenType::Struct), "do" => Some(TokenType::StartBlock), "end" => Some(TokenType::EndBlock), "false" => Some(TokenType::False), @@ -514,4 +514,32 @@ mod tests { assert_eq!(tokens[1].source_slice.start_position.line, 1); assert_eq!(tokens[1].source_slice.start_position.column, 0); } + + #[test] + fn handle_struct() { + let tokens = lex("Cosa :: struct {field1: Number, field2: String}"); + let tokens_tyepe: Vec = tokens.iter().map(|x| x.token_type.clone()).collect(); + let tokent_for_check = vec![ + TokenType::Identifier, + TokenType::Colon, + TokenType::Colon, + TokenType::Struct, + TokenType::LeftBrace, + TokenType::Identifier, + TokenType::Colon, + TokenType::Identifier, + TokenType::Comma, + TokenType::Identifier, + TokenType::Colon, + TokenType::Identifier, + TokenType::RightBrace, + TokenType::Eof, + ]; + assert_eq!(tokens_tyepe, tokent_for_check); + assert_eq!(tokens[0].lexeme, "Cosa"); + assert_eq!(tokens[5].lexeme, "field1"); + assert_eq!(tokens[7].lexeme, "Number"); + assert_eq!(tokens[9].lexeme, "field2"); + assert_eq!(tokens[11].lexeme, "String"); + } } diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs index 1a279c0..034bbb1 100755 --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -1,8 +1,11 @@ +use std::collections::hash_map::Values; + use crate::{ common::{ - ast::{AstNode, Expr, Stmt}, + ast::{AstNode, Expr}, base_value::{BaseValue, LoxFunction}, lox_result::{parse_error, runtime_error, LoxError, LoxResult}, + types::{FunctionType, Type}, }, frontend::{ source_registry::SourceSlice, @@ -20,11 +23,11 @@ impl Parser { Self { tokens, current: 0 } } - pub fn parse(&mut self) -> LoxResult>> { + pub fn parse(&mut self) -> LoxResult> { let mut statements = Vec::new(); let mut errors = Vec::new(); while !self.is_at_end() { - match self.statement(None) { + match self.statement() { Ok(stmt) => { statements.push(stmt.clone()); } @@ -42,12 +45,23 @@ impl Parser { Ok(statements) } - fn statement(&mut self, label: Option) -> LoxResult> { + fn statement(&mut self) -> LoxResult { match (&self.peek().token_type, &self.peek_next().token_type) { (TokenType::Print, _) => self.print_statement(), - (TokenType::Return, _) => self.return_statement(label), + (TokenType::Return, _) => { + let mut label = None; + if self.peek_next().token_type == TokenType::Colon { + self.advance(); + label = Some( + self.consume(TokenType::Identifier, "Expect label after return colon.")? + .lexeme + .clone(), + ); + } + self.return_statement(label) + } (TokenType::Break, _) => self.return_statement(Some("loop".to_string())), - (TokenType::StartBlock, _) => self.block_statement(label), + (TokenType::StartBlock, _) => self.block_statement(), (TokenType::Var, TokenType::Identifier) => { self.advance(); self.var_statement() @@ -60,7 +74,7 @@ impl Parser { } } - fn for_statement(&mut self) -> LoxResult> { + fn for_statement(&mut self) -> LoxResult { let start_slice = self.peek().source_slice.clone(); self.advance(); // consume 'for' @@ -68,7 +82,7 @@ impl Parser { let condition = self.expression()?; self.consume(TokenType::Semicolon, "Expected ';' after for condition")?; let increment = self.expression_statement()?; - let body = self.statement(Some("loop".to_string()))?; + let body = self.statement()?; let end_slice = self.previous().source_slice.clone(); let combined_slice = SourceSlice::from_positions( @@ -77,87 +91,43 @@ impl Parser { end_slice.end_position, ); - Ok(AstNode::new( - Stmt::For { + Ok(AstNode::new_statement( + Expr::For { variable: Box::new(variable), condition: Box::new(condition), increment: Box::new(increment), body: Box::new(body), - return_value: Box::new(BaseValue::Nil), }, combined_slice, )) } - // fn for_statement(&mut self) -> LoxResult> { - // let start_slice = self.peek().source_slice.clone(); - // self.advance(); - // let variable = if let Token { - // token_type: TokenType::Identifier, - // literal: Some(variable_name), - // source_slice, - // .. - // } = self.peek() - // { - // AstNode { - // node: Expr::Literal { - // value: variable_name.clone(), - // }, - // source_slice: source_slice.clone(), - // } - // } else { - // return parse_error( - // self.peek().source_slice.clone(), - // "Expect variable name after 'for' keyword.", - // ); - // }; - // self.advance(); - // self.consume(TokenType::In, "Expect 'in' after for variable.")?; - // let iterable = self.expression()?; - // let body = self.statement(None)?; - // 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::For { - // variable: Box::new(variable), - // iterable: Box::new(iterable), - // body: Box::new(body), - // }, - // combined_slice, - // )) - // } - - 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.statement(Some("loop".to_string()))?; + 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 { + Ok(AstNode::new_statement( + Expr::While { condition: Box::new(condition), body: Box::new(body), - return_value: Box::new(BaseValue::Nil), }, combined_slice, )) } - fn if_statement(&mut self) -> LoxResult> { + 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(None)?; + let then_branch = self.statement()?; let mut elif_branches = Vec::new(); let mut last_slice = then_branch.source_slice.clone(); @@ -165,14 +135,14 @@ impl Parser { self.advance(); // consume 'elif' let elif_condition = self.expression()?; self.consume(TokenType::Then, "Expect 'then' after elif condition.")?; - let elif_branch = self.statement(None)?; + 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(); // consume 'else' - let else_stmt = self.statement(None)?; + let else_stmt = self.statement()?; last_slice = else_stmt.source_slice.clone(); Some(Box::new(else_stmt)) } else { @@ -185,59 +155,97 @@ impl Parser { last_slice.end_position, ); - Ok(AstNode::new( - Stmt::If { + Ok(AstNode::new_statement( + Expr::If { condition: Box::new(condition), then_branch: Box::new(then_branch), - elif_branch: elif_branches, - else_branch: else_branch, - return_value: Box::new(BaseValue::Nil), + elif_branches, + else_branch, }, combined_slice, )) } - fn var_statement(&mut self) -> LoxResult> { + fn var_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(); + let name = self + .consume(TokenType::Identifier, "Expect variable name.")? + .lexeme + .clone(); + self.consume(TokenType::Colon, "Expected 'colon' after var identifier")?; + let start_slice_clone = start_slice.clone(); - let mut _type_annotation = BaseValue::Nil; - self.consume(TokenType::Colon, "Expect column")?; - if self.peek().token_type == TokenType::Identifier { - // TODO manage type annotation - self.consume(TokenType::Identifier, "Expect type name.")?; - } - - match self.peek().token_type { - TokenType::Equal | TokenType::Identifier => { - self.variable_declaration(start_slice, name_lexeme) + let mut var_type = Type::Any; + let var_content = match self.peek().token_type { + TokenType::Equal | TokenType::Identifier | TokenType::Nil => { + if self.peek_is_type_name() { + let tyepe_annotation = self.consume_type_name("Expected Identier")?; + var_type = Type::Unresolved(tyepe_annotation); + } + let mut value: Option> = None; + if self.peek().token_type == TokenType::Equal { + self.advance(); + value = Some(Box::new(self.expression()?)); + } + value } - TokenType::Colon => self.function_declaration(start_slice, name_lexeme), + + TokenType::Colon => { + self.consume(TokenType::Colon, "Expected 'colon' after var identifier")?; + var_type = Type::Unresolved("Function".to_string()); + let res = self.struct_or_function_declaration(start_slice)?; + Some(Box::new(res)) + } + _ => runtime_error(start_slice, "this is not supposed to be here")?, + }; + + let semicolon = self.consume( + TokenType::Semicolon, + "Expected 'semicolon' after var declaration", + )?; + + let slice = SourceSlice::from_source_slices( + start_slice_clone.clone(), + semicolon.source_slice.clone(), + ); + let variable = Expr::VarDeclaration { + name, + var_type, + initializer: var_content, + }; + + Ok(AstNode::new_statement(variable, slice)) + } + + fn struct_or_function_declaration(&mut self, start_slice: SourceSlice) -> LoxResult { + match self.peek().token_type { + TokenType::Fn => self.function_declaration(start_slice), + TokenType::Struct => self.struct_declaration(start_slice), _ => runtime_error(start_slice, "this is not supposed to be here"), } } - fn function_declaration( - &mut self, - start_slice: SourceSlice, - name_lexeme: String, - ) -> LoxResult> { - self.advance(); + fn struct_declaration(&mut self, start_slice: SourceSlice) -> LoxResult { + self.consume(TokenType::Struct, "Expect 'struct' after '::'")?; + self.consume(TokenType::LeftBrace, "Expect '{' after 'struct'")?; + + todo!() + } + + fn function_declaration(&mut self, start_slice: SourceSlice) -> LoxResult { self.consume(TokenType::Fn, "Expect 'fn' after '::'")?; self.consume(TokenType::LeftParen, "Expect '(' after 'fn' ")?; - let mut parameters: Vec<(String, String)> = vec![]; + let mut parameters: Vec<(String, Type)> = vec![]; while self.peek().token_type != TokenType::RightParen { - let expr = self + let parameter_name = self .consume(TokenType::Identifier, "Expect identifier after '('")? .clone(); if self.peek().token_type == TokenType::Colon { self.advance(); - let type_ = - self.consume(TokenType::Identifier, "Expected identifier after ':' ")?; - parameters.push((expr.lexeme, type_.lexeme.clone())); + let paramete_type = self.consume_type_name("Expected identifier after ':' ")?; + parameters.push((parameter_name.lexeme, Type::Unresolved(paramete_type))); } else { - parameters.push((expr.lexeme, "Any".to_string())); + parameters.push((parameter_name.lexeme, Type::Any)); } if self.peek().token_type == TokenType::Comma { self.advance(); @@ -251,79 +259,32 @@ impl Parser { end_position: end_position.end_position, }; - let mut guard: Option>> = None; + let mut guard: Option> = None; if self.peek().token_type == TokenType::LeftBrace { self.consume(TokenType::LeftBrace, "Expected '{' after guard expression")?; guard = Some(Box::new(self.expression()?)); self.consume(TokenType::RightBrace, "Expected '}' after guard expression")?; } + self.consume(TokenType::Colon, "Expected ':' after parameters")?; + let return_type = self.consume_type_name("Expected Type identifier after ':'")?; - let body = self.statement(None)?; - let node = AstNode::new( + let body = self.statement()?; + Ok(AstNode::new_expression( Expr::Literal { - value: BaseValue::Function(LoxFunction { + value: Box::new(BaseValue::Function(LoxFunction { parameters, - return_type: None, + return_type: Type::Unresolved(return_type.clone()), body, closure: None, guard, - }), - }, - combine_position.clone(), - ); - Ok(AstNode::new( - Stmt::VarDeclaration { - name: name_lexeme, - initializer: Some(Box::new(node)), - return_value: Box::new(BaseValue::Nil), + })), }, combine_position.clone(), + return_type.clone(), )) } - fn variable_declaration( - &mut self, - start_slice: SourceSlice, - name_lexeme: String, - ) -> LoxResult> { - if self.peek().token_type == TokenType::Identifier { - self.advance(); - if self.peek().token_type == TokenType::Identifier { - self.advance(); - } - // todo: make type annotation - } - let mut value = AstNode::new( - Expr::Literal { - value: BaseValue::Nil, - }, - start_slice.clone(), - ); - if self.peek().token_type == TokenType::Equal { - self.advance(); - value = self.expression()?; - } - let semicolon = self.consume( - TokenType::Semicolon, - "Expect ';' after variable declaration.", - )?; - 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::VarDeclaration { - name: name_lexeme, - initializer: Some(Box::new(value)), - return_value: Box::new(BaseValue::Nil), - }, - 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(); @@ -335,22 +296,31 @@ impl Parser { start_slice.start_position, end_slice.end_position, ); - Ok(AstNode::new( - Stmt::Print { + Ok(AstNode::new_statement( + Expr::Print { expression: Box::new(expr), - return_value: Box::new(BaseValue::Nil), }, combined_slice, )) } - fn block_statement(&mut self, label: Option) -> LoxResult> { + fn block_statement(&mut self) -> LoxResult { let start_slice = self.peek().source_slice.clone(); self.advance(); + let mut label = None; + if self.peek().token_type == TokenType::Colon { + self.advance(); + label = Some( + self.consume(TokenType::Identifier, "Expect label after colon.")? + .lexeme + .clone(), + ); + } + let mut statements = Vec::new(); while self.peek().token_type != TokenType::EndBlock && !self.is_at_end() { - let stmt = self.statement(label.clone())?; + let stmt = self.statement()?; statements.push(stmt); } let end_token = self.consume(TokenType::EndBlock, "Expect 'end' after block.")?; @@ -365,98 +335,66 @@ impl Parser { } else { String::default() }; - Ok(AstNode::new( - Stmt::Block { + Ok(AstNode::new_statement( + Expr::Block { statements: Box::new(statements), label: label_str, - return_value: Box::new(BaseValue::Nil), }, 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 { - 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::Expression { - expression: Box::new(expr), - return_value: Box::new(BaseValue::Nil), - }, - combined_slice, - )); - } - // 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), - return_value: Box::new(BaseValue::Nil), - }, - expr_slice, - )) + let mut expr = self.expression()?; + let semicolon = + self.consume(TokenType::Semicolon, "Expected semicolon after expression")?; + let end_slice = semicolon.source_slice.clone(); + let combined_slice = SourceSlice::from_source_slices(start_slice, end_slice); + expr.is_statement = true; + expr.source_slice = combined_slice; + return Ok(expr); } - fn return_statement(&mut self, label: Option) -> LoxResult> { + fn return_statement(&mut self, label: Option) -> LoxResult { let start_slice = self.peek().source_slice.clone(); self.advance(); - let expr = match self.expression() { - Ok(expr) => expr, - Err(LoxError::ParseError { - message, - source_slice, - }) => { - if message == "Expect expression." { - AstNode::new( - Expr::Literal { - value: BaseValue::Nil, - }, - start_slice.clone(), - ) - } else { - return Err(LoxError::ParseError { - message, - source_slice, - }); - } - } - Err(err) => return Err(err), - }; + if self.peek().token_type == TokenType::Semicolon { + let semicolon = self.consume(TokenType::Semicolon, "Expect ';' after return value.")?; + let sorce_slice = + SourceSlice::from_source_slices(start_slice, semicolon.source_slice.clone()); + return Ok(AstNode::new_expression( + Expr::Literal { + value: Box::new(BaseValue::Nil), + }, + sorce_slice, + "Nil".to_string(), + )); + } + let expr = self.expression()?; + 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, - ); + let combined_slice = SourceSlice::from_source_slices(start_slice, end_slice); let label_str = if let Some(label) = label { label } else { String::default() }; - Ok(AstNode::new( - Stmt::Return { + Ok(AstNode::new_statement( + Expr::Return { expression: Box::new(expr), - return_value: Box::new(BaseValue::Nil), label: label_str, }, combined_slice, )) } - fn expression(&mut self) -> LoxResult> { + fn expression(&mut self) -> LoxResult { self.assignment() } - fn assignment(&mut self) -> LoxResult> { + fn assignment(&mut self) -> LoxResult { let expr = self.or_and()?; if self.peek().token_type == TokenType::Equal { self.advance(); // consume '=' @@ -468,13 +406,15 @@ impl Parser { value.source_slice.end_position.clone(), ); let target_slice = expr.source_slice.clone(); + let var_type = value.return_type.to_string(); match expr.node { - Expr::Identifier { name } => Ok(AstNode::new( + Expr::Identifier { name } => Ok(AstNode::new_expression( Expr::Assign { name, value: Box::new(value), }, combined_slice, + var_type, )), _ => parse_error(target_slice, "Invalid assignment target."), } @@ -483,7 +423,7 @@ impl Parser { } } - fn or_and(&mut self) -> LoxResult> { + fn or_and(&mut self) -> LoxResult { let mut expr = self.logical_is()?; while [TokenType::Or, TokenType::And].contains(&self.peek().token_type) { @@ -497,20 +437,21 @@ impl Parser { start_slice.start_position, end_slice.end_position, ); - expr = AstNode::new( + expr = AstNode::new_expression( Expr::Binary { left: Box::new(expr), operator, right: Box::new(right), }, combined_slice, + "bool".to_string(), ); } Ok(expr) } - fn logical_is(&mut self) -> LoxResult> { + fn logical_is(&mut self) -> LoxResult { let mut expr = self.equality()?; while self.peek().token_type == TokenType::Is { @@ -524,20 +465,21 @@ impl Parser { start_slice.start_position, end_slice.end_position, ); - expr = AstNode::new( + expr = AstNode::new_expression( Expr::Binary { left: Box::new(expr), operator, right: Box::new(right), }, combined_slice, + "bool".to_string(), ); } 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) { @@ -551,20 +493,21 @@ impl Parser { start_slice.start_position, end_slice.end_position, ); - expr = AstNode::new( + expr = AstNode::new_expression( Expr::Binary { left: Box::new(expr), operator, right: Box::new(right), }, combined_slice, + "bool".to_string(), ); } Ok(expr) } - fn comparison(&mut self) -> LoxResult> { + fn comparison(&mut self) -> LoxResult { let mut expr = self.term()?; while [ @@ -585,20 +528,21 @@ impl Parser { start_slice.start_position, end_slice.end_position, ); - expr = AstNode::new( + expr = AstNode::new_expression( Expr::Binary { left: Box::new(expr), operator, right: Box::new(right), }, combined_slice, + "bool".to_string(), ); } 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) { @@ -612,20 +556,22 @@ impl Parser { start_slice.start_position, end_slice.end_position, ); - expr = AstNode::new( + let expr_type = expr.return_type.clone().to_string(); + expr = AstNode::new_expression( Expr::Binary { left: Box::new(expr), operator, right: Box::new(right), }, combined_slice, + expr_type, ); } 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) { @@ -639,38 +585,42 @@ impl Parser { start_slice.start_position, end_slice.end_position, ); - expr = AstNode::new( + let expr_type = expr.return_type.clone().to_string(); + expr = AstNode::new_expression( Expr::Binary { left: Box::new(expr), operator, right: Box::new(right), }, combined_slice, + expr_type, ); } 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(AstNode::new( + let expr_type = right.return_type.clone().to_string(); + return Ok(AstNode::new_expression( Expr::Unary { operator, operand: Box::new(right), }, source_slice, + expr_type, )); } self.call() } - fn call(&mut self) -> LoxResult> { + fn call(&mut self) -> LoxResult { let mut expr = self.primary()?; loop { @@ -683,7 +633,7 @@ impl Parser { Ok(expr) } - fn finish_call(&mut self, callee: AstNode) -> LoxResult> { + fn finish_call(&mut self, callee: AstNode) -> LoxResult { let start_slice = callee.source_slice.start_position.clone(); self.advance(); // Consume '(' @@ -709,46 +659,50 @@ impl Parser { start_position: start_slice.clone(), end_position: end_slice.clone(), }; - - Ok(AstNode::new( + let expr_type = callee.return_type.clone().to_string(); + Ok(AstNode::new_expression( Expr::Call { callee: Box::new(callee), arguments, }, full_slice, + expr_type, )) } - 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(AstNode::new( + Ok(AstNode::new_expression( Expr::Literal { - value: BaseValue::Boolean(false), + value: Box::new(BaseValue::Boolean(false)), }, source_slice, + "bool".to_string(), )) } TokenType::True => { let source_slice = self.peek().source_slice.clone(); self.advance(); - Ok(AstNode::new( + Ok(AstNode::new_expression( Expr::Literal { - value: BaseValue::Boolean(true), + value: Box::new(BaseValue::Boolean(true)), }, source_slice, + "bool".to_string(), )) } TokenType::Nil => { let source_slice = self.peek().source_slice.clone(); self.advance(); - Ok(AstNode::new( + Ok(AstNode::new_expression( Expr::Literal { - value: BaseValue::Nil, + value: Box::new(BaseValue::Nil), }, source_slice, + "nil".to_string(), )) } TokenType::Number => { @@ -756,7 +710,13 @@ impl Parser { let literal = self.peek().literal.clone(); self.advance(); if let Some(literal) = literal { - Ok(AstNode::new(Expr::Literal { value: literal }, source_slice)) + Ok(AstNode::new_expression( + Expr::Literal { + value: Box::new(literal), + }, + source_slice, + "number".to_string(), + )) } else { parse_error(self.peek().source_slice.clone(), "Expected number literal") } @@ -766,7 +726,13 @@ impl Parser { let literal = self.peek().literal.clone(); self.advance(); if let Some(literal) = literal { - Ok(AstNode::new(Expr::Literal { value: literal }, source_slice)) + Ok(AstNode::new_expression( + Expr::Literal { + value: Box::new(literal), + }, + source_slice, + "string".to_string(), + )) } else { parse_error(self.peek().source_slice.clone(), "Expected string literal") } @@ -776,18 +742,23 @@ impl Parser { self.advance(); let expr = self.expression()?; self.consume(TokenType::RightParen, "Expect ')' after expression.")?; - Ok(AstNode::new( + Ok(AstNode::new_expression( Expr::Grouping { expression: Box::new(expr), }, source_slice, + "grouping".to_string(), )) } TokenType::Identifier => { let name = self.peek().lexeme.clone(); let source_slice = self.peek().source_slice.clone(); self.advance(); - Ok(AstNode::new(Expr::Identifier { name }, source_slice)) + Ok(AstNode::new_expression( + Expr::Identifier { name }, + source_slice, + "identifier".to_string(), + )) } _ => parse_error(self.peek().source_slice.clone(), "Expect expression."), } @@ -841,6 +812,25 @@ impl Parser { } } + /// Consume a type name and return its lexeme. + /// + /// A type name is an identifier (e.g. `Int`, `Number`, a struct name) or a + /// built-in type keyword such as `nil` that overlaps with a value keyword. + fn consume_type_name(&mut self, message: &str) -> LoxResult { + match self.peek().token_type { + TokenType::Identifier | TokenType::Nil => Ok(self.advance().lexeme.clone()), + _ => parse_error(self.peek().source_slice.clone(), message), + } + } + + /// Whether the current token can begin a type name. + fn peek_is_type_name(&self) -> bool { + matches!( + self.peek().token_type, + TokenType::Identifier | TokenType::Nil + ) + } + fn synchronize(&mut self) { self.advance(); @@ -850,7 +840,7 @@ impl Parser { } match self.peek().token_type { - TokenType::Class + TokenType::Struct | TokenType::Fun | TokenType::For | TokenType::If @@ -872,7 +862,7 @@ mod tests { use crate::frontend::lexer::Lexer; /// Lex and parse `src`, returning the parser's result. - fn parse_source(src: &str) -> LoxResult>> { + fn parse_source(src: &str) -> LoxResult> { let tokens = Lexer::new(src.to_string(), 0) .scans_tokens() .expect("source should lex without errors"); @@ -880,16 +870,18 @@ mod tests { } /// Parse `src`, panicking if parsing fails. - fn parse_ok(src: &str) -> Vec> { + fn parse_ok(src: &str) -> Vec { parse_source(src).expect("expected source to parse") } /// Extract the inner expression of an expression statement. - fn expression_of(stmt: &AstNode) -> &AstNode { - match &stmt.node { - Stmt::Expression { expression, .. } => expression, - other => panic!("expected expression statement, got {:?}", other), - } + /// + /// Expression statements aren't wrapped in a dedicated `Expr` variant; + /// the parser just marks the node's `is_statement` flag, so the node + /// itself already holds the expression. + fn expression_of(stmt: &AstNode) -> &AstNode { + assert!(stmt.is_statement, "expected an expression statement"); + stmt } #[test] @@ -898,7 +890,7 @@ mod tests { assert_eq!(stmts.len(), 1); match &expression_of(&stmts[0]).node { Expr::Literal { value } => { - assert_eq!(*value, BaseValue::Number(Number::I32(42))); + assert_eq!(**value, BaseValue::Number(Number::I32(42))); } other => panic!("expected literal, got {:?}", other), } @@ -946,6 +938,15 @@ mod tests { } } + #[test] + fn parses_identifier_expression() { + let stmts = parse_ok("x;"); + match &expression_of(&stmts[0]).node { + Expr::Identifier { name } => assert_eq!(name, "x"), + other => panic!("expected identifier expression, got {:?}", other), + } + } + #[test] fn parses_comparison_expression() { let stmts = parse_ok("1 < 2;"); @@ -958,14 +959,14 @@ mod tests { #[test] fn parses_print_statement() { let stmts = parse_ok("print 1;"); - assert!(matches!(stmts[0].node, Stmt::Print { .. })); + assert!(matches!(stmts[0].node, Expr::Print { .. })); } #[test] fn parses_var_declaration_with_initializer() { let stmts = parse_ok("var x: Int = 5;"); match &stmts[0].node { - Stmt::VarDeclaration { + Expr::VarDeclaration { name, initializer, .. } => { assert_eq!(name, "x"); @@ -978,12 +979,9 @@ mod tests { #[test] fn parses_assignment_statement() { let stmts = parse_ok("x = 5;"); - match &stmts[0].node { - Stmt::Expression { expression, .. } => match &expression.node { - Expr::Assign { name, .. } => assert_eq!(name, "x"), - other => panic!("expected assign expression, got {:?}", other), - }, - other => panic!("expected expression statement, got {:?}", other), + match &expression_of(&stmts[0]).node { + Expr::Assign { name, .. } => assert_eq!(name, "x"), + other => panic!("expected assign expression, got {:?}", other), } } @@ -991,7 +989,7 @@ mod tests { fn parses_block_statement() { let stmts = parse_ok("do print 1; print 2; end"); match &stmts[0].node { - Stmt::Block { statements, .. } => assert_eq!(statements.len(), 2), + Expr::Block { statements, .. } => assert_eq!(statements.len(), 2), other => panic!("expected block statement, got {:?}", other), } } @@ -999,13 +997,13 @@ mod tests { #[test] fn parses_if_statement() { let stmts = parse_ok("if true then print 1;"); - assert!(matches!(stmts[0].node, Stmt::If { .. })); + assert!(matches!(stmts[0].node, Expr::If { .. })); } #[test] fn parses_while_statement() { let stmts = parse_ok("while true do print 1; end"); - assert!(matches!(stmts[0].node, Stmt::While { .. })); + assert!(matches!(stmts[0].node, Expr::While { .. })); } #[test] diff --git a/src/frontend/source_registry.rs b/src/frontend/source_registry.rs index 7c245fa..9dc55ef 100755 --- a/src/frontend/source_registry.rs +++ b/src/frontend/source_registry.rs @@ -121,6 +121,14 @@ impl SourceSlice { end_position, } } + + pub fn from_source_slices(start_point: SourceSlice, end_point: SourceSlice) -> Self { + Self { + source_id: start_point.source_id, + start_position: start_point.start_position, + end_position: end_point.end_position, + } + } pub fn tree_point( source_id: SourceId, column_start: usize, diff --git a/src/frontend/tokens.rs b/src/frontend/tokens.rs index d822670..2701b7e 100755 --- a/src/frontend/tokens.rs +++ b/src/frontend/tokens.rs @@ -35,11 +35,12 @@ pub enum TokenType { Identifier, String, Number, + Atom, // Keywords Fn, And, - Class, + Struct, StartBlock, EndBlock, False, @@ -80,8 +81,9 @@ impl fmt::Display for TokenType { TokenType::Identifier => write!(f, "IDENTIFIER"), TokenType::String => write!(f, "STRING"), TokenType::Number => write!(f, "NUMBER"), + TokenType::Atom => write!(f, "ATOM"), TokenType::And => write!(f, "and"), - TokenType::Class => write!(f, "class"), + TokenType::Struct => write!(f, "struct"), TokenType::Else => write!(f, "else"), TokenType::False => write!(f, "false"), TokenType::True => write!(f, "true"), diff --git a/src/lib.rs b/src/lib.rs index f030b0c..5f4de5d 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod backend; pub mod common; pub mod frontend; +pub mod logging; pub mod middleend; diff --git a/src/logging/display_ast.rs b/src/logging/display_ast.rs index 04775c0..4055696 100755 --- a/src/logging/display_ast.rs +++ b/src/logging/display_ast.rs @@ -5,7 +5,7 @@ use std::fmt::{self, Write}; -use crate::common::ast::{AstNode, AstNodeKind, Expr, Stmt}; +use crate::common::ast::{AstNode, Expr}; /// Configuration for pretty printing output #[derive(Clone, Debug)] @@ -264,33 +264,7 @@ impl PrettyPrint for Expr { write!(f, "] }}") } } - } - } -} - -impl PrettyPrint for Stmt { - fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result { - if ctx.should_truncate() { - return write!(f, "{}...", ctx.indent()); - } - - let indent = ctx.indent(); - - match self { - Stmt::Expression { expression, .. } => { - if ctx.config.compact { - let expr_str = - pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); - write!(f, "{};", expr_str) - } else { - writeln!(f, "{}Expression {{", indent)?; - write!(f, "{}expression: ", ctx.child_context(true).indent())?; - expression.pretty_print(&ctx.child_context(true), f)?; - writeln!(f)?; - write!(f, "{}}}", indent) - } - } - Stmt::Print { expression, .. } => { + Expr::Print { expression } => { if ctx.config.compact { let expr_str = pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); @@ -303,7 +277,7 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::VarDeclaration { + Expr::VarDeclaration { name, initializer, .. } => { if ctx.config.compact { @@ -331,8 +305,7 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - - Stmt::Return { expression, .. } => { + Expr::Return { expression, .. } => { if ctx.config.compact { let expr_str = pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); @@ -345,9 +318,7 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::Block { - statements, label, .. - } => { + Expr::Block { statements, label } => { if ctx.config.compact { write!(f, "{{ ... [{}] ({} statements) }}", label, statements.len()) } else { @@ -366,12 +337,11 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::If { + Expr::If { condition, then_branch, - elif_branch, + elif_branches, else_branch, - .. } => { if ctx.config.compact { let cond_str = @@ -386,10 +356,10 @@ impl PrettyPrint for Stmt { then_branch.pretty_print(&ctx.child_context(false), f)?; writeln!(f, ",")?; - if !elif_branch.is_empty() { + if !elif_branches.is_empty() { writeln!(f, "{}elif_branches: [", ctx.child_context(false).indent())?; - for (i, (cond, branch)) in elif_branch.iter().enumerate() { - let is_last = i == elif_branch.len() - 1; + for (i, (cond, branch)) in elif_branches.iter().enumerate() { + let is_last = i == elif_branches.len() - 1; let child_ctx = ctx.child_context(false).child_context(is_last); writeln!(f, "{}(", child_ctx.indent())?; write!(f, "{}condition: ", child_ctx.child_context(false).indent())?; @@ -421,21 +391,18 @@ impl PrettyPrint for Stmt { write!(f, "{}}}", indent) } } - Stmt::While { - condition, body, .. - } => { + Expr::While { condition, body } => { write!(f, "{}while (", ctx.child_context(true).indent())?; condition.pretty_print(&ctx.child_context(true), f)?; writeln!(f, ") {{")?; body.pretty_print(&ctx.child_context(true), f)?; writeln!(f, "{}}}", ctx.child_context(true).indent()) } - Stmt::For { + Expr::For { variable, condition, increment, body, - .. } => { write!(f, "{}for (", ctx.child_context(true).indent())?; variable.pretty_print(&ctx.child_context(true), f)?; @@ -451,7 +418,7 @@ impl PrettyPrint for Stmt { } } -impl PrettyPrint for AstNode { +impl PrettyPrint for AstNode { fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result { let indent = ctx.indent(); @@ -463,9 +430,9 @@ impl PrettyPrint for A if ctx.config.show_types { writeln!( f, - "{}kind: {:?},", + "{}type: {},", ctx.child_context(false).indent(), - self.node.kind() + self.return_type )?; } @@ -519,25 +486,7 @@ impl PrettyPrintExt for Expr { } } -impl PrettyPrintExt for Stmt { - fn pretty(&self) -> String { - pretty_print(self) - } - - fn pretty_compact(&self) -> String { - pretty_print_with_config(self, &PrettyConfig::compact()) - } - - fn pretty_tree(&self) -> String { - pretty_print_with_config(self, &PrettyConfig::tree()) - } - - fn pretty_with_config(&self, config: &PrettyConfig) -> String { - pretty_print_with_config(self, config) - } -} - -impl PrettyPrintExt for AstNode { +impl PrettyPrintExt for AstNode { fn pretty(&self) -> String { pretty_print(self) } diff --git a/src/logging/display_token.rs b/src/logging/display_token.rs index 5ce6980..4910c95 100755 --- a/src/logging/display_token.rs +++ b/src/logging/display_token.rs @@ -174,7 +174,8 @@ impl Token { TokenType::String => "STR", TokenType::Number => "NUM", TokenType::And => "AND", - TokenType::Class => "CLASS", + TokenType::Struct => "STRUCT", + TokenType::Atom => "ATOM", TokenType::StartBlock => "DO", TokenType::EndBlock => "END", TokenType::False => "FALSE", @@ -219,7 +220,7 @@ impl Token { | TokenType::Less | TokenType::LessEqual => ("\x1b[31m", self.token_type_symbol()), TokenType::And - | TokenType::Class + | TokenType::Struct | TokenType::False | TokenType::True | TokenType::Fun @@ -239,6 +240,7 @@ impl Token { | TokenType::Var | TokenType::Fn | TokenType::Is + | TokenType::Atom | TokenType::Val => ("\x1b[34m", self.token_type_symbol()), TokenType::Identifier | TokenType::String | TokenType::Number => { ("\x1b[32m", self.token_type_symbol()) diff --git a/src/main.rs b/src/main.rs index e60e051..ff296d4 100755 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,12 @@ -mod backend; -mod common; -mod frontend; -mod logging; -mod middleend; - +pub mod backend; +pub mod common; +pub mod frontend; +pub mod logging; +pub mod middleend; use crate::{ backend::interpreter::{EvaluateInterpreter, Interpreter}, common::{ - ast::{AstNode, Stmt}, + ast::AstNode, lox_result::{LoxError, LoxResult}, }, frontend::{ @@ -175,7 +174,7 @@ impl LoxInterpreter { } // Funzione per parsare fino all'AST - fn parse_to_ast(&self, source_id: SourceId) -> LoxResult>> { + fn parse_to_ast(&self, source_id: SourceId) -> LoxResult> { let tokens = self.tokenize(source_id)?; Parser::new(tokens).parse().or_else(|err| { @@ -185,7 +184,7 @@ impl LoxInterpreter { } // Funzione per eseguire l'AST - fn execute_ast(&self, ast: Vec>, debug: bool) -> LoxResult { + fn execute_ast(&self, ast: Vec, debug: bool) -> LoxResult { // Static resolution pass: compute variable scope distances and report // any resolution errors before executing. let locals = self.resolve(&ast)?; @@ -212,7 +211,7 @@ impl LoxInterpreter { // Static variable resolution; prints and returns the first error, if any. fn resolve( &self, - ast: &[AstNode], + ast: &[AstNode], ) -> LoxResult> { Resolver::resolve_program(ast).map_err(|errors| { for err in &errors { @@ -295,7 +294,7 @@ fn format_tokens(tokens: &[crate::frontend::tokens::Token]) -> String { .join("\n") } -fn format_ast(ast: &[AstNode]) -> String { +fn format_ast(ast: &[AstNode]) -> String { use crate::logging::display_ast::{pretty_print_with_config, PrettyConfig}; let config = PrettyConfig { diff --git a/src/middleend/variable_resolution.rs b/src/middleend/variable_resolution.rs index c2be7e5..5827240 100755 --- a/src/middleend/variable_resolution.rs +++ b/src/middleend/variable_resolution.rs @@ -17,13 +17,13 @@ use std::collections::HashMap; use crate::{ common::{ - ast::{AstNode, Expr, NodeId, Stmt}, + ast::{AstNode, Expr, NodeId}, base_value::{BaseValue, LoxFunction}, lox_result::{ErrorSink, LoxError, LoxResult}, }, middleend::{ scope_stack::ScopeStack, - visit_ast::{walk_expr, walk_function, walk_stmt, Visitor}, + visit_ast::{walk_expr, walk_function, Visitor}, }, }; @@ -55,13 +55,13 @@ impl Resolver { /// Resolve a whole program, returning the per-reference scope distances or /// the accumulated resolution errors. pub fn resolve_program( - statements: &[AstNode], + statements: &[AstNode], ) -> Result, Vec> { let mut resolver = Resolver::new(); for statement in statements { // Visiting only fails for fatal/internal errors, which this pass // never produces; user-facing diagnostics go to `errors`. - let _ = resolver.visit_stmt(statement); + let _ = resolver.visit_expr(statement); } if resolver.errors.has_errors() { Err(resolver.errors.into_errors()) @@ -105,52 +105,7 @@ impl Default for Resolver { } impl Visitor for Resolver { - fn visit_stmt(&mut self, stmt: &AstNode) -> LoxResult<()> { - match &stmt.node { - Stmt::VarDeclaration { - name, initializer, .. - } => { - // A function declaration is a var bound to a function literal. - // Define its name *before* resolving the body so it can recurse; - // a plain variable is defined *after* its initializer so that - // `var a = a;` is caught. - let is_function = matches!( - initializer.as_deref().map(|node| &node.node), - Some(Expr::Literal { - value: BaseValue::Function(_) - }) - ); - self.declare(name, &stmt.source_slice); - if is_function { - self.define(name); - walk_stmt(self, stmt)?; - } else { - walk_stmt(self, stmt)?; // resolves the initializer, if any - self.define(name); - } - Ok(()) - } - Stmt::Block { .. } => { - self.scopes.begin_scope(); - walk_stmt(self, stmt)?; - self.scopes.end_scope(); - Ok(()) - } - Stmt::Return { .. } => { - if self.current_function == FunctionType::None { - self.errors.report(LoxError::ParseError { - source_slice: stmt.source_slice.clone(), - message: "Can't return from top-level code.".to_string(), - }); - } - walk_stmt(self, stmt) // resolve the returned expression - } - // Expression, Print, If, While, For: default traversal. - _ => walk_stmt(self, stmt), - } - } - - fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { + fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { match &expr.node { Expr::Identifier { name } => { if self.scopes.get_local(name) == Some(&false) { @@ -167,6 +122,42 @@ impl Visitor for Resolver { self.resolve_local(expr.id, name); Ok(()) } + Expr::VarDeclaration { + name, initializer, .. + } => { + // A function declaration is a var bound to a function literal. + // Define its name *before* resolving the body so it can recurse; + // a plain variable is defined *after* its initializer so that + // `var a = a;` is caught. + let is_function = matches!( + initializer.as_deref().map(|node| &node.node), + Some(Expr::Literal { value }) if matches!(**value, BaseValue::Function(_)) + ); + self.declare(name, &expr.source_slice); + if is_function { + self.define(name); + walk_expr(self, expr)?; + } else { + walk_expr(self, expr)?; // resolves the initializer, if any + self.define(name); + } + Ok(()) + } + Expr::Block { .. } => { + self.scopes.begin_scope(); + walk_expr(self, expr)?; + self.scopes.end_scope(); + Ok(()) + } + Expr::Return { .. } => { + if self.current_function == FunctionType::None { + self.errors.report(LoxError::ParseError { + source_slice: expr.source_slice.clone(), + message: "Can't return from top-level code.".to_string(), + }); + } + walk_expr(self, expr) // resolve the returned expression + } _ => walk_expr(self, expr), } } @@ -193,7 +184,7 @@ mod tests { use crate::frontend::lexer::Lexer; use crate::frontend::parser::Parser; - fn parse(src: &str) -> Vec> { + fn parse(src: &str) -> Vec { let tokens = Lexer::new(src.to_string(), 0) .scans_tokens() .expect("source should lex"); @@ -251,7 +242,7 @@ mod tests { #[test] fn return_inside_a_function_is_allowed() { - let locals = resolve("f :: fn (n) do return n; end").expect("should resolve"); + let locals = resolve("f :: fn (n): Number do return n; end;").expect("should resolve"); // `n` resolves from the body block up to the parameter scope. assert_eq!(locals.len(), 1); assert_eq!(*locals.values().next().unwrap(), 1); diff --git a/src/middleend/visit_ast.rs b/src/middleend/visit_ast.rs index 35471d6..75be11e 100644 --- a/src/middleend/visit_ast.rs +++ b/src/middleend/visit_ast.rs @@ -23,7 +23,7 @@ //! struct IdentifierCounter { count: usize } //! //! impl Visitor for IdentifierCounter { -//! fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { +//! fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { //! if let Expr::Identifier { .. } = &expr.node { //! self.count += 1; //! } @@ -33,7 +33,7 @@ //! ``` use crate::common::{ - ast::{AstNode, Expr, Stmt}, + ast::{AstNode, Expr}, base_value::{BaseValue, LoxFunction}, lox_result::LoxResult, }; @@ -43,13 +43,8 @@ use crate::common::{ /// Every hook has a default implementation that performs the standard /// recursive traversal, so implementors only override the cases they need. pub trait Visitor: Sized { - /// Visit a statement node. Defaults to [`walk_stmt`]. - fn visit_stmt(&mut self, stmt: &AstNode) -> LoxResult<()> { - walk_stmt(self, stmt) - } - /// Visit an expression node. Defaults to [`walk_expr`]. - fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { + fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { walk_expr(self, expr) } @@ -62,70 +57,13 @@ pub trait Visitor: Sized { } } -/// Recurse into the children of `stmt`, calling back into `visitor`. -pub fn walk_stmt(visitor: &mut V, stmt: &AstNode) -> LoxResult<()> { - match &stmt.node { - Stmt::Expression { expression, .. } => visitor.visit_expr(expression), - Stmt::Print { expression, .. } => visitor.visit_expr(expression), - Stmt::VarDeclaration { initializer, .. } => { - if let Some(initializer) = initializer { - visitor.visit_expr(initializer)?; - } - Ok(()) - } - Stmt::Return { expression, .. } => visitor.visit_expr(expression), - Stmt::Block { statements, .. } => { - for statement in statements.iter() { - visitor.visit_stmt(statement)?; - } - Ok(()) - } - Stmt::If { - condition, - then_branch, - elif_branch, - else_branch, - .. - } => { - visitor.visit_expr(condition)?; - visitor.visit_stmt(then_branch)?; - for (elif_condition, elif_body) in elif_branch.iter() { - visitor.visit_expr(elif_condition)?; - visitor.visit_stmt(elif_body)?; - } - if let Some(else_body) = else_branch { - visitor.visit_stmt(else_body)?; - } - Ok(()) - } - Stmt::While { - condition, body, .. - } => { - visitor.visit_expr(condition)?; - visitor.visit_stmt(body) - } - Stmt::For { - variable, - condition, - increment, - body, - .. - } => { - visitor.visit_stmt(variable)?; - visitor.visit_expr(condition)?; - visitor.visit_stmt(increment)?; - visitor.visit_stmt(body) - } - } -} - /// Recurse into the children of `expr`, calling back into `visitor`. -pub fn walk_expr(visitor: &mut V, expr: &AstNode) -> LoxResult<()> { +pub fn walk_expr(visitor: &mut V, expr: &AstNode) -> LoxResult<()> { match &expr.node { // A function literal carries an entire sub-tree (its body), so it is // not a leaf: hand it to the dedicated function hook. Expr::Literal { value } => { - if let BaseValue::Function(function) = value { + if let BaseValue::Function(function) = &**value { visitor.visit_function(function)?; } Ok(()) @@ -147,6 +85,57 @@ pub fn walk_expr(visitor: &mut V, expr: &AstNode) -> LoxResult } Ok(()) } + + Expr::Print { expression, .. } => visitor.visit_expr(expression), + Expr::VarDeclaration { initializer, .. } => { + if let Some(initializer) = initializer { + visitor.visit_expr(initializer)?; + } + Ok(()) + } + Expr::Return { expression, .. } => visitor.visit_expr(expression), + Expr::Block { statements, .. } => { + for statement in statements.iter() { + visitor.visit_expr(statement)?; + } + Ok(()) + } + Expr::If { + condition, + then_branch, + elif_branches, + else_branch, + .. + } => { + visitor.visit_expr(condition)?; + visitor.visit_expr(then_branch)?; + for (elif_condition, elif_body) in elif_branches.iter() { + visitor.visit_expr(elif_condition)?; + visitor.visit_expr(elif_body)?; + } + if let Some(else_body) = else_branch { + visitor.visit_expr(else_body)?; + } + Ok(()) + } + Expr::While { + condition, body, .. + } => { + visitor.visit_expr(condition)?; + visitor.visit_expr(body) + } + Expr::For { + variable, + condition, + increment, + body, + .. + } => { + visitor.visit_expr(variable)?; + visitor.visit_expr(condition)?; + visitor.visit_expr(increment)?; + visitor.visit_expr(body) + } } } @@ -155,7 +144,7 @@ pub fn walk_function(visitor: &mut V, function: &LoxFunction) -> Lox if let Some(guard) = &function.guard { visitor.visit_expr(guard)?; } - visitor.visit_stmt(&function.body) + visitor.visit_expr(&function.body) } #[cfg(test)] @@ -165,7 +154,7 @@ mod tests { use crate::frontend::lexer::Lexer; use crate::frontend::parser::Parser; - fn parse(src: &str) -> Vec> { + fn parse(src: &str) -> Vec { let tokens = Lexer::new(src.to_string(), 0) .scans_tokens() .expect("source should lex"); @@ -173,29 +162,23 @@ mod tests { } /// A visitor that relies entirely on the default traversal and just counts - /// how many statement and expression nodes it sees. + /// how many nodes it sees. #[derive(Default)] struct Counter { - stmts: usize, - exprs: usize, + nodes: usize, } impl Visitor for Counter { - fn visit_stmt(&mut self, stmt: &AstNode) -> LoxResult<()> { - self.stmts += 1; - walk_stmt(self, stmt) - } - - fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { - self.exprs += 1; - walk_expr(self, expr) + fn visit_expr(&mut self, node: &AstNode) -> LoxResult<()> { + self.nodes += 1; + walk_expr(self, node) } } fn count(src: &str) -> Counter { let mut counter = Counter::default(); for stmt in parse(src).iter() { - counter.visit_stmt(stmt).unwrap(); + counter.visit_expr(stmt).unwrap(); } counter } @@ -204,19 +187,16 @@ mod tests { fn counts_every_node_via_default_traversal() { // 1 + 2 * 3 => Binary(+){ Literal, Binary(*){ Literal, Literal } } let counter = count("1 + 2 * 3;"); - assert_eq!(counter.stmts, 1); - assert_eq!(counter.exprs, 5); + assert_eq!(counter.nodes, 5); } #[test] fn descends_into_function_bodies() { // The function body must be traversed through `visit_function`, so the // `return a;` inside it should contribute to the counts. - let counter = count("f :: fn (a) do return a; end"); - // VarDeclaration + Block + Return - assert_eq!(counter.stmts, 3); - // Function literal + identifier `a` - assert_eq!(counter.exprs, 2); + let counter = count("f :: fn (a): Number do return a; end;"); + // VarDeclaration + Function literal + Block + Return + identifier `a` + assert_eq!(counter.nodes, 5); } /// A visitor that aborts as soon as it sees an identifier, used to check @@ -224,7 +204,7 @@ mod tests { struct FailOnIdentifier; impl Visitor for FailOnIdentifier { - fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { + fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> { if let Expr::Identifier { .. } = &expr.node { return runtime_error(expr.source_slice.clone(), "found an identifier"); } @@ -238,7 +218,7 @@ mod tests { let mut visitor = FailOnIdentifier; let mut result = Ok(()); for stmt in stmts.iter() { - result = visitor.visit_stmt(stmt); + result = visitor.visit_expr(stmt); if result.is_err() { break; } diff --git a/tests/frontend.rs b/tests/frontend.rs index 5ee5103..004e082 100644 --- a/tests/frontend.rs +++ b/tests/frontend.rs @@ -4,14 +4,14 @@ //! assert on the resulting AST, complementing the per-module unit tests inside //! `src/frontend/{lexer,parser}.rs`. -use rlox::common::ast::{AstNode, Expr, Stmt}; +use rlox::common::ast::{AstNode, Expr}; use rlox::common::base_value::{BaseValue, Number}; use rlox::frontend::lexer::Lexer; use rlox::frontend::parser::Parser; use rlox::frontend::tokens::TokenType; /// Run the full frontend pipeline on `src`. -fn parse(src: &str) -> Result>, String> { +fn parse(src: &str) -> Result, String> { let tokens = Lexer::new(src.to_string(), 0) .scans_tokens() .map_err(|e| format!("lex error: {e}"))?; @@ -21,23 +21,26 @@ fn parse(src: &str) -> Result>, String> { } /// Parse `src`, panicking if the frontend reports any error. -fn parse_ok(src: &str) -> Vec> { +fn parse_ok(src: &str) -> Vec { parse(src).expect("expected source to lex and parse") } -/// Parse `src` and return the single statement it should produce. -fn single_stmt(src: &str) -> Stmt { +/// Parse `src` and return the single node it should produce. +/// +/// Statements and expressions share the unified `Expr`/`AstNode` type, so this +/// simply returns the node of the sole top-level statement. +fn single_stmt(src: &str) -> Expr { let mut stmts = parse_ok(src); assert_eq!(stmts.len(), 1, "expected exactly one statement for {src:?}"); stmts.remove(0).node } -/// Parse `src` and return the expression of its single expression-statement. +/// Parse `src` and return its single expression-statement's expression. +/// +/// Expression statements aren't wrapped in a dedicated variant anymore; the +/// node itself is the expression. fn single_expr(src: &str) -> Expr { - match single_stmt(src) { - Stmt::Expression { expression, .. } => expression.node, - other => panic!("expected expression statement, got {other:?}"), - } + single_stmt(src) } // --------------------------------------------------------------------------- @@ -47,7 +50,7 @@ fn single_expr(src: &str) -> Expr { #[test] fn lexes_and_parses_number_literal() { match single_expr("42;") { - Expr::Literal { value } => assert_eq!(value, BaseValue::Number(Number::I32(42))), + Expr::Literal { value } => assert_eq!(*value, BaseValue::Number(Number::I32(42))), other => panic!("expected literal, got {other:?}"), } } @@ -194,7 +197,7 @@ fn assignment_to_non_identifier_is_an_error() { #[test] fn parses_var_declaration() { match single_stmt("x := 5;") { - Stmt::VarDeclaration { + Expr::VarDeclaration { name, initializer, .. } => { assert_eq!(name, "x"); @@ -206,13 +209,13 @@ fn parses_var_declaration() { #[test] fn parses_print_statement() { - assert!(matches!(single_stmt("print 1;"), Stmt::Print { .. })); + assert!(matches!(single_stmt("print 1;"), Expr::Print { .. })); } #[test] fn parses_block_with_multiple_statements() { match single_stmt("do print 1; print 2; end") { - Stmt::Block { statements, .. } => assert_eq!(statements.len(), 2), + Expr::Block { statements, .. } => assert_eq!(statements.len(), 2), other => panic!("expected block, got {other:?}"), } } @@ -220,7 +223,7 @@ fn parses_block_with_multiple_statements() { #[test] fn parses_if_else() { match single_stmt("if true then print 1; else print 2;") { - Stmt::If { + Expr::If { else_branch: Some(_), .. } => {} @@ -232,37 +235,32 @@ fn parses_if_else() { fn parses_while_loop() { assert!(matches!( single_stmt("while true do print 1; end"), - Stmt::While { .. } + Expr::While { .. } )); } #[test] fn parses_for_loop_with_assignment_increment() { match single_stmt("for i := 0; i <= 2; i = i + 1; do print i; end") { - Stmt::For { increment, .. } => match increment.node { - // The increment desugars to an expression statement holding an assignment. - Stmt::Expression { expression, .. } => { - assert!(matches!(expression.node, Expr::Assign { .. })) - } - other => panic!("expected expression-statement increment, got {other:?}"), - }, + Expr::For { increment, .. } => { + // The increment is an assignment expression. + assert!(matches!(increment.node, Expr::Assign { .. })) + } other => panic!("expected for loop, got {other:?}"), } } #[test] fn parses_function_declaration() { - match single_stmt("add :: fn (a, b) do return a + b; end") { - Stmt::VarDeclaration { + match single_stmt("add :: fn (a, b): Number do return a + b; end;") { + Expr::VarDeclaration { name, initializer, .. } => { assert_eq!(name, "add"); match initializer { Some(init) => assert!(matches!( - init.node, - Expr::Literal { - value: BaseValue::Function(_) - } + &init.node, + Expr::Literal { value } if matches!(&**value, BaseValue::Function(_)) )), None => panic!("expected a function initializer"), } diff --git a/tests/resolution.rs b/tests/resolution.rs index e099415..a897f83 100644 --- a/tests/resolution.rs +++ b/tests/resolution.rs @@ -37,13 +37,13 @@ fn resolved_nested_closure_reads_captured_local() { // `count` is read from inside a nested function, two scopes up; the // resolver records distance 2 and the interpreter uses `get_at(2, ..)`. let src = "\ -make :: fn () do +make :: fn (): Any do count := 10; - get :: fn () do + get :: fn (): Number do return count; - end + end; return get; -end +end; g := make(); g(); "; From b93a1394cd124d0edfaaa42e0c21c8bc317ac356 Mon Sep 17 00:00:00 2001 From: Giulio Agostini Date: Mon, 6 Jul 2026 19:10:01 +0200 Subject: [PATCH 9/9] Update parser.rs --- src/frontend/parser.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/frontend/parser.rs b/src/frontend/parser.rs index 034bbb1..d18dea3 100755 --- a/src/frontend/parser.rs +++ b/src/frontend/parser.rs @@ -1,11 +1,9 @@ -use std::collections::hash_map::Values; - use crate::{ common::{ ast::{AstNode, Expr}, base_value::{BaseValue, LoxFunction}, - lox_result::{parse_error, runtime_error, LoxError, LoxResult}, - types::{FunctionType, Type}, + lox_result::{parse_error, runtime_error, LoxResult}, + types::Type, }, frontend::{ source_registry::SourceSlice,