From e356c208a62e9069399a05738d00f9a9ffe40b35 Mon Sep 17 00:00:00 2001 From: 0264408 Date: Wed, 17 Jun 2026 15:57:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=BC=95=E5=85=A5=E5=8A=9F=E8=83=BD=E4=BB=A5=E5=8F=8A=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/math_mod.ast | 2 + examples/use_math.ast | 4 + src/interpreter/builtins/core.rs | 10 +-- src/interpreter/builtins/io.rs | 6 +- src/interpreter/builtins/mod.rs | 3 + src/interpreter/builtins/os.rs | 4 +- src/interpreter/builtins/string.rs | 20 ++--- src/interpreter/eval.rs | 44 ++++----- src/interpreter/interpreter.rs | 35 +++++++- src/interpreter/mod.rs | 3 +- src/interpreter/module.rs | 140 +++++++++++++++++++++++++++++ src/interpreter/tests.rs | 107 ++++++++++++++++++++++ src/main.rs | 11 ++- tests/fixtures/broken_syntax.ast | 3 + tests/fixtures/greet.ast | 2 + tests/fixtures/math.ast | 3 + tests/fixtures/sub/mod.ast | 1 + 17 files changed, 349 insertions(+), 49 deletions(-) create mode 100644 examples/math_mod.ast create mode 100644 examples/use_math.ast create mode 100644 src/interpreter/module.rs create mode 100644 tests/fixtures/broken_syntax.ast create mode 100644 tests/fixtures/greet.ast create mode 100644 tests/fixtures/math.ast create mode 100644 tests/fixtures/sub/mod.ast diff --git a/examples/math_mod.ast b/examples/math_mod.ast new file mode 100644 index 0000000..525196a --- /dev/null +++ b/examples/math_mod.ast @@ -0,0 +1,2 @@ +fn add(a, b) { return a + b; } +fn mul(a, b) { return a * b; } diff --git a/examples/use_math.ast b/examples/use_math.ast new file mode 100644 index 0000000..2681794 --- /dev/null +++ b/examples/use_math.ast @@ -0,0 +1,4 @@ +let math = require("math_mod.ast"); +print(math.add(10, 32)); +print(math.mul(6, 7)); +print(math.add(math.mul(2, 3), 1)); diff --git a/src/interpreter/builtins/core.rs b/src/interpreter/builtins/core.rs index 9f8af40..fe42a52 100644 --- a/src/interpreter/builtins/core.rs +++ b/src/interpreter/builtins/core.rs @@ -1,9 +1,9 @@ //! 标准库:core(len, typeof, push, pop) use crate::error::RuntimeError; -use crate::interpreter::Value; +use crate::interpreter::{Interpreter, Value}; -pub fn len(args: Vec) -> Result { +pub fn len(_interp: &mut Interpreter, args: Vec) -> Result { if args.is_empty() { return Err(RuntimeError::RuntimeError { message: "len() expects 1 argument".into(), @@ -21,7 +21,7 @@ pub fn len(args: Vec) -> Result { } } -pub fn typeof_fn(args: Vec) -> Result { +pub fn typeof_fn(_interp: &mut Interpreter, args: Vec) -> Result { if args.is_empty() { return Err(RuntimeError::RuntimeError { message: "typeof() expects 1 argument".into(), @@ -41,7 +41,7 @@ pub fn typeof_fn(args: Vec) -> Result { Ok(Value::String(s.into())) } -pub fn push(args: Vec) -> Result { +pub fn push(_interp: &mut Interpreter, args: Vec) -> Result { if args.len() < 2 { return Err(RuntimeError::RuntimeError { message: "push() expects 2 arguments (array, value)".into(), @@ -61,7 +61,7 @@ pub fn push(args: Vec) -> Result { } } -pub fn pop(args: Vec) -> Result { +pub fn pop(_interp: &mut Interpreter, args: Vec) -> Result { if args.is_empty() { return Err(RuntimeError::RuntimeError { message: "pop() expects 1 argument (array)".into(), diff --git a/src/interpreter/builtins/io.rs b/src/interpreter/builtins/io.rs index d4b19e4..3bc1540 100644 --- a/src/interpreter/builtins/io.rs +++ b/src/interpreter/builtins/io.rs @@ -1,9 +1,9 @@ //! 标准库:io(print, input) use crate::error::RuntimeError; -use crate::interpreter::Value; +use crate::interpreter::{Interpreter, Value}; -pub fn print(args: Vec) -> Result { +pub fn print(_interp: &mut Interpreter, args: Vec) -> Result { for arg in args.iter() { print!("{}", arg); } @@ -11,7 +11,7 @@ pub fn print(args: Vec) -> Result { Ok(Value::Nil) } -pub fn input(args: Vec) -> Result { +pub fn input(_interp: &mut Interpreter, args: Vec) -> Result { if let Some(prompt) = args.get(0) { print!("{}", prompt); std::io::Write::flush(&mut std::io::stdout()).unwrap(); diff --git a/src/interpreter/builtins/mod.rs b/src/interpreter/builtins/mod.rs index 173be8c..6727e94 100644 --- a/src/interpreter/builtins/mod.rs +++ b/src/interpreter/builtins/mod.rs @@ -36,6 +36,9 @@ pub fn register_all(env: &Rc>) { e.define("push".into(), Value::NativeFunction(Rc::new(core::push)), true); e.define("pop".into(), Value::NativeFunction(Rc::new(core::pop)), true); + // require — module loader + e.define("require".into(), Value::NativeFunction(Rc::new(super::module::require_fn)), true); + // string — split, trim, substring, replace, contains, upper, lower, starts_with, ends_with e.define("split".into(), Value::NativeFunction(Rc::new(string::split)), true); e.define("trim".into(), Value::NativeFunction(Rc::new(string::trim)), true); diff --git a/src/interpreter/builtins/os.rs b/src/interpreter/builtins/os.rs index 6e9c0e0..258d033 100644 --- a/src/interpreter/builtins/os.rs +++ b/src/interpreter/builtins/os.rs @@ -1,9 +1,9 @@ //! 标准库:os(clock) use crate::error::RuntimeError; -use crate::interpreter::Value; +use crate::interpreter::{Interpreter, Value}; -pub fn clock(_args: Vec) -> Result { +pub fn clock(_interp: &mut Interpreter, _args: Vec) -> Result { Ok(Value::Number( std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) diff --git a/src/interpreter/builtins/string.rs b/src/interpreter/builtins/string.rs index acc4675..c4ef2b8 100644 --- a/src/interpreter/builtins/string.rs +++ b/src/interpreter/builtins/string.rs @@ -1,7 +1,7 @@ //! 标准库:string(split, trim, substring, replace, contains, upper, lower, starts_with, ends_with) use crate::error::RuntimeError; -use crate::interpreter::Value; +use crate::interpreter::{Interpreter, Value}; use std::cell::RefCell; use std::rc::Rc; @@ -19,7 +19,7 @@ fn require_string(val: &Value, arg_name: &str) -> Result { // split(str, delim) → array of substrings // ============================================================================ -pub fn split(args: Vec) -> Result { +pub fn split(_interp: &mut Interpreter, args: Vec) -> Result { if args.len() < 2 { return Err(RuntimeError::RuntimeError { message: "split() expects 2 arguments (string, delimiter)".into(), @@ -43,7 +43,7 @@ pub fn split(args: Vec) -> Result { // trim(str) → string with leading/trailing whitespace removed // ============================================================================ -pub fn trim(args: Vec) -> Result { +pub fn trim(_interp: &mut Interpreter, args: Vec) -> Result { if args.is_empty() { return Err(RuntimeError::RuntimeError { message: "trim() expects 1 argument (string)".into(), @@ -58,7 +58,7 @@ pub fn trim(args: Vec) -> Result { // substring(str, start, len?) → substring // ============================================================================ -pub fn substring(args: Vec) -> Result { +pub fn substring(_interp: &mut Interpreter, args: Vec) -> Result { if args.is_empty() { return Err(RuntimeError::RuntimeError { message: "substring() expects at least 2 arguments (string, start, len?)".into(), @@ -81,7 +81,7 @@ pub fn substring(args: Vec) -> Result { // replace(str, old, new) → string with all occurrences replaced // ============================================================================ -pub fn replace(args: Vec) -> Result { +pub fn replace(_interp: &mut Interpreter, args: Vec) -> Result { if args.len() < 3 { return Err(RuntimeError::RuntimeError { message: "replace() expects 3 arguments (string, old, new)".into(), @@ -106,7 +106,7 @@ pub fn replace(args: Vec) -> Result { // contains(str, needle) → bool // ============================================================================ -pub fn contains(args: Vec) -> Result { +pub fn contains(_interp: &mut Interpreter, args: Vec) -> Result { if args.len() < 2 { return Err(RuntimeError::RuntimeError { message: "contains() expects 2 arguments (string, needle)".into(), @@ -122,7 +122,7 @@ pub fn contains(args: Vec) -> Result { // upper(str) → uppercase (ASCII) // ============================================================================ -pub fn upper(args: Vec) -> Result { +pub fn upper(_interp: &mut Interpreter, args: Vec) -> Result { if args.is_empty() { return Err(RuntimeError::RuntimeError { message: "upper() expects 1 argument (string)".into(), @@ -137,7 +137,7 @@ pub fn upper(args: Vec) -> Result { // lower(str) → lowercase (ASCII) // ============================================================================ -pub fn lower(args: Vec) -> Result { +pub fn lower(_interp: &mut Interpreter, args: Vec) -> Result { if args.is_empty() { return Err(RuntimeError::RuntimeError { message: "lower() expects 1 argument (string)".into(), @@ -152,7 +152,7 @@ pub fn lower(args: Vec) -> Result { // starts_with(str, prefix) → bool // ============================================================================ -pub fn starts_with(args: Vec) -> Result { +pub fn starts_with(_interp: &mut Interpreter, args: Vec) -> Result { if args.len() < 2 { return Err(RuntimeError::RuntimeError { message: "starts_with() expects 2 arguments (string, prefix)".into(), @@ -168,7 +168,7 @@ pub fn starts_with(args: Vec) -> Result { // ends_with(str, suffix) → bool // ============================================================================ -pub fn ends_with(args: Vec) -> Result { +pub fn ends_with(_interp: &mut Interpreter, args: Vec) -> Result { if args.len() < 2 { return Err(RuntimeError::RuntimeError { message: "ends_with() expects 2 arguments (string, suffix)".into(), diff --git a/src/interpreter/eval.rs b/src/interpreter/eval.rs index 4ca75ea..fd9a85e 100644 --- a/src/interpreter/eval.rs +++ b/src/interpreter/eval.rs @@ -103,7 +103,7 @@ impl super::Interpreter { "length" => Ok(Value::Number(arr.borrow().len() as f64)), "push" => { let arr = Rc::clone(&arr); - Ok(Value::NativeFunction(Rc::new(move |mut args: Vec| { + Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, mut args: Vec| { let val = args.pop().unwrap_or(Value::Nil); arr.borrow_mut().push(val.clone()); Ok(val) @@ -111,7 +111,7 @@ impl super::Interpreter { } "pop" => { let arr = Rc::clone(&arr); - Ok(Value::NativeFunction(Rc::new(move |_args: Vec| { + Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, _args: Vec| { arr.borrow_mut() .pop() .ok_or_else(|| RuntimeError::RuntimeError { @@ -129,74 +129,74 @@ impl super::Interpreter { "length" => Ok(Value::Number(s.chars().count() as f64)), "upper" => { let s = s.clone(); - Ok(Value::NativeFunction(Rc::new(move |args: Vec| { + Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec| { let mut all_args = vec![Value::String(s.clone())]; all_args.extend(args); - super::builtins::string::upper(all_args) + super::builtins::string::upper(_interp, all_args) }))) } "lower" => { let s = s.clone(); - Ok(Value::NativeFunction(Rc::new(move |args: Vec| { + Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec| { let mut all_args = vec![Value::String(s.clone())]; all_args.extend(args); - super::builtins::string::lower(all_args) + super::builtins::string::lower(_interp, all_args) }))) } "trim" => { let s = s.clone(); - Ok(Value::NativeFunction(Rc::new(move |args: Vec| { + Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec| { let mut all_args = vec![Value::String(s.clone())]; all_args.extend(args); - super::builtins::string::trim(all_args) + super::builtins::string::trim(_interp, all_args) }))) } "substring" => { let s = s.clone(); - Ok(Value::NativeFunction(Rc::new(move |args: Vec| { + Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec| { let mut all_args = vec![Value::String(s.clone())]; all_args.extend(args); - super::builtins::string::substring(all_args) + super::builtins::string::substring(_interp, all_args) }))) } "replace" => { let s = s.clone(); - Ok(Value::NativeFunction(Rc::new(move |args: Vec| { + Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec| { let mut all_args = vec![Value::String(s.clone())]; all_args.extend(args); - super::builtins::string::replace(all_args) + super::builtins::string::replace(_interp, all_args) }))) } "contains" => { let s = s.clone(); - Ok(Value::NativeFunction(Rc::new(move |args: Vec| { + Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec| { let mut all_args = vec![Value::String(s.clone())]; all_args.extend(args); - super::builtins::string::contains(all_args) + super::builtins::string::contains(_interp, all_args) }))) } "starts_with" => { let s = s.clone(); - Ok(Value::NativeFunction(Rc::new(move |args: Vec| { + Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec| { let mut all_args = vec![Value::String(s.clone())]; all_args.extend(args); - super::builtins::string::starts_with(all_args) + super::builtins::string::starts_with(_interp, all_args) }))) } "ends_with" => { let s = s.clone(); - Ok(Value::NativeFunction(Rc::new(move |args: Vec| { + Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec| { let mut all_args = vec![Value::String(s.clone())]; all_args.extend(args); - super::builtins::string::ends_with(all_args) + super::builtins::string::ends_with(_interp, all_args) }))) } "split" => { let s = s.clone(); - Ok(Value::NativeFunction(Rc::new(move |args: Vec| { + Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec| { let mut all_args = vec![Value::String(s.clone())]; all_args.extend(args); - super::builtins::string::split(all_args) + super::builtins::string::split(_interp, all_args) }))) } _ => Err(RuntimeError::RuntimeError { @@ -481,7 +481,7 @@ impl super::Interpreter { pub fn call_function(&mut self, func_val: Value, args: Vec) -> Result { match func_val { - Value::NativeFunction(native_fn) => native_fn(args), + Value::NativeFunction(native_fn) => native_fn(self, args), Value::Function(f) => self.call_user_function(f, args), _ => Err(RuntimeError::RuntimeError { message: "Attempt to call non-function".to_string(), @@ -553,6 +553,8 @@ impl super::Interpreter { y.get(k).map_or(false, |yv| self.is_equal(v, yv)) }) } + (Value::Function(x), Value::Function(y)) => Rc::ptr_eq(x, y), + (Value::NativeFunction(x), Value::NativeFunction(y)) => Rc::ptr_eq(x, y), _ => false, } } diff --git a/src/interpreter/interpreter.rs b/src/interpreter/interpreter.rs index 8d89cb7..829c07a 100644 --- a/src/interpreter/interpreter.rs +++ b/src/interpreter/interpreter.rs @@ -1,18 +1,45 @@ use crate::ast::*; use crate::error::RuntimeError; -use super::{Env}; +use super::{Env, Value}; +use std::collections::HashMap; use std::rc::Rc; use std::cell::RefCell; pub struct Interpreter { + /// 当前作用域(用户代码所在 env,parent 指向 builtins_env) pub env: Rc>, + /// 内置函数根作用域(parent = None,所有内置函数注册在此) + pub builtins_env: Rc>, + /// 模块缓存:规范路径 → exports 对象 + pub module_cache: RefCell>, + /// 当前执行文件的目录,用于 require() 解析相对路径 + pub current_dir: String, } impl Interpreter { pub fn new() -> Self { - let env = Rc::new(RefCell::new(Env::new(None))); - super::builtins::register_all(&env); - Self { env } + // 根层:仅包含内置函数 + let builtins_env = Rc::new(RefCell::new(Env::new(None))); + super::builtins::register_all(&builtins_env); + + // 用户层:parent 指向 builtins_env,用户定义的变量都在这层 + let script_env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&builtins_env))))); + + Self { + env: script_env, + builtins_env, + module_cache: RefCell::new(HashMap::new()), + current_dir: std::env::current_dir() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_else(|_| ".".to_string()), + } + } + + /// 指定当前目录的构造器,用于 `run_file` 时设置脚本所在目录 + pub fn with_current_dir(dir: String) -> Self { + let mut interp = Self::new(); + interp.current_dir = dir; + interp } pub fn interpret(&mut self, statements: Vec) -> Result<(), RuntimeError> { diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 57a6101..26d842c 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -3,6 +3,7 @@ pub mod env; pub mod eval; pub mod exec; pub mod interpreter; +pub mod module; pub use env::Env; pub use interpreter::Interpreter; @@ -12,7 +13,7 @@ use std::rc::Rc; use std::cell::RefCell; use std::fmt; -pub type NativeFn = Rc) -> Result>; +pub type NativeFn = Rc) -> Result>; #[derive(Clone)] pub enum Value { diff --git a/src/interpreter/module.rs b/src/interpreter/module.rs new file mode 100644 index 0000000..ba07c1a --- /dev/null +++ b/src/interpreter/module.rs @@ -0,0 +1,140 @@ +//! 模块系统:require() 加载器 +//! +//! `require("path/to/module.ast")` 加载并执行指定的 Aster 文件, +//! 返回一个包含模块所有顶层定义的 `Value::Object`。 +//! 模块在自己的作用域中执行,只能访问内置函数,无法访问调用者的变量。 +//! 第二次 require 同一文件会返回缓存的对象。 + +use crate::error::RuntimeError; +use crate::lexer::Lexer; +use crate::parser::Parser; +use super::{Interpreter, Env, Value, Signal}; +use std::cell::RefCell; +use std::collections::HashMap; +use std::path::Path; +use std::rc::Rc; + +/// require() 的内置函数实现 +pub fn require_fn(interp: &mut Interpreter, args: Vec) -> Result { + // 1. 参数验证 + let path_str = match args.first() { + Some(Value::String(s)) => s.clone(), + Some(other) => return Err(runtime_error(format!( + "require() expects a string argument, got {}", other))), + None => return Err(runtime_error( + "require() expects 1 argument (string path)")), + }; + + // 2. 路径解析 + let resolved = resolve_path(&interp.current_dir, &path_str)?; + + // 3. 缓存查找 + if let Some(cached) = interp.module_cache.borrow().get(&resolved) { + return Ok(cached.clone()); + } + + // 4. 读取文件 + let src = std::fs::read_to_string(&resolved) + .map_err(|e| runtime_error(format!( + "Module '{}' not found: {}", path_str, e)))?; + + // 5. 词法分析 + let (tokens, lex_errors) = Lexer::new(&src).tokenize(); + if !lex_errors.is_empty() { + return Err(runtime_error(format!( + "Lex error in module '{}': {}", path_str, lex_errors[0]))); + } + + // 6. 语法分析 + let mut parser = Parser::new(tokens); + let (stmts, parse_errors) = parser.parse(); + if !parse_errors.is_empty() { + return Err(runtime_error(format!( + "Parse error in module '{}': {}", path_str, parse_errors[0]))); + } + + // 7. 创建隔离的模块 env(父级 = builtins_env,看不到调用者的变量) + let module_env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&interp.builtins_env))))); + + // 8. 在缓存中插入占位符(支持循环 require) + let exports_map = Rc::new(RefCell::new(HashMap::new())); + let exports = Value::Object(Rc::clone(&exports_map)); + interp.module_cache.borrow_mut().insert(resolved.clone(), exports.clone()); + + // 9. 保存调用者状态 + let previous_env = Rc::clone(&interp.env); + let previous_dir = interp.current_dir.clone(); + + // 10. 切换到模块上下文 + interp.env = module_env.clone(); + interp.current_dir = module_dir(&resolved); + + // 11. 执行模块 + for stmt in &stmts { + match interp.execute(stmt.clone()) { + Ok(Signal::Break) | Ok(Signal::Continue) => { + // 恢复状态后返回错误 + interp.env = previous_env; + interp.current_dir = previous_dir; + interp.module_cache.borrow_mut().remove(&resolved); + return Err(runtime_error( + "break/continue outside of loop in module")); + } + Err(e) => { + // 恢复状态后传播错误 + interp.env = previous_env; + interp.current_dir = previous_dir; + interp.module_cache.borrow_mut().remove(&resolved); + return Err(e); + } + Ok(Signal::None) | Ok(Signal::Return(_)) => { + // 正常执行或顶层 return,继续 + } + } + } + + // 12. 恢复调用者状态 + interp.env = previous_env; + interp.current_dir = previous_dir; + + // 13. 收集 exports(模块 env 中的所有直接绑定) + for (name, (val, _mutable)) in module_env.borrow().values.clone() { + exports_map.borrow_mut().insert(name, val); + } + + Ok(exports) +} + +// ============================================================================ +// Helpers +// ============================================================================ + +/// 解析模块路径:相对路径基于 current_dir,用 canonicalize 规范化 +fn resolve_path(current_dir: &str, path_str: &str) -> Result { + let path = Path::new(path_str); + let resolved = if path.is_absolute() { + path.to_path_buf() + } else { + Path::new(current_dir).join(path) + }; + std::fs::canonicalize(&resolved) + .map(|p| p.to_string_lossy().to_string()) + .map_err(|_| runtime_error(format!( + "Module '{}' not found (resolved to '{}')", + path_str, resolved.display()))) +} + +/// 提取模块文件所在目录 +fn module_dir(resolved: &str) -> String { + Path::new(resolved) + .parent() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_else(|| ".".to_string()) +} + +fn runtime_error(msg: impl Into) -> RuntimeError { + RuntimeError::RuntimeError { + message: msg.into(), + token: None, + } +} diff --git a/src/interpreter/tests.rs b/src/interpreter/tests.rs index 92fe2b9..4e5d48f 100644 --- a/src/interpreter/tests.rs +++ b/src/interpreter/tests.rs @@ -1626,3 +1626,110 @@ fn error_string_unknown_property() { }); assert!(result.is_err(), "Expected error for unknown string property"); } + +// ========================================================================= +// Module system (require) +// ========================================================================= + +#[test] +fn eval_require_math_module() { + let interp = run("let math = require(\"tests/fixtures/math.ast\");"); + let math = get_var(&interp, "math"); + match math { + Value::Object(obj) => { + let obj = obj.borrow(); + assert!(obj.contains_key("add"), "math should contain 'add'"); + assert!(obj.contains_key("mul"), "math should contain 'mul'"); + assert!(obj.contains_key("version"), "math should contain 'version'"); + } + v => panic!("Expected Object, got {:?}", v), + } +} + +#[test] +fn eval_require_call_exported_function() { + let interp = run(r#" + let math = require("tests/fixtures/math.ast"); + let result = math.add(10, 32); + "#); + assert_num(&get_var(&interp, "result"), 42.0); +} + +#[test] +fn eval_require_cache_returns_same_object() { + let interp = run(r#" + let a = require("tests/fixtures/math.ast"); + let b = require("tests/fixtures/math.ast"); + let same = a == b; + "#); + assert_bool(&get_var(&interp, "same"), true); +} + +#[test] +fn eval_require_module_isolation() { + // 模块看不到调用者的变量,但模块内部定义可以正常工作 + let interp = run(r#" + let secret = 99; + let g = require("tests/fixtures/greet.ast"); + let msg = g.greet("Aster"); + "#); + match get_var(&interp, "msg") { + Value::String(s) => assert_eq!(s, "Hello!"), + v => panic!("Expected String, got {:?}", v), + } +} + +#[test] +fn eval_require_module_with_builtins() { + // 模块内部可以使用内置函数(因为 builtins_env 是模块 env 的祖先) + let interp = run(r#" + let m = require("tests/fixtures/math.ast"); + let r = m.add(5, 7); + "#); + assert_num(&get_var(&interp, "r"), 12.0); +} + +#[test] +fn eval_require_error_file_not_found() { + let result = std::panic::catch_unwind(|| { + run("let x = require(\"nonexistent_file_xyz.ast\");"); + }); + assert!(result.is_err(), "Expected error for missing module file"); +} + +#[test] +fn eval_require_error_no_args() { + let result = std::panic::catch_unwind(|| { + run("let x = require();"); + }); + assert!(result.is_err(), "Expected error for require() with no args"); +} + +#[test] +fn eval_require_error_non_string_arg() { + let result = std::panic::catch_unwind(|| { + run("let x = require(42);"); + }); + assert!(result.is_err(), "Expected error for require() with non-string arg"); +} + +#[test] +fn eval_require_error_broken_syntax() { + let result = std::panic::catch_unwind(|| { + run("let x = require(\"tests/fixtures/broken_syntax.ast\");"); + }); + assert!(result.is_err(), "Expected error for module with syntax error"); +} + +#[test] +fn eval_require_empty_module() { + // 空模块应该返回空对象 + let interp = run("let empty = require(\"tests/fixtures/sub/mod.ast\");"); + match get_var(&interp, "empty") { + Value::Object(obj) => { + let obj = obj.borrow(); + assert!(obj.contains_key("sub"), "empty module should contain 'sub'"); + } + v => panic!("Expected Object, got {:?}", v), + } +} diff --git a/src/main.rs b/src/main.rs index cb9d4b0..0bf0322 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ fn print_errors(errors: &[RuntimeError]) { } } -fn run_file(src: String) { +fn run_file(filename: &str, src: String) { let (tokens, lex_errors) = Lexer::new(&src).tokenize(); if !lex_errors.is_empty() { print_errors(&lex_errors); @@ -34,7 +34,12 @@ fn run_file(src: String) { std::process::exit(65); } - let mut interpreter = Interpreter::new(); + let script_dir = std::path::Path::new(filename) + .parent() + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_else(|| ".".to_string()); + + let mut interpreter = Interpreter::with_current_dir(script_dir); if let Err(e) = interpreter.interpret(stmts) { eprintln!("Error: {}", e); std::process::exit(70); @@ -104,7 +109,7 @@ fn main() { 2 => { let filename = &args[1]; match fs::read_to_string(filename) { - Ok(src) => run_file(src), + Ok(src) => run_file(filename, src), Err(e) => { eprintln!("Error reading file '{}': {}", filename, e); std::process::exit(1); diff --git a/tests/fixtures/broken_syntax.ast b/tests/fixtures/broken_syntax.ast new file mode 100644 index 0000000..97c5ac5 --- /dev/null +++ b/tests/fixtures/broken_syntax.ast @@ -0,0 +1,3 @@ +fn broken ( { + return 1; +} diff --git a/tests/fixtures/greet.ast b/tests/fixtures/greet.ast new file mode 100644 index 0000000..113fbfd --- /dev/null +++ b/tests/fixtures/greet.ast @@ -0,0 +1,2 @@ +fn greet(name) { return "Hello!"; } +let version = 1; diff --git a/tests/fixtures/math.ast b/tests/fixtures/math.ast new file mode 100644 index 0000000..1d13994 --- /dev/null +++ b/tests/fixtures/math.ast @@ -0,0 +1,3 @@ +fn add(a, b) { return a + b; } +fn mul(a, b) { return a * b; } +let version = "1.0"; diff --git a/tests/fixtures/sub/mod.ast b/tests/fixtures/sub/mod.ast new file mode 100644 index 0000000..bdcac27 --- /dev/null +++ b/tests/fixtures/sub/mod.ast @@ -0,0 +1 @@ +fn sub(a, b) { return a - b; }