af8fb5cb7f
Introduced `scope_depth` to the `Binder` to track block nesting. This allows for correctly determining whether a `def` binding should be `Address::Global` (at scope depth 0) or `Address::Local`. The `type_checker` was also updated to handle `Program` and `Block` nodes by creating a new `TypeContext` for them. Removed the `wrap_as_lambda` method from `Environment` as the compiler now always produces a `Program` node, effectively handling the wrapping at the AST level. Correspondingly, `instantiate` and `run_script_compiled` were simplified to directly run the `Program` node.
93 lines
2.6 KiB
Rust
93 lines
2.6 KiB
Rust
use myc::ast::environment::Environment;
|
|
use myc::ast::types::Value;
|
|
|
|
#[test]
|
|
fn test_closure_modification_from_source() {
|
|
let source = r#"
|
|
(do
|
|
(def x 10)
|
|
(def f (fn [] (assign x 20)))
|
|
(f)
|
|
x
|
|
)
|
|
"#;
|
|
|
|
let env = Environment::new();
|
|
let compiled = env
|
|
.compile(source)
|
|
.into_result()
|
|
.expect("Failed to compile");
|
|
let linked = env.link(compiled);
|
|
let result = env.create_vm().run(&linked);
|
|
|
|
match result {
|
|
Ok(Value::Int(20)) => (),
|
|
Ok(val) => panic!("Expected Int(20), got {:?}", val),
|
|
Err(e) => panic!("VM Error: {}", e),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_closure_reassignment_optimization_bug() {
|
|
let env = Environment::new();
|
|
// Regression: optimizer inlined a function whose parameters were assigned to
|
|
// by an inner lambda. Such functions must NOT be inlined.
|
|
let source = "(do (def f (fn [[x y]] (fn [] (assign x (+ x y))))) ((f [1 2])))";
|
|
let res = env.run_script(source);
|
|
assert_eq!(format!("{}", res.unwrap()), "3");
|
|
}
|
|
|
|
#[test]
|
|
fn test_optimizer_upvalue_inlining_bug_repro() {
|
|
let env = Environment::new();
|
|
let source = r#"
|
|
(do
|
|
(def make-counter (fn [init]
|
|
(do
|
|
(def val init)
|
|
{
|
|
:inc (fn [] (assign val (+ val 1)))
|
|
:get (fn [] val)
|
|
})))
|
|
(def c (make-counter 10))
|
|
((.inc c))
|
|
((.get c)))
|
|
"#;
|
|
let res = env.run_script(source);
|
|
assert!(res.is_ok(), "Optimizer bug triggered: {:?}", res.err());
|
|
assert_eq!(format!("{}", res.unwrap()), "11");
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "'again' is only allowed in tail position to avoid dead code.")]
|
|
fn test_again_non_tail_panic() {
|
|
let env = Environment::new();
|
|
let source = "(do (def f (fn [x] (do (again (- x 1)) x))) (f 5))";
|
|
let _ = env.run_script(source);
|
|
}
|
|
|
|
#[test]
|
|
fn test_debug_mode_logging() {
|
|
let mut env = Environment::new();
|
|
env.optimization = false;
|
|
let source = "(+ 10 20)";
|
|
let result = env.run_debug(source).expect("Failed to run debug");
|
|
|
|
let (val, logs) = result;
|
|
|
|
match val {
|
|
Ok(Value::Int(30)) => (),
|
|
_ => panic!("Expected Int(30), got {:?}", val),
|
|
}
|
|
|
|
assert!(!logs.is_empty(), "Logs should not be empty");
|
|
|
|
let has_call = logs.iter().any(|l| l.contains("CALL"));
|
|
let has_const = logs.iter().any(|l| l.contains("CONST(10)"));
|
|
let has_result = logs.iter().any(|l| l.contains("} -> 30"));
|
|
|
|
assert!(has_call, "Logs should contain CALL");
|
|
assert!(has_const, "Logs should contain CONST(10)");
|
|
assert!(has_result, "Logs should contain result 30");
|
|
}
|