Files
rlox/tests/resolution.rs
T
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

70 lines
2.2 KiB
Rust

//! 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 () do
count := 10;
get :: fn () do
return count;
end
return get;
end
g := make();
g();
";
assert_eq!(run(src).unwrap().to_string(), "10");
}
#[test]
fn use_before_initializer_is_rejected() {
let err = run("do x := x; end").expect_err("should be a resolution error");
assert!(err.contains("its own initializer"));
}
#[test]
fn top_level_return_is_rejected() {
let err = run("return 1;").expect_err("should be a resolution error");
assert!(err.contains("top-level"));
}
#[test]
fn duplicate_declaration_in_block_is_rejected() {
let err = run("do x := 1; x := 2; end").expect_err("should be a resolution error");
assert!(err.contains("Already a variable"));
}