mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
1852 lines
76 KiB
Rust
1852 lines
76 KiB
Rust
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||
use oxide_types::object::{JsObject, PropAttributes, MAX_DENSE_PROPS};
|
||
use oxide_types::private_key::make_well_known_symbol_key;
|
||
use oxide_types::value::JsValue;
|
||
|
||
use oxide_runtime_api::{NativeResult, VmHost};
|
||
|
||
use crate::builtins_debug;
|
||
use crate::builtins_error;
|
||
|
||
mod common;
|
||
pub(crate) use common::*;
|
||
|
||
/// JS `Array()` 构造逻辑:单个数字参数创建指定长度空数组,其余情况把参数作为元素。
|
||
pub fn array_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||
let proto_val = JsValue::from_js_object(proto);
|
||
|
||
if args.len() == 2 {
|
||
let val = vm.reg(args[1]);
|
||
if val.is_int() || val.is_double() {
|
||
let n = match array_length_arg(vm, val) {
|
||
Ok(n) => n,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n, vm.epoch().bump()));
|
||
return NativeResult::Ok(JsValue::from_js_object(arr));
|
||
}
|
||
}
|
||
|
||
let n_elems = if args.len() > 1 { args.len() - 1 } else { 0 };
|
||
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, n_elems, vm.epoch().bump()));
|
||
for i in 0..n_elems {
|
||
unsafe {
|
||
(*arr).set_prop_at(i, vm.reg(args[1 + i]));
|
||
}
|
||
}
|
||
unsafe {
|
||
(*arr).set_prop_count(n_elems);
|
||
}
|
||
NativeResult::Ok(JsValue::from_js_object(arr))
|
||
}
|
||
|
||
/// `Array.isArray(value)`:参数是否为真正的 Array 对象。
|
||
pub fn array_is_array<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
if args.len() < 2 {
|
||
return NativeResult::Ok(JsValue::bool(false));
|
||
}
|
||
let val = vm.reg(args[1]);
|
||
if !val.is_object() {
|
||
return NativeResult::Ok(JsValue::bool(false));
|
||
}
|
||
let ptr = val.as_js_object_ptr();
|
||
if ptr.is_null() {
|
||
return NativeResult::Ok(JsValue::bool(false));
|
||
}
|
||
NativeResult::Ok(JsValue::bool(unsafe { &*ptr }.is_array()))
|
||
}
|
||
|
||
/// `Array.of(...items)`:以参数为元素构造新数组。
|
||
///
|
||
/// # 步骤
|
||
/// 1. 按 `this`(构造函数 C)以参数个数构造结果对象:C 可构造时 `new C(len)`,
|
||
/// 否则新建普通数组。
|
||
/// 2. 元素逐个以 CreateDataProperty 语义写入结果对象。
|
||
/// 3. 收尾设置结果对象的 length(可触发继承的 length setter)。
|
||
pub fn array_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
let n_elems = if args.len() > 1 { args.len() - 1 } else { 0 };
|
||
let c = vm.reg(args[0]);
|
||
let (a_ptr, is_array) = match construct_array_from_result(vm, c, &[JsValue::int(n_elems as i32)]) {
|
||
Ok(v) => v,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
for i in 0..n_elems {
|
||
let val = vm.reg(args[1 + i]);
|
||
if let Err(err) = array_from_set_prop(vm, a_ptr, is_array, i, val) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
}
|
||
if let Err(err) = array_from_set_length(vm, a_ptr, is_array, n_elems) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
NativeResult::Ok(JsValue::from_js_object(a_ptr))
|
||
}
|
||
|
||
/// `Array.from(items, mapFn?, thisArg?)`:从可迭代对象或 array-like 构造新数组,
|
||
/// 可选 `mapFn` 逐元素映射(`thisArg` 作回调 this),返回新数组。
|
||
///
|
||
/// # 步骤
|
||
/// 1. `items` 为 null/undefined 抛 TypeError;`mapFn` 非 undefined 时须可调用。
|
||
/// 2. 读取 `@@iterator` 判定可迭代(不调用):可迭代路径先按 `this`(构造函数 C)
|
||
/// `new C()` 构造结果对象,再经迭代协议逐个取元素;array-like 路径先 ToObject
|
||
/// 读 `length`,再 `new C(len)` 构造结果对象,逐索引取值(缺失属性取 undefined)。
|
||
/// 3. 元素经 `mapFn` 映射后以 CreateDataProperty 语义写入结果对象,收尾设置 length。
|
||
/// 4. 迭代器或 `mapFn` 抛错时透传原异常值;迭代中途异常先执行 IteratorClose。
|
||
///
|
||
/// # 边界与前提
|
||
/// - C 不可构造(非函数/箭头/普通 native 方法)时回退到普通空数组。
|
||
/// - 写入元素遇不可扩展对象或不可配置属性冲突时抛 TypeError(CreateDataPropertyOrThrow)。
|
||
///
|
||
/// # 注意事项
|
||
/// - 迭代只读包装器 `next`/`done`/`value` 属性,与 spread 物化共用同一迭代协议。
|
||
/// - new.target 不向被构造的 C 传播(与 Reflect.construct 同一简化)。
|
||
pub fn array_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
let items = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
if items.is_null() || items.is_undefined() {
|
||
return NativeResult::Err(array_type_error(vm, "Array.from requires an iterable or array-like object"));
|
||
}
|
||
let mapfn = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
let mapping = if mapfn.is_undefined() {
|
||
None
|
||
} else {
|
||
match require_callback(vm, mapfn) {
|
||
Ok(cb) => Some(cb),
|
||
Err(err) => return NativeResult::Err(err),
|
||
}
|
||
};
|
||
let this_arg = if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() };
|
||
let c = vm.reg(args[0]);
|
||
|
||
// 先只读取 @@iterator 判定可迭代(不调用),保证"构造结果对象"先于"获取迭代器",
|
||
// 与规范的执行序一致。
|
||
let iterable = match crate::iterator::peek_iterator_method(vm, items) {
|
||
Ok(b) => b,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
|
||
let a_ptr;
|
||
let is_array;
|
||
if iterable {
|
||
// 可迭代路径:先 new C() 得到结果对象,再获取迭代器逐个取元素。
|
||
a_ptr = match construct_array_from_result(vm, c, &[]) {
|
||
Ok((ptr, is_arr)) => {
|
||
is_array = is_arr;
|
||
ptr
|
||
}
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
|
||
let iterator = match crate::iterator::make_iterator_for_value(vm, items) {
|
||
Ok(it) => it,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
let next_si = vm.kernel_core().perm_interner().intern("next").0;
|
||
let done_si = vm.kernel_core().perm_interner().intern("done").0;
|
||
let value_si = vm.kernel_core().perm_interner().intern("value").0;
|
||
let mut k = 0usize;
|
||
let iter_result: Result<(), JsValue> = (|| {
|
||
loop {
|
||
let iter_obj = unsafe { &*iterator.as_js_object_ptr() };
|
||
let next_fn = match vm.ordinary_get(iter_obj, next_si, iterator) {
|
||
Ok(v) => v,
|
||
Err(err) => return Err(from_engine_error(vm, &err)),
|
||
};
|
||
let result = match vm.call_function_sync(next_fn, iterator, &[]) {
|
||
Ok(v) => v,
|
||
Err(err) => return Err(from_engine_error(vm, &err)),
|
||
};
|
||
if !result.is_object() {
|
||
return Err(array_type_error(vm, "iterator result is not an object"));
|
||
}
|
||
let result_obj = unsafe { &*result.as_js_object_ptr() };
|
||
let done = match vm.ordinary_get(result_obj, done_si, result) {
|
||
Ok(v) => oxide_runtime_api::to_boolean(v),
|
||
Err(err) => return Err(from_engine_error(vm, &err)),
|
||
};
|
||
if done {
|
||
break;
|
||
}
|
||
let elem = match vm.ordinary_get(result_obj, value_si, result) {
|
||
Ok(v) => v,
|
||
Err(err) => return Err(from_engine_error(vm, &err)),
|
||
};
|
||
let mapped = match mapping {
|
||
Some(cb) => match invoke_native_callback(vm, cb, this_arg, &[elem, JsValue::int(k as i32)]) {
|
||
NativeResult::Ok(m) => m,
|
||
NativeResult::Err(err) => return Err(err),
|
||
NativeResult::TailCall { .. } => {
|
||
return Err(array_type_error(vm, "unexpected tail call in array callback"))
|
||
}
|
||
},
|
||
None => elem,
|
||
};
|
||
array_from_set_prop(vm, a_ptr, is_array, k, mapped)?;
|
||
k += 1;
|
||
}
|
||
Ok(())
|
||
})();
|
||
if let Err(err) = iter_result {
|
||
// IteratorClose:异常退出先关闭迭代器(return() 转发给内层),再抛原异常。
|
||
close_iterator(vm, iterator);
|
||
return NativeResult::Err(err);
|
||
}
|
||
if let Err(err) = array_from_set_length(vm, a_ptr, is_array, k) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
} else {
|
||
// array-like 路径:ToObject 后读 length,构造结果对象,逐索引取值。
|
||
let obj_val = match oxide_runtime_api::to_object(items, vm) {
|
||
Ok(o) => o,
|
||
Err(err) => return NativeResult::Err(crate::error::create_type_error(vm, &err)),
|
||
};
|
||
let obj_ptr = obj_val.as_js_object_ptr();
|
||
let obj = unsafe { &*obj_ptr };
|
||
let length_key = vm.new_string("length");
|
||
let length_si = vm.property_key_si(length_key);
|
||
let len_val = match vm.ordinary_get(obj, length_si, obj_val) {
|
||
Ok(v) => v,
|
||
Err(err) => return NativeResult::Err(from_engine_error(vm, &err)),
|
||
};
|
||
let len_u64 = oxide_runtime_api::to_length(len_val);
|
||
if len_u64 > u32::MAX as u64 {
|
||
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid array length"));
|
||
}
|
||
let len = len_u64.min(MAX_DENSE_PROPS as u64) as usize;
|
||
|
||
a_ptr = match construct_array_from_result(vm, c, &[JsValue::int(len as i32)]) {
|
||
Ok((ptr, is_arr)) => {
|
||
is_array = is_arr;
|
||
ptr
|
||
}
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
for i in 0..len {
|
||
let elem = arraylike_get_or_err!(vm, obj_ptr, i);
|
||
let mapped = match mapping {
|
||
Some(cb) => match invoke_native_callback(vm, cb, this_arg, &[elem, js_array_index(i)]) {
|
||
NativeResult::Ok(m) => m,
|
||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||
},
|
||
None => elem,
|
||
};
|
||
if let Err(err) = array_from_set_prop(vm, a_ptr, is_array, i, mapped) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
}
|
||
if let Err(err) = array_from_set_length(vm, a_ptr, is_array, len) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
}
|
||
|
||
NativeResult::Ok(JsValue::from_js_object(a_ptr))
|
||
}
|
||
|
||
/// 引擎调用边界抛出的错误恢复为原始异常值(透传),否则按错误文本构造对应 kind 的错误。
|
||
fn from_engine_error<H: VmHost>(vm: &mut H, err: &str) -> JsValue {
|
||
vm.take_uncaught_value()
|
||
.unwrap_or_else(|| crate::error::create_from_text(vm, err))
|
||
}
|
||
|
||
/// 按构造函数 C 构造 Array.from 的结果对象:C 可构造时以 C.prototype 分配 `this`
|
||
/// 后调用 C(返回非对象时回退 `this`),否则返回普通空数组。
|
||
///
|
||
/// # 返回值
|
||
/// `(结果对象指针, 是否为真数组)`。
|
||
fn construct_array_from_result<H: VmHost>(
|
||
vm: &mut H, c: JsValue, args: &[JsValue],
|
||
) -> Result<(*mut JsObject, bool), JsValue> {
|
||
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||
let proto_val = JsValue::from_js_object(proto);
|
||
let mut fallback_array = || {
|
||
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, proto_val, 0, vm.epoch().bump()));
|
||
(arr, true)
|
||
};
|
||
if !c.is_object() {
|
||
return Ok(fallback_array());
|
||
}
|
||
let c_obj = unsafe { &*c.as_js_object_ptr() };
|
||
// 不可构造:箭头函数、非构造器标记的 native 方法。
|
||
let constructible = c_obj.is_function()
|
||
&& !c_obj.is_arrow()
|
||
&& !(c_obj.native_fn().is_some() && c_obj.type_tag != JsObject::OBJ_TYPE_CONSTRUCTOR);
|
||
if !constructible {
|
||
return Ok(fallback_array());
|
||
}
|
||
|
||
// 仿 Reflect.construct:以 C.prototype 分配 this 后调用 C。
|
||
let proto_si = vm.kernel_core().perm_interner().intern("prototype").0;
|
||
let ctor_proto = match vm.resolve_property(c_obj, proto_si) {
|
||
Some(p) if p.is_object() => p,
|
||
_ => JsValue::from_js_object(vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject),
|
||
};
|
||
let this_ptr = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, ctor_proto));
|
||
let this_val = JsValue::from_js_object(this_ptr);
|
||
match vm.call_function_sync(c, this_val, args) {
|
||
Ok(ret) if ret.is_object() => {
|
||
let ret_ptr = ret.as_js_object_ptr();
|
||
let ret_tag = unsafe { &*ret_ptr }.type_tag;
|
||
// 原始值包装对象(如 `Object(4)` 返回的 Number 包装)的索引属性存储不完整,
|
||
// 回退到刚分配的对象;真数组/普通对象按规范使用构造返回对象。
|
||
if matches!(
|
||
ret_tag,
|
||
JsObject::OBJ_TYPE_STRING_OBJ | JsObject::OBJ_TYPE_NUMBER_OBJ | JsObject::OBJ_TYPE_BOOLEAN_OBJ
|
||
) {
|
||
return Ok((this_ptr, false));
|
||
}
|
||
Ok((ret_ptr, unsafe { &*ret_ptr }.is_array()))
|
||
}
|
||
Ok(_) => Ok((this_ptr, false)),
|
||
Err(err) => Err(from_engine_error(vm, &err)),
|
||
}
|
||
}
|
||
|
||
/// ArraySpeciesCreate(originalArray, length):真数组读取 constructor / @@species
|
||
/// 并构造目标对象;array-like(非数组)直接 ArrayCreate(length)。
|
||
fn array_species_create<H: VmHost>(
|
||
vm: &mut H, o_val: JsValue, is_array: bool, count: usize,
|
||
) -> Result<*mut JsObject, JsValue> {
|
||
if !is_array {
|
||
return Ok(create_new_array(vm, count));
|
||
}
|
||
let o_ptr = o_val.as_js_object_ptr();
|
||
let o_obj = unsafe { &*o_ptr };
|
||
let ctor_key = vm.kernel_core().perm_interner().intern("constructor").0;
|
||
let mut c = match vm.ordinary_get(o_obj, ctor_key, o_val) {
|
||
Ok(v) => v,
|
||
Err(msg) => return Err(from_engine_error(vm, &msg)),
|
||
};
|
||
if !is_constructor_value(c) {
|
||
if !c.is_object() {
|
||
return Err(crate::error::create_type_error(vm, "Species constructor not a constructor"));
|
||
}
|
||
let species_key = make_well_known_symbol_key(10);
|
||
let s = match vm.ordinary_get(unsafe { &*c.as_js_object_ptr() }, species_key, c) {
|
||
Ok(v) => v,
|
||
Err(msg) => return Err(from_engine_error(vm, &msg)),
|
||
};
|
||
c = if s.is_null() { JsValue::undefined() } else { s };
|
||
}
|
||
if c.is_undefined() {
|
||
return Ok(create_new_array(vm, count));
|
||
}
|
||
if !is_constructor_value(c) {
|
||
return Err(crate::error::create_type_error(vm, "Species constructor not a constructor"));
|
||
}
|
||
match construct_array_from_result(vm, c, &[js_array_index(count)]) {
|
||
Ok((ptr, _)) => Ok(ptr),
|
||
Err(err) => Err(err),
|
||
}
|
||
}
|
||
|
||
/// CreateDataPropertyOrThrow:以 `{writable, enumerable, configurable}=true` 写入属性,
|
||
/// 对象不可扩展或与既有不可配置属性冲突时抛 TypeError。
|
||
fn create_data_property_or_throw<H: VmHost>(
|
||
vm: &mut H, obj: &mut JsObject, prop_name_si: u32, val: JsValue,
|
||
) -> Result<(), JsValue> {
|
||
if vm.get_own_property_slot(obj, prop_name_si).is_none() && !obj.is_extensible() {
|
||
return Err(array_type_error(vm, "Cannot define property: object is not extensible"));
|
||
}
|
||
match vm.define_data_property(obj, prop_name_si, val, PropAttributes::new(true, true, true)) {
|
||
Ok(()) => Ok(()),
|
||
Err(err) => Err(crate::error::create_type_error(vm, &err)),
|
||
}
|
||
}
|
||
|
||
/// 把元素写入 Array.from 的结果对象:真数组走密集元素区,普通对象走
|
||
/// CreateDataProperty 语义(属性名按十进制索引字符串)。
|
||
fn array_from_set_prop<H: VmHost>(
|
||
vm: &mut H, a: *mut JsObject, is_array: bool, i: usize, val: JsValue,
|
||
) -> Result<(), JsValue> {
|
||
if is_array {
|
||
unsafe {
|
||
(*a).set_prop_at(i, val);
|
||
}
|
||
return Ok(());
|
||
}
|
||
let key = vm.new_string(&i.to_string());
|
||
let key_si = vm.property_key_si(key);
|
||
create_data_property_or_throw(vm, unsafe { &mut *a }, key_si, val)
|
||
}
|
||
|
||
/// 收尾设置 Array.from 结果对象的 length:真数组直接改元素计数,普通对象走
|
||
/// 普通 Set(可触发继承的 length setter 并透传其异常)。
|
||
fn array_from_set_length<H: VmHost>(vm: &mut H, a: *mut JsObject, is_array: bool, len: usize) -> Result<(), JsValue> {
|
||
if is_array {
|
||
unsafe {
|
||
(*a).set_prop_count_fast(len);
|
||
}
|
||
return Ok(());
|
||
}
|
||
let a_obj = unsafe { &mut *a };
|
||
let length_key = vm.new_string("length");
|
||
let length_si = vm.property_key_si(length_key);
|
||
let a_val = JsValue::from_js_object(a);
|
||
match vm.ordinary_set(a_obj, length_si, JsValue::int(len as i32), a_val, true) {
|
||
Ok(()) => Ok(()),
|
||
Err(err) => Err(from_engine_error(vm, &err)),
|
||
}
|
||
}
|
||
|
||
/// IteratorClose:迭代异常退出时调用迭代器包装器的 `return()`(转发给内层迭代器),
|
||
/// 丢弃其自身错误,保留在途异常。
|
||
fn close_iterator<H: VmHost>(vm: &mut H, iterator: JsValue) {
|
||
if !iterator.is_object() {
|
||
return;
|
||
}
|
||
let iter_obj = unsafe { &*iterator.as_js_object_ptr() };
|
||
let return_si = vm.kernel_core().perm_interner().intern("return").0;
|
||
if let Ok(ret) = vm.ordinary_get(iter_obj, return_si, iterator) {
|
||
if ret.is_object() && unsafe { &*ret.as_js_object_ptr() }.is_function() {
|
||
// return() 的抛错被忽略,其值不得外泄进槽覆盖在途异常。
|
||
let saved_uncaught = vm.take_uncaught_value();
|
||
let _ = vm.call_function_sync(ret, iterator, &[]);
|
||
vm.restore_uncaught_value(saved_uncaught);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// `Array.prototype.push(...items)`:追加元素到尾部,返回新长度。
|
||
pub fn array_push<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<JsValue> = 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<H: VmHost>(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<JsValue> = 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<H: VmHost>(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<String> = 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::<Result<Vec<String>, 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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<JsValue> {
|
||
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<JsValue> = (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<JsValue> = (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.forEach(callback, thisArg)`:对每个元素调用 callback,返回 undefined。
|
||
pub fn array_for_each<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.forEach called with {} args", args.len());
|
||
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args);
|
||
let o_val = vm.reg(args[0]);
|
||
if args.len() < 2 {
|
||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let callback_val = match require_callback(vm, vm.reg(args[1])) {
|
||
Ok(callback) => callback,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
for i in 0..n {
|
||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||
NativeResult::Ok(_) => {}
|
||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::undefined())
|
||
}
|
||
|
||
/// `Array.prototype.map(callback, thisArg)`:对每个元素调用 callback 生成新数组。
|
||
pub fn array_map<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.map called with {} args", args.len());
|
||
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args);
|
||
let o_val = vm.reg(args[0]);
|
||
if args.len() < 2 {
|
||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let callback_val = match require_callback(vm, vm.reg(args[1])) {
|
||
Ok(callback) => callback,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
let new_arr = create_new_array(vm, n);
|
||
if let Err(err) = check_array_create_len(vm, n) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
for i in 0..n {
|
||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||
NativeResult::Ok(mapped) => unsafe {
|
||
(*new_arr).set_prop_at(i, mapped);
|
||
},
|
||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||
}
|
||
}
|
||
unsafe {
|
||
(*new_arr).set_prop_count(n);
|
||
}
|
||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||
}
|
||
|
||
/// `Array.prototype.filter(callback, thisArg)`:保留 callback 返回真值的元素形成新数组。
|
||
pub fn array_filter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.filter called with {} args", args.len());
|
||
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args);
|
||
let o_val = vm.reg(args[0]);
|
||
if args.len() < 2 {
|
||
builtins_error!("Array.prototype.filter: invalid receiver");
|
||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let callback_val = match require_callback(vm, vm.reg(args[1])) {
|
||
Ok(callback) => callback,
|
||
Err(err) => {
|
||
builtins_error!("Array.prototype.filter: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
};
|
||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
let mut kept: Vec<JsValue> = Vec::new();
|
||
if let Err(err) = check_array_create_len(vm, n) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
for i in 0..n {
|
||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||
NativeResult::Ok(result_val) => {
|
||
if oxide_runtime_api::to_boolean(result_val) {
|
||
kept.push(elem);
|
||
}
|
||
}
|
||
NativeResult::Err(err) => return NativeResult::Err(err),
|
||
NativeResult::TailCall { .. } => return unexpected_tail_call_error(vm),
|
||
}
|
||
}
|
||
let new_arr = create_new_array(vm, kept.len());
|
||
unsafe {
|
||
for (i, val) in kept.iter().enumerate() {
|
||
(*new_arr).set_prop_at(i, *val);
|
||
}
|
||
(*new_arr).set_prop_count(kept.len());
|
||
}
|
||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||
}
|
||
|
||
/// `Array.prototype.reduce(callback, initialValue)`:从左到右累计归约;
|
||
/// 空数组且无初始值抛 TypeError。
|
||
pub fn array_reduce<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.reduce called with {} args", args.len());
|
||
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args);
|
||
let o_val = vm.reg(args[0]);
|
||
if n == 0 && args.len() < 3 {
|
||
builtins_error!("Array.prototype.reduce: invalid receiver");
|
||
return NativeResult::Err(array_type_error(vm, "Reduce of empty array with no initial value"));
|
||
}
|
||
if args.len() < 2 {
|
||
builtins_error!("Array.prototype.reduce: invalid receiver");
|
||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let callback_val = match require_callback(vm, vm.reg(args[1])) {
|
||
Ok(callback) => callback,
|
||
Err(err) => {
|
||
builtins_error!("Array.prototype.reduce: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
};
|
||
let has_initial = args.len() > 2;
|
||
let mut accumulator;
|
||
let start_idx;
|
||
if has_initial {
|
||
accumulator = vm.reg(args[2]);
|
||
start_idx = 0;
|
||
} else {
|
||
accumulator = arraylike_get_or_err!(vm, arr_ptr, 0);
|
||
start_idx = 1;
|
||
}
|
||
let this_val = JsValue::undefined();
|
||
for i in start_idx..n {
|
||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||
match invoke_native_callback(vm, callback_val, this_val, &[accumulator, elem, js_array_index(i), o_val]) {
|
||
NativeResult::Ok(result) => accumulator = result,
|
||
NativeResult::Err(err) => {
|
||
builtins_error!("Array.prototype.reduce: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
NativeResult::TailCall { .. } => {
|
||
builtins_error!("Array.prototype.reduce: invalid receiver");
|
||
return unexpected_tail_call_error(vm);
|
||
}
|
||
}
|
||
}
|
||
NativeResult::Ok(accumulator)
|
||
}
|
||
|
||
/// `Array.prototype.find(callback, thisArg)`:返回首个 callback 为真的元素,否则 undefined。
|
||
pub fn array_find<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.find called with {} args", args.len());
|
||
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args);
|
||
let o_val = vm.reg(args[0]);
|
||
if args.len() < 2 {
|
||
builtins_error!("Array.prototype.find: invalid receiver");
|
||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let callback_val = match require_callback(vm, vm.reg(args[1])) {
|
||
Ok(callback) => callback,
|
||
Err(err) => {
|
||
builtins_error!("Array.prototype.find: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
};
|
||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
for i in 0..n {
|
||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||
NativeResult::Ok(result_val) => {
|
||
if oxide_runtime_api::to_boolean(result_val) {
|
||
return NativeResult::Ok(elem);
|
||
}
|
||
}
|
||
NativeResult::Err(err) => {
|
||
builtins_error!("Array.prototype.find: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
NativeResult::TailCall { .. } => {
|
||
builtins_error!("Array.prototype.find: invalid receiver");
|
||
return unexpected_tail_call_error(vm);
|
||
}
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::undefined())
|
||
}
|
||
|
||
/// `Array.prototype.some(callback, thisArg)`:任一元素满足 callback 返回 true。
|
||
pub fn array_some<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.some called with {} args", args.len());
|
||
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args);
|
||
let o_val = vm.reg(args[0]);
|
||
if args.len() < 2 {
|
||
builtins_error!("Array.prototype.some: invalid receiver");
|
||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let callback_val = match require_callback(vm, vm.reg(args[1])) {
|
||
Ok(callback) => callback,
|
||
Err(err) => {
|
||
builtins_error!("Array.prototype.some: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
};
|
||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
for i in 0..n {
|
||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||
NativeResult::Ok(result_val) => {
|
||
if oxide_runtime_api::to_boolean(result_val) {
|
||
return NativeResult::Ok(JsValue::bool(true));
|
||
}
|
||
}
|
||
NativeResult::Err(err) => {
|
||
builtins_error!("Array.prototype.some: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
NativeResult::TailCall { .. } => {
|
||
builtins_error!("Array.prototype.some: invalid receiver");
|
||
return unexpected_tail_call_error(vm);
|
||
}
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::bool(false))
|
||
}
|
||
|
||
/// `Array.prototype.every(callback, thisArg)`:所有元素满足 callback 才返回 true。
|
||
pub fn array_every<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.every called with {} args", args.len());
|
||
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args);
|
||
let o_val = vm.reg(args[0]);
|
||
if args.len() < 2 {
|
||
builtins_error!("Array.prototype.every: invalid receiver");
|
||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let callback_val = match require_callback(vm, vm.reg(args[1])) {
|
||
Ok(callback) => callback,
|
||
Err(err) => {
|
||
builtins_error!("Array.prototype.every: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
};
|
||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
for i in 0..n {
|
||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||
NativeResult::Ok(result_val) => {
|
||
if !oxide_runtime_api::to_boolean(result_val) {
|
||
return NativeResult::Ok(JsValue::bool(false));
|
||
}
|
||
}
|
||
NativeResult::Err(err) => {
|
||
builtins_error!("Array.prototype.every: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
NativeResult::TailCall { .. } => {
|
||
builtins_error!("Array.prototype.every: invalid receiver");
|
||
return unexpected_tail_call_error(vm);
|
||
}
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::bool(true))
|
||
}
|
||
|
||
/// `Array.prototype.flatMap(callback, thisArg)`:map 后把返回的数组展开一层。
|
||
pub fn array_flat_map<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.flatMap called with {} args", args.len());
|
||
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args);
|
||
let o_val = vm.reg(args[0]);
|
||
if args.len() < 2 {
|
||
builtins_error!("Array.prototype.flatMap: invalid receiver");
|
||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let callback_val = match require_callback(vm, vm.reg(args[1])) {
|
||
Ok(callback) => callback,
|
||
Err(err) => {
|
||
builtins_error!("Array.prototype.flatMap: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
};
|
||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
let mut flat: Vec<JsValue> = Vec::new();
|
||
if let Err(err) = check_array_create_len(vm, n) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
for i in 0..n {
|
||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||
NativeResult::Ok(result) => {
|
||
if result.is_object() {
|
||
let r_ptr = result.as_js_object_ptr();
|
||
if !r_ptr.is_null() {
|
||
let r = unsafe { &*r_ptr };
|
||
if r.is_array() {
|
||
let rn = r.prop_count() as usize;
|
||
for j in 0..rn {
|
||
flat.push(r.get_prop_at(j));
|
||
}
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
flat.push(result);
|
||
}
|
||
NativeResult::Err(err) => {
|
||
builtins_error!("Array.prototype.flatMap: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
NativeResult::TailCall { .. } => {
|
||
builtins_error!("Array.prototype.flatMap: invalid receiver");
|
||
return unexpected_tail_call_error(vm);
|
||
}
|
||
}
|
||
}
|
||
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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.findIndex called with {} args", args.len());
|
||
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args);
|
||
let o_val = vm.reg(args[0]);
|
||
if args.len() < 2 {
|
||
builtins_error!("Array.prototype.findIndex: invalid receiver");
|
||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let callback_val = match require_callback(vm, vm.reg(args[1])) {
|
||
Ok(callback) => callback,
|
||
Err(err) => {
|
||
builtins_error!("Array.prototype.findIndex: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
};
|
||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
for i in 0..n {
|
||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||
NativeResult::Ok(r) => {
|
||
if oxide_runtime_api::to_boolean(r) {
|
||
return NativeResult::Ok(js_array_index(i));
|
||
}
|
||
}
|
||
NativeResult::Err(err) => {
|
||
builtins_error!("Array.prototype.findIndex: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
NativeResult::TailCall { .. } => {
|
||
builtins_error!("Array.prototype.findIndex: invalid receiver");
|
||
return unexpected_tail_call_error(vm);
|
||
}
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::int(-1))
|
||
}
|
||
|
||
/// `Array.prototype.findLast(callback, thisArg)`:从后往前返回首个 callback 为真的元素。
|
||
pub fn array_find_last<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.findLast called with {} args", args.len());
|
||
let (arr_ptr, n) = {
|
||
let (arr_ptr, len, _is_array) = array_ptr_len3!(vm, args);
|
||
(arr_ptr, len as i32)
|
||
};
|
||
let o_val = vm.reg(args[0]);
|
||
if args.len() < 2 {
|
||
builtins_error!("Array.prototype.findLast: invalid receiver");
|
||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let callback_val = match require_callback(vm, vm.reg(args[1])) {
|
||
Ok(callback) => callback,
|
||
Err(err) => {
|
||
builtins_error!("Array.prototype.findLast: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
};
|
||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
for i in (0..n).rev() {
|
||
let elem = unsafe { (*arr_ptr).get_prop_at(i) };
|
||
match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i), o_val]) {
|
||
NativeResult::Ok(r) => {
|
||
if oxide_runtime_api::to_boolean(r) {
|
||
return NativeResult::Ok(elem);
|
||
}
|
||
}
|
||
NativeResult::Err(err) => {
|
||
builtins_error!("Array.prototype.findLast: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
NativeResult::TailCall { .. } => {
|
||
builtins_error!("Array.prototype.findLast: invalid receiver");
|
||
return unexpected_tail_call_error(vm);
|
||
}
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::undefined())
|
||
}
|
||
|
||
/// `Array.prototype.reduceRight(callback, initialValue)`:从右到左累计归约。
|
||
pub fn array_reduce_right<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.reduceRight called with {} args", args.len());
|
||
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args);
|
||
let o_val = vm.reg(args[0]);
|
||
if args.len() < 2 {
|
||
builtins_error!("Array.prototype.reduceRight: invalid receiver");
|
||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let callback_val = match require_callback(vm, vm.reg(args[1])) {
|
||
Ok(callback) => callback,
|
||
Err(err) => {
|
||
builtins_error!("Array.prototype.reduceRight: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
};
|
||
let (mut acc, start_idx): (JsValue, i32) = if args.len() > 2 {
|
||
(vm.reg(args[2]), n as i32 - 1)
|
||
} else {
|
||
if n == 0 {
|
||
builtins_error!("Array.prototype.reduceRight: invalid receiver");
|
||
return NativeResult::Err(array_type_error(vm, "Reduce of empty array with no initial value"));
|
||
}
|
||
(unsafe { (*arr_ptr).get_prop_at(n - 1) }, n as i32 - 2)
|
||
};
|
||
for i in (0..=start_idx).rev() {
|
||
let elem = unsafe { (*arr_ptr).get_prop_at(i as usize) };
|
||
match invoke_native_callback(vm, callback_val, JsValue::undefined(), &[acc, elem, JsValue::int(i), o_val]) {
|
||
NativeResult::Ok(r) => acc = r,
|
||
NativeResult::Err(err) => {
|
||
builtins_error!("Array.prototype.reduceRight: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
NativeResult::TailCall { .. } => {
|
||
builtins_error!("Array.prototype.reduceRight: invalid receiver");
|
||
return unexpected_tail_call_error(vm);
|
||
}
|
||
}
|
||
}
|
||
NativeResult::Ok(acc)
|
||
}
|
||
|
||
/// `Array.prototype.sort(compareFn)`:原地排序。默认按字符串字典序;
|
||
/// 提供比较函数时按其返回值(<0/=0/>0)排序,回调抛错则中止。
|
||
/// 对元素向量执行 Array.prototype.sort 的比较语义(原地)。
|
||
/// 比较回调抛错时中止并把异常返回;默认按字符串字典序。
|
||
fn sort_values_inner<H: VmHost>(vm: &mut H, vals: &mut [JsValue], comparator: Option<JsValue>) -> Result<(), JsValue> {
|
||
let mut sort_error = None;
|
||
vals.sort_by(|a, b| {
|
||
if sort_error.is_some() {
|
||
return std::cmp::Ordering::Equal;
|
||
}
|
||
if let Some(callback) = comparator {
|
||
match invoke_native_callback(vm, callback, JsValue::undefined(), &[*a, *b]) {
|
||
NativeResult::Ok(result) => {
|
||
let n = oxide_runtime_api::to_number(result);
|
||
if n.is_nan() || n == 0.0 {
|
||
std::cmp::Ordering::Equal
|
||
} else if n < 0.0 {
|
||
std::cmp::Ordering::Less
|
||
} else {
|
||
std::cmp::Ordering::Greater
|
||
}
|
||
}
|
||
NativeResult::Err(err) => {
|
||
sort_error = Some(err);
|
||
std::cmp::Ordering::Equal
|
||
}
|
||
NativeResult::TailCall { .. } => {
|
||
sort_error = Some(crate::error::create_type_error(vm, "unexpected tail call in array callback"));
|
||
std::cmp::Ordering::Equal
|
||
}
|
||
}
|
||
} else {
|
||
let sa = oxide_runtime_api::to_string(*a);
|
||
let sb = oxide_runtime_api::to_string(*b);
|
||
sa.cmp(&sb)
|
||
}
|
||
});
|
||
if let Some(err) = sort_error {
|
||
return Err(err);
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
/// 解析 sort/toSorted 的比较回调参数(undefined 视为默认排序)。
|
||
fn parse_sort_comparator<H: VmHost>(vm: &mut H, candidate: JsValue) -> Result<Option<JsValue>, JsValue> {
|
||
if candidate.is_undefined() {
|
||
return Ok(None);
|
||
}
|
||
match require_callback(vm, candidate) {
|
||
Ok(callback) => Ok(Some(callback)),
|
||
Err(err) => Err(err),
|
||
}
|
||
}
|
||
|
||
pub fn array_sort<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.sort called with {} args", args.len());
|
||
let arr_ptr = array_ptr!(vm, args);
|
||
let len = unsafe { (*arr_ptr).prop_count() as usize };
|
||
let mut vals: Vec<JsValue> = (0..len).map(|i| unsafe { (*arr_ptr).get_prop_at(i) }).collect();
|
||
let comparator = if args.len() > 1 {
|
||
match parse_sort_comparator(vm, vm.reg(args[1])) {
|
||
Ok(c) => c,
|
||
Err(err) => {
|
||
builtins_error!("Array.prototype.sort: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
}
|
||
} else {
|
||
None
|
||
};
|
||
if let Err(err) = sort_values_inner(vm, &mut vals, comparator) {
|
||
builtins_error!("Array.prototype.sort: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
let arr = unsafe { &mut *arr_ptr };
|
||
for (i, &v) in vals.iter().enumerate() {
|
||
arr.set_prop_at(i, v);
|
||
}
|
||
NativeResult::Ok(vm.reg(args[0]))
|
||
}
|
||
|
||
/// `Array.prototype.values()`:返回迭代数组元素的迭代器(%ArrayIteratorPrototype%)。
|
||
pub fn array_values<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.values called with {} args", args.len());
|
||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||
match make_array_iterator(vm, this_val, ARRAY_ITER_KIND_VALUES) {
|
||
Ok(iterator) => NativeResult::Ok(iterator),
|
||
Err(err) => {
|
||
builtins_error!("Array.prototype.values: invalid receiver");
|
||
NativeResult::Err(err)
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Array Iterator 的内部 kind 编码:0=values,1=keys,2=entries。
|
||
/// TypedArray 迭代器复用同一编码与 %ArrayIteratorPrototype%(规范同族)。
|
||
pub(crate) const ARRAY_ITER_KIND_VALUES: i32 = 0;
|
||
pub(crate) const ARRAY_ITER_KIND_KEYS: i32 = 1;
|
||
pub(crate) const ARRAY_ITER_KIND_ENTRIES: i32 = 2;
|
||
|
||
const ARRAY_ITER_TARGET_PROP: &str = "__target__";
|
||
const ARRAY_ITER_INDEX_PROP: &str = "__index__";
|
||
const ARRAY_ITER_KIND_PROP: &str = "__kind__";
|
||
|
||
/// 创建 Array/TA Iterator 对象:记录目标、当前下标与迭代种类,原型挂
|
||
/// `%ArrayIteratorPrototype%`(链到 `%IteratorPrototype%`,Array 与 TA 共享)。
|
||
/// `next` 不挂实例 own——由原型提供。
|
||
pub(crate) fn make_array_iterator<H: VmHost>(vm: &mut H, this_val: JsValue, kind: i32) -> Result<JsValue, JsValue> {
|
||
let target = match oxide_runtime_api::to_object(this_val, vm) {
|
||
Ok(v) => v,
|
||
Err(msg) => return Err(array_type_error(vm, &msg)),
|
||
};
|
||
let array_iter_proto = vm.session().builtin_world().array_iterator_proto.as_ptr() as *mut JsObject;
|
||
let iter = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(array_iter_proto)));
|
||
let target_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_TARGET_PROP).0;
|
||
let index_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_INDEX_PROP).0;
|
||
let kind_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_KIND_PROP).0;
|
||
let iter_ref = unsafe { &mut *iter };
|
||
vm.set_or_create_prop_value(iter_ref, target_si, target);
|
||
vm.set_or_create_prop_value(iter_ref, index_si, JsValue::int(0));
|
||
vm.set_or_create_prop_value(iter_ref, kind_si, JsValue::int(kind));
|
||
Ok(JsValue::from_js_object(iter))
|
||
}
|
||
|
||
/// Array.prototype.entries():返回按索引产出 [index, value] 对的迭代器。
|
||
pub fn array_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.entries called with {} args", args.len());
|
||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||
match make_array_iterator(vm, this_val, ARRAY_ITER_KIND_ENTRIES) {
|
||
Ok(iter) => NativeResult::Ok(iter),
|
||
Err(err) => NativeResult::Err(err),
|
||
}
|
||
}
|
||
|
||
/// Array.prototype.keys():返回按索引产出下标的迭代器。
|
||
pub fn array_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.keys called with {} args", args.len());
|
||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||
match make_array_iterator(vm, this_val, ARRAY_ITER_KIND_KEYS) {
|
||
Ok(iter) => NativeResult::Ok(iter),
|
||
Err(err) => NativeResult::Err(err),
|
||
}
|
||
}
|
||
|
||
/// Array Iterator 的 next:读取目标当前下标处的值,按种类产出后推进下标。
|
||
pub fn array_iterator_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||
if !this_val.is_object() {
|
||
return NativeResult::Err(array_type_error(vm, "Array Iterator next called on non-object"));
|
||
}
|
||
let iter = unsafe { &mut *this_val.as_js_object_ptr() };
|
||
let target_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_TARGET_PROP).0;
|
||
let index_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_INDEX_PROP).0;
|
||
let kind_si = vm.kernel_core().perm_interner().intern(ARRAY_ITER_KIND_PROP).0;
|
||
let target = match vm.ordinary_get(iter, target_si, this_val) {
|
||
Ok(v) if v.is_undefined() => {
|
||
return NativeResult::Ok(crate::iterator::make_iter_result(vm, JsValue::undefined(), true));
|
||
}
|
||
Ok(v) if v.is_object() => v,
|
||
_ => return NativeResult::Err(array_type_error(vm, "Array Iterator has no iterated object")),
|
||
};
|
||
let index = vm
|
||
.ordinary_get(iter, index_si, this_val)
|
||
.ok()
|
||
.and_then(|v| if v.is_int() { Some(v.as_int()) } else { None })
|
||
.unwrap_or(0);
|
||
let kind = vm
|
||
.ordinary_get(iter, kind_si, this_val)
|
||
.ok()
|
||
.and_then(|v| if v.is_int() { Some(v.as_int()) } else { None })
|
||
.unwrap_or(ARRAY_ITER_KIND_VALUES);
|
||
|
||
let target_obj = unsafe { &*target.as_js_object_ptr() };
|
||
// TypedArray 目标走底层 buffer 读取(元素非普通属性,length 语义不同):
|
||
// 与 Array 共享 %ArrayIteratorPrototype%,须在此分支区分两种取数路径。
|
||
if target_obj.is_typed_array_obj() {
|
||
let view = match crate::typed_array::get_typed_array_data(vm, target) {
|
||
Ok(view) => view,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
if (index as usize) >= view.length {
|
||
vm.set_or_create_prop_value(iter, target_si, JsValue::undefined());
|
||
return NativeResult::Ok(crate::iterator::make_iter_result(vm, JsValue::undefined(), true));
|
||
}
|
||
let element = match crate::typed_array::typed_array_element_get(vm, target_obj, index as u32) {
|
||
Ok(v) => v,
|
||
Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)),
|
||
};
|
||
let value = match kind {
|
||
ARRAY_ITER_KIND_KEYS => JsValue::int(index),
|
||
ARRAY_ITER_KIND_ENTRIES => {
|
||
let pair = create_new_array(vm, 2);
|
||
let pair_ref = unsafe { &mut *pair };
|
||
pair_ref.set_prop_at(0, JsValue::int(index));
|
||
pair_ref.set_prop_at(1, element);
|
||
JsValue::from_js_object(pair)
|
||
}
|
||
_ => element,
|
||
};
|
||
if let Err(msg) = vm.ordinary_set(iter, index_si, JsValue::int(index + 1), this_val, true) {
|
||
return NativeResult::Err(crate::error::create_from_text(vm, &msg));
|
||
}
|
||
return NativeResult::Ok(crate::iterator::make_iter_result(vm, value, false));
|
||
}
|
||
|
||
let length_key = vm.new_string("length");
|
||
let length_si = vm.property_key_si(length_key);
|
||
let len_val = match vm.ordinary_get(target_obj, length_si, target) {
|
||
Ok(v) => v,
|
||
Err(err) => return NativeResult::Err(crate::error::create_from_text(vm, &err)),
|
||
};
|
||
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 };
|
||
|
||
if (index as usize) >= len {
|
||
vm.set_or_create_prop_value(iter, target_si, JsValue::undefined());
|
||
return NativeResult::Ok(crate::iterator::make_iter_result(vm, JsValue::undefined(), true));
|
||
}
|
||
|
||
let _ = target_obj.is_array();
|
||
let element = arraylike_get_or_err!(vm, target.as_js_object_ptr(), index as usize);
|
||
let value = match kind {
|
||
ARRAY_ITER_KIND_KEYS => JsValue::int(index),
|
||
ARRAY_ITER_KIND_ENTRIES => {
|
||
let pair = create_new_array(vm, 2);
|
||
let pair_ref = unsafe { &mut *pair };
|
||
pair_ref.set_prop_at(0, JsValue::int(index));
|
||
pair_ref.set_prop_at(1, element);
|
||
JsValue::from_js_object(pair)
|
||
}
|
||
_ => element,
|
||
};
|
||
if let Err(msg) = vm.ordinary_set(iter, index_si, JsValue::int(index + 1), this_val, true) {
|
||
return NativeResult::Err(crate::error::create_from_text(vm, &msg));
|
||
}
|
||
NativeResult::Ok(crate::iterator::make_iter_result(vm, value, false))
|
||
}
|
||
|
||
/// Array.prototype.findLastIndex(callback, thisArg):从后往前返回首个 callback 为真的下标。
|
||
pub fn array_find_last_index<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.findLastIndex called with {} args", args.len());
|
||
let (arr_ptr, n, _is_array) = array_ptr_len3!(vm, args);
|
||
let o_val = vm.reg(args[0]);
|
||
if args.len() < 2 {
|
||
builtins_error!("Array.prototype.findLastIndex: invalid receiver");
|
||
return NativeResult::Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let callback_val = match require_callback(vm, vm.reg(args[1])) {
|
||
Ok(callback) => callback,
|
||
Err(err) => {
|
||
builtins_error!("Array.prototype.findLastIndex: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
};
|
||
let this_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
for i in (0..n).rev() {
|
||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||
match invoke_native_callback(vm, callback_val, this_val, &[elem, js_array_index(i), o_val]) {
|
||
NativeResult::Ok(r) => {
|
||
if oxide_runtime_api::to_boolean(r) {
|
||
return NativeResult::Ok(js_array_index(i));
|
||
}
|
||
}
|
||
NativeResult::Err(err) => {
|
||
builtins_error!("Array.prototype.findLastIndex: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
NativeResult::TailCall { .. } => {
|
||
builtins_error!("Array.prototype.findLastIndex: invalid receiver");
|
||
return unexpected_tail_call_error(vm);
|
||
}
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::int(-1))
|
||
}
|
||
|
||
/// Array.prototype.toReversed():返回元素逆序的新数组,原数组不变。
|
||
pub fn array_to_reversed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.toReversed 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(err) => return NativeResult::Err(err),
|
||
};
|
||
let new_arr = create_new_array(vm, n);
|
||
if let Err(err) = check_array_create_len(vm, n) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
// ????????????????getter ???????????
|
||
for i in (0..n).rev() {
|
||
let elem = arraylike_get_or_err!(vm, arr_ptr, i);
|
||
unsafe {
|
||
(*new_arr).set_prop_at(n - 1 - i, elem);
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||
}
|
||
|
||
/// Array.prototype.with(index, value):返回把指定下标替换为新值的新数组,原数组不变。
|
||
pub fn array_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.with 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(err) => return NativeResult::Err(err),
|
||
};
|
||
let index_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||
let idx = match vm.coerce_number_bounded(index_val) {
|
||
Ok(v) => v,
|
||
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
|
||
};
|
||
// ToIntegerOrInfinity:NaN/-0/+0 -> 0,其余截断。
|
||
let rel = if idx.is_nan() || idx == 0.0 { 0.0 } else { idx.trunc() };
|
||
let actual = if rel < 0.0 { n as f64 + rel } else { rel };
|
||
if actual < 0.0 || actual >= n as f64 {
|
||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid index for Array.prototype.with"));
|
||
}
|
||
let actual_idx = actual as usize;
|
||
let value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||
if let Err(err) = check_array_create_len(vm, n) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
let new_arr = create_new_array(vm, n);
|
||
for i in 0..n {
|
||
// 规范:被替换位置不执行 Get。
|
||
let elem = if i == actual_idx { value } else { arraylike_get_or_err!(vm, arr_ptr, i) };
|
||
unsafe {
|
||
(*new_arr).set_prop_at(i, elem);
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||
}
|
||
pub fn array_to_sorted<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.toSorted called with {} args", args.len());
|
||
// 规范先校验 compareFn 可调用,再读取 this/length。
|
||
let comparator = if args.len() > 1 {
|
||
match parse_sort_comparator(vm, vm.reg(args[1])) {
|
||
Ok(c) => c,
|
||
Err(err) => return NativeResult::Err(err),
|
||
}
|
||
} else {
|
||
None
|
||
};
|
||
let this_val = vm.reg(args[0]);
|
||
let (arr_ptr, n, _is_array) = match get_this_arraylike(vm, this_val) {
|
||
Ok(v) => v,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
if let Err(err) = check_array_create_len(vm, n) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
let mut vals: Vec<JsValue> = match (0..n)
|
||
.map(|i| arraylike_get(vm, arr_ptr, i))
|
||
.collect::<Result<Vec<JsValue>, JsValue>>()
|
||
{
|
||
Ok(v) => v,
|
||
Err(err) => return NativeResult::Err(err),
|
||
};
|
||
if let Err(err) = sort_values_inner(vm, &mut vals, comparator) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
let new_arr = create_new_array(vm, n);
|
||
for (i, &v) in vals.iter().enumerate() {
|
||
unsafe {
|
||
(*new_arr).set_prop_at(i, v);
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||
}
|
||
pub fn array_to_spliced<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||
builtins_debug!("Array.prototype.toSpliced 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(err) => return NativeResult::Err(err),
|
||
};
|
||
let relative_start = if args.len() > 1 {
|
||
match vm.coerce_number_bounded(vm.reg(args[1])) {
|
||
Ok(v) => v,
|
||
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
|
||
}
|
||
} else {
|
||
0.0
|
||
};
|
||
let relative_start = if relative_start.is_nan() { 0.0 } else { relative_start.trunc() };
|
||
let actual_start = if relative_start < 0.0 {
|
||
(n as f64 + relative_start).max(0.0) as usize
|
||
} else {
|
||
relative_start.min(n as f64) as usize
|
||
};
|
||
// 缺省规则:start 与 deleteCount 都缺省 -> 拷贝;仅缺省 deleteCount -> 删到末尾。
|
||
let delete_count = if args.len() <= 1 {
|
||
0
|
||
} else if args.len() == 2 {
|
||
n - actual_start
|
||
} else {
|
||
let dc = match vm.coerce_number_bounded(vm.reg(args[2])) {
|
||
Ok(v) => v,
|
||
Err(msg) => return NativeResult::Err(from_engine_error(vm, &msg)),
|
||
};
|
||
let dc = if dc.is_nan() || dc <= 0.0 { 0 } else { dc.trunc() as usize };
|
||
dc.min(n - actual_start)
|
||
};
|
||
let insert_count = if args.len() > 3 { args.len() - 3 } else { 0 };
|
||
// ???newLen = len + insertCount - actualDeleteCount?> 2**53-1 ? TypeError?
|
||
// ArrayCreate(newLen) ? 2**32-1 ? RangeError??????/???????
|
||
let new_len = n.saturating_add(insert_count).saturating_sub(delete_count);
|
||
if new_len > 9_007_199_254_740_991 {
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "Invalid array length"));
|
||
}
|
||
if let Err(err) = check_array_create_len(vm, new_len) {
|
||
return NativeResult::Err(err);
|
||
}
|
||
let mut out: Vec<JsValue> = Vec::with_capacity(new_len);
|
||
out.extend(
|
||
match (0..actual_start)
|
||
.map(|i| arraylike_get(vm, arr_ptr, i))
|
||
.collect::<Result<Vec<JsValue>, JsValue>>()
|
||
{
|
||
Ok(v) => v,
|
||
Err(err) => return NativeResult::Err(err),
|
||
},
|
||
);
|
||
for k in 0..insert_count {
|
||
out.push(vm.reg(args[3 + k]));
|
||
}
|
||
out.extend(
|
||
match (actual_start + delete_count..n)
|
||
.map(|i| arraylike_get(vm, arr_ptr, i))
|
||
.collect::<Result<Vec<JsValue>, JsValue>>()
|
||
{
|
||
Ok(v) => v,
|
||
Err(err) => return NativeResult::Err(err),
|
||
},
|
||
);
|
||
let new_arr = create_new_array(vm, out.len());
|
||
for (i, &v) in out.iter().enumerate() {
|
||
unsafe {
|
||
(*new_arr).set_prop_at(i, v);
|
||
}
|
||
}
|
||
NativeResult::Ok(JsValue::from_js_object(new_arr))
|
||
}
|