feat: String.raw/groupBy/withResolvers/f16round/__defineGetter__ 低垂果实批量实现

This commit is contained in:
2026-08-22 10:26:00 +08:00
parent d2572682dc
commit c7b54cb181
13 changed files with 591 additions and 4 deletions
+135
View File
@@ -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)
}
+100
View File
@@ -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);
+166
View File
@@ -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"));
+114
View File
@@ -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))
}
+14 -3
View File
@@ -60,13 +60,25 @@ impl Emitter {
));
}
// 模板对象:按 GetTemplateObject 语义把 raw 数组挂到 cooked 数组的 "raw"
// 属性,使标签函数首参为标准模板对象(cooked 数组 + raw 属性)。
// String.raw / 模板标签测试依赖 template.raw 存在。
let raw_key_idx = ctx.add_constant(Constant::String("raw".to_string()));
let raw_key_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(raw_key_reg), raw_key_idx));
ctx.inst(Inst::new(
OpCode::SET_PROP,
Operand::Reg(cooked_temp),
Operand::Reg(raw_temp),
Operand::Reg(raw_key_reg),
));
let mut expr_temps = Vec::new();
for expr in expressions {
expr_temps.push(self.emit_expression(expr, ctx)?);
}
let cooked_slot = ctx.alloc_reg();
let raw_slot = ctx.alloc_reg();
let mut expr_slots = Vec::new();
for _ in expressions {
expr_slots.push(ctx.alloc_reg());
@@ -78,7 +90,6 @@ impl Emitter {
Operand::Reg(cooked_temp),
Operand::None,
));
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(raw_slot), Operand::Reg(raw_temp), Operand::None));
for (slot, temp) in expr_slots.iter().zip(expr_temps.iter()) {
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(*slot), Operand::Reg(*temp), Operand::None));
}
@@ -87,7 +98,7 @@ impl Emitter {
let undef_reg = ctx.alloc_reg();
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
let arg_count = 2 + expressions.len();
let arg_count = 1 + expressions.len();
ctx.inst(Inst::call(
Operand::Reg(tag_reg),
Operand::Reg(undef_reg),
+4
View File
@@ -66,6 +66,7 @@ pub struct ObjectMethods {
pub values: *const (),
pub has_own_property: *const (),
pub property_is_enumerable: *const (),
pub group_by: *const (),
}
/// Array 静态方法与原型方法的 native 函数指针集合,由 [`BuiltinWorld::bind_array_methods`] 安装。
@@ -160,6 +161,7 @@ pub struct StringMethods {
pub from_code_point: *const (),
pub is_well_formed: *const (),
pub to_well_formed: *const (),
pub from_raw: *const (),
}
/// RegExp 原型方法的 native 函数指针集合。
@@ -1393,6 +1395,7 @@ impl BuiltinWorld {
("hasOwn", methods.has_own, 2),
("entries", methods.entries, 1),
("values", methods.values, 1),
("groupBy", methods.group_by, 2),
);
let proto_ptr = P::as_ptr(&self.object_proto) as *mut JsObject;
@@ -1576,6 +1579,7 @@ impl BuiltinWorld {
shape_forge,
("fromCharCode", methods.from_char_code, 1),
("fromCodePoint", methods.from_code_point, 1),
("raw", methods.from_raw, 1),
);
let proto_ptr = P::as_ptr(&self.string_proto) as *mut JsObject;
+4
View File
@@ -139,6 +139,10 @@ pub trait VmHost {
// 属性解析
fn property_key_si(&mut self, val: JsValue) -> u32;
/// ToPropertyKey 完整路径:键值 → 内部属性键 si。对象经 ToPrimitive(string
/// hint),转换异常以 `Err` 返回(调用方须取 `take_uncaught_value` 传播原异常)。
/// 与 `property_key_si` 的区别:后者对转换失败退化为空键,不传播异常。
fn to_property_key_si(&mut self, val: JsValue) -> Result<u32, String>;
/// 字符串→键规范化:规范数字串(`"5"`)映射整数键,其余 intern 字符串键。
/// 供建键入口(fromEntries/json/rest excluded)与 `property_key_si` 的字符串分支统一口径。
fn string_key_si(&mut self, s: &str) -> u32;
+8
View File
@@ -38,5 +38,13 @@ pub fn bind_map(core: &Arc<KernelCore>, session: &KernelSession, global: &mut Js
bind_well_known_method_alias(core, proto, "entries", 0);
bind_accessor_getter(core, session, proto, "size", oxide_builtins::map::map_size::<crate::vm::Vm> as *const ());
// Map.groupBy 静态方法。
apply_binding_table(
session.builtin_world(),
ctor,
core,
&[("groupBy", oxide_builtins::map::map_group_by::<crate::vm::Vm> as *const (), 2)],
);
bind_constructor!(core, global, "Map", ctor_ptr, oxide_builtins::map::map_constructor::<crate::vm::Vm>, 1, hash: true);
}
@@ -52,6 +52,7 @@ pub fn bind_math(core: &Arc<KernelCore>, session: &KernelSession, global: &mut J
("atanh", oxide_builtins::math::math_atanh::<crate::vm::Vm>, 1),
("clz32", oxide_builtins::math::math_clz32::<crate::vm::Vm>, 1),
("expm1", oxide_builtins::math::math_expm1::<crate::vm::Vm>, 1),
("f16round", oxide_builtins::math::math_f16round::<crate::vm::Vm>, 1),
("fround", oxide_builtins::math::math_fround::<crate::vm::Vm>, 1),
("log1p", oxide_builtins::math::math_log1p::<crate::vm::Vm>, 1),
);
+7 -1
View File
@@ -34,6 +34,7 @@ pub fn bind_object(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
has_own_property: oxide_builtins::object::object_proto_has_own_property::<crate::vm::Vm> as *const (),
property_is_enumerable: oxide_builtins::object::object_proto_property_is_enumerable::<crate::vm::Vm>
as *const (),
group_by: oxide_builtins::object::object_group_by::<crate::vm::Vm> as *const (),
};
session
.builtin_world()
@@ -52,10 +53,15 @@ pub fn bind_object(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
0,
),
("valueOf", oxide_builtins::object::object_proto_value_of::<crate::vm::Vm> as *const (), 0),
(
"__defineGetter__",
oxide_builtins::object::object_proto_define_getter::<crate::vm::Vm> as *const (),
2,
),
],
);
// 内置原型方法不可枚举(否则会泄漏进 for-in)。
for name in ["toString", "valueOf"] {
for name in ["toString", "valueOf", "__defineGetter__"] {
let si = core.perm_interner().intern(name).0;
if let Some(pos) = core.shape_forge().lookup_position(object_proto.shape_id(), si) {
object_proto.set_data_meta(pos, PropAttributes::new(true, false, true));
@@ -42,6 +42,7 @@ pub fn bind_string(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
from_code_point: oxide_builtins::string::string_from_code_point::<crate::vm::Vm> as *const (),
is_well_formed: oxide_builtins::string::string_is_well_formed::<crate::vm::Vm> as *const (),
to_well_formed: oxide_builtins::string::string_to_well_formed::<crate::vm::Vm> as *const (),
from_raw: oxide_builtins::string::string_raw::<crate::vm::Vm> as *const (),
};
session.builtin_world().bind_string_methods(
&string_methods,
+32
View File
@@ -764,6 +764,7 @@ impl Vm {
("race", promise_static_race as *const (), 1),
("allSettled", promise_static_all_settled as *const (), 1),
("any", promise_static_any as *const (), 1),
("withResolvers", promise_static_with_resolvers as *const (), 0),
);
// 固定地址后互相接线:proto.constructor ↔ ctor.prototype。
@@ -1284,6 +1285,37 @@ fn promise_static_reject(vm: &mut Vm, args: &[u8]) -> NativeResult {
}
}
/// `Promise.withResolvers()`:按 `this` 构造器建能力(非构造器抛 TypeError),
/// 返回 `{promise, resolve, reject}` 普通对象(proto 为 `%Object.prototype%`)。
///
/// # 步骤
/// 1. 取 `this` 为构造器 C,经 `new_promise_capability_with_ctor` 建能力
/// 2. 创建普通对象,依次写入 promise / resolve / reject 三个数据属性
///
/// # 边界与前提
/// - C 非构造器(普通值 / arrow / 非构造 native)时抛 TypeError
/// - 返回值属性为默认数据描述符(writable/enumerable/configurable 均 true)
fn promise_static_with_resolvers(vm: &mut Vm, args: &[u8]) -> NativeResult {
let ctor = vm.reg(if args.is_empty() { 0 } else { args[0] });
let (promise, resolve, reject) = match vm.new_promise_capability_with_ctor(ctor) {
Ok(t) => t,
Err(err) => return NativeResult::Err(err),
};
let object_proto =
JsValue::from_js_object(vm.session.builtin_world().object_proto.as_ptr() as *mut JsObject);
let ptr = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, object_proto));
let obj = unsafe { &mut *ptr };
let sf = vm.kernel_core.perm_interner().as_ref();
let sh = vm.kernel_core.shape_forge().as_ref();
for (name, val) in [("promise", promise), ("resolve", resolve), ("reject", reject)] {
let si = sf.intern(name).0;
let shape = sh.make_shape(obj.shape_id(), si);
obj.set_shape_id(shape);
obj.push_prop(val);
}
NativeResult::Ok(JsValue::from_js_object(ptr))
}
/// `Promise.all(iterable)`:全部元素结算后以结果数组完成,任一拒绝则拒绝。
fn promise_static_all(vm: &mut Vm, args: &[u8]) -> NativeResult {
let ctor = vm.reg(if args.is_empty() { 0 } else { args[0] });
+5
View File
@@ -2161,6 +2161,11 @@ impl oxide_runtime_api::VmHost for Vm {
self.property_key_si(val)
.unwrap_or_else(|_| self.kernel_core.perm_interner().intern("").0)
}
fn to_property_key_si(&mut self, val: JsValue) -> Result<u32, String> {
// ToPropertyKey 完整路径:转换异常(对象 ToPrimitive 抛错 / Symbol 处理)
// 原样返回,供需传播异常的 builtins(groupBy / __defineGetter__ 等)使用。
self.property_key_si(val)
}
fn string_key_si(&mut self, s: &str) -> u32 {
if let Some(i) = canonical_index_of(s) {
make_int_key(i)