diff options
author | Matthew Olsson <matthewcolsson@gmail.com> | 2020-05-07 17:09:00 -0700 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-05-08 09:49:20 +0200 |
commit | 532d4bc0abf4f345aed153bb795bb7f7509bee5a (patch) | |
tree | 813ff2572fd97a4b14be00fe19668a71f98b6cf8 /Libraries/LibJS/Runtime/Value.cpp | |
parent | cc01933840d8c8e71f1162a92c8b6758bf30ebd3 (diff) | |
download | serenity-532d4bc0abf4f345aed153bb795bb7f7509bee5a.zip |
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.
Diffstat (limited to 'Libraries/LibJS/Runtime/Value.cpp')
-rw-r--r-- | Libraries/LibJS/Runtime/Value.cpp | 160 |
1 files changed, 104 insertions, 56 deletions
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,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; } } |