mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
refactor: 重写 abstract_eq 为 ES §7.2.15 11 步算法
This commit is contained in:
@@ -254,52 +254,79 @@ pub fn to_boolean(val: JsValue) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn abstract_eq(lhs: JsValue, rhs: JsValue) -> bool {
|
||||
if lhs.is_null() && rhs.is_undefined() {
|
||||
/// Whether two values share the same ECMAScript language type. Number treats
|
||||
/// int- and double-tagged values as one type (both are Number).
|
||||
fn same_type(a: JsValue, b: JsValue) -> bool {
|
||||
if a.is_string() && b.is_string() {
|
||||
return true;
|
||||
}
|
||||
if lhs.is_undefined() && rhs.is_null() {
|
||||
if (a.is_int() || a.is_double()) && (b.is_int() || b.is_double()) {
|
||||
return true;
|
||||
}
|
||||
if lhs.is_string() && rhs.is_string() {
|
||||
return string_value_eq(lhs, rhs);
|
||||
}
|
||||
if lhs.is_int() && rhs.is_int() {
|
||||
return lhs.as_int() == rhs.as_int();
|
||||
}
|
||||
if lhs.is_double() && rhs.is_double() {
|
||||
return strict_double_eq(lhs.as_double(), rhs.as_double());
|
||||
}
|
||||
if lhs.is_bool() && rhs.is_bool() {
|
||||
return lhs.as_bool() == rhs.as_bool();
|
||||
}
|
||||
if lhs.is_null() && rhs.is_null() {
|
||||
if a.is_bool() && b.is_bool() {
|
||||
return true;
|
||||
}
|
||||
if lhs.is_undefined() && rhs.is_undefined() {
|
||||
if a.is_null() && b.is_null() {
|
||||
return true;
|
||||
}
|
||||
if (lhs.is_int() || lhs.is_double()) && (rhs.is_int() || rhs.is_double()) {
|
||||
return strict_double_eq(to_number(lhs), to_number(rhs));
|
||||
if a.is_undefined() && b.is_undefined() {
|
||||
return true;
|
||||
}
|
||||
if (lhs.is_int() || lhs.is_double()) && rhs.is_bool() {
|
||||
return to_number(lhs) == to_number(rhs);
|
||||
if a.is_object() && b.is_object() {
|
||||
return true;
|
||||
}
|
||||
if lhs.is_bool() && (rhs.is_int() || rhs.is_double()) {
|
||||
return to_number(lhs) == to_number(rhs);
|
||||
}
|
||||
if lhs.is_bool() || rhs.is_bool() {
|
||||
return to_number(lhs) == to_number(rhs);
|
||||
}
|
||||
if lhs.is_null() || rhs.is_null() {
|
||||
return false;
|
||||
}
|
||||
if lhs.is_object() || rhs.is_object() {
|
||||
return lhs.is_object() && rhs.is_object() && lhs.as_ptr() == rhs.as_ptr();
|
||||
if a.is_symbol() && b.is_symbol() {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// IsLooselyEqual(x, y) — ECMA-262 §7.2.15 (`==`).
|
||||
///
|
||||
/// BigInt steps are omitted (the engine prunes BigInt). Object operands are
|
||||
/// coerced via ToPrimitive, which may invoke user `valueOf` / `toString` /
|
||||
/// `@@toPrimitive`; hence the `VmHost` parameter and the `Result` (a thrown
|
||||
/// TypeError from those callbacks propagates as `Err`). Note: `Object == Symbol`
|
||||
/// does NOT trigger ToPrimitive (spec steps 11/12 cover only Number/String) and
|
||||
/// falls through to `false`.
|
||||
pub fn abstract_eq<H: VmHost>(lhs: JsValue, rhs: JsValue, host: &mut H) -> Result<bool, String> {
|
||||
// Step 1: same type -> strict equality.
|
||||
if same_type(lhs, rhs) {
|
||||
return Ok(strict_equality(lhs, rhs));
|
||||
}
|
||||
// Steps 2-3: null <-> undefined.
|
||||
if (lhs.is_null() && rhs.is_undefined()) || (lhs.is_undefined() && rhs.is_null()) {
|
||||
return Ok(true);
|
||||
}
|
||||
// Steps 5-6: Number <-> String.
|
||||
if (lhs.is_int() || lhs.is_double()) && rhs.is_string() {
|
||||
return Ok(strict_double_eq(to_number(lhs), to_number(rhs)));
|
||||
}
|
||||
if lhs.is_string() && (rhs.is_int() || rhs.is_double()) {
|
||||
return Ok(strict_double_eq(to_number(lhs), to_number(rhs)));
|
||||
}
|
||||
// Step 9: x is Boolean -> compare ToNumber(x).
|
||||
if lhs.is_bool() {
|
||||
return abstract_eq(JsValue::float(to_number(lhs)), rhs, host);
|
||||
}
|
||||
// Step 10: y is Boolean -> compare ToNumber(y).
|
||||
if rhs.is_bool() {
|
||||
return abstract_eq(lhs, JsValue::float(to_number(rhs)), host);
|
||||
}
|
||||
// Step 11: x is Number/String, y is Object -> ToPrimitive(y).
|
||||
if (lhs.is_int() || lhs.is_double() || lhs.is_string()) && rhs.is_object() {
|
||||
let prim = to_primitive(rhs, ToPrimitiveHint::Default, host)?;
|
||||
return abstract_eq(lhs, prim, host);
|
||||
}
|
||||
// Step 12: x is Object, y is Number/String -> ToPrimitive(x).
|
||||
if lhs.is_object() && (rhs.is_int() || rhs.is_double() || rhs.is_string()) {
|
||||
let prim = to_primitive(lhs, ToPrimitiveHint::Default, host)?;
|
||||
return abstract_eq(prim, rhs, host);
|
||||
}
|
||||
// Step 14: otherwise not equal.
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn strict_eq(lhs: JsValue, rhs: JsValue) -> bool {
|
||||
if lhs.is_int() && rhs.is_int() {
|
||||
return lhs.as_int() == rhs.as_int();
|
||||
|
||||
@@ -2,9 +2,10 @@ use std::sync::Arc;
|
||||
|
||||
use oxide_kernel::builtin::ArrayMethods;
|
||||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
|
||||
use crate::bind_constructor;
|
||||
use crate::bindings::apply_binding_table;
|
||||
|
||||
pub fn bind_array(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
|
||||
let _array_methods = ArrayMethods {
|
||||
@@ -46,6 +47,20 @@ pub fn bind_array(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
core.shape_forge().as_ref(),
|
||||
);
|
||||
|
||||
let array_proto_ptr = session.builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let array_proto = unsafe { &mut *array_proto_ptr };
|
||||
apply_binding_table(
|
||||
session.builtin_world(),
|
||||
array_proto,
|
||||
core,
|
||||
&[("toString", oxide_builtins::array::array_to_string::<crate::vm::Vm> as *const (), 0)],
|
||||
);
|
||||
// Built-in prototype methods are non-enumerable (otherwise they leak into for-in).
|
||||
let si = core.perm_interner().intern("toString").0;
|
||||
if let Some(pos) = core.shape_forge().lookup_position(array_proto.shape_id(), si) {
|
||||
array_proto.set_data_meta(pos, PropAttributes::new(true, false, true));
|
||||
}
|
||||
|
||||
let ctor_ptr = session.builtin_world().array_constructor.as_ptr() as *mut JsObject;
|
||||
bind_constructor!(core, global, "Array", ctor_ptr, oxide_builtins::array::array_constructor::<crate::vm::Vm>, 1, hash: true);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ use std::sync::Arc;
|
||||
|
||||
use oxide_kernel::builtin::ObjectMethods;
|
||||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
|
||||
use crate::bind_constructor;
|
||||
use crate::bindings::apply_binding_table;
|
||||
|
||||
pub fn bind_object(core: &Arc<KernelCore>, session: &KernelSession, global: &mut JsObject) {
|
||||
let methods = ObjectMethods {
|
||||
@@ -36,6 +37,29 @@ pub fn bind_object(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
.builtin_world()
|
||||
.bind_object_methods(&methods, core.perm_interner().as_ref(), core.shape_forge().as_ref());
|
||||
|
||||
let object_proto_ptr = session.builtin_world().object_proto.as_ptr() as *mut JsObject;
|
||||
let object_proto = unsafe { &mut *object_proto_ptr };
|
||||
apply_binding_table(
|
||||
session.builtin_world(),
|
||||
object_proto,
|
||||
core,
|
||||
&[
|
||||
(
|
||||
"toString",
|
||||
oxide_builtins::object::object_proto_to_string::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
("valueOf", oxide_builtins::object::object_proto_value_of::<crate::vm::Vm> as *const (), 0),
|
||||
],
|
||||
);
|
||||
// Built-in prototype methods are non-enumerable (otherwise they leak into for-in).
|
||||
for name in ["toString", "valueOf"] {
|
||||
let si = core.perm_interner().intern(name).0;
|
||||
if let Some(pos) = core.shape_forge().lookup_position(object_proto.shape_id(), si) {
|
||||
object_proto.set_data_meta(pos, PropAttributes::new(true, false, true));
|
||||
}
|
||||
}
|
||||
|
||||
let ctor_ptr = session.builtin_world().object_constructor.as_ptr() as *mut JsObject;
|
||||
bind_constructor!(
|
||||
core,
|
||||
|
||||
@@ -5,69 +5,83 @@ use oxide_types::value::JsValue;
|
||||
|
||||
impl Vm {
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_eq(&mut self, rd: usize, a: usize, b: usize) {
|
||||
pub(crate) fn dispatch_eq(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
vm_trace!("EQ rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]);
|
||||
let eq = coercion::abstract_eq(self.regs[a], self.regs[b]);
|
||||
let lhs = self.regs[a];
|
||||
let rhs = self.regs[b];
|
||||
let eq = coercion::abstract_eq(lhs, rhs, self)?;
|
||||
self.regs[rd] = JsValue::bool(eq);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_neq(&mut self, rd: usize, a: usize, b: usize) {
|
||||
pub(crate) fn dispatch_neq(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
vm_trace!("NEQ rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]);
|
||||
let ne = !coercion::abstract_eq(self.regs[a], self.regs[b]);
|
||||
let lhs = self.regs[a];
|
||||
let rhs = self.regs[b];
|
||||
let ne = !coercion::abstract_eq(lhs, rhs, self)?;
|
||||
self.regs[rd] = JsValue::bool(ne);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_lt(&mut self, rd: usize, a: usize, b: usize) {
|
||||
pub(crate) fn dispatch_lt(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
vm_trace!("LT rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]);
|
||||
let rel = coercion::relational_compare(self.regs[a], self.regs[b]);
|
||||
match rel {
|
||||
let va = self.coerce_primitive_bounded(self.regs[a], false)?;
|
||||
let vb = self.coerce_primitive_bounded(self.regs[b], false)?;
|
||||
match coercion::relational_compare(va, vb) {
|
||||
Some(r) => self.regs[rd] = JsValue::bool(r),
|
||||
None => {
|
||||
vm_trace!("LT incomparable r{}={:?} r{}={:?}", a, self.regs[a], b, self.regs[b]);
|
||||
self.regs[rd] = JsValue::bool(false);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_gt(&mut self, rd: usize, a: usize, b: usize) {
|
||||
pub(crate) fn dispatch_gt(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
vm_trace!("GT rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]);
|
||||
let rel = coercion::relational_compare(self.regs[b], self.regs[a]);
|
||||
match rel {
|
||||
let va = self.coerce_primitive_bounded(self.regs[a], false)?;
|
||||
let vb = self.coerce_primitive_bounded(self.regs[b], false)?;
|
||||
match coercion::relational_compare(vb, va) {
|
||||
Some(r) => self.regs[rd] = JsValue::bool(r),
|
||||
None => {
|
||||
vm_trace!("GT incomparable r{}={:?} r{}={:?}", a, self.regs[a], b, self.regs[b]);
|
||||
self.regs[rd] = JsValue::bool(false);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_lte(&mut self, rd: usize, a: usize, b: usize) {
|
||||
pub(crate) fn dispatch_lte(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
vm_trace!("LTE rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]);
|
||||
let rel = coercion::relational_compare(self.regs[b], self.regs[a]);
|
||||
match rel {
|
||||
let va = self.coerce_primitive_bounded(self.regs[a], false)?;
|
||||
let vb = self.coerce_primitive_bounded(self.regs[b], false)?;
|
||||
match coercion::relational_compare(vb, va) {
|
||||
Some(r) => self.regs[rd] = JsValue::bool(!r),
|
||||
None => {
|
||||
vm_trace!("LTE incomparable r{}={:?} r{}={:?}", a, self.regs[a], b, self.regs[b]);
|
||||
self.regs[rd] = JsValue::bool(false);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn dispatch_gte(&mut self, rd: usize, a: usize, b: usize) {
|
||||
pub(crate) fn dispatch_gte(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
|
||||
vm_trace!("GTE rd={} r{}={:?} r{}={:?}", rd, a, self.regs[a], b, self.regs[b]);
|
||||
let rel = coercion::relational_compare(self.regs[a], self.regs[b]);
|
||||
match rel {
|
||||
let va = self.coerce_primitive_bounded(self.regs[a], false)?;
|
||||
let vb = self.coerce_primitive_bounded(self.regs[b], false)?;
|
||||
match coercion::relational_compare(va, vb) {
|
||||
Some(r) => self.regs[rd] = JsValue::bool(!r),
|
||||
None => {
|
||||
vm_trace!("GTE incomparable r{}={:?} r{}={:?}", a, self.regs[a], b, self.regs[b]);
|
||||
self.regs[rd] = JsValue::bool(false);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
||||
@@ -830,27 +830,27 @@ impl Vm {
|
||||
}
|
||||
|
||||
OpCode::EQ => {
|
||||
self.dispatch_eq(rd, a, b);
|
||||
self.dispatch_eq(rd, a, b)?;
|
||||
}
|
||||
|
||||
OpCode::NEQ => {
|
||||
self.dispatch_neq(rd, a, b);
|
||||
self.dispatch_neq(rd, a, b)?;
|
||||
}
|
||||
|
||||
OpCode::LT => {
|
||||
self.dispatch_lt(rd, a, b);
|
||||
self.dispatch_lt(rd, a, b)?;
|
||||
}
|
||||
|
||||
OpCode::GT => {
|
||||
self.dispatch_gt(rd, a, b);
|
||||
self.dispatch_gt(rd, a, b)?;
|
||||
}
|
||||
|
||||
OpCode::LTE => {
|
||||
self.dispatch_lte(rd, a, b);
|
||||
self.dispatch_lte(rd, a, b)?;
|
||||
}
|
||||
|
||||
OpCode::GTE => {
|
||||
self.dispatch_gte(rd, a, b);
|
||||
self.dispatch_gte(rd, a, b)?;
|
||||
}
|
||||
|
||||
OpCode::STRICT_EQ => {
|
||||
|
||||
Reference in New Issue
Block a user