3 Commits
Author SHA1 Message Date
Giulio Agostini 9a817befac Move variable resolution to middleend
- Introduce AST-based visitor and resolver in middleend
- Update guard field and parser to use AstNode<Expr>
- Remove backend variable_resolution and adjust exports
- Expose middleend in the library
2026-07-22 11:36:43 +02:00
Giulio Agostini 5f4fa86979 Make environment generic and adapt closures
Added unit test
2026-07-22 11:36:43 +02:00
fizban 6bf255668a wip: i dont rembemer 2026-07-11 18:07:07 +02:00
32 changed files with 1181 additions and 151 deletions
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+15
View File
@@ -10,3 +10,18 @@ end
internal := closure(); internal := closure();
internal(); internal();
a := "Global";
b := a;
c := b;
c = b = a;
d := c = b = a;
do
showA :: fn() do
print a;
end
showA();
a := "Local";
showA();
end
Regular → Executable
View File
Regular → Executable
View File
View File
Regular → Executable
+118 -13
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() {
@@ -42,12 +53,12 @@ impl EnvironmentStack {
) )
} }
pub fn declare(&mut self, name: String, value: BaseValue) -> LoxResult<BaseValue> { pub fn declare(&mut self, name: String, value: T) -> LoxResult<T> {
self.stack.last_mut().unwrap().insert(name, value.clone()); self.stack.last_mut().unwrap().insert(name, value.clone());
Ok(value) Ok(value)
} }
pub fn set(&mut self, name: String, value: BaseValue) -> LoxResult<BaseValue> { pub fn set(&mut self, name: String, value: T) -> LoxResult<T> {
let size = self.stack.len(); let size = self.stack.len();
for i in (0..size).rev() { for i in (0..size).rev() {
@@ -68,4 +79,98 @@ impl EnvironmentStack {
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
+142 -6
View File
@@ -10,14 +10,14 @@ use crate::{
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
pub struct Interpreter { pub struct Interpreter {
enviorment: EnvironmentStack, enviorment: EnvironmentStack<BaseValue>,
} }
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<R: AstNodeKind + Clone + Debug + Display> EvaluateInterpreter<AstNode<R>> for Interpreter
where where
Interpreter: EvaluateInterpreter<R>, Interpreter: EvaluateInterpreter<R>,
{ {
@@ -36,8 +36,6 @@ impl EvaluateInterpreter<Expr> for Interpreter {
Expr::Literal { value } => match value { Expr::Literal { value } => match value {
BaseValue::Function(mut func) => { BaseValue::Function(mut func) => {
func.closure = Some(self.enviorment.clone()); func.closure = Some(self.enviorment.clone());
println!("Closure created");
println!("current enviorment: {:?}", self.enviorment);
Ok(BaseValue::Function(func)) Ok(BaseValue::Function(func))
} }
_ => Ok(value), _ => Ok(value),
@@ -128,9 +126,7 @@ impl Interpreter {
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) => {
// Save the current environment
let saved_env = self.enviorment.clone(); let saved_env = self.enviorment.clone();
// If the function captured a closure, use it as the base environment // If the function captured a closure, use it as the base environment
if let Some(closure_env) = func.closure { if let Some(closure_env) = func.closure {
self.enviorment = closure_env; self.enviorment = closure_env;
@@ -348,3 +344,143 @@ impl Interpreter {
result result
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::frontend::lexer::Lexer;
use crate::frontend::parser::Parser;
/// Lex, parse and interpret `src`, returning the value of the last statement.
fn eval(src: &str) -> LoxResult<BaseValue> {
let tokens = Lexer::new(src.to_string(), 0)
.scans_tokens()
.expect("source should lex without errors");
let statements = Parser::new(tokens)
.parse()
.expect("source should parse without errors");
let mut interpreter = Interpreter::new();
let mut last = BaseValue::Nil;
for stmt in statements {
last = interpreter.evaluate(stmt)?;
}
Ok(last)
}
#[test]
fn evaluates_arithmetic_with_precedence() {
assert_eq!(eval("1 + 2 * 3;").unwrap().to_string(), "7");
}
#[test]
fn grouping_changes_precedence() {
assert_eq!(eval("(1 + 2) * 3;").unwrap().to_string(), "9");
}
#[test]
fn division_and_subtraction() {
assert_eq!(eval("10 - 4 / 2;").unwrap().to_string(), "8");
}
#[test]
fn comparison_yields_boolean() {
assert_eq!(eval("1 < 2;").unwrap(), BaseValue::Boolean(true));
assert_eq!(eval("2 < 1;").unwrap(), BaseValue::Boolean(false));
}
#[test]
fn equality_operators() {
assert_eq!(eval("1 == 1;").unwrap(), BaseValue::Boolean(true));
assert_eq!(eval("1 != 2;").unwrap(), BaseValue::Boolean(true));
}
#[test]
fn unary_negation() {
assert_eq!(eval("-5;").unwrap().to_string(), "-5");
}
#[test]
fn logical_not() {
assert_eq!(eval("!true;").unwrap(), BaseValue::Boolean(false));
}
#[test]
fn concatenates_strings() {
match eval("\"a\" + \"b\";").unwrap() {
BaseValue::String(s) => assert!(s.contains('a') && s.contains('b')),
other => panic!("expected string, got {:?}", other),
}
}
#[test]
fn declares_and_reads_variable() {
assert_eq!(eval("var x: Int = 10; x + 5;").unwrap().to_string(), "15");
}
#[test]
fn assignment_updates_variable() {
assert_eq!(
eval("var x: Int = 1; x = 42; x;").unwrap().to_string(),
"42"
);
}
#[test]
fn if_takes_then_branch_when_true() {
assert_eq!(
eval("var x: Int = 0; if true then x = 1; x;")
.unwrap()
.to_string(),
"1"
);
}
#[test]
fn if_takes_else_branch_when_false() {
assert_eq!(
eval("var x: Int = 0; if false then x = 1; else x = 2; x;")
.unwrap()
.to_string(),
"2"
);
}
#[test]
fn while_loop_runs_until_condition_false() {
let src = "var i: Int = 0; while i < 3 do i = i + 1; end i;";
assert_eq!(eval(src).unwrap().to_string(), "3");
}
#[test]
fn block_scope_does_not_leak_outer_assignment() {
// `set` walks outer scopes, so the outer `x` is updated from inside the block.
let src = "var x: Int = 1; do x = 5; end x;";
assert_eq!(eval(src).unwrap().to_string(), "5");
}
#[test]
fn defines_and_calls_a_function() {
let src = "add :: fn (a, b) do return a + b; end add(2, 3);";
assert_eq!(eval(src).unwrap().to_string(), "5");
}
#[test]
fn clock_native_function_returns_a_number() {
assert!(matches!(eval("clock();").unwrap(), BaseValue::Number(..)));
}
#[test]
fn undefined_variable_is_runtime_error() {
assert!(eval("missing;").is_err());
}
#[test]
fn calling_a_non_function_is_runtime_error() {
assert!(eval("var x: Int = 5; x();").is_err());
}
#[test]
fn adding_incompatible_types_is_runtime_error() {
assert!(eval("1 + true;").is_err());
}
}
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
+5 -4
View File
@@ -2,6 +2,7 @@ use core::fmt;
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::backend::environment::Environment;
use crate::common::lox_result::{runtime_error, LoxResult}; use crate::common::lox_result::{runtime_error, LoxResult};
use crate::{ use crate::{
backend::environment::EnvironmentStack, backend::environment::EnvironmentStack,
@@ -312,16 +313,16 @@ pub struct LoxFunction {
pub parameters: Vec<(String, String)>, pub parameters: Vec<(String, String)>,
pub return_type: Option<String>, pub return_type: Option<String>,
pub body: AstNode<Stmt>, pub body: AstNode<Stmt>,
pub closure: Option<EnvironmentStack>, pub closure: Option<EnvironmentStack<BaseValue>>,
pub guard: Option<Box<Expr>>, pub guard: Option<Box<AstNode<Expr>>>,
} }
impl LoxFunction { impl LoxFunction {
pub fn new( pub fn new(
parameters: Vec<(String, String)>, parameters: Vec<(String, String)>,
body: AstNode<Stmt>, body: AstNode<Stmt>,
closure: Option<EnvironmentStack>, closure: Option<EnvironmentStack<BaseValue>>,
guard: Option<Box<Expr>>, guard: Option<Box<AstNode<Expr>>>,
) -> Self { ) -> Self {
LoxFunction { LoxFunction {
parameters, parameters,
Regular → Executable
+1 -5
View File
@@ -106,11 +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 { LoxError::Return { .. } => None,
value,
return_label,
..
} => None,
} }
} }
Regular → Executable
View File
Regular → Executable
+188
View File
@@ -322,3 +322,191 @@ impl Lexer {
} }
} }
} }
#[cfg(test)]
mod tests {
use std::f64;
use super::*;
/// Lex `input` and return the resulting tokens, panicking on lexical errors.
fn lex(input: &str) -> Vec<Token> {
Lexer::new(input.to_string(), 0)
.scans_tokens()
.expect("expected input to lex without errors")
}
/// Lex `input` and collect just the token types (including the trailing EOF).
fn token_types(input: &str) -> Vec<TokenType> {
lex(input).into_iter().map(|t| t.token_type).collect()
}
#[test]
fn scans_single_character_tokens() {
assert_eq!(
token_types("(){}[],.-+;:*%"),
vec![
TokenType::LeftParen,
TokenType::RightParen,
TokenType::LeftBrace,
TokenType::RightBrace,
TokenType::LeftBracket,
TokenType::RightBracket,
TokenType::Comma,
TokenType::Dot,
TokenType::Minus,
TokenType::Plus,
TokenType::Semicolon,
TokenType::Colon,
TokenType::Star,
TokenType::Percent,
TokenType::Eof,
]
);
}
#[test]
fn scans_one_and_two_char_operators() {
assert_eq!(
token_types("! != = == < <= > >= /"),
vec![
TokenType::Bang,
TokenType::BangEqual,
TokenType::Equal,
TokenType::EqualEqual,
TokenType::Less,
TokenType::LessEqual,
TokenType::Greater,
TokenType::GreaterEqual,
TokenType::Slash,
TokenType::Eof,
]
);
}
#[test]
fn keyword_lookup_matches_known_words() {
assert_eq!(get_keyword_token("and"), Some(TokenType::And));
assert_eq!(get_keyword_token("if"), Some(TokenType::If));
assert_eq!(get_keyword_token("then"), Some(TokenType::Then));
assert_eq!(get_keyword_token("while"), Some(TokenType::While));
assert_eq!(get_keyword_token("do"), Some(TokenType::StartBlock));
assert_eq!(get_keyword_token("end"), Some(TokenType::EndBlock));
assert_eq!(get_keyword_token("not_a_keyword"), None);
}
#[test]
fn scans_keywords_in_a_stream() {
assert_eq!(
token_types("if then else while print return"),
vec![
TokenType::If,
TokenType::Then,
TokenType::Else,
TokenType::While,
TokenType::Print,
TokenType::Return,
TokenType::Eof,
]
);
}
#[test]
fn scans_integer_number() {
let tokens = lex("42");
assert_eq!(tokens[0].token_type, TokenType::Number);
assert_eq!(tokens[0].literal, Some(BaseValue::Number(Number::I32(42))));
}
#[test]
fn scans_float_number() {
let tokens = lex("3.141592653589793");
assert_eq!(
tokens[0].literal,
Some(BaseValue::Number(Number::F64(f64::consts::PI)))
);
}
#[test]
fn scans_number_with_float_suffix() {
let tokens = lex("5f");
assert_eq!(tokens[0].literal, Some(BaseValue::Number(Number::F64(5.0))));
}
#[test]
fn scans_number_with_unsigned_suffix() {
let tokens = lex("7u");
assert_eq!(tokens[0].literal, Some(BaseValue::Number(Number::U128(7))));
}
#[test]
fn scans_string_literal_including_quotes() {
// The lexer slices from the opening quote through the closing quote,
// so the literal currently retains the surrounding quotes.
let tokens = lex("\"hello\"");
assert_eq!(tokens[0].token_type, TokenType::String);
assert_eq!(
tokens[0].literal,
Some(BaseValue::String("\"hello\"".to_string()))
);
}
#[test]
fn scans_identifier() {
let tokens = lex("foo_bar");
assert_eq!(tokens[0].token_type, TokenType::Identifier);
assert_eq!(
tokens[0].literal,
Some(BaseValue::String("foo_bar".to_string()))
);
}
#[test]
fn scans_boolean_and_nil_literals() {
let tokens = lex("true false Nil");
assert_eq!(tokens[0].literal, Some(BaseValue::Boolean(true)));
assert_eq!(tokens[1].literal, Some(BaseValue::Boolean(false)));
assert_eq!(tokens[2].literal, Some(BaseValue::Nil));
}
#[test]
fn ignores_line_comments() {
assert_eq!(
token_types("1 // a comment\n2"),
vec![TokenType::Number, TokenType::Number, TokenType::Eof]
);
}
#[test]
fn ignores_block_comments() {
assert_eq!(
token_types("1 /* multi\nline */ 2"),
vec![TokenType::Number, TokenType::Number, TokenType::Eof]
);
}
#[test]
fn always_appends_eof_even_for_empty_input() {
let tokens = lex("");
assert_eq!(tokens.len(), 1);
assert_eq!(tokens[0].token_type, TokenType::Eof);
}
#[test]
fn unterminated_string_is_a_lexical_error() {
let result = Lexer::new("\"oops".to_string(), 0).scans_tokens();
assert!(matches!(result, Err(LoxError::LexicalError { .. })));
}
#[test]
fn unterminated_block_comment_is_a_lexical_error() {
let result = Lexer::new("/* never ends".to_string(), 0).scans_tokens();
assert!(matches!(result, Err(LoxError::LexicalError { .. })));
}
#[test]
fn unexpected_character_is_a_lexical_error() {
let result = Lexer::new("@".to_string(), 0).scans_tokens();
assert!(result.is_err());
}
}
Regular → Executable
+1
View File
@@ -2,3 +2,4 @@ pub mod lexer;
pub mod parser; pub mod parser;
pub mod source_registry; pub mod source_registry;
pub mod tokens; pub mod tokens;
pub mod variable_resolution;
Regular → Executable
+157 -7
View File
@@ -205,6 +205,10 @@ impl Parser {
let mut _type_annotation = BaseValue::Nil; let mut _type_annotation = BaseValue::Nil;
self.consume(TokenType::Colon, "Expect column")?; self.consume(TokenType::Colon, "Expect column")?;
if self.peek().token_type == TokenType::Identifier {
// TODO manage type annotation
self.consume(TokenType::Identifier, "Expect type name.")?;
}
match self.peek().token_type { match self.peek().token_type {
TokenType::Equal | TokenType::Identifier => { TokenType::Equal | TokenType::Identifier => {
@@ -248,11 +252,10 @@ impl Parser {
end_position: end_position.end_position, end_position: end_position.end_position,
}; };
let mut guard: Option<Box<Expr>> = None; let mut guard: Option<Box<AstNode<Expr>>> = None;
if self.peek().token_type == TokenType::LeftBrace { if self.peek().token_type == TokenType::LeftBrace {
self.consume(TokenType::LeftBrace, "Expected '{' after guard expression")?; self.consume(TokenType::LeftBrace, "Expected '{' after guard expression")?;
println!("ho trovato una guradia"); guard = Some(Box::new(self.expression()?));
guard = Some(Box::new(self.expression()?.node.clone()));
self.consume(TokenType::RightBrace, "Expected '}' after guard expression")?; self.consume(TokenType::RightBrace, "Expected '}' after guard expression")?;
} }
@@ -374,11 +377,7 @@ impl Parser {
while self.peek().token_type != TokenType::EndBlock && !self.is_at_end() { while self.peek().token_type != TokenType::EndBlock && !self.is_at_end() {
let stmt = self.statement(label.clone())?; let stmt = self.statement(label.clone())?;
let should_break = matches!(stmt.node, Stmt::Expression { .. });
statements.push(stmt); statements.push(stmt);
if should_break {
break;
}
} }
let end_token = self.consume(TokenType::EndBlock, "Expect 'end' after block.")?; let end_token = self.consume(TokenType::EndBlock, "Expect 'end' after block.")?;
let end_slice = end_token.source_slice.clone(); let end_slice = end_token.source_slice.clone();
@@ -864,3 +863,154 @@ impl Parser {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::common::base_value::Number;
use crate::frontend::lexer::Lexer;
/// Lex and parse `src`, returning the parser's result.
fn parse_source(src: &str) -> LoxResult<Vec<AstNode<Stmt>>> {
let tokens = Lexer::new(src.to_string(), 0)
.scans_tokens()
.expect("source should lex without errors");
Parser::new(tokens).parse()
}
/// Parse `src`, panicking if parsing fails.
fn parse_ok(src: &str) -> Vec<AstNode<Stmt>> {
parse_source(src).expect("expected source to parse")
}
/// Extract the inner expression of an expression statement.
fn expression_of(stmt: &AstNode<Stmt>) -> &AstNode<Expr> {
match &stmt.node {
Stmt::Expression { expression, .. } => expression,
other => panic!("expected expression statement, got {:?}", other),
}
}
#[test]
fn parses_number_literal_expression() {
let stmts = parse_ok("42;");
assert_eq!(stmts.len(), 1);
match &expression_of(&stmts[0]).node {
Expr::Literal { value } => {
assert_eq!(*value, BaseValue::Number(Number::I32(42)));
}
other => panic!("expected literal, got {:?}", other),
}
}
#[test]
fn respects_multiplication_precedence_over_addition() {
// 1 + 2 * 3 should parse as 1 + (2 * 3)
let stmts = parse_ok("1 + 2 * 3;");
match &expression_of(&stmts[0]).node {
Expr::Binary {
operator, right, ..
} => {
assert_eq!(*operator, TokenType::Plus);
match &right.node {
Expr::Binary { operator, .. } => {
assert_eq!(*operator, TokenType::Star)
}
other => panic!("expected nested binary, got {:?}", other),
}
}
other => panic!("expected binary expression, got {:?}", other),
}
}
#[test]
fn parses_unary_negation() {
let stmts = parse_ok("-5;");
match &expression_of(&stmts[0]).node {
Expr::Unary { operator, .. } => assert_eq!(*operator, TokenType::Minus),
other => panic!("expected unary expression, got {:?}", other),
}
}
#[test]
fn parses_grouping_to_change_precedence() {
// (1 + 2) * 3 should have a grouping on the left of the multiply.
let stmts = parse_ok("(1 + 2) * 3;");
match &expression_of(&stmts[0]).node {
Expr::Binary { operator, left, .. } => {
assert_eq!(*operator, TokenType::Star);
assert!(matches!(left.node, Expr::Grouping { .. }));
}
other => panic!("expected binary expression, got {:?}", other),
}
}
#[test]
fn parses_comparison_expression() {
let stmts = parse_ok("1 < 2;");
match &expression_of(&stmts[0]).node {
Expr::Binary { operator, .. } => assert_eq!(*operator, TokenType::Less),
other => panic!("expected binary expression, got {:?}", other),
}
}
#[test]
fn parses_print_statement() {
let stmts = parse_ok("print 1;");
assert!(matches!(stmts[0].node, Stmt::Print { .. }));
}
#[test]
fn parses_var_declaration_with_initializer() {
let stmts = parse_ok("var x: Int = 5;");
match &stmts[0].node {
Stmt::VarDeclaration {
name, initializer, ..
} => {
assert_eq!(name, "x");
assert!(initializer.is_some());
}
other => panic!("expected var declaration, got {:?}", other),
}
}
#[test]
fn parses_assignment_statement() {
let stmts = parse_ok("x = 5;");
match &stmts[0].node {
Stmt::VarAssigment { name, .. } => assert_eq!(name, "x"),
other => panic!("expected assignment statement, got {:?}", other),
}
}
#[test]
fn parses_block_statement() {
let stmts = parse_ok("do print 1; print 2; end");
match &stmts[0].node {
Stmt::Block { statements, .. } => assert_eq!(statements.len(), 2),
other => panic!("expected block statement, got {:?}", other),
}
}
#[test]
fn parses_if_statement() {
let stmts = parse_ok("if true then print 1;");
assert!(matches!(stmts[0].node, Stmt::If { .. }));
}
#[test]
fn parses_while_statement() {
let stmts = parse_ok("while true do print 1; end");
assert!(matches!(stmts[0].node, Stmt::While { .. }));
}
#[test]
fn errors_on_missing_semicolon_after_print() {
assert!(parse_source("print 1").is_err());
}
#[test]
fn errors_on_unclosed_grouping() {
assert!(parse_source("(1 + 2;").is_err());
}
}
Regular → Executable
View File
Regular → Executable
View File
+204
View File
@@ -0,0 +1,204 @@
use crate::{
backend::environment::EnvironmentStack,
common::{
ast::{AstNode, AstNodeKind, Expr, Stmt},
lox_result::{runtime_error, LoxError, LoxResult},
},
frontend::source_registry::SourceSlice,
};
use std::fmt::{Debug, Display};
struct Resolver {
scopes: EnvironmentStack<bool>,
}
impl Resolver {
pub fn new() -> Self {
Resolver {
scopes: EnvironmentStack::new(),
}
}
fn declare(&mut self, name: &String) {
if self.scopes.is_empty() {
return;
}
let _ = self.scopes.set(name.clone(), false);
}
fn define(&mut self, name: &String) {
if self.scopes.is_empty() {
return;
}
let _ = self.scopes.set(name.clone(), true);
}
}
pub trait StaticAnalyzer<T> {
fn resolve(&mut self, node: &T) -> LoxResult<bool>;
}
impl<R: AstNodeKind + Clone + Debug + Display> StaticAnalyzer<AstNode<R>> for Resolver
where
Resolver: StaticAnalyzer<R>,
{
fn resolve(&mut self, node: &AstNode<R>) -> LoxResult<bool> {
self.resolve(&node.node)
}
}
impl StaticAnalyzer<Stmt> for Resolver {
fn resolve(&mut self, node: &Stmt) -> LoxResult<bool> {
match node {
Stmt::Expression { expression, .. } => {
// Visit the expression
self.resolve(expression.as_ref())
}
Stmt::Print { expression, .. } => {
// Visit the expression
self.resolve(expression.as_ref())
}
Stmt::VarDeclaration {
initializer, name, ..
} => {
self.declare(name);
// Visit the initializer if present
if let Some(init) = initializer {
self.resolve(init.as_ref())?;
}
self.define(name);
Ok(true)
}
Stmt::VarAssigment { value, name, .. } => {
match self.scopes.get(name) {
Ok(true) => (),
Ok(false) => {
return Err(LoxError::RuntimeError {
source_slice: SourceSlice::default(),
message: "Cant read loac variable in it own lintilizer".to_string(),
})
}
Err(err) => return Err(err),
}
self.resolve(value.as_ref())
}
Stmt::Return { expression, .. } => {
// Visit the return expression
self.resolve(expression.as_ref())
}
Stmt::Block { statements, .. } => {
self.scopes.push_new_scope();
// Visit all statements in the block
for stmt in statements.iter() {
self.resolve(stmt)?;
}
self.scopes.pop_scope();
Ok(true)
}
Stmt::If {
condition,
then_branch,
elif_branch,
else_branch,
..
} => {
// Visit the condition
self.resolve(condition.as_ref())?;
// Visit the then branch
self.resolve(then_branch.as_ref())?;
// Visit all elif branches
for (elif_condition, elif_stmt) in elif_branch.iter() {
self.resolve(elif_condition.as_ref())?;
self.resolve(elif_stmt.as_ref())?;
}
// Visit the else branch if present
if let Some(else_stmt) = else_branch {
self.resolve(else_stmt.as_ref())?;
}
Ok(true)
}
Stmt::While {
condition, body, ..
} => {
// Visit the condition
self.resolve(condition.as_ref())?;
// Visit the body
self.resolve(body.as_ref())?;
Ok(true)
}
Stmt::For {
variable,
condition,
increment,
body,
..
} => {
// Visit the variable initialization
self.resolve(variable.as_ref())?;
// Visit the condition
self.resolve(condition.as_ref())?;
// Visit the increment
self.resolve(increment.as_ref())?;
// Visit the body
self.resolve(body.as_ref())?;
Ok(true)
}
}
}
}
impl StaticAnalyzer<Expr> for Resolver {
fn resolve(&mut self, node: &Expr) -> LoxResult<bool> {
match node {
Expr::Literal { .. } => {
// Leaf node - no children to visit
Ok(true)
}
Expr::Binary { left, right, .. } => {
// Visit left operand
self.resolve(left.as_ref())?;
// Visit right operand
self.resolve(right.as_ref())
}
Expr::Unary { operand, .. } => {
// Visit the operand
self.resolve(operand.as_ref())
}
Expr::Grouping { expression } => {
// Visit the grouped expression
self.resolve(expression.as_ref())
}
Expr::Identifier { name, .. } => {
if !self.scopes.is_empty() && self.scopes.get(name).is_ok() {
return Err(LoxError::ParseError {
source_slice: SourceSlice::default(),
message: "Cant read local varialbe in it own initializer".to_string(),
});
}
Ok(true)
}
Expr::Call {
callee, arguments, ..
} => {
// Visit the callee
self.resolve(callee.as_ref())?;
// Visit all arguments
for arg in arguments.iter() {
self.resolve(arg)?;
}
Ok(true)
}
}
}
}
Regular → Executable
+1
View File
@@ -1,3 +1,4 @@
pub mod backend; pub mod backend;
pub mod common; pub mod common;
pub mod frontend; pub mod frontend;
pub mod middleend;
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
-115
View File
@@ -1,115 +0,0 @@
use crate::common::{
ast::{AstNode, Expr, Stmt},
lox_result::LoxResult,
};
// Enum per tutti i possibili target di operazioni
enum OperationTarget<'a> {
Stmt(&'a mut Stmt),
Expr(&'a mut Expr),
AstStmt(&'a mut AstNode<Stmt>),
AstExpr(&'a mut AstNode<Expr>),
}
// Trait per le operazioni
trait Operation: Send + Sync {
fn execute(&self, target: OperationTarget) -> LoxResult<()>;
}
// Operazione concreta per set_label
struct SetLabelOperation {
new_label: String,
}
impl SetLabelOperation {
fn new(label: String) -> Self {
Self { new_label: label }
}
}
impl Operation for SetLabelOperation {
fn execute(&self, target: OperationTarget) -> LoxResult<()> {
match target {
OperationTarget::Stmt(stmt) => {
if let Stmt::Block { label, .. } = stmt {
*label = self.new_label.clone();
}
}
OperationTarget::AstStmt(node) => {
if let Stmt::Block { label, .. } = &mut node.node {
*label = self.new_label.clone();
}
}
_ => {} // Expr non hanno label
}
Ok(())
}
}
// Trait per convertire tipi in OperationTarget
trait AsOperationTarget {
fn as_target(&mut self) -> OperationTarget;
}
impl AsOperationTarget for Stmt {
fn as_target(&mut self) -> OperationTarget {
OperationTarget::Stmt(self)
}
}
impl AsOperationTarget for Expr {
fn as_target(&mut self) -> OperationTarget {
OperationTarget::Expr(self)
}
}
impl AsOperationTarget for AstNode<Stmt> {
fn as_target(&mut self) -> OperationTarget {
OperationTarget::AstStmt(self)
}
}
impl AsOperationTarget for AstNode<Expr> {
fn as_target(&mut self) -> OperationTarget {
OperationTarget::AstExpr(self)
}
}
// Crawler generico con Command Pattern
struct Crawler<T: AsOperationTarget> {
node: T,
operations: Vec<Box<dyn Operation>>,
}
impl<T: AsOperationTarget> Crawler<T> {
fn new(node: T) -> Self {
Self {
node,
operations: vec![],
}
}
fn add_operation(&mut self, op: Box<dyn Operation>) {
self.operations.push(op);
}
fn execute_operations(&mut self) -> LoxResult<()> {
for op in &self.operations {
op.execute(self.node.as_target())?;
}
Ok(())
}
fn with_operation(mut self, op: Box<dyn Operation>) -> Self {
self.operations.push(op);
self
}
fn get_node(&self) -> &T {
&self.node
}
fn get_node_mut(&mut self) -> &mut T {
&mut self.node
}
}
Regular → Executable
+2 -1
View File
@@ -1 +1,2 @@
pub mod crawler; pub mod variable_resolution;
pub mod visit_ast;
+99
View File
@@ -0,0 +1,99 @@
use std::collections::HashMap;
use crate::{
backend::environment::EnvironmentStack,
common::{
ast::{AstNode, Expr, Stmt},
lox_result::{LoxError, LoxResult},
},
frontend::source_registry::SourceSlice,
middleend::visit_ast::{walk_expr, walk_stmt, Visitor},
};
struct Resolver {
scopes: EnvironmentStack<bool>,
locals: HashMap<SourceSlice, usize>,
}
impl Resolver {
pub fn new() -> Self {
Resolver {
scopes: EnvironmentStack::new(),
locals: HashMap::new(),
}
}
fn declare(&mut self, name: &String) {
if self.scopes.is_empty() {
return;
}
let _ = self.scopes.set(name.clone(), false);
}
fn define(&mut self, name: &String) {
if self.scopes.is_empty() {
return;
}
let _ = self.scopes.set(name.clone(), true);
}
fn resolve_local(&mut self, name: &String) {
let depth = self.scopes.depth();
for i in (0..depth).rev() {
if self.scopes.scope_contains(i, name) {
self.locals.insert(, i);
}
}
}
}
impl Visitor for Resolver {
fn visit_stmt(&mut self, stmt: &AstNode<Stmt>) -> LoxResult<()> {
match &stmt.node {
Stmt::VarDeclaration { name, .. } => {
self.declare(name);
// `walk_stmt` resolves the initializer (if present).
walk_stmt(self, stmt)?;
self.define(name);
Ok(())
}
Stmt::VarAssigment { name, .. } => {
match self.scopes.get(name) {
Ok(true) => (),
Ok(false) => {
return Err(LoxError::RuntimeError {
source_slice: SourceSlice::default(),
message: "Cant read local variable in it own lintilizer".to_string(),
})
}
Err(err) => return Err(err),
}
// `walk_stmt` resolves the assigned value.
walk_stmt(self, stmt)
}
Stmt::Block { .. } => {
self.scopes.push_new_scope();
walk_stmt(self, stmt)?;
self.scopes.pop_scope();
Ok(())
}
// Expression, Print, Return, If, While, For: default traversal.
_ => walk_stmt(self, stmt),
}
}
fn visit_expr(&mut self, expr: &AstNode<Expr>) -> LoxResult<()> {
match &expr.node {
Expr::Identifier { name, .. } => {
if !self.scopes.is_empty() && self.scopes.get(name).is_ok() {
return Err(LoxError::ParseError {
source_slice: SourceSlice::default(),
message: "Cant read local varialbe in it own initializer".to_string(),
});
}
Ok(())
}
_ => walk_expr(self, expr),
}
}
}
+248
View File
@@ -0,0 +1,248 @@
//! A reusable, read-only AST visitor with default traversal.
//!
//! The traversal is split into two layers so that many different static
//! analyses can share a single definition of "how to walk the tree":
//!
//! * The `walk_*` free functions contain the canonical recursion. For each
//! node they call back into the visitor on every child. This is the only
//! place that needs to know the shape of the AST.
//! * The [`Visitor`] trait's `visit_*` methods are the overridable hooks. Each
//! one defaults to calling the matching `walk_*` function, so a visitor that
//! overrides nothing still performs a complete traversal.
//!
//! To implement an analysis, implement [`Visitor`] and override only the hooks
//! you care about. Inside an override, call the corresponding `walk_*` function
//! whenever you want the default "descend into children" behaviour to happen.
//! Accumulate results in your own struct fields (errors, scope tables, type
//! information, ...) rather than through the return value, which is only used
//! to short-circuit on error.
//!
//! # Example
//!
//! ```ignore
//! struct IdentifierCounter { count: usize }
//!
//! impl Visitor for IdentifierCounter {
//! fn visit_expr(&mut self, expr: &AstNode<Expr>) -> LoxResult<()> {
//! if let Expr::Identifier { .. } = &expr.node {
//! self.count += 1;
//! }
//! walk_expr(self, expr) // keep descending into children
//! }
//! }
//! ```
use crate::common::{
ast::{AstNode, Expr, Stmt},
base_value::{BaseValue, LoxFunction},
lox_result::LoxResult,
};
/// A read-only visitor over the AST.
///
/// Every hook has a default implementation that performs the standard
/// recursive traversal, so implementors only override the cases they need.
pub trait Visitor: Sized {
/// Visit a statement node. Defaults to [`walk_stmt`].
fn visit_stmt(&mut self, stmt: &AstNode<Stmt>) -> LoxResult<()> {
walk_stmt(self, stmt)
}
/// Visit an expression node. Defaults to [`walk_expr`].
fn visit_expr(&mut self, expr: &AstNode<Expr>) -> LoxResult<()> {
walk_expr(self, expr)
}
/// Visit a function literal (parameters, optional guard and body).
///
/// Defaults to [`walk_function`], which walks the guard (if any) and the
/// body. Override this to manage a parameter scope before descending.
fn visit_function(&mut self, function: &LoxFunction) -> LoxResult<()> {
walk_function(self, function)
}
}
/// Recurse into the children of `stmt`, calling back into `visitor`.
pub fn walk_stmt<V: Visitor>(visitor: &mut V, stmt: &AstNode<Stmt>) -> LoxResult<()> {
match &stmt.node {
Stmt::Expression { expression, .. } => visitor.visit_expr(expression),
Stmt::Print { expression, .. } => visitor.visit_expr(expression),
Stmt::VarDeclaration { initializer, .. } => {
if let Some(initializer) = initializer {
visitor.visit_expr(initializer)?;
}
Ok(())
}
Stmt::VarAssigment { value, .. } => visitor.visit_expr(value),
Stmt::Return { expression, .. } => visitor.visit_expr(expression),
Stmt::Block { statements, .. } => {
for statement in statements.iter() {
visitor.visit_stmt(statement)?;
}
Ok(())
}
Stmt::If {
condition,
then_branch,
elif_branch,
else_branch,
..
} => {
visitor.visit_expr(condition)?;
visitor.visit_stmt(then_branch)?;
for (elif_condition, elif_body) in elif_branch.iter() {
visitor.visit_expr(elif_condition)?;
visitor.visit_stmt(elif_body)?;
}
if let Some(else_body) = else_branch {
visitor.visit_stmt(else_body)?;
}
Ok(())
}
Stmt::While {
condition, body, ..
} => {
visitor.visit_expr(condition)?;
visitor.visit_stmt(body)
}
Stmt::For {
variable,
condition,
increment,
body,
..
} => {
visitor.visit_stmt(variable)?;
visitor.visit_expr(condition)?;
visitor.visit_stmt(increment)?;
visitor.visit_stmt(body)
}
}
}
/// Recurse into the children of `expr`, calling back into `visitor`.
pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &AstNode<Expr>) -> LoxResult<()> {
match &expr.node {
// A function literal carries an entire sub-tree (its body), so it is
// not a leaf: hand it to the dedicated function hook.
Expr::Literal { value } => {
if let BaseValue::Function(function) = value {
visitor.visit_function(function)?;
}
Ok(())
}
Expr::Identifier { .. } => Ok(()),
Expr::Binary { left, right, .. } => {
visitor.visit_expr(left)?;
visitor.visit_expr(right)
}
Expr::Unary { operand, .. } => visitor.visit_expr(operand),
Expr::Grouping { expression } => visitor.visit_expr(expression),
Expr::Call {
callee, arguments, ..
} => {
visitor.visit_expr(callee)?;
for argument in arguments.iter() {
visitor.visit_expr(argument)?;
}
Ok(())
}
}
}
/// Walk the guard (if present) and body of a function literal.
pub fn walk_function<V: Visitor>(visitor: &mut V, function: &LoxFunction) -> LoxResult<()> {
if let Some(guard) = &function.guard {
visitor.visit_expr(guard)?;
}
visitor.visit_stmt(&function.body)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::lox_result::runtime_error;
use crate::frontend::lexer::Lexer;
use crate::frontend::parser::Parser;
fn parse(src: &str) -> Vec<AstNode<Stmt>> {
let tokens = Lexer::new(src.to_string(), 0)
.scans_tokens()
.expect("source should lex");
Parser::new(tokens).parse().expect("source should parse")
}
/// A visitor that relies entirely on the default traversal and just counts
/// how many statement and expression nodes it sees.
#[derive(Default)]
struct Counter {
stmts: usize,
exprs: usize,
}
impl Visitor for Counter {
fn visit_stmt(&mut self, stmt: &AstNode<Stmt>) -> LoxResult<()> {
self.stmts += 1;
walk_stmt(self, stmt)
}
fn visit_expr(&mut self, expr: &AstNode<Expr>) -> LoxResult<()> {
self.exprs += 1;
walk_expr(self, expr)
}
}
fn count(src: &str) -> Counter {
let mut counter = Counter::default();
for stmt in parse(src).iter() {
counter.visit_stmt(stmt).unwrap();
}
counter
}
#[test]
fn counts_every_node_via_default_traversal() {
// 1 + 2 * 3 => Binary(+){ Literal, Binary(*){ Literal, Literal } }
let counter = count("1 + 2 * 3;");
assert_eq!(counter.stmts, 1);
assert_eq!(counter.exprs, 5);
}
#[test]
fn descends_into_function_bodies() {
// The function body must be traversed through `visit_function`, so the
// `return a;` inside it should contribute to the counts.
let counter = count("f :: fn (a) do return a; end");
// VarDeclaration + Block + Return
assert_eq!(counter.stmts, 3);
// Function literal + identifier `a`
assert_eq!(counter.exprs, 2);
}
/// A visitor that aborts as soon as it sees an identifier, used to check
/// that errors short-circuit the traversal.
struct FailOnIdentifier;
impl Visitor for FailOnIdentifier {
fn visit_expr(&mut self, expr: &AstNode<Expr>) -> LoxResult<()> {
if let Expr::Identifier { .. } = &expr.node {
return runtime_error(expr.source_slice.clone(), "found an identifier");
}
walk_expr(self, expr)
}
}
#[test]
fn errors_propagate_through_traversal() {
let stmts = parse("var x: Int = 1; x;");
let mut visitor = FailOnIdentifier;
let mut result = Ok(());
for stmt in stmts.iter() {
result = visitor.visit_stmt(stmt);
if result.is_err() {
break;
}
}
assert!(result.is_err());
}
}
Regular → Executable
View File
Regular → Executable
View File