use crate::compiler::BytecodeCompiler;
use crate::executor::VirtualMachine;
use crate::{VMConfig, VMError};
use shape_ast::parser::parse_program;
use shape_value::{ValueWord, ValueWordExt};
fn compile_and_execute(source: &str) -> Result<ValueWord, VMError> {
let program =
parse_program(source).map_err(|e| VMError::RuntimeError(format!("Parse: {:?}", e)))?;
let mut compiler = BytecodeCompiler::new();
compiler.set_source(source);
let bytecode = compiler
.compile(&program)
.map_err(|e| VMError::RuntimeError(format!("Compile: {:?}", e)))?;
let mut vm = VirtualMachine::new(VMConfig::default());
vm.load_program(bytecode);
vm.execute(None).map(|nb| nb.clone())
}
fn assert_result_number(code: &str, expected: f64) {
match compile_and_execute(code) {
Ok(result) => {
let n = result.to_number().expect("Expected number result");
assert!(
(n - expected).abs() < f64::EPSILON,
"Expected {}, got {}",
expected,
n
);
}
Err(e) => panic!("Expected result {}, got error: {:?}", expected, e),
}
}
fn assert_compiles(source: &str) {
let program = parse_program(source).expect("Parse failed");
let mut compiler = BytecodeCompiler::new();
compiler.set_source(source);
compiler.compile(&program).expect("Compile failed");
}
#[test]
fn test_module_exec_nested_module_access() {
let code = r#"
mod outer {
mod inner {
fn value() { 42; }
}
}
outer::inner::value();
"#;
assert_result_number(code, 42.0);
}
#[test]
fn test_module_exec_nested_module_function_resolution() {
let code = r#"
mod outer {
fn outer_fn() { 1; }
mod inner {
fn inner_fn() { 2; }
}
}
outer::outer_fn() + outer::inner::inner_fn();
"#;
assert_result_number(code, 3.0);
}
#[test]
fn test_module_exec_module_deeply_nested_access() {
let code = r#"
mod a {
mod b {
mod c {
fn deep() { 99; }
}
}
}
a::b::c::deep();
"#;
assert_result_number(code, 99.0);
}
#[test]
fn test_module_exec_module_does_not_leak_into_global() {
let code = r#"
mod secret {
fn hidden() { 42; }
}
hidden();
"#;
let result = compile_and_execute(code);
assert!(
result.is_err(),
"Module-scoped function should not be globally accessible"
);
}
#[test]
fn test_module_exec_module_function_cannot_access_outer_locals() {
let code = r#"
let outer_val = 99;
mod m {
fn get_outer() { outer_val; }
}
m::get_outer();
"#;
let _result = compile_and_execute(code);
}
#[test]
fn test_module_exec_empty_module_access_fails() {
let code = r#"
mod empty { }
empty::nonexistent();
"#;
let result = compile_and_execute(code);
assert!(
result.is_err(),
"accessing nonexistent member of empty module should fail"
);
}
#[test]
fn test_module_exec_module_function_with_closure() {
let code = r#"
mod util {
fn apply(f, x) { f(x); }
}
util::apply(|x| x * 2, 21);
"#;
assert_result_number(code, 42.0);
}
#[test]
fn test_module_exec_module_function_recursion() {
let code = r#"
mod fib {
fn compute(n) {
if n <= 1 { return n }
return compute(n - 1) + compute(n - 2)
}
}
fib::compute(10);
"#;
assert_result_number(code, 55.0);
}
#[test]
fn test_module_exec_module_function_with_default_like_pattern() {
let code = r#"
mod config {
fn get_value(key) {
if key == "port" { return 8080 }
return 0
}
}
config::get_value("port");
"#;
assert_result_number(code, 8080.0);
}
#[test]
fn test_module_exec_module_function_chaining() {
let code = r#"
mod pipeline {
fn step1(x) { x + 1; }
fn step2(x) { x * 2; }
fn step3(x) { x - 3; }
}
pipeline::step3(pipeline::step2(pipeline::step1(5)));
"#;
assert_result_number(code, 9.0);
}
#[test]
fn test_module_exec_two_modules_calling_each_other_functions() {
let code = r#"
mod converter {
fn to_celsius(f) { (f - 32) * 5 / 9; }
}
mod formatter {
fn format_temp(c) { c; }
}
formatter::format_temp(converter::to_celsius(212));
"#;
assert_result_number(code, 100.0);
}
#[test]
fn test_module_exec_module_with_match_expression() {
let code = r#"
mod evaluator {
fn eval(op, a, b) {
match op {
"add" => a + b,
"sub" => a - b,
"mul" => a * b,
_ => 0,
};
}
}
evaluator::eval("mul", 6, 7);
"#;
assert_result_number(code, 42.0);
}
#[test]
fn test_module_exec_pub_fn_with_generic_type_compiles() {
assert_compiles(
r#"
pub fn identity<T>(x: T) -> T { x; }
"#,
);
}