//! 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 { 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")); }