1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 06:48:12 +00:00

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.
This commit is contained in:
Matthew Olsson 2020-05-07 17:09:00 -07:00 committed by Andreas Kling
parent cc01933840
commit 532d4bc0ab
6 changed files with 126 additions and 82 deletions

View file

@ -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;

View file

@ -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);
}

View file

@ -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)

View file

@ -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,61 +348,6 @@ 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)
{
if (rhs.type() != lhs.type())
return Value(false);
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());
case Value::Type::String:
return Value(lhs.as_string().string() == rhs.as_string().string());
case Value::Type::Boolean:
return Value(lhs.as_bool() == rhs.as_bool());
case Value::Type::Object:
return Value(&lhs.as_object() == &rhs.as_object());
}
ASSERT_NOT_REACHED();
}
Value 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.is_object())
return eq(interpreter, lhs.as_object().to_primitive(), rhs);
if (rhs.is_object())
return eq(interpreter, lhs, rhs.as_object().to_primitive());
if (lhs.is_number() || rhs.is_number())
return Value(lhs.to_number().as_double() == rhs.to_number().as_double());
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());
return Value(false);
}
Value in(Interpreter& interpreter, Value lhs, Value rhs)
{
if (!rhs.is_object())
@ -428,4 +373,107 @@ 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();
case Value::Type::Undefined:
case Value::Type::Null:
return true;
case Value::Type::String:
return lhs.as_string().string() == rhs.as_string().string();
case Value::Type::Boolean:
return lhs.as_bool() == rhs.as_bool();
case Value::Type::Object:
return &lhs.as_object() == &rhs.as_object();
default:
ASSERT_NOT_REACHED();
}
}
bool strict_eq(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 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;
}
return same_value_non_numeric(interpreter, lhs, rhs);
}
bool abstract_eq(Interpreter& interpreter, Value lhs, Value rhs)
{
if (lhs.type() == rhs.type())
return strict_eq(interpreter, lhs, rhs);
if ((lhs.is_undefined() || lhs.is_null()) && (rhs.is_undefined() || rhs.is_null()))
return true;
if (lhs.is_number() && rhs.is_string())
return abstract_eq(interpreter, lhs, rhs.to_number());
if (lhs.is_string() && rhs.is_number())
return abstract_eq(interpreter, lhs.to_number(), rhs);
if (lhs.is_boolean())
return abstract_eq(interpreter, lhs.to_number(), rhs);
if (rhs.is_boolean())
return abstract_eq(interpreter, lhs, rhs.to_number());
if ((lhs.is_string() || lhs.is_number()) && rhs.is_object())
return abstract_eq(interpreter, lhs, rhs.to_primitive(interpreter));
if (lhs.is_object() && (rhs.is_string() || rhs.is_number()))
return abstract_eq(interpreter, lhs.to_primitive(interpreter), rhs);
return false;
}
}

View file

@ -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&);
}

View file

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