diff --git a/Libraries/LibJS/AST.cpp b/Libraries/LibJS/AST.cpp index c9538e61dd..58c797d648 100644 --- a/Libraries/LibJS/AST.cpp +++ b/Libraries/LibJS/AST.cpp @@ -41,7 +41,6 @@ #include #include #include -#include #include 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; diff --git a/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Libraries/LibJS/Runtime/ArrayPrototype.cpp index 9df20acea3..8a9fa55876 100644 --- a/Libraries/LibJS/Runtime/ArrayPrototype.cpp +++ b/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); } diff --git a/Libraries/LibJS/Runtime/ObjectConstructor.cpp b/Libraries/LibJS/Runtime/ObjectConstructor.cpp index e3f96fe4b7..a7700b0aee 100644 --- a/Libraries/LibJS/Runtime/ObjectConstructor.cpp +++ b/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) diff --git a/Libraries/LibJS/Runtime/Value.cpp b/Libraries/LibJS/Runtime/Value.cpp index a2f1cceec5..fbcab42581 100644 --- a/Libraries/LibJS/Runtime/Value.cpp +++ b/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,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; +} + } diff --git a/Libraries/LibJS/Runtime/Value.h b/Libraries/LibJS/Runtime/Value.h index 82c4e4478a..a57098ac32 100644 --- a/Libraries/LibJS/Runtime/Value.h +++ b/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&); } diff --git a/Libraries/LibJS/Tests/switch-basic.js b/Libraries/LibJS/Tests/switch-basic.js index 04bda00717..1fba80f9f0 100644 --- a/Libraries/LibJS/Tests/switch-basic.js +++ b/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;