mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 03:01:12 +08:00
feat: String.raw/groupBy/withResolvers/f16round/__defineGetter__ 低垂果实批量实现
This commit is contained in:
@@ -6,6 +6,8 @@ use crate::set::SetKey;
|
||||
|
||||
use oxide_runtime_api::{NativeResult, VmHost};
|
||||
|
||||
use crate::builtins_debug;
|
||||
|
||||
macro_rules! native_try {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
@@ -366,3 +368,136 @@ pub fn map_keys<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
crate::iterator::MapSetMode::MapKeys,
|
||||
))
|
||||
}
|
||||
|
||||
/// `Map.groupBy(items, callbackFn)`:按回调返回的键分组迭代元素到新 Map。
|
||||
/// 回调以 `(element, key)` 调用(对数组 items,key 为下标),返回值作为分组键
|
||||
/// (SameValueZero 语义,±0 归 +0 由 Map.set 键比较处理)。
|
||||
pub fn map_group_by<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("Map.groupBy called with {} args", args.len());
|
||||
if args.len() < 3 {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Map.groupBy requires 2 arguments"));
|
||||
}
|
||||
let callback_val = vm.reg(args[2]);
|
||||
if !crate::iterator::is_callable(callback_val) {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Map.groupBy: callbackFn is not callable"));
|
||||
}
|
||||
let items_val = vm.reg(args[1]);
|
||||
// 创建新 Map。
|
||||
let map_obj = alloc_map(vm);
|
||||
let map_val = JsValue::from_js_object(map_obj);
|
||||
// 取 @@iterator 方法(items 经 ToObject 装箱,null/undefined 抛 TypeError)。
|
||||
let items_obj = match oxide_runtime_api::to_object(items_val, vm) {
|
||||
Ok(v) => v,
|
||||
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
|
||||
};
|
||||
let items_ptr = items_obj.as_js_object_ptr();
|
||||
let sym_iter_si = oxide_types::private_key::make_well_known_symbol_key(0);
|
||||
let iter_method = match unsafe { vm.ordinary_get(&*items_ptr, sym_iter_si, items_val) } {
|
||||
Ok(m) => m,
|
||||
Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)),
|
||||
};
|
||||
if !crate::iterator::is_callable(iter_method) {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Map.groupBy: items[Symbol.iterator] is not callable"));
|
||||
}
|
||||
let iter_val = match vm.call_function_sync(iter_method, items_val, &[]) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
};
|
||||
if !iter_val.is_object() || iter_val.as_js_object_ptr().is_null() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Map.groupBy: iterator is not an object"));
|
||||
}
|
||||
let iter_obj = unsafe { &*iter_val.as_js_object_ptr() };
|
||||
let next_si = vm.kernel_core().perm_interner().intern("next").0;
|
||||
let next_fn = match vm.ordinary_get(iter_obj, next_si, iter_val) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)),
|
||||
};
|
||||
// 读取 Map.set / Map.get 方法。
|
||||
let map_ref = unsafe { &*map_obj };
|
||||
let set_si = vm.kernel_core().perm_interner().intern("set").0;
|
||||
let adder = match vm.ordinary_get(map_ref, set_si, map_val) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)),
|
||||
};
|
||||
let get_si = vm.kernel_core().perm_interner().intern("get").0;
|
||||
let getter = match vm.ordinary_get(map_ref, get_si, map_val) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)),
|
||||
};
|
||||
if !crate::iterator::is_callable(adder) || !crate::iterator::is_callable(getter) {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Map.groupBy: Map.set/get is not callable"));
|
||||
}
|
||||
let mut counter: i32 = 0;
|
||||
loop {
|
||||
let next_result = match vm.call_function_sync(next_fn, iter_val, &[]) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
};
|
||||
if !next_result.is_object() || next_result.as_js_object_ptr().is_null() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Map.groupBy: iterator next() returned non-object"));
|
||||
}
|
||||
let nr = unsafe { &*next_result.as_js_object_ptr() };
|
||||
let done_si = vm.kernel_core().perm_interner().intern("done").0;
|
||||
let done = match vm.ordinary_get(nr, done_si, next_result) {
|
||||
Ok(v) => oxide_runtime_api::to_boolean(v),
|
||||
Err(_) => false,
|
||||
};
|
||||
if done {
|
||||
break;
|
||||
}
|
||||
let value_si = vm.kernel_core().perm_interner().intern("value").0;
|
||||
let element = match vm.ordinary_get(nr, value_si, next_result) {
|
||||
Ok(v) => v,
|
||||
Err(_) => JsValue::undefined(),
|
||||
};
|
||||
// 调用 callbackFn(element, counter):counter 对数组 items 即元素下标 k。
|
||||
let group_key = match vm.call_function_sync(callback_val, JsValue::undefined(), &[element, JsValue::int(counter)]) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
};
|
||||
// 取该键已有分组数组;无则新建空数组并 set 到 Map,再 push 元素。
|
||||
let existing = match vm.call_function_sync(getter, map_val, &[group_key]) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
};
|
||||
let arr_val = if existing.is_object()
|
||||
&& !existing.as_js_object_ptr().is_null()
|
||||
&& unsafe { &*existing.as_js_object_ptr() }.is_array()
|
||||
{
|
||||
existing
|
||||
} else {
|
||||
let arr_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let arr = vm.alloc_object(JsObject::new_array(
|
||||
EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(arr_proto),
|
||||
0,
|
||||
vm.epoch().bump(),
|
||||
));
|
||||
let new_arr_val = JsValue::from_js_object(arr);
|
||||
if let Err(e) = vm.call_function_sync(adder, map_val, &[group_key, new_arr_val]) {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
new_arr_val
|
||||
};
|
||||
// push element 到分组数组(写入前 promote,与 Array.prototype.push 同款)。
|
||||
let arr_obj = unsafe { &mut *arr_val.as_js_object_ptr() };
|
||||
let idx = arr_obj.prop_count();
|
||||
let promoted_elem = vm.promote_if_needed_for_write_ptr(arr_val.as_js_object_ptr(), element);
|
||||
arr_obj.set_prop_at(idx, promoted_elem);
|
||||
counter += 1;
|
||||
}
|
||||
NativeResult::Ok(map_val)
|
||||
}
|
||||
|
||||
@@ -106,6 +106,106 @@ pub fn math_fround<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(JsValue::float(arg1(vm, args) as f32 as f64))
|
||||
}
|
||||
|
||||
/// IEEE 754 binary16(float16)舍入:f64 → binary16(round-to-nearest-even)→ 还原为 f64。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 拆 f64 位(符号 / 指数 / 尾数),特殊值(±0 / 无穷 / NaN)直接保位
|
||||
/// 2. 正规 f16 路径:53 位有效位收缩到 11 位(丢 42 位)做 RNE,进位溢出则指数 +1
|
||||
/// 3. 次正规 f16 路径:指数 < -14 时右移舍入到 10 位尾数(步长 2⁻²⁴),
|
||||
/// 溢出进位到最小正规 2⁻¹⁴;指数 < -25 时塌缩到 ±0
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - RNE 半位判定:丢弃部分 > 半位进位;== 半位且保留位末位为 1 进位(round-half-even)
|
||||
/// - 尾数进位溢出到指数后若超出 f16 指数上限 15 → ±Infinity(与规范溢出点一致)
|
||||
/// - 输出直接以 f16 的 u16 位模式给出,供 Math.f16round 还原为 f64
|
||||
/// 或 DataView setFloat16 写缓冲复用
|
||||
fn f64_to_f16_bits(x: f64) -> u16 {
|
||||
let bits = x.to_bits();
|
||||
let sign = ((bits >> 63) & 1) as u16;
|
||||
let exp = ((bits >> 52) & 0x7FF) as i32;
|
||||
let mant = bits & 0xF_FFFF_FFFF_FFFF;
|
||||
|
||||
// 零与次正规 f64(exp==0):粒度不足 binary16,直接塌缩到 ±0。
|
||||
if exp == 0 {
|
||||
return sign << 15;
|
||||
}
|
||||
// 无穷 / NaN:exp 字段全 1,NaN 尾数置非零位(保持 NaN 语义)。
|
||||
if exp == 0x7FF {
|
||||
let mant16 = if mant == 0 { 0 } else { 0x200 };
|
||||
return (sign << 15) | 0x7C00 | mant16;
|
||||
}
|
||||
|
||||
let e = exp - 1023; // 真实二进制指数(含隐含位)。
|
||||
let significand = (1u64 << 52) | mant; // 53 位有效位。
|
||||
|
||||
if e > 15 {
|
||||
// 值 ≥ 2¹⁶:必大于溢出舍入阈值(65520 起进 Inf),直接返回 ±Infinity。
|
||||
return (sign << 15) | 0x7C00;
|
||||
}
|
||||
if e >= -14 {
|
||||
// 正规 f16:11 位有效位,收缩 42 位做 RNE。
|
||||
let shift = 42;
|
||||
let dropped = significand & ((1u64 << shift) - 1);
|
||||
let half = 1u64 << (shift - 1);
|
||||
let mut q = significand >> shift;
|
||||
if dropped > half || (dropped == half && (q & 1) == 1) {
|
||||
q += 1;
|
||||
}
|
||||
if q == (1u64 << 11) {
|
||||
// 11 位有效位溢出进位到指数;指数再溢出 → Infinity。
|
||||
let e16 = e + 1;
|
||||
if e16 > 15 {
|
||||
return (sign << 15) | 0x7C00;
|
||||
}
|
||||
return (sign << 15) | (((e16 + 15) as u16) << 10);
|
||||
}
|
||||
return (sign << 15) | (((e + 15) as u16) << 10) | ((q & 0x3FF) as u16);
|
||||
}
|
||||
if e < -25 {
|
||||
// 小于最小次正规一半(2⁻²⁵)→ RNE 塌缩到 ±0。
|
||||
return sign << 15;
|
||||
}
|
||||
// 次正规 f16:值 = 10 位尾数 × 2⁻²⁴,右移 (28 - e) 位做 RNE。
|
||||
let shift = (28 - e) as u32;
|
||||
let dropped = significand & ((1u64 << shift) - 1);
|
||||
let half = 1u64 << (shift - 1);
|
||||
let mut q = significand >> shift;
|
||||
if dropped > half || (dropped == half && (q & 1) == 1) {
|
||||
q += 1;
|
||||
}
|
||||
// q 进位到 1024 → 最小正规 2⁻¹⁴(exp 字段 1,尾数 0);否则次正规(exp 字段 0)。
|
||||
if q == 1024 {
|
||||
return (sign << 15) | (1 << 10);
|
||||
}
|
||||
(sign << 15) | (q as u16)
|
||||
}
|
||||
|
||||
/// IEEE 754 binary16 → f64 精确还原(f16 有效位 ≤ 11 bit,f64 全可精确表示)。
|
||||
/// 供 Math.f16round 与 DataView getFloat16 复用。
|
||||
fn f16_bits_to_f64(h: u16) -> f64 {
|
||||
let sign = ((h >> 15) & 1) as f64;
|
||||
let exp = ((h >> 10) & 0x1F) as i32;
|
||||
let mant = (h & 0x3FF) as f64;
|
||||
let v = if exp == 0 {
|
||||
// 零与次正规:mant × 2⁻²⁴。
|
||||
mant * 2.0f64.powi(-24)
|
||||
} else if exp == 0x1F {
|
||||
if mant == 0.0 {
|
||||
return if sign == 1.0 { f64::NEG_INFINITY } else { f64::INFINITY };
|
||||
}
|
||||
return f64::NAN;
|
||||
} else {
|
||||
// 正规:(1 + mant/2¹⁰) × 2^(exp-15)。
|
||||
(1.0 + mant / 1024.0) * 2.0f64.powi(exp - 15)
|
||||
};
|
||||
if sign == 1.0 { -v } else { v }
|
||||
}
|
||||
|
||||
/// `Math.f16round`:把 double 舍入到 binary16 精度再还原为 double。
|
||||
pub fn math_f16round<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(JsValue::float(f16_bits_to_f64(f64_to_f16_bits(arg1(vm, args)))))
|
||||
}
|
||||
|
||||
/// `Math.hypot`:返回 sqrt(a²+b²)(当前仅支持两个参数)。
|
||||
pub fn math_hypot<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (a, b) = arg2(vm, args);
|
||||
|
||||
@@ -9,6 +9,8 @@ use oxide_types::value::JsValue;
|
||||
|
||||
use oxide_runtime_api::{NativeResult, VmHost};
|
||||
|
||||
use crate::builtins_debug;
|
||||
|
||||
fn is_integer_index(key: &str) -> bool {
|
||||
if key.is_empty() || key.len() > 1 && key.as_bytes()[0] == b'0' {
|
||||
return false;
|
||||
@@ -1233,6 +1235,52 @@ pub fn object_proto_property_is_enumerable<H: VmHost>(vm: &mut H, args: &[u8]) -
|
||||
NativeResult::Ok(JsValue::bool(enumerable))
|
||||
}
|
||||
|
||||
/// `Object.prototype.__defineGetter__(key, getter)`:把 key 定义为访问器属性,
|
||||
/// getter 作 `[[Get]]`,描述符 `{ enumerable: true, configurable: true }`。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. ToObject 装箱 this(null/undefined 抛 TypeError)
|
||||
/// 2. getter 非 callable → TypeError
|
||||
/// 3. ToPropertyKey 求键
|
||||
/// 4. DefinePropertyOrThrow(复用 `define_accessor_property` 的拒绝语义:
|
||||
/// 不可扩展对象 / 不可配置属性覆盖均抛 TypeError)
|
||||
///
|
||||
/// # 副作用
|
||||
/// - 修改 this 的 shape 链与属性表;返回 undefined
|
||||
pub fn object_proto_define_getter<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 3 {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Object.prototype.__defineGetter__ requires 2 arguments",
|
||||
));
|
||||
}
|
||||
let this_val = vm.reg(args[0]);
|
||||
let obj_val = match oxide_runtime_api::to_object(this_val, vm) {
|
||||
Ok(v) => v,
|
||||
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
|
||||
};
|
||||
let getter = vm.reg(args[2]);
|
||||
if !is_callable(getter) {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Object.prototype.__defineGetter__: getter must be callable",
|
||||
));
|
||||
}
|
||||
let key_si = match vm.to_property_key_si(vm.reg(args[1])) {
|
||||
Ok(si) => si,
|
||||
Err(e) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
};
|
||||
let obj = unsafe { &mut *obj_val.as_js_object_ptr() };
|
||||
let attrs = PropAttributes::new(false, true, true);
|
||||
if let Err(e) = vm.define_accessor_property(obj, key_si, getter, JsValue::undefined(), attrs) {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, &e));
|
||||
}
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `Object.entries(obj)`:返回可枚举自身属性的 `[key, value]` 对数组。
|
||||
pub fn object_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let obj_ptr = native_try!(require_obj_arg(vm, args, "entries"));
|
||||
@@ -1277,6 +1325,124 @@ pub fn object_entries<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(JsValue::from_js_object(arr))
|
||||
}
|
||||
|
||||
/// `Object.groupBy(items, callbackFn)`:按回调返回的键分组迭代元素到新对象。
|
||||
/// 回调以 `(element, key)` 调用(对数组 items,key 为下标),返回值经
|
||||
/// ToPropertyKey 转换后作为分组键;结果对象原型为 null。
|
||||
pub fn object_group_by<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("Object.groupBy called with {} args", args.len());
|
||||
if args.len() < 3 {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Object.groupBy requires 2 arguments"));
|
||||
}
|
||||
let callback_val = vm.reg(args[2]);
|
||||
if !is_callable(callback_val) {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Object.groupBy: callbackFn is not callable"));
|
||||
}
|
||||
let items_val = vm.reg(args[1]);
|
||||
// 取 @@iterator 方法(同步)。
|
||||
let items_obj = match oxide_runtime_api::to_object(items_val, vm) {
|
||||
Ok(v) => v,
|
||||
Err(msg) => return NativeResult::Err(crate::error::create_type_error(vm, &msg)),
|
||||
};
|
||||
let items_ptr = items_obj.as_js_object_ptr();
|
||||
let sym_iter_si = make_well_known_symbol_key(0);
|
||||
let iter_method = match unsafe { vm.ordinary_get(&*items_ptr, sym_iter_si, items_val) } {
|
||||
Ok(m) => m,
|
||||
Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)),
|
||||
};
|
||||
if !is_callable(iter_method) {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Object.groupBy: items[Symbol.iterator] is not callable"));
|
||||
}
|
||||
let iter_val = match vm.call_function_sync(iter_method, items_val, &[]) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
};
|
||||
if !iter_val.is_object() || iter_val.as_js_object_ptr().is_null() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Object.groupBy: iterator is not an object"));
|
||||
}
|
||||
let iter_obj = unsafe { &*iter_val.as_js_object_ptr() };
|
||||
let next_si = vm.kernel_core().perm_interner().intern("next").0;
|
||||
let next_fn = match vm.ordinary_get(iter_obj, next_si, iter_val) {
|
||||
Ok(f) => f,
|
||||
Err(e) => return NativeResult::Err(crate::error::create_type_error(vm, &e)),
|
||||
};
|
||||
// 创建结果对象(OrdinaryObjectCreate(null),与 Object.create(null) 同款)。
|
||||
let result = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
|
||||
let result_val = JsValue::from_js_object(result);
|
||||
let mut counter: i32 = 0;
|
||||
loop {
|
||||
let next_result = match vm.call_function_sync(next_fn, iter_val, &[]) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
};
|
||||
if !next_result.is_object() || next_result.as_js_object_ptr().is_null() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Object.groupBy: iterator next() returned non-object"));
|
||||
}
|
||||
let nr = unsafe { &*next_result.as_js_object_ptr() };
|
||||
let done_si = vm.kernel_core().perm_interner().intern("done").0;
|
||||
let done = match vm.ordinary_get(nr, done_si, next_result) {
|
||||
Ok(v) => oxide_runtime_api::to_boolean(v),
|
||||
Err(_) => false,
|
||||
};
|
||||
if done {
|
||||
break;
|
||||
}
|
||||
let value_si = vm.kernel_core().perm_interner().intern("value").0;
|
||||
let element = match vm.ordinary_get(nr, value_si, next_result) {
|
||||
Ok(v) => v,
|
||||
Err(_) => JsValue::undefined(),
|
||||
};
|
||||
// 调用 callbackFn(element, counter):counter 对数组 items 即元素下标 k。
|
||||
let key = match vm.call_function_sync(callback_val, JsValue::undefined(), &[element, JsValue::int(counter)]) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
};
|
||||
// ToPropertyKey:undefined 归 "undefined" 分组,不跳过;对象转换异常原样传播。
|
||||
let key_si = match vm.to_property_key_si(key) {
|
||||
Ok(si) => si,
|
||||
Err(e) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
};
|
||||
// 检查 result 上是否已有该键的数组分组。
|
||||
let result_ref = unsafe { &*result };
|
||||
let existing = match vm.ordinary_get(result_ref, key_si, result_val) {
|
||||
Ok(v) if v.is_object() && !v.as_js_object_ptr().is_null() => {
|
||||
let arr = unsafe { &*v.as_js_object_ptr() };
|
||||
if arr.is_array() { Some(v) } else { None }
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let arr_val = if let Some(existing) = existing {
|
||||
existing
|
||||
} else {
|
||||
let arr_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let arr = vm.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(arr_proto), 0, vm.epoch().bump()));
|
||||
let new_arr_val = JsValue::from_js_object(arr);
|
||||
let result_ref_mut = unsafe { &mut *result };
|
||||
let promoted = vm.promote_if_needed_for_write_ptr(result, new_arr_val);
|
||||
let _ = vm.ordinary_set(result_ref_mut, key_si, promoted, result_val);
|
||||
new_arr_val
|
||||
};
|
||||
// push element 到分组数组(写入前 promote,与 Array.prototype.push 同款)。
|
||||
let arr_obj = unsafe { &mut *arr_val.as_js_object_ptr() };
|
||||
let idx = arr_obj.prop_count();
|
||||
let promoted_elem = vm.promote_if_needed_for_write_ptr(arr_val.as_js_object_ptr(), element);
|
||||
arr_obj.set_prop_at(idx, promoted_elem);
|
||||
counter += 1;
|
||||
}
|
||||
NativeResult::Ok(result_val)
|
||||
}
|
||||
|
||||
/// `Object.values(obj)`:返回可枚举自身属性的值数组。
|
||||
pub fn object_values<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let obj_ptr = native_try!(require_obj_arg(vm, args, "values"));
|
||||
|
||||
@@ -1659,3 +1659,117 @@ pub fn string_replace_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.replaceAll called with {} args", args.len());
|
||||
string_replace_impl(vm, args, true)
|
||||
}
|
||||
|
||||
/// `String.raw(template, ...substitutions)`:从模板对象的 `raw` 数组元素与
|
||||
/// substitutions 交错拼接,返回原始字符串。raw 元素按 ToString 转换,
|
||||
/// substitutions 按原始 JS 语义经 ToString 拼接。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 取 template 的 `raw` 属性(必须为对象,否则抛 TypeError)
|
||||
/// 2. 取 raw 的 `length` 属性(ToLength)
|
||||
/// 3. 逐下标读 raw[i] 拼接;仅在段间(i + 1 < length)追加 substitutions[i]
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - raw[i] 缺失时视为 undefined(ToString 为 "undefined")
|
||||
/// - substitutions 缺失时段间追加空串,末段后不追加任何内容
|
||||
pub fn string_raw<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.raw called with {} args", args.len());
|
||||
// Step 1: 取 template 参数(args[1] 为 this 区域后的首个实参)。
|
||||
if args.len() < 2 {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "String.raw: template is required"));
|
||||
}
|
||||
let template_val = vm.reg(args[1]);
|
||||
if !template_val.is_object() || template_val.as_js_object_ptr().is_null() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "String.raw: template must be an object"));
|
||||
}
|
||||
let template_ptr = template_val.as_js_object_ptr();
|
||||
|
||||
// Step 2: 取 template.raw 属性。
|
||||
let raw_si = vm.kernel_core().perm_interner().intern("raw").0;
|
||||
let template_obj = unsafe { &*template_ptr };
|
||||
let raw_val = match vm.ordinary_get(template_obj, raw_si, template_val) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
};
|
||||
if !raw_val.is_object() || raw_val.as_js_object_ptr().is_null() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "String.raw: template.raw must be an object"));
|
||||
}
|
||||
let raw_ptr = raw_val.as_js_object_ptr();
|
||||
|
||||
// Step 3: 取 raw.length(ToLength,Symbol 抛 TypeError)。
|
||||
let length_si = vm.kernel_core().perm_interner().intern("length").0;
|
||||
let raw_obj = unsafe { &*raw_ptr };
|
||||
let raw_len = match vm.ordinary_get(raw_obj, length_si, raw_val) {
|
||||
Ok(v) => {
|
||||
// ToLength:经 to_number_full(Symbol 抛 TypeError / 对象 ToPrimitive 异常
|
||||
// 原样传播),再按 ToLength 归一并截断(防超大 raw 数组导致 OOM)。
|
||||
match oxide_runtime_api::to_number_full(v, vm) {
|
||||
Ok(n) => {
|
||||
let len = if n.is_nan() || n <= 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
n.min(9_007_199_254_740_991.0)
|
||||
};
|
||||
(len.trunc() as u64).min(0x1FFFFF) as usize
|
||||
}
|
||||
Err(msg) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &e));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
};
|
||||
|
||||
// Step 4: 逐下标拼接 raw[i] + substitutions[i]。
|
||||
let mut result = String::new();
|
||||
let raw_obj_ref = unsafe { &*raw_ptr };
|
||||
for i in 0..raw_len {
|
||||
// 读 raw[i]:用 property_key_si 把整数 i 映射为整数键 si,
|
||||
// 再经 ordinary_get 沿原型链查找(触发 getter)。
|
||||
let index_key = vm.property_key_si(JsValue::int(i as i32));
|
||||
let raw_elem = vm.ordinary_get(raw_obj_ref, index_key, raw_val);
|
||||
let raw_str = match raw_elem {
|
||||
// ToString 完整路径:对象经 ToPrimitive(异常原样传播),Symbol 抛 TypeError。
|
||||
Ok(v) => match oxide_runtime_api::to_string_full(v, vm) {
|
||||
Ok(s) => s,
|
||||
Err(msg) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
"undefined".to_string()
|
||||
}
|
||||
};
|
||||
result.push_str(&raw_str);
|
||||
|
||||
// 段间(i + 1 < length)才追加 substitutions[i];substitutions 缺失时
|
||||
// 追加空串(spec 24.b.i),末段后不再追加任何内容。
|
||||
if i + 1 < raw_len {
|
||||
let sub_idx = i + 2;
|
||||
if sub_idx < args.len() {
|
||||
let sub_val = vm.reg(args[sub_idx]);
|
||||
// ToString 完整路径:对象 ToPrimitive 异常原样传播,Symbol 抛 TypeError。
|
||||
let sub_str = match oxide_runtime_api::to_string_full(sub_val, vm) {
|
||||
Ok(s) => s,
|
||||
Err(msg) => {
|
||||
let exc = vm.take_uncaught_value().unwrap_or_else(|| crate::error::create_type_error(vm, &msg));
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
};
|
||||
result.push_str(&sub_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(vm.new_string_owned(result))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user