diff --git a/crates/oxide_builtins/src/array.rs b/crates/oxide_builtins/src/array.rs index 9578660..a7534b1 100644 --- a/crates/oxide_builtins/src/array.rs +++ b/crates/oxide_builtins/src/array.rs @@ -46,6 +46,46 @@ fn get_this_array_ref(vm: &mut H, val: JsValue) -> Result<*mut JsObje Ok(ptr) } +/// 接受 array 或 ArrayLike (object with length property) — for read-only methods. +/// 返回 (object_ptr, length, is_real_array) +fn get_this_arraylike(vm: &mut H, val: JsValue) -> Result<(*mut JsObject, usize, bool), JsValue> { + if !val.is_object() { + return Err(array_type_error(vm, "Array.prototype method called on null or undefined")); + } + let ptr = val.as_js_object_ptr(); + if ptr.is_null() { + return Err(array_type_error(vm, "Array.prototype method called on null or undefined")); + } + let obj = unsafe { &*ptr }; + if obj.is_array() { + return Ok((ptr, obj.prop_count() as usize, true)); + } + // 读取 length 属性 + let length_key = vm.new_string("length"); + let length_si = vm.property_key_si(length_key); + let len_val = vm.ordinary_get(unsafe { &*ptr }, length_si, val) + .unwrap_or(JsValue::int(0)); + let len_num = vm.coerce_number_bounded(len_val).unwrap_or(0.0); + let len = if !len_num.is_finite() || len_num <= 0.0 { + 0 + } else { + (len_num as usize).min(MAX_DENSE_PROPS) + }; + Ok((ptr, len, false)) +} + +/// 从 arraylike 读取 index 位置元素 +fn arraylike_get(vm: &mut H, ptr: *mut JsObject, is_array: bool, i: usize) -> JsValue { + if is_array { + unsafe { (*ptr).get_prop_at(i) } + } else { + let key_str = vm.new_string(&i.to_string()); + let key_si = vm.property_key_si(key_str); + let recv = JsValue::from_js_object(ptr); + vm.ordinary_get(unsafe { &*ptr }, key_si, recv).unwrap_or(JsValue::undefined()) + } +} + fn require_callback(vm: &mut H, callback_val: JsValue) -> Result { if !callback_val.is_object() { return Err(array_type_error(vm, "callback is not a function")); @@ -350,15 +390,32 @@ pub fn array_to_string(vm: &mut H, args: &[u8]) -> NativeResult { pub fn array_index_of(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.indexOf called with {} args", args.len()); - let arr_ptr = array_ptr!(vm, args); - let arr = unsafe { &*arr_ptr }; - let n = arr.prop_count() as usize; - if args.len() < 2 { + let this_val = vm.reg(args[0]); + let (ptr, n, is_array) = match get_this_arraylike(vm, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + if n == 0 || args.len() < 2 { return NativeResult::Ok(JsValue::int(-1)); } let target = vm.reg(args[1]); - for i in 0..n { - if oxide_runtime_api::strict_eq(arr.get_prop_at(i), target) { + // fromIndex + let from_index = if args.len() >= 3 { + let v = vm.reg(args[2]); + let f = vm.coerce_number_bounded(v).unwrap_or(0.0); + let f = if f.is_nan() { 0.0 } else { f.trunc() }; + if f >= 0.0 { + (f as usize).min(n) + } else { + let from = n as f64 + f; + if from < 0.0 { 0 } else { from as usize } + } + } else { + 0 + }; + for i in from_index..n { + let elem = arraylike_get(vm, ptr, is_array, i); + if oxide_runtime_api::strict_eq(elem, target) { return NativeResult::Ok(JsValue::int(i as i32)); } } @@ -367,29 +424,52 @@ pub fn array_index_of(vm: &mut H, args: &[u8]) -> NativeResult { pub fn array_includes(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.includes called with {} args", args.len()); - let arr_ptr = array_ptr!(vm, args); - let arr = unsafe { &*arr_ptr }; - let n = arr.prop_count() as usize; - if args.len() < 2 { + let this_val = vm.reg(args[0]); + let (ptr, n, is_array) = match get_this_arraylike(vm, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + if n == 0 { return NativeResult::Ok(JsValue::bool(false)); } - let target = vm.reg(args[1]); - for i in 0..n { - let elem = arr.get_prop_at(i); - if elem.is_double() && target.is_double() { - let ea = elem.as_double(); - let ta = target.as_double(); - if ea.is_nan() && ta.is_nan() { - return NativeResult::Ok(JsValue::bool(true)); - } + let target = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let from_index = if args.len() >= 3 { + let v = vm.reg(args[2]); + let f = vm.coerce_number_bounded(v).unwrap_or(0.0); + let f = if f.is_nan() { 0.0 } else { f.trunc() }; + if f >= 0.0 { + (f as usize).min(n) + } else { + let from = n as f64 + f; + if from < 0.0 { 0 } else { from as usize } } - if oxide_runtime_api::strict_equality(elem, target) { + } else { + 0 + }; + for i in from_index..n { + let elem = arraylike_get(vm, ptr, is_array, i); + // SameValueZero: NaN === NaN, +0 === -0 + if same_value_zero(elem, target) { return NativeResult::Ok(JsValue::bool(true)); } } NativeResult::Ok(JsValue::bool(false)) } +/// SameValueZero (ES2015 7.2.10): NaN === NaN, +0 === -0 +fn same_value_zero(a: JsValue, b: JsValue) -> bool { + // 都是数字时特殊处理 + let a_is_num = a.is_int() || a.is_double(); + let b_is_num = b.is_int() || b.is_double(); + if a_is_num && b_is_num { + let av = if a.is_int() { a.as_int() as f64 } else { a.as_double() }; + let bv = if b.is_int() { b.as_int() as f64 } else { b.as_double() }; + if av.is_nan() && bv.is_nan() { return true; } + return av == bv; // +0 == -0 in Rust f64 + } + oxide_runtime_api::strict_equality(a, b) +} + pub fn array_reverse(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.reverse called with {} args", args.len()); let arr_ptr = array_ptr!(vm, args); @@ -911,13 +991,36 @@ pub fn array_at(vm: &mut H, args: &[u8]) -> NativeResult { pub fn array_last_index_of(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.lastIndexOf called with {} args", args.len()); - let arr_ptr = array_ptr!(vm, args); - let arr = unsafe { &*arr_ptr }; - let len = arr.prop_count() as i32; + let this_val = vm.reg(args[0]); + let (ptr, n, is_array) = match get_this_arraylike(vm, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + if n == 0 { + return NativeResult::Ok(JsValue::int(-1)); + } let search = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - for i in (0..len).rev() { - if oxide_runtime_api::strict_eq(arr.get_prop_at(i), search) { - return NativeResult::Ok(JsValue::int(i)); + // fromIndex (default: n-1) + let from_index_isize: isize = if args.len() >= 3 { + let v = vm.reg(args[2]); + let f = vm.coerce_number_bounded(v).unwrap_or(0.0); + if f.is_nan() { return NativeResult::Ok(JsValue::int(-1)); } + let f = f.trunc(); + if f >= 0.0 { + (f as isize).min(n as isize - 1) + } else { + n as isize + f as isize + } + } else { + n as isize - 1 + }; + if from_index_isize < 0 { + return NativeResult::Ok(JsValue::int(-1)); + } + for i in (0..=from_index_isize as usize).rev() { + let elem = arraylike_get(vm, ptr, is_array, i); + if oxide_runtime_api::strict_eq(elem, search) { + return NativeResult::Ok(JsValue::int(i as i32)); } } NativeResult::Ok(JsValue::int(-1)) diff --git a/crates/oxide_builtins/src/error.rs b/crates/oxide_builtins/src/error.rs index 598a457..80dc558 100644 --- a/crates/oxide_builtins/src/error.rs +++ b/crates/oxide_builtins/src/error.rs @@ -39,14 +39,24 @@ pub fn create_kind_error(host: &mut H, kind: &str, msg: &str) -> JsVa let obj = host .epoch() .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr))); + let sf = Arc::clone(host.kernel_core().perm_interner()); + let sh = Arc::clone(host.kernel_core().shape_forge()); + // 设置 .name = kind (ES spec: Error.prototype.name) + { + let si_name = sf.intern("name").0; + let shape = sh.make_shape(EMPTY_SHAPE_ID, si_name); + let name_val = host.new_string(kind); + unsafe { + (*obj).set_shape_id(shape); + (*obj).push_prop(name_val); + } + } if !msg.is_empty() { - let sf = Arc::clone(host.kernel_core().perm_interner()); - let sh = Arc::clone(host.kernel_core().shape_forge()); - let si = sf.intern("message").0; - let new_shape = sh.make_shape(EMPTY_SHAPE_ID, si); + let si_msg = sf.intern("message").0; + let shape = sh.make_shape(unsafe { (*obj).shape_id() }, si_msg); let msg_val = host.new_string(msg); unsafe { - (*obj).set_shape_id(new_shape); + (*obj).set_shape_id(shape); (*obj).push_prop(msg_val); } } diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index d6a1d1f..a498687 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -234,25 +234,24 @@ fn make_pair( si_name: u32, ) -> (P, P) { intern_label(string_forge, name); + let name_si = string_forge.intern(name).0; // intern the constructor's name value too let mut proto = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()); let mut ctor = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()); let proto_shape = shape_forge.make_shape(EMPTY_SHAPE_ID, si_constructor); proto.set_shape_id(proto_shape); - // proto shape: EMPTY -> "constructor" (slot 0) let ctor_shape1 = shape_forge.make_shape(EMPTY_SHAPE_ID, si_prototype); let ctor_shape2 = shape_forge.make_shape(ctor_shape1, si_name); ctor.set_shape_id(ctor_shape2); - // ctor shape: EMPTY -> "prototype" (slot 0) -> "name" (slot 1) ctor.set_function(true); - // Pre-allocate Vec slots so wire_ctor_proto can overwrite vec[0] later. - // Proto: 1 property ("constructor"), Ctor: 2 properties ("prototype", "name"). - proto.ensure_hash_props().push(JsValue::undefined()); // slot 0: "constructor" placeholder - ctor.ensure_hash_props().push(JsValue::undefined()); // slot 0: "prototype" placeholder - ctor.ensure_hash_props().push(JsValue::undefined()); // slot 1: "name" placeholder + // Pre-allocate slots: proto[0]="constructor", ctor[0]="prototype", ctor[1]="name" + proto.ensure_hash_props().push(JsValue::undefined()); // placeholder, set by wire_ctor_proto + ctor.ensure_hash_props().push(JsValue::undefined()); // placeholder, set by wire_ctor_proto + // Set the actual name value immediately (vec[1]) + ctor.ensure_hash_props().push(JsValue::perm_string(string_forge.string_ptr(name_si))); (P::new(proto), P::new(ctor)) } @@ -1150,6 +1149,12 @@ impl BuiltinWorld { // callers, all of which use fn-item expressions). wrapper.set_native_fn(Some(native_fn_ptr)); wrapper.set_native_arg_count(arg_count); + // 设置 .length (ES spec: Function.length = formal parameter count, excluding rest/default) + let si_length = string_forge.intern("length").0; + let length_shape = shape_forge.make_shape(wrapper.shape_id(), si_length); + wrapper.set_shape_id(length_shape); + wrapper.ensure_hash_props().push(JsValue::int(arg_count as i32)); + // 设置 .name let si_name = string_forge.intern("name").0; let name_shape = shape_forge.make_shape(wrapper.shape_id(), si_name); wrapper.set_shape_id(name_shape); diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index a8eb597..2fc09ea 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -49,6 +49,11 @@ macro_rules! bind_constructor { let ptr: *const () = ($ctor_fn as fn(&mut $crate::vm::Vm, &[u8]) -> oxide_runtime_api::NativeResult) as *const (); ctor.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(ptr) })); ctor.set_native_arg_count($nargs); + // 设置 constructor.length (Function.length = formal parameter count) + let length_si = $core.perm_interner().intern("length").0; + let length_shape = $core.shape_forge().make_shape(ctor.shape_id(), length_si); + ctor.set_shape_id(length_shape); + ctor.ensure_hash_props().push($crate::JsValue::int($nargs as i32)); }}; }