mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: BigInt 构造器精确大整数与 new/无参/分隔符语义
This commit is contained in:
@@ -2,23 +2,17 @@ use num_bigint::BigInt;
|
||||
use oxide_runtime_api::{to_primitive, NativeResult, ToPrimitiveHint, VmHost};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
/// JS `BigInt(value)` 构造逻辑:
|
||||
/// - `new BigInt()` 抛 TypeError(BigInt 不可 new)
|
||||
/// - 无参 → `0n`
|
||||
/// - 其余参数先 ToPrimitive(number),Number 走 NumberToBigInt,其余走 ToBigInt。
|
||||
/// JS `BigInt(value)` 构造逻辑(§21.2.1.1):
|
||||
/// - new 形态无条件抛 TypeError(BigInt 不可 new);
|
||||
/// - 无参走 ToBigInt(undefined) → TypeError;
|
||||
/// - Number 参数走 NumberToBigInt(非整数抛 RangeError,幅值无 2^53 上界),
|
||||
/// 其余参数走 ToBigInt。
|
||||
pub fn bigint_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if args.len() > 1 {
|
||||
let this_val = vm.reg(args[0]);
|
||||
if this_val.is_object() {
|
||||
// new 语义:BigInt 不可 new,抛 TypeError。
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "BigInt is not a constructor"));
|
||||
}
|
||||
if vm.constructing_native() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "BigInt is not a constructor"));
|
||||
}
|
||||
// 无参 BigInt() → 0n(注意 BigInt(undefined) 必须抛 TypeError)。
|
||||
if args.len() <= 1 {
|
||||
return NativeResult::Ok(vm.new_bigint(BigInt::from(0)));
|
||||
}
|
||||
let val = vm.reg(args[1]);
|
||||
// 无参 BigInt() 等价于 ToBigInt(undefined) → TypeError,与显式 undefined 同路。
|
||||
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
// ToPrimitive(value, number):对象先出盒,用户 @@toPrimitive/valueOf/toString
|
||||
// 抛出的异常原样传播,且装箱转换的异常只触发一次。
|
||||
let prim = match to_primitive(val, ToPrimitiveHint::Number, vm) {
|
||||
@@ -42,7 +36,7 @@ pub fn bigint_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
"The number cannot be converted to a BigInt because it is not an integer",
|
||||
));
|
||||
}
|
||||
return NativeResult::Ok(vm.new_bigint(BigInt::from(d.trunc() as i128)));
|
||||
return NativeResult::Ok(vm.new_bigint(integer_double_to_bigint(d)));
|
||||
}
|
||||
// 步骤 4:其它原始类型走 ToBigInt。
|
||||
match to_bigint(vm, prim) {
|
||||
@@ -51,6 +45,29 @@ pub fn bigint_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// NumberToBigInt 步骤 3:整数 double 的精确值 ℤ(x)。
|
||||
/// |x| < 2^53 由尾数右移指数位直转;|x| ≥ 2^53 位分解(尾数左移 exp-1075 位),
|
||||
/// 全程精确无饱和。-0 返回 0(BigInt 无负零)。
|
||||
fn integer_double_to_bigint(d: f64) -> BigInt {
|
||||
let bits = d.to_bits();
|
||||
let negative = bits >> 63 != 0;
|
||||
let exp_field = (bits >> 52) & 0x7FF;
|
||||
let significand = bits & 0xF_FFFF_FFFF_FFFF;
|
||||
if exp_field == 0 {
|
||||
return BigInt::from(0);
|
||||
}
|
||||
let mag = if exp_field < 1075 {
|
||||
BigInt::from((0x10_0000_0000_0000u64 + significand) >> (1075 - exp_field))
|
||||
} else {
|
||||
BigInt::from(0x10_0000_0000_0000u64 + significand) << (exp_field - 1075)
|
||||
};
|
||||
if negative {
|
||||
-mag
|
||||
} else {
|
||||
mag
|
||||
}
|
||||
}
|
||||
|
||||
/// `BigInt.prototype.toString(radix)`:thisBigIntValue 后按 radix 输出。
|
||||
pub fn bigint_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(args[0]);
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_types::value::JsValue;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
fn eval(source: &str) -> Result<JsValue, String> {
|
||||
let allocator = oxide_parser::Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
|
||||
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&Arc::new(module))
|
||||
}
|
||||
|
||||
/// 求值一个应得布尔结果的表达式并断言其类型。
|
||||
fn eval_bool(source: &str) -> bool {
|
||||
let result = eval(source).expect("eval should succeed");
|
||||
assert!(result.is_bool(), "expected boolean result, got {result:?}");
|
||||
result.as_bool()
|
||||
}
|
||||
|
||||
// --- Number 参数:NumberToBigInt 精确值 ---
|
||||
|
||||
/// 1e300 的 double 精确值(f64 位分解的整数部分),非十进制 10^300。
|
||||
const ONE_E300: &str = "1000000000000000052504760255204420248704468581108159154915854115511802457988908195786371375080447864043704443832883878176942523235360430575644792184786706982848387200926575803737830233794788090059368953234970799945081119038967640880074652742780142494579258788820056842838115669472196386865459400540160";
|
||||
|
||||
#[test]
|
||||
fn double_1e300_exact_value() {
|
||||
assert!(eval_bool(&format!("BigInt(1e300) === {ONE_E300}n")));
|
||||
assert!(eval_bool(&format!("BigInt(-1e300) === -{ONE_E300}n")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_two_pow_53_exact_value() {
|
||||
assert!(eval_bool("BigInt(9007199254740992) === 9007199254740992n"));
|
||||
assert!(eval_bool("BigInt(9007199254740991.0) === 9007199254740991n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_between_two_pow_67_exact_value() {
|
||||
assert!(eval_bool("BigInt(2.5e20) === 250000000000000000000n"));
|
||||
assert!(eval_bool("BigInt(3500000000000000.0) === 3500000000000000n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_negative_zero_is_zero_bigint() {
|
||||
assert!(eval_bool("BigInt(-0.0) === 0n"));
|
||||
}
|
||||
|
||||
// --- 构造形态:new 无条件 TypeError ---
|
||||
|
||||
#[test]
|
||||
fn new_bigint_no_arg_throws_type_error() {
|
||||
assert!(eval_bool("try { new BigInt(); 0 } catch (e) { e instanceof TypeError }"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_bigint_with_value_throws_type_error() {
|
||||
assert!(eval_bool("try { new BigInt(1); 0 } catch (e) { e instanceof TypeError }"));
|
||||
}
|
||||
|
||||
// --- 无参:ToBigInt(undefined) → TypeError ---
|
||||
|
||||
#[test]
|
||||
fn no_arg_throws_type_error() {
|
||||
assert!(eval_bool("try { BigInt(); 0 } catch (e) { e instanceof TypeError }"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_undefined_throws_type_error() {
|
||||
assert!(eval_bool("try { BigInt(undefined); 0 } catch (e) { e instanceof TypeError }"));
|
||||
}
|
||||
|
||||
// --- 字符串参数:数字分隔符非法 ---
|
||||
|
||||
#[test]
|
||||
fn decimal_digit_separator_syntax_error() {
|
||||
assert!(eval_bool("try { BigInt(\"1_0\"); 0 } catch (e) { e instanceof SyntaxError }"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_digit_separator_syntax_error() {
|
||||
assert!(eval_bool("try { BigInt(\"0x1_F\"); 0 } catch (e) { e instanceof SyntaxError }"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_string_forms_unaffected() {
|
||||
assert!(eval_bool("BigInt(\"10\") === 10n"));
|
||||
assert!(eval_bool("BigInt(\"0x1F\") === 31n"));
|
||||
assert!(eval_bool("BigInt(\" 0o17 \") === 15n"));
|
||||
}
|
||||
@@ -960,7 +960,16 @@ fn string_to_bigint_full<H: VmHost>(host: &mut H, s: &str) -> Result<JsValue, St
|
||||
(10u32, rest)
|
||||
};
|
||||
if digits.is_empty() {
|
||||
return Err(host.error_message_text("SyntaxError", "Cannot convert string to BigInt: invalid integer literal"));
|
||||
return Err(
|
||||
host.error_message_text("SyntaxError", "Cannot convert string to a BigInt: invalid integer literal")
|
||||
);
|
||||
}
|
||||
// StringIntegerLiteral 四种基数语法均不含数字分隔符;parse_bytes 会接受
|
||||
// 下划线,故先显式拒绝。
|
||||
if digits.contains('_') {
|
||||
return Err(
|
||||
host.error_message_text("SyntaxError", "Cannot convert string to a BigInt: invalid integer literal")
|
||||
);
|
||||
}
|
||||
let m = num_bigint::BigInt::parse_bytes(digits.as_bytes(), radix).ok_or_else(|| {
|
||||
host.error_message_text("SyntaxError", "Cannot convert string to BigInt: invalid integer literal")
|
||||
|
||||
Reference in New Issue
Block a user