Author SHA1 Message Date
Giulio Agostini 9a817befac Move variable resolution to middleend
- Introduce AST-based visitor and resolver in middleend
- Update guard field and parser to use AstNode<Expr>
- Remove backend variable_resolution and adjust exports
- Expose middleend in the library
2026-07-22 11:36:43 +02:00
Giulio Agostini 5f4fa86979 Make environment generic and adapt closures
Added unit test
2026-07-22 11:36:43 +02:00
fizban 6bf255668a wip: i dont rembemer 2026-07-11 18:07:07 +02:00
fizban 4e2645e12d closure 2026-02-11 16:35:05 +01:00
Giulio Agostini ee279425f2 wip 2025-11-12 20:45:35 +01:00
Giulio Agostini 07cedc27bf 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.
2025-10-10 10:18:02 +02:00
31 changed files with 1816 additions and 271 deletions
Regular → Executable
+2
View File
@@ -26,3 +26,5 @@ rust-project.json
# End of https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer
.zed/
.idea/
Regular → Executable
View File
Executable
View File
+4 -3
View File
@@ -1,8 +1,9 @@
// Test script to verify error positioning
var a = 5;
var b = "hello";
"no"();
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;
+27
View File
@@ -0,0 +1,27 @@
closure :: fn() do
value := "Closure";
internal :: fn() do
print value;
return value;
end
return internal;
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
+12 -9
View File
@@ -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)
+12 -2
View File
@@ -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()
+3 -3
View File
@@ -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
Regular → Executable
+118 -13
View File
@@ -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<HashMap<String, BaseValue>>,
pub type Environment<T> = HashMap<String, T>;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct EnvironmentStack<T: Clone + Debug + PartialEq> {
stack: Vec<HashMap<String, T>>,
}
impl EnvironmentStack {
impl<T: Clone + Debug + PartialEq> EnvironmentStack<T> {
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<String, T>) {
self.stack.push(scope);
}
pub fn pop_scope(&mut self) {
self.stack.pop();
}
pub fn get(&self, name: &str) -> LoxResult<BaseValue> {
pub fn clone_last_scope(&mut self) -> HashMap<String, T> {
self.stack.last().unwrap().clone()
}
pub fn get(&self, name: &str) -> LoxResult<T> {
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<BaseValue> {
pub fn declare(&mut self, name: String, value: T) -> LoxResult<T> {
self.stack.last_mut().unwrap().insert(name, value.clone());
Ok(value)
}
pub fn set(&mut self, name: String, value: BaseValue) -> LoxResult<BaseValue> {
pub fn set(&mut self, name: String, value: T) -> LoxResult<T> {
let size = self.stack.len();
for i in (0..size).rev() {
@@ -68,4 +79,98 @@ 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)]
mod tests {
use super::*;
#[test]
fn declare_and_get() {
let mut env: EnvironmentStack<i32> = 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<i32> = EnvironmentStack::new();
assert!(env.get("nope").is_err());
}
#[test]
fn declare_overwrites_in_same_scope() {
let mut env: EnvironmentStack<i32> = 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<i32> = 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<i32> = 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<i32> = 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<i32> = EnvironmentStack::new();
assert!(env.set("a".to_string(), 1).is_err());
}
#[test]
fn pop_scope_discards_inner_declarations() {
let mut env: EnvironmentStack<i32> = 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<i32> = 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<i32> = EnvironmentStack::new();
env.declare("a".to_string(), 1).unwrap();
let scope = env.clone_last_scope();
assert_eq!(scope.get("a"), Some(&1));
}
}
Regular → Executable
+224 -60
View File
@@ -10,14 +10,14 @@ use crate::{
use std::fmt::{Debug, Display};
pub struct Interpreter {
enviorment: EnvironmentStack,
enviorment: EnvironmentStack<BaseValue>,
}
pub trait EvaluateInterpreter<T> {
fn evaluate(&mut self, stmt: T) -> LoxResult<BaseValue>;
}
impl<'a, R: AstNodeKind + Clone + Debug + Display> EvaluateInterpreter<AstNode<R>> for Interpreter
impl<R: AstNodeKind + Clone + Debug + Display> EvaluateInterpreter<AstNode<R>> for Interpreter
where
Interpreter: EvaluateInterpreter<R>,
{
@@ -33,7 +33,13 @@ where
impl EvaluateInterpreter<Expr> for Interpreter {
fn evaluate(&mut self, expr: Expr) -> LoxResult<BaseValue> {
match expr {
Expr::Literal { value } => Ok(value),
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,
@@ -120,15 +126,32 @@ 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::<LoxResult<Vec<BaseValue>>>()?;
self.evaluate(func.body)
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(),
@@ -182,33 +205,24 @@ impl Interpreter {
fn interpret_stmt_inner(&mut self, stmt: Stmt) -> LoxResult<BaseValue> {
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 } => {
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 } => {
Stmt::VarAssigment { name, value, .. } => {
let result = self.evaluate(*value)?;
self.enviorment.set(name.clone(), result)
}
@@ -217,20 +231,31 @@ 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, ..
} => {
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,
condition,
increment,
body,
..
} => {
self.enviorment.push_new_scope();
let source_slice = variable.source_slice.clone();
let val = self.evaluate(*variable)?;
if !matches!(val, BaseValue::Number(..)) {
@@ -238,7 +263,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())?;
}
@@ -289,34 +321,166 @@ impl Interpreter {
fn evaluate_block(&mut self, statements: Vec<AstNode<Stmt>>) -> LoxResult<BaseValue> {
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
}
}
#[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<BaseValue> {
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());
}
}
Regular → Executable
View File
Regular → Executable
+148 -43
View File
@@ -120,96 +120,148 @@ impl Debug for Expr {
pub enum Stmt {
Expression {
expression: Box<AstNode<Expr>>,
return_value: Box<BaseValue>,
},
Print {
expression: Box<AstNode<Expr>>,
},
Stmt {
expression: Box<AstNode<Expr>>,
return_value: Box<BaseValue>,
},
VarDeclaration {
name: String,
initializer: Option<Box<AstNode<Expr>>>,
return_value: Box<BaseValue>,
},
VarAssigment {
name: String,
value: Box<AstNode<Expr>>,
return_value: Box<BaseValue>,
},
Return {
expression: Box<AstNode<Expr>>,
label: String,
return_value: Box<BaseValue>,
},
Block {
statements: Box<Vec<AstNode<Stmt>>>,
label: String,
return_value: Box<BaseValue>,
},
If {
condition: Box<AstNode<Expr>>,
then_branch: Box<AstNode<Stmt>>,
elif_branch: Vec<(Box<AstNode<Expr>>, Box<AstNode<Stmt>>)>,
else_branch: Option<Box<AstNode<Stmt>>>,
return_value: Box<BaseValue>,
},
While {
condition: Box<AstNode<Expr>>,
body: Box<AstNode<Stmt>>,
return_value: Box<BaseValue>,
},
For {
variable: Box<AstNode<Stmt>>,
condition: Box<AstNode<Expr>>,
increment: Box<AstNode<Stmt>>,
body: Box<AstNode<Stmt>>,
return_value: Box<BaseValue>,
},
}
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,
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::<Vec<_>>()
.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 +270,111 @@ 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,
label,
} => write!(
f,
"Block([\n{:?}\n])",
"Return({:?}, {}) -> {:?};",
expression.node, label, return_value
),
Stmt::Block {
label,
statements,
return_value,
} => write!(
f,
"Block [{}] ([\n{:?}\n]) -> {:?}",
label,
statements
.iter()
.map(|stmt| format!("{:?}", stmt.node))
.collect::<Vec<_>>()
.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 +411,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",
}
Regular → Executable
+19 -4
View File
@@ -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,
@@ -273,6 +274,17 @@ pub enum BaseValue {
NativeFunction(NativeFunction),
}
struct ReturnValue {
label: Vec<String>,
value: BaseValue,
}
impl ReturnValue {
pub fn new(label: Vec<String>, value: BaseValue) -> Self {
Self { label, value }
}
}
impl BaseValue {
pub fn is_callable(&self) -> bool {
match self {
@@ -299,20 +311,22 @@ impl Display for BaseValue {
#[derive(Debug, Clone, PartialEq)]
pub struct LoxFunction {
pub parameters: Vec<(String, String)>,
pub return_type: Option<String>,
pub body: AstNode<Stmt>,
pub closure: Option<EnvironmentStack>,
pub guard: Option<Box<Expr>>,
pub closure: Option<EnvironmentStack<BaseValue>>,
pub guard: Option<Box<AstNode<Expr>>>,
}
impl LoxFunction {
pub fn new(
parameters: Vec<(String, String)>,
body: AstNode<Stmt>,
closure: Option<EnvironmentStack>,
guard: Option<Box<Expr>>,
closure: Option<EnvironmentStack<BaseValue>>,
guard: Option<Box<AstNode<Expr>>>,
) -> Self {
LoxFunction {
parameters,
return_type: None,
body,
closure,
guard,
@@ -322,6 +336,7 @@ impl LoxFunction {
pub fn anonymous_function(parameters: Vec<(String, String)>, body: AstNode<Stmt>) -> Self {
LoxFunction {
parameters,
return_type: None,
body,
closure: None,
guard: None,
Regular → Executable
+34 -3
View File
@@ -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 {
@@ -23,6 +26,11 @@ pub enum LoxError {
expected: String,
found: String,
},
Return {
source_slice: SourceSlice,
value: BaseValue,
return_label: String,
},
}
/// Configuration for error display formatting
@@ -81,6 +89,13 @@ impl LoxError {
} => {
format!("expected {}, found {}", expected, found)
}
LoxError::Return {
value,
return_label,
..
} => {
format!("return value: {} and label: {}", value, return_label)
}
}
}
@@ -91,6 +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 { .. } => None,
}
}
@@ -101,6 +117,7 @@ impl LoxError {
LoxError::ParseError { .. } => "parse error",
LoxError::IoError { .. } => "io error",
LoxError::TypeMismatch { .. } => "type error",
LoxError::Return { .. } => "return error",
}
}
@@ -393,11 +410,25 @@ 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
)
}
}
}
}
impl std::error::Error for LoxError {}
impl Error for LoxError {}
pub type LoxResult<T> = Result<T, LoxError>;
Regular → Executable
View File
Regular → Executable
+189
View File
@@ -29,6 +29,7 @@ fn get_keyword_token(word: &str) -> Option<TokenType> {
"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),
@@ -321,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<Token> {
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<TokenType> {
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());
}
}
Regular → Executable
+1
View File
@@ -2,3 +2,4 @@ pub mod lexer;
pub mod parser;
pub mod source_registry;
pub mod tokens;
pub mod variable_resolution;
Regular → Executable
+221 -28
View File
@@ -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<AstNode<Stmt>> {
fn statement(&mut self, label: Option<String>) -> LoxResult<AstNode<Stmt>> {
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(
@@ -87,6 +84,7 @@ impl Parser {
condition: Box::new(condition),
increment: Box::new(increment),
body: Box::new(body),
return_value: Box::new(BaseValue::Nil),
},
combined_slice,
))
@@ -117,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,
@@ -138,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,
@@ -149,6 +147,7 @@ impl Parser {
Stmt::While {
condition: Box::new(condition),
body: Box::new(body),
return_value: Box::new(BaseValue::Nil),
},
combined_slice,
))
@@ -159,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();
@@ -167,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 {
@@ -193,6 +192,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,
))
@@ -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 => {
@@ -248,19 +252,19 @@ impl Parser {
end_position: end_position.end_position,
};
let mut guard: Option<Box<Expr>> = None;
let mut guard: Option<Box<AstNode<Expr>>> = 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")?;
}
let body = self.statement()?;
let body = self.statement(None)?;
let node = AstNode {
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,22 +364,20 @@ impl Parser {
Ok(AstNode::new(
Stmt::Print {
expression: Box::new(expr),
return_value: Box::new(BaseValue::Nil),
},
combined_slice,
))
}
fn block_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
fn block_statement(&mut self, label: Option<String>) -> LoxResult<AstNode<Stmt>> {
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 should_break = matches!(stmt.node, Stmt::Expression { .. });
let stmt = self.statement(label.clone())?;
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();
@@ -381,9 +386,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::Block {
statements: Box::new(statements),
label: label_str,
return_value: Box::new(BaseValue::Nil),
},
combined_slice,
))
@@ -401,8 +413,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,15 +425,37 @@ impl Parser {
Ok(AstNode::new(
Stmt::Expression {
expression: Box::new(expr),
return_value: Box::new(BaseValue::Nil),
},
expr_slice,
))
}
fn return_statement(&mut self) -> LoxResult<AstNode<Stmt>> {
fn return_statement(&mut self, label: Option<String>) -> LoxResult<AstNode<Stmt>> {
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(
@@ -428,9 +463,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,
))
@@ -821,3 +863,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<Vec<AstNode<Stmt>>> {
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<AstNode<Stmt>> {
parse_source(src).expect("expected source to parse")
}
/// Extract the inner expression of an expression statement.
fn expression_of(stmt: &AstNode<Stmt>) -> &AstNode<Expr> {
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());
}
}
Regular → Executable
+1 -1
View File
@@ -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<SourceFile>,
Regular → Executable
+2
View File
@@ -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"),
+204
View File
@@ -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<bool>,
}
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<T> {
fn resolve(&mut self, node: &T) -> LoxResult<bool>;
}
impl<R: AstNodeKind + Clone + Debug + Display> StaticAnalyzer<AstNode<R>> for Resolver
where
Resolver: StaticAnalyzer<R>,
{
fn resolve(&mut self, node: &AstNode<R>) -> LoxResult<bool> {
self.resolve(&node.node)
}
}
impl StaticAnalyzer<Stmt> for Resolver {
fn resolve(&mut self, node: &Stmt) -> LoxResult<bool> {
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<Expr> for Resolver {
fn resolve(&mut self, node: &Expr) -> LoxResult<bool> {
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)
}
}
}
}
Regular → Executable
+1
View File
@@ -1,3 +1,4 @@
pub mod backend;
pub mod common;
pub mod frontend;
pub mod middleend;
Regular → Executable
+17 -10
View File
@@ -263,7 +263,7 @@ impl PrettyPrint for Stmt {
let indent = ctx.indent();
match self {
Stmt::Expression { expression } => {
Stmt::Expression { expression, .. } => {
if ctx.config.compact {
let expr_str =
pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact());
@@ -276,7 +276,7 @@ impl PrettyPrint for Stmt {
write!(f, "{}}}", indent)
}
}
Stmt::Print { expression } => {
Stmt::Print { expression, .. } => {
if ctx.config.compact {
let expr_str =
pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact());
@@ -289,7 +289,9 @@ impl PrettyPrint for Stmt {
write!(f, "{}}}", indent)
}
}
Stmt::VarDeclaration { name, initializer } => {
Stmt::VarDeclaration {
name, initializer, ..
} => {
if ctx.config.compact {
match initializer {
Some(init) => {
@@ -315,7 +317,7 @@ impl PrettyPrint for Stmt {
write!(f, "{}}}", indent)
}
}
Stmt::VarAssigment { name, value } => {
Stmt::VarAssigment { name, value, .. } => {
if ctx.config.compact {
let value_str =
pretty_print_with_config(value.as_ref(), &PrettyConfig::compact());
@@ -329,7 +331,7 @@ impl PrettyPrint for Stmt {
write!(f, "{}}}", indent)
}
}
Stmt::Return { expression } => {
Stmt::Return { expression, .. } => {
if ctx.config.compact {
let expr_str =
pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact());
@@ -342,11 +344,13 @@ impl PrettyPrint for Stmt {
write!(f, "{}}}", indent)
}
}
Stmt::Block { statements } => {
Stmt::Block {
statements, label, ..
} => {
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 +370,7 @@ impl PrettyPrint for Stmt {
then_branch,
elif_branch,
else_branch,
..
} => {
if ctx.config.compact {
let cond_str =
@@ -415,8 +420,9 @@ impl PrettyPrint for Stmt {
write!(f, "{}}}", indent)
}
}
Stmt::Stmt { expression } => expression.pretty_print(ctx, f),
Stmt::While { condition, body } => {
Stmt::While {
condition, body, ..
} => {
write!(f, "{}while (", ctx.child_context(true).indent())?;
condition.pretty_print(&ctx.child_context(true), f)?;
writeln!(f, ") {{")?;
@@ -428,6 +434,7 @@ impl PrettyPrint for Stmt {
condition,
increment,
body,
..
} => {
write!(f, "{}for (", ctx.child_context(true).indent())?;
variable.pretty_print(&ctx.child_context(true), f)?;
Regular → Executable
+2
View File
@@ -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
Regular → Executable
View File
Regular → Executable
+19 -92
View File
@@ -2,6 +2,7 @@ mod backend;
mod common;
mod frontend;
mod logging;
mod middleend;
use crate::{
backend::interpreter::{EvaluateInterpreter, Interpreter},
@@ -29,109 +30,35 @@ fn main() -> LoxResult<()> {
let args: Vec<String> = 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<String>, 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 {
+2
View File
@@ -0,0 +1,2 @@
pub mod variable_resolution;
pub mod visit_ast;
+99
View File
@@ -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<bool>,
locals: HashMap<SourceSlice, usize>,
}
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<Stmt>) -> 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<Expr>) -> 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),
}
}
}
+248
View File
@@ -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<Expr>) -> 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<Stmt>) -> LoxResult<()> {
walk_stmt(self, stmt)
}
/// Visit an expression node. Defaults to [`walk_expr`].
fn visit_expr(&mut self, expr: &AstNode<Expr>) -> 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<V: Visitor>(visitor: &mut V, stmt: &AstNode<Stmt>) -> 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<V: Visitor>(visitor: &mut V, expr: &AstNode<Expr>) -> 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<V: Visitor>(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<AstNode<Stmt>> {
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<Stmt>) -> LoxResult<()> {
self.stmts += 1;
walk_stmt(self, stmt)
}
fn visit_expr(&mut self, expr: &AstNode<Expr>) -> 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<Expr>) -> 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());
}
}
Regular → Executable
View File
+207
View File
@@ -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(())
}