From c65f3be1794b6bc29bdcbec2f9f41ec608c14321 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 9 Aug 2026 13:18:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20TypedArray=20=E5=8E=9F=E5=9E=8B?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E6=97=8F=E5=85=A8=E9=87=8F=E8=A7=A3=E9=94=81?= =?UTF-8?q?=EF=BC=88=E5=9B=9E=E8=B0=83/=E6=9F=A5=E6=89=BE/=E8=BF=AD?= =?UTF-8?q?=E4=BB=A3/=E6=8E=92=E5=BA=8F/ES2023=20copy=20=E6=97=8F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/iterator.rs | 60 ++ crates/oxide_builtins/src/typed_array.rs | 766 +++++++++++++++++- crates/oxide_emit/src/emit.rs | 1 + crates/oxide_runtime_api/src/lib.rs | 4 + .../oxide_vm/src/bindings/bind_typed_array.rs | 91 ++- 5 files changed, 904 insertions(+), 18 deletions(-) diff --git a/crates/oxide_builtins/src/iterator.rs b/crates/oxide_builtins/src/iterator.rs index 963dbe90..af61db5d 100644 --- a/crates/oxide_builtins/src/iterator.rs +++ b/crates/oxide_builtins/src/iterator.rs @@ -567,6 +567,66 @@ pub(crate) fn set_entries_iter_next(vm: &mut H, args: &[u8]) -> Nativ map_set_next_dispatch::(vm, args, MapSetMode::SetEntries) } +#[derive(Clone, Copy)] +enum TypedArrayMode { + Values, + Keys, + Entries, +} + +/// 按模式让 TypedArray 迭代器包装器推进一步:values 产出元素,keys 产出索引, +/// entries 产出 `[index, element]` 对;耗尽后把下标推进到哨兵值防止"复活"。 +fn typed_array_step( + vm: &mut H, wrapper: &mut JsObject, inner: JsValue, index_si: u32, mode: TypedArrayMode, +) -> Result { + 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(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(vm: &mut H, args: &[u8]) -> NativeResult { + typed_array_next_dispatch::(vm, args, TypedArrayMode::Values) +} + +pub(crate) fn typed_array_keys_iter_next(vm: &mut H, args: &[u8]) -> NativeResult { + typed_array_next_dispatch::(vm, args, TypedArrayMode::Keys) +} + +pub(crate) fn typed_array_entries_iter_next(vm: &mut H, args: &[u8]) -> NativeResult { + typed_array_next_dispatch::(vm, args, TypedArrayMode::Entries) +} + /// 构造迭代器包装器,其 `next` 委托给调用方指定的按模式分发的 native 函数。 /// 与 `make_iterator_for_value` 一致,但允许 Map/Set 原型方法选择 /// values/keys/entries 变体。 diff --git a/crates/oxide_builtins/src/typed_array.rs b/crates/oxide_builtins/src/typed_array.rs index 47d23b26..1cf31d14 100644 --- a/crates/oxide_builtins/src/typed_array.rs +++ b/crates/oxide_builtins/src/typed_array.rs @@ -32,7 +32,10 @@ fn range_error(vm: &mut H, msg: &str) -> JsValue { } fn to_index(vm: &mut H, value: JsValue, msg: &str) -> Result { - 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() { return Ok(0); } @@ -42,16 +45,21 @@ fn to_index(vm: &mut H, value: JsValue, msg: &str) -> Result(vm: &mut H, value: JsValue, len: usize) -> usize { - let n = vm.coerce_number_bounded(value).unwrap_or(f64::NAN); +/// 按 ToIntegerOrInfinity 语义把索引归一化到 `[0, len]`(越界夹取);符号等不可 +/// 转换值透传异常。 +fn normalize_index(vm: &mut H, value: JsValue, len: usize) -> Result { + 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 0; + return Ok(0); } let int = n.trunc() as isize; if int < 0 { - len.saturating_sub((-int) as usize) + Ok(len.saturating_sub((-int) as usize)) } else { - (int as usize).min(len) + Ok((int as usize).min(len)) } } @@ -450,7 +458,7 @@ pub fn typed_array_at(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 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 { 0 }; @@ -468,17 +476,17 @@ pub fn typed_array_fill(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 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 { - normalize_index(vm, vm.reg(args[2]), view.length) + native_try!(normalize_index(vm, vm.reg(args[2]), view.length)) } else { 0 }; - let end = if args.len() > 3 { - normalize_index(vm, vm.reg(args[3]), view.length) + let end = if args.len() > 3 && !vm.reg(args[3]).is_undefined() { + native_try!(normalize_index(vm, vm.reg(args[3]), view.length)) } else { view.length }; - let n = numeric_value(vm, value); let buffer_ptr = native_try!(array_buffer_data_ptr(vm, view.buffer)); let buffer = unsafe { &mut *buffer_ptr }; for idx in start..end.max(start) { @@ -492,12 +500,12 @@ pub fn typed_array_slice(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 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 { 0 }; - let end = if args.len() > 2 { - normalize_index(vm, vm.reg(args[2]), view.length) + let end = if args.len() > 2 && !vm.reg(args[2]).is_undefined() { + native_try!(normalize_index(vm, vm.reg(args[2]), view.length)) } else { view.length }; @@ -518,12 +526,12 @@ pub fn typed_array_subarray(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 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 { 0 }; - let end = if args.len() > 2 { - normalize_index(vm, vm.reg(args[2]), view.length) + let end = if args.len() > 2 && !vm.reg(args[2]).is_undefined() { + native_try!(normalize_index(vm, vm.reg(args[2]), view.length)) } else { view.length }; @@ -661,6 +669,730 @@ pub fn typed_array_of(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(new_obj) } +/// 读 TypedArray 指定索引的元素(视图 data 已取出的形式,供原型方法内部使用); +/// 越界返回 undefined。 +fn ta_read(vm: &mut H, view: TypedArrayData, index: usize) -> Result { + 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(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( + vm: &mut H, kind: TypedArrayKind, numbers: Vec, +) -> 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(vm: &mut H, cb: JsValue, this_arg: JsValue, cb_args: &[JsValue]) -> Result { + 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(vm: &mut H, value: JsValue) -> Result { + 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(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(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(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(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(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(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(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(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(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(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(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(vm: &mut H, value: JsValue, len: usize) -> Result { + 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(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(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(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(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(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:: as *const (), + )) +} + +/// `%TypedArray%.prototype.keys()`:返回迭代元素索引的迭代器。 +pub fn typed_array_keys(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:: as *const (), + )) +} + +/// `%TypedArray%.prototype.entries()`:返回迭代 `[index, element]` 对的迭代器。 +pub fn typed_array_entries(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:: 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(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 = 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(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(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(vm: &mut H, element: JsValue) -> Result { + 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(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(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(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 = 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(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 构造 /// 以 `this`(构造器 C)为类型的 TypedArray;`mapfn` 逐元素映射(`thisArg` 作回调 /// this),元素经 ToNumber 写入。 diff --git a/crates/oxide_emit/src/emit.rs b/crates/oxide_emit/src/emit.rs index 7e0eab8f..52b7512d 100644 --- a/crates/oxide_emit/src/emit.rs +++ b/crates/oxide_emit/src/emit.rs @@ -106,6 +106,7 @@ const BUILTIN_GLOBALS: &[&str] = &[ "DataView", "Iterator", "BigInt", + "TypedArray", "Int8Array", "Uint8Array", "Uint8ClampedArray", diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index e4b3e5c9..fa71cc31 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -520,6 +520,10 @@ pub fn strict_eq(lhs: JsValue, rhs: JsValue) -> bool { if lhs.is_int() && rhs.is_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() { return strict_double_eq(lhs.as_double(), rhs.as_double()); } diff --git a/crates/oxide_vm/src/bindings/bind_typed_array.rs b/crates/oxide_vm/src/bindings/bind_typed_array.rs index aa46ac9c..aa64f895 100644 --- a/crates/oxide_vm/src/bindings/bind_typed_array.rs +++ b/crates/oxide_vm/src/bindings/bind_typed_array.rs @@ -1,7 +1,9 @@ use std::sync::Arc; 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_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; @@ -110,8 +112,90 @@ pub fn bind_typed_array(core: &Arc, session: &KernelSession, global: oxide_builtins::typed_array::typed_array_subarray:: as *const (), 2, ), + ("map", oxide_builtins::typed_array::typed_array_map:: as *const (), 1), + ("filter", oxide_builtins::typed_array::typed_array_filter:: as *const (), 1), + ("reduce", oxide_builtins::typed_array::typed_array_reduce:: as *const (), 1), + ( + "reduceRight", + oxide_builtins::typed_array::typed_array_reduce_right:: as *const (), + 1, + ), + ( + "forEach", + oxide_builtins::typed_array::typed_array_for_each:: as *const (), + 1, + ), + ("every", oxide_builtins::typed_array::typed_array_every:: as *const (), 1), + ("some", oxide_builtins::typed_array::typed_array_some:: as *const (), 1), + ("find", oxide_builtins::typed_array::typed_array_find:: as *const (), 1), + ( + "findIndex", + oxide_builtins::typed_array::typed_array_find_index:: as *const (), + 1, + ), + ( + "findLast", + oxide_builtins::typed_array::typed_array_find_last:: as *const (), + 1, + ), + ( + "findLastIndex", + oxide_builtins::typed_array::typed_array_find_last_index:: as *const (), + 1, + ), + ( + "indexOf", + oxide_builtins::typed_array::typed_array_index_of:: as *const (), + 1, + ), + ( + "lastIndexOf", + oxide_builtins::typed_array::typed_array_last_index_of:: as *const (), + 1, + ), + ( + "includes", + oxide_builtins::typed_array::typed_array_includes:: as *const (), + 1, + ), + ("join", oxide_builtins::typed_array::typed_array_join:: as *const (), 1), + ("values", oxide_builtins::typed_array::typed_array_values:: as *const (), 0), + ("keys", oxide_builtins::typed_array::typed_array_keys:: as *const (), 0), + ( + "entries", + oxide_builtins::typed_array::typed_array_entries:: as *const (), + 0, + ), + ("sort", oxide_builtins::typed_array::typed_array_sort:: as *const (), 1), + ( + "reverse", + oxide_builtins::typed_array::typed_array_reverse:: as *const (), + 0, + ), + ( + "copyWithin", + oxide_builtins::typed_array::typed_array_copy_within:: as *const (), + 2, + ), + ( + "toLocaleString", + oxide_builtins::typed_array::typed_array_to_locale_string:: as *const (), + 0, + ), + ( + "toReversed", + oxide_builtins::typed_array::typed_array_to_reversed:: as *const (), + 0, + ), + ( + "toSorted", + oxide_builtins::typed_array::typed_array_to_sorted:: as *const (), + 1, + ), + ("with", oxide_builtins::typed_array::typed_array_with:: as *const (), 2), ], ); + bind_method_alias(core, shared_proto, "values", "@@iterator"); // 原型访问器:视图属性(buffer/byteOffset/byteLength/length)读内部数据槽, // @@toStringTag 返回具体类型名,供 Object.prototype.toString 区分类型。 @@ -153,6 +237,11 @@ pub fn bind_typed_array(core: &Arc, session: &KernelSession, global: 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!( core, global,