#![allow(missing_docs)]
use std::fs;
use std::rc::Rc;
use tempfile::TempDir;
#[test]
fn test_parser_060_01_file_resolution_simple_module() {
let temp_dir = TempDir::new().unwrap();
let utils_path = temp_dir.path().join("utils.ruchy");
fs::write(&utils_path, "fun helper() { 42 }").unwrap();
let module_path = "utils";
let resolved = resolve_module_path(module_path, temp_dir.path()).unwrap();
assert_eq!(resolved, utils_path);
}
#[test]
fn test_parser_060_01_file_resolution_nested_module() {
let temp_dir = TempDir::new().unwrap();
let foo_dir = temp_dir.path().join("foo");
fs::create_dir(&foo_dir).unwrap();
let bar_path = foo_dir.join("bar.ruchy");
fs::write(&bar_path, "fun baz() { 100 }").unwrap();
let module_path = "foo::bar";
let resolved = resolve_module_path(module_path, temp_dir.path()).unwrap();
assert_eq!(resolved, bar_path);
}
#[test]
fn test_parser_060_01_file_resolution_deeply_nested() {
let temp_dir = TempDir::new().unwrap();
let a_dir = temp_dir.path().join("a");
let b_dir = a_dir.join("b");
fs::create_dir_all(&b_dir).unwrap();
let c_path = b_dir.join("c.ruchy");
fs::write(&c_path, "fun deep() { 999 }").unwrap();
let module_path = "a::b::c";
let resolved = resolve_module_path(module_path, temp_dir.path()).unwrap();
assert_eq!(resolved, c_path);
}
#[test]
fn test_parser_060_01_file_resolution_missing_file() {
let temp_dir = TempDir::new().unwrap();
let module_path = "nonexistent";
let result =
std::panic::catch_unwind(|| resolve_module_path(module_path, temp_dir.path()).unwrap());
assert!(
result.is_err(),
"Should fail when module file doesn't exist"
);
}
#[test]
fn test_parser_060_01_file_resolution_dot_notation() {
let temp_dir = TempDir::new().unwrap();
let foo_dir = temp_dir.path().join("foo");
fs::create_dir(&foo_dir).unwrap();
let bar_path = foo_dir.join("bar.ruchy");
fs::write(&bar_path, "fun test() { 1 }").unwrap();
let module_path = "foo.bar"; let resolved = resolve_module_path_dot_notation(module_path, temp_dir.path()).unwrap();
assert_eq!(resolved, bar_path);
}
#[test]
fn test_parser_060_02_loading_simple_module() {
let temp_dir = TempDir::new().unwrap();
let module_path = temp_dir.path().join("math.ruchy");
fs::write(&module_path, "fun add(x, y) { x + y }").unwrap();
let loaded_module = load_module(&module_path).unwrap();
assert!(loaded_module.is_loaded());
assert_eq!(loaded_module.path(), &module_path);
}
#[test]
fn test_parser_060_02_loading_module_with_multiple_functions() {
let temp_dir = TempDir::new().unwrap();
let module_path = temp_dir.path().join("utils.ruchy");
fs::write(
&module_path,
r"
fun add(x, y) { x + y }
fun sub(x, y) { x - y }
fun mul(x, y) { x * y }
",
)
.unwrap();
let loaded_module = load_module(&module_path).unwrap();
let symbols = loaded_module.symbols();
assert_eq!(symbols.len(), 3);
assert!(symbols.contains_key("add"));
assert!(symbols.contains_key("sub"));
assert!(symbols.contains_key("mul"));
}
#[test]
fn test_parser_060_02_loading_module_parse_error() {
let temp_dir = TempDir::new().unwrap();
let module_path = temp_dir.path().join("broken.ruchy");
fs::write(&module_path, "fun broken( { incomplete syntax").unwrap();
let result = load_module(&module_path);
assert!(result.is_err(), "Should return error for invalid syntax");
}
#[test]
fn test_parser_060_03_extract_function_symbols() {
let temp_dir = TempDir::new().unwrap();
let module_path = temp_dir.path().join("funcs.ruchy");
fs::write(
&module_path,
r"
fun public_func() { 1 }
fun helper_func(x) { x * 2 }
",
)
.unwrap();
let loaded_module = load_module(&module_path).unwrap();
let functions = extract_functions(&loaded_module);
assert_eq!(functions.len(), 2);
assert!(functions.iter().any(|f| f.name == "public_func"));
assert!(functions.iter().any(|f| f.name == "helper_func"));
}
#[test]
fn test_parser_060_03_extract_struct_symbols() {
let temp_dir = TempDir::new().unwrap();
let module_path = temp_dir.path().join("structs.ruchy");
fs::write(
&module_path,
r"
struct Point { x: i32, y: i32 }
struct Circle { center: Point, radius: f64 }
",
)
.unwrap();
let loaded_module = load_module(&module_path).unwrap();
let structs = extract_structs(&loaded_module);
assert_eq!(structs.len(), 2);
assert!(structs.iter().any(|s| s.name == "Point"));
assert!(structs.iter().any(|s| s.name == "Circle"));
}
#[test]
fn test_parser_060_03_extract_const_symbols() {
let temp_dir = TempDir::new().unwrap();
let module_path = temp_dir.path().join("consts.ruchy");
fs::write(
&module_path,
r"
const PI = 3.14159
const MAX_SIZE = 1000
",
)
.unwrap();
let loaded_module = load_module(&module_path).unwrap();
let consts = extract_consts(&loaded_module);
assert_eq!(consts.len(), 2);
assert!(consts.iter().any(|c| c.name == "PI"));
assert!(consts.iter().any(|c| c.name == "MAX_SIZE"));
}
#[test]
fn test_parser_060_03_extract_mixed_symbols() {
let temp_dir = TempDir::new().unwrap();
let module_path = temp_dir.path().join("mixed.ruchy");
fs::write(
&module_path,
r#"
const VERSION = "1.0"
struct Config { debug: bool }
fun init() { Config { debug: true } }
"#,
)
.unwrap();
let loaded_module = load_module(&module_path).unwrap();
let all_symbols = extract_all_symbols(&loaded_module);
assert_eq!(all_symbols.functions.len(), 1);
assert_eq!(all_symbols.structs.len(), 1);
assert_eq!(all_symbols.consts.len(), 1);
}
#[test]
fn test_parser_060_04_import_simple_function() {
let temp_dir = TempDir::new().unwrap();
let utils_path = temp_dir.path().join("utils.ruchy");
fs::write(&utils_path, "fun helper() { 42 }").unwrap();
let code = "use utils::helper\nhelper()";
let result = execute_with_imports(code, temp_dir.path()).unwrap();
assert_eq!(result, Value::Integer(42));
}
#[test]
#[ignore = "RED phase: multi-function imports not yet implemented - PARSER-060"]
fn test_parser_060_04_import_multiple_functions() {
let temp_dir = TempDir::new().unwrap();
let utils_path = temp_dir.path().join("utils.ruchy");
fs::write(
&utils_path,
r"
fun add(x, y) { x + y }
fun sub(x, y) { x - y }
",
)
.unwrap();
let code = "use utils::{add, sub}\nadd(10, 5) + sub(10, 5)";
let result = execute_with_imports(code, temp_dir.path()).unwrap();
assert_eq!(result, Value::Integer(20)); }
#[test]
fn test_parser_060_04_import_struct_constructor() {
let temp_dir = TempDir::new().unwrap();
let types_path = temp_dir.path().join("types.ruchy");
fs::write(&types_path, "struct Point { x: i32, y: i32 }").unwrap();
let code = "use types::Point\nPoint { x: 10, y: 20 }.x";
let result = execute_with_imports(code, temp_dir.path()).unwrap();
assert_eq!(result, Value::Integer(10));
}
#[test]
fn test_parser_060_04_import_const_value() {
let temp_dir = TempDir::new().unwrap();
let config_path = temp_dir.path().join("config.ruchy");
fs::write(&config_path, "const VERSION = \"1.0\"").unwrap();
let code = "use config::VERSION\nVERSION";
let result = execute_with_imports(code, temp_dir.path()).unwrap();
assert_eq!(result, Value::from_string("1.0".to_string()));
}
#[test]
fn test_parser_060_04_import_from_nested_module() {
let temp_dir = TempDir::new().unwrap();
let foo_dir = temp_dir.path().join("foo");
fs::create_dir(&foo_dir).unwrap();
let bar_path = foo_dir.join("bar.ruchy");
fs::write(&bar_path, "fun baz() { 100 }").unwrap();
let code = "use foo::bar::baz\nbaz()";
let result = execute_with_imports(code, temp_dir.path()).unwrap();
assert_eq!(result, Value::Integer(100));
}
#[test]
fn test_parser_060_04_import_nonexistent_symbol() {
let temp_dir = TempDir::new().unwrap();
let utils_path = temp_dir.path().join("utils.ruchy");
fs::write(&utils_path, "fun helper() { 1 }").unwrap();
let code = "use utils::missing\nmissing()";
let result = execute_with_imports(code, temp_dir.path());
assert!(
result.is_err(),
"Should fail when importing nonexistent symbol"
);
let err_msg = format!("{:?}", result.unwrap_err());
assert!(err_msg.contains("not found") || err_msg.contains("missing"));
}
#[test]
fn test_parser_060_05_cache_module_on_first_load() {
let temp_dir = TempDir::new().unwrap();
let module_path = temp_dir.path().join("cached.ruchy");
fs::write(&module_path, "fun test() { 1 }").unwrap();
let cache = ModuleCache::new();
let module1 = cache.load(&module_path).unwrap();
let module2 = cache.load(&module_path).unwrap();
assert!(
Rc::ptr_eq(&module1, &module2),
"Should return same cached instance"
);
}
#[test]
fn test_parser_060_05_cache_multiple_modules() {
let temp_dir = TempDir::new().unwrap();
let mod1_path = temp_dir.path().join("mod1.ruchy");
fs::write(&mod1_path, "fun test1() { 1 }").unwrap();
let mod2_path = temp_dir.path().join("mod2.ruchy");
fs::write(&mod2_path, "fun test2() { 2 }").unwrap();
let cache = ModuleCache::new();
let module1 = cache.load(&mod1_path).unwrap();
let module2 = cache.load(&mod2_path).unwrap();
assert!(
!Rc::ptr_eq(&module1, &module2),
"Different modules should be different instances"
);
}
use ruchy::frontend::ast::{Expr, ExprKind};
use ruchy::frontend::parser::Parser;
use ruchy::runtime::module_loader::{
extract_all_symbols, extract_consts, extract_functions, extract_structs, load_module,
resolve_module_path, resolve_module_path_dot_notation, ModuleCache,
};
use ruchy::runtime::{Interpreter, Value};
fn execute_with_imports(code: &str, base: &std::path::Path) -> Result<Value, String> {
let ast = Parser::new(code)
.parse()
.map_err(|e| format!("Parse error: {e:?}"))?;
let cache = ModuleCache::new();
let mut interpreter = Interpreter::new();
let exprs: Vec<&Expr> = match &ast.kind {
ExprKind::Block(exprs) => exprs.iter().collect(),
_ => vec![&ast],
};
let mut non_import_exprs: Vec<Expr> = Vec::new();
for expr in &exprs {
if let ExprKind::Import { module, items } = &expr.kind {
let (module_file, symbol_names): (String, Vec<String>) = if let Some(item_names) = items
{
(module.clone(), item_names.clone())
} else {
let parts: Vec<&str> = module.split("::").collect();
if parts.len() == 1 {
(module.clone(), vec![])
} else {
let module_part = parts[..parts.len() - 1].join("::");
let symbol_part = parts[parts.len() - 1].to_string();
(module_part, vec![symbol_part])
}
};
let module_path = resolve_module_path(&module_file, base).map_err(|e| e.to_string())?;
let loaded_module = cache.load(&module_path).map_err(|e| e.to_string())?;
interpreter
.eval_expr(loaded_module.ast())
.map_err(|e| format!("Failed to load module: {e}"))?;
if !symbol_names.is_empty() {
for item_name in &symbol_names {
loaded_module.get_symbol(item_name).ok_or_else(|| {
format!("Symbol '{item_name}' not found in module '{module_file}'")
})?;
}
}
} else {
non_import_exprs.push((*expr).clone());
}
}
if non_import_exprs.is_empty() {
Ok(Value::Nil)
} else if non_import_exprs.len() == 1 {
interpreter
.eval_expr(&non_import_exprs[0])
.map_err(|e| format!("Execution error: {e}"))
} else {
let block_expr = Expr {
kind: ExprKind::Block(non_import_exprs),
span: ast.span,
attributes: Vec::new(),
leading_comments: Vec::new(),
trailing_comment: None,
};
interpreter
.eval_expr(&block_expr)
.map_err(|e| format!("Execution error: {e}"))
}
}