diff --git a/crates/oxide_builtins/src/map.rs b/crates/oxide_builtins/src/map.rs index 19b7dfdb..4034c640 100644 --- a/crates/oxide_builtins/src/map.rs +++ b/crates/oxide_builtins/src/map.rs @@ -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(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(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) +} diff --git a/crates/oxide_builtins/src/math.rs b/crates/oxide_builtins/src/math.rs index 3618fe46..21d7db0a 100644 --- a/crates/oxide_builtins/src/math.rs +++ b/crates/oxide_builtins/src/math.rs @@ -106,6 +106,106 @@ pub fn math_fround(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(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(vm: &mut H, args: &[u8]) -> NativeResult { let (a, b) = arg2(vm, args); diff --git a/crates/oxide_builtins/src/object.rs b/crates/oxide_builtins/src/object.rs index 4cdffa1e..07f20b2c 100644 --- a/crates/oxide_builtins/src/object.rs +++ b/crates/oxide_builtins/src/object.rs @@ -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(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(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(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(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(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(vm: &mut H, args: &[u8]) -> NativeResult { let obj_ptr = native_try!(require_obj_arg(vm, args, "values")); diff --git a/crates/oxide_builtins/src/string.rs b/crates/oxide_builtins/src/string.rs index f93fa77a..4a070b37 100644 --- a/crates/oxide_builtins/src/string.rs +++ b/crates/oxide_builtins/src/string.rs @@ -1659,3 +1659,117 @@ pub fn string_replace_all(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(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)) +} diff --git a/crates/oxide_emit/src/expr/template/tagged.rs b/crates/oxide_emit/src/expr/template/tagged.rs index a836c442..1305acfa 100644 --- a/crates/oxide_emit/src/expr/template/tagged.rs +++ b/crates/oxide_emit/src/expr/template/tagged.rs @@ -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), diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index e2f08274..98ca506f 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -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; diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index b8ee296a..c7c7bd78 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -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; /// 字符串→键规范化:规范数字串(`"5"`)映射整数键,其余 intern 字符串键。 /// 供建键入口(fromEntries/json/rest excluded)与 `property_key_si` 的字符串分支统一口径。 fn string_key_si(&mut self, s: &str) -> u32; diff --git a/crates/oxide_vm/src/bindings/bind_map.rs b/crates/oxide_vm/src/bindings/bind_map.rs index 9a40ba1c..7549f118 100644 --- a/crates/oxide_vm/src/bindings/bind_map.rs +++ b/crates/oxide_vm/src/bindings/bind_map.rs @@ -38,5 +38,13 @@ pub fn bind_map(core: &Arc, 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:: as *const ()); + // Map.groupBy 静态方法。 + apply_binding_table( + session.builtin_world(), + ctor, + core, + &[("groupBy", oxide_builtins::map::map_group_by:: as *const (), 2)], + ); + bind_constructor!(core, global, "Map", ctor_ptr, oxide_builtins::map::map_constructor::, 1, hash: true); } diff --git a/crates/oxide_vm/src/bindings/bind_math.rs b/crates/oxide_vm/src/bindings/bind_math.rs index eb3f7192..78f397a2 100644 --- a/crates/oxide_vm/src/bindings/bind_math.rs +++ b/crates/oxide_vm/src/bindings/bind_math.rs @@ -52,6 +52,7 @@ pub fn bind_math(core: &Arc, session: &KernelSession, global: &mut J ("atanh", oxide_builtins::math::math_atanh::, 1), ("clz32", oxide_builtins::math::math_clz32::, 1), ("expm1", oxide_builtins::math::math_expm1::, 1), + ("f16round", oxide_builtins::math::math_f16round::, 1), ("fround", oxide_builtins::math::math_fround::, 1), ("log1p", oxide_builtins::math::math_log1p::, 1), ); diff --git a/crates/oxide_vm/src/bindings/bind_object.rs b/crates/oxide_vm/src/bindings/bind_object.rs index a855e67b..4a570d33 100644 --- a/crates/oxide_vm/src/bindings/bind_object.rs +++ b/crates/oxide_vm/src/bindings/bind_object.rs @@ -34,6 +34,7 @@ pub fn bind_object(core: &Arc, session: &KernelSession, global: &mut has_own_property: oxide_builtins::object::object_proto_has_own_property:: as *const (), property_is_enumerable: oxide_builtins::object::object_proto_property_is_enumerable:: as *const (), + group_by: oxide_builtins::object::object_group_by:: as *const (), }; session .builtin_world() @@ -52,10 +53,15 @@ pub fn bind_object(core: &Arc, session: &KernelSession, global: &mut 0, ), ("valueOf", oxide_builtins::object::object_proto_value_of:: as *const (), 0), + ( + "__defineGetter__", + oxide_builtins::object::object_proto_define_getter:: 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)); diff --git a/crates/oxide_vm/src/bindings/bind_string.rs b/crates/oxide_vm/src/bindings/bind_string.rs index 1d1d51db..dc22d7eb 100644 --- a/crates/oxide_vm/src/bindings/bind_string.rs +++ b/crates/oxide_vm/src/bindings/bind_string.rs @@ -42,6 +42,7 @@ pub fn bind_string(core: &Arc, session: &KernelSession, global: &mut from_code_point: oxide_builtins::string::string_from_code_point:: as *const (), is_well_formed: oxide_builtins::string::string_is_well_formed:: as *const (), to_well_formed: oxide_builtins::string::string_to_well_formed:: as *const (), + from_raw: oxide_builtins::string::string_raw:: as *const (), }; session.builtin_world().bind_string_methods( &string_methods, diff --git a/crates/oxide_vm/src/promise.rs b/crates/oxide_vm/src/promise.rs index 2d754188..532a32a7 100644 --- a/crates/oxide_vm/src/promise.rs +++ b/crates/oxide_vm/src/promise.rs @@ -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] }); diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 713a4efe..b8488249 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -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 { + // 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)