feat: 添加内置函数支持(len, typeof, push, pop),更新相关错误处理和测试用例
This commit is contained in:
@@ -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)
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
//! 标准库:core(len, 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,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,21 +1,22 @@
|
|||||||
//! 标准库:io(print, input)
|
//! 标准库:io(print, 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()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
//! 标准库:os(clock)
|
//! 标准库:os(clock)
|
||||||
|
|
||||||
|
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(),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user