From f8ca6ddc92a0ca0f434b86a4de71489602c9ecaf Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Tue, 15 Sep 2026 17:41:07 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20runtime=5Fapi=20=E5=BC=BA=E8=BD=AC?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E6=97=8F=E6=95=B4=E4=BD=93=E7=A7=BB=E5=85=A5?= =?UTF-8?q?=20coercion=20=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_runtime_api/src/coercion.rs | 995 +++++++++++++++++++++++ crates/oxide_runtime_api/src/lib.rs | 994 +--------------------- 2 files changed, 1007 insertions(+), 982 deletions(-) create mode 100644 crates/oxide_runtime_api/src/coercion.rs diff --git a/crates/oxide_runtime_api/src/coercion.rs b/crates/oxide_runtime_api/src/coercion.rs new file mode 100644 index 00000000..62cf982d --- /dev/null +++ b/crates/oxide_runtime_api/src/coercion.rs @@ -0,0 +1,995 @@ +//! ECMAScript 强转函数族:字符串/BigInt 辅助、数值域、字符串与单元编解码、 +//! 等值与比较、对象完整强转,按源序混排。 +//! +//! 私有辅助跨域互调(`abstract_eq` → `parse_js_number`、`to_string_full` → +//! `to_string`),无合法切分点;整块为本模块,不按域拆文件。 + +use std::cmp::Ordering; + +use num_traits::{ToPrimitive, Zero}; +use oxide_types::mem::P; +use oxide_types::object::JsObject; +use oxide_types::shape::EMPTY_SHAPE_ID; +use oxide_types::value::{JsType, JsValue}; + +use crate::host::VmHost; + +/// 拼接 JS 错误消息,格式为 `"{name}: {msg}"`;任一段为空时只取非空段。 +pub fn format_error_message(name: &str, msg: &str) -> String { + if name.is_empty() { + msg.to_string() + } else if msg.is_empty() { + name.to_string() + } else { + format!("{name}: {msg}") + } +} + +/// 取字符串值的 lossy 文本(孤立 surrogate 单元映射为 U+FFFD),生命周期为 +/// `'static`。仅限同一函数内即时消费。 +/// +/// # Safety +/// 调用方必须保证:从取用返回值到最后一次消费之间,不发生任何可能释放该字符串 +/// 的操作(分配、GC、`full_reset`)——session 字符串在执行期 GC 或 reset 时会被 +/// 释放,跨分配点持有即悬垂。需要单元语义(不可 lossy)的场景走 +/// [`VmHost::string_units`] / [`to_units_full`]。 +#[inline] +pub(crate) unsafe fn string_data(val: JsValue) -> std::borrow::Cow<'static, str> { + // Borrowed 臂借用期名义绑定解引用临时;与旧 `&'static str` 版本同款契约 + // (同函数内即时消费),生命周期统一升为 'static。 + std::mem::transmute((*val.as_string_ptr()).as_lossy_str()) +} + +/// 按内容比较两个字符串 `JsValue` 是否相等。 +/// +/// 先做指针级短路(同一 interned 字符串必等);双 Flat 比文本(与旧路径逐位 +/// 一致),其余按单元序列比较(对"rope vs 同内容不同指针"也正确)。 +#[inline] +pub fn string_value_eq(a: JsValue, b: JsValue) -> bool { + if a.as_string_ptr() == b.as_string_ptr() { + return true; + } + let sa = unsafe { &*a.as_string_ptr() }; + let sb = unsafe { &*b.as_string_ptr() }; + if sa.is_flat() && sb.is_flat() { + return sa.as_str() == sb.as_str(); + } + sa.units() == sb.units() +} + +/// BigInt 转 f64 的近似转换(连续整数用 to_u64,大数用 Display 解析)。 +#[inline] +pub fn bigint_to_f64(v: &num_bigint::BigInt) -> f64 { + if let Some(u) = v.to_u64() { + return u as f64; + } + v.to_string().parse::().unwrap_or(f64::INFINITY) +} + +/// 借出 BigInt 值。 +/// +/// # Safety +/// `val` 必须是 BigInt `JsValue`,且其 `num_bigint::BigInt` box 存活(VM 在 full_reset 前保证)。 +#[inline] +pub unsafe fn bigint_data(val: JsValue) -> &'static num_bigint::BigInt { + &*val.as_bigint_ptr() +} + +/// BigInt 的十进制字符串表示(num_bigint::BigInt → 十进制)。 +/// +/// 与 ECMA-262 `Number::toString` 无关:BigInt 恒以十进制输出,负号前缀。 +#[inline] +pub fn bigint_to_string(v: &num_bigint::BigInt) -> String { + v.to_string() +} + +/// 对原始值执行 ECMAScript ToNumber 的快速路径(不触发对象 coercion)。 +/// +/// Number/String/Boolean/null 按规范转换;undefined 与不可解析字符串为 `NaN`; +/// Object 与 Symbol 不在此处理,返回 `NaN`(对象需走 [`to_number_full`])。 +pub fn to_number(val: JsValue) -> f64 { + match val.js_type() { + JsType::Int => val.as_int() as f64, + JsType::Double => val.as_double(), + JsType::Bool => { + if val.as_bool() { + 1.0 + } else { + 0.0 + } + } + JsType::Null => 0.0, + JsType::Undefined => f64::NAN, + JsType::String => { + // 孤立 surrogate 的 lossy 文本不含数字/数字前缀,解析结果与单元口径一致(NaN)。 + parse_js_number(&unsafe { string_data(val) }) + } + JsType::BigInt => { + // BigInt → Number 近似转换:i128 超出 f64 精度时舍入为近似值。 + // 规范路径(显式 Number(bigint) / 位运算)在 builtins / dispatch 层精确处理。 + bigint_to_f64(unsafe { bigint_data(val) }) + } + JsType::Object | JsType::Symbol => f64::NAN, + } +} + +/// 按 ECMA-262 ToNumber 的 StringNumericLiteral 语法解析字符串。 +/// +/// 在 Rust `parse::` 之上补齐 JS 特有规则:空串为 0、`0x/0o/0b` 前缀按 +/// 对应进制解析、精确匹配 `±Infinity`。Rust parse 对 inf/nan 大小写不敏感, +/// 需先按十进制字符集排除这些令牌(JS 只接受精确的 "Infinity")。 +/// +/// # 边界与前提 +/// - trim 后空串 → 0 +/// - 十六/八/二进制要求全部字符为有效数字,含非法字符 → NaN +/// - 十进制结果与 Rust parse 一致,溢出时归 ±inf / 0 +fn parse_js_number(s: &str) -> f64 { + let t = s.trim(); + if t.is_empty() { + return 0.0; + } + let b = t.as_bytes(); + // 前缀探测按字节直接比较(ASCII 大小写不敏感),避免每次转换分配 lowercase。 + if b.len() >= 2 && b[0] == b'0' && (b[1] == b'x' || b[1] == b'X') { + return parse_radix_int(&t[2..], 16); + } + if b.len() >= 2 && b[0] == b'0' && (b[1] == b'o' || b[1] == b'O') { + return parse_radix_int(&t[2..], 8); + } + if b.len() >= 2 && b[0] == b'0' && (b[1] == b'b' || b[1] == b'B') { + return parse_radix_int(&t[2..], 2); + } + if t == "Infinity" || t == "+Infinity" { + return f64::INFINITY; + } + if t == "-Infinity" { + return f64::NEG_INFINITY; + } + // 十进制语法仅允许数字、符号、小数点与指数 e;含其它字符的令牌(如 + // inf/nan 变体)不是合法 StringNumericLiteral,一律 NaN。 + if !b + .iter() + .all(|&c| c.is_ascii_digit() || c == b'+' || c == b'-' || c == b'.' || c == b'e' || c == b'E') + { + return f64::NAN; + } + t.parse::().unwrap_or(f64::NAN) +} + +/// 解析指定进制的无符号整数;全部字符必须是有效数字,否则返回 NaN。 +/// +/// 累积在 f64 中:2/8/16 是 2 的幂,逐位乘加即为正确舍入结果;十进制不适用。 +fn parse_radix_int(s: &str, radix: u32) -> f64 { + if s.is_empty() { + return f64::NAN; + } + let mut v = 0.0f64; + for c in s.chars() { + match c.to_digit(radix) { + Some(d) => v = v * radix as f64 + d as f64, + None => return f64::NAN, + } + } + v +} + +/// 把 f64 格式化为 ECMA-262 Number::toString 的字符串(含 NaN/±Infinity 专名)。 +/// +/// 直接调用 [`write_number_into`] 写入 32 字节预分配的缓冲区(ryu 输出上界 +/// ~24 字节 + 负号),单次分配无扩容。 +/// +/// # 边界与前提 +/// - NaN → "NaN",±∞ → "±Infinity",±0 → "0" +/// - 32 字节容量恒够:最长输出 `-1.7976931348623157e+308`(25 字节)。 +pub fn js_number_to_string(d: f64) -> String { + let mut out = String::with_capacity(32); + write_number_into(d, &mut out); + out +} + +/// 把 f64 格式化为 ECMA-262 Number::toString 的文本,追加到 `out`。 +/// +/// 对有限非零数,先经 ryu 最短表示取出有效数字与十进制指数(`format_finite` +/// 输出形如 `"123.45"` / `"1e21"` / `"1000000000000000.0"`),再按规范分段 +/// 重建:指数 `-6 < n <= 21` 时用定点表示,其余用科学计数法 `d.ddde±e`。 +/// +/// # 步骤 +/// 1. NaN/±∞/±0 直接写静态串;负数先写 `-`。 +/// 2. 整数 double 快路径:`fract()==0` 且 `|d| < 2^53` 时整数直写(i64 itoa), +/// 跳过 ryu 与重建。 +/// 3. 含 `e` 时按字节解析有效数字与指数(ryu 输出全 ASCII,指数不带 `+`), +/// 有效数字收栈上数组后按四段规则一次写齐。 +/// 4. 不含 `e` 的定点形式仅需去掉整数末尾的 `.0` 后缀。 +/// +/// # 边界与前提 +/// - 整数快路径上限取 2^53 而非 1e21:2^53 内整数在 f64 中精确且十进制有效 +/// 位数 ≤ 16,整数直写与最短表示一致;[2^53, 1e21) 的整数 double 精确值 +/// 可能与规范最短表示分歧(如 2^55 精确值 36028797018963968,规范输出 +/// 36028797018963970),故该区间保留 ryu 路径。1e21/1e22 等自然被排除。 +/// - ryu 输出全 ASCII 且指数无 `+`;有效数字 ≤ 17 位,栈缓冲 24 字节恒够。 +/// +/// # 副作用 +/// - 追加到 `out`,不新建中间字符串、不做堆分配(`out` 扩容除外)。 +pub fn write_number_into(d: f64, out: &mut String) { + if d.is_nan() { + out.push_str("NaN"); + return; + } + if d.is_infinite() { + out.push_str(if d.is_sign_positive() { "Infinity" } else { "-Infinity" }); + return; + } + if d == 0.0 { + out.push('0'); + return; + } + let neg = d.is_sign_negative(); + let abs = d.abs(); + // 有限非零负数先追加负号(追加语义:插到当前数值之前,不扰动 out 已有前缀)。 + if neg { + out.push('-'); + } + // 整数 double 快路径:无小数部分且在 2^53 内(精确整数,直写与最短表示一致)。 + if abs.fract() == 0.0 && abs < 9_007_199_254_740_992.0 { + use std::fmt::Write; + let _ = write!(out, "{}", abs as i64); + return; + } + let mut buf = ryu::Buffer::new(); + let s = buf.format_finite(abs); + if let Some(e_pos) = s.find('e') { + // 有效数字收栈上数组(ryu 输出全 ASCII),跳过 '.' 逐字节拷贝。 + let mut digits = [0u8; 24]; + let mut k = 0usize; + for &b in s.as_bytes()[..e_pos].iter() { + if b != b'.' { + digits[k] = b; + k += 1; + } + } + debug_assert!(k <= 24, "ryu 有效数字不超过 24 字节"); + let exp: i32 = s[e_pos + 1..].parse().unwrap_or(0); + let n = exp + 1; + if n > -6 && n <= 21 { + // 定点:ryu 在此范围输出指数形式但 JS 要求十进制。 + if n <= 0 { + out.push_str("0."); + for _ in 0..(-n) { + out.push('0'); + } + out.push_str(std::str::from_utf8(&digits[..k]).expect("有效数字恒为 ASCII")); + } else if k as i32 <= n { + out.push_str(std::str::from_utf8(&digits[..k]).expect("有效数字恒为 ASCII")); + for _ in 0..(n - k as i32) { + out.push('0'); + } + } else { + out.push_str(std::str::from_utf8(&digits[..n as usize]).expect("有效数字恒为 ASCII")); + out.push('.'); + out.push_str(std::str::from_utf8(&digits[n as usize..k]).expect("有效数字恒为 ASCII")); + } + } else { + // 科学计数:首位 + (可选 "." + 剩余) + e[+/-]指数。指数 ≤ 3 位,write! 直写。 + out.push(digits[0] as char); + if k > 1 { + out.push('.'); + out.push_str(std::str::from_utf8(&digits[1..k]).expect("有效数字恒为 ASCII")); + } + out.push('e'); + if n > 1 { + out.push('+'); + } + use std::fmt::Write; + let _ = write!(out, "{}", n - 1); + } + } else if let Some(stripped) = s.strip_suffix(".0") { + out.push_str(stripped); + } else { + out.push_str(s); + } +} + +/// ToUint32(ECMA-262 §7.1.6):对数值取模 2^32。NaN/±0/Infinity 归零。 +pub fn to_uint32(val: JsValue) -> u32 { + let n = to_number(val); + if n == 0.0 || !n.is_finite() { + return 0; + } + n.trunc().rem_euclid(4_294_967_296.0) as u32 +} + +/// ToInt32(ECMA-262 §7.1.5):对数值取模 2^32 后按有符号 32 位解释。 +pub fn to_int32(val: JsValue) -> i32 { + let int = to_uint32(val); + if int > i32::MAX as u32 { + (int as i64 - 4_294_967_296i64) as i32 + } else { + int as i32 + } +} + +/// 把 `val` 的字符串表示追加到 `buf`,不分配中间临时 String。 +/// 用于热路径字符串拼接。 +pub fn push_to_string(val: JsValue, buf: &mut String) { + if val.is_int() { + use std::fmt::Write; + let _ = write!(buf, "{}", val.as_int()); + return; + } + if val.is_double() { + write_number_into(val.as_double(), buf); + return; + } + if val.is_bool() { + buf.push_str(if val.as_bool() { "true" } else { "false" }); + return; + } + if val.is_null() { + buf.push_str("null"); + return; + } + if val.is_undefined() { + buf.push_str("undefined"); + return; + } + if val.is_string() { + unsafe { buf.push_str(&string_data(val)) }; + return; + } + if val.is_bigint() { + use std::fmt::Write; + let _ = write!(buf, "{}", unsafe { bigint_data(val) }); + return; + } + if val.is_object() { + buf.push_str("[object]"); + } +} + +/// 把原始值转成字符串(ToString 的原始值路径)。 +/// +/// 数值走 ECMA-262 Number::toString 格式化;Object 在此返回占位符 `[object]`, +/// 完整路径见 [`to_string_full`]。 +pub fn to_string(val: JsValue) -> String { + if val.is_int() { + return val.as_int().to_string(); + } + if val.is_double() { + return js_number_to_string(val.as_double()); + } + if val.is_bool() { + return val.as_bool().to_string(); + } + if val.is_null() { + return "null".to_string(); + } + if val.is_undefined() { + return "undefined".to_string(); + } + if val.is_string() { + return unsafe { string_data(val) }.to_string(); + } + if val.is_bigint() { + return bigint_to_string(unsafe { bigint_data(val) }); + } + if val.is_object() { + return "[object]".to_string(); + } + String::new() +} + +/// 把任意值转为 UTF-16 单元序列(`ToPrimitive(string hint)` 的单元口径): +/// 字符串值直接借出单元(Flat 惰性编码、rope 扁平化);Symbol 返回 TypeError +/// 错误;其余值经 ToString 原始值路径后 UTF-16 编码。 +pub fn to_units_full(val: JsValue, host: &mut H) -> Result, String> { + if val.is_string() { + return Ok(host.string_units(val).into_owned()); + } + if val.is_symbol() { + return Err(host.error_message_text("TypeError", "Cannot convert a Symbol value to a string")); + } + let prim = to_primitive(val, ToPrimitiveHint::String, host)?; + if prim.is_string() { + return Ok(host.string_units(prim).into_owned()); + } + // 非字符串原始值(数字等):先格式化再编码。 + let s = to_string(prim); + Ok(s.encode_utf16().collect()) +} + +/// 把值追加其 UTF-16 单元到 `buf`:字符串值直接扩单元序列,非字符串值经 +/// ToString 后 UTF-16 编码。字符串拼接的单元通道路径(不可 lossy)。 +pub fn push_units_to(val: JsValue, buf: &mut Vec) { + if val.is_string() { + // SAFETY: val 为字符串值,借用即时消费。 + let s = unsafe { &*val.as_string_ptr() }; + buf.extend_from_slice(s.units().as_ref()); + return; + } + let s = to_string(val); + buf.extend(s.encode_utf16()); +} + +/// ToBoolean(ECMA-262 §7.1.2):falsy 值仅限 undefined/null/false/±0/NaN/空串,其余为 true; +/// [[IsHTMLDDA]] 宿主对象按 undefined 处理(B.3.4)。 +pub fn to_boolean(val: JsValue) -> bool { + match val.js_type() { + JsType::Undefined | JsType::Null => false, + JsType::Bool => val.as_bool(), + JsType::Int => val.as_int() != 0, + JsType::Double => { + let d = val.as_double(); + // ±0 与 NaN 均为 falsy(IEEE 中 0.0 == -0.0,符号位无需单独判断)。 + !(d == 0.0 || d.is_nan()) + } + JsType::String => !unsafe { (*val.as_string_ptr()).is_empty() }, + JsType::BigInt => !unsafe { bigint_data(val) }.is_zero(), + JsType::Object => { + // SAFETY: Object 型 JsValue 的载荷位是存活对象的裸指针,借用即时消费。 + let obj = unsafe { &*val.as_js_object_ptr() }; + !obj.is_html_dda_obj() + } + JsType::Symbol => true, + } +} + +/// IsLooselyEqual(x, y) — ECMA-262 §7.2.15(`==`)。 +/// +/// 同类型委托严格相等;null 与 undefined 互等;BigInt/Number/String 两两 +/// 转数值比较;Boolean 操作数先 ToNumber;原始值 vs Object 经 ToPrimitive +/// 强制转换后重入(Symbol 与 Object 同样触发,规范步骤 10-11)。对象路径 +/// 可能调用用户 `valueOf` / `toString` / `@@toPrimitive`,因此需要 `VmHost` +/// 参数与 `Result`(回调抛出的 TypeError 以 `Err` 传播)。 +pub fn abstract_eq(lhs: JsValue, rhs: JsValue, host: &mut H) -> Result { + let tl = lhs.js_type(); + let tr = rhs.js_type(); + // [[IsHTMLDDA]] 宿主对象:宽松相等一律按 undefined 处理(B.3.4), + // 重入 undefined 口径(undefined/null 互等、与其余类型不等)。 + if tl == JsType::Object { + // SAFETY: Object 型 JsValue 的载荷位是存活对象的裸指针,借用即时消费。 + if unsafe { &*lhs.as_js_object_ptr() }.is_html_dda_obj() { + return abstract_eq(JsValue::undefined(), rhs, host); + } + } + if tr == JsType::Object { + // SAFETY: 同上。 + if unsafe { &*rhs.as_js_object_ptr() }.is_html_dda_obj() { + return abstract_eq(lhs, JsValue::undefined(), host); + } + } + // 同类型 → 严格相等。 + if tl == tr { + return Ok(strict_equality(lhs, rhs)); + } + match (tl, tr) { + // null 与 undefined 互等。 + (JsType::Null, JsType::Undefined) | (JsType::Undefined, JsType::Null) => Ok(true), + // Number 混合表示(int vs double):同属 ECMAScript Number,按严格相等。 + (JsType::Int | JsType::Double, JsType::Int | JsType::Double) => Ok(strict_equality(lhs, rhs)), + // BigInt 与 Number:转 f64 比较(精度内精确;超出精度近似)。 + (JsType::BigInt, JsType::Int | JsType::Double) => { + Ok(bigint_to_f64(unsafe { bigint_data(lhs) }) == to_number(rhs)) + } + (JsType::Int | JsType::Double, JsType::BigInt) => { + Ok(to_number(lhs) == bigint_to_f64(unsafe { bigint_data(rhs) })) + } + // BigInt 与 String:字符串解析为数字后比较(lossy 文本口径,见 to_number)。 + (JsType::BigInt, JsType::String) => { + let r = parse_js_number(&unsafe { string_data(rhs) }); + Ok(bigint_to_f64(unsafe { bigint_data(lhs) }) == r) + } + (JsType::String, JsType::BigInt) => { + let l = parse_js_number(&unsafe { string_data(lhs) }); + Ok(l == bigint_to_f64(unsafe { bigint_data(rhs) })) + } + // Number 与 String:字符串经 ToNumber 后按严格数值比较。 + (JsType::Int | JsType::Double, JsType::String) | (JsType::String, JsType::Int | JsType::Double) => { + Ok(strict_double_eq(to_number(lhs), to_number(rhs))) + } + // Boolean 操作数:先 ToNumber 再重入。int(0/1) 轻量编码,省 float + // 装箱与 to_number 链;int 属于 Number,重入后各数值分支全部认。 + (JsType::Bool, _) => abstract_eq(JsValue::int(lhs.as_bool() as i32), rhs, host), + (_, JsType::Bool) => abstract_eq(lhs, JsValue::int(rhs.as_bool() as i32), host), + // Object 与原始值(Number/String/BigInt/Symbol):ToPrimitive 后重入。 + (JsType::Object, JsType::Int | JsType::Double | JsType::String | JsType::BigInt | JsType::Symbol) => { + let prim = to_primitive(lhs, ToPrimitiveHint::Default, host)?; + abstract_eq(prim, rhs, host) + } + (JsType::Int | JsType::Double | JsType::String | JsType::BigInt | JsType::Symbol, JsType::Object) => { + let prim = to_primitive(rhs, ToPrimitiveHint::Default, host)?; + abstract_eq(lhs, prim, host) + } + // 其余组合不相等(含 null/undefined 与其它类型、Object/Object 已由同类型分支处理)。 + _ => Ok(false), + } +} + +fn strict_double_eq(a: f64, b: f64) -> bool { + if a.is_nan() || b.is_nan() { + return false; + } + a == b +} + +/// Relational Comparison(`<`,ECMA-262 §7.2.13)的原始值版本。 +/// +/// 双字符串按字典序;否则转数值比较,任一侧为 NaN 时返回 `None`(表示比较未定义,调用方据此处理 `<`/`>`)。 +pub fn relational_compare(lhs: JsValue, rhs: JsValue) -> Option { + if lhs.is_string() && rhs.is_string() { + // SAFETY: 两侧均为字符串值,借用即时消费。 + let l = unsafe { &*lhs.as_string_ptr() }; + let r = unsafe { &*rhs.as_string_ptr() }; + if l.is_flat() && r.is_flat() { + // 双 Flat:文本比较与旧路径逐位一致。 + return Some(l.as_str() < r.as_str()); + } + // 单元口径:码元字典序即规范序,Flat 编码与 rope 扁平化统一走单元通道。 + return Some(l.units() < r.units()); + } + if lhs.is_bigint() && rhs.is_bigint() { + let l = unsafe { bigint_data(lhs) }; + let r = unsafe { bigint_data(rhs) }; + return Some(l < r); + } + // BigInt 与 Number 混合:Number 为整数且落在 i128 范围时转 i128 精确比较, + // 否则(非整数/超出范围/NaN/±Infinity)转 f64 比较(NaN 结果为未定义)。 + if lhs.is_bigint() && (rhs.is_int() || rhs.is_double()) { + if rhs.is_double() && rhs.as_double().is_nan() { + return None; + } + let l = unsafe { bigint_data(lhs) }; + return Some(bigint_cmp_number(l, rhs) == Ordering::Less); + } + if (lhs.is_int() || lhs.is_double()) && rhs.is_bigint() { + if lhs.is_double() && lhs.as_double().is_nan() { + return None; + } + let r = unsafe { bigint_data(rhs) }; + // lhs(Number) < rhs(BigInt) ⇔ 反向比较(lhs 作为"number 侧")。 + return Some(number_cmp_bigint(lhs, r) == Ordering::Less); + } + // BigInt 与 String/Boolean 混合:转 Number 后比较。 + if lhs.is_bigint() || rhs.is_bigint() { + let l = to_number(lhs); + let r = to_number(rhs); + if l.is_nan() || r.is_nan() { + return None; + } + return l.partial_cmp(&r).map(|o| o.is_lt()); + } + let l = to_number(lhs); + let r = to_number(rhs); + if l.is_nan() || r.is_nan() { + return None; + } + l.partial_cmp(&r).map(|o| o.is_lt()) +} + +/// 拼接两个字符串,按已知总长度预分配容量以避免重复扩容。 +pub fn string_concat(lhs: &str, rhs: &str) -> String { + let mut s = String::with_capacity(lhs.len() + rhs.len()); + s.push_str(lhs); + s.push_str(rhs); + s +} + +fn to_f64(val: JsValue) -> f64 { + if val.is_int() { + val.as_int() as f64 + } else if val.is_double() { + val.as_double() + } else { + f64::NAN + } +} + +/// BigInt 与 Number 的序比较:Number 为整数且落于 i128 范围时精确比较, +/// 否则退化为 f64 近似比较(非有限/超出 i128 范围时)。 +fn bigint_cmp_number(big: &num_bigint::BigInt, num: JsValue) -> Ordering { + number_cmp_bigint(num, big).reverse() +} + +/// Number 与 BigInt 的序比较(`num` 与 `big` 的 `<`/`=`/`>`)。 +fn number_cmp_bigint(num: JsValue, big: &num_bigint::BigInt) -> Ordering { + if num.is_int() { + return (num.as_int() as i128).cmp(&big.to_i128().unwrap_or(if big.sign() == num_bigint::Sign::Minus { + i128::MIN + } else { + i128::MAX + })); + } + let d = num.as_double(); + if d.is_nan() { + return Ordering::Greater; + } + if d.is_infinite() { + return if d.is_sign_positive() { Ordering::Greater } else { Ordering::Less }; + } + let truncated = d.trunc(); + if truncated != d || truncated.abs() > i128::MAX as f64 { + return (d).partial_cmp(&bigint_to_f64(big)).unwrap_or(Ordering::Equal); + } + (truncated as i128).cmp(&big.to_i128().unwrap_or(if big.sign() == num_bigint::Sign::Minus { + i128::MIN + } else { + i128::MAX + })) +} + +/// SameValue(x, y)(ECMA-262 §7.2.9):与 `===` 的区别在于 NaN 视为相等、+0/-0 视为不同。 +pub fn same_value(lhs: JsValue, rhs: JsValue) -> bool { + match (lhs.js_type(), rhs.js_type()) { + (JsType::Double, JsType::Double) => { + let a = lhs.as_double(); + let b = rhs.as_double(); + if a.is_nan() && b.is_nan() { + return true; + } + if a == 0.0 && b == 0.0 { + return a.is_sign_negative() == b.is_sign_negative(); + } + a == b + } + (JsType::Int, JsType::Int) => lhs.as_int() == rhs.as_int(), + // Number 混合表示(int vs double):SameValue 的 ±0/NaN 特判原样保留。 + (JsType::Int | JsType::Double, JsType::Int | JsType::Double) => { + let a = to_f64(lhs); + let b = to_f64(rhs); + if a.is_nan() && b.is_nan() { + return true; + } + if a == 0.0 && b == 0.0 { + return a.is_sign_negative() == b.is_sign_negative(); + } + a == b + } + (JsType::Bool, JsType::Bool) => lhs.as_bool() == rhs.as_bool(), + (JsType::Null, JsType::Null) => true, + (JsType::Undefined, JsType::Undefined) => true, + (JsType::String, JsType::String) => string_value_eq(lhs, rhs), + (JsType::Object, JsType::Object) => lhs.as_ptr() == rhs.as_ptr(), + (JsType::Symbol, JsType::Symbol) => lhs.as_symbol_index() == rhs.as_symbol_index(), + // SAFETY: bigint 指针由 JsValue::bigint 构造,指向 VM 登记的存活 box。 + (JsType::BigInt, JsType::BigInt) => unsafe { bigint_data(lhs) == bigint_data(rhs) }, + _ => false, + } +} + +/// ToIntegerOrInfinity(argument) — ECMA-262 §7.1.4. +pub fn to_integer_or_infinity(val: JsValue) -> f64 { + let n = to_number(val); + if n.is_nan() || n == 0.0 { + 0.0 + } else if n.is_infinite() { + n + } else { + n.trunc() + } +} + +/// ToLength(argument) — ECMA-262 §7.1.20. +pub fn to_length(val: JsValue) -> u64 { + let n = to_number(val); + 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 +} + +/// SameValueZero(x, y) — ECMA-262 §7.2.11. +pub fn same_value_zero(lhs: JsValue, rhs: JsValue) -> bool { + match (lhs.js_type(), rhs.js_type()) { + (JsType::Int, JsType::Int) => lhs.as_int() == rhs.as_int(), + (JsType::Double, JsType::Double) => { + let a = lhs.as_double(); + let b = rhs.as_double(); + if a.is_nan() && b.is_nan() { + return true; + } + a == b + } + // Number 混合表示:NaN 视为相等,+0/-0 相等(Rust f64 中 +0 == -0)。 + (JsType::Int | JsType::Double, JsType::Int | JsType::Double) => { + let a = to_f64(lhs); + let b = to_f64(rhs); + if a.is_nan() && b.is_nan() { + return true; + } + a == b + } + _ => same_value(lhs, rhs), + } +} + +/// Strict Equality Comparison(`===`,ECMA-262 §7.2.14)。 +/// +/// 类型不同直接为 false;同类型下 Number(int/double 两种表示)按数值 +/// 比较(NaN 恒 false、+0/-0 相等,含 int/double 混合——`0 === -0` 为 true), +/// Object 按指针、其余按值比较。 +pub fn strict_equality(lhs: JsValue, rhs: JsValue) -> bool { + match (lhs.js_type(), rhs.js_type()) { + (JsType::Int, JsType::Int) => lhs.as_int() == rhs.as_int(), + (JsType::Double, JsType::Double) => strict_double_eq(lhs.as_double(), rhs.as_double()), + // Number 跨 int/double 表示:`42 === 42.0`、`0 === -0` 均按数值语义。 + (JsType::Int | JsType::Double, JsType::Int | JsType::Double) => strict_double_eq(to_f64(lhs), to_f64(rhs)), + (JsType::Bool, JsType::Bool) => lhs.as_bool() == rhs.as_bool(), + (JsType::Null, JsType::Null) => true, + (JsType::Undefined, JsType::Undefined) => true, + (JsType::String, JsType::String) => string_value_eq(lhs, rhs), + (JsType::Object, JsType::Object) => lhs.as_ptr() == rhs.as_ptr(), + (JsType::Symbol, JsType::Symbol) => lhs.as_symbol_index() == rhs.as_symbol_index(), + // SAFETY: bigint 指针由 JsValue::bigint 构造,指向 VM 登记的存活 box。 + (JsType::BigInt, JsType::BigInt) => unsafe { bigint_data(lhs) == bigint_data(rhs) }, + _ => false, + } +} + +/// ToObject(ECMA-262 §7.1.13):null/undefined 抛 TypeError,其余原始值包装为对应包装对象。 +/// +/// 包装对象按类型选择原型(String/Number/Boolean 原型或默认 Object 原型),原始值存入 hash props 槽。 +pub fn to_object(val: JsValue, host: &mut H) -> Result { + if val.is_object() { + return Ok(val); + } + if val.is_null() || val.is_undefined() { + return Err(host.error_message_text("TypeError", "Cannot convert null or undefined to object")); + } + let world = host.session().builtin_world(); + let (proto_ptr, type_tag) = if val.is_string() { + (P::as_ptr(&world.string_proto) as *mut JsObject, JsObject::OBJ_TYPE_STRING_OBJ) + } else if val.is_int() || val.is_double() { + (P::as_ptr(&world.number_proto) as *mut JsObject, JsObject::OBJ_TYPE_NUMBER_OBJ) + } else if val.is_bool() { + (P::as_ptr(&world.boolean_proto) as *mut JsObject, JsObject::OBJ_TYPE_BOOLEAN_OBJ) + } else if val.is_bigint() { + (P::as_ptr(&world.bigint_proto) as *mut JsObject, JsObject::OBJ_TYPE_PLAIN) + } else if val.is_symbol() { + (P::as_ptr(&world.symbol_proto) as *mut JsObject, JsObject::OBJ_TYPE_SYMBOL_OBJ) + } else { + (P::as_ptr(&world.object_proto) as *mut JsObject, JsObject::OBJ_TYPE_PLAIN) + }; + let proto_val = JsValue::from_js_object(proto_ptr); + let obj = host.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val)); + let obj_val = JsValue::from_js_object(obj); + let obj_ref = unsafe { &mut *obj }; + obj_ref.type_tag = type_tag; + obj_ref.ensure_hash_props().push(val); + obj_ref.set_prop_count(1); + Ok(obj_val) +} + +/// Hint for the abstract ToPrimitive operation (ECMA-262 §7.1.1). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ToPrimitiveHint { + Default, + String, + Number, +} + +impl ToPrimitiveHint { + /// OrdinaryToPrimitive 方法名顺序提示:`Default`/`Number` → "number",`String` → "string"。 + pub fn as_str(self) -> &'static str { + match self { + ToPrimitiveHint::String => "string", + ToPrimitiveHint::Default | ToPrimitiveHint::Number => "number", + } + } +} + +/// ToPrimitive(input, hint) per ECMA-262 §7.1.1. +/// +/// 原始值原样通过。对象经 VM 的 `coerce_primitive_bounded` 强制转换——先查询 +/// `obj[Symbol.toPrimitive]`,否则按 hint 顺序运行 OrdinaryToPrimitive +/// (valueOf/toString)。对象路径集中在单处,避免在此重复 OrdinaryToPrimitive。 +pub fn to_primitive(val: JsValue, hint: ToPrimitiveHint, host: &mut H) -> Result { + if !val.is_object() { + return Ok(val); + } + host.coerce_primitive_bounded(val, hint == ToPrimitiveHint::String) +} + +/// 带完整对象强制转换的 ToNumber(input):对象经 ToPrimitive(number hint) +/// 处理;Symbol 值按规范抛 TypeError。 +pub fn to_number_full(val: JsValue, host: &mut H) -> Result { + let primitive = to_primitive(val, ToPrimitiveHint::Number, host)?; + if primitive.is_symbol() { + return Err(host.error_message_text("TypeError", "Cannot convert a Symbol value to a number")); + } + Ok(to_number(primitive)) +} + +/// 带完整对象强制转换的 ToString(input):对象经 ToPrimitive(string hint)处理; +/// Symbol 值按规范(§7.1.17)抛 TypeError。 +pub fn to_string_full(val: JsValue, host: &mut H) -> Result { + let primitive = to_primitive(val, ToPrimitiveHint::String, host)?; + if primitive.is_symbol() { + return Err(host.error_message_text("TypeError", "Cannot convert a Symbol value to a string")); + } + Ok(to_string(primitive)) +} + +/// ToPrimitive(string hint) 完整转换并返回字符串*值*:字符串值原样返回(载荷 +/// 形态保留,非 Flat 不 lossy);Symbol 返回 TypeError;其余值经 ToString 后 +/// 以新字符串值返回(非 Flat 源的转换结果落良形 Flat)。 +pub fn to_string_value_full(val: JsValue, host: &mut H) -> Result { + if val.is_string() { + return Ok(val); + } + let primitive = to_primitive(val, ToPrimitiveHint::String, host)?; + if primitive.is_symbol() { + return Err(host.error_message_text("TypeError", "Cannot convert a Symbol value to a string")); + } + if primitive.is_string() { + return Ok(primitive); + } + Ok(host.new_string_owned(to_string(primitive))) +} + +/// 带完整对象强制转换的 ToBigInt(input)(§7.1.14):BigInt 原样;Boolean → 0/1; +/// String 走 StringToBigInt;Number/Symbol/undefined/null → TypeError;对象经 +/// ToPrimitive(default hint)后递归处理。 +pub fn to_bigint_full(val: JsValue, host: &mut H) -> Result { + let primitive = to_primitive(val, ToPrimitiveHint::Default, host)?; + if primitive.is_bigint() { + return Ok(primitive); + } + if primitive.is_bool() { + let v = if primitive.as_bool() { 1 } else { 0 }; + return Ok(host.new_bigint(num_bigint::BigInt::from(v))); + } + if primitive.is_int() || primitive.is_double() { + return Err(host.error_message_text("TypeError", "Cannot convert a Number value to a BigInt")); + } + if primitive.is_string() { + return string_to_bigint_full(host, &to_string(primitive)); + } + // undefined / null / symbol。 + Err(host.error_message_text("TypeError", "Cannot convert value to a BigInt")) +} + +/// StringToBigInt(§7.1.14 步骤):去首尾空白,可选 +/- 号与 0x/0o/0b 前缀; +/// 空串 → 0n;非法 → SyntaxError。 +fn string_to_bigint_full(host: &mut H, s: &str) -> Result { + let trimmed = s.trim(); + if trimmed.is_empty() { + return Ok(host.new_bigint(num_bigint::BigInt::from(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")属于非法语法。 + let (radix, digits) = if let Some(hex) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) { + if neg { + return Err( + host.error_message_text("SyntaxError", "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( + host.error_message_text("SyntaxError", "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( + host.error_message_text("SyntaxError", "Cannot convert string to BigInt: invalid integer literal") + ); + } + (2u32, bin) + } else { + (10u32, rest) + }; + if digits.is_empty() { + return Err(host.error_message_text("SyntaxError", "Cannot convert string to 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") + })?; + Ok(host.new_bigint(if neg { -m } else { m })) +} + +/// `String()` 构造器的字符串转换(§21.1.1.1):Symbol 值(及本引擎以空对象表示的 +/// well-known symbol)返回描述串 `Symbol(desc)`;其余走完整 ToString。 +pub fn to_string_for_string_constructor(val: JsValue, host: &mut H) -> Result { + if val.is_object() { + if let Some(id) = well_known_symbol_id(host, val.as_js_object_ptr()) { + if let Some(name) = well_known_symbol_name(id) { + return Ok(format!("Symbol({name})")); + } + } + } + let primitive = to_primitive(val, ToPrimitiveHint::String, host)?; + if primitive.is_symbol() { + let desc = host.symbol_description(primitive.as_symbol_index()).unwrap_or(""); + return Ok(format!("Symbol({desc})")); + } + Ok(to_string(primitive)) +} + +/// Well-known symbols are stored as empty objects in the builtin world; map an object +/// pointer back to its well-known symbol id (0..WELL_KNOWN_SYMBOL_COUNT) if it is one. +pub fn well_known_symbol_id(host: &H, ptr: *mut JsObject) -> Option { + if ptr.is_null() { + return None; + } + let world = host.session().builtin_world(); + if std::ptr::eq(ptr, world.sym_iterator.as_ptr()) { + Some(0) + } else if std::ptr::eq(ptr, world.sym_match.as_ptr()) { + Some(1) + } else if std::ptr::eq(ptr, world.sym_replace.as_ptr()) { + Some(2) + } else if std::ptr::eq(ptr, world.sym_search.as_ptr()) { + Some(3) + } else if std::ptr::eq(ptr, world.sym_split.as_ptr()) { + Some(4) + } else if std::ptr::eq(ptr, world.sym_to_primitive.as_ptr()) { + Some(5) + } else if std::ptr::eq(ptr, world.sym_has_instance.as_ptr()) { + Some(6) + } else if std::ptr::eq(ptr, world.sym_match_all.as_ptr()) { + Some(7) + } else if std::ptr::eq(ptr, world.sym_async_iterator.as_ptr()) { + Some(8) + } else if std::ptr::eq(ptr, world.sym_to_string_tag.as_ptr()) { + Some(9) + } else if std::ptr::eq(ptr, world.sym_species.as_ptr()) { + Some(10) + } else if std::ptr::eq(ptr, world.sym_async_dispose.as_ptr()) { + Some(11) + } else if std::ptr::eq(ptr, world.sym_dispose.as_ptr()) { + Some(12) + } else { + None + } +} + +/// Descriptive name of a well-known symbol id, e.g. `Symbol.toStringTag` for id 9. +pub fn well_known_symbol_name(id: u32) -> Option<&'static str> { + Some(match id { + 0 => "Symbol.iterator", + 1 => "Symbol.match", + 2 => "Symbol.replace", + 3 => "Symbol.search", + 4 => "Symbol.split", + 5 => "Symbol.toPrimitive", + 6 => "Symbol.hasInstance", + 7 => "Symbol.matchAll", + 8 => "Symbol.asyncIterator", + 9 => "Symbol.toStringTag", + 10 => "Symbol.species", + 11 => "Symbol.asyncDispose", + 12 => "Symbol.dispose", + _ => return None, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_js_number_rules() { + assert_eq!(parse_js_number(""), 0.0); + assert_eq!(parse_js_number(" "), 0.0); + assert_eq!(parse_js_number("0xa"), 10.0); + assert_eq!(parse_js_number("0X1f"), 31.0); + assert_eq!(parse_js_number("0b101"), 5.0); + assert_eq!(parse_js_number("0o17"), 15.0); + assert_eq!(parse_js_number("Infinity"), f64::INFINITY); + assert_eq!(parse_js_number("-Infinity"), f64::NEG_INFINITY); + assert!(parse_js_number("INFINITY").is_nan()); + assert!(parse_js_number("infinity").is_nan()); + assert!(parse_js_number("0x1g").is_nan()); + assert!(parse_js_number("-0x1").is_nan()); + assert_eq!(parse_js_number("1.5e3"), 1500.0); + assert_eq!(parse_js_number("-0"), -0.0); + assert!(parse_js_number("1abc").is_nan()); + } +} diff --git a/crates/oxide_runtime_api/src/lib.rs b/crates/oxide_runtime_api/src/lib.rs index a8cce0b2..b99bc55c 100644 --- a/crates/oxide_runtime_api/src/lib.rs +++ b/crates/oxide_runtime_api/src/lib.rs @@ -10,979 +10,28 @@ //! trait 面向泛型而非对象安全:单态化 `H = Vm` 使每个 `host.*()` 调用内联, //! 相对 builtins 直接位于 `oxide_vm` 内部没有运行时开销。 +/// ECMAScript 强转函数族。 +mod coercion; /// builtins 依赖的 `VmHost` 能力面。 mod host; /// builtin native 函数的三态返回值。 mod native_result; mod runtime_api_log; -use std::cmp::Ordering; - -use num_traits::{ToPrimitive, Zero}; -use oxide_types::mem::P; -use oxide_types::object::JsObject; -use oxide_types::shape::EMPTY_SHAPE_ID; -use oxide_types::value::{JsType, JsValue}; +#[cfg(test)] +use oxide_types::value::JsValue; +pub use coercion::{ + abstract_eq, bigint_data, bigint_to_f64, bigint_to_string, format_error_message, js_number_to_string, + push_to_string, push_units_to, relational_compare, same_value, same_value_zero, strict_equality, string_concat, + string_value_eq, to_bigint_full, to_boolean, to_int32, to_integer_or_infinity, to_length, to_number, + to_number_full, to_object, to_primitive, to_string, to_string_for_string_constructor, to_string_full, + to_string_value_full, to_uint32, to_units_full, well_known_symbol_id, well_known_symbol_name, write_number_into, + ToPrimitiveHint, +}; pub use host::VmHost; pub use native_result::NativeResult; -/// 拼接 JS 错误消息,格式为 `"{name}: {msg}"`;任一段为空时只取非空段。 -pub fn format_error_message(name: &str, msg: &str) -> String { - if name.is_empty() { - msg.to_string() - } else if msg.is_empty() { - name.to_string() - } else { - format!("{name}: {msg}") - } -} - -/// 取字符串值的 lossy 文本(孤立 surrogate 单元映射为 U+FFFD),生命周期为 -/// `'static`。仅限同一函数内即时消费。 -/// -/// # Safety -/// 调用方必须保证:从取用返回值到最后一次消费之间,不发生任何可能释放该字符串 -/// 的操作(分配、GC、`full_reset`)——session 字符串在执行期 GC 或 reset 时会被 -/// 释放,跨分配点持有即悬垂。需要单元语义(不可 lossy)的场景走 -/// [`VmHost::string_units`] / [`to_units_full`]。 -#[inline] -pub(crate) unsafe fn string_data(val: JsValue) -> std::borrow::Cow<'static, str> { - // Borrowed 臂借用期名义绑定解引用临时;与旧 `&'static str` 版本同款契约 - // (同函数内即时消费),生命周期统一升为 'static。 - std::mem::transmute((*val.as_string_ptr()).as_lossy_str()) -} - -/// 按内容比较两个字符串 `JsValue` 是否相等。 -/// -/// 先做指针级短路(同一 interned 字符串必等);双 Flat 比文本(与旧路径逐位 -/// 一致),其余按单元序列比较(对"rope vs 同内容不同指针"也正确)。 -#[inline] -pub fn string_value_eq(a: JsValue, b: JsValue) -> bool { - if a.as_string_ptr() == b.as_string_ptr() { - return true; - } - let sa = unsafe { &*a.as_string_ptr() }; - let sb = unsafe { &*b.as_string_ptr() }; - if sa.is_flat() && sb.is_flat() { - return sa.as_str() == sb.as_str(); - } - sa.units() == sb.units() -} - -/// BigInt 转 f64 的近似转换(连续整数用 to_u64,大数用 Display 解析)。 -#[inline] -pub fn bigint_to_f64(v: &num_bigint::BigInt) -> f64 { - if let Some(u) = v.to_u64() { - return u as f64; - } - v.to_string().parse::().unwrap_or(f64::INFINITY) -} - -/// 借出 BigInt 值。 -/// -/// # Safety -/// `val` 必须是 BigInt `JsValue`,且其 `num_bigint::BigInt` box 存活(VM 在 full_reset 前保证)。 -#[inline] -pub unsafe fn bigint_data(val: JsValue) -> &'static num_bigint::BigInt { - &*val.as_bigint_ptr() -} - -/// BigInt 的十进制字符串表示(num_bigint::BigInt → 十进制)。 -/// -/// 与 ECMA-262 `Number::toString` 无关:BigInt 恒以十进制输出,负号前缀。 -#[inline] -pub fn bigint_to_string(v: &num_bigint::BigInt) -> String { - v.to_string() -} - -/// 对原始值执行 ECMAScript ToNumber 的快速路径(不触发对象 coercion)。 -/// -/// Number/String/Boolean/null 按规范转换;undefined 与不可解析字符串为 `NaN`; -/// Object 与 Symbol 不在此处理,返回 `NaN`(对象需走 [`to_number_full`])。 -pub fn to_number(val: JsValue) -> f64 { - match val.js_type() { - JsType::Int => val.as_int() as f64, - JsType::Double => val.as_double(), - JsType::Bool => { - if val.as_bool() { - 1.0 - } else { - 0.0 - } - } - JsType::Null => 0.0, - JsType::Undefined => f64::NAN, - JsType::String => { - // 孤立 surrogate 的 lossy 文本不含数字/数字前缀,解析结果与单元口径一致(NaN)。 - parse_js_number(&unsafe { string_data(val) }) - } - JsType::BigInt => { - // BigInt → Number 近似转换:i128 超出 f64 精度时舍入为近似值。 - // 规范路径(显式 Number(bigint) / 位运算)在 builtins / dispatch 层精确处理。 - bigint_to_f64(unsafe { bigint_data(val) }) - } - JsType::Object | JsType::Symbol => f64::NAN, - } -} - -/// 按 ECMA-262 ToNumber 的 StringNumericLiteral 语法解析字符串。 -/// -/// 在 Rust `parse::` 之上补齐 JS 特有规则:空串为 0、`0x/0o/0b` 前缀按 -/// 对应进制解析、精确匹配 `±Infinity`。Rust parse 对 inf/nan 大小写不敏感, -/// 需先按十进制字符集排除这些令牌(JS 只接受精确的 "Infinity")。 -/// -/// # 边界与前提 -/// - trim 后空串 → 0 -/// - 十六/八/二进制要求全部字符为有效数字,含非法字符 → NaN -/// - 十进制结果与 Rust parse 一致,溢出时归 ±inf / 0 -fn parse_js_number(s: &str) -> f64 { - let t = s.trim(); - if t.is_empty() { - return 0.0; - } - let b = t.as_bytes(); - // 前缀探测按字节直接比较(ASCII 大小写不敏感),避免每次转换分配 lowercase。 - if b.len() >= 2 && b[0] == b'0' && (b[1] == b'x' || b[1] == b'X') { - return parse_radix_int(&t[2..], 16); - } - if b.len() >= 2 && b[0] == b'0' && (b[1] == b'o' || b[1] == b'O') { - return parse_radix_int(&t[2..], 8); - } - if b.len() >= 2 && b[0] == b'0' && (b[1] == b'b' || b[1] == b'B') { - return parse_radix_int(&t[2..], 2); - } - if t == "Infinity" || t == "+Infinity" { - return f64::INFINITY; - } - if t == "-Infinity" { - return f64::NEG_INFINITY; - } - // 十进制语法仅允许数字、符号、小数点与指数 e;含其它字符的令牌(如 - // inf/nan 变体)不是合法 StringNumericLiteral,一律 NaN。 - if !b - .iter() - .all(|&c| c.is_ascii_digit() || c == b'+' || c == b'-' || c == b'.' || c == b'e' || c == b'E') - { - return f64::NAN; - } - t.parse::().unwrap_or(f64::NAN) -} - -/// 解析指定进制的无符号整数;全部字符必须是有效数字,否则返回 NaN。 -/// -/// 累积在 f64 中:2/8/16 是 2 的幂,逐位乘加即为正确舍入结果;十进制不适用。 -fn parse_radix_int(s: &str, radix: u32) -> f64 { - if s.is_empty() { - return f64::NAN; - } - let mut v = 0.0f64; - for c in s.chars() { - match c.to_digit(radix) { - Some(d) => v = v * radix as f64 + d as f64, - None => return f64::NAN, - } - } - v -} - -/// 把 f64 格式化为 ECMA-262 Number::toString 的字符串(含 NaN/±Infinity 专名)。 -/// -/// 直接调用 [`write_number_into`] 写入 32 字节预分配的缓冲区(ryu 输出上界 -/// ~24 字节 + 负号),单次分配无扩容。 -/// -/// # 边界与前提 -/// - NaN → "NaN",±∞ → "±Infinity",±0 → "0" -/// - 32 字节容量恒够:最长输出 `-1.7976931348623157e+308`(25 字节)。 -pub fn js_number_to_string(d: f64) -> String { - let mut out = String::with_capacity(32); - write_number_into(d, &mut out); - out -} - -/// 把 f64 格式化为 ECMA-262 Number::toString 的文本,追加到 `out`。 -/// -/// 对有限非零数,先经 ryu 最短表示取出有效数字与十进制指数(`format_finite` -/// 输出形如 `"123.45"` / `"1e21"` / `"1000000000000000.0"`),再按规范分段 -/// 重建:指数 `-6 < n <= 21` 时用定点表示,其余用科学计数法 `d.ddde±e`。 -/// -/// # 步骤 -/// 1. NaN/±∞/±0 直接写静态串;负数先写 `-`。 -/// 2. 整数 double 快路径:`fract()==0` 且 `|d| < 2^53` 时整数直写(i64 itoa), -/// 跳过 ryu 与重建。 -/// 3. 含 `e` 时按字节解析有效数字与指数(ryu 输出全 ASCII,指数不带 `+`), -/// 有效数字收栈上数组后按四段规则一次写齐。 -/// 4. 不含 `e` 的定点形式仅需去掉整数末尾的 `.0` 后缀。 -/// -/// # 边界与前提 -/// - 整数快路径上限取 2^53 而非 1e21:2^53 内整数在 f64 中精确且十进制有效 -/// 位数 ≤ 16,整数直写与最短表示一致;[2^53, 1e21) 的整数 double 精确值 -/// 可能与规范最短表示分歧(如 2^55 精确值 36028797018963968,规范输出 -/// 36028797018963970),故该区间保留 ryu 路径。1e21/1e22 等自然被排除。 -/// - ryu 输出全 ASCII 且指数无 `+`;有效数字 ≤ 17 位,栈缓冲 24 字节恒够。 -/// -/// # 副作用 -/// - 追加到 `out`,不新建中间字符串、不做堆分配(`out` 扩容除外)。 -pub fn write_number_into(d: f64, out: &mut String) { - if d.is_nan() { - out.push_str("NaN"); - return; - } - if d.is_infinite() { - out.push_str(if d.is_sign_positive() { "Infinity" } else { "-Infinity" }); - return; - } - if d == 0.0 { - out.push('0'); - return; - } - let neg = d.is_sign_negative(); - let abs = d.abs(); - // 有限非零负数先追加负号(追加语义:插到当前数值之前,不扰动 out 已有前缀)。 - if neg { - out.push('-'); - } - // 整数 double 快路径:无小数部分且在 2^53 内(精确整数,直写与最短表示一致)。 - if abs.fract() == 0.0 && abs < 9_007_199_254_740_992.0 { - use std::fmt::Write; - let _ = write!(out, "{}", abs as i64); - return; - } - let mut buf = ryu::Buffer::new(); - let s = buf.format_finite(abs); - if let Some(e_pos) = s.find('e') { - // 有效数字收栈上数组(ryu 输出全 ASCII),跳过 '.' 逐字节拷贝。 - let mut digits = [0u8; 24]; - let mut k = 0usize; - for &b in s.as_bytes()[..e_pos].iter() { - if b != b'.' { - digits[k] = b; - k += 1; - } - } - debug_assert!(k <= 24, "ryu 有效数字不超过 24 字节"); - let exp: i32 = s[e_pos + 1..].parse().unwrap_or(0); - let n = exp + 1; - if n > -6 && n <= 21 { - // 定点:ryu 在此范围输出指数形式但 JS 要求十进制。 - if n <= 0 { - out.push_str("0."); - for _ in 0..(-n) { - out.push('0'); - } - out.push_str(std::str::from_utf8(&digits[..k]).expect("有效数字恒为 ASCII")); - } else if k as i32 <= n { - out.push_str(std::str::from_utf8(&digits[..k]).expect("有效数字恒为 ASCII")); - for _ in 0..(n - k as i32) { - out.push('0'); - } - } else { - out.push_str(std::str::from_utf8(&digits[..n as usize]).expect("有效数字恒为 ASCII")); - out.push('.'); - out.push_str(std::str::from_utf8(&digits[n as usize..k]).expect("有效数字恒为 ASCII")); - } - } else { - // 科学计数:首位 + (可选 "." + 剩余) + e[+/-]指数。指数 ≤ 3 位,write! 直写。 - out.push(digits[0] as char); - if k > 1 { - out.push('.'); - out.push_str(std::str::from_utf8(&digits[1..k]).expect("有效数字恒为 ASCII")); - } - out.push('e'); - if n > 1 { - out.push('+'); - } - use std::fmt::Write; - let _ = write!(out, "{}", n - 1); - } - } else if let Some(stripped) = s.strip_suffix(".0") { - out.push_str(stripped); - } else { - out.push_str(s); - } -} - -/// ToUint32(ECMA-262 §7.1.6):对数值取模 2^32。NaN/±0/Infinity 归零。 -pub fn to_uint32(val: JsValue) -> u32 { - let n = to_number(val); - if n == 0.0 || !n.is_finite() { - return 0; - } - n.trunc().rem_euclid(4_294_967_296.0) as u32 -} - -/// ToInt32(ECMA-262 §7.1.5):对数值取模 2^32 后按有符号 32 位解释。 -pub fn to_int32(val: JsValue) -> i32 { - let int = to_uint32(val); - if int > i32::MAX as u32 { - (int as i64 - 4_294_967_296i64) as i32 - } else { - int as i32 - } -} - -/// 把 `val` 的字符串表示追加到 `buf`,不分配中间临时 String。 -/// 用于热路径字符串拼接。 -pub fn push_to_string(val: JsValue, buf: &mut String) { - if val.is_int() { - use std::fmt::Write; - let _ = write!(buf, "{}", val.as_int()); - return; - } - if val.is_double() { - write_number_into(val.as_double(), buf); - return; - } - if val.is_bool() { - buf.push_str(if val.as_bool() { "true" } else { "false" }); - return; - } - if val.is_null() { - buf.push_str("null"); - return; - } - if val.is_undefined() { - buf.push_str("undefined"); - return; - } - if val.is_string() { - unsafe { buf.push_str(&string_data(val)) }; - return; - } - if val.is_bigint() { - use std::fmt::Write; - let _ = write!(buf, "{}", unsafe { bigint_data(val) }); - return; - } - if val.is_object() { - buf.push_str("[object]"); - } -} - -/// 把原始值转成字符串(ToString 的原始值路径)。 -/// -/// 数值走 ECMA-262 Number::toString 格式化;Object 在此返回占位符 `[object]`, -/// 完整路径见 [`to_string_full`]。 -pub fn to_string(val: JsValue) -> String { - if val.is_int() { - return val.as_int().to_string(); - } - if val.is_double() { - return js_number_to_string(val.as_double()); - } - if val.is_bool() { - return val.as_bool().to_string(); - } - if val.is_null() { - return "null".to_string(); - } - if val.is_undefined() { - return "undefined".to_string(); - } - if val.is_string() { - return unsafe { string_data(val) }.to_string(); - } - if val.is_bigint() { - return bigint_to_string(unsafe { bigint_data(val) }); - } - if val.is_object() { - return "[object]".to_string(); - } - String::new() -} - -/// 把任意值转为 UTF-16 单元序列(`ToPrimitive(string hint)` 的单元口径): -/// 字符串值直接借出单元(Flat 惰性编码、rope 扁平化);Symbol 返回 TypeError -/// 错误;其余值经 ToString 原始值路径后 UTF-16 编码。 -pub fn to_units_full(val: JsValue, host: &mut H) -> Result, String> { - if val.is_string() { - return Ok(host.string_units(val).into_owned()); - } - if val.is_symbol() { - return Err(host.error_message_text("TypeError", "Cannot convert a Symbol value to a string")); - } - let prim = to_primitive(val, ToPrimitiveHint::String, host)?; - if prim.is_string() { - return Ok(host.string_units(prim).into_owned()); - } - // 非字符串原始值(数字等):先格式化再编码。 - let s = to_string(prim); - Ok(s.encode_utf16().collect()) -} - -/// 把值追加其 UTF-16 单元到 `buf`:字符串值直接扩单元序列,非字符串值经 -/// ToString 后 UTF-16 编码。字符串拼接的单元通道路径(不可 lossy)。 -pub fn push_units_to(val: JsValue, buf: &mut Vec) { - if val.is_string() { - // SAFETY: val 为字符串值,借用即时消费。 - let s = unsafe { &*val.as_string_ptr() }; - buf.extend_from_slice(s.units().as_ref()); - return; - } - let s = to_string(val); - buf.extend(s.encode_utf16()); -} - -/// ToBoolean(ECMA-262 §7.1.2):falsy 值仅限 undefined/null/false/±0/NaN/空串,其余为 true; -/// [[IsHTMLDDA]] 宿主对象按 undefined 处理(B.3.4)。 -pub fn to_boolean(val: JsValue) -> bool { - match val.js_type() { - JsType::Undefined | JsType::Null => false, - JsType::Bool => val.as_bool(), - JsType::Int => val.as_int() != 0, - JsType::Double => { - let d = val.as_double(); - // ±0 与 NaN 均为 falsy(IEEE 中 0.0 == -0.0,符号位无需单独判断)。 - !(d == 0.0 || d.is_nan()) - } - JsType::String => !unsafe { (*val.as_string_ptr()).is_empty() }, - JsType::BigInt => !unsafe { bigint_data(val) }.is_zero(), - JsType::Object => { - // SAFETY: Object 型 JsValue 的载荷位是存活对象的裸指针,借用即时消费。 - let obj = unsafe { &*val.as_js_object_ptr() }; - !obj.is_html_dda_obj() - } - JsType::Symbol => true, - } -} - -/// IsLooselyEqual(x, y) — ECMA-262 §7.2.15(`==`)。 -/// -/// 同类型委托严格相等;null 与 undefined 互等;BigInt/Number/String 两两 -/// 转数值比较;Boolean 操作数先 ToNumber;原始值 vs Object 经 ToPrimitive -/// 强制转换后重入(Symbol 与 Object 同样触发,规范步骤 10-11)。对象路径 -/// 可能调用用户 `valueOf` / `toString` / `@@toPrimitive`,因此需要 `VmHost` -/// 参数与 `Result`(回调抛出的 TypeError 以 `Err` 传播)。 -pub fn abstract_eq(lhs: JsValue, rhs: JsValue, host: &mut H) -> Result { - let tl = lhs.js_type(); - let tr = rhs.js_type(); - // [[IsHTMLDDA]] 宿主对象:宽松相等一律按 undefined 处理(B.3.4), - // 重入 undefined 口径(undefined/null 互等、与其余类型不等)。 - if tl == JsType::Object { - // SAFETY: Object 型 JsValue 的载荷位是存活对象的裸指针,借用即时消费。 - if unsafe { &*lhs.as_js_object_ptr() }.is_html_dda_obj() { - return abstract_eq(JsValue::undefined(), rhs, host); - } - } - if tr == JsType::Object { - // SAFETY: 同上。 - if unsafe { &*rhs.as_js_object_ptr() }.is_html_dda_obj() { - return abstract_eq(lhs, JsValue::undefined(), host); - } - } - // 同类型 → 严格相等。 - if tl == tr { - return Ok(strict_equality(lhs, rhs)); - } - match (tl, tr) { - // null 与 undefined 互等。 - (JsType::Null, JsType::Undefined) | (JsType::Undefined, JsType::Null) => Ok(true), - // Number 混合表示(int vs double):同属 ECMAScript Number,按严格相等。 - (JsType::Int | JsType::Double, JsType::Int | JsType::Double) => Ok(strict_equality(lhs, rhs)), - // BigInt 与 Number:转 f64 比较(精度内精确;超出精度近似)。 - (JsType::BigInt, JsType::Int | JsType::Double) => { - Ok(bigint_to_f64(unsafe { bigint_data(lhs) }) == to_number(rhs)) - } - (JsType::Int | JsType::Double, JsType::BigInt) => { - Ok(to_number(lhs) == bigint_to_f64(unsafe { bigint_data(rhs) })) - } - // BigInt 与 String:字符串解析为数字后比较(lossy 文本口径,见 to_number)。 - (JsType::BigInt, JsType::String) => { - let r = parse_js_number(&unsafe { string_data(rhs) }); - Ok(bigint_to_f64(unsafe { bigint_data(lhs) }) == r) - } - (JsType::String, JsType::BigInt) => { - let l = parse_js_number(&unsafe { string_data(lhs) }); - Ok(l == bigint_to_f64(unsafe { bigint_data(rhs) })) - } - // Number 与 String:字符串经 ToNumber 后按严格数值比较。 - (JsType::Int | JsType::Double, JsType::String) | (JsType::String, JsType::Int | JsType::Double) => { - Ok(strict_double_eq(to_number(lhs), to_number(rhs))) - } - // Boolean 操作数:先 ToNumber 再重入。int(0/1) 轻量编码,省 float - // 装箱与 to_number 链;int 属于 Number,重入后各数值分支全部认。 - (JsType::Bool, _) => abstract_eq(JsValue::int(lhs.as_bool() as i32), rhs, host), - (_, JsType::Bool) => abstract_eq(lhs, JsValue::int(rhs.as_bool() as i32), host), - // Object 与原始值(Number/String/BigInt/Symbol):ToPrimitive 后重入。 - (JsType::Object, JsType::Int | JsType::Double | JsType::String | JsType::BigInt | JsType::Symbol) => { - let prim = to_primitive(lhs, ToPrimitiveHint::Default, host)?; - abstract_eq(prim, rhs, host) - } - (JsType::Int | JsType::Double | JsType::String | JsType::BigInt | JsType::Symbol, JsType::Object) => { - let prim = to_primitive(rhs, ToPrimitiveHint::Default, host)?; - abstract_eq(lhs, prim, host) - } - // 其余组合不相等(含 null/undefined 与其它类型、Object/Object 已由同类型分支处理)。 - _ => Ok(false), - } -} - -fn strict_double_eq(a: f64, b: f64) -> bool { - if a.is_nan() || b.is_nan() { - return false; - } - a == b -} - -/// Relational Comparison(`<`,ECMA-262 §7.2.13)的原始值版本。 -/// -/// 双字符串按字典序;否则转数值比较,任一侧为 NaN 时返回 `None`(表示比较未定义,调用方据此处理 `<`/`>`)。 -pub fn relational_compare(lhs: JsValue, rhs: JsValue) -> Option { - if lhs.is_string() && rhs.is_string() { - // SAFETY: 两侧均为字符串值,借用即时消费。 - let l = unsafe { &*lhs.as_string_ptr() }; - let r = unsafe { &*rhs.as_string_ptr() }; - if l.is_flat() && r.is_flat() { - // 双 Flat:文本比较与旧路径逐位一致。 - return Some(l.as_str() < r.as_str()); - } - // 单元口径:码元字典序即规范序,Flat 编码与 rope 扁平化统一走单元通道。 - return Some(l.units() < r.units()); - } - if lhs.is_bigint() && rhs.is_bigint() { - let l = unsafe { bigint_data(lhs) }; - let r = unsafe { bigint_data(rhs) }; - return Some(l < r); - } - // BigInt 与 Number 混合:Number 为整数且落在 i128 范围时转 i128 精确比较, - // 否则(非整数/超出范围/NaN/±Infinity)转 f64 比较(NaN 结果为未定义)。 - if lhs.is_bigint() && (rhs.is_int() || rhs.is_double()) { - if rhs.is_double() && rhs.as_double().is_nan() { - return None; - } - let l = unsafe { bigint_data(lhs) }; - return Some(bigint_cmp_number(l, rhs) == Ordering::Less); - } - if (lhs.is_int() || lhs.is_double()) && rhs.is_bigint() { - if lhs.is_double() && lhs.as_double().is_nan() { - return None; - } - let r = unsafe { bigint_data(rhs) }; - // lhs(Number) < rhs(BigInt) ⇔ 反向比较(lhs 作为"number 侧")。 - return Some(number_cmp_bigint(lhs, r) == Ordering::Less); - } - // BigInt 与 String/Boolean 混合:转 Number 后比较。 - if lhs.is_bigint() || rhs.is_bigint() { - let l = to_number(lhs); - let r = to_number(rhs); - if l.is_nan() || r.is_nan() { - return None; - } - return l.partial_cmp(&r).map(|o| o.is_lt()); - } - let l = to_number(lhs); - let r = to_number(rhs); - if l.is_nan() || r.is_nan() { - return None; - } - l.partial_cmp(&r).map(|o| o.is_lt()) -} - -/// 拼接两个字符串,按已知总长度预分配容量以避免重复扩容。 -pub fn string_concat(lhs: &str, rhs: &str) -> String { - let mut s = String::with_capacity(lhs.len() + rhs.len()); - s.push_str(lhs); - s.push_str(rhs); - s -} - -fn to_f64(val: JsValue) -> f64 { - if val.is_int() { - val.as_int() as f64 - } else if val.is_double() { - val.as_double() - } else { - f64::NAN - } -} - -/// BigInt 与 Number 的序比较:Number 为整数且落于 i128 范围时精确比较, -/// 否则退化为 f64 近似比较(非有限/超出 i128 范围时)。 -fn bigint_cmp_number(big: &num_bigint::BigInt, num: JsValue) -> Ordering { - number_cmp_bigint(num, big).reverse() -} - -/// Number 与 BigInt 的序比较(`num` 与 `big` 的 `<`/`=`/`>`)。 -fn number_cmp_bigint(num: JsValue, big: &num_bigint::BigInt) -> Ordering { - if num.is_int() { - return (num.as_int() as i128).cmp(&big.to_i128().unwrap_or(if big.sign() == num_bigint::Sign::Minus { - i128::MIN - } else { - i128::MAX - })); - } - let d = num.as_double(); - if d.is_nan() { - return Ordering::Greater; - } - if d.is_infinite() { - return if d.is_sign_positive() { Ordering::Greater } else { Ordering::Less }; - } - let truncated = d.trunc(); - if truncated != d || truncated.abs() > i128::MAX as f64 { - return (d).partial_cmp(&bigint_to_f64(big)).unwrap_or(Ordering::Equal); - } - (truncated as i128).cmp(&big.to_i128().unwrap_or(if big.sign() == num_bigint::Sign::Minus { - i128::MIN - } else { - i128::MAX - })) -} - -/// SameValue(x, y)(ECMA-262 §7.2.9):与 `===` 的区别在于 NaN 视为相等、+0/-0 视为不同。 -pub fn same_value(lhs: JsValue, rhs: JsValue) -> bool { - match (lhs.js_type(), rhs.js_type()) { - (JsType::Double, JsType::Double) => { - let a = lhs.as_double(); - let b = rhs.as_double(); - if a.is_nan() && b.is_nan() { - return true; - } - if a == 0.0 && b == 0.0 { - return a.is_sign_negative() == b.is_sign_negative(); - } - a == b - } - (JsType::Int, JsType::Int) => lhs.as_int() == rhs.as_int(), - // Number 混合表示(int vs double):SameValue 的 ±0/NaN 特判原样保留。 - (JsType::Int | JsType::Double, JsType::Int | JsType::Double) => { - let a = to_f64(lhs); - let b = to_f64(rhs); - if a.is_nan() && b.is_nan() { - return true; - } - if a == 0.0 && b == 0.0 { - return a.is_sign_negative() == b.is_sign_negative(); - } - a == b - } - (JsType::Bool, JsType::Bool) => lhs.as_bool() == rhs.as_bool(), - (JsType::Null, JsType::Null) => true, - (JsType::Undefined, JsType::Undefined) => true, - (JsType::String, JsType::String) => string_value_eq(lhs, rhs), - (JsType::Object, JsType::Object) => lhs.as_ptr() == rhs.as_ptr(), - (JsType::Symbol, JsType::Symbol) => lhs.as_symbol_index() == rhs.as_symbol_index(), - // SAFETY: bigint 指针由 JsValue::bigint 构造,指向 VM 登记的存活 box。 - (JsType::BigInt, JsType::BigInt) => unsafe { bigint_data(lhs) == bigint_data(rhs) }, - _ => false, - } -} - -/// ToIntegerOrInfinity(argument) — ECMA-262 §7.1.4. -pub fn to_integer_or_infinity(val: JsValue) -> f64 { - let n = to_number(val); - if n.is_nan() || n == 0.0 { - 0.0 - } else if n.is_infinite() { - n - } else { - n.trunc() - } -} - -/// ToLength(argument) — ECMA-262 §7.1.20. -pub fn to_length(val: JsValue) -> u64 { - let n = to_number(val); - 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 -} - -/// SameValueZero(x, y) — ECMA-262 §7.2.11. -pub fn same_value_zero(lhs: JsValue, rhs: JsValue) -> bool { - match (lhs.js_type(), rhs.js_type()) { - (JsType::Int, JsType::Int) => lhs.as_int() == rhs.as_int(), - (JsType::Double, JsType::Double) => { - let a = lhs.as_double(); - let b = rhs.as_double(); - if a.is_nan() && b.is_nan() { - return true; - } - a == b - } - // Number 混合表示:NaN 视为相等,+0/-0 相等(Rust f64 中 +0 == -0)。 - (JsType::Int | JsType::Double, JsType::Int | JsType::Double) => { - let a = to_f64(lhs); - let b = to_f64(rhs); - if a.is_nan() && b.is_nan() { - return true; - } - a == b - } - _ => same_value(lhs, rhs), - } -} - -/// Strict Equality Comparison(`===`,ECMA-262 §7.2.14)。 -/// -/// 类型不同直接为 false;同类型下 Number(int/double 两种表示)按数值 -/// 比较(NaN 恒 false、+0/-0 相等,含 int/double 混合——`0 === -0` 为 true), -/// Object 按指针、其余按值比较。 -pub fn strict_equality(lhs: JsValue, rhs: JsValue) -> bool { - match (lhs.js_type(), rhs.js_type()) { - (JsType::Int, JsType::Int) => lhs.as_int() == rhs.as_int(), - (JsType::Double, JsType::Double) => strict_double_eq(lhs.as_double(), rhs.as_double()), - // Number 跨 int/double 表示:`42 === 42.0`、`0 === -0` 均按数值语义。 - (JsType::Int | JsType::Double, JsType::Int | JsType::Double) => strict_double_eq(to_f64(lhs), to_f64(rhs)), - (JsType::Bool, JsType::Bool) => lhs.as_bool() == rhs.as_bool(), - (JsType::Null, JsType::Null) => true, - (JsType::Undefined, JsType::Undefined) => true, - (JsType::String, JsType::String) => string_value_eq(lhs, rhs), - (JsType::Object, JsType::Object) => lhs.as_ptr() == rhs.as_ptr(), - (JsType::Symbol, JsType::Symbol) => lhs.as_symbol_index() == rhs.as_symbol_index(), - // SAFETY: bigint 指针由 JsValue::bigint 构造,指向 VM 登记的存活 box。 - (JsType::BigInt, JsType::BigInt) => unsafe { bigint_data(lhs) == bigint_data(rhs) }, - _ => false, - } -} - -/// ToObject(ECMA-262 §7.1.13):null/undefined 抛 TypeError,其余原始值包装为对应包装对象。 -/// -/// 包装对象按类型选择原型(String/Number/Boolean 原型或默认 Object 原型),原始值存入 hash props 槽。 -pub fn to_object(val: JsValue, host: &mut H) -> Result { - if val.is_object() { - return Ok(val); - } - if val.is_null() || val.is_undefined() { - return Err(host.error_message_text("TypeError", "Cannot convert null or undefined to object")); - } - let world = host.session().builtin_world(); - let (proto_ptr, type_tag) = if val.is_string() { - (P::as_ptr(&world.string_proto) as *mut JsObject, JsObject::OBJ_TYPE_STRING_OBJ) - } else if val.is_int() || val.is_double() { - (P::as_ptr(&world.number_proto) as *mut JsObject, JsObject::OBJ_TYPE_NUMBER_OBJ) - } else if val.is_bool() { - (P::as_ptr(&world.boolean_proto) as *mut JsObject, JsObject::OBJ_TYPE_BOOLEAN_OBJ) - } else if val.is_bigint() { - (P::as_ptr(&world.bigint_proto) as *mut JsObject, JsObject::OBJ_TYPE_PLAIN) - } else if val.is_symbol() { - (P::as_ptr(&world.symbol_proto) as *mut JsObject, JsObject::OBJ_TYPE_SYMBOL_OBJ) - } else { - (P::as_ptr(&world.object_proto) as *mut JsObject, JsObject::OBJ_TYPE_PLAIN) - }; - let proto_val = JsValue::from_js_object(proto_ptr); - let obj = host.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, proto_val)); - let obj_val = JsValue::from_js_object(obj); - let obj_ref = unsafe { &mut *obj }; - obj_ref.type_tag = type_tag; - obj_ref.ensure_hash_props().push(val); - obj_ref.set_prop_count(1); - Ok(obj_val) -} - -/// Hint for the abstract ToPrimitive operation (ECMA-262 §7.1.1). -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum ToPrimitiveHint { - Default, - String, - Number, -} - -impl ToPrimitiveHint { - /// OrdinaryToPrimitive 方法名顺序提示:`Default`/`Number` → "number",`String` → "string"。 - pub fn as_str(self) -> &'static str { - match self { - ToPrimitiveHint::String => "string", - ToPrimitiveHint::Default | ToPrimitiveHint::Number => "number", - } - } -} - -/// ToPrimitive(input, hint) per ECMA-262 §7.1.1. -/// -/// 原始值原样通过。对象经 VM 的 `coerce_primitive_bounded` 强制转换——先查询 -/// `obj[Symbol.toPrimitive]`,否则按 hint 顺序运行 OrdinaryToPrimitive -/// (valueOf/toString)。对象路径集中在单处,避免在此重复 OrdinaryToPrimitive。 -pub fn to_primitive(val: JsValue, hint: ToPrimitiveHint, host: &mut H) -> Result { - if !val.is_object() { - return Ok(val); - } - host.coerce_primitive_bounded(val, hint == ToPrimitiveHint::String) -} - -/// 带完整对象强制转换的 ToNumber(input):对象经 ToPrimitive(number hint) -/// 处理;Symbol 值按规范抛 TypeError。 -pub fn to_number_full(val: JsValue, host: &mut H) -> Result { - let primitive = to_primitive(val, ToPrimitiveHint::Number, host)?; - if primitive.is_symbol() { - return Err(host.error_message_text("TypeError", "Cannot convert a Symbol value to a number")); - } - Ok(to_number(primitive)) -} - -/// 带完整对象强制转换的 ToString(input):对象经 ToPrimitive(string hint)处理; -/// Symbol 值按规范(§7.1.17)抛 TypeError。 -pub fn to_string_full(val: JsValue, host: &mut H) -> Result { - let primitive = to_primitive(val, ToPrimitiveHint::String, host)?; - if primitive.is_symbol() { - return Err(host.error_message_text("TypeError", "Cannot convert a Symbol value to a string")); - } - Ok(to_string(primitive)) -} - -/// ToPrimitive(string hint) 完整转换并返回字符串*值*:字符串值原样返回(载荷 -/// 形态保留,非 Flat 不 lossy);Symbol 返回 TypeError;其余值经 ToString 后 -/// 以新字符串值返回(非 Flat 源的转换结果落良形 Flat)。 -pub fn to_string_value_full(val: JsValue, host: &mut H) -> Result { - if val.is_string() { - return Ok(val); - } - let primitive = to_primitive(val, ToPrimitiveHint::String, host)?; - if primitive.is_symbol() { - return Err(host.error_message_text("TypeError", "Cannot convert a Symbol value to a string")); - } - if primitive.is_string() { - return Ok(primitive); - } - Ok(host.new_string_owned(to_string(primitive))) -} - -/// 带完整对象强制转换的 ToBigInt(input)(§7.1.14):BigInt 原样;Boolean → 0/1; -/// String 走 StringToBigInt;Number/Symbol/undefined/null → TypeError;对象经 -/// ToPrimitive(default hint)后递归处理。 -pub fn to_bigint_full(val: JsValue, host: &mut H) -> Result { - let primitive = to_primitive(val, ToPrimitiveHint::Default, host)?; - if primitive.is_bigint() { - return Ok(primitive); - } - if primitive.is_bool() { - let v = if primitive.as_bool() { 1 } else { 0 }; - return Ok(host.new_bigint(num_bigint::BigInt::from(v))); - } - if primitive.is_int() || primitive.is_double() { - return Err(host.error_message_text("TypeError", "Cannot convert a Number value to a BigInt")); - } - if primitive.is_string() { - return string_to_bigint_full(host, &to_string(primitive)); - } - // undefined / null / symbol。 - Err(host.error_message_text("TypeError", "Cannot convert value to a BigInt")) -} - -/// StringToBigInt(§7.1.14 步骤):去首尾空白,可选 +/- 号与 0x/0o/0b 前缀; -/// 空串 → 0n;非法 → SyntaxError。 -fn string_to_bigint_full(host: &mut H, s: &str) -> Result { - let trimmed = s.trim(); - if trimmed.is_empty() { - return Ok(host.new_bigint(num_bigint::BigInt::from(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")属于非法语法。 - let (radix, digits) = if let Some(hex) = rest.strip_prefix("0x").or_else(|| rest.strip_prefix("0X")) { - if neg { - return Err( - host.error_message_text("SyntaxError", "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( - host.error_message_text("SyntaxError", "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( - host.error_message_text("SyntaxError", "Cannot convert string to BigInt: invalid integer literal") - ); - } - (2u32, bin) - } else { - (10u32, rest) - }; - if digits.is_empty() { - return Err(host.error_message_text("SyntaxError", "Cannot convert string to 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") - })?; - Ok(host.new_bigint(if neg { -m } else { m })) -} - -/// `String()` 构造器的字符串转换(§21.1.1.1):Symbol 值(及本引擎以空对象表示的 -/// well-known symbol)返回描述串 `Symbol(desc)`;其余走完整 ToString。 -pub fn to_string_for_string_constructor(val: JsValue, host: &mut H) -> Result { - if val.is_object() { - if let Some(id) = well_known_symbol_id(host, val.as_js_object_ptr()) { - if let Some(name) = well_known_symbol_name(id) { - return Ok(format!("Symbol({name})")); - } - } - } - let primitive = to_primitive(val, ToPrimitiveHint::String, host)?; - if primitive.is_symbol() { - let desc = host.symbol_description(primitive.as_symbol_index()).unwrap_or(""); - return Ok(format!("Symbol({desc})")); - } - Ok(to_string(primitive)) -} - -/// Well-known symbols are stored as empty objects in the builtin world; map an object -/// pointer back to its well-known symbol id (0..WELL_KNOWN_SYMBOL_COUNT) if it is one. -pub fn well_known_symbol_id(host: &H, ptr: *mut JsObject) -> Option { - if ptr.is_null() { - return None; - } - let world = host.session().builtin_world(); - if std::ptr::eq(ptr, world.sym_iterator.as_ptr()) { - Some(0) - } else if std::ptr::eq(ptr, world.sym_match.as_ptr()) { - Some(1) - } else if std::ptr::eq(ptr, world.sym_replace.as_ptr()) { - Some(2) - } else if std::ptr::eq(ptr, world.sym_search.as_ptr()) { - Some(3) - } else if std::ptr::eq(ptr, world.sym_split.as_ptr()) { - Some(4) - } else if std::ptr::eq(ptr, world.sym_to_primitive.as_ptr()) { - Some(5) - } else if std::ptr::eq(ptr, world.sym_has_instance.as_ptr()) { - Some(6) - } else if std::ptr::eq(ptr, world.sym_match_all.as_ptr()) { - Some(7) - } else if std::ptr::eq(ptr, world.sym_async_iterator.as_ptr()) { - Some(8) - } else if std::ptr::eq(ptr, world.sym_to_string_tag.as_ptr()) { - Some(9) - } else if std::ptr::eq(ptr, world.sym_species.as_ptr()) { - Some(10) - } else if std::ptr::eq(ptr, world.sym_async_dispose.as_ptr()) { - Some(11) - } else if std::ptr::eq(ptr, world.sym_dispose.as_ptr()) { - Some(12) - } else { - None - } -} - -/// Descriptive name of a well-known symbol id, e.g. `Symbol.toStringTag` for id 9. -pub fn well_known_symbol_name(id: u32) -> Option<&'static str> { - Some(match id { - 0 => "Symbol.iterator", - 1 => "Symbol.match", - 2 => "Symbol.replace", - 3 => "Symbol.search", - 4 => "Symbol.split", - 5 => "Symbol.toPrimitive", - 6 => "Symbol.hasInstance", - 7 => "Symbol.matchAll", - 8 => "Symbol.asyncIterator", - 9 => "Symbol.toStringTag", - 10 => "Symbol.species", - 11 => "Symbol.asyncDispose", - 12 => "Symbol.dispose", - _ => return None, - }) -} - #[cfg(test)] mod tests { use super::*; @@ -1083,23 +132,4 @@ mod tests { assert_eq!(buf, expected, "非空前缀 + double {d} 的追加语义破坏,实际 {buf}"); } } - - #[test] - fn parse_js_number_rules() { - assert_eq!(parse_js_number(""), 0.0); - assert_eq!(parse_js_number(" "), 0.0); - assert_eq!(parse_js_number("0xa"), 10.0); - assert_eq!(parse_js_number("0X1f"), 31.0); - assert_eq!(parse_js_number("0b101"), 5.0); - assert_eq!(parse_js_number("0o17"), 15.0); - assert_eq!(parse_js_number("Infinity"), f64::INFINITY); - assert_eq!(parse_js_number("-Infinity"), f64::NEG_INFINITY); - assert!(parse_js_number("INFINITY").is_nan()); - assert!(parse_js_number("infinity").is_nan()); - assert!(parse_js_number("0x1g").is_nan()); - assert!(parse_js_number("-0x1").is_nan()); - assert_eq!(parse_js_number("1.5e3"), 1500.0); - assert_eq!(parse_js_number("-0"), -0.0); - assert!(parse_js_number("1abc").is_nan()); - } }