From aa51d57660431bfdea8333d1e29311c1882168b8 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 12 Aug 2026 02:23:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20BigInt=20=E6=9E=84=E9=80=A0=E5=99=A8=20?= =?UTF-8?q?ToPrimitive=20=E8=AF=AD=E4=B9=89=E3=80=81toString(radix)?= =?UTF-8?q?=E3=80=81asIntN/asUintN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/bigint.rs | 381 ++++++++++++++++---- crates/oxide_kernel/src/builtin.rs | 29 ++ crates/oxide_vm/src/bindings/bind_bigint.rs | 26 +- 3 files changed, 371 insertions(+), 65 deletions(-) diff --git a/crates/oxide_builtins/src/bigint.rs b/crates/oxide_builtins/src/bigint.rs index 3e606d3f..d0bea0ed 100644 --- a/crates/oxide_builtins/src/bigint.rs +++ b/crates/oxide_builtins/src/bigint.rs @@ -1,16 +1,10 @@ -use oxide_runtime_api::{NativeResult, VmHost}; +use oxide_runtime_api::{to_primitive, NativeResult, ToPrimitiveHint, VmHost}; use oxide_types::value::JsValue; -/// JS `BigInt()` 构造逻辑:把参数转成 BigInt 值。 -/// -/// 普通调用返回原始 BigInt;`new BigInt()` 抛 TypeError(BigInt 不可构造)。 -/// 参数转换规则(最小子集): -/// - 无参数 → `0n` -/// - BigInt → 原样返回 -/// - Number → 截断为整数(非整数抛 RangeError) -/// - String → 解析十进制整数 -/// - Boolean → `0n`/`1n` -/// - 其它类型 → TypeError +/// JS `BigInt(value)` 构造逻辑(ES2020 §20.2.1.1): +/// - `new BigInt()` 抛 TypeError(BigInt 不可 new) +/// - 无参 → `0n` +/// - 其余参数先 ToPrimitive(number),Number 走 NumberToBigInt,其余走 ToBigInt。 pub fn bigint_constructor(vm: &mut H, args: &[u8]) -> NativeResult { if args.len() > 1 { let this_val = vm.reg(args[0]); @@ -22,31 +16,170 @@ pub fn bigint_constructor(vm: &mut H, args: &[u8]) -> NativeResult { )); } } - let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - match to_bigint_value(vm, val) { + // 无参 BigInt() → 0n(注意 BigInt(undefined) 必须抛 TypeError)。 + if args.len() <= 1 { + return NativeResult::Ok(vm.new_bigint(0)); + } + let val = vm.reg(args[1]); + // ToPrimitive(value, number):对象先出盒,用户 @@toPrimitive/valueOf/toString + // 抛出的异常要原样传播(只触发一次,见 constructor-coercion 测试)。 + let prim = match to_primitive(val, ToPrimitiveHint::Number, vm) { + Ok(p) => p, + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return NativeResult::Err(exc); + } + return NativeResult::Err(crate::error::create_type_error( + vm, + "Cannot convert value to a BigInt", + )); + } + }; + // 步骤 3:Type(prim) 为 Number → NumberToBigInt。 + if prim.is_int() { + return NativeResult::Ok(vm.new_bigint(prim.as_int() as i128)); + } + if prim.is_double() { + let d = prim.as_double(); + if d.is_nan() || d.is_infinite() || d.trunc() != d { + return NativeResult::Err(crate::error::create_range_error( + vm, + "The number cannot be converted to a BigInt because it is not an integer", + )); + } + // i128 暂存上限:超出范围的整数后续由任意精度表示补齐。 + if d < i128::MIN as f64 || d > i128::MAX as f64 { + return NativeResult::Err(crate::error::create_range_error( + vm, + "The number is too large to be converted to a BigInt", + )); + } + return NativeResult::Ok(vm.new_bigint(d.trunc() as i128)); + } + // 步骤 4:其它原始类型走 ToBigInt。 + match to_bigint(vm, prim) { Ok(v) => NativeResult::Ok(v), - Err(err) => NativeResult::Err(err), + Err(e) => NativeResult::Err(e), } } -/// `BigInt.prototype.toString`:返回 BigInt 的十进制字符串。 +/// `BigInt.prototype.toString(radix)`:thisBigIntValue 后按 radix 输出。 pub fn bigint_to_string(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(args[0]); - if !this_val.is_bigint() { - return NativeResult::Err(crate::error::create_type_error( - vm, - "BigInt.prototype.toString called on incompatible receiver", - )); - } - let v = unsafe { oxide_runtime_api::bigint_data(this_val) }; - NativeResult::Ok(vm.new_string(&oxide_runtime_api::bigint_to_string(v))) + let v = match this_bigint_value(vm, this_val) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + let radix = if args.len() > 1 { + let radix_arg = vm.reg(args[1]); + if radix_arg.is_undefined() { + 10u32 + } else { + // radix 走 ToIntegerOrInfinity:ToNumber 拒绝 Symbol/BigInt, + // 对象先 ToPrimitive(用户异常原样传播),NaN/±0 归 0。 + let raw = match coerce_number_or_throw(vm, radix_arg) { + Ok(n) => n, + Err(e) => return NativeResult::Err(e), + }; + let r = if raw.is_nan() || raw == 0.0 { + 0.0 + } else if raw.is_infinite() { + raw + } else { + raw.trunc() + }; + if !(2.0..=36.0).contains(&r) { + return NativeResult::Err(crate::error::create_range_error( + vm, + "toString() radix must be between 2 and 36", + )); + } + r as u32 + } + } else { + 10u32 + }; + let text = i128_to_radix_string(vm.bigint_value(v), radix); + NativeResult::Ok(vm.new_string(&text)) +} + +/// `BigInt.prototype.toLocaleString`:返回与 toString() 相同的十进制字符串。 +pub fn bigint_to_locale_string(vm: &mut H, args: &[u8]) -> NativeResult { + bigint_to_string(vm, args) } /// `BigInt.prototype.valueOf`:返回包装的 BigInt 原始值。 pub fn bigint_value_of(vm: &mut H, args: &[u8]) -> NativeResult { let this_val = vm.reg(args[0]); + match this_bigint_value(vm, this_val) { + Ok(v) => NativeResult::Ok(v), + Err(e) => NativeResult::Err(e), + } +} + +/// `BigInt.asIntN(bits, bigint)`:ToIndex(bits) + ToBigInt(bigint), +/// 结果按二进制补码截断为 bits 位有符号整数。 +pub fn bigint_as_int_n(vm: &mut H, args: &[u8]) -> NativeResult { + let bits = match to_index(vm, args) { + Ok(b) => b, + Err(e) => return NativeResult::Err(e), + }; + let arg = bigint_arg(vm, args); + let val = match to_bigint(vm, arg) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + let v = vm.bigint_value(val); + if bits == 0 { + return NativeResult::Ok(vm.new_bigint(0)); + } + if bits >= 127 { + // i128 表示范围内 `2^bits` 恒大于 |v|:取模后符号位折叠回原值。 + return NativeResult::Ok(val); + } + let modulus = 1i128 << bits; + let half = 1i128 << (bits - 1); + let mut m = v % modulus; + if m < 0 { + m += modulus; + } + if m >= half { + m -= modulus; + } + NativeResult::Ok(vm.new_bigint(m)) +} + +/// `BigInt.asUintN(bits, bigint)`:按 `2^bits` 取模的无符号截断。 +pub fn bigint_as_uint_n(vm: &mut H, args: &[u8]) -> NativeResult { + let bits = match to_index(vm, args) { + Ok(b) => b, + Err(e) => return NativeResult::Err(e), + }; + let arg = bigint_arg(vm, args); + let val = match to_bigint(vm, arg) { + Ok(v) => v, + Err(e) => return NativeResult::Err(e), + }; + let v = vm.bigint_value(val); + if bits == 0 { + return NativeResult::Ok(vm.new_bigint(0)); + } + if bits >= 128 { + // i128 上限:非负值取模不变;负值结果超出表示范围,待任意精度补齐。 + return NativeResult::Ok(val); + } + let modulus = 1i128 << bits; + let mut m = v % modulus; + if m < 0 { + m += modulus; + } + NativeResult::Ok(vm.new_bigint(m)) +} + +/// thisBigIntValue:BigInt 原样返回;包装对象解盒;其它类型 TypeError。 +fn this_bigint_value(vm: &mut H, this_val: JsValue) -> Result { if this_val.is_bigint() { - return NativeResult::Ok(this_val); + return Ok(this_val); } if this_val.is_object() { let ptr = this_val.as_js_object_ptr(); @@ -54,78 +187,198 @@ pub fn bigint_value_of(vm: &mut H, args: &[u8]) -> NativeResult { let obj = unsafe { &*ptr }; if obj.proto().is_object() { let proto_ptr = obj.proto().as_js_object_ptr(); - let bigint_proto = vm.session().builtin_world().bigint_proto.as_ptr() as *mut oxide_types::object::JsObject; + let bigint_proto = + vm.session().builtin_world().bigint_proto.as_ptr() as *mut oxide_types::object::JsObject; if !proto_ptr.is_null() && std::ptr::eq(proto_ptr, bigint_proto) { let v = obj.get_prop_at(0); if v.is_bigint() { - return NativeResult::Ok(v); + return Ok(v); } } } } } - NativeResult::Err(crate::error::create_type_error( + Err(crate::error::create_type_error( vm, - "BigInt.prototype.valueOf called on incompatible receiver", + "BigInt.prototype method called on incompatible receiver", )) } -/// 把原始值转为 BigInt 值(`BigInt(value)` 的参数转换)。 -fn to_bigint_value(vm: &mut H, val: JsValue) -> Result { +/// ToNumber 且拒绝 Symbol/BigInt(ToIntegerOrInfinity 的 ToNumber 语义)。 +fn coerce_number_or_throw(vm: &mut H, value: JsValue) -> Result { + let prim = match to_primitive(value, ToPrimitiveHint::Number, vm) { + Ok(p) => p, + Err(_) => { + if let Some(exc) = vm.take_uncaught_value() { + return Err(exc); + } + return Err(crate::error::create_type_error(vm, "Cannot convert value to a number")); + } + }; + if prim.is_symbol() || prim.is_bigint() { + return Err(crate::error::create_type_error( + vm, + "Cannot convert a Symbol or BigInt value to a number", + )); + } + Ok(oxide_runtime_api::to_number(prim)) +} + +/// ToIndex(§7.1.17):ToIntegerOrInfinity 后必须为有限且 `[0, 2^53-1]` 内的整数。 +fn to_index(vm: &mut H, args: &[u8]) -> Result { + // ToIndex 作用在第一个参数(bits)上;缺省为 undefined。 + let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let n = coerce_number_or_throw(vm, value)?; + if n.is_nan() { + return Ok(0); + } + // ToIntegerOrInfinity 先截断(-0.9 → -0,不越界);再检查有限且 [0, 2^53-1]。 + let int = if n == 0.0 { 0.0 } else if n.is_infinite() { n } else { n.trunc() }; + if !(0.0..=9007199254740991.0).contains(&int) { + return Err(crate::error::create_range_error(vm, "Invalid index")); + } + Ok(int as usize) +} + +/// 取 asIntN/asUintN 的第二个参数;缺省为 undefined。 +fn bigint_arg(vm: &mut H, args: &[u8]) -> JsValue { + if args.len() > 2 { + vm.reg(args[2]) + } else { + JsValue::undefined() + } +} + +/// ToBigInt 抽象操作(§7.1.14):BigInt 原样;String → StringToBigInt; +/// Boolean → 0/1;Number/Symbol/undefined/null → TypeError;对象先 ToPrimitive 再递归。 +fn to_bigint(vm: &mut H, val: JsValue) -> Result { if val.is_bigint() { return Ok(val); } - if val.is_undefined() { - return Ok(vm.new_bigint(0)); - } if val.is_bool() { return Ok(vm.new_bigint(if val.as_bool() { 1 } else { 0 })); } if val.is_int() { - return Ok(vm.new_bigint(val.as_int() as i128)); + // ToBigInt(Number) 抛 TypeError(与构造器不同:构造器先 ToPrimitive 再 + // NumberToBigInt;ToBigInt 直接拒绝 Number)。 + return Err(crate::error::create_type_error( + vm, + "Cannot convert a Number value to a BigInt", + )); } if val.is_double() { - let d = val.as_double(); - if d.is_nan() || d.is_infinite() { - return Err(crate::error::create_range_error(vm, "The number cannot be converted to a BigInt because it is not an integer")); - } - if d.trunc() != d { - return Err(crate::error::create_range_error(vm, "The number cannot be converted to a BigInt because it is not an integer")); - } - return Ok(vm.new_bigint(d.trunc() as i128)); + return Err(crate::error::create_type_error( + vm, + "Cannot convert a Number value to a BigInt", + )); } if val.is_string() { let s = unsafe { oxide_runtime_api::string_data(val) }.to_string(); - let s = s.trim(); - let sign = if let Some(rest) = s.strip_prefix('-') { - (true, rest) - } else if let Some(rest) = s.strip_prefix('+') { - (false, rest) - } else { - (false, s) + return match string_to_bigint(vm, &s) { + Ok(v) => Ok(v), + Err(e) => Err(e), }; - let (radix, digits) = if let Some(hex) = sign.1.strip_prefix("0x").or_else(|| sign.1.strip_prefix("0X")) { - (16u32, hex) - } else if let Some(oct) = sign.1.strip_prefix("0o").or_else(|| sign.1.strip_prefix("0O")) { - (8u32, oct) - } else if let Some(bin) = sign.1.strip_prefix("0b").or_else(|| sign.1.strip_prefix("0B")) { - (2u32, bin) - } else { - (10u32, sign.1) - }; - let magnitude = match i128::from_str_radix(digits, radix) { - Ok(m) => m, + } + if val.is_object() { + let prim = match to_primitive(val, ToPrimitiveHint::Default, vm) { + Ok(p) => p, Err(_) => { - return Err(crate::error::create_syntax_error( + if let Some(exc) = vm.take_uncaught_value() { + return Err(exc); + } + return Err(crate::error::create_type_error( vm, - "Cannot convert string to BigInt: invalid integer literal", - )) + "Cannot convert value to a BigInt", + )); } }; - return Ok(vm.new_bigint(if sign.0 { -magnitude } else { magnitude })); + return to_bigint(vm, prim); } + // undefined / null / symbol。 Err(crate::error::create_type_error( vm, "Cannot convert value to a BigInt", )) } + +/// StringToBigInt(§7.1.14 步骤):去首尾空白,可选 +/- 号与 0x/0o/0b 前缀; +/// 空串 → 0n;非法 → SyntaxError。 +fn string_to_bigint(vm: &mut H, s: &str) -> Result { + let trimmed = s.trim(); + if trimmed.is_empty() { + return Ok(vm.new_bigint(0)); + } + let (neg, rest) = if let Some(r) = trimmed.strip_prefix('-') { + (true, r) + } else if let Some(r) = trimmed.strip_prefix('+') { + (false, r) + } else { + (false, trimmed) + }; + // StringIntegerLiteral:符号只允许出现在纯十进制前;0x/0o/0b 前缀前带 + // +/-(如 "-0x1")属于非法语法(test262 constructor-from-string-syntax-errors)。 + let (radix, digits) = if let Some(hex) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) { + if neg { + return Err(crate::error::create_syntax_error( + vm, + "Cannot convert string to BigInt: invalid integer literal", + )); + } + (16u32, hex) + } else if let Some(oct) = rest.strip_prefix("0o").or_else(|| rest.strip_prefix("0O")) { + if neg { + return Err(crate::error::create_syntax_error( + vm, + "Cannot convert string to BigInt: invalid integer literal", + )); + } + (8u32, oct) + } else if let Some(bin) = rest.strip_prefix("0b").or_else(|| rest.strip_prefix("0B")) { + if neg { + return Err(crate::error::create_syntax_error( + vm, + "Cannot convert string to BigInt: invalid integer literal", + )); + } + (2u32, bin) + } else { + (10u32, rest) + }; + if digits.is_empty() { + return Err(crate::error::create_syntax_error( + vm, + "Cannot convert string to BigInt: invalid integer literal", + )); + } + match i128::from_str_radix(digits, radix) { + Ok(m) => Ok(vm.new_bigint(if neg { -m } else { m })), + Err(_) => Err(crate::error::create_syntax_error( + vm, + "Cannot convert string to BigInt: invalid integer literal", + )), + } +} + +/// 把 i128 按 radix 转成字符串(含负号前缀)。 +fn i128_to_radix_string(value: i128, radix: u32) -> String { + let neg = value < 0; + let mut mag = value.unsigned_abs(); + let chars = b"0123456789abcdefghijklmnopqrstuvwxyz"; + let mut result = String::new(); + if mag == 0 { + result.push('0'); + } else { + let mut digits = Vec::new(); + while mag > 0 { + digits.push(chars[(mag % radix as u128) as usize] as char); + mag /= radix as u128; + } + for ch in digits.iter().rev() { + result.push(*ch); + } + } + if neg { + result.insert(0, '-'); + } + result +} diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index 950569ec..14b9dc37 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -516,6 +516,35 @@ fn wire_builtin_world_links(world: &BuiltinWorld) { wire_ctor_proto(&world.plain_date_time_constructor, &world.plain_date_time_proto); wire_ctor_proto(&world.bigint_constructor, &world.bigint_proto); + // 所有内置构造器的 [[Prototype]] 指向 %FunctionPrototype%(ECMA-262 §17: + // 标准内置函数对象均继承 Function.prototype;此前仅 TypedArray 构造器设置过)。 + let fn_proto_val = world.fn_proto_val(); + for ctor in [ + &world.object_constructor, + &world.array_constructor, + &world.function_constructor, + &world.string_constructor, + &world.number_constructor, + &world.boolean_constructor, + &world.error_constructor, + &world.symbol_constructor, + &world.date_constructor, + &world.set_constructor, + &world.map_constructor, + &world.regexp_constructor, + &world.array_buffer_constructor, + &world.data_view_constructor, + &world.bigint_constructor, + &world.instant_constructor, + &world.plain_date_constructor, + &world.plain_time_constructor, + &world.duration_constructor, + &world.zoned_date_time_constructor, + &world.plain_date_time_constructor, + ] { + set_proto_if_changed(ctor, fn_proto_val); + } + let obj_proto_val = JsValue::from_js_object(world.object_proto.as_ptr() as *mut JsObject); let non_object_protos: [&P; 21] = [ &world.array_proto, diff --git a/crates/oxide_vm/src/bindings/bind_bigint.rs b/crates/oxide_vm/src/bindings/bind_bigint.rs index a384b63d..f8408949 100644 --- a/crates/oxide_vm/src/bindings/bind_bigint.rs +++ b/crates/oxide_vm/src/bindings/bind_bigint.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use crate::bindings::{apply_binding_table, bind_global_value, configure_native_constructor}; use oxide_kernel::kernel::{KernelCore, KernelSession}; -use oxide_types::object::JsObject; +use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::value::JsValue; /// 把 BigInt 构造器与原型方法绑定到 global。 @@ -18,6 +18,24 @@ pub fn bind_bigint(core: &Arc, session: &KernelSession, global: &mut let proto = unsafe { &mut *proto_ptr }; configure_native_constructor(ctor, oxide_builtins::bigint::bigint_constructor:: as *const (), 1); + // BigInt.length = 1,属性 { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }。 + let length_si = core.perm_interner().intern("length").0; + let length_shape = core.shape_forge().make_shape(ctor.shape_id(), length_si); + ctor.set_shape_id(length_shape); + ctor.ensure_hash_props().push(JsValue::int(1)); + let length_pos = ctor.hash_props_vec().map_or(0, |v| v.len() as u32).saturating_sub(1); + ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true)); + + // 静态方法:asIntN / asUintN(length 均为 2)。 + apply_binding_table( + world, + ctor, + core, + &[ + ("asIntN", oxide_builtins::bigint::bigint_as_int_n:: as *const (), 2), + ("asUintN", oxide_builtins::bigint::bigint_as_uint_n:: as *const (), 2), + ], + ); apply_binding_table( world, @@ -25,9 +43,15 @@ pub fn bind_bigint(core: &Arc, session: &KernelSession, global: &mut core, &[ ("toString", oxide_builtins::bigint::bigint_to_string:: as *const (), 0), + ("toLocaleString", oxide_builtins::bigint::bigint_to_locale_string:: as *const (), 0), ("valueOf", oxide_builtins::bigint::bigint_value_of:: as *const (), 0), ], ); bind_global_value(core, global, "BigInt", JsValue::from_js_object(ctor_ptr)); + // 全局 BigInt 属性描述符:{ writable: true, enumerable: false, configurable: true }。 + let bigint_si = core.perm_interner().intern("BigInt").0; + if let Some(pos) = core.shape_forge().lookup_position(global.shape_id(), bigint_si) { + global.set_data_meta(pos, PropAttributes::new(true, false, true)); + } }