Prechádzať zdrojové kódy

LibJS: Spec-compliant equality comparisons

The ECMAScript spec defines multiple equality operations which are used
all over the spec; this patch introduces them. Of course, the two
primary equality operations are AbtractEquals ('==') and StrictEquals
('==='), which have been renamed to 'abstract_eq' and 'strict_eq' in
this patch.

In support of the two operations mentioned above, the following have
also been added: SameValue, SameValueZero, and SameValueNonNumeric.
These are important to have, because they are used elsewhere in the spec
aside from the two primary equality comparisons.
Matthew Olsson 5 rokov pred
rodič
commit
532d4bc0ab

+ 5 - 6
Libraries/LibJS/AST.cpp

@@ -41,7 +41,6 @@
 #include <LibJS/Runtime/ScriptFunction.h>
 #include <LibJS/Runtime/Shape.h>
 #include <LibJS/Runtime/StringObject.h>
-#include <LibJS/Runtime/Value.h>
 #include <stdio.h>
 
 namespace JS {
@@ -345,13 +344,13 @@ Value BinaryExpression::execute(Interpreter& interpreter) const
     case BinaryOp::Exponentiation:
         return exp(interpreter, lhs_result, rhs_result);
     case BinaryOp::TypedEquals:
-        return typed_eq(interpreter, lhs_result, rhs_result);
+        return Value(strict_eq(interpreter, lhs_result, rhs_result));
     case BinaryOp::TypedInequals:
-        return Value(!typed_eq(interpreter, lhs_result, rhs_result).as_bool());
+        return Value(!strict_eq(interpreter, lhs_result, rhs_result));
     case BinaryOp::AbstractEquals:
-        return eq(interpreter, lhs_result, rhs_result);
+        return Value(abstract_eq(interpreter, lhs_result, rhs_result));
     case BinaryOp::AbstractInequals:
-        return Value(!eq(interpreter, lhs_result, rhs_result).as_bool());
+        return Value(!abstract_eq(interpreter, lhs_result, rhs_result));
     case BinaryOp::GreaterThan:
         return greater_than(interpreter, lhs_result, rhs_result);
     case BinaryOp::GreaterThanEquals:
@@ -1444,7 +1443,7 @@ Value SwitchStatement::execute(Interpreter& interpreter) const
             auto test_result = switch_case.test()->execute(interpreter);
             if (interpreter.exception())
                 return {};
-            if (!eq(interpreter, discriminant_result, test_result).to_boolean())
+            if (!strict_eq(interpreter, discriminant_result, test_result))
                 continue;
         }
         falling_through = true;

+ 3 - 3
Libraries/LibJS/Runtime/ArrayPrototype.cpp

@@ -344,7 +344,7 @@ Value ArrayPrototype::index_of(Interpreter& interpreter)
     auto search_element = interpreter.argument(0);
     for (i32 i = from_index; i < array_size; ++i) {
         auto& element = array->elements().at(i);
-        if (typed_eq(interpreter, element, search_element).as_bool())
+        if (strict_eq(interpreter, element, search_element))
             return Value(i);
     }
 
@@ -398,7 +398,7 @@ Value ArrayPrototype::last_index_of(Interpreter& interpreter)
     auto search_element = interpreter.argument(0);
     for (i32 i = array_size - 1; i >= from_index; --i) {
         auto& element = array->elements().at(i);
-        if (typed_eq(interpreter, element, search_element).as_bool())
+        if (strict_eq(interpreter, element, search_element))
             return Value(i);
     }
 
@@ -432,7 +432,7 @@ Value ArrayPrototype::includes(Interpreter& interpreter)
     auto value_to_find = interpreter.argument(0);
     for (i32 i = from_index; i < array_size; ++i) {
         auto& element = array->elements().at(i);
-        if (typed_eq(interpreter, element, value_to_find).as_bool())
+        if (same_value_zero(interpreter, element, value_to_find))
             return Value(true);
     }
 

+ 1 - 12
Libraries/LibJS/Runtime/ObjectConstructor.cpp

@@ -131,18 +131,7 @@ Value ObjectConstructor::define_property(Interpreter& interpreter)
 
 Value ObjectConstructor::is(Interpreter& interpreter)
 {
-    auto value1 = interpreter.argument(0);
-    auto value2 = interpreter.argument(1);
-    if (value1.is_nan() && value2.is_nan())
-        return Value(true);
-    if (value1.is_number() && value1.as_double() == 0 && value2.is_number() && value2.as_double() == 0) {
-        if (value1.is_positive_zero() && value2.is_positive_zero())
-            return Value(true);
-        if (value1.is_negative_zero() && value2.is_negative_zero())
-            return Value(true);
-        return Value(false);
-    }
-    return typed_eq(interpreter, value1, value2);
+    return Value(same_value(interpreter, interpreter.argument(0), interpreter.argument(1)));
 }
 
 Value ObjectConstructor::keys(Interpreter& interpreter)

+ 104 - 56
Libraries/LibJS/Runtime/Value.cpp

@@ -156,12 +156,14 @@ Value Value::to_number() const
     case Type::Empty:
         ASSERT_NOT_REACHED();
         return {};
+    case Type::Undefined:
+        return js_nan();
+    case Type::Null:
+        return Value(0);
     case Type::Boolean:
         return Value(m_value.as_bool ? 1 : 0);
     case Type::Number:
         return Value(m_value.as_double);
-    case Type::Null:
-        return Value(0);
     case Type::String: {
         // FIXME: Trim whitespace beforehand
         auto& string = as_string().string();
@@ -179,8 +181,6 @@ Value Value::to_number() const
 
         return js_nan();
     }
-    case Type::Undefined:
-        return js_nan();
     case Type::Object:
         return m_value.as_object->to_primitive(Object::PreferredType::Number).to_number();
     }
@@ -348,84 +348,132 @@ Value exp(Interpreter&, Value lhs, Value rhs)
     return Value(pow(lhs.to_number().as_double(), rhs.to_number().as_double()));
 }
 
-Value typed_eq(Interpreter&, Value lhs, Value rhs)
+Value in(Interpreter& interpreter, Value lhs, Value rhs)
+{
+    if (!rhs.is_object())
+        return interpreter.throw_exception<TypeError>("'in' operator must be used on object");
+
+    return Value(!rhs.as_object().get(lhs.to_string()).is_empty());
+}
+
+Value instance_of(Interpreter&, Value lhs, Value rhs)
 {
-    if (rhs.type() != lhs.type())
+    if (!lhs.is_object() || !rhs.is_object())
+        return Value(false);
+
+    auto constructor_prototype_property = rhs.as_object().get("prototype");
+    if (!constructor_prototype_property.is_object())
         return Value(false);
 
+    return Value(lhs.as_object().has_prototype(&constructor_prototype_property.as_object()));
+}
+
+const LogStream& operator<<(const LogStream& stream, const Value& value)
+{
+    return stream << value.to_string();
+}
+
+bool same_value(Interpreter& interpreter, Value lhs, Value rhs)
+{
+    if (lhs.type() != rhs.type())
+        return false;
+
+    if (lhs.is_number()) {
+        if (lhs.is_nan() && rhs.is_nan())
+            return true;
+        if (lhs.is_positive_zero() && rhs.is_negative_zero())
+            return false;
+        if (lhs.is_negative_zero() && rhs.is_positive_zero())
+            return false;
+        return lhs.to_double() == rhs.to_double();
+    }
+
+    return same_value_non_numeric(interpreter, lhs, rhs);
+}
+
+bool same_value_zero(Interpreter& interpreter, Value lhs, Value rhs)
+{
+    if (lhs.type() != rhs.type())
+        return false;
+
+    if (lhs.is_number()) {
+        if (lhs.is_nan() && rhs.is_nan())
+            return true;
+        if ((lhs.is_positive_zero() || lhs.is_negative_zero()) && (rhs.is_positive_zero() || rhs.is_negative_zero()))
+            return true;
+        return lhs.to_double() == rhs.to_double();
+    }
+
+    return same_value_non_numeric(interpreter, lhs, rhs);
+}
+
+bool same_value_non_numeric(Interpreter&, Value lhs, Value rhs)
+{
+    ASSERT(!lhs.is_number());
+    ASSERT(lhs.type() == rhs.type());
+
     switch (lhs.type()) {
     case Value::Type::Empty:
         ASSERT_NOT_REACHED();
-        return {};
     case Value::Type::Undefined:
-        return Value(true);
     case Value::Type::Null:
-        return Value(true);
-    case Value::Type::Number:
-        return Value(lhs.as_double() == rhs.as_double());
+        return true;
     case Value::Type::String:
-        return Value(lhs.as_string().string() == rhs.as_string().string());
+        return lhs.as_string().string() == rhs.as_string().string();
     case Value::Type::Boolean:
-        return Value(lhs.as_bool() == rhs.as_bool());
+        return lhs.as_bool() == rhs.as_bool();
     case Value::Type::Object:
-        return Value(&lhs.as_object() == &rhs.as_object());
+        return &lhs.as_object() == &rhs.as_object();
+    default:
+        ASSERT_NOT_REACHED();
     }
-
-    ASSERT_NOT_REACHED();
 }
 
-Value eq(Interpreter& interpreter, Value lhs, Value rhs)
+bool strict_eq(Interpreter& interpreter, Value lhs, Value rhs)
 {
-    if (lhs.type() == rhs.type())
-        return typed_eq(interpreter, lhs, rhs);
-
-    if ((lhs.is_undefined() || lhs.is_null()) && (rhs.is_undefined() || rhs.is_null()))
-        return Value(true);
-
-    if (lhs.is_object() && rhs.is_boolean())
-        return eq(interpreter, lhs.as_object().to_primitive(), rhs.to_number());
-
-    if (lhs.is_boolean() && rhs.is_object())
-        return eq(interpreter, lhs.to_number(), rhs.as_object().to_primitive());
+    if (lhs.type() != rhs.type())
+        return false;
 
-    if (lhs.is_object())
-        return eq(interpreter, lhs.as_object().to_primitive(), rhs);
+    if (lhs.is_number()) {
+        if (lhs.is_nan() || rhs.is_nan())
+            return false;
+        if (lhs.to_double() == rhs.to_double())
+            return true;
+        if ((lhs.is_positive_zero() || lhs.is_negative_zero()) && (rhs.is_positive_zero() || rhs.is_negative_zero()))
+            return true;
+        return false;
+    }
 
-    if (rhs.is_object())
-        return eq(interpreter, lhs, rhs.as_object().to_primitive());
+    return same_value_non_numeric(interpreter, lhs, rhs);
+}
 
-    if (lhs.is_number() || rhs.is_number())
-        return Value(lhs.to_number().as_double() == rhs.to_number().as_double());
+bool abstract_eq(Interpreter& interpreter, Value lhs, Value rhs)
+{
+    if (lhs.type() == rhs.type())
+        return strict_eq(interpreter, lhs, rhs);
 
-    if ((lhs.is_string() && rhs.is_boolean()) || (lhs.is_string() && rhs.is_boolean()))
-        return Value(lhs.to_number().as_double() == rhs.to_number().as_double());
+    if ((lhs.is_undefined() || lhs.is_null()) && (rhs.is_undefined() || rhs.is_null()))
+        return true;
 
-    return Value(false);
-}
+    if (lhs.is_number() && rhs.is_string())
+        return abstract_eq(interpreter, lhs, rhs.to_number());
 
-Value in(Interpreter& interpreter, Value lhs, Value rhs)
-{
-    if (!rhs.is_object())
-        return interpreter.throw_exception<TypeError>("'in' operator must be used on object");
+    if (lhs.is_string() && rhs.is_number())
+        return abstract_eq(interpreter, lhs.to_number(), rhs);
 
-    return Value(!rhs.as_object().get(lhs.to_string()).is_empty());
-}
+    if (lhs.is_boolean())
+        return abstract_eq(interpreter, lhs.to_number(), rhs);
 
-Value instance_of(Interpreter&, Value lhs, Value rhs)
-{
-    if (!lhs.is_object() || !rhs.is_object())
-        return Value(false);
+    if (rhs.is_boolean())
+        return abstract_eq(interpreter, lhs, rhs.to_number());
 
-    auto constructor_prototype_property = rhs.as_object().get("prototype");
-    if (!constructor_prototype_property.is_object())
-        return Value(false);
+    if ((lhs.is_string() || lhs.is_number()) && rhs.is_object())
+        return abstract_eq(interpreter, lhs, rhs.to_primitive(interpreter));
 
-    return Value(lhs.as_object().has_prototype(&constructor_prototype_property.as_object()));
-}
+    if (lhs.is_object() && (rhs.is_string() || rhs.is_number()))
+        return abstract_eq(interpreter, lhs.to_primitive(interpreter), rhs);
 
-const LogStream& operator<<(const LogStream& stream, const Value& value)
-{
-    return stream << value.to_string();
+    return false;
 }
 
 }

+ 6 - 2
Libraries/LibJS/Runtime/Value.h

@@ -233,11 +233,15 @@ Value mul(Interpreter&, Value lhs, Value rhs);
 Value div(Interpreter&, Value lhs, Value rhs);
 Value mod(Interpreter&, Value lhs, Value rhs);
 Value exp(Interpreter&, Value lhs, Value rhs);
-Value eq(Interpreter&, Value lhs, Value rhs);
-Value typed_eq(Interpreter&, Value lhs, Value rhs);
 Value in(Interpreter&, Value lhs, Value rhs);
 Value instance_of(Interpreter&, Value lhs, Value rhs);
 
+bool abstract_eq(Interpreter&, Value lhs, Value rhs);
+bool strict_eq(Interpreter&, Value lhs, Value rhs);
+bool same_value(Interpreter&, Value lhs, Value rhs);
+bool same_value_zero(Interpreter&, Value lhs, Value rhs);
+bool same_value_non_numeric(Interpreter&, Value lhs, Value rhs);
+
 const LogStream& operator<<(const LogStream&, const Value&);
 
 }

+ 4 - 0
Libraries/LibJS/Tests/switch-basic.js

@@ -1,4 +1,8 @@
+load("test-common.js");
+
 switch (1 + 2) {
+case '3':
+    assertNotReached();
 case 3:
     console.log("PASS");
     break;