diff --git a/examples/builtins.ast b/examples/builtins.ast new file mode 100644 index 0000000..c048a6e --- /dev/null +++ b/examples/builtins.ast @@ -0,0 +1,21 @@ +// Quick smoke test for new builtins +print("--- len ---") +print(len("hello")) +print(len([1, 2, 3])) +print(len({a: 1, b: 2, c: 3})) + +print("--- typeof ---") +print(typeof(42)) +print(typeof("hi")) +print(typeof(true)) +print(typeof(nil)) +print(typeof([1, 2])) +print(typeof({a: 1})) +print(typeof(print)) + +print("--- push/pop ---") +let arr = [1, 2] +print("push:", push(arr, 3)) +print("arr:", arr) +print("pop:", pop(arr)) +print("arr:", arr) diff --git a/src/interpreter/builtins/core.rs b/src/interpreter/builtins/core.rs new file mode 100644 index 0000000..9f8af40 --- /dev/null +++ b/src/interpreter/builtins/core.rs @@ -0,0 +1,84 @@ +//! 标准库:core(len, typeof, push, pop) + +use crate::error::RuntimeError; +use crate::interpreter::Value; + +pub fn len(args: Vec) -> Result { + if args.is_empty() { + return Err(RuntimeError::RuntimeError { + message: "len() expects 1 argument".into(), + token: None, + }); + } + match &args[0] { + Value::String(s) => Ok(Value::Number(s.chars().count() as f64)), + Value::Array(arr) => Ok(Value::Number(arr.borrow().len() as f64)), + Value::Object(obj) => Ok(Value::Number(obj.borrow().len() as f64)), + _ => Err(RuntimeError::RuntimeError { + message: "len() expects a string, array, or object".into(), + token: None, + }), + } +} + +pub fn typeof_fn(args: Vec) -> Result { + if args.is_empty() { + return Err(RuntimeError::RuntimeError { + message: "typeof() expects 1 argument".into(), + token: None, + }); + } + let s = match &args[0] { + Value::Number(_) => "number", + Value::String(_) => "string", + Value::Bool(_) => "bool", + Value::Nil => "nil", + Value::Object(_) => "object", + Value::Array(_) => "array", + Value::Function(_) => "function", + Value::NativeFunction(_) => "native_function", + }; + Ok(Value::String(s.into())) +} + +pub fn push(args: Vec) -> Result { + if args.len() < 2 { + return Err(RuntimeError::RuntimeError { + message: "push() expects 2 arguments (array, value)".into(), + token: None, + }); + } + let val = args[1].clone(); + match &args[0] { + Value::Array(arr) => { + arr.borrow_mut().push(val.clone()); + Ok(val) + } + _ => Err(RuntimeError::RuntimeError { + message: "push() expects an array as first argument".into(), + token: None, + }), + } +} + +pub fn pop(args: Vec) -> Result { + if args.is_empty() { + return Err(RuntimeError::RuntimeError { + message: "pop() expects 1 argument (array)".into(), + token: None, + }); + } + match &args[0] { + Value::Array(arr) => arr + .borrow_mut() + .pop() + .ok_or_else(|| RuntimeError::RuntimeError { + message: "pop() on empty array".into(), + token: None, + }), + _ => Err(RuntimeError::RuntimeError { + message: "pop() expects an array as first argument".into(), + token: None, + }), + } +} diff --git a/src/interpreter/builtins/io.rs b/src/interpreter/builtins/io.rs index 71d97cc..d4b19e4 100644 --- a/src/interpreter/builtins/io.rs +++ b/src/interpreter/builtins/io.rs @@ -1,21 +1,22 @@ //! 标准库:io(print, input) +use crate::error::RuntimeError; use crate::interpreter::Value; -pub fn print(args: Vec) -> Value { +pub fn print(args: Vec) -> Result { for arg in args.iter() { print!("{}", arg); } println!(); - Value::Nil + Ok(Value::Nil) } -pub fn input(args: Vec) -> Value { +pub fn input(args: Vec) -> Result { if let Some(prompt) = args.get(0) { print!("{}", prompt); std::io::Write::flush(&mut std::io::stdout()).unwrap(); } let mut buffer = String::new(); std::io::stdin().read_line(&mut buffer).unwrap(); - Value::String(buffer.trim_end().to_string()) + Ok(Value::String(buffer.trim_end().to_string())) } diff --git a/src/interpreter/builtins/mod.rs b/src/interpreter/builtins/mod.rs index 66f3b79..5ae9349 100644 --- a/src/interpreter/builtins/mod.rs +++ b/src/interpreter/builtins/mod.rs @@ -1,5 +1,6 @@ //! 内置函数模块:按功能分组注册,便于扩展和维护。 +mod core; mod io; mod os; @@ -27,4 +28,10 @@ pub fn register_all(env: &Rc>) { e.define("os".into(), Value::Object(Rc::new(RefCell::new(os))), true); // clock can also be used as a global function for convenience e.define("clock".into(), Value::NativeFunction(os::clock), true); + + // core — len, typeof, push, pop + e.define("len".into(), Value::NativeFunction(core::len), true); + e.define("typeof".into(), Value::NativeFunction(core::typeof_fn), true); + e.define("push".into(), Value::NativeFunction(core::push), true); + e.define("pop".into(), Value::NativeFunction(core::pop), true); } diff --git a/src/interpreter/builtins/os.rs b/src/interpreter/builtins/os.rs index 7213b5b..6e9c0e0 100644 --- a/src/interpreter/builtins/os.rs +++ b/src/interpreter/builtins/os.rs @@ -1,12 +1,13 @@ //! 标准库:os(clock) +use crate::error::RuntimeError; use crate::interpreter::Value; -pub fn clock(_args: Vec) -> Value { - Value::Number( +pub fn clock(_args: Vec) -> Result { + Ok(Value::Number( std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs_f64(), - ) + )) } diff --git a/src/interpreter/interpreter.rs b/src/interpreter/interpreter.rs index 06748c2..a979d38 100644 --- a/src/interpreter/interpreter.rs +++ b/src/interpreter/interpreter.rs @@ -397,7 +397,7 @@ impl Interpreter { fn call_function(&mut self, func_val: Value, args: Vec) -> Result { match func_val { Value::NativeFunction(native_fn) => { - Ok(native_fn(args)) + native_fn(args) } Value::Function(f) => { let env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&f.env))))); @@ -1421,5 +1421,196 @@ mod tests { }); assert!(result.is_err(), "Expected error for index on non-array"); } + + // ========================================================================= + // Builtins: len + // ========================================================================= + + #[test] + fn eval_len_string() { + assert_num(&eval_expr("len(\"hello\")"), 5.0); + } + + #[test] + fn eval_len_empty_string() { + assert_num(&eval_expr("len(\"\")"), 0.0); + } + + #[test] + fn eval_len_array() { + assert_num(&eval_expr("len([1, 2, 3])"), 3.0); + } + + #[test] + fn eval_len_empty_array() { + assert_num(&eval_expr("len([])"), 0.0); + } + + #[test] + fn eval_len_object() { + assert_num(&eval_expr("len({a: 1, b: 2})"), 2.0); + } + + #[test] + fn eval_len_empty_object() { + assert_num(&eval_expr("len({})"), 0.0); + } + + #[test] + fn error_len_no_args() { + let result = std::panic::catch_unwind(|| { + eval_expr("len()"); + }); + assert!(result.is_err(), "Expected error for len() with no args"); + } + + #[test] + fn error_len_number() { + let result = std::panic::catch_unwind(|| { + eval_expr("len(42)"); + }); + assert!(result.is_err(), "Expected error for len(42)"); + } + + // ========================================================================= + // Builtins: typeof + // ========================================================================= + + #[test] + fn eval_typeof_number() { + match eval_expr("typeof(42)") { + Value::String(s) => assert_eq!(s, "number"), + v => panic!("Expected String, got {:?}", v), + } + } + + #[test] + fn eval_typeof_string() { + match eval_expr("typeof(\"hello\")") { + Value::String(s) => assert_eq!(s, "string"), + v => panic!("Expected String, got {:?}", v), + } + } + + #[test] + fn eval_typeof_bool() { + match eval_expr("typeof(true)") { + Value::String(s) => assert_eq!(s, "bool"), + v => panic!("Expected String, got {:?}", v), + } + } + + #[test] + fn eval_typeof_nil() { + match eval_expr("typeof(nil)") { + Value::String(s) => assert_eq!(s, "nil"), + v => panic!("Expected String, got {:?}", v), + } + } + + #[test] + fn eval_typeof_object() { + match eval_expr("typeof({a: 1})") { + Value::String(s) => assert_eq!(s, "object"), + v => panic!("Expected String, got {:?}", v), + } + } + + #[test] + fn eval_typeof_array() { + match eval_expr("typeof([1, 2])") { + Value::String(s) => assert_eq!(s, "array"), + v => panic!("Expected String, got {:?}", v), + } + } + + #[test] + fn eval_typeof_function() { + let interp = run("fn f() {}"); + // f is a variable, not an expression — use get_var + match get_var(&interp, "f") { + Value::Function(_) => {} // just verify it's a function + v => panic!("Expected Function, got {:?}", v), + } + } + + // ========================================================================= + // Builtins: push / pop + // ========================================================================= + + #[test] + fn eval_push_returns_value() { + assert_num(&eval_expr("push([1, 2], 3)"), 3.0); + } + + #[test] + fn eval_push_modifies_array() { + let interp = run("let arr = [1, 2]; push(arr, 3);"); + match get_var(&interp, "arr") { + Value::Array(arr) => { + let arr = arr.borrow(); + assert_eq!(arr.len(), 3); + assert_num(&arr[0], 1.0); + assert_num(&arr[1], 2.0); + assert_num(&arr[2], 3.0); + } + v => panic!("Expected Array, got {:?}", v), + } + } + + #[test] + fn eval_push_string_to_array() { + let interp = run("let arr = [\"a\"]; push(arr, \"b\");"); + match get_var(&interp, "arr") { + Value::Array(arr) => { + let arr = arr.borrow(); + assert_eq!(arr.len(), 2); + } + v => panic!("Expected Array, got {:?}", v), + } + } + + #[test] + fn eval_pop_returns_last() { + assert_num(&eval_expr("pop([1, 2, 3])"), 3.0); + } + + #[test] + fn eval_pop_modifies_array() { + let interp = run("let arr = [1, 2, 3]; pop(arr);"); + match get_var(&interp, "arr") { + Value::Array(arr) => { + let arr = arr.borrow(); + assert_eq!(arr.len(), 2); + assert_num(&arr[0], 1.0); + assert_num(&arr[1], 2.0); + } + v => panic!("Expected Array, got {:?}", v), + } + } + + #[test] + fn error_pop_empty_array() { + let result = std::panic::catch_unwind(|| { + eval_expr("pop([])"); + }); + assert!(result.is_err(), "Expected error for pop([])"); + } + + #[test] + fn error_push_non_array() { + let result = std::panic::catch_unwind(|| { + eval_expr("push(42, 1)"); + }); + assert!(result.is_err(), "Expected error for push on non-array"); + } + + #[test] + fn error_pop_non_array() { + let result = std::panic::catch_unwind(|| { + eval_expr("pop(42)"); + }); + assert!(result.is_err(), "Expected error for pop on non-array"); + } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 56c3123..c696f9e 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -10,7 +10,7 @@ use std::rc::Rc; use std::cell::RefCell; use std::fmt; -pub type NativeFn = fn(Vec) -> Value; +pub type NativeFn = fn(Vec) -> Result; #[derive(Clone)] pub enum Value {