feat: TypedArray 原型方法族全量解锁(回调/查找/迭代/排序/ES2023 copy 族)
This commit is contained in:
@@ -567,6 +567,66 @@ pub(crate) fn set_entries_iter_next<H: VmHost>(vm: &mut H, args: &[u8]) -> Nativ
|
|||||||
map_set_next_dispatch::<H>(vm, args, MapSetMode::SetEntries)
|
map_set_next_dispatch::<H>(vm, args, MapSetMode::SetEntries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum TypedArrayMode {
|
||||||
|
Values,
|
||||||
|
Keys,
|
||||||
|
Entries,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 按模式让 TypedArray 迭代器包装器推进一步:values 产出元素,keys 产出索引,
|
||||||
|
/// entries 产出 `[index, element]` 对;耗尽后把下标推进到哨兵值防止"复活"。
|
||||||
|
fn typed_array_step<H: VmHost>(
|
||||||
|
vm: &mut H, wrapper: &mut JsObject, inner: JsValue, index_si: u32, mode: TypedArrayMode,
|
||||||
|
) -> Result<JsValue, JsValue> {
|
||||||
|
let index = current_index(vm, wrapper, index_si);
|
||||||
|
let view = crate::typed_array::get_typed_array_data(vm, inner)?;
|
||||||
|
if index >= view.length {
|
||||||
|
vm.set_or_create_prop_value(wrapper, index_si, JsValue::int(i32::MAX));
|
||||||
|
return Ok(make_iter_result(vm, JsValue::undefined(), true));
|
||||||
|
}
|
||||||
|
let elem = crate::typed_array::typed_array_element_get(vm, unsafe { &*inner.as_js_object_ptr() }, index as u32)
|
||||||
|
.map_err(|e| crate::error::create_type_error(vm, &e))?;
|
||||||
|
vm.set_or_create_prop_value(wrapper, index_si, JsValue::int((index + 1) as i32));
|
||||||
|
let value = match mode {
|
||||||
|
TypedArrayMode::Values => elem,
|
||||||
|
TypedArrayMode::Keys => JsValue::int(index as i32),
|
||||||
|
TypedArrayMode::Entries => make_map_set_pair(vm, JsValue::int(index as i32), elem),
|
||||||
|
};
|
||||||
|
Ok(make_iter_result(vm, value, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TypedArray 模式迭代器 `next` 的分发:校验包装器后按模式推进。
|
||||||
|
fn typed_array_next_dispatch<H: VmHost>(vm: &mut H, args: &[u8], mode: TypedArrayMode) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
if !this_val.is_object() {
|
||||||
|
return NativeResult::Err(crate::error::create_type_error(vm, "iterator next called on non-object"));
|
||||||
|
}
|
||||||
|
let wrapper = unsafe { &mut *this_val.as_js_object_ptr() };
|
||||||
|
let inner_si = vm.kernel_core().perm_interner().intern(INNER_PROP).0;
|
||||||
|
let index_si = vm.kernel_core().perm_interner().intern(INDEX_PROP).0;
|
||||||
|
let inner = match vm.ordinary_get(wrapper, inner_si, this_val) {
|
||||||
|
Ok(inner) if !inner.is_undefined() => inner,
|
||||||
|
_ => return NativeResult::Err(crate::error::create_type_error(vm, "iterator has no inner typed array")),
|
||||||
|
};
|
||||||
|
match typed_array_step(vm, wrapper, inner, index_si, mode) {
|
||||||
|
Ok(result) => NativeResult::Ok(result),
|
||||||
|
Err(err) => NativeResult::Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn typed_array_values_iter_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
typed_array_next_dispatch::<H>(vm, args, TypedArrayMode::Values)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn typed_array_keys_iter_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
typed_array_next_dispatch::<H>(vm, args, TypedArrayMode::Keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn typed_array_entries_iter_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
typed_array_next_dispatch::<H>(vm, args, TypedArrayMode::Entries)
|
||||||
|
}
|
||||||
|
|
||||||
/// 构造迭代器包装器,其 `next` 委托给调用方指定的按模式分发的 native 函数。
|
/// 构造迭代器包装器,其 `next` 委托给调用方指定的按模式分发的 native 函数。
|
||||||
/// 与 `make_iterator_for_value` 一致,但允许 Map/Set 原型方法选择
|
/// 与 `make_iterator_for_value` 一致,但允许 Map/Set 原型方法选择
|
||||||
/// values/keys/entries 变体。
|
/// values/keys/entries 变体。
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ fn range_error<H: VmHost>(vm: &mut H, msg: &str) -> JsValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn to_index<H: VmHost>(vm: &mut H, value: JsValue, msg: &str) -> Result<usize, JsValue> {
|
fn to_index<H: VmHost>(vm: &mut H, value: JsValue, msg: &str) -> Result<usize, JsValue> {
|
||||||
let n = vm.coerce_number_bounded(value).unwrap_or(f64::NAN);
|
let n = match vm.coerce_number_bounded(value) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
|
||||||
|
};
|
||||||
if n.is_nan() {
|
if n.is_nan() {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
@@ -42,16 +45,21 @@ fn to_index<H: VmHost>(vm: &mut H, value: JsValue, msg: &str) -> Result<usize, J
|
|||||||
Ok(n.trunc() as usize)
|
Ok(n.trunc() as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_index<H: VmHost>(vm: &mut H, value: JsValue, len: usize) -> usize {
|
/// 按 ToIntegerOrInfinity 语义把索引归一化到 `[0, len]`(越界夹取);符号等不可
|
||||||
let n = vm.coerce_number_bounded(value).unwrap_or(f64::NAN);
|
/// 转换值透传异常。
|
||||||
|
fn normalize_index<H: VmHost>(vm: &mut H, value: JsValue, len: usize) -> Result<usize, JsValue> {
|
||||||
|
let n = match vm.coerce_number_bounded(value) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
|
||||||
|
};
|
||||||
if n.is_nan() {
|
if n.is_nan() {
|
||||||
return 0;
|
return Ok(0);
|
||||||
}
|
}
|
||||||
let int = n.trunc() as isize;
|
let int = n.trunc() as isize;
|
||||||
if int < 0 {
|
if int < 0 {
|
||||||
len.saturating_sub((-int) as usize)
|
Ok(len.saturating_sub((-int) as usize))
|
||||||
} else {
|
} else {
|
||||||
(int as usize).min(len)
|
Ok((int as usize).min(len))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +458,7 @@ pub fn typed_array_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
let view = native_try!(get_typed_array_data(vm, this_val));
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
let raw_index = if args.len() > 1 {
|
let raw_index = if args.len() > 1 {
|
||||||
numeric_value(vm, vm.reg(args[1])).trunc() as isize
|
native_try!(ta_to_number(vm, vm.reg(args[1]))).trunc() as isize
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
@@ -468,17 +476,17 @@ pub fn typed_array_fill<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
let view = native_try!(get_typed_array_data(vm, this_val));
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
|
let n = native_try!(ta_to_number(vm, value));
|
||||||
let start = if args.len() > 2 {
|
let start = if args.len() > 2 {
|
||||||
normalize_index(vm, vm.reg(args[2]), view.length)
|
native_try!(normalize_index(vm, vm.reg(args[2]), view.length))
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
let end = if args.len() > 3 {
|
let end = if args.len() > 3 && !vm.reg(args[3]).is_undefined() {
|
||||||
normalize_index(vm, vm.reg(args[3]), view.length)
|
native_try!(normalize_index(vm, vm.reg(args[3]), view.length))
|
||||||
} else {
|
} else {
|
||||||
view.length
|
view.length
|
||||||
};
|
};
|
||||||
let n = numeric_value(vm, value);
|
|
||||||
let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer));
|
let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer));
|
||||||
let buffer = unsafe { &mut *buffer_ptr };
|
let buffer = unsafe { &mut *buffer_ptr };
|
||||||
for idx in start..end.max(start) {
|
for idx in start..end.max(start) {
|
||||||
@@ -492,12 +500,12 @@ pub fn typed_array_slice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
let view = native_try!(get_typed_array_data(vm, this_val));
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
let start = if args.len() > 1 {
|
let start = if args.len() > 1 {
|
||||||
normalize_index(vm, vm.reg(args[1]), view.length)
|
native_try!(normalize_index(vm, vm.reg(args[1]), view.length))
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
let end = if args.len() > 2 {
|
let end = if args.len() > 2 && !vm.reg(args[2]).is_undefined() {
|
||||||
normalize_index(vm, vm.reg(args[2]), view.length)
|
native_try!(normalize_index(vm, vm.reg(args[2]), view.length))
|
||||||
} else {
|
} else {
|
||||||
view.length
|
view.length
|
||||||
};
|
};
|
||||||
@@ -518,12 +526,12 @@ pub fn typed_array_subarray<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
|||||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
let view = native_try!(get_typed_array_data(vm, this_val));
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
let start = if args.len() > 1 {
|
let start = if args.len() > 1 {
|
||||||
normalize_index(vm, vm.reg(args[1]), view.length)
|
native_try!(normalize_index(vm, vm.reg(args[1]), view.length))
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
let end = if args.len() > 2 {
|
let end = if args.len() > 2 && !vm.reg(args[2]).is_undefined() {
|
||||||
normalize_index(vm, vm.reg(args[2]), view.length)
|
native_try!(normalize_index(vm, vm.reg(args[2]), view.length))
|
||||||
} else {
|
} else {
|
||||||
view.length
|
view.length
|
||||||
};
|
};
|
||||||
@@ -661,6 +669,730 @@ pub fn typed_array_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
NativeResult::Ok(new_obj)
|
NativeResult::Ok(new_obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 读 TypedArray 指定索引的元素(视图 data 已取出的形式,供原型方法内部使用);
|
||||||
|
/// 越界返回 undefined。
|
||||||
|
fn ta_read<H: VmHost>(vm: &mut H, view: TypedArrayData, index: usize) -> Result<JsValue, JsValue> {
|
||||||
|
if index >= view.length {
|
||||||
|
return Ok(JsValue::undefined());
|
||||||
|
}
|
||||||
|
let buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?;
|
||||||
|
let buffer = unsafe { &*buffer_ptr };
|
||||||
|
Ok(read_element(view.kind, buffer, absolute_byte_offset(view, index)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 把数值写入 TypedArray 指定索引(视图 data 已取出的形式,供原型方法内部使用)。
|
||||||
|
fn ta_write<H: VmHost>(vm: &mut H, view: TypedArrayData, index: usize, value: f64) -> Result<(), JsValue> {
|
||||||
|
let buffer_ptr = array_buffer_data_ptr(vm, view.buffer)?;
|
||||||
|
let buffer = unsafe { &mut *buffer_ptr };
|
||||||
|
write_element(view.kind, buffer, absolute_byte_offset(view, index), value);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 把一组数值写成同类型的新 TypedArray(map/filter/toReversed/toSorted/with 共用)。
|
||||||
|
fn create_ta_from_numbers<H: VmHost>(
|
||||||
|
vm: &mut H, kind: TypedArrayKind, numbers: Vec<f64>,
|
||||||
|
) -> Result<*mut JsObject, JsValue> {
|
||||||
|
let bpe = kind.bytes_per_element();
|
||||||
|
let len = numbers.len();
|
||||||
|
let buffer = JsValue::from_js_object(new_array_buffer(vm, vec![0; len * bpe]));
|
||||||
|
let buffer_ptr = array_buffer_data_ptr(vm, buffer)?;
|
||||||
|
let buffer_ref = unsafe { &mut *buffer_ptr };
|
||||||
|
for (idx, n) in numbers.into_iter().enumerate() {
|
||||||
|
write_element(kind, buffer_ref, idx * bpe, n);
|
||||||
|
}
|
||||||
|
Ok(create_typed_array(vm, kind, buffer, 0, len))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 调用 TypedArray 回调并收敛异常:tail call 一律视为内部错误。
|
||||||
|
fn invoke_cb<H: VmHost>(vm: &mut H, cb: JsValue, this_arg: JsValue, cb_args: &[JsValue]) -> Result<JsValue, JsValue> {
|
||||||
|
match crate::array::invoke_native_callback(vm, cb, this_arg, cb_args) {
|
||||||
|
NativeResult::Ok(v) => Ok(v),
|
||||||
|
NativeResult::Err(e) => Err(e),
|
||||||
|
NativeResult::TailCall { .. } => Err(type_error(vm, "unexpected tail call in TypedArray callback")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ToNumber 并恢复引擎错误为原始异常值。
|
||||||
|
fn ta_to_number<H: VmHost>(vm: &mut H, value: JsValue) -> Result<f64, JsValue> {
|
||||||
|
oxide_runtime_api::to_number_full(value, vm).map_err(|e| crate::iterator::engine_error(vm, &e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.forEach(callback, thisArg)`:对每个元素调用 callback,返回 undefined。
|
||||||
|
pub fn typed_array_for_each<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if args.len() < 2 {
|
||||||
|
return NativeResult::Err(type_error(vm, "callback is not a function"));
|
||||||
|
}
|
||||||
|
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
|
||||||
|
let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
for i in 0..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
native_try!(invoke_cb(vm, callback, this_arg, &[elem, JsValue::int(i as i32), this_val]));
|
||||||
|
}
|
||||||
|
NativeResult::Ok(JsValue::undefined())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.map(callback, thisArg)`:对每个元素调用 callback,
|
||||||
|
/// 结果 ToNumber 后写入同类型的新 TypedArray。
|
||||||
|
pub fn typed_array_map<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if args.len() < 2 {
|
||||||
|
return NativeResult::Err(type_error(vm, "callback is not a function"));
|
||||||
|
}
|
||||||
|
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
|
||||||
|
let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
let mut numbers = Vec::with_capacity(view.length);
|
||||||
|
for i in 0..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
let mapped = native_try!(invoke_cb(vm, callback, this_arg, &[elem, JsValue::int(i as i32), this_val]));
|
||||||
|
numbers.push(native_try!(ta_to_number(vm, mapped)));
|
||||||
|
}
|
||||||
|
let new_obj = native_try!(create_ta_from_numbers(vm, view.kind, numbers));
|
||||||
|
NativeResult::Ok(JsValue::from_js_object(new_obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.filter(callback, thisArg)`:保留 callback 为真的元素,
|
||||||
|
/// 组成同类型的新 TypedArray。
|
||||||
|
pub fn typed_array_filter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if args.len() < 2 {
|
||||||
|
return NativeResult::Err(type_error(vm, "callback is not a function"));
|
||||||
|
}
|
||||||
|
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
|
||||||
|
let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
let mut numbers = Vec::new();
|
||||||
|
for i in 0..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
let result = native_try!(invoke_cb(vm, callback, this_arg, &[elem, JsValue::int(i as i32), this_val]));
|
||||||
|
if oxide_runtime_api::to_boolean(result) {
|
||||||
|
numbers.push(native_try!(ta_to_number(vm, elem)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let new_obj = native_try!(create_ta_from_numbers(vm, view.kind, numbers));
|
||||||
|
NativeResult::Ok(JsValue::from_js_object(new_obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.reduce(callback, initialValue)`:从左到右累计归约;
|
||||||
|
/// 空数组且无初始值抛 TypeError。
|
||||||
|
pub fn typed_array_reduce<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if view.length == 0 && args.len() < 3 {
|
||||||
|
return NativeResult::Err(type_error(vm, "Reduce of empty array with no initial value"));
|
||||||
|
}
|
||||||
|
if args.len() < 2 {
|
||||||
|
return NativeResult::Err(type_error(vm, "callback is not a function"));
|
||||||
|
}
|
||||||
|
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
|
||||||
|
let (mut accumulator, start_idx) = if args.len() > 2 {
|
||||||
|
(vm.reg(args[2]), 0usize)
|
||||||
|
} else {
|
||||||
|
(native_try!(ta_read(vm, view, 0)), 1)
|
||||||
|
};
|
||||||
|
for i in start_idx..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
accumulator = native_try!(invoke_cb(
|
||||||
|
vm,
|
||||||
|
callback,
|
||||||
|
JsValue::undefined(),
|
||||||
|
&[accumulator, elem, JsValue::int(i as i32), this_val],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
NativeResult::Ok(accumulator)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.reduceRight(callback, initialValue)`:从右到左累计归约。
|
||||||
|
pub fn typed_array_reduce_right<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if view.length == 0 && args.len() < 3 {
|
||||||
|
return NativeResult::Err(type_error(vm, "Reduce of empty array with no initial value"));
|
||||||
|
}
|
||||||
|
if args.len() < 2 {
|
||||||
|
return NativeResult::Err(type_error(vm, "callback is not a function"));
|
||||||
|
}
|
||||||
|
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
|
||||||
|
let (mut accumulator, start_idx): (JsValue, i32) = if args.len() > 2 {
|
||||||
|
(vm.reg(args[2]), view.length as i32 - 1)
|
||||||
|
} else {
|
||||||
|
(native_try!(ta_read(vm, view, view.length - 1)), view.length as i32 - 2)
|
||||||
|
};
|
||||||
|
for i in (0..=start_idx).rev() {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i as usize));
|
||||||
|
accumulator = native_try!(invoke_cb(
|
||||||
|
vm,
|
||||||
|
callback,
|
||||||
|
JsValue::undefined(),
|
||||||
|
&[accumulator, elem, JsValue::int(i), this_val],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
NativeResult::Ok(accumulator)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.every(callback, thisArg)`:所有元素满足 callback 才返回 true。
|
||||||
|
pub fn typed_array_every<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if args.len() < 2 {
|
||||||
|
return NativeResult::Err(type_error(vm, "callback is not a function"));
|
||||||
|
}
|
||||||
|
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
|
||||||
|
let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
for i in 0..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
let result = native_try!(invoke_cb(vm, callback, this_arg, &[elem, JsValue::int(i as i32), this_val]));
|
||||||
|
if !oxide_runtime_api::to_boolean(result) {
|
||||||
|
return NativeResult::Ok(JsValue::bool(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NativeResult::Ok(JsValue::bool(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.some(callback, thisArg)`:任一元素满足 callback 返回 true。
|
||||||
|
pub fn typed_array_some<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if args.len() < 2 {
|
||||||
|
return NativeResult::Err(type_error(vm, "callback is not a function"));
|
||||||
|
}
|
||||||
|
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
|
||||||
|
let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
for i in 0..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
let result = native_try!(invoke_cb(vm, callback, this_arg, &[elem, JsValue::int(i as i32), this_val]));
|
||||||
|
if oxide_runtime_api::to_boolean(result) {
|
||||||
|
return NativeResult::Ok(JsValue::bool(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NativeResult::Ok(JsValue::bool(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.find(callback, thisArg)`:返回首个 callback 为真的元素,否则 undefined。
|
||||||
|
pub fn typed_array_find<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if args.len() < 2 {
|
||||||
|
return NativeResult::Err(type_error(vm, "callback is not a function"));
|
||||||
|
}
|
||||||
|
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
|
||||||
|
let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
for i in 0..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
let result = native_try!(invoke_cb(vm, callback, this_arg, &[elem, JsValue::int(i as i32), this_val]));
|
||||||
|
if oxide_runtime_api::to_boolean(result) {
|
||||||
|
return NativeResult::Ok(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NativeResult::Ok(JsValue::undefined())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.findIndex(callback, thisArg)`:返回首个 callback 为真的索引,否则 -1。
|
||||||
|
pub fn typed_array_find_index<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if args.len() < 2 {
|
||||||
|
return NativeResult::Err(type_error(vm, "callback is not a function"));
|
||||||
|
}
|
||||||
|
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
|
||||||
|
let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
for i in 0..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
let result = native_try!(invoke_cb(vm, callback, this_arg, &[elem, JsValue::int(i as i32), this_val]));
|
||||||
|
if oxide_runtime_api::to_boolean(result) {
|
||||||
|
return NativeResult::Ok(JsValue::int(i as i32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NativeResult::Ok(JsValue::int(-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.findLast(callback, thisArg)`:从后往前返回首个 callback 为真的元素。
|
||||||
|
pub fn typed_array_find_last<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if args.len() < 2 {
|
||||||
|
return NativeResult::Err(type_error(vm, "callback is not a function"));
|
||||||
|
}
|
||||||
|
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
|
||||||
|
let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
for i in (0..view.length).rev() {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
let result = native_try!(invoke_cb(vm, callback, this_arg, &[elem, JsValue::int(i as i32), this_val]));
|
||||||
|
if oxide_runtime_api::to_boolean(result) {
|
||||||
|
return NativeResult::Ok(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NativeResult::Ok(JsValue::undefined())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.findLastIndex(callback, thisArg)`:从后往前返回首个 callback
|
||||||
|
/// 为真的索引,否则 -1。
|
||||||
|
pub fn typed_array_find_last_index<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if args.len() < 2 {
|
||||||
|
return NativeResult::Err(type_error(vm, "callback is not a function"));
|
||||||
|
}
|
||||||
|
let callback = native_try!(crate::array::require_callback(vm, vm.reg(args[1])));
|
||||||
|
let this_arg = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
for i in (0..view.length).rev() {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
let result = native_try!(invoke_cb(vm, callback, this_arg, &[elem, JsValue::int(i as i32), this_val]));
|
||||||
|
if oxide_runtime_api::to_boolean(result) {
|
||||||
|
return NativeResult::Ok(JsValue::int(i as i32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NativeResult::Ok(JsValue::int(-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 规范化 indexOf/includes 的 fromIndex:NaN 视为 0,负值折算后从 0 夹取,正值夹到长度。
|
||||||
|
fn normalize_from_index<H: VmHost>(vm: &mut H, value: JsValue, len: usize) -> Result<usize, JsValue> {
|
||||||
|
let n = match vm.coerce_number_bounded(value) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => return Err(crate::iterator::engine_error(vm, &e)),
|
||||||
|
};
|
||||||
|
if n.is_nan() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
let int = n.trunc();
|
||||||
|
if int >= 0.0 {
|
||||||
|
Ok((int as usize).min(len))
|
||||||
|
} else {
|
||||||
|
let from = len as f64 + int;
|
||||||
|
if from < 0.0 {
|
||||||
|
Ok(0)
|
||||||
|
} else {
|
||||||
|
Ok(from as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.indexOf(searchElement, fromIndex)`:用严格相等查找首个匹配索引。
|
||||||
|
pub fn typed_array_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if view.length == 0 || args.len() < 2 {
|
||||||
|
return NativeResult::Ok(JsValue::int(-1));
|
||||||
|
}
|
||||||
|
let target = vm.reg(args[1]);
|
||||||
|
let from_index = if args.len() >= 3 {
|
||||||
|
native_try!(normalize_from_index(vm, vm.reg(args[2]), view.length))
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
for i in from_index..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
if oxide_runtime_api::strict_eq(elem, target) {
|
||||||
|
return NativeResult::Ok(JsValue::int(i as i32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NativeResult::Ok(JsValue::int(-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.lastIndexOf(searchElement, fromIndex)`:从后往前查找首个匹配索引。
|
||||||
|
pub fn typed_array_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if view.length == 0 {
|
||||||
|
return NativeResult::Ok(JsValue::int(-1));
|
||||||
|
}
|
||||||
|
let target = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
|
let from_index: isize = if args.len() >= 3 {
|
||||||
|
let v = vm.reg(args[2]);
|
||||||
|
let f = match vm.coerce_number_bounded(v) {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(e) => return NativeResult::Err(crate::iterator::engine_error(vm, &e)),
|
||||||
|
};
|
||||||
|
if f.is_nan() {
|
||||||
|
return NativeResult::Ok(JsValue::int(-1));
|
||||||
|
}
|
||||||
|
let f = f.trunc();
|
||||||
|
if f >= 0.0 {
|
||||||
|
(f as isize).min(view.length as isize - 1)
|
||||||
|
} else {
|
||||||
|
view.length as isize + f as isize
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
view.length as isize - 1
|
||||||
|
};
|
||||||
|
if from_index < 0 {
|
||||||
|
return NativeResult::Ok(JsValue::int(-1));
|
||||||
|
}
|
||||||
|
for i in (0..=from_index as usize).rev() {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
if oxide_runtime_api::strict_eq(elem, target) {
|
||||||
|
return NativeResult::Ok(JsValue::int(i as i32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NativeResult::Ok(JsValue::int(-1))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.includes(searchElement, fromIndex)`:用 SameValueZero 判断是否包含
|
||||||
|
/// (NaN 视为存在、+0/-0 视为相同)。
|
||||||
|
pub fn typed_array_includes<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if view.length == 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 {
|
||||||
|
native_try!(normalize_from_index(vm, vm.reg(args[2]), view.length))
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
for i in from_index..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
if oxide_runtime_api::same_value_zero(elem, target) {
|
||||||
|
return NativeResult::Ok(JsValue::bool(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NativeResult::Ok(JsValue::bool(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.join(separator)`:用分隔符连接元素字符串(元素恒为数值)。
|
||||||
|
pub fn typed_array_join<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
let sep = if args.len() > 1 {
|
||||||
|
native_try!(
|
||||||
|
oxide_runtime_api::to_string_full(vm.reg(args[1]), vm).map_err(|e| crate::iterator::engine_error(vm, &e))
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
",".to_string()
|
||||||
|
};
|
||||||
|
let mut parts = Vec::with_capacity(view.length);
|
||||||
|
for i in 0..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
parts.push(oxide_runtime_api::to_string(elem));
|
||||||
|
}
|
||||||
|
NativeResult::Ok(vm.new_string(&parts.join(&sep)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.values()`:返回迭代元素值的迭代器。
|
||||||
|
pub fn typed_array_values<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
NativeResult::Ok(crate::iterator::make_mode_iterator(
|
||||||
|
vm,
|
||||||
|
this_val,
|
||||||
|
crate::iterator::typed_array_values_iter_next::<H> as *const (),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.keys()`:返回迭代元素索引的迭代器。
|
||||||
|
pub fn typed_array_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
NativeResult::Ok(crate::iterator::make_mode_iterator(
|
||||||
|
vm,
|
||||||
|
this_val,
|
||||||
|
crate::iterator::typed_array_keys_iter_next::<H> as *const (),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.entries()`:返回迭代 `[index, element]` 对的迭代器。
|
||||||
|
pub fn typed_array_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
NativeResult::Ok(crate::iterator::make_mode_iterator(
|
||||||
|
vm,
|
||||||
|
this_val,
|
||||||
|
crate::iterator::typed_array_entries_iter_next::<H> as *const (),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 默认数值排序比较:NaN 视为最大排到末尾,其余按数值升序。
|
||||||
|
fn default_ta_order(a: f64, b: f64) -> std::cmp::Ordering {
|
||||||
|
if a.is_nan() && b.is_nan() {
|
||||||
|
std::cmp::Ordering::Equal
|
||||||
|
} else if a.is_nan() {
|
||||||
|
std::cmp::Ordering::Greater
|
||||||
|
} else if b.is_nan() {
|
||||||
|
std::cmp::Ordering::Less
|
||||||
|
} else {
|
||||||
|
a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.sort(comparefn)`:原地排序,默认按数值升序(NaN 排末尾)。
|
||||||
|
pub fn typed_array_sort<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
let comparator = if args.len() > 1 {
|
||||||
|
let c = vm.reg(args[1]);
|
||||||
|
if c.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(crate::array::require_callback(vm, c)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mut vals: Vec<f64> = Vec::with_capacity(view.length);
|
||||||
|
for i in 0..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
vals.push(native_try!(ta_to_number(vm, elem)));
|
||||||
|
}
|
||||||
|
let mut sort_error = None;
|
||||||
|
vals.sort_by(|a, b| {
|
||||||
|
if sort_error.is_some() {
|
||||||
|
return std::cmp::Ordering::Equal;
|
||||||
|
}
|
||||||
|
if let Some(cb) = comparator {
|
||||||
|
match crate::array::invoke_native_callback(
|
||||||
|
vm,
|
||||||
|
cb,
|
||||||
|
JsValue::undefined(),
|
||||||
|
&[JsValue::float(*a), JsValue::float(*b)],
|
||||||
|
) {
|
||||||
|
NativeResult::Ok(r) => {
|
||||||
|
let n = oxide_runtime_api::to_number(r);
|
||||||
|
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 typed array callback"));
|
||||||
|
std::cmp::Ordering::Equal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
default_ta_order(*a, *b)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(err) = sort_error {
|
||||||
|
return NativeResult::Err(err);
|
||||||
|
}
|
||||||
|
for (i, n) in vals.into_iter().enumerate() {
|
||||||
|
native_try!(ta_write(vm, view, i, n));
|
||||||
|
}
|
||||||
|
NativeResult::Ok(this_val)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.reverse()`:原地反转元素顺序,返回 this。
|
||||||
|
pub fn typed_array_reverse<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
let mut i = 0;
|
||||||
|
let mut j = view.length.saturating_sub(1);
|
||||||
|
while i < j {
|
||||||
|
let tmp = native_try!(ta_read(vm, view, i));
|
||||||
|
let n = native_try!(ta_to_number(vm, tmp));
|
||||||
|
let jtmp = native_try!(ta_read(vm, view, j));
|
||||||
|
let jv = native_try!(ta_to_number(vm, jtmp));
|
||||||
|
native_try!(ta_write(vm, view, j, n));
|
||||||
|
native_try!(ta_write(vm, view, i, jv));
|
||||||
|
i += 1;
|
||||||
|
j = j.saturating_sub(1);
|
||||||
|
}
|
||||||
|
NativeResult::Ok(this_val)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.copyWithin(target, start, end)`:在数组内部复制元素区间,返回 this。
|
||||||
|
pub fn typed_array_copy_within<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
let len = view.length;
|
||||||
|
let target = if args.len() > 1 {
|
||||||
|
native_try!(normalize_index(vm, vm.reg(args[1]), len))
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let start = if args.len() > 2 {
|
||||||
|
native_try!(normalize_index(vm, vm.reg(args[2]), len))
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let end = if args.len() > 3 && !vm.reg(args[3]).is_undefined() {
|
||||||
|
native_try!(normalize_index(vm, vm.reg(args[3]), len))
|
||||||
|
} else {
|
||||||
|
len
|
||||||
|
};
|
||||||
|
let count = end.max(start).saturating_sub(start).min(len.saturating_sub(target));
|
||||||
|
// 目标区间前移与源区间重叠时逆序遍历,避免覆盖未读的源元素。
|
||||||
|
let (mut from, mut to, direction) = if start < target && target < start + count {
|
||||||
|
(start + count - 1, target + count - 1, -1isize)
|
||||||
|
} else {
|
||||||
|
(start, target, 1isize)
|
||||||
|
};
|
||||||
|
for _ in 0..count {
|
||||||
|
let elem = native_try!(ta_read(vm, view, from));
|
||||||
|
let v = native_try!(ta_to_number(vm, elem));
|
||||||
|
native_try!(ta_write(vm, view, to, v));
|
||||||
|
from = (from as isize + direction) as usize;
|
||||||
|
to = (to as isize + direction) as usize;
|
||||||
|
}
|
||||||
|
NativeResult::Ok(this_val)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 以元素为 receiver 调用其 `toLocaleString` 方法,返回结果(失败透传原异常)。
|
||||||
|
///
|
||||||
|
/// # 注意事项
|
||||||
|
/// `toLocaleString` 方法缺失时回退为 ToString 结果(无 Intl 的最小实现)。
|
||||||
|
fn invoke_element_to_locale_string<H: VmHost>(vm: &mut H, element: JsValue) -> Result<JsValue, JsValue> {
|
||||||
|
let obj_val = oxide_runtime_api::to_object(element, vm).map_err(|e| crate::iterator::engine_error(vm, &e))?;
|
||||||
|
let obj = unsafe { &*obj_val.as_js_object_ptr() };
|
||||||
|
let method_si = vm.kernel_core().perm_interner().intern("toLocaleString").0;
|
||||||
|
let method = vm
|
||||||
|
.ordinary_get(obj, method_si, element)
|
||||||
|
.map_err(|e| crate::iterator::engine_error(vm, &e))?;
|
||||||
|
if !crate::iterator::is_callable(method) {
|
||||||
|
return Ok(vm.new_string(&oxide_runtime_api::to_string(element)));
|
||||||
|
}
|
||||||
|
match vm.call_function_sync(method, element, &[]) {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
Err(e) => Err(crate::iterator::engine_error(vm, &e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.toLocaleString()`:元素逐个调用 `toLocaleString` 后用 `,` 连接。
|
||||||
|
pub fn typed_array_to_locale_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if view.length == 0 {
|
||||||
|
return NativeResult::Ok(vm.new_string(""));
|
||||||
|
}
|
||||||
|
let mut parts = Vec::with_capacity(view.length);
|
||||||
|
for i in 0..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
let r = native_try!(invoke_element_to_locale_string(vm, elem));
|
||||||
|
let s =
|
||||||
|
native_try!(oxide_runtime_api::to_string_full(r, vm).map_err(|e| crate::iterator::engine_error(vm, &e)));
|
||||||
|
parts.push(s);
|
||||||
|
}
|
||||||
|
NativeResult::Ok(vm.new_string(&parts.join(",")))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.toReversed()`:返回元素反转的同类型新 TypedArray(原对象不变)。
|
||||||
|
pub fn typed_array_to_reversed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
let mut numbers = Vec::with_capacity(view.length);
|
||||||
|
for i in (0..view.length).rev() {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
numbers.push(native_try!(ta_to_number(vm, elem)));
|
||||||
|
}
|
||||||
|
let new_obj = native_try!(create_ta_from_numbers(vm, view.kind, numbers));
|
||||||
|
NativeResult::Ok(JsValue::from_js_object(new_obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.toSorted(comparefn)`:返回元素排序后的同类型新 TypedArray
|
||||||
|
/// (原对象不变)。
|
||||||
|
pub fn typed_array_to_sorted<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
let comparator = if args.len() > 1 {
|
||||||
|
let c = vm.reg(args[1]);
|
||||||
|
if c.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(crate::array::require_callback(vm, c)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mut vals: Vec<f64> = Vec::with_capacity(view.length);
|
||||||
|
for i in 0..view.length {
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
vals.push(native_try!(ta_to_number(vm, elem)));
|
||||||
|
}
|
||||||
|
let mut sort_error = None;
|
||||||
|
vals.sort_by(|a, b| {
|
||||||
|
if sort_error.is_some() {
|
||||||
|
return std::cmp::Ordering::Equal;
|
||||||
|
}
|
||||||
|
if let Some(cb) = comparator {
|
||||||
|
match crate::array::invoke_native_callback(
|
||||||
|
vm,
|
||||||
|
cb,
|
||||||
|
JsValue::undefined(),
|
||||||
|
&[JsValue::float(*a), JsValue::float(*b)],
|
||||||
|
) {
|
||||||
|
NativeResult::Ok(r) => {
|
||||||
|
let n = oxide_runtime_api::to_number(r);
|
||||||
|
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 typed array callback"));
|
||||||
|
std::cmp::Ordering::Equal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
default_ta_order(*a, *b)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(err) = sort_error {
|
||||||
|
return NativeResult::Err(err);
|
||||||
|
}
|
||||||
|
let new_obj = native_try!(create_ta_from_numbers(vm, view.kind, vals));
|
||||||
|
NativeResult::Ok(JsValue::from_js_object(new_obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `%TypedArray%.prototype.with(index, value)`:返回替换指定索引元素后的同类型新 TypedArray;
|
||||||
|
/// 负索引从尾部折算,折算后越界抛 RangeError。
|
||||||
|
pub fn typed_array_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||||
|
let view = native_try!(get_typed_array_data(vm, this_val));
|
||||||
|
if args.len() < 2 {
|
||||||
|
return NativeResult::Err(type_error(vm, "TypedArray.prototype.with requires an index"));
|
||||||
|
}
|
||||||
|
// ToIntegerOrInfinity(index),负值折算为 len + index。
|
||||||
|
let raw = native_try!(ta_to_number(vm, vm.reg(args[1])));
|
||||||
|
let relative_index = if raw.is_nan() || raw == 0.0 {
|
||||||
|
0.0
|
||||||
|
} else if raw.is_infinite() {
|
||||||
|
raw
|
||||||
|
} else {
|
||||||
|
raw.trunc()
|
||||||
|
};
|
||||||
|
let actual_index = if relative_index >= 0.0 {
|
||||||
|
relative_index
|
||||||
|
} else {
|
||||||
|
view.length as f64 + relative_index
|
||||||
|
};
|
||||||
|
// 先 ToNumber(value)(可触发副作用/抛错),再做索引范围校验。
|
||||||
|
let value = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
let replacement = native_try!(ta_to_number(vm, value));
|
||||||
|
if actual_index.is_nan() || actual_index < 0.0 || actual_index >= view.length as f64 {
|
||||||
|
return NativeResult::Err(range_error(vm, "Invalid typed array index"));
|
||||||
|
}
|
||||||
|
let index = actual_index as usize;
|
||||||
|
let mut numbers = Vec::with_capacity(view.length);
|
||||||
|
for i in 0..view.length {
|
||||||
|
if i == index {
|
||||||
|
numbers.push(replacement);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let elem = native_try!(ta_read(vm, view, i));
|
||||||
|
numbers.push(native_try!(ta_to_number(vm, elem)));
|
||||||
|
}
|
||||||
|
let new_obj = native_try!(create_ta_from_numbers(vm, view.kind, numbers));
|
||||||
|
NativeResult::Ok(JsValue::from_js_object(new_obj))
|
||||||
|
}
|
||||||
|
|
||||||
/// `%TypedArray%.from(source, mapfn?, thisArg?)`:从可迭代对象或 array-like 构造
|
/// `%TypedArray%.from(source, mapfn?, thisArg?)`:从可迭代对象或 array-like 构造
|
||||||
/// 以 `this`(构造器 C)为类型的 TypedArray;`mapfn` 逐元素映射(`thisArg` 作回调
|
/// 以 `this`(构造器 C)为类型的 TypedArray;`mapfn` 逐元素映射(`thisArg` 作回调
|
||||||
/// this),元素经 ToNumber 写入。
|
/// this),元素经 ToNumber 写入。
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ const BUILTIN_GLOBALS: &[&str] = &[
|
|||||||
"DataView",
|
"DataView",
|
||||||
"Iterator",
|
"Iterator",
|
||||||
"BigInt",
|
"BigInt",
|
||||||
|
"TypedArray",
|
||||||
"Int8Array",
|
"Int8Array",
|
||||||
"Uint8Array",
|
"Uint8Array",
|
||||||
"Uint8ClampedArray",
|
"Uint8ClampedArray",
|
||||||
|
|||||||
@@ -520,6 +520,10 @@ pub fn strict_eq(lhs: JsValue, rhs: JsValue) -> bool {
|
|||||||
if lhs.is_int() && rhs.is_int() {
|
if lhs.is_int() && rhs.is_int() {
|
||||||
return lhs.as_int() == rhs.as_int();
|
return lhs.as_int() == rhs.as_int();
|
||||||
}
|
}
|
||||||
|
// Number 跨 int/double 表示比较:`42 === 42.0` 按 SameValue 语义应相等。
|
||||||
|
if (lhs.is_int() || lhs.is_double()) && (rhs.is_int() || rhs.is_double()) {
|
||||||
|
return strict_double_eq(to_f64(lhs), to_f64(rhs));
|
||||||
|
}
|
||||||
if lhs.is_double() && rhs.is_double() {
|
if lhs.is_double() && rhs.is_double() {
|
||||||
return strict_double_eq(lhs.as_double(), rhs.as_double());
|
return strict_double_eq(lhs.as_double(), rhs.as_double());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::bind_constructor;
|
use crate::bind_constructor;
|
||||||
use crate::bindings::{apply_binding_table, bind_accessor_getter, configure_native_constructor};
|
use crate::bindings::{
|
||||||
|
apply_binding_table, bind_accessor_getter, bind_global_value, bind_method_alias, configure_native_constructor,
|
||||||
|
};
|
||||||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||||||
use oxide_types::object::{JsObject, PropAttributes};
|
use oxide_types::object::{JsObject, PropAttributes};
|
||||||
use oxide_types::value::JsValue;
|
use oxide_types::value::JsValue;
|
||||||
@@ -110,8 +112,90 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
|||||||
oxide_builtins::typed_array::typed_array_subarray::<crate::vm::Vm> as *const (),
|
oxide_builtins::typed_array::typed_array_subarray::<crate::vm::Vm> as *const (),
|
||||||
2,
|
2,
|
||||||
),
|
),
|
||||||
|
("map", oxide_builtins::typed_array::typed_array_map::<crate::vm::Vm> as *const (), 1),
|
||||||
|
("filter", oxide_builtins::typed_array::typed_array_filter::<crate::vm::Vm> as *const (), 1),
|
||||||
|
("reduce", oxide_builtins::typed_array::typed_array_reduce::<crate::vm::Vm> as *const (), 1),
|
||||||
|
(
|
||||||
|
"reduceRight",
|
||||||
|
oxide_builtins::typed_array::typed_array_reduce_right::<crate::vm::Vm> as *const (),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"forEach",
|
||||||
|
oxide_builtins::typed_array::typed_array_for_each::<crate::vm::Vm> as *const (),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
("every", oxide_builtins::typed_array::typed_array_every::<crate::vm::Vm> as *const (), 1),
|
||||||
|
("some", oxide_builtins::typed_array::typed_array_some::<crate::vm::Vm> as *const (), 1),
|
||||||
|
("find", oxide_builtins::typed_array::typed_array_find::<crate::vm::Vm> as *const (), 1),
|
||||||
|
(
|
||||||
|
"findIndex",
|
||||||
|
oxide_builtins::typed_array::typed_array_find_index::<crate::vm::Vm> as *const (),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"findLast",
|
||||||
|
oxide_builtins::typed_array::typed_array_find_last::<crate::vm::Vm> as *const (),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"findLastIndex",
|
||||||
|
oxide_builtins::typed_array::typed_array_find_last_index::<crate::vm::Vm> as *const (),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"indexOf",
|
||||||
|
oxide_builtins::typed_array::typed_array_index_of::<crate::vm::Vm> as *const (),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"lastIndexOf",
|
||||||
|
oxide_builtins::typed_array::typed_array_last_index_of::<crate::vm::Vm> as *const (),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"includes",
|
||||||
|
oxide_builtins::typed_array::typed_array_includes::<crate::vm::Vm> as *const (),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
("join", oxide_builtins::typed_array::typed_array_join::<crate::vm::Vm> as *const (), 1),
|
||||||
|
("values", oxide_builtins::typed_array::typed_array_values::<crate::vm::Vm> as *const (), 0),
|
||||||
|
("keys", oxide_builtins::typed_array::typed_array_keys::<crate::vm::Vm> as *const (), 0),
|
||||||
|
(
|
||||||
|
"entries",
|
||||||
|
oxide_builtins::typed_array::typed_array_entries::<crate::vm::Vm> as *const (),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
("sort", oxide_builtins::typed_array::typed_array_sort::<crate::vm::Vm> as *const (), 1),
|
||||||
|
(
|
||||||
|
"reverse",
|
||||||
|
oxide_builtins::typed_array::typed_array_reverse::<crate::vm::Vm> as *const (),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"copyWithin",
|
||||||
|
oxide_builtins::typed_array::typed_array_copy_within::<crate::vm::Vm> as *const (),
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"toLocaleString",
|
||||||
|
oxide_builtins::typed_array::typed_array_to_locale_string::<crate::vm::Vm> as *const (),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"toReversed",
|
||||||
|
oxide_builtins::typed_array::typed_array_to_reversed::<crate::vm::Vm> as *const (),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"toSorted",
|
||||||
|
oxide_builtins::typed_array::typed_array_to_sorted::<crate::vm::Vm> as *const (),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
("with", oxide_builtins::typed_array::typed_array_with::<crate::vm::Vm> as *const (), 2),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
bind_method_alias(core, shared_proto, "values", "@@iterator");
|
||||||
|
|
||||||
// 原型访问器:视图属性(buffer/byteOffset/byteLength/length)读内部数据槽,
|
// 原型访问器:视图属性(buffer/byteOffset/byteLength/length)读内部数据槽,
|
||||||
// @@toStringTag 返回具体类型名,供 Object.prototype.toString 区分类型。
|
// @@toStringTag 返回具体类型名,供 Object.prototype.toString 区分类型。
|
||||||
@@ -153,6 +237,11 @@ pub fn bind_typed_array(core: &Arc<KernelCore>, session: &KernelSession, global:
|
|||||||
|
|
||||||
bind_typed_array_abstract_ctor(core, session);
|
bind_typed_array_abstract_ctor(core, session);
|
||||||
|
|
||||||
|
// 把 `%TypedArray%` 抽象构造器暴露为全局 `TypedArray`(具体构造器的 [[Prototype]])。
|
||||||
|
let abstract_ctor_val =
|
||||||
|
JsValue::from_js_object(session.builtin_world().typed_array_constructor.as_ptr() as *mut JsObject);
|
||||||
|
bind_global_value(core, global, "TypedArray", abstract_ctor_val);
|
||||||
|
|
||||||
bind_typed_array_constructor!(
|
bind_typed_array_constructor!(
|
||||||
core,
|
core,
|
||||||
global,
|
global,
|
||||||
|
|||||||
Reference in New Issue
Block a user