70 lines
2.2 KiB
Rust
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"));
|
||
|
|
}
|