Procházet zdrojové kódy

LibJS: Add Value::to_{index,length,integer_or_infinity} abstract operations

We should pay more attention to using the well-defined abstract
operations from the spec rather than making up our own, often slightly
different rules. This is another step in that direction.
Linus Groh před 4 roky
rodič
revize
eaa85969c4

+ 1 - 0
Libraries/LibJS/Runtime/ErrorTypes.h

@@ -46,6 +46,7 @@
     M(InOperatorWithObject, "'in' operator must be used on an object")                                                                  \
     M(InOperatorWithObject, "'in' operator must be used on an object")                                                                  \
     M(InstanceOfOperatorBadPrototype, "'prototype' property of {} is not an object")                                                    \
     M(InstanceOfOperatorBadPrototype, "'prototype' property of {} is not an object")                                                    \
     M(InvalidAssignToConst, "Invalid assignment to const variable")                                                                     \
     M(InvalidAssignToConst, "Invalid assignment to const variable")                                                                     \
+    M(InvalidIndex, "Index must be a positive integer")                                                                                 \
     M(InvalidLeftHandAssignment, "Invalid left-hand side in assignment")                                                                \
     M(InvalidLeftHandAssignment, "Invalid left-hand side in assignment")                                                                \
     M(InvalidLength, "Invalid {} length")                                                                                               \
     M(InvalidLength, "Invalid {} length")                                                                                               \
     M(InvalidRadix, "Radix must be an integer no less than 2, and no greater than 36")                                                  \
     M(InvalidRadix, "Radix must be an integer no less than 2, and no greater than 36")                                                  \

+ 62 - 1
Libraries/LibJS/Runtime/Value.cpp

@@ -53,6 +53,9 @@
 
 
 namespace JS {
 namespace JS {
 
 
+// Used in various abstract operations to make it obvious when a non-optional return value must be discarded.
+static const double INVALID { 0 };
+
 static const Crypto::SignedBigInteger BIGINT_ZERO { 0 };
 static const Crypto::SignedBigInteger BIGINT_ZERO { 0 };
 
 
 static bool is_valid_bigint_value(String string)
 static bool is_valid_bigint_value(String string)
@@ -362,11 +365,12 @@ i32 Value::to_i32(GlobalObject& global_object) const
 
 
 size_t Value::to_size_t(GlobalObject& global_object) const
 size_t Value::to_size_t(GlobalObject& global_object) const
 {
 {
+    // FIXME: Replace uses of this function with to_length/to_index for correct behaviour and remove this eventually.
     if (is_empty())
     if (is_empty())
         return 0;
         return 0;
     auto number = to_number(global_object);
     auto number = to_number(global_object);
     if (global_object.vm().exception())
     if (global_object.vm().exception())
-        return 0;
+        return INVALID;
     if (number.is_nan())
     if (number.is_nan())
         return 0;
         return 0;
     if (number.as_double() <= 0)
     if (number.as_double() <= 0)
@@ -374,6 +378,63 @@ size_t Value::to_size_t(GlobalObject& global_object) const
     return number.as_size_t();
     return number.as_size_t();
 }
 }
 
 
+size_t Value::to_length(GlobalObject& global_object) const
+{
+    // 7.1.20 ToLength, https://tc39.es/ecma262/#sec-tolength
+
+    auto& vm = global_object.vm();
+
+    auto len = to_integer_or_infinity(global_object);
+    if (vm.exception())
+        return INVALID;
+    if (len <= 0)
+        return 0;
+    return min(len, MAX_ARRAY_LIKE_INDEX);
+}
+
+size_t Value::to_index(GlobalObject& global_object) const
+{
+    // 7.1.22 ToIndex, https://tc39.es/ecma262/#sec-toindex
+
+    auto& vm = global_object.vm();
+
+    if (is_undefined())
+        return 0;
+    auto integer_index = to_integer_or_infinity(global_object);
+    if (vm.exception())
+        return INVALID;
+    if (integer_index < 0) {
+        vm.throw_exception<RangeError>(global_object, ErrorType::InvalidIndex);
+        return INVALID;
+    }
+    auto index = Value(integer_index).to_length(global_object);
+    ASSERT(!vm.exception());
+    if (integer_index != index) {
+        vm.throw_exception<RangeError>(global_object, ErrorType::InvalidIndex);
+        return INVALID;
+    }
+    return index;
+}
+
+double Value::to_integer_or_infinity(GlobalObject& global_object) const
+{
+    // 7.1.5 ToIntegerOrInfinity, https://tc39.es/ecma262/#sec-tointegerorinfinity
+
+    auto& vm = global_object.vm();
+
+    auto number = to_number(global_object);
+    if (vm.exception())
+        return INVALID;
+    if (number.is_nan() || number.as_double() == 0)
+        return 0;
+    if (number.is_infinity())
+        return number.as_double();
+    auto integer = floor(abs(number.as_double()));
+    if (number.as_double() < 0)
+        integer = -integer;
+    return integer;
+}
+
 Value greater_than(GlobalObject& global_object, Value lhs, Value rhs)
 Value greater_than(GlobalObject& global_object, Value lhs, Value rhs)
 {
 {
     TriState relation = abstract_relation(global_object, false, lhs, rhs);
     TriState relation = abstract_relation(global_object, false, lhs, rhs);

+ 3 - 0
Libraries/LibJS/Runtime/Value.h

@@ -251,6 +251,9 @@ public:
     double to_double(GlobalObject&) const;
     double to_double(GlobalObject&) const;
     i32 to_i32(GlobalObject&) const;
     i32 to_i32(GlobalObject&) const;
     size_t to_size_t(GlobalObject&) const;
     size_t to_size_t(GlobalObject&) const;
+    size_t to_length(GlobalObject&) const;
+    size_t to_index(GlobalObject&) const;
+    double to_integer_or_infinity(GlobalObject&) const;
     bool to_boolean() const;
     bool to_boolean() const;
 
 
     String to_string_without_side_effects() const;
     String to_string_without_side_effects() const;