feat: rest 参数(function f(...args)),实参区收集数组
This commit is contained in:
@@ -196,6 +196,43 @@ impl Vm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 创建 rest 参数数组:实参区下标 ≥ `fixed_count` 的实参收集为数组存入 `rd`。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 读当前帧(或 inline 同步调用)的实参区基址与个数。
|
||||
/// 2. 实参个数超出固定形参数的部分作为数组元素写入。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 实参来源与 CREATE_ARGUMENTS 相同(push_bytecode_frame / inline 路径写入 spill 栈)。
|
||||
/// - 实参不足 `fixed_count` 时数组为空。
|
||||
pub(crate) fn dispatch_create_rest_array(&mut self, rd: usize, fixed_count: usize) -> Result<(), String> {
|
||||
let (base, count) = match self.frames.last() {
|
||||
Some(frame) => (frame.arguments_base, frame.arguments_count),
|
||||
None => (self.inline_args_base, self.inline_args_count),
|
||||
};
|
||||
let n = count as usize;
|
||||
let rest_len = n.saturating_sub(fixed_count);
|
||||
let proto_ptr = self.session.builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let bump = self.epoch.bump();
|
||||
let obj_ptr = self.alloc_object(JsObject::new_array(
|
||||
EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(proto_ptr),
|
||||
rest_len,
|
||||
bump,
|
||||
));
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
for i in 0..rest_len {
|
||||
let val = self
|
||||
.spill_stack
|
||||
.get(base as usize + fixed_count + i)
|
||||
.copied()
|
||||
.unwrap_or(JsValue::undefined());
|
||||
obj.set_prop_at(i as u32, val);
|
||||
}
|
||||
self.regs[rd] = JsValue::object(obj_ptr as *mut u8);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_new_array(&mut self, rd: usize, instr: u32) {
|
||||
let n = opcode::imm16(instr) as usize;
|
||||
|
||||
@@ -1330,6 +1330,11 @@ impl Vm {
|
||||
self.dispatch_create_arguments(rd)?;
|
||||
}
|
||||
|
||||
OpCode::CREATE_REST_ARRAY => {
|
||||
let fixed_count = opcode::b(instr) as usize;
|
||||
self.dispatch_create_rest_array(rd, fixed_count)?;
|
||||
}
|
||||
|
||||
OpCode::NEW_ARRAY => {
|
||||
self.dispatch_new_array(rd, instr);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
//! rest 参数集成测试:实参收集、length 交互、与 spread/箭头/闭包组合。
|
||||
//! 每个用例独立编译执行,断言顶层表达式结果。
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(source: &str) -> JsValue {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse failed");
|
||||
let module = Compiler::new().compile(&program).expect("compile failed");
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&module).expect("vm run failed")
|
||||
}
|
||||
|
||||
fn eval_int(source: &str) -> i32 {
|
||||
let v = eval(source);
|
||||
if v.is_int() {
|
||||
v.as_int()
|
||||
} else if v.is_double() {
|
||||
v.as_double() as i32
|
||||
} else {
|
||||
panic!("expected number, got {v}")
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_str(source: &str) -> String {
|
||||
let v = eval(source);
|
||||
if v.is_string() {
|
||||
unsafe { oxide_runtime_api::string_data(v) }.to_string()
|
||||
} else {
|
||||
format!("{v}")
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rest_collects_remaining_arguments() {
|
||||
assert_eq!(eval_int("function f(...args){return args.length} f(1,2,3)"), 3);
|
||||
assert_eq!(eval_str("function f(...args){return args.join(',')} f(1,2,3)"), "1,2,3");
|
||||
assert_eq!(eval_int("function f(...args){return args.length} f()"), 0);
|
||||
assert_eq!(eval_int("function f(...args){return args[0]+args[1]} f(7,8)"), 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rest_after_fixed_params_starts_at_fixed_count() {
|
||||
assert_eq!(eval_str("function f(a,...rest){return a+':'+rest.join(',')} f(1,2,3)"), "1:2,3");
|
||||
assert_eq!(eval_int("function f(a,b,...rest){return rest.length} f(1,2,3,4,5)"), 3);
|
||||
assert_eq!(eval_int("function f(a,b,...rest){return rest.length} f(1)"), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rest_is_a_mutable_array() {
|
||||
assert_eq!(eval_str("function f(...args){return Array.isArray(args)&&'y'} f(1)"), "y");
|
||||
assert_eq!(eval_str("function f(a,...r){r.push(4); return r.join(',')} f(1,2,3)"), "2,3,4");
|
||||
assert_eq!(eval_str("function f(...a){a[0]=99; return a.join(',')} f(1,2,3)"), "99,2,3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rest_combines_with_spread_and_arrow() {
|
||||
assert_eq!(eval_int("function f(...rest){return rest.length} f(...[1,2,3])"), 3);
|
||||
assert_eq!(eval_int("var f=(...args)=>args.length; f(1,2,3)"), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rest_captured_by_closure() {
|
||||
assert_eq!(eval_int("function f(...a){return ()=>a.length} f(1,2,3)()"), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rest_does_not_count_toward_function_length() {
|
||||
assert_eq!(eval("function f(...r){}; f.length"), JsValue::int(0));
|
||||
assert_eq!(eval("function f(a,b=1,...r){}; f.length"), JsValue::int(1));
|
||||
}
|
||||
Reference in New Issue
Block a user