mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
319 lines
12 KiB
Rust
319 lines
12 KiB
Rust
//! Array 族共享助手:`this`/类数组解析、长度与回调校验,以及 `array_ptr` 族宏。
|
||
|
||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||
use oxide_types::object::{JsObject, MAX_DENSE_PROPS};
|
||
use oxide_types::value::JsValue;
|
||
|
||
use oxide_runtime_api::{NativeResult, ProtoKind, VmHost};
|
||
|
||
use super::from::from_engine_error;
|
||
|
||
macro_rules! array_ptr {
|
||
($vm:expr, $args:expr) => {{
|
||
match get_this_array_ref($vm, $vm.reg($args[0])) {
|
||
Ok(ptr) => ptr,
|
||
Err(err) => {
|
||
builtins_error!("Array method: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
}
|
||
}};
|
||
}
|
||
pub(crate) use array_ptr;
|
||
|
||
#[allow(unused_macros)]
|
||
macro_rules! array_ptr_len {
|
||
($vm:expr, $args:expr) => {{
|
||
let this_val = $vm.reg($args[0]);
|
||
let (arr_ptr, len, _is_arr) = match get_this_arraylike($vm, this_val) {
|
||
Ok(v) => v,
|
||
Err(err) => {
|
||
builtins_error!("Array method: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
};
|
||
(arr_ptr, len)
|
||
}};
|
||
}
|
||
#[expect(unused_imports)] // 无调用点的预留宏,保留与其余助手宏一致的导出
|
||
pub(crate) use array_ptr_len;
|
||
|
||
macro_rules! array_ptr_len3 {
|
||
($vm:expr, $args:expr) => {{
|
||
let this_val = $vm.reg($args[0]);
|
||
let (arr_ptr, len, is_arr) = match get_this_arraylike($vm, this_val) {
|
||
Ok(v) => v,
|
||
Err(err) => {
|
||
builtins_error!("Array method: invalid receiver");
|
||
return NativeResult::Err(err);
|
||
}
|
||
};
|
||
(arr_ptr, len, is_arr)
|
||
}};
|
||
}
|
||
pub(crate) use array_ptr_len3;
|
||
|
||
pub(crate) fn array_type_error<H: VmHost>(vm: &mut H, msg: &str) -> JsValue {
|
||
crate::error::create_type_error(vm, msg)
|
||
}
|
||
|
||
pub(crate) fn get_this_array_ref<H: VmHost>(vm: &mut H, val: JsValue) -> Result<*mut JsObject, JsValue> {
|
||
if !val.is_object() {
|
||
return Err(array_type_error(vm, "Array method called on incompatible receiver"));
|
||
}
|
||
let ptr = val.as_js_object_ptr();
|
||
if ptr.is_null() {
|
||
return Err(array_type_error(vm, "Array method called on incompatible receiver"));
|
||
}
|
||
let obj = unsafe { &*ptr };
|
||
if !obj.is_array() {
|
||
return Err(array_type_error(vm, "Array method called on incompatible receiver"));
|
||
}
|
||
Ok(ptr)
|
||
}
|
||
|
||
/// 取 this 为真数组或类数组对象(带 length 属性的普通对象),按规范读取长度,
|
||
/// 返回 (对象指针, 长度, 是否为真数组),供遍历查找族(forEach/map/filter/reduce/
|
||
/// some/every/find 族/flatMap)、切片拼接族(slice/concat/join/indexOf/includes/
|
||
/// lastIndexOf)与不可变复制族(toReversed/with/toSorted/toSpliced)共用。
|
||
///
|
||
/// # 步骤
|
||
/// 1. this 非对象(null/undefined/原始值)→ TypeError。
|
||
/// 2. 按规范 Get 语义读 length(getter 抛错向上传播,不走 prop_count 直读)。
|
||
/// 3. ToLength:ToIntegerOrInfinity 后负数与 NaN 归 0,上限 2^53-1。
|
||
#[inline(always)]
|
||
pub(crate) fn get_this_arraylike<H: VmHost>(vm: &mut H, val: JsValue) -> Result<(*mut JsObject, usize, bool), JsValue> {
|
||
if !val.is_object() {
|
||
return Err(array_type_error(vm, "Array.prototype method called on null or undefined"));
|
||
}
|
||
let ptr = val.as_js_object_ptr();
|
||
if ptr.is_null() {
|
||
return Err(array_type_error(vm, "Array.prototype method called on null or undefined"));
|
||
}
|
||
let obj = unsafe { &*ptr };
|
||
let is_array = obj.is_array();
|
||
// 读取 length 属性(按规范 Get 语义,getter 抛错需向上传播)。
|
||
// 真数组也走 Get:`arr.length = N` 已由 ordinary_set 的数组分支同步元素区,
|
||
// 直接 prop_count() 会漏掉 length 赋值。
|
||
let length_key = vm.new_string("length");
|
||
let length_si = vm.property_key_si(length_key);
|
||
let len_val = match vm.ordinary_get(unsafe { &*ptr }, length_si, val) {
|
||
Ok(v) => v,
|
||
Err(msg) => return Err(from_engine_error(vm, &msg)),
|
||
};
|
||
if len_val.is_symbol() {
|
||
return Err(array_type_error(vm, "Cannot convert a Symbol value to a number"));
|
||
}
|
||
let len_num = match vm.coerce_number_bounded(len_val) {
|
||
Ok(v) => v,
|
||
Err(msg) => return Err(from_engine_error(vm, &msg)),
|
||
};
|
||
// ToLength:len 先按 ToIntegerOrInfinity 取整,负数与 NaN 归 0,上限 2^53-1。
|
||
let len = if len_num.is_nan() || len_num <= 0.0 {
|
||
0
|
||
} else {
|
||
len_num.min(9_007_199_254_740_991.0) as usize
|
||
};
|
||
Ok((ptr, len, is_array))
|
||
}
|
||
|
||
/// 从 arraylike 读取 index 位置元素(按规范 Get:访问器/原型链/异常完整传播)。
|
||
#[inline(always)]
|
||
pub(crate) fn arraylike_get<H: VmHost>(vm: &mut H, ptr: *mut JsObject, i: usize) -> Result<JsValue, JsValue> {
|
||
// 真数组密集直读快路径:无元素元信息表(记录 hole/accessor 的可选侧表,
|
||
// 空指针判定为 O(1))且索引在元素区内时,直接读数据槽,免每元素构造数字串
|
||
// + 属性键转换。语义与 ordinary_get 的数组分支一致:无元素元信息表保证该
|
||
// 索引恒为数据属性,直读数据槽与规范 Get 结果相同。
|
||
let obj = unsafe { &*ptr };
|
||
if obj.is_array() && obj.array_elements_meta_vec().is_none() && i < obj.array_prop_count as usize {
|
||
return Ok(obj.get_prop_at(i));
|
||
}
|
||
let key_str = vm.new_string(&i.to_string());
|
||
let key_si = vm.property_key_si(key_str);
|
||
let recv = JsValue::from_js_object(ptr);
|
||
match vm.ordinary_get(obj, key_si, recv) {
|
||
Ok(v) => Ok(v),
|
||
Err(msg) => Err(from_engine_error(vm, &msg)),
|
||
}
|
||
}
|
||
|
||
/// 字符串源的 array-like 单元序列;非字符串源返回 `None`。
|
||
///
|
||
/// Array.from / %TypedArray%.from 的 array-like 回退按 UTF-16 单元直接读:
|
||
/// 原始串取自身单元,装箱串取其 `[[StringData]]`(boxed_value 载荷)。
|
||
/// 单元长度即 `length`,第 i 个索引为第 i 个码元。
|
||
///
|
||
/// # 边界
|
||
/// 装箱串载荷非字符串(异常形态)按非字符串源处理;指针为空返回 `None`。
|
||
pub(crate) fn string_arraylike_units<H: VmHost>(vm: &H, value: JsValue) -> Option<Vec<u16>> {
|
||
let raw = if value.is_string() {
|
||
value
|
||
} else if value.is_object() {
|
||
let ptr = value.as_js_object_ptr();
|
||
if ptr.is_null() {
|
||
return None;
|
||
}
|
||
// SAFETY: ptr 来自对象 JsValue,指向存活的 JsObject。
|
||
let obj = unsafe { &*ptr };
|
||
if !obj.is_string_obj() {
|
||
return None;
|
||
}
|
||
let data = obj.boxed_value();
|
||
if !data.is_string() {
|
||
return None;
|
||
}
|
||
data
|
||
} else {
|
||
return None;
|
||
};
|
||
Some(vm.string_units(raw).into_owned())
|
||
}
|
||
|
||
/// 取单个 UTF-16 单元对应的字符串值:ASCII 单元命中共享 perm 串,其余按单元序列创建。
|
||
pub(crate) fn unit_string_value<H: VmHost>(vm: &mut H, unit: u16) -> JsValue {
|
||
match vm.single_unit(unit) {
|
||
Some(v) => v,
|
||
None => vm.new_string_units(&[unit]),
|
||
}
|
||
}
|
||
|
||
/// 索引位置存在性判定(规范 HasProperty:自身与原型链任一层命中即存在,
|
||
/// 数组元素区 hole 视同缺失,TypedArray 整数索引按视图长度判在界)。
|
||
pub(crate) fn arraylike_index_present<H: VmHost>(vm: &mut H, ptr: *mut JsObject, i: usize) -> bool {
|
||
// SAFETY: ptr 来自 this/参数对象,指向存活对象。
|
||
let key_si = vm.string_key_si(&i.to_string());
|
||
vm.has_property(unsafe { &*ptr }, key_si)
|
||
}
|
||
|
||
/// 元素读取失败时直接把异常作为 NativeResult::Err 返回。
|
||
macro_rules! arraylike_get_or_err {
|
||
($vm:expr, $ptr:expr, $i:expr) => {
|
||
match arraylike_get($vm, $ptr, $i) {
|
||
Ok(v) => v,
|
||
Err(err) => return NativeResult::Err(err),
|
||
}
|
||
};
|
||
}
|
||
pub(crate) use arraylike_get_or_err;
|
||
|
||
/// 数组下标转 JsValue:超出 i32 范围时用 double 精确表示。
|
||
#[inline(always)]
|
||
pub(crate) fn js_array_index(i: usize) -> JsValue {
|
||
if i <= i32::MAX as usize {
|
||
JsValue::int(i as i32)
|
||
} else {
|
||
JsValue::float(i as f64)
|
||
}
|
||
}
|
||
|
||
/// ToIntegerOrInfinity 的 builtins 版本:对象经 ToPrimitive(number) 后取整,
|
||
/// NaN/±0 → 0,±Inf 保留;转换异常向上传播。
|
||
pub(crate) fn to_integer_or_infinity_bounded<H: VmHost>(vm: &mut H, val: JsValue) -> Result<f64, JsValue> {
|
||
let n = match vm.coerce_number_bounded(val) {
|
||
Ok(v) => v,
|
||
Err(msg) => return Err(from_engine_error(vm, &msg)),
|
||
};
|
||
Ok(if n.is_nan() || n == 0.0 { 0.0 } else { n.trunc() })
|
||
}
|
||
|
||
/// 把 ToIntegerOrInfinity 结果夹到 [0, len]:负数按 len+rel,正无穷取 len。
|
||
#[inline(always)]
|
||
pub(crate) fn clamp_relative(rel: f64, n_f: f64, n: usize) -> usize {
|
||
if rel.is_infinite() {
|
||
if rel < 0.0 {
|
||
0
|
||
} else {
|
||
n
|
||
}
|
||
} else if rel < 0.0 {
|
||
(n_f + rel).max(0.0) as usize
|
||
} else {
|
||
rel.min(n_f) as usize
|
||
}
|
||
}
|
||
|
||
/// IsConstructor 的近似判定(与 construct_array_from_result 保持一致)。
|
||
///
|
||
/// native 函数须带构造器 tag;bound 包装恒接受(target 可构造性在
|
||
/// `dispatch_new_bound` 入口复核,此处仅判值形态)。
|
||
#[inline]
|
||
pub(crate) fn is_constructor_value(c: JsValue) -> bool {
|
||
if !c.is_object() {
|
||
return false;
|
||
}
|
||
let c_obj = unsafe { &*c.as_js_object_ptr() };
|
||
c_obj.is_function()
|
||
&& !c_obj.is_arrow()
|
||
&& !(c_obj.native_fn().is_some()
|
||
&& c_obj.type_tag != JsObject::OBJ_TYPE_CONSTRUCTOR
|
||
&& c_obj.type_tag != JsObject::OBJ_TYPE_BOUND)
|
||
}
|
||
|
||
/// 校验回调参数为可调用函数对象(判空与 is_function 均须通过),不是则抛 TypeError。
|
||
pub(crate) fn require_callback<H: VmHost>(vm: &mut H, callback_val: JsValue) -> Result<JsValue, JsValue> {
|
||
if !callback_val.is_object() {
|
||
return Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
let ptr = callback_val.as_js_object_ptr();
|
||
if ptr.is_null() || !unsafe { &*ptr }.is_function() {
|
||
return Err(array_type_error(vm, "callback is not a function"));
|
||
}
|
||
Ok(callback_val)
|
||
}
|
||
|
||
pub(crate) fn create_new_array<H: VmHost>(vm: &mut H, n: usize) -> *mut JsObject {
|
||
let proto = vm.builtin_proto(ProtoKind::ArrayProto);
|
||
vm.alloc_object(JsObject::new_array(
|
||
EMPTY_SHAPE_ID,
|
||
JsValue::from_js_object(proto),
|
||
n.min(MAX_DENSE_PROPS),
|
||
))
|
||
}
|
||
|
||
/// ArrayCreate 前置检查:创建长度超过 2^32-1 时抛 RangeError
|
||
/// (密集数组长度按 u32 存储,超之不可容纳)。
|
||
pub(crate) fn check_array_create_len<H: VmHost>(vm: &mut H, n: usize) -> Result<(), JsValue> {
|
||
if n > u32::MAX as usize {
|
||
return Err(crate::error::create_range_error(vm, "Invalid array length"));
|
||
}
|
||
Ok(())
|
||
}
|
||
|
||
pub(crate) fn array_length_arg<H: VmHost>(vm: &mut H, value: JsValue) -> Result<usize, JsValue> {
|
||
let n = vm.coerce_number_bounded(value).unwrap_or(f64::NAN);
|
||
if !n.is_finite() || n < 0.0 || n.fract() != 0.0 || n > u32::MAX as f64 {
|
||
return Err(crate::error::create_range_error(vm, "Invalid array length"));
|
||
}
|
||
Ok(n as usize)
|
||
}
|
||
|
||
/// native 回调通用调用入口:先校验回调可调用(非对象、空指针、非函数均转
|
||
/// TypeError),再以 this_val 为接收者、cb_args 为实参同步调用用户回调;调用
|
||
/// 失败时优先取回未捕获的原始异常值,无原始值时按错误文本重建异常。
|
||
pub(crate) fn invoke_native_callback<H: VmHost>(
|
||
vm: &mut H, callback_val: JsValue, this_val: JsValue, cb_args: &[JsValue],
|
||
) -> NativeResult {
|
||
if !callback_val.is_object() {
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "callback is not a function"));
|
||
}
|
||
let cb_ptr = callback_val.as_js_object_ptr();
|
||
if cb_ptr.is_null() {
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "callback is not a function"));
|
||
}
|
||
let cb = unsafe { &*cb_ptr };
|
||
if !cb.is_function() {
|
||
return NativeResult::Err(crate::error::create_type_error(vm, "callback is not a function"));
|
||
}
|
||
match vm.call_function_sync(callback_val, this_val, cb_args) {
|
||
Ok(value) => NativeResult::Ok(value),
|
||
Err(err) => match vm.take_uncaught_value() {
|
||
Some(original) => NativeResult::Err(original),
|
||
None => NativeResult::Err(crate::error::create_from_text(vm, &err)),
|
||
},
|
||
}
|
||
}
|
||
|
||
pub(crate) fn unexpected_tail_call_error<H: VmHost>(vm: &mut H) -> NativeResult {
|
||
NativeResult::Err(crate::error::create_type_error(vm, "unexpected tail call in array callback"))
|
||
}
|