feat: 添加内置函数支持(len, typeof, push, pop),更新相关错误处理和测试用例

This commit is contained in:
0264408
2026-06-16 19:03:47 +08:00
parent 18fa53cf4a
commit 75c1d230ad
7 changed files with 314 additions and 9 deletions
+21
View File
@@ -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)
+84
View File
@@ -0,0 +1,84 @@
//! 标准库:corelen, typeof, push, pop
use crate::error::RuntimeError;
use crate::interpreter::Value;
pub fn len(args: Vec<Value>) -> Result<Value, RuntimeError> {
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<Value>) -> Result<Value, RuntimeError> {
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<Value>) -> Result<Value, RuntimeError> {
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<Value>) -> Result<Value, RuntimeError> {
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,
}),
}
}
+5 -4
View File
@@ -1,21 +1,22 @@
//! 标准库:ioprint, input //! 标准库:ioprint, input
use crate::error::RuntimeError;
use crate::interpreter::Value; use crate::interpreter::Value;
pub fn print(args: Vec<Value>) -> Value { pub fn print(args: Vec<Value>) -> Result<Value, RuntimeError> {
for arg in args.iter() { for arg in args.iter() {
print!("{}", arg); print!("{}", arg);
} }
println!(); println!();
Value::Nil Ok(Value::Nil)
} }
pub fn input(args: Vec<Value>) -> Value { pub fn input(args: Vec<Value>) -> Result<Value, RuntimeError> {
if let Some(prompt) = args.get(0) { if let Some(prompt) = args.get(0) {
print!("{}", prompt); print!("{}", prompt);
std::io::Write::flush(&mut std::io::stdout()).unwrap(); std::io::Write::flush(&mut std::io::stdout()).unwrap();
} }
let mut buffer = String::new(); let mut buffer = String::new();
std::io::stdin().read_line(&mut buffer).unwrap(); std::io::stdin().read_line(&mut buffer).unwrap();
Value::String(buffer.trim_end().to_string()) Ok(Value::String(buffer.trim_end().to_string()))
} }
+7
View File
@@ -1,5 +1,6 @@
//! 内置函数模块:按功能分组注册,便于扩展和维护。 //! 内置函数模块:按功能分组注册,便于扩展和维护。
mod core;
mod io; mod io;
mod os; mod os;
@@ -27,4 +28,10 @@ pub fn register_all(env: &Rc<RefCell<Env>>) {
e.define("os".into(), Value::Object(Rc::new(RefCell::new(os))), true); e.define("os".into(), Value::Object(Rc::new(RefCell::new(os))), true);
// clock can also be used as a global function for convenience // clock can also be used as a global function for convenience
e.define("clock".into(), Value::NativeFunction(os::clock), true); 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);
} }
+4 -3
View File
@@ -1,12 +1,13 @@
//! 标准库:osclock //! 标准库:osclock
use crate::error::RuntimeError;
use crate::interpreter::Value; use crate::interpreter::Value;
pub fn clock(_args: Vec<Value>) -> Value { pub fn clock(_args: Vec<Value>) -> Result<Value, RuntimeError> {
Value::Number( Ok(Value::Number(
std::time::SystemTime::now() std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH) .duration_since(std::time::UNIX_EPOCH)
.unwrap() .unwrap()
.as_secs_f64(), .as_secs_f64(),
) ))
} }
+192 -1
View File
@@ -397,7 +397,7 @@ impl Interpreter {
fn call_function(&mut self, func_val: Value, args: Vec<Value>) -> Result<Value, RuntimeError> { fn call_function(&mut self, func_val: Value, args: Vec<Value>) -> Result<Value, RuntimeError> {
match func_val { match func_val {
Value::NativeFunction(native_fn) => { Value::NativeFunction(native_fn) => {
Ok(native_fn(args)) native_fn(args)
} }
Value::Function(f) => { Value::Function(f) => {
let env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&f.env))))); 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"); 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");
}
} }
+1 -1
View File
@@ -10,7 +10,7 @@ use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt; use std::fmt;
pub type NativeFn = fn(Vec<Value>) -> Value; pub type NativeFn = fn(Vec<Value>) -> Result<Value, crate::error::RuntimeError>;
#[derive(Clone)] #[derive(Clone)]
pub enum Value { pub enum Value {