10 Commits
Author SHA1 Message Date
gmagostini 2498b3b345 Merge pull request 'Ai/refactor' (#1) from ai/refactor into dev
Reviewed-on: https://gitea.lambstudios.eu/ProjectValkay/rlox/pulls/1
2026-07-06 17:14:35 +00:00
Giulio Agostini b93a1394cd Update parser.rs 2026-07-06 19:10:01 +02:00
Giulio Agostini 842216729b wip:
Add type system and refator for having only epression
2026-07-06 10:43:17 +02:00
Giulio Agostini 9f15a00b98 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
2026-06-30 15:05:34 +02:00
Giulio Agostini d40fe2a550 Introduce NodeId for AST and synthetic slices 2026-06-30 14:05:46 +02:00
Giulio Agostini ef8abda048 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-06-30 09:47:22 +02:00
Giulio Agostini 29c86d278d Make environment generic and adapt closures
Added unit test
2026-06-29 20:50:29 +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
37 changed files with 3119 additions and 936 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 # End of https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer
.zed/ .zed/
.idea/
View File
Regular → Executable
View File
Executable
+36
View File
@@ -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
+4 -3
View File
@@ -1,8 +1,9 @@
// Test script to verify error positioning // Test script to verify error positioning
var a = 5; "no"();
var b = "hello"; var a := 5;
var b := "hello";
print 0; print 0;
var result = a + b; // This should cause a type error var result := a + b; // This should cause a type error
print 1; print 1;
43 + "hello"; // This should cause a type error 43 + "hello"; // This should cause a type error
print result; print result;
-20
View File
@@ -1,20 +0,0 @@
function :: fn() do
print "Function";
end
function_factory :: fn() do
print "Function Factory";
function
end
function_fatcoty_factory :: fn() do
print "Function Factory Factory";
function_factory
end
//function();
//function_factory()();
function_fatcoty_factory()()();
clock()
+27
View File
@@ -0,0 +1,27 @@
closure :: fn(): Any do
value := "Closure";
internal :: fn(): String 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(): Nil do
print a;
end;
showA();
a := "Local";
showA();
end
+14 -11
View File
@@ -4,25 +4,28 @@
// address: String, // address: String,
// } // }
//func_name :: fn(param: Number, param2: String) {param is Int} do //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 param;
cavallo := 5; print param2;
print cavallo; while param < param2 do
while cavallo < 10 do param = param + 1;
cavallo = cavallo + 1; print param;
print cavallo; print param2;
//break;
end end
if True then do if False then do
print "this shoul be stop"; print "this shoul be stop";
return False; return "Hahahaha";
end end
print "this shoud be un other stop"; print "this shoud be un other stop";
return 42; //return 42;
for i := 11; i <= 21; i = i + 1; do for i := 11; i <= 21; i = i + 1; do
print i; print i;
print "hello";
break;
end end
return False or True; return False or True;
end end;
func_name(1,2) func_name(1,20);
+30
View File
@@ -0,0 +1,30 @@
function :: fn(): Nil do
print "Function";
end;
function_factory :: fn(): Any do
print "Function Factory";
function;
end;
function_fatcoty_factory :: fn(): Any do
print "Function Factory Factory";
return function_factory;
end;
//function();
//function_factory()();
//function_fatcoty_factory()()();
clock();
closure :: fn(): Any do
value := "Closure";
internal :: fn(): Nil do
print value;
end;
return internal;
end;
closure();
+3 -3
View File
@@ -1,10 +1,10 @@
// Test file for source slice tracking // Test file for source slice tracking
var x = 42; var x := 42;
print x; print x;
if x > 0 then do if x > 0 then do
print "positive"; print "positive";
var y = x + 1; var y := x + 1;
end end
elif x == 0 then do elif x == 0 then do
print "zero"; print "zero";
@@ -19,7 +19,7 @@ while x > 0 do
end end
do do
var z = 10; var z := 10;
print z * 2; print z * 2;
end end
+57
View File
@@ -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 )* ")"
Regular → Executable
+152 -15
View File
@@ -1,34 +1,45 @@
use std::collections::HashMap;
use crate::{ use crate::{
common::{ common::lox_result::{runtime_error, LoxResult},
base_value::BaseValue,
lox_result::{runtime_error, LoxResult},
},
frontend::source_registry::SourceSlice, frontend::source_registry::SourceSlice,
}; };
use std::collections::HashMap;
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)] pub type Environment<T> = HashMap<String, T>;
pub struct EnvironmentStack {
stack: Vec<HashMap<String, BaseValue>>, #[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 { pub fn new() -> Self {
EnvironmentStack { EnvironmentStack {
stack: vec![HashMap::new()], stack: vec![HashMap::new()],
} }
} }
pub fn is_empty(&self) -> bool {
self.stack.is_empty()
}
pub fn push_new_scope(&mut self) { pub fn push_new_scope(&mut self) {
self.stack.push(HashMap::new()); 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) { pub fn pop_scope(&mut self) {
self.stack.pop(); 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(); let size = self.stack.len();
for i in (0..size).rev() { for i in (0..size).rev() {
@@ -37,17 +48,49 @@ impl EnvironmentStack {
} }
} }
runtime_error( 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), format!("Undefined variable '{}'", name),
) )
} }
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()); self.stack.last_mut().unwrap().insert(name, value.clone());
Ok(value) Ok(value)
} }
pub fn set(&mut self, name: String, value: BaseValue) -> LoxResult<BaseValue> { /// 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<T> {
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<T> {
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<T> {
let size = self.stack.len(); let size = self.stack.len();
for i in (0..size).rev() { for i in (0..size).rev() {
@@ -64,8 +107,102 @@ impl EnvironmentStack {
} }
} }
runtime_error( 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), 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
+341 -181
View File
@@ -1,73 +1,29 @@
use crate::{ use crate::{
backend::environment::EnvironmentStack, backend::environment::EnvironmentStack,
common::{ common::{
ast::{AstNode, AstNodeKind, Expr, Stmt}, ast::{AstNode, Expr, NodeId},
base_value::{BaseValue, NativeFunction, Number, Truthy}, base_value::{BaseValue, NativeFunction, Number, Truthy},
lox_result::{runtime_error, LoxError, LoxResult}, lox_result::{runtime_error, LoxError, LoxResult},
}, },
frontend::{source_registry::SourceSlice, tokens::TokenType}, frontend::{source_registry::SourceSlice, tokens::TokenType},
}; };
use std::fmt::{Debug, Display}; use std::collections::HashMap;
pub struct Interpreter { pub struct Interpreter {
enviorment: EnvironmentStack, enviorment: EnvironmentStack<BaseValue>,
/// 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<NodeId, usize>,
} }
pub trait EvaluateInterpreter<T> { pub trait EvaluateInterpreter<T> {
fn evaluate(&mut self, stmt: T) -> LoxResult<BaseValue>; fn evaluate(&mut self, stmt: T) -> LoxResult<BaseValue>;
} }
impl<'a, R: AstNodeKind + Clone + Debug + Display> EvaluateInterpreter<AstNode<R>> for Interpreter impl EvaluateInterpreter<AstNode> for Interpreter {
where fn evaluate(&mut self, node: AstNode) -> LoxResult<BaseValue> {
Interpreter: EvaluateInterpreter<R>, self.eval_expr(&node)
{
fn evaluate(&mut self, stmt: AstNode<R>) -> LoxResult<BaseValue> {
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<Expr> for Interpreter {
fn evaluate(&mut self, expr: Expr) -> LoxResult<BaseValue> {
match expr {
Expr::Literal { value } => 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),
}
}
}
impl EvaluateInterpreter<AstNode<Stmt>> for Interpreter {
fn evaluate(&mut self, node: AstNode<Stmt>) -> LoxResult<BaseValue> {
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 == SourceSlice::default() => {
runtime_error(node.source_slice.clone(), message)
}
Err(err) => Err(err),
}
} }
} }
@@ -85,27 +41,150 @@ impl Interpreter {
)) ))
})), })),
); );
Self { enviorment: env } Self {
enviorment: env,
locals: HashMap::new(),
} }
fn evaluate_call( }
/// Install the resolver's per-reference scope distances.
pub fn set_locals(&mut self, locals: HashMap<NodeId, usize>) {
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<BaseValue> {
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)
}
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 {
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<BaseValue> {
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, &mut self,
callee: Box<AstNode<Expr>>, name: &str,
arguments: Vec<AstNode<Expr>>, id: NodeId,
value: BaseValue,
) -> LoxResult<BaseValue> { ) -> LoxResult<BaseValue> {
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: &AstNode, arguments: &[AstNode]) -> LoxResult<BaseValue> {
let source_slice = callee.source_slice.clone(); let source_slice = callee.source_slice.clone();
// Estrai il nome della variabile se il callee è un identificatore // A bare identifier callee resolves through `locals`; anything else is
let function_name = match &callee.node { // evaluated as a general expression.
Expr::Identifier { name } => Some(name.clone()), let function = match &callee.node {
_ => None, Expr::Identifier { name } => self.look_up_variable(name, callee.id)?,
}; _ => self.eval_expr(callee)?,
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)?
}; };
if !function.is_callable() { if !function.is_callable() {
@@ -114,21 +193,38 @@ impl Interpreter {
let evaluated_arguments = arguments let evaluated_arguments = arguments
.iter() .iter()
.map(|arg| self.evaluate(arg.clone())) .map(|arg| self.eval_expr(arg))
.collect::<Result<Vec<BaseValue>, LoxError>>()?; .collect::<Result<Vec<BaseValue>, LoxError>>()?;
match function { match function {
BaseValue::NativeFunction(func) => Ok((func.function)(&evaluated_arguments)), BaseValue::NativeFunction(func) => Ok((func.function)(&evaluated_arguments)),
BaseValue::Function(func) => { BaseValue::Function(func) => {
func.parameters let saved_env = self.enviorment.clone();
.iter() // If the function captured a closure, use it as the base environment
.enumerate() if let Some(closure_env) = func.closure {
.map(|(index, par)| { 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(); let value = evaluated_arguments.get(index).unwrap();
self.enviorment.declare(par.0.clone(), value.clone()) self.enviorment.declare(par.0.clone(), value.clone())?;
}) }
.collect::<LoxResult<Vec<BaseValue>>>()?;
self.evaluate(func.body) // Execute the function body
let result = match self.eval_expr(&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( _ => runtime_error(
source_slice.clone(), source_slice.clone(),
@@ -157,7 +253,7 @@ impl Interpreter {
TokenType::And => Ok(BaseValue::Boolean(left.is_truthy() && right.is_truthy())), TokenType::And => Ok(BaseValue::Boolean(left.is_truthy() && right.is_truthy())),
TokenType::Or => Ok(BaseValue::Boolean(left.is_truthy() || right.is_truthy())), TokenType::Or => Ok(BaseValue::Boolean(left.is_truthy() || right.is_truthy())),
_ => Err(LoxError::RuntimeError { _ => Err(LoxError::RuntimeError {
source_slice: SourceSlice::default(), source_slice: SourceSlice::synthetic(),
message: format!("Unsupported binary operator: {:?}", operator), message: format!("Unsupported binary operator: {:?}", operator),
}), }),
} }
@@ -168,105 +264,39 @@ impl Interpreter {
TokenType::Minus => match operand { TokenType::Minus => match operand {
BaseValue::Number(n) => Ok(BaseValue::Number(n.neg())), BaseValue::Number(n) => Ok(BaseValue::Number(n.neg())),
_ => Err(LoxError::RuntimeError { _ => Err(LoxError::RuntimeError {
source_slice: SourceSlice::default(), source_slice: SourceSlice::synthetic(),
message: "Cannot negate non-numeric value".to_string(), message: "Cannot negate non-numeric value".to_string(),
}), }),
}, },
TokenType::Bang => Ok(!operand), TokenType::Bang => Ok(!operand),
_ => Err(LoxError::RuntimeError { _ => Err(LoxError::RuntimeError {
source_slice: SourceSlice::default(), source_slice: SourceSlice::synthetic(),
message: format!("Unsupported unary operator: {:?}", operator), message: format!("Unsupported unary operator: {:?}", operator),
}), }),
} }
} }
fn interpret_stmt_inner(&mut self, stmt: Stmt) -> LoxResult<BaseValue> {
match stmt {
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 } => {
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)?,
},
None => BaseValue::Nil,
};
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,
elif_branch,
else_branch,
} => self.evaluate_if(condition, then_branch, elif_branch, else_branch),
Stmt::While { condition, body } => {
while self.evaluate(*condition.clone())?.is_truthy() {
self.evaluate(*body.clone())?;
}
Ok(BaseValue::Nil)
}
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(..)) {
return runtime_error(source_slice, "Expected number literal");
}
let mut ret = BaseValue::Nil;
while self.evaluate(*condition.clone())?.is_truthy() {
ret = self.evaluate(*body.clone())?;
self.evaluate(*increment.clone())?;
}
Ok(ret)
}
}
}
fn evaluate_if( fn evaluate_if(
&mut self, &mut self,
condition: Box<AstNode<Expr>>, condition: &AstNode,
then_branch: Box<AstNode<Stmt>>, then_branch: &AstNode,
elif_branch: Vec<(Box<AstNode<Expr>>, Box<AstNode<Stmt>>)>, elif_branch: &[(Box<AstNode>, Box<AstNode>)],
else_branch: Option<Box<AstNode<Stmt>>>, else_branch: &Option<Box<AstNode>>,
) -> LoxResult<BaseValue> { ) -> LoxResult<BaseValue> {
let condition = self.evaluate(*condition)?; let condition = self.eval_expr(condition)?;
match condition { match condition {
BaseValue::Boolean(true) => self.evaluate(*then_branch), BaseValue::Boolean(true) => self.eval_expr(then_branch),
BaseValue::Boolean(false) => { BaseValue::Boolean(false) => {
for (elif_condition, elif_then_branch) in elif_branch { for (elif_condition, elif_then_branch) in elif_branch {
let condition = self.evaluate(*elif_condition)?; let condition = self.eval_expr(elif_condition)?;
match condition { match condition {
BaseValue::Boolean(true) => { BaseValue::Boolean(true) => {
return self.evaluate(*elif_then_branch); return self.eval_expr(elif_then_branch);
} }
BaseValue::Boolean(false) => continue, BaseValue::Boolean(false) => continue,
_ => { _ => {
return Err(LoxError::TypeMismatch { 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(), expected: "boolean".to_string(),
found: condition.to_string(), found: condition.to_string(),
}); });
@@ -274,49 +304,179 @@ impl Interpreter {
}; };
} }
if let Some(else_block) = else_branch { if let Some(else_block) = else_branch {
self.evaluate(*else_block) self.eval_expr(else_block)
} else { } else {
Ok(BaseValue::Nil) Ok(BaseValue::Nil)
} }
} }
_ => Err(LoxError::TypeMismatch { _ => 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(), expected: "boolean".to_string(),
found: condition.to_string(), found: condition.to_string(),
}), }),
} }
} }
fn evaluate_block(&mut self, statements: Vec<AstNode<Stmt>>) -> LoxResult<BaseValue> { fn evaluate_block(&mut self, statements: &[AstNode]) -> LoxResult<BaseValue> {
self.enviorment.push_new_scope(); 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 => { let mut result = Ok(BaseValue::Nil);
(&[][..], None) // Blocco vuoto for statement in statements.iter() {
match &statement.node {
Expr::Return { expression, .. } => {
let value = self.eval_expr(expression)?;
result = Err(LoxError::Return {
source_slice: statement.source_slice.clone(),
value,
return_label: "Hi".to_string(),
});
break;
} }
_ => result = self.eval_expr(statement),
}; };
}
// Ora elements è sempre disponibile self.enviorment.pop_scope();
for statement in elements.iter() { result
self.evaluate((*statement).clone())?; }
} }
// Gestisci l'espressione finale se presente #[cfg(test)]
match final_expr { mod tests {
Some(expr) => { use super::*;
let res = self.evaluate(*expr.clone()); use crate::frontend::lexer::Lexer;
self.enviorment.pop_scope(); use crate::frontend::parser::Parser;
res
/// 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)?;
} }
None => { Ok(last)
self.enviorment.pop_scope(); }
Ok(BaseValue::Nil)
#[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): Number 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
+180 -207
View File
@@ -1,65 +1,87 @@
use crate::{ use crate::{
common::base_value::BaseValue, common::{base_value::BaseValue, types::Type},
frontend::{source_registry::SourceSlice, tokens::TokenType}, frontend::{source_registry::SourceSlice, tokens::TokenType},
}; };
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
/* use std::sync::atomic::{AtomicUsize, Ordering};
* grammar:
* program -> statement* EOF /// A unique identity for an AST node, assigned once at construction.
* statement -> expression_statement ///
* | print_statement /// Unlike a [`SourceSlice`] (which describes *where* a node is, for
* | var_statement /// diagnostics), a `NodeId` describes *which* node it is. It is stable across
* | block_statement /// clones, so analyses such as the resolver can key per-reference data on it
* | assignment_statement /// without relying on source positions being unique.
* | if_statement #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
* pub struct NodeId(pub usize);
* expression_statement -> expression ";"
* print_statement -> "print" expression ";" static NEXT_NODE_ID: AtomicUsize = AtomicUsize::new(0);
* var_statement -> ("var"|"dyn"|"mut")? IDENTIFIER (":" IDENTIFIER)? (("=" expression)? ";")| function_declaration
* function_declaration -> "::" "(" parameters? ")" ("->" IDENTIFIER)? block_statement impl NodeId {
* parameters -> ( IDENTIFIER (":" IDENTIFIER)? ("," IDENTIFIER (":" IDENTIFIER)? )* ) /// Allocate the next globally-unique node id.
* assignment_statement -> IDENTIFIER "=" expression ";" pub fn next() -> Self {
* block_statement -> "do" statement* "end" NodeId(NEXT_NODE_ID.fetch_add(1, Ordering::Relaxed))
* 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)] #[derive(Clone, PartialEq)]
pub enum Expr { pub enum Expr {
Literal { Literal {
value: BaseValue, value: Box<BaseValue>,
}, },
Binary { Binary {
left: Box<AstNode<Expr>>, left: Box<AstNode>,
operator: TokenType, operator: TokenType,
right: Box<AstNode<Expr>>, right: Box<AstNode>,
}, },
Unary { Unary {
operator: TokenType, operator: TokenType,
operand: Box<AstNode<Expr>>, operand: Box<AstNode>,
}, },
Grouping { Grouping {
expression: Box<AstNode<Expr>>, expression: Box<AstNode>,
}, },
Identifier { Identifier {
name: String, name: String,
}, },
Call { Call {
callee: Box<AstNode<Expr>>, callee: Box<AstNode>,
arguments: Vec<AstNode<Expr>>, arguments: Vec<AstNode>,
},
Assign {
name: String,
value: Box<AstNode>,
},
Print {
expression: Box<AstNode>,
},
VarDeclaration {
name: String,
var_type: Type,
initializer: Option<Box<AstNode>>,
},
Return {
expression: Box<AstNode>,
label: String,
},
Block {
statements: Box<Vec<AstNode>>,
label: String,
},
If {
condition: Box<AstNode>,
then_branch: Box<AstNode>,
elif_branches: Vec<(Box<AstNode>, Box<AstNode>)>,
else_branch: Option<Box<AstNode>>,
},
While {
condition: Box<AstNode>,
body: Box<AstNode>,
},
For {
variable: Box<AstNode>,
condition: Box<AstNode>,
increment: Box<AstNode>,
body: Box<AstNode>,
}, },
} }
@@ -74,8 +96,10 @@ impl std::fmt::Display for Expr {
right, right,
} => write!(f, "Binary ({} {} {})", left.node, operator, right.node), } => write!(f, "Binary ({} {} {})", left.node, operator, right.node),
Expr::Unary { operator, operand } => write!(f, "Unary ({} {})", operator, operand.node), Expr::Unary { operator, operand } => write!(f, "Unary ({} {})", operator, operand.node),
Expr::Grouping { expression } => write!(f, "Grouping (group {})", expression.node), Expr::Grouping { expression } => write!(f, "Grouping (group {})", expression.node,),
Expr::Identifier { name } => write!(f, "Identifier (variable {})", name), Expr::Identifier { name } => {
write!(f, "Identifier (variable {})", name)
}
Expr::Call { callee, arguments } => write!( Expr::Call { callee, arguments } => write!(
f, f,
"Call ({}({}))", "Call ({}({}))",
@@ -86,94 +110,18 @@ impl std::fmt::Display for Expr {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", ") .join(", ")
), ),
} Expr::Assign { name, value } => write!(f, "Assign ({} = {})", name, value.node),
} Expr::If {
}
impl Debug for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
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),
Expr::Call { callee, arguments } => write!(
f,
"Call ({}({}))",
callee.node,
arguments
.iter()
.map(|arg| arg.node.to_string())
.collect::<Vec<String>>()
.join(", ")
),
}
}
}
#[derive(Clone, PartialEq)]
pub enum Stmt {
Expression {
expression: Box<AstNode<Expr>>,
},
Print {
expression: Box<AstNode<Expr>>,
},
Stmt {
expression: Box<AstNode<Expr>>,
},
VarDeclaration {
name: String,
initializer: Option<Box<AstNode<Expr>>>,
},
VarAssigment {
name: String,
value: Box<AstNode<Expr>>,
},
Return {
expression: Box<AstNode<Expr>>,
},
Block {
statements: Box<Vec<AstNode<Stmt>>>,
},
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>>>,
},
While {
condition: Box<AstNode<Expr>>,
body: Box<AstNode<Stmt>>,
},
For {
variable: Box<AstNode<Stmt>>,
condition: Box<AstNode<Expr>>,
increment: Box<AstNode<Stmt>>,
body: Box<AstNode<Stmt>>,
},
}
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::If {
condition, condition,
then_branch, then_branch,
elif_branch, elif_branches,
else_branch, else_branch,
} => { } => {
let mut result = format!("IF ({}) {{\n{}\n}}", condition.node, then_branch.node); let mut result = format!("IF ({}) {{\n{}\n}}", condition.node, then_branch.node);
for (condition, branch) in elif_branch { for (condition, branch) in elif_branches {
result.push_str(&format!( result.push_str(&format!(
" ELIF ({}) {{\n{}\n}}", " ELIF ({}) {{\n{}\n}}",
condition.node, branch.node condition.node, branch.node,
)); ));
} }
if let Some(else_branch) = else_branch { if let Some(else_branch) = else_branch {
@@ -181,27 +129,32 @@ impl Display for Stmt {
} }
write!(f, "IfStmt {}", result) write!(f, "IfStmt {}", result)
} }
Stmt::Print { expression } => write!(f, "Print({});", expression.node), Expr::Print { expression } => write!(f, "Print({})", expression.node),
Stmt::Stmt { expression } => write!(f, "Stmt({});", expression.node), Expr::VarDeclaration {
Stmt::VarDeclaration { name, initializer } => match initializer { name,
Some(init) => write!(f, "Var({} = {});", name, init.node), initializer,
None => write!(f, "Var({});", name), var_type,
} => match initializer {
Some(init) => write!(f, "Var({} : {:?} = {})", name, var_type, init.node),
None => write!(f, "Var({} : {:?})", name, var_type),
}, },
Stmt::VarAssigment { name, value } => write!(f, "Assign({} = {});", name, value.node), Expr::Return { expression, label } => {
Stmt::Return { expression } => write!(f, "Return({});", expression.node), write!(f, "Return({}, {})", expression.node, label)
Stmt::Block { statements } => write!( }
Expr::Block { statements, label } => write!(
f, f,
"Block([\n{}\n])", "Block [{}] ([\n{}\n])",
label,
statements statements
.iter() .iter()
.map(|stmt| format!("\t \t{}", stmt.node)) .map(|stmt| format!("\t \t{}", stmt.node))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n") .join("\n"),
), ),
Stmt::While { condition, body } => { Expr::While { condition, body } => {
write!(f, "While({}) {{\n{}\n}}", condition.node, body.node) write!(f, "While({}) {{\n{}\n}}", condition.node, body.node)
} }
Stmt::For { Expr::For {
variable, variable,
condition, condition,
increment, increment,
@@ -215,52 +168,84 @@ impl Display for Stmt {
} }
} }
impl Debug for Stmt { impl Debug for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Stmt::Expression { expression } => write!(f, " Expression ({:?})", expression.node), Expr::Literal { value } => write!(f, "{:?} ", value,),
Stmt::If { Expr::Binary {
left,
operator,
right,
} => 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 ({}({}))",
callee.node,
arguments
.iter()
.map(|arg| arg.node.to_string())
.collect::<Vec<String>>()
.join(", "),
),
Expr::Assign { name, value } => {
write!(f, "(assign {:?} {:?})", name, value.node,)
}
Expr::If {
condition, condition,
then_branch, then_branch,
elif_branch, elif_branches,
else_branch, else_branch,
} => { } => {
let mut result = let mut result =
format!("IF ({:?}) {{\n{:?}\n}}", condition.node, then_branch.node); format!("IF ({:?}) {{\n{:?}\n}}", condition.node, then_branch.node,);
for (condition, branch) in elif_branch { for (condition, branch) in elif_branches {
result.push_str(&format!( result.push_str(&format!(
" ELIF ({:?}) {{\n{:?}\n}}", " ELIF ({:?}) {{\n{:?}\n}}",
condition.node, branch.node condition.node, branch.node,
)); ));
} }
if let Some(else_branch) = else_branch { 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,));
} }
write!(f, "IfStmt {:?}", result) write!(f, "IfStmt {:?}", result)
} }
Stmt::Print { expression } => write!(f, "Print({:?});", expression.node), Expr::Print { expression } => {
Stmt::Stmt { expression } => write!(f, "Stmt({:?});", expression.node), write!(f, "Print({:?});", expression.node,)
Stmt::VarDeclaration { name, initializer } => match initializer {
Some(init) => write!(f, "Var({:?} = {:?});", name, init.node),
None => write!(f, "Var({:?});", name),
},
Stmt::VarAssigment { name, value } => {
write!(f, "Assign({:?} = {:?});", name, value.node)
} }
Stmt::Return { expression } => write!(f, "Return({:?});", expression.node), Expr::VarDeclaration {
Stmt::Block { statements } => write!( 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 { label, statements } => write!(
f, f,
"Block([\n{:?}\n])", "Block [{}] ([\n{:?}\n])",
label,
statements statements
.iter() .iter()
.map(|stmt| format!("{:?}", stmt.node)) .map(|stmt| format!("{:?}", stmt.node))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\t\t\n") .join("\t\t\n"),
), ),
Stmt::While { condition, body } => { Expr::While { condition, body } => {
write!(f, "While({:?}) {{\n{:?}\n}}", condition.node, body.node) write!(f, "While({:?}) {{\n{:?}\n}}", condition.node, body.node,)
} }
Stmt::For { Expr::For {
variable, variable,
condition, condition,
increment, increment,
@@ -268,57 +253,31 @@ impl Debug for Stmt {
} => write!( } => write!(
f, f,
"For({:?} = {:?} in {:?}) {{\n{:?}\n}} ", "For({:?} = {:?} in {:?}) {{\n{:?}\n}} ",
variable.node, condition.node, increment.node, body.node variable.node, condition.node, increment.node, body.node,
), ),
} }
} }
} }
pub trait AstNodeKind { #[derive(Clone)]
fn kind(&self) -> &'static str; pub struct AstNode {
} /// Stable identity, assigned at construction and preserved across clones.
pub id: NodeId,
impl AstNodeKind for Expr { pub node: Expr,
fn kind(&self) -> &'static str { pub is_statement: bool,
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",
}
}
}
impl AstNodeKind for Stmt {
fn kind(&self) -> &'static str {
match self {
Stmt::Expression { .. } => "expression",
Stmt::VarDeclaration { .. } => "variable declaration",
Stmt::VarAssigment { .. } => "assignment",
Stmt::Return { .. } => "return statement",
Stmt::Block { .. } => "block statement",
Stmt::If { .. } => "if statement",
Stmt::Print { .. } => "print statement",
Stmt::Stmt { .. } => "statement",
Stmt::While { .. } => "while statement",
Stmt::For { .. } => "for statement",
}
}
}
#[derive(Clone, PartialEq, Default)]
pub struct AstNode<T: AstNodeKind + Debug + Display> {
pub node: T,
pub source_slice: SourceSlice, pub source_slice: SourceSlice,
pub return_type: Type,
} }
impl<T: AstNodeKind + Debug + Display> Display for AstNode<T> { // 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 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
@@ -328,7 +287,7 @@ impl<T: AstNodeKind + Debug + Display> Display for AstNode<T> {
} }
} }
impl<T: AstNodeKind + Debug + Display> Debug for AstNode<T> { impl Debug for AstNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
@@ -338,8 +297,22 @@ impl<T: AstNodeKind + Debug + Display> Debug for AstNode<T> {
} }
} }
impl<T: AstNodeKind + Debug + Display> AstNode<T> { impl AstNode {
pub fn new(node: T, source_slice: SourceSlice) -> Self { fn new(node: Expr, source_slice: SourceSlice, type_name: String, is_statement: bool) -> Self {
AstNode { node, source_slice } 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)
} }
} }
Regular → Executable
+46 -20
View File
@@ -1,11 +1,12 @@
use core::fmt; use core::fmt;
use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use std::ops::{Add, Div, Mul, Not, Rem, Sub}; use std::ops::{Add, Div, Mul, Not, Rem, Sub};
use crate::common::lox_result::{runtime_error, LoxResult}; use crate::common::lox_result::{runtime_error, LoxResult};
use crate::common::types::Type;
use crate::{ use crate::{
backend::environment::EnvironmentStack, backend::environment::EnvironmentStack, common::ast::AstNode,
common::ast::{AstNode, Expr, Stmt},
frontend::source_registry::SourceSlice, frontend::source_registry::SourceSlice,
}; };
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@@ -271,6 +272,22 @@ pub enum BaseValue {
Nil, Nil,
Function(LoxFunction), Function(LoxFunction),
NativeFunction(NativeFunction), NativeFunction(NativeFunction),
Struct(Struct),
}
struct Dict {
map: HashMap<String, BaseValue>,
}
struct ReturnValue {
label: Vec<String>,
value: Type,
}
impl ReturnValue {
pub fn new(label: Vec<String>, value: Type) -> Self {
Self { label, value }
}
} }
impl BaseValue { impl BaseValue {
@@ -292,36 +309,40 @@ impl Display for BaseValue {
BaseValue::Nil => write!(f, "nil"), BaseValue::Nil => write!(f, "nil"),
BaseValue::Function(..) => write!(f, "<fn lox function>"), BaseValue::Function(..) => write!(f, "<fn lox function>"),
BaseValue::NativeFunction(..) => write!(f, "<native native function>"), BaseValue::NativeFunction(..) => write!(f, "<native native function>"),
BaseValue::Struct(_) => write!(f, "<struct>"),
} }
} }
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct LoxFunction { pub struct LoxFunction {
pub parameters: Vec<(String, String)>, pub parameters: Vec<(String, Type)>,
pub body: AstNode<Stmt>, pub return_type: Type,
pub closure: Option<EnvironmentStack>, pub body: AstNode,
pub guard: Option<Box<Expr>>, pub closure: Option<EnvironmentStack<BaseValue>>,
pub guard: Option<Box<AstNode>>,
} }
impl LoxFunction { impl LoxFunction {
pub fn new( pub fn new(
parameters: Vec<(String, String)>, parameters: Vec<(String, Type)>,
body: AstNode<Stmt>, body: AstNode,
closure: Option<EnvironmentStack>, closure: Option<EnvironmentStack<BaseValue>>,
guard: Option<Box<Expr>>, guard: Option<Box<AstNode>>,
) -> Self { ) -> Self {
LoxFunction { LoxFunction {
parameters, parameters,
return_type: Type::Any,
body, body,
closure, closure,
guard, guard,
} }
} }
pub fn anonymous_function(parameters: Vec<(String, String)>, body: AstNode<Stmt>) -> Self { pub fn anonymous_function(parameters: Vec<(String, Type)>, body: AstNode) -> Self {
LoxFunction { LoxFunction {
parameters, parameters,
return_type: Type::Any,
body, body,
closure: None, closure: None,
guard: None, guard: None,
@@ -331,12 +352,12 @@ impl LoxFunction {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct NativeFunction { pub struct NativeFunction {
pub parameters: Vec<(String, String)>, pub parameters: Vec<(String, Type)>,
pub function: fn(&[BaseValue]) -> BaseValue, pub function: fn(&[BaseValue]) -> BaseValue,
} }
impl NativeFunction { 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 { NativeFunction {
parameters, parameters,
function, function,
@@ -344,6 +365,11 @@ impl NativeFunction {
} }
} }
#[derive(Debug, Clone, PartialEq)]
pub struct Struct {
pub fields: Vec<(String, BaseValue)>,
}
// Trait implementations for BaseValue // Trait implementations for BaseValue
pub trait Truthy { pub trait Truthy {
@@ -383,7 +409,7 @@ impl Add for BaseValue {
Ok(BaseValue::String(format!("{}{}", a, b))) Ok(BaseValue::String(format!("{}{}", a, b)))
} }
_ => runtime_error( _ => runtime_error(
SourceSlice::default(), SourceSlice::synthetic(),
"Cannot add non-numeric values".to_string(), "Cannot add non-numeric values".to_string(),
), ),
} }
@@ -413,7 +439,7 @@ impl Sub for BaseValue {
match (self, other) { match (self, other) {
(BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.sub(b))), (BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.sub(b))),
_ => runtime_error( _ => runtime_error(
SourceSlice::default(), SourceSlice::synthetic(),
"Cannot subtract non-numeric values".to_string(), "Cannot subtract non-numeric values".to_string(),
), ),
} }
@@ -427,10 +453,10 @@ impl Div for BaseValue {
match (self, other) { match (self, other) {
(BaseValue::Number(a), BaseValue::Number(b)) => match a.div(b) { (BaseValue::Number(a), BaseValue::Number(b)) => match a.div(b) {
Some(result) => Ok(BaseValue::Number(result)), 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( _ => runtime_error(
SourceSlice::default(), SourceSlice::synthetic(),
"Cannot divide non-numeric values".to_string(), "Cannot divide non-numeric values".to_string(),
), ),
} }
@@ -444,7 +470,7 @@ impl Mul for BaseValue {
match (self, other) { match (self, other) {
(BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.mul(b))), (BaseValue::Number(a), BaseValue::Number(b)) => Ok(BaseValue::Number(a.mul(b))),
_ => runtime_error( _ => runtime_error(
SourceSlice::default(), SourceSlice::synthetic(),
"Cannot multiply non-numeric values".to_string(), "Cannot multiply non-numeric values".to_string(),
), ),
} }
@@ -458,10 +484,10 @@ impl Rem for BaseValue {
match (self, other) { match (self, other) {
(BaseValue::Number(a), BaseValue::Number(b)) => match a.rem(b) { (BaseValue::Number(a), BaseValue::Number(b)) => match a.rem(b) {
Some(result) => Ok(BaseValue::Number(result)), 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( _ => runtime_error(
SourceSlice::default(), SourceSlice::synthetic(),
"Cannot divide non-numeric values".to_string(), "Cannot divide non-numeric values".to_string(),
), ),
} }
Regular → Executable
+121 -3
View File
@@ -1,5 +1,8 @@
use crate::frontend::source_registry::{SourceRegistry, SourceSlice}; use crate::{
use std::fmt; common::base_value::BaseValue,
frontend::source_registry::{SourceRegistry, SourceSlice},
};
use std::{error::Error, fmt};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum LoxError { pub enum LoxError {
@@ -23,6 +26,11 @@ pub enum LoxError {
expected: String, expected: String,
found: String, found: String,
}, },
Return {
source_slice: SourceSlice,
value: BaseValue,
return_label: String,
},
} }
/// Configuration for error display formatting /// Configuration for error display formatting
@@ -81,6 +89,13 @@ impl LoxError {
} => { } => {
format!("expected {}, found {}", expected, found) 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::ParseError { source_slice, .. } => Some(source_slice.clone()),
LoxError::IoError { .. } => None, LoxError::IoError { .. } => None,
LoxError::TypeMismatch { source_slice, .. } => Some(source_slice.clone()), LoxError::TypeMismatch { source_slice, .. } => Some(source_slice.clone()),
LoxError::Return { .. } => None,
} }
} }
@@ -101,6 +117,7 @@ impl LoxError {
LoxError::ParseError { .. } => "parse error", LoxError::ParseError { .. } => "parse error",
LoxError::IoError { .. } => "io error", LoxError::IoError { .. } => "io error",
LoxError::TypeMismatch { .. } => "type error", LoxError::TypeMismatch { .. } => "type error",
LoxError::Return { .. } => "return error",
} }
} }
@@ -393,11 +410,25 @@ impl fmt::Display for LoxError {
found 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>; pub type LoxResult<T> = Result<T, LoxError>;
@@ -448,3 +479,90 @@ pub fn io_error<T>(message: impl Into<String>) -> LoxResult<T> {
message: message.into(), 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<LoxError>,
}
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<LoxError> {
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);
}
}
Regular → Executable
+1
View File
@@ -1,3 +1,4 @@
pub mod ast; pub mod ast;
pub mod base_value; pub mod base_value;
pub mod lox_result; pub mod lox_result;
pub mod types;
+70
View File
@@ -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<StructType>),
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<Type>)>,
pub return_type: Box<Type>,
}
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::<Vec<_>>()
.join(", ");
write!(f, "fn({}) -> {}", rendered_params, self.return_type)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct StructType {
pub fields: Vec<(String, Box<Type>)>,
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::<Vec<_>>()
.join(", ");
let rendered_method = self
.methods
.iter()
.map(|(name, ty)| format!("{}: {}", name, ty))
.collect::<Vec<_>>()
.join(", ");
write!(f, "struct {{ {}, {} }}", rendered_field, rendered_method)
}
}
Regular → Executable
+263 -41
View File
@@ -5,8 +5,9 @@ use crate::frontend::tokens::{Token, TokenType};
pub struct Lexer { pub struct Lexer {
input: String, input: String,
start_char: usize, // Byte offsets into `input` (not char counts) so slicing is UTF-8 correct.
current_char: usize, start: usize,
current: usize,
start_pos: SourcePosition, start_pos: SourcePosition,
end_pos: SourcePosition, end_pos: SourcePosition,
source_id: SourceId, source_id: SourceId,
@@ -15,7 +16,7 @@ pub struct Lexer {
fn get_keyword_token(word: &str) -> Option<TokenType> { fn get_keyword_token(word: &str) -> Option<TokenType> {
match word { match word {
"and" => Some(TokenType::And), "and" => Some(TokenType::And),
"class" => Some(TokenType::Class), "struct" => Some(TokenType::Struct),
"do" => Some(TokenType::StartBlock), "do" => Some(TokenType::StartBlock),
"end" => Some(TokenType::EndBlock), "end" => Some(TokenType::EndBlock),
"false" => Some(TokenType::False), "false" => Some(TokenType::False),
@@ -29,6 +30,7 @@ fn get_keyword_token(word: &str) -> Option<TokenType> {
"in" => Some(TokenType::In), "in" => Some(TokenType::In),
"print" => Some(TokenType::Print), "print" => Some(TokenType::Print),
"return" => Some(TokenType::Return), "return" => Some(TokenType::Return),
"break" => Some(TokenType::Break),
"super" => Some(TokenType::Super), "super" => Some(TokenType::Super),
"this" => Some(TokenType::This), "this" => Some(TokenType::This),
"true" => Some(TokenType::True), "true" => Some(TokenType::True),
@@ -47,28 +49,18 @@ impl Lexer {
pub fn new(input: String, source_id: SourceId) -> Lexer { pub fn new(input: String, source_id: SourceId) -> Lexer {
Lexer { Lexer {
input, input,
start_char: 0, start: 0,
current_char: 0, current: 0,
start_pos: SourcePosition::default(), start_pos: SourcePosition::default(),
end_pos: SourcePosition::default(), end_pos: SourcePosition::default(),
source_id, 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<Vec<Token>> { pub fn scans_tokens(&mut self) -> LoxResult<Vec<Token>> {
let mut tokens = Vec::new(); let mut tokens = Vec::new();
while !self.is_at_end() { while !self.is_at_end() {
self.start_char = self.current_char; self.start = self.current;
self.start_pos = self.end_pos.clone(); self.start_pos = self.end_pos.clone();
match self.scan_token() { match self.scan_token() {
Ok(Some(token)) => tokens.push(token), Ok(Some(token)) => tokens.push(token),
@@ -81,32 +73,31 @@ impl Lexer {
} }
fn is_at_end(&self) -> bool { fn is_at_end(&self) -> bool {
self.current_char >= self.input.len() self.current >= self.input.len()
} }
fn advance(&mut self) -> char { fn advance(&mut self) -> char {
self.advance_column(); let c = self.input[self.current..].chars().next().unwrap();
self.input.chars().nth(self.current_char - 1).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 { fn peek(&self) -> char {
if self.is_at_end() { self.input[self.current..].chars().next().unwrap_or('\0')
'\0'
} else {
self.input.chars().nth(self.current_char).unwrap()
}
} }
fn peek_next(&self) -> char { fn peek_next(&self) -> char {
if self.current_char + 1 >= self.input.len() { self.input[self.current..].chars().nth(1).unwrap_or('\0')
'\0'
} else {
self.input.chars().nth(self.current_char + 1).unwrap()
}
} }
fn make_token(&self, token_type: TokenType) -> Token { 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::new(
token_type, token_type,
text, text,
@@ -118,7 +109,7 @@ impl Lexer {
) )
} }
fn make_token_with_literal(&self, token_type: TokenType, literal: BaseValue) -> Token { 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::new_complete(
token_type, token_type,
text, text,
@@ -178,9 +169,6 @@ impl Lexer {
('/', '*') => { ('/', '*') => {
// Commento multi-line // Commento multi-line
while (self.peek() != '*' || self.peek_next() != '/') && !self.is_at_end() { while (self.peek() != '*' || self.peek_next() != '/') && !self.is_at_end() {
if self.peek() == '\n' {
self.advance_line();
}
self.advance(); self.advance();
} }
if self.is_at_end() { if self.is_at_end() {
@@ -200,10 +188,7 @@ impl Lexer {
} }
('/', _) => Ok(Some(self.make_token(TokenType::Slash))), ('/', _) => Ok(Some(self.make_token(TokenType::Slash))),
(' ', _) | ('\r', _) | ('\t', _) => Ok(None), (' ', _) | ('\r', _) | ('\t', _) => Ok(None),
('\n', _) => { ('\n', _) => Ok(None),
self.advance_line();
Ok(None)
}
('"', _) => self.string(), ('"', _) => self.string(),
(c, _) if c.is_digit(10) => self.number(), (c, _) if c.is_digit(10) => self.number(),
(c, _) if c.is_alphanumeric() || c == '_' => self.identifier(), (c, _) if c.is_alphanumeric() || c == '_' => self.identifier(),
@@ -235,7 +220,7 @@ impl Lexer {
self.advance(); self.advance();
Ok(Some(self.make_token_with_literal( Ok(Some(self.make_token_with_literal(
TokenType::String, TokenType::String,
BaseValue::String(self.input[self.start_char..self.current_char].to_string()), BaseValue::String(self.input[self.start..self.current].to_string()),
))) )))
} }
@@ -265,7 +250,7 @@ impl Lexer {
None 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() { let num_str_without_suffix = if suffix.is_some() {
&num_str[..num_str.len() - 1] &num_str[..num_str.len() - 1]
} else { } else {
@@ -302,7 +287,7 @@ impl Lexer {
while self.peek().is_alphanumeric() || self.peek() == '_' { while self.peek().is_alphanumeric() || self.peek() == '_' {
self.advance(); 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) { match get_keyword_token(&text) {
Some(TokenType::True) => Ok(Some( Some(TokenType::True) => Ok(Some(
self.make_token_with_literal(TokenType::True, BaseValue::Boolean(true)), self.make_token_with_literal(TokenType::True, BaseValue::Boolean(true)),
@@ -321,3 +306,240 @@ 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());
}
#[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);
}
#[test]
fn handle_struct() {
let tokens = lex("Cosa :: struct {field1: Number, field2: String}");
let tokens_tyepe: Vec<TokenType> = 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");
}
}
Regular → Executable
View File
Regular → Executable
+431 -238
View File
File diff suppressed because it is too large Load Diff
Regular → Executable
+30 -3
View File
@@ -4,7 +4,7 @@ use std::{
io::ErrorKind, io::ErrorKind,
}; };
use crate::common::lox_result::{io_error, LoxError, LoxResult}; use crate::common::lox_result::{io_error, LoxResult};
pub struct SourceRegistry { pub struct SourceRegistry {
pub sources: Vec<SourceFile>, pub sources: Vec<SourceFile>,
@@ -51,7 +51,7 @@ impl SourceFile {
} }
} }
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct SourcePosition { pub struct SourcePosition {
pub line: usize, pub line: usize,
pub column: 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 struct SourceSlice {
pub source_id: SourceId, pub source_id: SourceId,
pub start_position: SourcePosition, pub start_position: SourcePosition,
@@ -91,6 +91,25 @@ impl Display for SourceSlice {
} }
impl 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( pub fn from_positions(
source_id: SourceId, source_id: SourceId,
start_position: SourcePosition, start_position: SourcePosition,
@@ -102,6 +121,14 @@ impl SourceSlice {
end_position, 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( pub fn tree_point(
source_id: SourceId, source_id: SourceId,
column_start: usize, column_start: usize,
Regular → Executable
+6 -2
View File
@@ -35,11 +35,12 @@ pub enum TokenType {
Identifier, Identifier,
String, String,
Number, Number,
Atom,
// Keywords // Keywords
Fn, Fn,
And, And,
Class, Struct,
StartBlock, StartBlock,
EndBlock, EndBlock,
False, False,
@@ -57,6 +58,7 @@ pub enum TokenType {
Is, Is,
Print, Print,
Return, Return,
Break,
Super, Super,
This, This,
Var, Var,
@@ -79,8 +81,9 @@ impl fmt::Display for TokenType {
TokenType::Identifier => write!(f, "IDENTIFIER"), TokenType::Identifier => write!(f, "IDENTIFIER"),
TokenType::String => write!(f, "STRING"), TokenType::String => write!(f, "STRING"),
TokenType::Number => write!(f, "NUMBER"), TokenType::Number => write!(f, "NUMBER"),
TokenType::Atom => write!(f, "ATOM"),
TokenType::And => write!(f, "and"), TokenType::And => write!(f, "and"),
TokenType::Class => write!(f, "class"), TokenType::Struct => write!(f, "struct"),
TokenType::Else => write!(f, "else"), TokenType::Else => write!(f, "else"),
TokenType::False => write!(f, "false"), TokenType::False => write!(f, "false"),
TokenType::True => write!(f, "true"), TokenType::True => write!(f, "true"),
@@ -92,6 +95,7 @@ impl fmt::Display for TokenType {
TokenType::Or => write!(f, "or"), TokenType::Or => write!(f, "or"),
TokenType::Print => write!(f, "print"), TokenType::Print => write!(f, "print"),
TokenType::Return => write!(f, "return"), TokenType::Return => write!(f, "return"),
TokenType::Break => write!(f, "break"),
TokenType::Super => write!(f, "super"), TokenType::Super => write!(f, "super"),
TokenType::This => write!(f, "this"), TokenType::This => write!(f, "this"),
TokenType::Var => write!(f, "var"), TokenType::Var => write!(f, "var"),
Regular → Executable
+2
View File
@@ -1,3 +1,5 @@
pub mod backend; pub mod backend;
pub mod common; pub mod common;
pub mod frontend; pub mod frontend;
pub mod logging;
pub mod middleend;
Regular → Executable
+34 -77
View File
@@ -5,7 +5,7 @@
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use crate::common::ast::{AstNode, AstNodeKind, Expr, Stmt}; use crate::common::ast::{AstNode, Expr};
/// Configuration for pretty printing output /// Configuration for pretty printing output
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@@ -234,6 +234,20 @@ impl PrettyPrint for Expr {
write!(f, "{}Identifier {{ name: {:?} }}", indent, name) 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 } => { Expr::Call { callee, arguments } => {
if ctx.config.compact { if ctx.config.compact {
write!(f, "{}", callee) write!(f, "{}", callee)
@@ -250,33 +264,7 @@ impl PrettyPrint for Expr {
write!(f, "] }}") write!(f, "] }}")
} }
} }
} Expr::Print { expression } => {
}
}
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 } => {
if ctx.config.compact { if ctx.config.compact {
let expr_str = let expr_str =
pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact());
@@ -289,7 +277,9 @@ impl PrettyPrint for Stmt {
write!(f, "{}}}", indent) write!(f, "{}}}", indent)
} }
} }
Stmt::VarDeclaration { name, initializer } => { Expr::VarDeclaration {
name, initializer, ..
} => {
if ctx.config.compact { if ctx.config.compact {
match initializer { match initializer {
Some(init) => { Some(init) => {
@@ -315,21 +305,7 @@ impl PrettyPrint for Stmt {
write!(f, "{}}}", indent) write!(f, "{}}}", indent)
} }
} }
Stmt::VarAssigment { name, value } => { Expr::Return { expression, .. } => {
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 { if ctx.config.compact {
let expr_str = let expr_str =
pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact()); pretty_print_with_config(expression.as_ref(), &PrettyConfig::compact());
@@ -342,11 +318,11 @@ impl PrettyPrint for Stmt {
write!(f, "{}}}", indent) write!(f, "{}}}", indent)
} }
} }
Stmt::Block { statements } => { Expr::Block { statements, label } => {
if ctx.config.compact { if ctx.config.compact {
write!(f, "{{ ... ({} statements) }}", statements.len()) write!(f, "{{ ... [{}] ({} statements) }}", label, statements.len())
} else { } else {
writeln!(f, "{}Block {{", indent)?; writeln!(f, "{}Block [{}] {{", label, indent)?;
writeln!(f, "{}statements: [", ctx.child_context(false).indent())?; writeln!(f, "{}statements: [", ctx.child_context(false).indent())?;
for (i, stmt) in statements.iter().enumerate() { for (i, stmt) in statements.iter().enumerate() {
let is_last = i == statements.len() - 1; let is_last = i == statements.len() - 1;
@@ -361,10 +337,10 @@ impl PrettyPrint for Stmt {
write!(f, "{}}}", indent) write!(f, "{}}}", indent)
} }
} }
Stmt::If { Expr::If {
condition, condition,
then_branch, then_branch,
elif_branch, elif_branches,
else_branch, else_branch,
} => { } => {
if ctx.config.compact { if ctx.config.compact {
@@ -380,10 +356,10 @@ impl PrettyPrint for Stmt {
then_branch.pretty_print(&ctx.child_context(false), f)?; then_branch.pretty_print(&ctx.child_context(false), f)?;
writeln!(f, ",")?; writeln!(f, ",")?;
if !elif_branch.is_empty() { if !elif_branches.is_empty() {
writeln!(f, "{}elif_branches: [", ctx.child_context(false).indent())?; writeln!(f, "{}elif_branches: [", ctx.child_context(false).indent())?;
for (i, (cond, branch)) in elif_branch.iter().enumerate() { for (i, (cond, branch)) in elif_branches.iter().enumerate() {
let is_last = i == elif_branch.len() - 1; let is_last = i == elif_branches.len() - 1;
let child_ctx = ctx.child_context(false).child_context(is_last); let child_ctx = ctx.child_context(false).child_context(is_last);
writeln!(f, "{}(", child_ctx.indent())?; writeln!(f, "{}(", child_ctx.indent())?;
write!(f, "{}condition: ", child_ctx.child_context(false).indent())?; write!(f, "{}condition: ", child_ctx.child_context(false).indent())?;
@@ -415,15 +391,14 @@ impl PrettyPrint for Stmt {
write!(f, "{}}}", indent) write!(f, "{}}}", indent)
} }
} }
Stmt::Stmt { expression } => expression.pretty_print(ctx, f), Expr::While { condition, body } => {
Stmt::While { condition, body } => {
write!(f, "{}while (", ctx.child_context(true).indent())?; write!(f, "{}while (", ctx.child_context(true).indent())?;
condition.pretty_print(&ctx.child_context(true), f)?; condition.pretty_print(&ctx.child_context(true), f)?;
writeln!(f, ") {{")?; writeln!(f, ") {{")?;
body.pretty_print(&ctx.child_context(true), f)?; body.pretty_print(&ctx.child_context(true), f)?;
writeln!(f, "{}}}", ctx.child_context(true).indent()) writeln!(f, "{}}}", ctx.child_context(true).indent())
} }
Stmt::For { Expr::For {
variable, variable,
condition, condition,
increment, increment,
@@ -443,7 +418,7 @@ impl PrettyPrint for Stmt {
} }
} }
impl<T: AstNodeKind + fmt::Debug + fmt::Display + PrettyPrint> PrettyPrint for AstNode<T> { impl PrettyPrint for AstNode {
fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result { fn pretty_print(&self, ctx: &PrettyContext, f: &mut fmt::Formatter) -> fmt::Result {
let indent = ctx.indent(); let indent = ctx.indent();
@@ -455,9 +430,9 @@ impl<T: AstNodeKind + fmt::Debug + fmt::Display + PrettyPrint> PrettyPrint for A
if ctx.config.show_types { if ctx.config.show_types {
writeln!( writeln!(
f, f,
"{}kind: {:?},", "{}type: {},",
ctx.child_context(false).indent(), ctx.child_context(false).indent(),
self.node.kind() self.return_type
)?; )?;
} }
@@ -511,25 +486,7 @@ impl PrettyPrintExt for Expr {
} }
} }
impl PrettyPrintExt for Stmt { impl PrettyPrintExt for AstNode {
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<T: AstNodeKind + fmt::Debug + fmt::Display + PrettyPrint> PrettyPrintExt for AstNode<T> {
fn pretty(&self) -> String { fn pretty(&self) -> String {
pretty_print(self) pretty_print(self)
} }
Regular → Executable
+6 -2
View File
@@ -174,7 +174,8 @@ impl Token {
TokenType::String => "STR", TokenType::String => "STR",
TokenType::Number => "NUM", TokenType::Number => "NUM",
TokenType::And => "AND", TokenType::And => "AND",
TokenType::Class => "CLASS", TokenType::Struct => "STRUCT",
TokenType::Atom => "ATOM",
TokenType::StartBlock => "DO", TokenType::StartBlock => "DO",
TokenType::EndBlock => "END", TokenType::EndBlock => "END",
TokenType::False => "FALSE", TokenType::False => "FALSE",
@@ -190,6 +191,7 @@ impl Token {
TokenType::Or => "OR", TokenType::Or => "OR",
TokenType::Print => "PRINT", TokenType::Print => "PRINT",
TokenType::Return => "RETURN", TokenType::Return => "RETURN",
TokenType::Break => "BREAK",
TokenType::Super => "SUPER", TokenType::Super => "SUPER",
TokenType::This => "THIS", TokenType::This => "THIS",
TokenType::Var => "VAR", TokenType::Var => "VAR",
@@ -218,7 +220,7 @@ impl Token {
| TokenType::Less | TokenType::Less
| TokenType::LessEqual => ("\x1b[31m", self.token_type_symbol()), | TokenType::LessEqual => ("\x1b[31m", self.token_type_symbol()),
TokenType::And TokenType::And
| TokenType::Class | TokenType::Struct
| TokenType::False | TokenType::False
| TokenType::True | TokenType::True
| TokenType::Fun | TokenType::Fun
@@ -232,11 +234,13 @@ impl Token {
| TokenType::Or | TokenType::Or
| TokenType::Print | TokenType::Print
| TokenType::Return | TokenType::Return
| TokenType::Break
| TokenType::Super | TokenType::Super
| TokenType::This | TokenType::This
| TokenType::Var | TokenType::Var
| TokenType::Fn | TokenType::Fn
| TokenType::Is | TokenType::Is
| TokenType::Atom
| TokenType::Val => ("\x1b[34m", self.token_type_symbol()), | TokenType::Val => ("\x1b[34m", self.token_type_symbol()),
TokenType::Identifier | TokenType::String | TokenType::Number => { TokenType::Identifier | TokenType::String | TokenType::Number => {
("\x1b[32m", self.token_type_symbol()) ("\x1b[32m", self.token_type_symbol())
Regular → Executable
View File
Regular → Executable
+43 -97
View File
@@ -1,12 +1,12 @@
mod backend; pub mod backend;
mod common; pub mod common;
mod frontend; pub mod frontend;
mod logging; pub mod logging;
pub mod middleend;
use crate::{ use crate::{
backend::interpreter::{EvaluateInterpreter, Interpreter}, backend::interpreter::{EvaluateInterpreter, Interpreter},
common::{ common::{
ast::{AstNode, Stmt}, ast::AstNode,
lox_result::{LoxError, LoxResult}, lox_result::{LoxError, LoxResult},
}, },
frontend::{ frontend::{
@@ -14,6 +14,7 @@ use crate::{
parser::Parser, parser::Parser,
source_registry::{SourceId, SourceRegistry}, source_registry::{SourceId, SourceRegistry},
}, },
middleend::variable_resolution::Resolver,
}; };
use std::env; use std::env;
use std::fs; use std::fs;
@@ -29,109 +30,35 @@ fn main() -> LoxResult<()> {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
let (stage, file_path, debug) = parse_args(&args); let (stage, file_path, debug) = parse_args(&args);
println!("running {file_path:?}");
let mut lox = LoxInterpreter::new(debug); let mut lox = LoxInterpreter::new(debug);
let _ = match file_path { let res = match file_path {
Some(path) => lox.run_file(&path, stage), Some(path) => lox.run_file(&path, stage),
None => lox.run_prompt(stage), None => lox.run_prompt(stage),
}; };
Ok(()) res
} }
fn parse_args(args: &[String]) -> (ExecutionStage, Option<String>, bool) { fn parse_args(args: &[String]) -> (ExecutionStage, Option<String>, bool) {
if args.len() == 1 { println!("{args:?}");
// 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)
}
} 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 stage = ExecutionStage::Full;
let mut file_path = None; let mut file_path = None;
let mut debug = false;
for arg in &args[1..3] { for arg in args.iter().skip(1) {
if arg == "--debug" { match arg.as_str() {
debug = true; "-t" | "--tokens" => stage = ExecutionStage::Tokens,
} else if arg == "--tokens" || arg == "--ast" || arg == "--full" { "-a" | "--ast" => stage = ExecutionStage::Ast,
stage = match arg.as_str() { "-f" | "--full" => stage = ExecutionStage::Full,
"--tokens" => ExecutionStage::Tokens, "-d" | "--debug" => debug = true,
"--ast" => ExecutionStage::Ast, _ => file_path = Some(arg.clone()),
"--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) (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);
}
} }
struct LoxInterpreter { struct LoxInterpreter {
@@ -247,7 +174,7 @@ impl LoxInterpreter {
} }
// Funzione per parsare fino all'AST // Funzione per parsare fino all'AST
fn parse_to_ast(&self, source_id: SourceId) -> LoxResult<Vec<AstNode<Stmt>>> { fn parse_to_ast(&self, source_id: SourceId) -> LoxResult<Vec<AstNode>> {
let tokens = self.tokenize(source_id)?; let tokens = self.tokenize(source_id)?;
Parser::new(tokens).parse().or_else(|err| { Parser::new(tokens).parse().or_else(|err| {
@@ -257,8 +184,12 @@ impl LoxInterpreter {
} }
// Funzione per eseguire l'AST // Funzione per eseguire l'AST
fn execute_ast(&self, ast: Vec<AstNode<Stmt>>, debug: bool) -> LoxResult<String> { fn execute_ast(&self, ast: Vec<AstNode>, debug: bool) -> LoxResult<String> {
// Static resolution pass: compute variable scope distances and report
// any resolution errors before executing.
let locals = self.resolve(&ast)?;
let mut interpreter = Interpreter::new(); let mut interpreter = Interpreter::new();
interpreter.set_locals(locals);
let mut result = None; let mut result = None;
for (index, stmt) in ast.iter().enumerate() { for (index, stmt) in ast.iter().enumerate() {
@@ -277,6 +208,19 @@ impl LoxInterpreter {
} }
} }
// Static variable resolution; prints and returns the first error, if any.
fn resolve(
&self,
ast: &[AstNode],
) -> LoxResult<std::collections::HashMap<crate::common::ast::NodeId, usize>> {
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( fn process_source(
&self, &self,
source_id: SourceId, source_id: SourceId,
@@ -318,8 +262,10 @@ impl LoxInterpreter {
return Ok(format_ast(&ast)); return Ok(format_ast(&ast));
} }
// Stage 3: Interpretation // Stage 3: Resolution + Interpretation
let locals = self.resolve(&ast)?;
let mut interpreter = Interpreter::new(); let mut interpreter = Interpreter::new();
interpreter.set_locals(locals);
let mut result = None; let mut result = None;
for (index, stmt) in ast.iter().enumerate() { for (index, stmt) in ast.iter().enumerate() {
@@ -348,7 +294,7 @@ fn format_tokens(tokens: &[crate::frontend::tokens::Token]) -> String {
.join("\n") .join("\n")
} }
fn format_ast(ast: &[AstNode<Stmt>]) -> String { fn format_ast(ast: &[AstNode]) -> String {
use crate::logging::display_ast::{pretty_print_with_config, PrettyConfig}; use crate::logging::display_ast::{pretty_print_with_config, PrettyConfig};
let config = PrettyConfig { let config = PrettyConfig {
+3
View File
@@ -0,0 +1,3 @@
pub mod scope_stack;
pub mod variable_resolution;
pub mod visit_ast;
+146
View File
@@ -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<T> {
scopes: Vec<HashMap<String, T>>,
}
impl<T> Default for ScopeStack<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> ScopeStack<T> {
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<String>, 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<usize> {
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<bool> = 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<bool> = 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<bool> = 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<bool> = 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<bool> = 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);
}
}
+258
View File
@@ -0,0 +1,258 @@
//! 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::{
common::{
ast::{AstNode, Expr, NodeId},
base_value::{BaseValue, LoxFunction},
lox_result::{ErrorSink, LoxError, LoxResult},
},
middleend::{
scope_stack::ScopeStack,
visit_ast::{walk_expr, walk_function, Visitor},
},
};
/// 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<bool>,
locals: HashMap<NodeId, usize>,
errors: ErrorSink,
current_function: FunctionType,
}
impl Resolver {
pub fn new() -> Self {
Resolver {
scopes: ScopeStack::new(),
locals: HashMap::new(),
errors: ErrorSink::new(),
current_function: FunctionType::None,
}
}
/// Resolve a whole program, returning the per-reference scope distances or
/// the accumulated resolution errors.
pub fn resolve_program(
statements: &[AstNode],
) -> Result<HashMap<NodeId, usize>, Vec<LoxError>> {
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_expr(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;
}
self.scopes.set_local(name, true);
}
fn resolve_local(&mut self, id: NodeId, name: &str) {
if let Some(distance) = self.scopes.resolve(name) {
self.locals.insert(id, distance);
}
// Not found locally: assume global, record nothing.
}
}
impl Default for Resolver {
fn default() -> Self {
Self::new()
}
}
impl Visitor for Resolver {
fn visit_expr(&mut self, expr: &AstNode) -> LoxResult<()> {
match &expr.node {
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(),
});
}
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);
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),
}
}
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, &param_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<AstNode> {
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<HashMap<NodeId, usize>, Vec<LoxError>> {
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): 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);
}
#[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());
}
}
+228
View File
@@ -0,0 +1,228 @@
//! 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},
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 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 `expr`, calling back into `visitor`.
pub fn walk_expr<V: Visitor>(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::Assign { value, .. } => visitor.visit_expr(value),
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(())
}
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)
}
}
}
/// 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_expr(&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> {
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 nodes it sees.
#[derive(Default)]
struct Counter {
nodes: usize,
}
impl Visitor for Counter {
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_expr(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.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): 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
/// 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_expr(stmt);
if result.is_err() {
break;
}
}
assert!(result.is_err());
}
}
+296
View File
@@ -0,0 +1,296 @@
//! 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};
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<Vec<AstNode>, 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<AstNode> {
parse(src).expect("expected source to lex and parse")
}
/// 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 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 {
single_stmt(src)
}
// ---------------------------------------------------------------------------
// 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;") {
Expr::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;"), Expr::Print { .. }));
}
#[test]
fn parses_block_with_multiple_statements() {
match single_stmt("do print 1; print 2; end") {
Expr::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;") {
Expr::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"),
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") {
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): 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 } if matches!(&**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());
}
Regular → Executable
View File
+69
View File
@@ -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<BaseValue, String> {
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 (): Any do
count := 10;
get :: fn (): Number 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"));
}
+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(())
}