From 25bfe2d076589fdd684ab1488649fcc55c87d874 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 17 Sep 2026 11:58:41 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20array=20element=20=E6=97=8F?= =?UTF-8?q?=E6=8B=86=E5=87=BA=20element=20=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/array/element.rs | 568 +++++++++++++++++++++ crates/oxide_builtins/src/array/mod.rs | 556 +------------------- 2 files changed, 570 insertions(+), 554 deletions(-) create mode 100644 crates/oxide_builtins/src/array/element.rs diff --git a/crates/oxide_builtins/src/array/element.rs b/crates/oxide_builtins/src/array/element.rs new file mode 100644 index 00000000..5f2ccfb5 --- /dev/null +++ b/crates/oxide_builtins/src/array/element.rs @@ -0,0 +1,568 @@ +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; + +use oxide_runtime_api::{NativeResult, VmHost}; + +use crate::builtins_debug; +use crate::builtins_error; + +use super::common::{ + array_ptr, arraylike_get, arraylike_get_or_err, check_array_create_len, clamp_relative, create_new_array, + get_this_array_ref, get_this_arraylike, js_array_index, to_integer_or_infinity_bounded, +}; +use super::from::{array_species_create, create_data_property_or_throw, from_engine_error}; + +/// `Array.prototype.push(...items)`:追加元素到尾部,返回新长度。 +pub fn array_push(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.push called with {} args", args.len()); + let arr_ptr = array_ptr!(vm, args); + for &arg_reg in args.iter().skip(1) { + let val = vm.promote_if_needed_for_write_ptr(arr_ptr, vm.reg(arg_reg)); + // 元素写入须维护 array_prop_count(set_prop_at 对数组自动更新)。 + let arr = unsafe { &mut *arr_ptr }; + let idx = arr.prop_count(); + arr.set_prop_at(idx, val); + } + let len = unsafe { &*arr_ptr }.prop_count(); + NativeResult::Ok(JsValue::int(len as i32)) +} + +/// `Array.prototype.pop()`:移除并返回末位元素;空数组返回 undefined。 +pub fn array_pop(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.pop called with {} args", args.len()); + let arr_ptr = array_ptr!(vm, args); + let arr = unsafe { &mut *arr_ptr }; + let len = arr.prop_count(); + if len == 0 { + return NativeResult::Ok(JsValue::undefined()); + } + let last = arr.get_prop_at(len - 1); + arr.set_prop_count_fast(len - 1); + NativeResult::Ok(last) +} + +/// `Array.prototype.slice(start, end)`:复制区间元素返回新数组(支持负索引)。 +pub fn array_slice(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.slice called with {} args", args.len()); + let this_val = vm.reg(args[0]); + let (arr_ptr, n, is_array) = match get_this_arraylike(vm, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + let n_f = n as f64; + // ToIntegerOrInfinity(start),缺省 0;对象经 ToPrimitive(number)。 + let rel_start = if args.len() > 1 { + match to_integer_or_infinity_bounded(vm, vm.reg(args[1])) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + } + } else { + 0.0 + }; + // 规范:end 为 undefined 时使用 len;其余走 ToIntegerOrInfinity。 + let rel_end = if args.len() > 2 && !vm.reg(args[2]).is_undefined() { + match to_integer_or_infinity_bounded(vm, vm.reg(args[2])) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + } + } else { + n_f + }; + let start = clamp_relative(rel_start, n_f, n); + let end = clamp_relative(rel_end, n_f, n); + let count = end.saturating_sub(start); + if let Err(err) = check_array_create_len(vm, count) { + return NativeResult::Err(err); + } + // ArraySpeciesCreate(O, count):真数组按 constructor/@@species 构造目标。 + let a_ptr = match array_species_create(vm, this_val, is_array, count) { + Ok(p) => p, + Err(err) => return NativeResult::Err(err), + }; + // HasProperty + Get + CreateDataPropertyOrThrow 逐元素拷贝。 + let mut k = start; + let mut out_idx = 0usize; + while k < end { + let key_str = vm.new_string(&k.to_string()); + let key_si = vm.property_key_si(key_str); + if vm.resolve_property(unsafe { &*arr_ptr }, key_si).is_some() { + let elem = arraylike_get_or_err!(vm, arr_ptr, k); + let out_key = vm.new_string(&out_idx.to_string()); + let out_key_si = vm.property_key_si(out_key); + if let Err(err) = create_data_property_or_throw(vm, unsafe { &mut *a_ptr }, out_key_si, elem) { + return NativeResult::Err(err); + } + out_idx += 1; + } + k += 1; + } + // 收尾 Set(A, "length", final):普通 Set 语义(可触发继承 setter/异常)。 + let a_val = JsValue::from_js_object(a_ptr); + let length_key = vm.new_string("length"); + let length_si = vm.property_key_si(length_key); + if let Err(err) = vm.ordinary_set(unsafe { &mut *a_ptr }, length_si, js_array_index(out_idx), a_val, true) { + return NativeResult::Err(from_engine_error(vm, &err)); + } + NativeResult::Ok(JsValue::from_js_object(a_ptr)) +} + +/// `Array.prototype.splice(start, deleteCount, ...items)`:删除并/或插入元素, +/// 返回被删除元素组成的新数组。 +pub fn array_splice(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.splice called with {} args", args.len()); + let arr_ptr = array_ptr!(vm, args); + let arr = unsafe { &mut *arr_ptr }; + let n = arr.prop_count() as usize; + + let start = if args.len() > 1 { + let v = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN); + let s = v as i32; + if s < 0 { + (n as i32 + s).max(0) as usize + } else { + (s as usize).min(n) + } + } else { + 0 + }; + + let delete_count = if args.len() > 2 { + let v = vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN); + (v as usize).min(n - start) + } else { + n - start + }; + + let insert_count = if args.len() > 3 { args.len() - 3 } else { 0 }; + + let mut removed: Vec = Vec::new(); + for i in 0..delete_count { + removed.push(arr.get_prop_at(start + i)); + } + + if insert_count > delete_count { + let shift = insert_count - delete_count; + for i in (start + delete_count..n).rev() { + let val = arr.get_prop_at(i); + arr.set_prop_at(i + shift, val); + } + } else if insert_count < delete_count { + let shift = delete_count - insert_count; + for i in start + delete_count..n { + let val = arr.get_prop_at(i); + arr.set_prop_at(i - shift, val); + } + } + + for i in 0..insert_count { + arr.set_prop_at(start + i, vm.reg(args[3 + i])); + } + + let new_len = n + insert_count - delete_count; + arr.set_prop_count_fast(new_len); + + let removed_arr = create_new_array(vm, removed.len()); + unsafe { + for (i, val) in removed.iter().enumerate() { + (*removed_arr).set_prop_at(i, *val); + } + (*removed_arr).set_prop_count(removed.len()); + } + NativeResult::Ok(JsValue::from_js_object(removed_arr)) +} + +/// `Array.prototype.concat(...items)`:连接 this 与参数(数组参数展开)返回新数组。 +pub fn array_concat(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.concat called with {} args", args.len()); + let this_val = vm.reg(args[0]); + let (arr_ptr, n, _is_array) = match get_this_arraylike(vm, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + let mut all: Vec = Vec::new(); + if let Err(err) = check_array_create_len(vm, n) { + return NativeResult::Err(err); + } + for i in 0..n { + all.push(arraylike_get_or_err!(vm, arr_ptr, i)); + } + for &arg_reg in args.iter().skip(1) { + let val = vm.reg(arg_reg); + if val.is_object() { + let o_ptr = val.as_js_object_ptr(); + if !o_ptr.is_null() { + let o = unsafe { &*o_ptr }; + let on = o.prop_count() as usize; + // 真数组时展开元素。 + if o.is_array() && on > 0 { + for i in 0..on { + all.push(o.get_prop_at(i)); + } + continue; + } + } + } + all.push(val); + } + let new_arr = create_new_array(vm, all.len()); + unsafe { + for (i, val) in all.iter().enumerate() { + (*new_arr).set_prop_at(i, *val); + } + (*new_arr).set_prop_count(all.len()); + } + NativeResult::Ok(JsValue::from_js_object(new_arr)) +} + +/// `Array.prototype.join(separator)`:用分隔符连接元素字符串(null/undefined 视为空串)。 +pub fn array_join(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.join called with {} args", args.len()); + let this_val = vm.reg(args[0]); + let (arr_ptr, n, _is_array) = match get_this_arraylike(vm, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + let sep = if args.len() > 1 { + oxide_runtime_api::to_string(vm.reg(args[1])) + } else { + ",".to_string() + }; + let parts: Vec = match (0..n) + .map(|i| { + let v = arraylike_get(vm, arr_ptr, i)?; + if v.is_undefined() || v.is_null() { + Ok(String::new()) + } else { + Ok(oxide_runtime_api::to_string(v)) + } + }) + .collect::, JsValue>>() + { + Ok(v) => v, + Err(err) => return NativeResult::Err(err), + }; + let joined = parts.join(&sep); + NativeResult::Ok(vm.new_string(&joined)) +} + +/// `Array.prototype.toString`:委托给 join,默认用 `,` 分隔。 +pub fn array_to_string(vm: &mut H, args: &[u8]) -> NativeResult { + // Array.prototype.toString() 委托给 join,默认用 "," 分隔, + // 按规范忽略自身参数。 + array_join(vm, &[args[0]]) +} + +/// `Array.prototype.indexOf(searchElement, fromIndex)`:用严格相等查找首个匹配索引,找不到返回 -1。 +pub fn array_index_of(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.indexOf called with {} args", args.len()); + 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]); + // fromIndex:负索引从尾部折算,NaN 视为 0。 + 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_or_err!(vm, ptr, i); + if oxide_runtime_api::strict_equality(elem, target) { + return NativeResult::Ok(js_array_index(i)); + } + } + NativeResult::Ok(JsValue::int(-1)) +} + +/// `Array.prototype.includes(searchElement, fromIndex)`:用 SameValueZero 判断是否包含 +/// (NaN 视为存在、+0/-0 视为相同)。 +pub fn array_includes(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.includes called with {} args", args.len()); + 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 = 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 + } + } + } else { + 0 + }; + for i in from_index..n { + let elem = arraylike_get_or_err!(vm, ptr, i); + // SameValueZero:NaN 视为相等、+0 与 -0 视为相同。 + if oxide_runtime_api::same_value_zero(elem, target) { + return NativeResult::Ok(JsValue::bool(true)); + } + } + NativeResult::Ok(JsValue::bool(false)) +} + +/// `Array.prototype.reverse()`:原地反转元素顺序,返回 this。 +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); + let arr = unsafe { &mut *arr_ptr }; + let n = arr.prop_count() as usize; + let mut i = 0; + let mut j = n.saturating_sub(1); + while i < j { + let tmp = arr.get_prop_at(i); + arr.set_prop_at(i, arr.get_prop_at(j)); + arr.set_prop_at(j, tmp); + i += 1; + j = j.saturating_sub(1); + } + NativeResult::Ok(vm.reg(args[0])) +} + +/// `Array.prototype.flat(depth)`:按深度递归展开嵌套数组(含循环引用保护)返回新数组。 +pub fn array_flat(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.flat called with {} args", args.len()); + let arr_ptr = array_ptr!(vm, args); + let arr = unsafe { &*arr_ptr }; + let n = arr.prop_count() as usize; + let depth = if args.len() > 1 { + let n = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN); + if !n.is_finite() { + vm.kernel_core().config.max_call_depth + } else { + (n as i32).max(1) as usize + } + } else { + 1 + } + .min(vm.kernel_core().config.max_call_depth); + + fn flatten(items: &[JsValue], remaining_depth: usize, seen: &mut Vec<*mut JsObject>) -> Vec { + let mut out = Vec::new(); + for &v in items { + if remaining_depth > 0 && v.is_object() { + let ptr = v.as_js_object_ptr(); + if !ptr.is_null() { + if seen.iter().any(|seen_ptr| std::ptr::eq(*seen_ptr, ptr)) { + out.push(v); + continue; + } + let o = unsafe { &*ptr }; + if o.is_array() { + seen.push(ptr); + let on = o.prop_count() as usize; + let sub: Vec = (0..on).map(|i| o.get_prop_at(i)).collect(); + let flat = flatten(&sub, remaining_depth - 1, seen); + seen.pop(); + out.extend(flat); + continue; + } + } + } + out.push(v); + } + out + } + + let all: Vec = (0..n).map(|i| arr.get_prop_at(i)).collect(); + let mut seen = vec![arr_ptr]; + let flat = flatten(&all, depth, &mut seen); + let new_arr = create_new_array(vm, flat.len()); + unsafe { + for (i, val) in flat.iter().enumerate() { + (*new_arr).set_prop_at(i, *val); + } + (*new_arr).set_prop_count(flat.len()); + } + NativeResult::Ok(JsValue::from_js_object(new_arr)) +} + +/// `Array.prototype.shift()`:移除并返回首元素,其余元素前移;空数组返回 undefined。 +pub fn array_shift(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.shift called with {} args", args.len()); + let arr_ptr = array_ptr!(vm, args); + let arr = unsafe { &mut *arr_ptr }; + let len = arr.prop_count(); + if len == 0 { + return NativeResult::Ok(JsValue::undefined()); + } + let first = arr.get_prop_at(0); + for i in 1..len { + let v = arr.get_prop_at(i); + arr.set_prop_at(i - 1, v); + } + arr.set_prop_count_fast(len - 1); + NativeResult::Ok(first) +} + +/// `Array.prototype.unshift(...items)`:插入元素到头部,返回新长度。 +pub fn array_unshift(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.unshift called with {} args", args.len()); + let arr_ptr = array_ptr!(vm, args); + let arr = unsafe { &mut *arr_ptr }; + let len = arr.prop_count(); + let n_items = args.len().saturating_sub(1); + for i in (0..len as usize).rev() { + let v = arr.get_prop_at(i); + arr.set_prop_at(i + n_items, v); + } + for (j, &arg_reg) in args.iter().skip(1).enumerate() { + arr.set_prop_at(j, vm.reg(arg_reg)); + } + let new_len = len as usize + n_items; + arr.set_prop_count_fast(new_len); + NativeResult::Ok(JsValue::int(new_len as i32)) +} + +/// `Array.prototype.fill(value, start, end)`:用给定值填充区间,返回 this。 +pub fn array_fill(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.fill called with {} args", args.len()); + let arr_ptr = array_ptr!(vm, args); + let arr = unsafe { &mut *arr_ptr }; + let len = arr.prop_count() as isize; + let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let rel_start = if args.len() > 2 { + oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])) as isize + } else { + 0 + }; + let rel_end = if args.len() > 3 { + oxide_runtime_api::to_integer_or_infinity(vm.reg(args[3])) as isize + } else { + len + }; + let start = (if rel_start < 0 { (len + rel_start).max(0) } else { rel_start.min(len) }) as usize; + let end = (if rel_end < 0 { (len + rel_end).max(0) } else { rel_end.min(len) }) as usize; + for i in start..end { + arr.set_prop_at(i, value); + } + NativeResult::Ok(vm.reg(args[0])) +} + +/// `Array.prototype.copyWithin(target, start, end)`:在数组内部复制元素区间,返回 this。 +pub fn array_copy_within(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.copyWithin called with {} args", args.len()); + let arr_ptr = array_ptr!(vm, args); + let arr = unsafe { &mut *arr_ptr }; + let len = arr.prop_count() as isize; + if len == 0 { + return NativeResult::Ok(vm.reg(args[0])); + } + let rel_target = if args.len() > 1 { + oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1])) as isize + } else { + 0 + }; + let rel_start = if args.len() > 2 { + oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])) as isize + } else { + 0 + }; + let rel_end = if args.len() > 3 { + oxide_runtime_api::to_integer_or_infinity(vm.reg(args[3])) as isize + } else { + len + }; + let target = (if rel_target < 0 { (len + rel_target).max(0) } else { rel_target.min(len) }) as usize; + let start = (if rel_start < 0 { (len + rel_start).max(0) } else { rel_start.min(len) }) as usize; + let end = (if rel_end < 0 { (len + rel_end).max(0) } else { rel_end.min(len) }) as usize; + let len_usize = len as usize; + for (to, from) in (target..).zip(start..end) { + if to >= len_usize { + break; + } + let v = arr.get_prop_at(from); + arr.set_prop_at(to, v); + } + NativeResult::Ok(vm.reg(args[0])) +} + +/// `Array.prototype.at(index)`:按索引取元素,支持负索引;越界返回 undefined。 +pub fn array_at(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.at 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 mut index = if args.len() > 1 { + vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32 + } else { + 0 + }; + if index < 0 { + index += len; + } + if index < 0 || index >= len { + return NativeResult::Ok(JsValue::undefined()); + } + NativeResult::Ok(arr.get_prop_at(index)) +} + +/// `Array.prototype.lastIndexOf(searchElement, fromIndex)`:从后往前查找首个匹配索引。 +pub fn array_last_index_of(vm: &mut H, args: &[u8]) -> NativeResult { + builtins_debug!("Array.prototype.lastIndexOf called with {} args", args.len()); + 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() }; + // fromIndex(缺省: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_or_err!(vm, ptr, i); + if oxide_runtime_api::strict_equality(elem, search) { + return NativeResult::Ok(js_array_index(i)); + } + } + NativeResult::Ok(JsValue::int(-1)) +} diff --git a/crates/oxide_builtins/src/array/mod.rs b/crates/oxide_builtins/src/array/mod.rs index a958dbd9..84d41748 100644 --- a/crates/oxide_builtins/src/array/mod.rs +++ b/crates/oxide_builtins/src/array/mod.rs @@ -13,400 +13,8 @@ pub(crate) use common::*; mod from; pub use from::*; -/// `Array.prototype.push(...items)`:追加元素到尾部,返回新长度。 -pub fn array_push(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.push called with {} args", args.len()); - let arr_ptr = array_ptr!(vm, args); - for &arg_reg in args.iter().skip(1) { - let val = vm.promote_if_needed_for_write_ptr(arr_ptr, vm.reg(arg_reg)); - // 元素写入须维护 array_prop_count(set_prop_at 对数组自动更新)。 - let arr = unsafe { &mut *arr_ptr }; - let idx = arr.prop_count(); - arr.set_prop_at(idx, val); - } - let len = unsafe { &*arr_ptr }.prop_count(); - NativeResult::Ok(JsValue::int(len as i32)) -} - -/// `Array.prototype.pop()`:移除并返回末位元素;空数组返回 undefined。 -pub fn array_pop(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.pop called with {} args", args.len()); - let arr_ptr = array_ptr!(vm, args); - let arr = unsafe { &mut *arr_ptr }; - let len = arr.prop_count(); - if len == 0 { - return NativeResult::Ok(JsValue::undefined()); - } - let last = arr.get_prop_at(len - 1); - arr.set_prop_count_fast(len - 1); - NativeResult::Ok(last) -} - -/// `Array.prototype.slice(start, end)`:复制区间元素返回新数组(支持负索引)。 -pub fn array_slice(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.slice called with {} args", args.len()); - let this_val = vm.reg(args[0]); - let (arr_ptr, n, is_array) = match get_this_arraylike(vm, this_val) { - Ok(v) => v, - Err(e) => return NativeResult::Err(e), - }; - let n_f = n as f64; - // ToIntegerOrInfinity(start),缺省 0;对象经 ToPrimitive(number)。 - let rel_start = if args.len() > 1 { - match to_integer_or_infinity_bounded(vm, vm.reg(args[1])) { - Ok(v) => v, - Err(e) => return NativeResult::Err(e), - } - } else { - 0.0 - }; - // 规范:end 为 undefined 时使用 len;其余走 ToIntegerOrInfinity。 - let rel_end = if args.len() > 2 && !vm.reg(args[2]).is_undefined() { - match to_integer_or_infinity_bounded(vm, vm.reg(args[2])) { - Ok(v) => v, - Err(e) => return NativeResult::Err(e), - } - } else { - n_f - }; - let start = clamp_relative(rel_start, n_f, n); - let end = clamp_relative(rel_end, n_f, n); - let count = end.saturating_sub(start); - if let Err(err) = check_array_create_len(vm, count) { - return NativeResult::Err(err); - } - // ArraySpeciesCreate(O, count):真数组按 constructor/@@species 构造目标。 - let a_ptr = match array_species_create(vm, this_val, is_array, count) { - Ok(p) => p, - Err(err) => return NativeResult::Err(err), - }; - // HasProperty + Get + CreateDataPropertyOrThrow 逐元素拷贝。 - let mut k = start; - let mut out_idx = 0usize; - while k < end { - let key_str = vm.new_string(&k.to_string()); - let key_si = vm.property_key_si(key_str); - if vm.resolve_property(unsafe { &*arr_ptr }, key_si).is_some() { - let elem = arraylike_get_or_err!(vm, arr_ptr, k); - let out_key = vm.new_string(&out_idx.to_string()); - let out_key_si = vm.property_key_si(out_key); - if let Err(err) = create_data_property_or_throw(vm, unsafe { &mut *a_ptr }, out_key_si, elem) { - return NativeResult::Err(err); - } - out_idx += 1; - } - k += 1; - } - // 收尾 Set(A, "length", final):普通 Set 语义(可触发继承 setter/异常)。 - let a_val = JsValue::from_js_object(a_ptr); - let length_key = vm.new_string("length"); - let length_si = vm.property_key_si(length_key); - if let Err(err) = vm.ordinary_set(unsafe { &mut *a_ptr }, length_si, js_array_index(out_idx), a_val, true) { - return NativeResult::Err(from_engine_error(vm, &err)); - } - NativeResult::Ok(JsValue::from_js_object(a_ptr)) -} - -/// `Array.prototype.splice(start, deleteCount, ...items)`:删除并/或插入元素, -/// 返回被删除元素组成的新数组。 -pub fn array_splice(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.splice called with {} args", args.len()); - let arr_ptr = array_ptr!(vm, args); - let arr = unsafe { &mut *arr_ptr }; - let n = arr.prop_count() as usize; - - let start = if args.len() > 1 { - let v = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN); - let s = v as i32; - if s < 0 { - (n as i32 + s).max(0) as usize - } else { - (s as usize).min(n) - } - } else { - 0 - }; - - let delete_count = if args.len() > 2 { - let v = vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN); - (v as usize).min(n - start) - } else { - n - start - }; - - let insert_count = if args.len() > 3 { args.len() - 3 } else { 0 }; - - let mut removed: Vec = Vec::new(); - for i in 0..delete_count { - removed.push(arr.get_prop_at(start + i)); - } - - if insert_count > delete_count { - let shift = insert_count - delete_count; - for i in (start + delete_count..n).rev() { - let val = arr.get_prop_at(i); - arr.set_prop_at(i + shift, val); - } - } else if insert_count < delete_count { - let shift = delete_count - insert_count; - for i in start + delete_count..n { - let val = arr.get_prop_at(i); - arr.set_prop_at(i - shift, val); - } - } - - for i in 0..insert_count { - arr.set_prop_at(start + i, vm.reg(args[3 + i])); - } - - let new_len = n + insert_count - delete_count; - arr.set_prop_count_fast(new_len); - - let removed_arr = create_new_array(vm, removed.len()); - unsafe { - for (i, val) in removed.iter().enumerate() { - (*removed_arr).set_prop_at(i, *val); - } - (*removed_arr).set_prop_count(removed.len()); - } - NativeResult::Ok(JsValue::from_js_object(removed_arr)) -} - -/// `Array.prototype.concat(...items)`:连接 this 与参数(数组参数展开)返回新数组。 -pub fn array_concat(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.concat called with {} args", args.len()); - let this_val = vm.reg(args[0]); - let (arr_ptr, n, _is_array) = match get_this_arraylike(vm, this_val) { - Ok(v) => v, - Err(e) => return NativeResult::Err(e), - }; - let mut all: Vec = Vec::new(); - if let Err(err) = check_array_create_len(vm, n) { - return NativeResult::Err(err); - } - for i in 0..n { - all.push(arraylike_get_or_err!(vm, arr_ptr, i)); - } - for &arg_reg in args.iter().skip(1) { - let val = vm.reg(arg_reg); - if val.is_object() { - let o_ptr = val.as_js_object_ptr(); - if !o_ptr.is_null() { - let o = unsafe { &*o_ptr }; - let on = o.prop_count() as usize; - // 真数组时展开元素。 - if o.is_array() && on > 0 { - for i in 0..on { - all.push(o.get_prop_at(i)); - } - continue; - } - } - } - all.push(val); - } - let new_arr = create_new_array(vm, all.len()); - unsafe { - for (i, val) in all.iter().enumerate() { - (*new_arr).set_prop_at(i, *val); - } - (*new_arr).set_prop_count(all.len()); - } - NativeResult::Ok(JsValue::from_js_object(new_arr)) -} - -/// `Array.prototype.join(separator)`:用分隔符连接元素字符串(null/undefined 视为空串)。 -pub fn array_join(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.join called with {} args", args.len()); - let this_val = vm.reg(args[0]); - let (arr_ptr, n, _is_array) = match get_this_arraylike(vm, this_val) { - Ok(v) => v, - Err(e) => return NativeResult::Err(e), - }; - let sep = if args.len() > 1 { - oxide_runtime_api::to_string(vm.reg(args[1])) - } else { - ",".to_string() - }; - let parts: Vec = match (0..n) - .map(|i| { - let v = arraylike_get(vm, arr_ptr, i)?; - if v.is_undefined() || v.is_null() { - Ok(String::new()) - } else { - Ok(oxide_runtime_api::to_string(v)) - } - }) - .collect::, JsValue>>() - { - Ok(v) => v, - Err(err) => return NativeResult::Err(err), - }; - let joined = parts.join(&sep); - NativeResult::Ok(vm.new_string(&joined)) -} - -/// `Array.prototype.toString`:委托给 join,默认用 `,` 分隔。 -pub fn array_to_string(vm: &mut H, args: &[u8]) -> NativeResult { - // Array.prototype.toString() 委托给 join,默认用 "," 分隔, - // 按规范忽略自身参数。 - array_join(vm, &[args[0]]) -} - -/// `Array.prototype.indexOf(searchElement, fromIndex)`:用严格相等查找首个匹配索引,找不到返回 -1。 -pub fn array_index_of(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.indexOf called with {} args", args.len()); - 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]); - // fromIndex:负索引从尾部折算,NaN 视为 0。 - 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_or_err!(vm, ptr, i); - if oxide_runtime_api::strict_equality(elem, target) { - return NativeResult::Ok(js_array_index(i)); - } - } - NativeResult::Ok(JsValue::int(-1)) -} - -/// `Array.prototype.includes(searchElement, fromIndex)`:用 SameValueZero 判断是否包含 -/// (NaN 视为存在、+0/-0 视为相同)。 -pub fn array_includes(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.includes called with {} args", args.len()); - 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 = 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 - } - } - } else { - 0 - }; - for i in from_index..n { - let elem = arraylike_get_or_err!(vm, ptr, i); - // SameValueZero:NaN 视为相等、+0 与 -0 视为相同。 - if oxide_runtime_api::same_value_zero(elem, target) { - return NativeResult::Ok(JsValue::bool(true)); - } - } - NativeResult::Ok(JsValue::bool(false)) -} - -/// `Array.prototype.reverse()`:原地反转元素顺序,返回 this。 -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); - let arr = unsafe { &mut *arr_ptr }; - let n = arr.prop_count() as usize; - let mut i = 0; - let mut j = n.saturating_sub(1); - while i < j { - let tmp = arr.get_prop_at(i); - arr.set_prop_at(i, arr.get_prop_at(j)); - arr.set_prop_at(j, tmp); - i += 1; - j = j.saturating_sub(1); - } - NativeResult::Ok(vm.reg(args[0])) -} - -/// `Array.prototype.flat(depth)`:按深度递归展开嵌套数组(含循环引用保护)返回新数组。 -pub fn array_flat(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.flat called with {} args", args.len()); - let arr_ptr = array_ptr!(vm, args); - let arr = unsafe { &*arr_ptr }; - let n = arr.prop_count() as usize; - let depth = if args.len() > 1 { - let n = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN); - if !n.is_finite() { - vm.kernel_core().config.max_call_depth - } else { - (n as i32).max(1) as usize - } - } else { - 1 - } - .min(vm.kernel_core().config.max_call_depth); - - fn flatten(items: &[JsValue], remaining_depth: usize, seen: &mut Vec<*mut JsObject>) -> Vec { - let mut out = Vec::new(); - for &v in items { - if remaining_depth > 0 && v.is_object() { - let ptr = v.as_js_object_ptr(); - if !ptr.is_null() { - if seen.iter().any(|seen_ptr| std::ptr::eq(*seen_ptr, ptr)) { - out.push(v); - continue; - } - let o = unsafe { &*ptr }; - if o.is_array() { - seen.push(ptr); - let on = o.prop_count() as usize; - let sub: Vec = (0..on).map(|i| o.get_prop_at(i)).collect(); - let flat = flatten(&sub, remaining_depth - 1, seen); - seen.pop(); - out.extend(flat); - continue; - } - } - } - out.push(v); - } - out - } - - let all: Vec = (0..n).map(|i| arr.get_prop_at(i)).collect(); - let mut seen = vec![arr_ptr]; - let flat = flatten(&all, depth, &mut seen); - let new_arr = create_new_array(vm, flat.len()); - unsafe { - for (i, val) in flat.iter().enumerate() { - (*new_arr).set_prop_at(i, *val); - } - (*new_arr).set_prop_count(flat.len()); - } - NativeResult::Ok(JsValue::from_js_object(new_arr)) -} +mod element; +pub use element::*; /// `Array.prototype.forEach(callback, thisArg)`:对每个元素调用 callback,返回 undefined。 pub fn array_for_each(vm: &mut H, args: &[u8]) -> NativeResult { @@ -731,166 +339,6 @@ pub fn array_flat_map(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::from_js_object(new_arr)) } -/// `Array.prototype.shift()`:移除并返回首元素,其余元素前移;空数组返回 undefined。 -pub fn array_shift(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.shift called with {} args", args.len()); - let arr_ptr = array_ptr!(vm, args); - let arr = unsafe { &mut *arr_ptr }; - let len = arr.prop_count(); - if len == 0 { - return NativeResult::Ok(JsValue::undefined()); - } - let first = arr.get_prop_at(0); - for i in 1..len { - let v = arr.get_prop_at(i); - arr.set_prop_at(i - 1, v); - } - arr.set_prop_count_fast(len - 1); - NativeResult::Ok(first) -} - -/// `Array.prototype.unshift(...items)`:插入元素到头部,返回新长度。 -pub fn array_unshift(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.unshift called with {} args", args.len()); - let arr_ptr = array_ptr!(vm, args); - let arr = unsafe { &mut *arr_ptr }; - let len = arr.prop_count(); - let n_items = args.len().saturating_sub(1); - for i in (0..len as usize).rev() { - let v = arr.get_prop_at(i); - arr.set_prop_at(i + n_items, v); - } - for (j, &arg_reg) in args.iter().skip(1).enumerate() { - arr.set_prop_at(j, vm.reg(arg_reg)); - } - let new_len = len as usize + n_items; - arr.set_prop_count_fast(new_len); - NativeResult::Ok(JsValue::int(new_len as i32)) -} - -/// `Array.prototype.fill(value, start, end)`:用给定值填充区间,返回 this。 -pub fn array_fill(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.fill called with {} args", args.len()); - let arr_ptr = array_ptr!(vm, args); - let arr = unsafe { &mut *arr_ptr }; - let len = arr.prop_count() as isize; - let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let rel_start = if args.len() > 2 { - oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])) as isize - } else { - 0 - }; - let rel_end = if args.len() > 3 { - oxide_runtime_api::to_integer_or_infinity(vm.reg(args[3])) as isize - } else { - len - }; - let start = (if rel_start < 0 { (len + rel_start).max(0) } else { rel_start.min(len) }) as usize; - let end = (if rel_end < 0 { (len + rel_end).max(0) } else { rel_end.min(len) }) as usize; - for i in start..end { - arr.set_prop_at(i, value); - } - NativeResult::Ok(vm.reg(args[0])) -} - -/// `Array.prototype.copyWithin(target, start, end)`:在数组内部复制元素区间,返回 this。 -pub fn array_copy_within(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.copyWithin called with {} args", args.len()); - let arr_ptr = array_ptr!(vm, args); - let arr = unsafe { &mut *arr_ptr }; - let len = arr.prop_count() as isize; - if len == 0 { - return NativeResult::Ok(vm.reg(args[0])); - } - let rel_target = if args.len() > 1 { - oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1])) as isize - } else { - 0 - }; - let rel_start = if args.len() > 2 { - oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])) as isize - } else { - 0 - }; - let rel_end = if args.len() > 3 { - oxide_runtime_api::to_integer_or_infinity(vm.reg(args[3])) as isize - } else { - len - }; - let target = (if rel_target < 0 { (len + rel_target).max(0) } else { rel_target.min(len) }) as usize; - let start = (if rel_start < 0 { (len + rel_start).max(0) } else { rel_start.min(len) }) as usize; - let end = (if rel_end < 0 { (len + rel_end).max(0) } else { rel_end.min(len) }) as usize; - let len_usize = len as usize; - for (to, from) in (target..).zip(start..end) { - if to >= len_usize { - break; - } - let v = arr.get_prop_at(from); - arr.set_prop_at(to, v); - } - NativeResult::Ok(vm.reg(args[0])) -} - -/// `Array.prototype.at(index)`:按索引取元素,支持负索引;越界返回 undefined。 -pub fn array_at(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.at 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 mut index = if args.len() > 1 { - vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32 - } else { - 0 - }; - if index < 0 { - index += len; - } - if index < 0 || index >= len { - return NativeResult::Ok(JsValue::undefined()); - } - NativeResult::Ok(arr.get_prop_at(index)) -} - -/// `Array.prototype.lastIndexOf(searchElement, fromIndex)`:从后往前查找首个匹配索引。 -pub fn array_last_index_of(vm: &mut H, args: &[u8]) -> NativeResult { - builtins_debug!("Array.prototype.lastIndexOf called with {} args", args.len()); - 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() }; - // fromIndex(缺省: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_or_err!(vm, ptr, i); - if oxide_runtime_api::strict_equality(elem, search) { - return NativeResult::Ok(js_array_index(i)); - } - } - NativeResult::Ok(JsValue::int(-1)) -} - /// `Array.prototype.findIndex(callback, thisArg)`:返回首个 callback 为真的索引,否则 -1。 pub fn array_find_index(vm: &mut H, args: &[u8]) -> NativeResult { builtins_debug!("Array.prototype.findIndex called with {} args", args.len());