mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 对齐 Number 原型四方法规范序位并补齐绑定元数据面
This commit is contained in:
@@ -2,6 +2,19 @@ use oxide_types::value::JsValue;
|
||||
|
||||
use oxide_runtime_api::{NativeResult, VmHost};
|
||||
|
||||
/// 整值且 i32 域内的有限数转 int 表示;-0 排除
|
||||
/// (int 表示丢符号,规范要求 `Number(-0)` 得 -0)。
|
||||
fn i32_of_integral(n: f64) -> Option<i32> {
|
||||
if n == 0.0 && n.is_sign_negative() {
|
||||
return None;
|
||||
}
|
||||
if n.fract() == 0.0 && n.is_finite() && n >= i32::MIN as f64 && n <= i32::MAX as f64 {
|
||||
Some(n as i32)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// JS `Number()` 构造逻辑:把参数按 ToNumber 语义转换。
|
||||
/// 普通调用返回原始 number(整数走 int 表示);new 语义返回 `[[NumberData]]` 包装对象。
|
||||
pub fn number_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
@@ -55,38 +68,42 @@ pub fn number_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let this_val = vm.reg(args[0]);
|
||||
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
|
||||
obj.type_tag = oxide_types::object::JsObject::OBJ_TYPE_NUMBER_OBJ;
|
||||
let boxed = if n.fract() == 0.0 && n.is_finite() && n >= i32::MIN as f64 && n <= i32::MAX as f64 {
|
||||
JsValue::int(n as i32)
|
||||
} else {
|
||||
JsValue::float(n)
|
||||
};
|
||||
let boxed = i32_of_integral(n).map(JsValue::int).unwrap_or_else(|| JsValue::float(n));
|
||||
obj.set_boxed_value(boxed);
|
||||
return NativeResult::Ok(this_val);
|
||||
}
|
||||
|
||||
if n.fract() == 0.0 && n.is_finite() && n >= i32::MIN as f64 && n <= i32::MAX as f64 {
|
||||
NativeResult::Ok(JsValue::int(n as i32))
|
||||
} else {
|
||||
NativeResult::Ok(JsValue::float(n))
|
||||
}
|
||||
NativeResult::Ok(i32_of_integral(n).map(JsValue::int).unwrap_or_else(|| JsValue::float(n)))
|
||||
}
|
||||
|
||||
/// `Number.isNaN`:参数严格等于 NaN 才返回 true(不做隐式类型转换)。
|
||||
///
|
||||
/// 步 1 为严格判型:非 Number 原始类型(含 Number 对象)直接 false,
|
||||
/// 不走 ToNumber 强转(否则 "NaN"/对象装箱会误报 true)。
|
||||
pub fn number_is_nan<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let n = oxide_runtime_api::to_number(vm.reg(args[1]));
|
||||
NativeResult::Ok(JsValue::bool(n.is_nan()))
|
||||
let val = vm.reg(args[1]);
|
||||
if !val.is_int() && !val.is_double() {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
NativeResult::Ok(JsValue::bool(oxide_runtime_api::to_number(val).is_nan()))
|
||||
}
|
||||
|
||||
/// `Number.isFinite`:参数为有限数才返回 true(不做隐式类型转换)。
|
||||
///
|
||||
/// 步 1 为严格判型:非 Number 原始类型(含 Number 对象)直接 false,
|
||||
/// 不走 ToNumber 强转(否则 "1" 等会误报 true)。
|
||||
pub fn number_is_finite<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if args.len() < 2 {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let n = oxide_runtime_api::to_number(vm.reg(args[1]));
|
||||
NativeResult::Ok(JsValue::bool(n.is_finite()))
|
||||
let val = vm.reg(args[1]);
|
||||
if !val.is_int() && !val.is_double() {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
NativeResult::Ok(JsValue::bool(oxide_runtime_api::to_number(val).is_finite()))
|
||||
}
|
||||
|
||||
/// 判定 parseInt/parseFloat 修剪时要剥掉的空白字符(规范 WhiteSpace 与
|
||||
@@ -303,13 +320,75 @@ pub fn number_parse_float<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// thisNumberValue(spec 21.7.3 共用步 1):Number 原始值直返;带
|
||||
/// [[NumberData]] 的 Number 对象取被包值;其余一律 TypeError。
|
||||
/// toFixed/toExponential/toPrecision/toString 四方法均以本助手起步,
|
||||
/// 取代 ToObject 语义的 coerce(后者对 {} 静默装箱,不抛错)。
|
||||
fn this_number_value<H: VmHost>(vm: &mut H, this_val: JsValue) -> Result<f64, JsValue> {
|
||||
if this_val.is_int() || this_val.is_double() {
|
||||
return Ok(oxide_runtime_api::to_number(this_val));
|
||||
}
|
||||
if this_val.is_object() {
|
||||
let ptr = this_val.as_js_object_ptr();
|
||||
if !ptr.is_null() {
|
||||
// Number.prototype 本身即 [[NumberData]] = +0 的 Number 对象;
|
||||
// 与 number_value_of 的 proto 特判同形。
|
||||
let number_proto = vm.session().builtin_world().number_proto.as_ptr() as *mut oxide_types::object::JsObject;
|
||||
if std::ptr::eq(ptr, number_proto) {
|
||||
return Ok(0.0);
|
||||
}
|
||||
let obj = unsafe { &*ptr };
|
||||
if obj.is_number_obj() {
|
||||
return Ok(oxide_runtime_api::to_number(obj.boxed_value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(crate::error::create_type_error(vm, "Cannot convert this value to a number"))
|
||||
}
|
||||
|
||||
/// f/p/radix 参数转换:完整 ToNumber(对象执行 ToPrimitive 且 valueOf/toString
|
||||
/// 抛出的异常原样传播)后走 ToIntegerOrInfinity。纯核 `to_integer_or_infinity`
|
||||
/// 不传播异常,不得用于参数位。
|
||||
fn coerce_to_integer_or_infinity<H: VmHost>(vm: &mut H, arg: JsValue) -> Result<f64, JsValue> {
|
||||
let raw = match vm.coerce_number_bounded(arg) {
|
||||
Ok(n) => n,
|
||||
Err(_) => {
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return Err(exc);
|
||||
}
|
||||
return Err(crate::error::create_type_error(vm, "Cannot convert argument to a number"));
|
||||
}
|
||||
};
|
||||
if raw.is_nan() || raw == 0.0 {
|
||||
Ok(0.0)
|
||||
} else if raw.is_infinite() {
|
||||
Ok(raw)
|
||||
} else {
|
||||
Ok(raw.trunc())
|
||||
}
|
||||
}
|
||||
|
||||
/// `Number.prototype.toLocaleString()`:locale 参数忽略,恒按十进制输出。
|
||||
/// 与 toString 的 radix-10 路径同形;不读第二个实参(length 为 0)。
|
||||
pub fn number_to_locale_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let n = match this_number_value(vm, vm.reg(args[0])) {
|
||||
Ok(v) => v,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
NativeResult::Ok(vm.new_string_owned(oxide_runtime_api::js_number_to_string(n)))
|
||||
}
|
||||
|
||||
/// `Number.prototype.toString(radix)`:按指定进制(2..36)转字符串。
|
||||
///
|
||||
/// 十进制走共享的 ECMA-262 Number::toString 格式化;非十进制对截断后的整数
|
||||
/// 部分做进制转换(小数部分按近似处理)。radix 经 ToInteger 后越界抛 RangeError,
|
||||
/// 步 1 先 thisNumberValue(非 Number 原始值/对象抛 TypeError);十进制走共享的
|
||||
/// ECMA-262 Number::toString 格式化;非十进制对截断后的整数部分做进制转换
|
||||
/// (小数部分按近似处理)。radix 经 ToInteger 后越界抛 RangeError,
|
||||
/// NaN/Infinity 输出专名。
|
||||
pub fn number_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let n = vm.coerce_number_bounded(vm.reg(args[0])).unwrap_or(f64::NAN);
|
||||
let n = match this_number_value(vm, vm.reg(args[0])) {
|
||||
Ok(v) => v,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
let radix = if args.len() > 1 {
|
||||
let radix_arg = vm.reg(args[1]);
|
||||
if radix_arg.is_undefined() {
|
||||
@@ -385,28 +464,43 @@ pub fn number_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
|
||||
/// `Number.prototype.toFixed(digits)`:固定小数位数(0..100)输出字符串,
|
||||
/// 超出范围抛 RangeError;NaN/Infinity 输出专名。
|
||||
///
|
||||
/// 步序:先 thisNumberValue,再转换 fractionDigits 并做范围校验(范围检查
|
||||
/// 先于 NaN 短路,NaN.toFixed(Infinity) 须抛 RangeError);随后 x ≥ 10^21
|
||||
/// 退化为 String(x) 科学式(±Infinity 同形,经此分支输出专名)。
|
||||
pub fn number_to_fixed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let n = vm.coerce_number_bounded(vm.reg(args[0])).unwrap_or(f64::NAN);
|
||||
let n = match this_number_value(vm, vm.reg(args[0])) {
|
||||
Ok(v) => v,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
let raw = if args.len() > 1 {
|
||||
match coerce_to_integer_or_infinity(vm, vm.reg(args[1])) {
|
||||
Ok(v) => v,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
}
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
if !(0.0..=100.0).contains(&raw) {
|
||||
return NativeResult::Err(crate::error::create_range_error(
|
||||
vm,
|
||||
"toFixed() fractionDigits must be between 0 and 100",
|
||||
));
|
||||
}
|
||||
if n.is_nan() {
|
||||
return NativeResult::Ok(vm.new_string("NaN"));
|
||||
}
|
||||
if n.is_infinite() {
|
||||
return NativeResult::Ok(vm.new_string(if n.is_sign_positive() { "Infinity" } else { "-Infinity" }));
|
||||
// x ≥ 10^21 用科学式 String 形态(±Infinity 同为该形态)。
|
||||
if n.abs() >= 1e21 {
|
||||
let s = if n < 0.0 {
|
||||
format!("-{}", oxide_runtime_api::js_number_to_string(-n))
|
||||
} else {
|
||||
oxide_runtime_api::js_number_to_string(n)
|
||||
};
|
||||
return NativeResult::Ok(vm.new_string_owned(s));
|
||||
}
|
||||
let fraction_digits = if args.len() > 1 {
|
||||
let raw = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1]));
|
||||
if !(0.0..=100.0).contains(&raw) {
|
||||
return NativeResult::Err(crate::error::create_range_error(
|
||||
vm,
|
||||
"toFixed() fractionDigits must be between 0 and 100",
|
||||
));
|
||||
}
|
||||
raw as usize
|
||||
} else {
|
||||
0usize
|
||||
};
|
||||
let n_abs = if n == 0.0 && n.is_sign_negative() { 0.0 } else { n };
|
||||
let formatted = format!("{:.precision$}", n_abs, precision = fraction_digits);
|
||||
let formatted = format!("{:.precision$}", n_abs, precision = raw as usize);
|
||||
NativeResult::Ok(vm.new_string(&formatted))
|
||||
}
|
||||
|
||||
@@ -433,15 +527,28 @@ pub fn number_is_safe_integer<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
|
||||
|
||||
/// `Number.prototype.toPrecision(precision)`:按有效数字位数(1..100)输出,
|
||||
/// 科学计数法与定点表示按指数自动切换,超范围抛 RangeError。
|
||||
///
|
||||
/// 步序:先 thisNumberValue;precision 缺省直接返 ToString(x)(含 NaN/±∞
|
||||
/// 全形);再转 precision,NaN/±∞ 专名先于范围校验((±∞).toPrecision(1000)
|
||||
/// 返 "Infinity" 而非 RangeError),最后舍入渲染(指数恒带符号)。
|
||||
pub fn number_to_precision<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if args.len() <= 1 {
|
||||
return NativeResult::Err(crate::error::create_range_error(
|
||||
vm,
|
||||
"toPrecision() requires a precision argument between 1 and 100",
|
||||
));
|
||||
let n = match this_number_value(vm, vm.reg(args[0])) {
|
||||
Ok(v) => v,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
if args.len() <= 1 || vm.reg(args[1]).is_undefined() {
|
||||
return NativeResult::Ok(vm.new_string_owned(oxide_runtime_api::js_number_to_string(n)));
|
||||
}
|
||||
let raw = match coerce_to_integer_or_infinity(vm, vm.reg(args[1])) {
|
||||
Ok(v) => v,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
if n.is_nan() {
|
||||
return NativeResult::Ok(vm.new_string("NaN"));
|
||||
}
|
||||
if n.is_infinite() {
|
||||
return NativeResult::Ok(vm.new_string(if n.is_sign_positive() { "Infinity" } else { "-Infinity" }));
|
||||
}
|
||||
let n = vm.coerce_number_bounded(vm.reg(args[0])).unwrap_or(f64::NAN);
|
||||
let raw = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1]));
|
||||
if !(1.0..=100.0).contains(&raw) {
|
||||
return NativeResult::Err(crate::error::create_range_error(
|
||||
vm,
|
||||
@@ -449,12 +556,6 @@ pub fn number_to_precision<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
));
|
||||
}
|
||||
let precision = raw as usize;
|
||||
if n.is_nan() {
|
||||
return NativeResult::Ok(vm.new_string("NaN"));
|
||||
}
|
||||
if n.is_infinite() {
|
||||
return NativeResult::Ok(vm.new_string(if n.is_sign_positive() { "Infinity" } else { "-Infinity" }));
|
||||
}
|
||||
let n_abs = if n == 0.0 && n.is_sign_negative() { 0.0 } else { n.abs() };
|
||||
let is_neg = n.is_sign_negative() && !(n == 0.0);
|
||||
let e = if n_abs == 0.0 { 0i32 } else { n_abs.log10().floor() as i32 };
|
||||
@@ -462,7 +563,14 @@ pub fn number_to_precision<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let dec = ((precision as i32 - e - 1).max(0)) as usize;
|
||||
format!("{:.dec$}", n_abs, dec = dec)
|
||||
} else {
|
||||
format!("{:.dec$e}", n_abs, dec = precision - 1)
|
||||
// Rust 的 {:e} 指数不带符号,规范要求恒带 +/-。
|
||||
let mut s = format!("{:.dec$e}", n_abs, dec = precision - 1);
|
||||
if let Some(e_pos) = s.find('e') {
|
||||
if !s[e_pos + 1..].starts_with('-') {
|
||||
s.insert(e_pos + 1, '+');
|
||||
}
|
||||
}
|
||||
s
|
||||
};
|
||||
if is_neg {
|
||||
NativeResult::Ok(vm.new_string(&format!("-{}", formatted)))
|
||||
@@ -472,18 +580,39 @@ pub fn number_to_precision<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
|
||||
/// `Number.prototype.toExponential(digits)`:按科学计数法输出;
|
||||
/// digits 缺省时自动决定小数位数,超范围抛 RangeError。
|
||||
/// digits 缺省/undefined 取最短精确有效数字数,超范围抛 RangeError。
|
||||
///
|
||||
/// 步序:先 thisNumberValue,再转换 fractionDigits(对象参数执行 ToPrimitive
|
||||
/// 且异常原样传播);NaN/±∞ 专名先于范围检查;指数恒带符号。
|
||||
pub fn number_to_exponential<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let n = vm.coerce_number_bounded(vm.reg(args[0])).unwrap_or(f64::NAN);
|
||||
let n = match this_number_value(vm, vm.reg(args[0])) {
|
||||
Ok(v) => v,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
};
|
||||
let raw = if args.len() > 1 && !vm.reg(args[1]).is_undefined() {
|
||||
match coerce_to_integer_or_infinity(vm, vm.reg(args[1])) {
|
||||
Ok(v) => v,
|
||||
Err(exc) => return NativeResult::Err(exc),
|
||||
}
|
||||
} else {
|
||||
f64::NAN
|
||||
};
|
||||
if n.is_nan() {
|
||||
return NativeResult::Ok(vm.new_string("NaN"));
|
||||
}
|
||||
if n.is_infinite() {
|
||||
return NativeResult::Ok(vm.new_string(if n.is_sign_positive() { "Infinity" } else { "-Infinity" }));
|
||||
}
|
||||
let has_digits = !raw.is_nan();
|
||||
if has_digits && !(0.0..=100.0).contains(&raw) {
|
||||
return NativeResult::Err(crate::error::create_range_error(
|
||||
vm,
|
||||
"toExponential() fractionDigits must be between 0 and 100",
|
||||
));
|
||||
}
|
||||
let n_abs = if n == 0.0 && n.is_sign_negative() { 0.0 } else { n.abs() };
|
||||
let sign_prefix = if n.is_sign_negative() && !n.is_nan() && !(n == 0.0) { "-" } else { "" };
|
||||
if args.len() <= 1 {
|
||||
let sign_prefix = if n < 0.0 { "-" } else { "" };
|
||||
if !has_digits {
|
||||
let formatted = format!("{:e}", n_abs);
|
||||
let mut s = formatted;
|
||||
if let Some(e_pos) = s.find('e') {
|
||||
@@ -495,20 +624,23 @@ pub fn number_to_exponential<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
if mantissa.contains('.') {
|
||||
s = format!("{}{}", mantissa, &s[e_pos..]);
|
||||
}
|
||||
// 指数恒带符号(Rust 正指数不输出 +)。
|
||||
if !s[e_pos + 1..].starts_with('-') {
|
||||
s.insert(e_pos + 1, '+');
|
||||
}
|
||||
}
|
||||
let result = format!("{}{}", sign_prefix, s);
|
||||
return NativeResult::Ok(vm.new_string_owned(result));
|
||||
}
|
||||
let raw = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1]));
|
||||
if !(0.0..=100.0).contains(&raw) {
|
||||
return NativeResult::Err(crate::error::create_range_error(
|
||||
vm,
|
||||
"toExponential() fractionDigits must be between 0 and 100",
|
||||
));
|
||||
let formatted = format!("{:.digits$e}", n_abs, digits = raw as usize);
|
||||
// 指数恒带符号(Rust 正指数不输出 +)。
|
||||
let mut s = formatted;
|
||||
if let Some(e_pos) = s.find('e') {
|
||||
if !s[e_pos + 1..].starts_with('-') {
|
||||
s.insert(e_pos + 1, '+');
|
||||
}
|
||||
}
|
||||
let fraction_digits = raw as usize;
|
||||
let formatted = format!("{:.digits$e}", n_abs, digits = fraction_digits);
|
||||
NativeResult::Ok(vm.new_string(&format!("{}{}", sign_prefix, formatted)))
|
||||
NativeResult::Ok(vm.new_string(&format!("{}{}", sign_prefix, s)))
|
||||
}
|
||||
|
||||
/// `Number.prototype.valueOf`:返回包装对象的原始 number;
|
||||
|
||||
@@ -75,6 +75,18 @@ pub(crate) fn make_named_pair(
|
||||
make_pair(string_forge, shape_forge, name, labels.prototype, labels.constructor, labels.name)
|
||||
}
|
||||
|
||||
/// 把 Number.prototype 标成 Number 对象本体:规范 21.7.3 规定其是
|
||||
/// [[NumberData]] = +0 的 Number object。type_tag 是 Object.prototype.toString
|
||||
/// 品牌表的判据(置位后报 "[object Number]"),boxed 值供 thisNumberValue/
|
||||
/// valueOf 的通用分支读取。
|
||||
pub(crate) fn tag_number_proto(proto: &P<JsObject>) {
|
||||
let ptr = proto.as_ptr() as *mut JsObject;
|
||||
// SAFETY: proto 是 make_named_pair 刚建的本进程对象,P 引用与裸指针同址。
|
||||
let obj = unsafe { &mut *ptr };
|
||||
obj.type_tag = JsObject::OBJ_TYPE_NUMBER_OBJ;
|
||||
obj.set_boxed_value(JsValue::int(0));
|
||||
}
|
||||
|
||||
pub(crate) fn make_error_subtypes(error_proto: &P<JsObject>) -> ErrorSubtypeProtos {
|
||||
let error_proto_val = JsValue::from_js_object(error_proto.as_ptr() as *mut JsObject);
|
||||
ErrorSubtypeProtos {
|
||||
@@ -436,6 +448,7 @@ impl BuiltinWorld {
|
||||
let (function_proto, function_constructor) = make_named_pair(string_forge, shape_forge, labels, "Function");
|
||||
let (string_proto, string_constructor) = make_named_pair(string_forge, shape_forge, labels, "String");
|
||||
let (number_proto, number_constructor) = make_named_pair(string_forge, shape_forge, labels, "Number");
|
||||
tag_number_proto(&number_proto);
|
||||
let (boolean_proto, boolean_constructor) = make_named_pair(string_forge, shape_forge, labels, "Boolean");
|
||||
let (error_proto, error_constructor) = make_named_pair(string_forge, shape_forge, labels, "Error");
|
||||
let (symbol_proto, symbol_constructor) = make_named_pair(string_forge, shape_forge, labels, "Symbol");
|
||||
|
||||
@@ -7,8 +7,8 @@ use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use super::construct::{
|
||||
builtin_labels, make_error_subtypes, make_named_pair, make_typed_array_family, wire_builtin_world_links,
|
||||
ErrorSubtypeProtos, TypedArrayFamily,
|
||||
builtin_labels, make_error_subtypes, make_named_pair, make_typed_array_family, tag_number_proto,
|
||||
wire_builtin_world_links, ErrorSubtypeProtos, TypedArrayFamily,
|
||||
};
|
||||
use super::BuiltinWorld;
|
||||
use crate::kernel::BuiltinDirtySet;
|
||||
@@ -183,7 +183,11 @@ impl BuiltinWorld {
|
||||
(current.string_proto.clone(), current.string_constructor.clone())
|
||||
};
|
||||
let (number_proto, number_constructor) = if dirty.number {
|
||||
make_named_pair(string_forge, shape_forge, labels, "Number")
|
||||
// 脏重建与全量构造同形:原型本体须带 Number 对象 tag 与 +0 包值,
|
||||
// 漏此分支 full_reset 后 tag 翻回 "Object"。
|
||||
let (proto, ctor) = make_named_pair(string_forge, shape_forge, labels, "Number");
|
||||
tag_number_proto(&proto);
|
||||
(proto, ctor)
|
||||
} else {
|
||||
(current.number_proto.clone(), current.number_constructor.clone())
|
||||
};
|
||||
|
||||
@@ -38,16 +38,23 @@ pub fn bind_number(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
proto,
|
||||
core,
|
||||
&[
|
||||
("toString", oxide_builtins::number::number_to_string::<crate::vm::Vm> as *const (), 0),
|
||||
("toFixed", oxide_builtins::number::number_to_fixed::<crate::vm::Vm> as *const (), 0),
|
||||
("toString", oxide_builtins::number::number_to_string::<crate::vm::Vm> as *const (), 1),
|
||||
("toFixed", oxide_builtins::number::number_to_fixed::<crate::vm::Vm> as *const (), 1),
|
||||
(
|
||||
"toExponential",
|
||||
oxide_builtins::number::number_to_exponential::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
1,
|
||||
),
|
||||
(
|
||||
"toPrecision",
|
||||
oxide_builtins::number::number_to_precision::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
// toLocaleString 为 Number.prototype 的 own 属性(规范 21.7.3.25):
|
||||
// 保留参数不计 length(length 0),locale 参数忽略、恒十进制。
|
||||
(
|
||||
"toLocaleString",
|
||||
oxide_builtins::number::number_to_locale_string::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
("valueOf", oxide_builtins::number::number_value_of::<crate::vm::Vm> as *const (), 0),
|
||||
@@ -85,4 +92,17 @@ pub fn bind_number(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
("isFinite", oxide_builtins::global::global_is_finite::<crate::vm::Vm> as *const (), 1),
|
||||
],
|
||||
);
|
||||
|
||||
// 规范(21.7.3 / 20.1.3 注):Number.parseInt/parseFloat 与全局同名函数
|
||||
// 是同一函数对象(SameValue)。ctor 侧绑定先执行、产生独立 wrapper,
|
||||
// 此处把 global 侧 wrapper 值原位写回 ctor 同名槽,描述符形态不变。
|
||||
for name in ["parseInt", "parseFloat"] {
|
||||
let si = core.perm_interner().intern(name).0;
|
||||
if let Some(global_pos) = core.shape_forge().lookup_position(global.shape_id(), si) {
|
||||
let val = global.get_prop_at(global_pos);
|
||||
if let Some(ctor_pos) = core.shape_forge().lookup_position(ctor.shape_id(), si) {
|
||||
ctor.set_prop_at(ctor_pos, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +282,8 @@ fn number_to_exponential() {
|
||||
let mut vm = Vm::new();
|
||||
let result = eval(&mut vm, "var n = 123.456; n.toExponential(2)").unwrap();
|
||||
let s = vm.lookup_str(result).unwrap_or_default();
|
||||
assert_eq!(s, "1.23e2");
|
||||
// 指数恒带符号(规范科学式形态)。
|
||||
assert_eq!(s, "1.23e+2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -316,3 +317,133 @@ fn boxed_number_is_object_and_call_stays_primitive() {
|
||||
let n = eval(&mut vm, "Number('7')").unwrap();
|
||||
assert_eq!(n.as_int(), 7);
|
||||
}
|
||||
|
||||
fn str_of(vm: &mut Vm, source: &str) -> String {
|
||||
let r = eval(vm, source).unwrap();
|
||||
vm.lookup_str(r).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_proto_methods_this_number_value() {
|
||||
let mut vm = Vm::new();
|
||||
// Number 原始值与 Number 对象均合法 this。
|
||||
assert_eq!(str_of(&mut vm, "(new Number(7)).toFixed(1)"), "7.0");
|
||||
assert_eq!(str_of(&mut vm, "(new Number(7)).toPrecision(2)"), "7.0");
|
||||
assert_eq!(str_of(&mut vm, "(new Number(7)).toExponential(1)"), "7.0e+0");
|
||||
assert_eq!(str_of(&mut vm, "(new Number(255)).toString(16)"), "ff");
|
||||
// Number.prototype 本体是 [[NumberData]] = +0 的 Number 对象。
|
||||
assert_eq!(str_of(&mut vm, "Number.prototype.toFixed(1)"), "0.0");
|
||||
assert_eq!(str_of(&mut vm, "Number.prototype.toExponential(0)"), "0e+0");
|
||||
// 其余 this 一律 TypeError(四方法非通用)。
|
||||
for method in ["toString", "toFixed", "toExponential", "toPrecision"] {
|
||||
let src = format!("Number.prototype.{}.call({{}})", method);
|
||||
let err = eval(&mut vm, &src).unwrap_err();
|
||||
assert!(err.contains("TypeError"), "for {}: {}", method, err);
|
||||
}
|
||||
let err = eval(&mut vm, "Number.prototype.toFixed.call(new String('1'))").unwrap_err();
|
||||
assert!(err.contains("TypeError"), "got: {}", err);
|
||||
let err = eval(&mut vm, "Number.prototype.toPrecision.call(null)").unwrap_err();
|
||||
assert!(err.contains("TypeError"), "got: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_methods_argument_coercion_and_ordering() {
|
||||
let mut vm = Vm::new();
|
||||
// 参数走完整 ToNumber:Symbol/BigInt 抛 TypeError。
|
||||
for method in ["toFixed", "toExponential", "toPrecision"] {
|
||||
let err = eval(&mut vm, &format!("(0).{}(0n)", method)).unwrap_err();
|
||||
assert!(err.contains("TypeError"), "bigint for {}: {}", method, err);
|
||||
let err = eval(&mut vm, &format!("(0).{}(Symbol())", method)).unwrap_err();
|
||||
assert!(err.contains("TypeError"), "symbol for {}: {}", method, err);
|
||||
}
|
||||
// 参数 valueOf 抛出的原始异常原样传播(保留异常值)。
|
||||
let v = eval(&mut vm, "try { (0).toFixed({valueOf(){ throw 42 }}) } catch (e) { e }").unwrap();
|
||||
assert_eq!(v.as_int(), 42);
|
||||
// toFixed:范围检查先于 NaN 短路。
|
||||
let err = eval(&mut vm, "NaN.toFixed(Infinity)").unwrap_err();
|
||||
assert!(err.contains("RangeError"), "got: {}", err);
|
||||
assert_eq!(str_of(&mut vm, "NaN.toFixed(0)"), "NaN");
|
||||
// toPrecision:NaN/±∞ 专名先于范围检查。
|
||||
assert_eq!(str_of(&mut vm, "NaN.toPrecision(1000)"), "NaN");
|
||||
assert_eq!(str_of(&mut vm, "(Infinity).toPrecision(1000)"), "Infinity");
|
||||
assert_eq!(str_of(&mut vm, "(-Infinity).toPrecision(1000)"), "-Infinity");
|
||||
let err = eval(&mut vm, "(10).toPrecision(0)").unwrap_err();
|
||||
assert!(err.contains("RangeError"), "got: {}", err);
|
||||
// toExponential:NaN/±∞ 专名先于范围检查。
|
||||
assert_eq!(str_of(&mut vm, "NaN.toExponential(101)"), "NaN");
|
||||
assert_eq!(str_of(&mut vm, "(Infinity).toExponential(101)"), "Infinity");
|
||||
let err = eval(&mut vm, "(3).toExponential(101)").unwrap_err();
|
||||
assert!(err.contains("RangeError"), "got: {}", err);
|
||||
let err = eval(&mut vm, "(3).toExponential(-1)").unwrap_err();
|
||||
assert!(err.contains("RangeError"), "got: {}", err);
|
||||
// toExponential 的 undefined 走最短式,非按 0 位处理。
|
||||
assert_eq!(str_of(&mut vm, "(123.456).toExponential(undefined)"), "1.23456e+2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_methods_default_precision_and_boundaries() {
|
||||
let mut vm = Vm::new();
|
||||
// toPrecision 缺省 precision = ToString(x)。
|
||||
assert_eq!(str_of(&mut vm, "(42).toPrecision()"), "42");
|
||||
assert_eq!(str_of(&mut vm, "(123.456).toPrecision(undefined)"), "123.456");
|
||||
assert_eq!(str_of(&mut vm, "NaN.toPrecision()"), "NaN");
|
||||
assert_eq!(str_of(&mut vm, "Number.prototype.toPrecision()"), "0");
|
||||
// toFixed 的 x ≥ 1e21 退化为 String 科学式。
|
||||
assert_eq!(str_of(&mut vm, "(1e21).toFixed()"), "1e+21");
|
||||
assert_eq!(str_of(&mut vm, "(-1e21).toFixed(0)"), "-1e+21");
|
||||
assert_eq!(str_of(&mut vm, "(new Number(1e21)).toFixed()"), "1e+21");
|
||||
// -0 三面:构造、toFixed、toExponential。
|
||||
assert_eq!(str_of(&mut vm, "String(Object.is(Number(-0), -0))"), "true");
|
||||
assert_eq!(str_of(&mut vm, "String(Object.is(new Number(-0).valueOf(), -0))"), "true");
|
||||
assert_eq!(str_of(&mut vm, "(-0).toFixed(0)"), "0");
|
||||
assert_eq!(str_of(&mut vm, "(-0).toExponential(0)"), "0e+0");
|
||||
assert_eq!(str_of(&mut vm, "(-0).toPrecision(2)"), "0.0");
|
||||
// 指数恒带符号。
|
||||
assert_eq!(str_of(&mut vm, "(3).toExponential(0)"), "3e+0");
|
||||
assert_eq!(str_of(&mut vm, "(0).toExponential(1)"), "0.0e+0");
|
||||
assert_eq!(str_of(&mut vm, "(10).toPrecision(1)"), "1e+1");
|
||||
assert_eq!(str_of(&mut vm, "(1.1e-32).toExponential()"), "1.1e-32");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_isnan_isfinite_strict_type() {
|
||||
let mut vm = Vm::new();
|
||||
// 非 Number 类型(含字符串/对象)一律 false,不强转。
|
||||
assert_eq!(str_of(&mut vm, "String(Number.isFinite('1'))"), "false");
|
||||
assert_eq!(str_of(&mut vm, "String(Number.isNaN('NaN'))"), "false");
|
||||
assert_eq!(str_of(&mut vm, "String(Number.isFinite(new Number(42)))"), "false");
|
||||
assert_eq!(str_of(&mut vm, "String(Number.isFinite(42))"), "true");
|
||||
assert_eq!(str_of(&mut vm, "String(Number.isNaN(NaN))"), "true");
|
||||
assert_eq!(str_of(&mut vm, "String(Number.isFinite(Infinity))"), "false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn number_proto_tag_and_metadata() {
|
||||
let mut vm = Vm::new();
|
||||
// Number.prototype 本体 tag 为 Number(品牌表据 type_tag 命中)。
|
||||
assert_eq!(str_of(&mut vm, "Object.prototype.toString.call(Number.prototype)"), "[object Number]");
|
||||
// parseInt/parseFloat 与全局同名函数是同一函数对象。
|
||||
assert_eq!(str_of(&mut vm, "String(Number.parseInt === parseInt)"), "true");
|
||||
assert_eq!(str_of(&mut vm, "String(Number.parseFloat === parseFloat)"), "true");
|
||||
// toLocaleString 为 own 属性,行为同 toString(locale 参数忽略)。
|
||||
assert_eq!(str_of(&mut vm, "String(Number.prototype.hasOwnProperty('toLocaleString'))"), "true");
|
||||
assert_eq!(str_of(&mut vm, "(1.5).toLocaleString()"), "1.5");
|
||||
// 四方法 length 为 1。
|
||||
assert_eq!(str_of(&mut vm, "String(Number.prototype.toString.length)"), "1");
|
||||
assert_eq!(str_of(&mut vm, "String(Number.prototype.toFixed.length)"), "1");
|
||||
assert_eq!(str_of(&mut vm, "String(Number.prototype.toExponential.length)"), "1");
|
||||
assert_eq!(str_of(&mut vm, "String(Number.prototype.toPrecision.length)"), "1");
|
||||
}
|
||||
|
||||
/// full_reset 脏重建:number 家族重建后 Number.prototype 仍带 Number 对象 tag
|
||||
/// 与 +0 包值(漏此分支则 tag 翻回 "Object")。
|
||||
#[test]
|
||||
fn number_proto_tag_survives_dirty_rebuild() {
|
||||
let mut vm = Vm::new();
|
||||
// 属性写使 number 家族置脏,触发选择性重建路径。
|
||||
eval(&mut vm, "Number.prototype.marker = 1").unwrap();
|
||||
vm.full_reset();
|
||||
assert_eq!(str_of(&mut vm, "Object.prototype.toString.call(Number.prototype)"), "[object Number]");
|
||||
assert_eq!(str_of(&mut vm, "Number.prototype.toFixed(1)"), "0.0");
|
||||
assert_eq!(str_of(&mut vm, "String(Number.parseInt === parseInt)"), "true");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user