From f19c4ab693527ddefa2f19af6f85e953bd68c445 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 15 Jul 2022 07:47:07 -0400 Subject: [PATCH] LibJS: Sort Array.prototype methods in spec order Makes it much easier to scroll through the file while comparing to the spec. Proposals are inserted alphabetically as that's where they will likely end up once merged into the main spec. --- .../LibJS/Runtime/ArrayPrototype.cpp | 2223 +++++++++-------- .../Libraries/LibJS/Runtime/ArrayPrototype.h | 54 +- 2 files changed, 1139 insertions(+), 1138 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp index 6d7e198348..d9b502fb65 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp @@ -39,45 +39,45 @@ void ArrayPrototype::initialize(GlobalObject& global_object) Array::initialize(global_object); u8 attr = Attribute::Writable | Attribute::Configurable; - define_native_function(vm.names.filter, filter, 1, attr); - define_native_function(vm.names.forEach, for_each, 1, attr); - define_native_function(vm.names.map, map, 1, attr); - define_native_function(vm.names.pop, pop, 0, attr); - define_native_function(vm.names.push, push, 1, attr); - define_native_function(vm.names.shift, shift, 0, attr); - define_native_function(vm.names.toString, to_string, 0, attr); - define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr); - define_native_function(vm.names.unshift, unshift, 1, attr); - define_native_function(vm.names.join, join, 1, attr); + define_native_function(vm.names.at, at, 1, attr); define_native_function(vm.names.concat, concat, 1, attr); - define_native_function(vm.names.slice, slice, 2, attr); - define_native_function(vm.names.indexOf, index_of, 1, attr); - define_native_function(vm.names.reduce, reduce, 1, attr); - define_native_function(vm.names.reduceRight, reduce_right, 1, attr); - define_native_function(vm.names.reverse, reverse, 0, attr); - define_native_function(vm.names.sort, sort, 1, attr); - define_native_function(vm.names.lastIndexOf, last_index_of, 1, attr); - define_native_function(vm.names.includes, includes, 1, attr); + define_native_function(vm.names.copyWithin, copy_within, 2, attr); + define_native_function(vm.names.entries, entries, 0, attr); + define_native_function(vm.names.every, every, 1, attr); + define_native_function(vm.names.fill, fill, 1, attr); + define_native_function(vm.names.filter, filter, 1, attr); define_native_function(vm.names.find, find, 1, attr); define_native_function(vm.names.findIndex, find_index, 1, attr); define_native_function(vm.names.findLast, find_last, 1, attr); define_native_function(vm.names.findLastIndex, find_last_index, 1, attr); - define_native_function(vm.names.some, some, 1, attr); - define_native_function(vm.names.every, every, 1, attr); - define_native_function(vm.names.splice, splice, 2, attr); - define_native_function(vm.names.fill, fill, 1, attr); - define_native_function(vm.names.values, values, 0, attr); define_native_function(vm.names.flat, flat, 0, attr); define_native_function(vm.names.flatMap, flat_map, 1, attr); - define_native_function(vm.names.at, at, 1, attr); - define_native_function(vm.names.keys, keys, 0, attr); - define_native_function(vm.names.entries, entries, 0, attr); - define_native_function(vm.names.copyWithin, copy_within, 2, attr); + define_native_function(vm.names.forEach, for_each, 1, attr); define_native_function(vm.names.group, group, 1, attr); define_native_function(vm.names.groupToMap, group_to_map, 1, attr); + define_native_function(vm.names.includes, includes, 1, attr); + define_native_function(vm.names.indexOf, index_of, 1, attr); + define_native_function(vm.names.join, join, 1, attr); + define_native_function(vm.names.keys, keys, 0, attr); + define_native_function(vm.names.lastIndexOf, last_index_of, 1, attr); + define_native_function(vm.names.map, map, 1, attr); + define_native_function(vm.names.pop, pop, 0, attr); + define_native_function(vm.names.push, push, 1, attr); + define_native_function(vm.names.reduce, reduce, 1, attr); + define_native_function(vm.names.reduceRight, reduce_right, 1, attr); + define_native_function(vm.names.reverse, reverse, 0, attr); + define_native_function(vm.names.shift, shift, 0, attr); + define_native_function(vm.names.slice, slice, 2, attr); + define_native_function(vm.names.some, some, 1, attr); + define_native_function(vm.names.sort, sort, 1, attr); + define_native_function(vm.names.splice, splice, 2, attr); + define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr); define_native_function(vm.names.toReversed, to_reversed, 0, attr); define_native_function(vm.names.toSorted, to_sorted, 1, attr); define_native_function(vm.names.toSpliced, to_spliced, 2, attr); + define_native_function(vm.names.toString, to_string, 0, attr); + define_native_function(vm.names.unshift, unshift, 1, attr); + define_native_function(vm.names.values, values, 0, attr); define_native_function(vm.names.with, with, 2, attr); // Use define_direct_property here instead of define_native_function so that @@ -149,345 +149,24 @@ static ThrowCompletionOr array_species_create(GlobalObject& global_obje return TRY(construct(global_object, constructor.as_function(), Value(length))); } -// 23.1.3.8 Array.prototype.filter ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.filter -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::filter) -{ - auto callback_function = vm.argument(0); - auto this_arg = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let len be ? LengthOfArrayLike(O). - auto length = TRY(length_of_array_like(global_object, *object)); - - // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - if (!callback_function.is_function()) - return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); - - // 4. Let A be ? ArraySpeciesCreate(O, 0). - auto* array = TRY(array_species_create(global_object, *object, 0)); - - // 5. Let k be 0. - size_t k = 0; - - // 6. Let to be 0. - size_t to = 0; - - // 7. Repeat, while k < len, - for (; k < length; ++k) { - // a. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; - - // b. Let kPresent be ? HasProperty(O, Pk). - auto k_present = TRY(object->has_property(property_key)); - - // c. If kPresent is true, then - if (k_present) { - // i. Let kValue be ? Get(O, Pk). - auto k_value = TRY(object->get(k)); - - // ii. Let selected be ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). - auto selected = TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(k), object)).to_boolean(); - - // iii. If selected is true, then - if (selected) { - // 1. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(to)), kValue). - TRY(array->create_data_property_or_throw(to, k_value)); - - // 2. Set to to to + 1. - ++to; - } - } - - // d. Set k to k + 1. - } - - // 8. Return A. - return array; -} - -// 23.1.3.13 Array.prototype.forEach ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.foreach -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::for_each) -{ - auto callback_function = vm.argument(0); - auto this_arg = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let len be ? LengthOfArrayLike(O). - auto length = TRY(length_of_array_like(global_object, *object)); - - // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - if (!callback_function.is_function()) - return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); - - // 4. Let k be 0. - // 5. Repeat, while k < len, - for (size_t k = 0; k < length; ++k) { - // a. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; - - // b. Let kPresent be ? HasProperty(O, Pk). - auto k_present = TRY(object->has_property(property_key)); - - // c. If kPresent is true, then - if (k_present) { - // i. Let kValue be ? Get(O, Pk). - auto k_value = TRY(object->get(property_key)); - - // ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). - TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(k), object)); - } - - // d. Set k to k + 1. - } - - // 6. Return undefined. - return js_undefined(); -} - -// 23.1.3.19 Array.prototype.map ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.map -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::map) -{ - auto callback_function = vm.argument(0); - auto this_arg = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let len be ? LengthOfArrayLike(O). - auto length = TRY(length_of_array_like(global_object, *object)); - - // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - if (!callback_function.is_function()) - return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); - - // 4. Let A be ? ArraySpeciesCreate(O, len). - auto* array = TRY(array_species_create(global_object, *object, length)); - - // 5. Let k be 0. - // 6. Repeat, while k < len, - for (size_t k = 0; k < length; ++k) { - // a. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; - - // b. Let kPresent be ? HasProperty(O, Pk). - auto k_present = TRY(object->has_property(property_key)); - - // c. If kPresent is true, then - if (k_present) { - // i. Let kValue be ? Get(O, Pk). - auto k_value = TRY(object->get(property_key)); - - // ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). - auto mapped_value = TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(k), object)); - - // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). - TRY(array->create_data_property_or_throw(property_key, mapped_value)); - } - - // d. Set k to k + 1. - } - - // 7. Return A. - return array; -} - -// 23.1.3.21 Array.prototype.push ( ...items ), https://tc39.es/ecma262/#sec-array.prototype.push -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::push) +// 23.1.3.1 Array.prototype.at ( index ), https://tc39.es/ecma262/#sec-array.prototype.at +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::at) { auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); auto length = TRY(length_of_array_like(global_object, *this_object)); - auto argument_count = vm.argument_count(); - auto new_length = length + argument_count; - if (new_length > MAX_ARRAY_LIKE_INDEX) - return vm.throw_completion(global_object, ErrorType::ArrayMaxSize); - for (size_t i = 0; i < argument_count; ++i) - TRY(this_object->set(length + i, vm.argument(i), Object::ShouldThrowExceptions::Yes)); - auto new_length_value = Value(new_length); - TRY(this_object->set(vm.names.length, new_length_value, Object::ShouldThrowExceptions::Yes)); - return new_length_value; -} - -// 23.1.3.32 Array.prototype.unshift ( ...items ), https://tc39.es/ecma262/#sec-array.prototype.unshift -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::unshift) -{ - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - auto length = TRY(length_of_array_like(global_object, *this_object)); - auto arg_count = vm.argument_count(); - size_t new_length = length + arg_count; - if (arg_count > 0) { - if (new_length > MAX_ARRAY_LIKE_INDEX) - return vm.throw_completion(global_object, ErrorType::ArrayMaxSize); - - for (size_t k = length; k > 0; --k) { - auto from = k - 1; - auto to = k + arg_count - 1; - - bool from_present = TRY(this_object->has_property(from)); - if (from_present) { - auto from_value = TRY(this_object->get(from)); - TRY(this_object->set(to, from_value, Object::ShouldThrowExceptions::Yes)); - } else { - TRY(this_object->delete_property_or_throw(to)); - } - } - - for (size_t j = 0; j < arg_count; j++) - TRY(this_object->set(j, vm.argument(j), Object::ShouldThrowExceptions::Yes)); - } - - TRY(this_object->set(vm.names.length, Value(new_length), Object::ShouldThrowExceptions::Yes)); - return Value(new_length); -} - -// 23.1.3.20 Array.prototype.pop ( ), https://tc39.es/ecma262/#sec-array.prototype.pop -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::pop) -{ - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - auto length = TRY(length_of_array_like(global_object, *this_object)); - if (length == 0) { - TRY(this_object->set(vm.names.length, Value(0), Object::ShouldThrowExceptions::Yes)); + auto relative_index = TRY(vm.argument(0).to_integer_or_infinity(global_object)); + if (Value(relative_index).is_infinity()) return js_undefined(); + Checked index { 0 }; + if (relative_index >= 0) { + index += relative_index; + } else { + index += length; + index -= -relative_index; } - auto index = length - 1; - auto element = TRY(this_object->get(index)); - TRY(this_object->delete_property_or_throw(index)); - TRY(this_object->set(vm.names.length, Value(index), Object::ShouldThrowExceptions::Yes)); - return element; -} - -// 23.1.3.25 Array.prototype.shift ( ), https://tc39.es/ecma262/#sec-array.prototype.shift -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::shift) -{ - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - auto length = TRY(length_of_array_like(global_object, *this_object)); - if (length == 0) { - TRY(this_object->set(vm.names.length, Value(0), Object::ShouldThrowExceptions::Yes)); + if (index.has_overflow() || index.value() >= length) return js_undefined(); - } - auto first = TRY(this_object->get(0)); - for (size_t k = 1; k < length; ++k) { - size_t from = k; - size_t to = k - 1; - bool from_present = TRY(this_object->has_property(from)); - if (from_present) { - auto from_value = TRY(this_object->get(from)); - TRY(this_object->set(to, from_value, Object::ShouldThrowExceptions::Yes)); - } else { - TRY(this_object->delete_property_or_throw(to)); - } - } - - TRY(this_object->delete_property_or_throw(length - 1)); - TRY(this_object->set(vm.names.length, Value(length - 1), Object::ShouldThrowExceptions::Yes)); - return first; -} - -// 23.1.3.31 Array.prototype.toString ( ), https://tc39.es/ecma262/#sec-array.prototype.tostring -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_string) -{ - // 1. Let array be ? ToObject(this value). - auto* array = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let func be ? Get(array, "join"). - auto func = TRY(array->get(vm.names.join)); - - // 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%. - if (!func.is_function()) - func = global_object.object_prototype_to_string_function(); - - // 4. Return ? Call(func, array). - return TRY(call(global_object, func.as_function(), array)); -} - -// 19.5.1 Array.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sup-array.prototype.tolocalestring -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_locale_string) -{ - auto locales = vm.argument(0); - auto options = vm.argument(1); - - // 1. Let array be ? ToObject(this value). - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - - if (s_array_join_seen_objects.contains(this_object)) - return js_string(vm, ""); - s_array_join_seen_objects.set(this_object); - ArmedScopeGuard unsee_object_guard = [&] { - s_array_join_seen_objects.remove(this_object); - }; - - // 2. Let len be ? ToLength(? Get(array, "length")). - auto length = TRY(length_of_array_like(global_object, *this_object)); - - // 3. Let separator be the implementation-defined list-separator String value appropriate for the host environment's current locale (such as ", "). - constexpr auto separator = ","sv; - - // 4. Let R be the empty String. - StringBuilder builder; - - // 5. Let k be 0. - // 6. Repeat, while k < len, - for (size_t i = 0; i < length; ++i) { - // a. If k > 0, then - if (i > 0) { - // i. Set R to the string-concatenation of R and separator. - builder.append(separator); - } - - // b. Let nextElement be ? Get(array, ! ToString(k)). - auto value = TRY(this_object->get(i)); - - // c. If nextElement is not undefined or null, then - if (!value.is_nullish()) { - // i. Let S be ? ToString(? Invoke(nextElement, "toLocaleString", « locales, options »)). - auto locale_string_result = TRY(value.invoke(global_object, vm.names.toLocaleString, locales, options)); - - // ii. Set R to the string-concatenation of R and S. - auto string = TRY(locale_string_result.to_string(global_object)); - builder.append(string); - } - - // d. Increase k by 1. - } - - // 7. Return R. - return js_string(vm, builder.to_string()); -} - -// 23.1.3.16 Array.prototype.join ( separator ), https://tc39.es/ecma262/#sec-array.prototype.join -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::join) -{ - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - - // This is not part of the spec, but all major engines do some kind of circular reference checks. - // FWIW: engine262, a "100% spec compliant" ECMA-262 impl, aborts with "too much recursion". - // Same applies to Array.prototype.toLocaleString(). - if (s_array_join_seen_objects.contains(this_object)) - return js_string(vm, ""); - s_array_join_seen_objects.set(this_object); - ArmedScopeGuard unsee_object_guard = [&] { - s_array_join_seen_objects.remove(this_object); - }; - - auto length = TRY(length_of_array_like(global_object, *this_object)); - String separator = ","; - if (!vm.argument(0).is_undefined()) - separator = TRY(vm.argument(0).to_string(global_object)); - StringBuilder builder; - for (size_t i = 0; i < length; ++i) { - if (i > 0) - builder.append(separator); - auto value = TRY(this_object->get(i)); - if (value.is_nullish()) - continue; - auto string = TRY(value.to_string(global_object)); - builder.append(string); - } - - return js_string(vm, builder.to_string()); + return TRY(this_object->get(index.value())); } // 23.1.3.2 Array.prototype.concat ( ...items ), https://tc39.es/ecma262/#sec-array.prototype.concat @@ -548,62 +227,685 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::concat) return Value(new_array); } -// 23.1.3.26 Array.prototype.slice ( start, end ), https://tc39.es/ecma262/#sec-array.prototype.slice -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::slice) +// 23.1.3.4 Array.prototype.copyWithin ( target, start [ , end ] ), https://tc39.es/ecma262/#sec-array.prototype.copywithin +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::copy_within) +{ + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + auto length = TRY(length_of_array_like(global_object, *this_object)); + + auto relative_target = TRY(vm.argument(0).to_integer_or_infinity(global_object)); + + double to; + if (relative_target < 0) + to = max(length + relative_target, 0.0); + else + to = min(relative_target, (double)length); + + auto relative_start = TRY(vm.argument(1).to_integer_or_infinity(global_object)); + + double from; + if (relative_start < 0) + from = max(length + relative_start, 0.0); + else + from = min(relative_start, (double)length); + + auto relative_end = vm.argument(2).is_undefined() ? length : TRY(vm.argument(2).to_integer_or_infinity(global_object)); + + double final; + if (relative_end < 0) + final = max(length + relative_end, 0.0); + else + final = min(relative_end, (double)length); + + double count = min(final - from, length - to); + + i32 direction = 1; + + if (from < to && to < from + count) { + direction = -1; + from = from + count - 1; + to = to + count - 1; + } + + if (count < 0) { + return this_object; + } + + size_t from_i = from; + size_t to_i = to; + size_t count_i = count; + + while (count_i > 0) { + auto from_present = TRY(this_object->has_property(from_i)); + + if (from_present) { + auto from_value = TRY(this_object->get(from_i)); + TRY(this_object->set(to_i, from_value, Object::ShouldThrowExceptions::Yes)); + } else { + TRY(this_object->delete_property_or_throw(to_i)); + } + + from_i += direction; + to_i += direction; + --count_i; + } + + return this_object; +} + +// 23.1.3.5 Array.prototype.entries ( ), https://tc39.es/ecma262/#sec-array.prototype.entries +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::entries) { auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - auto initial_length = TRY(length_of_array_like(global_object, *this_object)); + return ArrayIterator::create(global_object, this_object, Object::PropertyKind::KeyAndValue); +} - auto relative_start = TRY(vm.argument(0).to_integer_or_infinity(global_object)); +// 23.1.3.6 Array.prototype.every ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.every +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::every) +{ + auto callback_function = vm.argument(0); + auto this_arg = vm.argument(1); - double actual_start; + // 1. Let O be ? ToObject(this value). + auto* object = TRY(vm.this_value(global_object).to_object(global_object)); - if (Value(relative_start).is_negative_infinity()) - actual_start = 0.0; - else if (relative_start < 0.0) - actual_start = max((double)initial_length + relative_start, 0.0); - else - actual_start = min(relative_start, (double)initial_length); + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *object)); - double relative_end; + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); - if (vm.argument(1).is_undefined() || vm.argument(1).is_empty()) - relative_end = (double)initial_length; - else - relative_end = TRY(vm.argument(1).to_integer_or_infinity(global_object)); + // 4. Let k be 0. + // 5. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_key = PropertyKey { k }; - double final; + // b. Let kPresent be ? HasProperty(O, Pk). + auto k_present = TRY(object->has_property(property_key)); - if (Value(relative_end).is_negative_infinity()) - final = 0.0; - else if (relative_end < 0.0) - final = max((double)initial_length + relative_end, 0.0); - else - final = min(relative_end, (double)initial_length); + // c. If kPresent is true, then + if (k_present) { + // i. Let kValue be ? Get(O, Pk). + auto k_value = TRY(object->get(property_key)); - auto count = max(final - actual_start, 0.0); + // ii. Let testResult be ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + auto test_result = TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(k), object)).to_boolean(); - auto* new_array = TRY(array_species_create(global_object, *this_object, count)); - - size_t index = 0; - size_t k = actual_start; - - while (k < final) { - bool present = TRY(this_object->has_property(k)); - if (present) { - auto value = TRY(this_object->get(k)); - TRY(new_array->create_data_property_or_throw(index, value)); + // iii. If testResult is false, return false. + if (!test_result) + return Value(false); } - ++k; - ++index; + // d. Set k to k + 1. } - TRY(new_array->set(vm.names.length, Value(index), Object::ShouldThrowExceptions::Yes)); + // 6. Return true. + return Value(true); +} + +// 23.1.3.7 Array.prototype.fill ( value [ , start [ , end ] ] ), https://tc39.es/ecma262/#sec-array.prototype.fill +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::fill) +{ + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + + auto length = TRY(length_of_array_like(global_object, *this_object)); + + double relative_start = 0; + double relative_end = length; + + if (vm.argument_count() >= 2) { + relative_start = TRY(vm.argument(1).to_integer_or_infinity(global_object)); + if (Value(relative_start).is_negative_infinity()) + relative_start = 0; + } + + // If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + if (vm.argument_count() >= 3 && !vm.argument(2).is_undefined()) { + relative_end = TRY(vm.argument(2).to_integer_or_infinity(global_object)); + if (Value(relative_end).is_negative_infinity()) + relative_end = 0; + } + + u64 from, to; + + if (relative_start < 0) + from = max(length + relative_start, 0L); + else + from = min(relative_start, length); + + if (relative_end < 0) + to = max(length + relative_end, 0L); + else + to = min(relative_end, length); + + for (u64 i = from; i < to; i++) + TRY(this_object->set(i, vm.argument(0), Object::ShouldThrowExceptions::Yes)); + + return this_object; +} + +// 23.1.3.8 Array.prototype.filter ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.filter +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::filter) +{ + auto callback_function = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *object)); + + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); + + // 4. Let A be ? ArraySpeciesCreate(O, 0). + auto* array = TRY(array_species_create(global_object, *object, 0)); + + // 5. Let k be 0. + size_t k = 0; + + // 6. Let to be 0. + size_t to = 0; + + // 7. Repeat, while k < len, + for (; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_key = PropertyKey { k }; + + // b. Let kPresent be ? HasProperty(O, Pk). + auto k_present = TRY(object->has_property(property_key)); + + // c. If kPresent is true, then + if (k_present) { + // i. Let kValue be ? Get(O, Pk). + auto k_value = TRY(object->get(k)); + + // ii. Let selected be ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + auto selected = TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(k), object)).to_boolean(); + + // iii. If selected is true, then + if (selected) { + // 1. Perform ? CreateDataPropertyOrThrow(A, ! ToString(𝔽(to)), kValue). + TRY(array->create_data_property_or_throw(to, k_value)); + + // 2. Set to to to + 1. + ++to; + } + } + + // d. Set k to k + 1. + } + + // 8. Return A. + return array; +} + +// 23.1.3.9 Array.prototype.find ( predicate [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.find +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find) +{ + auto predicate = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *object)); + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (!predicate.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, predicate.to_string_without_side_effects()); + + // 4. Let k be 0. + // 5. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_key = PropertyKey { k }; + + // b. Let kValue be ? Get(O, Pk). + auto k_value = TRY(object->get(property_key)); + + // c. Let testResult be ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). + auto test_result = TRY(call(global_object, predicate.as_function(), this_arg, k_value, Value(k), object)).to_boolean(); + + // d. If testResult is true, return kValue. + if (test_result) + return k_value; + + // e. Set k to k + 1. + } + + // 6. Return undefined. + return js_undefined(); +} + +// 23.1.3.10 Array.prototype.findIndex ( predicate [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.findindex +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find_index) +{ + auto predicate = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *object)); + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (!predicate.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, predicate.to_string_without_side_effects()); + + // 4. Let k be 0. + // 5. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_key = PropertyKey { k }; + + // b. Let kValue be ? Get(O, Pk). + auto k_value = TRY(object->get(property_key)); + + // c. Let testResult be ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). + auto test_result = TRY(call(global_object, predicate.as_function(), this_arg, k_value, Value(k), object)).to_boolean(); + + // d. If testResult is true, return 𝔽(k). + if (test_result) + return Value(k); + + // e. Set k to k + 1. + } + + // 6. Return -1𝔽. + return Value(-1); +} + +// 1 Array.prototype.findLast ( predicate [ , thisArg ] ), https://tc39.es/proposal-array-find-from-last/#sec-array.prototype.findlast +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find_last) +{ + auto predicate = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *object)); + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (!predicate.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, predicate.to_string_without_side_effects()); + + // 4. Let k be len - 1. + // 5. Repeat, while k ≥ 0, + for (i64 k = static_cast(length) - 1; k >= 0; --k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_key = PropertyKey { k }; + + // b. Let kValue be ? Get(O, Pk). + auto k_value = TRY(object->get(property_key)); + + // c. Let testResult be ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). + auto test_result = TRY(call(global_object, predicate.as_function(), this_arg, k_value, Value((double)k), object)).to_boolean(); + + // d. If testResult is true, return kValue. + if (test_result) + return k_value; + + // e. Set k to k - 1. + } + + // 6. Return undefined. + return js_undefined(); +} + +// 2 Array.prototype.findLastIndex ( predicate [ , thisArg ] ), https://tc39.es/proposal-array-find-from-last/#sec-array.prototype.findlastindex +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find_last_index) +{ + auto predicate = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *object)); + + // 3. If IsCallable(predicate) is false, throw a TypeError exception. + if (!predicate.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, predicate.to_string_without_side_effects()); + + // 4. Let k be len - 1. + // 5. Repeat, while k ≥ 0, + for (i64 k = static_cast(length) - 1; k >= 0; --k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_key = PropertyKey { k }; + + // b. Let kValue be ? Get(O, Pk). + auto k_value = TRY(object->get(property_key)); + + // c. Let testResult be ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). + auto test_result = TRY(call(global_object, predicate.as_function(), this_arg, k_value, Value((double)k), object)).to_boolean(); + + // d. If testResult is true, return 𝔽(k). + if (test_result) + return Value((double)k); + + // e. Set k to k - 1. + } + + // 6. Return -1𝔽. + return Value(-1); +} + +// 23.1.3.11.1 FlattenIntoArray ( target, source, sourceLen, start, depth [ , mapperFunction [ , thisArg ] ] ), https://tc39.es/ecma262/#sec-flattenintoarray +static ThrowCompletionOr flatten_into_array(GlobalObject& global_object, Object& new_array, Object& array, size_t array_length, size_t target_index, double depth, FunctionObject* mapper_func = {}, Value this_arg = {}) +{ + VERIFY(!mapper_func || (!this_arg.is_empty() && depth == 1)); + auto& vm = global_object.vm(); + + for (size_t j = 0; j < array_length; ++j) { + auto value_exists = TRY(array.has_property(j)); + + if (!value_exists) + continue; + auto value = TRY(array.get(j)); + + if (mapper_func) + value = TRY(call(global_object, *mapper_func, this_arg, value, Value(j), &array)); + + if (depth > 0 && TRY(value.is_array(global_object))) { + if (vm.did_reach_stack_space_limit()) + return vm.throw_completion(global_object, ErrorType::CallStackSizeExceeded); + + auto length = TRY(length_of_array_like(global_object, value.as_object())); + target_index = TRY(flatten_into_array(global_object, new_array, value.as_object(), length, target_index, depth - 1)); + continue; + } + + if (target_index >= MAX_ARRAY_LIKE_INDEX) + return vm.throw_completion(global_object, ErrorType::InvalidIndex); + + TRY(new_array.create_data_property_or_throw(target_index, value)); + + ++target_index; + } + return target_index; +} + +// 23.1.3.11 Array.prototype.flat ( [ depth ] ), https://tc39.es/ecma262/#sec-array.prototype.flat +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::flat) +{ + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + + auto length = TRY(length_of_array_like(global_object, *this_object)); + + double depth = 1; + if (!vm.argument(0).is_undefined()) { + auto depth_num = TRY(vm.argument(0).to_integer_or_infinity(global_object)); + depth = max(depth_num, 0.0); + } + + auto* new_array = TRY(array_species_create(global_object, *this_object, 0)); + + TRY(flatten_into_array(global_object, *new_array, *this_object, length, 0, depth)); return new_array; } +// 23.1.3.12 Array.prototype.flatMap ( mapperFunction [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.flatmap +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::flat_map) +{ + auto mapper_function = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let sourceLen be ? LengthOfArrayLike(O). + auto source_length = TRY(length_of_array_like(global_object, *object)); + + // 3. If IsCallable(mapperFunction) is false, throw a TypeError exception. + if (!mapper_function.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, mapper_function.to_string_without_side_effects()); + + // 4. Let A be ? ArraySpeciesCreate(O, 0). + auto* array = TRY(array_species_create(global_object, *object, 0)); + + // 5. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg). + TRY(flatten_into_array(global_object, *array, *object, source_length, 0, 1, &mapper_function.as_function(), this_arg)); + + // 6. Return A. + return array; +} + +// 23.1.3.13 Array.prototype.forEach ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.foreach +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::for_each) +{ + auto callback_function = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *object)); + + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); + + // 4. Let k be 0. + // 5. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_key = PropertyKey { k }; + + // b. Let kPresent be ? HasProperty(O, Pk). + auto k_present = TRY(object->has_property(property_key)); + + // c. If kPresent is true, then + if (k_present) { + // i. Let kValue be ? Get(O, Pk). + auto k_value = TRY(object->get(property_key)); + + // ii. Perform ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). + TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(k), object)); + } + + // d. Set k to k + 1. + } + + // 6. Return undefined. + return js_undefined(); +} + +// 2.3 AddValueToKeyedGroup ( groups, key, value ), https://tc39.es/proposal-array-grouping/#sec-add-value-to-keyed-group +template +static void add_value_to_keyed_group(GlobalObject& global_object, GroupsType& groups, KeyType key, Value value) +{ + // 1. For each Record { [[Key]], [[Elements]] } g of groups, do + // a. If SameValue(g.[[Key]], key) is true, then + // NOTE: This is performed in KeyedGroupTraits::equals for groupToMap and Traits::equals for group. + auto existing_elements_iterator = groups.find(key); + if (existing_elements_iterator != groups.end()) { + // i. Assert: exactly one element of groups meets this criteria. + // NOTE: This is done on insertion into the hash map, as only `set` tells us if we overrode an entry. + + // ii. Append value as the last element of g.[[Elements]]. + existing_elements_iterator->value.append(value); + + // iii. Return unused. + return; + } + + // 2. Let group be the Record { [[Key]]: key, [[Elements]]: « value » }. + MarkedVector new_elements { global_object.heap() }; + new_elements.append(value); + + // 3. Append group as the last element of groups. + auto result = groups.set(key, move(new_elements)); + VERIFY(result == AK::HashSetResult::InsertedNewEntry); +} + +// 2.1 Array.prototype.group ( callbackfn [ , thisArg ] ), https://tc39.es/proposal-array-grouping/#sec-array.prototype.group +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::group) +{ + auto callback_function = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *this_object)); + + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); + + // 5. Let groups be a new empty List. + OrderedHashMap> groups; + + // 4. Let k be 0. + // 6. Repeat, while k < len + for (size_t index = 0; index < length; ++index) { + // a. Let Pk be ! ToString(𝔽(k)). + auto index_property = PropertyKey { index }; + + // b. Let kValue be ? Get(O, Pk). + auto k_value = TRY(this_object->get(index_property)); + + // c. Let propertyKey be ? ToPropertyKey(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + auto property_key_value = TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(index), this_object)); + auto property_key = TRY(property_key_value.to_property_key(global_object)); + + // d. Perform AddValueToKeyedGroup(groups, propertyKey, kValue). + add_value_to_keyed_group(global_object, groups, property_key, k_value); + + // e. Set k to k + 1. + } + + // 7. Let obj be OrdinaryObjectCreate(null). + auto* object = Object::create(global_object, nullptr); + + // 8. For each Record { [[Key]], [[Elements]] } g of groups, do + for (auto& group : groups) { + // a. Let elements be CreateArrayFromList(g.[[Elements]]). + auto* elements = Array::create_from(global_object, group.value); + + // b. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements). + MUST(object->create_data_property_or_throw(group.key, elements)); + } + + // 9. Return obj. + return object; +} + +// 2.2 Array.prototype.groupToMap ( callbackfn [ , thisArg ] ), https://tc39.es/proposal-array-grouping/#sec-array.prototype.grouptomap +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::group_to_map) +{ + auto callback_function = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *this_object)); + + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); + + struct KeyedGroupTraits : public Traits> { + static unsigned hash(Handle const& value_handle) + { + return ValueTraits::hash(value_handle.value()); + } + + static bool equals(Handle const& a, Handle const& b) + { + // AddValueToKeyedGroup uses SameValue on the keys on Step 1.a. + return same_value(a.value(), b.value()); + } + }; + + // 5. Let groups be a new empty List. + OrderedHashMap, MarkedVector, KeyedGroupTraits> groups; + + // 4. Let k be 0. + // 6. Repeat, while k < len + for (size_t index = 0; index < length; ++index) { + // a. Let Pk be ! ToString(𝔽(k)). + auto index_property = PropertyKey { index }; + + // b. Let kValue be ? Get(O, Pk). + auto k_value = TRY(this_object->get(index_property)); + + // c. Let key be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). + auto key = TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(index), this_object)); + + // d. If key is -0𝔽, set key to +0𝔽. + if (key.is_negative_zero()) + key = Value(0); + + // e. Perform AddValueToKeyedGroup(groups, key, kValue). + add_value_to_keyed_group(global_object, groups, make_handle(key), k_value); + + // f. Set k to k + 1. + } + + // 7. Let map be ! Construct(%Map%). + auto* map = Map::create(global_object); + + // 8. For each Record { [[Key]], [[Elements]] } g of groups, do + for (auto& group : groups) { + // a. Let elements be CreateArrayFromList(g.[[Elements]]). + auto* elements = Array::create_from(global_object, group.value); + + // b. Let entry be the Record { [[Key]]: g.[[Key]], [[Value]]: elements }. + // c. Append entry as the last element of map.[[MapData]]. + map->map_set(group.key.value(), elements); + } + + // 9. Return map. + return map; +} + +// 23.1.3.14 Array.prototype.includes ( searchElement [ , fromIndex ] ), https://tc39.es/ecma262/#sec-array.prototype.includes +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::includes) +{ + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + auto length = TRY(length_of_array_like(global_object, *this_object)); + if (length == 0) + return Value(false); + u64 from_index = 0; + if (vm.argument_count() >= 2) { + auto from_argument = TRY(vm.argument(1).to_integer_or_infinity(global_object)); + + if (Value(from_argument).is_positive_infinity() || from_argument >= length) + return Value(false); + + if (Value(from_argument).is_negative_infinity()) + from_argument = 0; + + if (from_argument < 0) + from_index = max(length + from_argument, 0); + else + from_index = from_argument; + } + auto value_to_find = vm.argument(0); + for (u64 i = from_index; i < length; ++i) { + auto element = TRY(this_object->get(i)); + if (same_value_zero(element, value_to_find)) + return Value(true); + } + return Value(false); +} + // 23.1.3.15 Array.prototype.indexOf ( searchElement [ , fromIndex ] ), https://tc39.es/ecma262/#sec-array.prototype.indexof JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::index_of) { @@ -676,6 +978,194 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::index_of) return Value(-1); } +// 23.1.3.16 Array.prototype.join ( separator ), https://tc39.es/ecma262/#sec-array.prototype.join +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::join) +{ + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + + // This is not part of the spec, but all major engines do some kind of circular reference checks. + // FWIW: engine262, a "100% spec compliant" ECMA-262 impl, aborts with "too much recursion". + // Same applies to Array.prototype.toLocaleString(). + if (s_array_join_seen_objects.contains(this_object)) + return js_string(vm, ""); + s_array_join_seen_objects.set(this_object); + ArmedScopeGuard unsee_object_guard = [&] { + s_array_join_seen_objects.remove(this_object); + }; + + auto length = TRY(length_of_array_like(global_object, *this_object)); + String separator = ","; + if (!vm.argument(0).is_undefined()) + separator = TRY(vm.argument(0).to_string(global_object)); + StringBuilder builder; + for (size_t i = 0; i < length; ++i) { + if (i > 0) + builder.append(separator); + auto value = TRY(this_object->get(i)); + if (value.is_nullish()) + continue; + auto string = TRY(value.to_string(global_object)); + builder.append(string); + } + + return js_string(vm, builder.to_string()); +} + +// 23.1.3.17 Array.prototype.keys ( ), https://tc39.es/ecma262/#sec-array.prototype.keys +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::keys) +{ + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + + return ArrayIterator::create(global_object, this_object, Object::PropertyKind::Key); +} + +// 23.1.3.18 Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] ), https://tc39.es/ecma262/#sec-array.prototype.lastindexof +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::last_index_of) +{ + auto search_element = vm.argument(0); + auto from_index = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *object)); + + // 3. If len is 0, return -1𝔽. + if (length == 0) + return Value(-1); + + double n; + + // 4. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1. + if (vm.argument_count() >= 2) + n = TRY(from_index.to_integer_or_infinity(global_object)); + else + n = (double)length - 1; + + // 5. If n is -∞, return -1𝔽. + if (Value(n).is_negative_infinity()) + return Value(-1); + + ssize_t k; + + // 6. If n ≥ 0, then + if (n >= 0) { + // a. Let k be min(n, len - 1). + k = min(n, (double)length - 1); + } + // 7. Else, + else { + // a. Let k be len + n. + k = (double)length + n; + } + + // 8. Repeat, while k ≥ 0, + for (; k >= 0; --k) { + auto property_key = PropertyKey { k }; + + // a. Let kPresent be ? HasProperty(O, ! ToString(𝔽(k))). + auto k_present = TRY(object->has_property(property_key)); + + // b. If kPresent is true, then + if (k_present) { + // i. Let elementK be ? Get(O, ! ToString(𝔽(k))). + auto element_k = TRY(object->get(property_key)); + + // ii. Let same be IsStrictlyEqual(searchElement, elementK). + auto same = is_strictly_equal(search_element, element_k); + + // iii. If same is true, return 𝔽(k). + if (same) + return Value((size_t)k); + } + + // c. Set k to k - 1. + } + + // 9. Return -1𝔽. + return Value(-1); +} + +// 23.1.3.19 Array.prototype.map ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.map +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::map) +{ + auto callback_function = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *object)); + + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); + + // 4. Let A be ? ArraySpeciesCreate(O, len). + auto* array = TRY(array_species_create(global_object, *object, length)); + + // 5. Let k be 0. + // 6. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_key = PropertyKey { k }; + + // b. Let kPresent be ? HasProperty(O, Pk). + auto k_present = TRY(object->has_property(property_key)); + + // c. If kPresent is true, then + if (k_present) { + // i. Let kValue be ? Get(O, Pk). + auto k_value = TRY(object->get(property_key)); + + // ii. Let mappedValue be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). + auto mapped_value = TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(k), object)); + + // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). + TRY(array->create_data_property_or_throw(property_key, mapped_value)); + } + + // d. Set k to k + 1. + } + + // 7. Return A. + return array; +} + +// 23.1.3.20 Array.prototype.pop ( ), https://tc39.es/ecma262/#sec-array.prototype.pop +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::pop) +{ + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + auto length = TRY(length_of_array_like(global_object, *this_object)); + if (length == 0) { + TRY(this_object->set(vm.names.length, Value(0), Object::ShouldThrowExceptions::Yes)); + return js_undefined(); + } + auto index = length - 1; + auto element = TRY(this_object->get(index)); + TRY(this_object->delete_property_or_throw(index)); + TRY(this_object->set(vm.names.length, Value(index), Object::ShouldThrowExceptions::Yes)); + return element; +} + +// 23.1.3.21 Array.prototype.push ( ...items ), https://tc39.es/ecma262/#sec-array.prototype.push +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::push) +{ + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + auto length = TRY(length_of_array_like(global_object, *this_object)); + auto argument_count = vm.argument_count(); + auto new_length = length + argument_count; + if (new_length > MAX_ARRAY_LIKE_INDEX) + return vm.throw_completion(global_object, ErrorType::ArrayMaxSize); + for (size_t i = 0; i < argument_count; ++i) + TRY(this_object->set(length + i, vm.argument(i), Object::ShouldThrowExceptions::Yes)); + auto new_length_value = Value(new_length); + TRY(this_object->set(vm.names.length, new_length_value, Object::ShouldThrowExceptions::Yes)); + return new_length_value; +} + // 23.1.3.22 Array.prototype.reduce ( callbackfn [ , initialValue ] ), https://tc39.es/ecma262/#sec-array.prototype.reduce JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reduce) { @@ -875,6 +1365,134 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::reverse) return this_object; } +// 23.1.3.25 Array.prototype.shift ( ), https://tc39.es/ecma262/#sec-array.prototype.shift +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::shift) +{ + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + auto length = TRY(length_of_array_like(global_object, *this_object)); + if (length == 0) { + TRY(this_object->set(vm.names.length, Value(0), Object::ShouldThrowExceptions::Yes)); + return js_undefined(); + } + auto first = TRY(this_object->get(0)); + for (size_t k = 1; k < length; ++k) { + size_t from = k; + size_t to = k - 1; + bool from_present = TRY(this_object->has_property(from)); + if (from_present) { + auto from_value = TRY(this_object->get(from)); + TRY(this_object->set(to, from_value, Object::ShouldThrowExceptions::Yes)); + } else { + TRY(this_object->delete_property_or_throw(to)); + } + } + + TRY(this_object->delete_property_or_throw(length - 1)); + TRY(this_object->set(vm.names.length, Value(length - 1), Object::ShouldThrowExceptions::Yes)); + return first; +} + +// 23.1.3.26 Array.prototype.slice ( start, end ), https://tc39.es/ecma262/#sec-array.prototype.slice +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::slice) +{ + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + + auto initial_length = TRY(length_of_array_like(global_object, *this_object)); + + auto relative_start = TRY(vm.argument(0).to_integer_or_infinity(global_object)); + + double actual_start; + + if (Value(relative_start).is_negative_infinity()) + actual_start = 0.0; + else if (relative_start < 0.0) + actual_start = max((double)initial_length + relative_start, 0.0); + else + actual_start = min(relative_start, (double)initial_length); + + double relative_end; + + if (vm.argument(1).is_undefined() || vm.argument(1).is_empty()) + relative_end = (double)initial_length; + else + relative_end = TRY(vm.argument(1).to_integer_or_infinity(global_object)); + + double final; + + if (Value(relative_end).is_negative_infinity()) + final = 0.0; + else if (relative_end < 0.0) + final = max((double)initial_length + relative_end, 0.0); + else + final = min(relative_end, (double)initial_length); + + auto count = max(final - actual_start, 0.0); + + auto* new_array = TRY(array_species_create(global_object, *this_object, count)); + + size_t index = 0; + size_t k = actual_start; + + while (k < final) { + bool present = TRY(this_object->has_property(k)); + if (present) { + auto value = TRY(this_object->get(k)); + TRY(new_array->create_data_property_or_throw(index, value)); + } + + ++k; + ++index; + } + + TRY(new_array->set(vm.names.length, Value(index), Object::ShouldThrowExceptions::Yes)); + return new_array; +} + +// 23.1.3.27 Array.prototype.some ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.some +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::some) +{ + auto callback_function = vm.argument(0); + auto this_arg = vm.argument(1); + + // 1. Let O be ? ToObject(this value). + auto* object = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let len be ? LengthOfArrayLike(O). + auto length = TRY(length_of_array_like(global_object, *object)); + + // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. + if (!callback_function.is_function()) + return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); + + // 4. Let k be 0. + // 5. Repeat, while k < len, + for (size_t k = 0; k < length; ++k) { + // a. Let Pk be ! ToString(𝔽(k)). + auto property_key = PropertyKey { k }; + + // b. Let kPresent be ? HasProperty(O, Pk). + auto k_present = TRY(object->has_property(property_key)); + + // c. If kPresent is true, then + if (k_present) { + // i. Let kValue be ? Get(O, Pk). + auto k_value = TRY(object->get(property_key)); + + // ii. Let testResult be ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + auto test_result = TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(k), object)).to_boolean(); + + // iii. If testResult is true, return true. + if (test_result) + return Value(true); + } + + // d. Set k to k + 1. + } + + // 6. Return false. + return Value(false); +} + ThrowCompletionOr array_merge_sort(GlobalObject& global_object, FunctionObject* compare_func, MarkedVector& arr_to_sort) { // FIXME: it would probably be better to switch to insertion sort for small arrays for @@ -973,351 +1591,6 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::sort) return object; } -// 23.1.3.18 Array.prototype.lastIndexOf ( searchElement [ , fromIndex ] ), https://tc39.es/ecma262/#sec-array.prototype.lastindexof -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::last_index_of) -{ - auto search_element = vm.argument(0); - auto from_index = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let len be ? LengthOfArrayLike(O). - auto length = TRY(length_of_array_like(global_object, *object)); - - // 3. If len is 0, return -1𝔽. - if (length == 0) - return Value(-1); - - double n; - - // 4. If fromIndex is present, let n be ? ToIntegerOrInfinity(fromIndex); else let n be len - 1. - if (vm.argument_count() >= 2) - n = TRY(from_index.to_integer_or_infinity(global_object)); - else - n = (double)length - 1; - - // 5. If n is -∞, return -1𝔽. - if (Value(n).is_negative_infinity()) - return Value(-1); - - ssize_t k; - - // 6. If n ≥ 0, then - if (n >= 0) { - // a. Let k be min(n, len - 1). - k = min(n, (double)length - 1); - } - // 7. Else, - else { - // a. Let k be len + n. - k = (double)length + n; - } - - // 8. Repeat, while k ≥ 0, - for (; k >= 0; --k) { - auto property_key = PropertyKey { k }; - - // a. Let kPresent be ? HasProperty(O, ! ToString(𝔽(k))). - auto k_present = TRY(object->has_property(property_key)); - - // b. If kPresent is true, then - if (k_present) { - // i. Let elementK be ? Get(O, ! ToString(𝔽(k))). - auto element_k = TRY(object->get(property_key)); - - // ii. Let same be IsStrictlyEqual(searchElement, elementK). - auto same = is_strictly_equal(search_element, element_k); - - // iii. If same is true, return 𝔽(k). - if (same) - return Value((size_t)k); - } - - // c. Set k to k - 1. - } - - // 9. Return -1𝔽. - return Value(-1); -} - -// 23.1.3.14 Array.prototype.includes ( searchElement [ , fromIndex ] ), https://tc39.es/ecma262/#sec-array.prototype.includes -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::includes) -{ - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - auto length = TRY(length_of_array_like(global_object, *this_object)); - if (length == 0) - return Value(false); - u64 from_index = 0; - if (vm.argument_count() >= 2) { - auto from_argument = TRY(vm.argument(1).to_integer_or_infinity(global_object)); - - if (Value(from_argument).is_positive_infinity() || from_argument >= length) - return Value(false); - - if (Value(from_argument).is_negative_infinity()) - from_argument = 0; - - if (from_argument < 0) - from_index = max(length + from_argument, 0); - else - from_index = from_argument; - } - auto value_to_find = vm.argument(0); - for (u64 i = from_index; i < length; ++i) { - auto element = TRY(this_object->get(i)); - if (same_value_zero(element, value_to_find)) - return Value(true); - } - return Value(false); -} - -// 23.1.3.9 Array.prototype.find ( predicate [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.find -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find) -{ - auto predicate = vm.argument(0); - auto this_arg = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let len be ? LengthOfArrayLike(O). - auto length = TRY(length_of_array_like(global_object, *object)); - - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (!predicate.is_function()) - return vm.throw_completion(global_object, ErrorType::NotAFunction, predicate.to_string_without_side_effects()); - - // 4. Let k be 0. - // 5. Repeat, while k < len, - for (size_t k = 0; k < length; ++k) { - // a. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; - - // b. Let kValue be ? Get(O, Pk). - auto k_value = TRY(object->get(property_key)); - - // c. Let testResult be ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). - auto test_result = TRY(call(global_object, predicate.as_function(), this_arg, k_value, Value(k), object)).to_boolean(); - - // d. If testResult is true, return kValue. - if (test_result) - return k_value; - - // e. Set k to k + 1. - } - - // 6. Return undefined. - return js_undefined(); -} - -// 23.1.3.10 Array.prototype.findIndex ( predicate [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.findindex -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find_index) -{ - auto predicate = vm.argument(0); - auto this_arg = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let len be ? LengthOfArrayLike(O). - auto length = TRY(length_of_array_like(global_object, *object)); - - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (!predicate.is_function()) - return vm.throw_completion(global_object, ErrorType::NotAFunction, predicate.to_string_without_side_effects()); - - // 4. Let k be 0. - // 5. Repeat, while k < len, - for (size_t k = 0; k < length; ++k) { - // a. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; - - // b. Let kValue be ? Get(O, Pk). - auto k_value = TRY(object->get(property_key)); - - // c. Let testResult be ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). - auto test_result = TRY(call(global_object, predicate.as_function(), this_arg, k_value, Value(k), object)).to_boolean(); - - // d. If testResult is true, return 𝔽(k). - if (test_result) - return Value(k); - - // e. Set k to k + 1. - } - - // 6. Return -1𝔽. - return Value(-1); -} - -// 1 Array.prototype.findLast ( predicate [ , thisArg ] ), https://tc39.es/proposal-array-find-from-last/#sec-array.prototype.findlast -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find_last) -{ - auto predicate = vm.argument(0); - auto this_arg = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let len be ? LengthOfArrayLike(O). - auto length = TRY(length_of_array_like(global_object, *object)); - - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (!predicate.is_function()) - return vm.throw_completion(global_object, ErrorType::NotAFunction, predicate.to_string_without_side_effects()); - - // 4. Let k be len - 1. - // 5. Repeat, while k ≥ 0, - for (i64 k = static_cast(length) - 1; k >= 0; --k) { - // a. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; - - // b. Let kValue be ? Get(O, Pk). - auto k_value = TRY(object->get(property_key)); - - // c. Let testResult be ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). - auto test_result = TRY(call(global_object, predicate.as_function(), this_arg, k_value, Value((double)k), object)).to_boolean(); - - // d. If testResult is true, return kValue. - if (test_result) - return k_value; - - // e. Set k to k - 1. - } - - // 6. Return undefined. - return js_undefined(); -} - -// 2 Array.prototype.findLastIndex ( predicate [ , thisArg ] ), https://tc39.es/proposal-array-find-from-last/#sec-array.prototype.findlastindex -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::find_last_index) -{ - auto predicate = vm.argument(0); - auto this_arg = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let len be ? LengthOfArrayLike(O). - auto length = TRY(length_of_array_like(global_object, *object)); - - // 3. If IsCallable(predicate) is false, throw a TypeError exception. - if (!predicate.is_function()) - return vm.throw_completion(global_object, ErrorType::NotAFunction, predicate.to_string_without_side_effects()); - - // 4. Let k be len - 1. - // 5. Repeat, while k ≥ 0, - for (i64 k = static_cast(length) - 1; k >= 0; --k) { - // a. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; - - // b. Let kValue be ? Get(O, Pk). - auto k_value = TRY(object->get(property_key)); - - // c. Let testResult be ToBoolean(? Call(predicate, thisArg, « kValue, 𝔽(k), O »)). - auto test_result = TRY(call(global_object, predicate.as_function(), this_arg, k_value, Value((double)k), object)).to_boolean(); - - // d. If testResult is true, return 𝔽(k). - if (test_result) - return Value((double)k); - - // e. Set k to k - 1. - } - - // 6. Return -1𝔽. - return Value(-1); -} - -// 23.1.3.27 Array.prototype.some ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.some -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::some) -{ - auto callback_function = vm.argument(0); - auto this_arg = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let len be ? LengthOfArrayLike(O). - auto length = TRY(length_of_array_like(global_object, *object)); - - // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - if (!callback_function.is_function()) - return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); - - // 4. Let k be 0. - // 5. Repeat, while k < len, - for (size_t k = 0; k < length; ++k) { - // a. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; - - // b. Let kPresent be ? HasProperty(O, Pk). - auto k_present = TRY(object->has_property(property_key)); - - // c. If kPresent is true, then - if (k_present) { - // i. Let kValue be ? Get(O, Pk). - auto k_value = TRY(object->get(property_key)); - - // ii. Let testResult be ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). - auto test_result = TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(k), object)).to_boolean(); - - // iii. If testResult is true, return true. - if (test_result) - return Value(true); - } - - // d. Set k to k + 1. - } - - // 6. Return false. - return Value(false); -} - -// 23.1.3.6 Array.prototype.every ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.every -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::every) -{ - auto callback_function = vm.argument(0); - auto this_arg = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let len be ? LengthOfArrayLike(O). - auto length = TRY(length_of_array_like(global_object, *object)); - - // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - if (!callback_function.is_function()) - return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); - - // 4. Let k be 0. - // 5. Repeat, while k < len, - for (size_t k = 0; k < length; ++k) { - // a. Let Pk be ! ToString(𝔽(k)). - auto property_key = PropertyKey { k }; - - // b. Let kPresent be ? HasProperty(O, Pk). - auto k_present = TRY(object->has_property(property_key)); - - // c. If kPresent is true, then - if (k_present) { - // i. Let kValue be ? Get(O, Pk). - auto k_value = TRY(object->get(property_key)); - - // ii. Let testResult be ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). - auto test_result = TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(k), object)).to_boolean(); - - // iii. If testResult is false, return false. - if (!test_result) - return Value(false); - } - - // d. Set k to k + 1. - } - - // 6. Return true. - return Value(true); -} - // 23.1.3.29 Array.prototype.splice ( start, deleteCount, ...items ), https://tc39.es/ecma262/#sec-array.prototype.splice JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::splice) { @@ -1405,388 +1678,59 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::splice) return removed_elements; } -// 23.1.3.7 Array.prototype.fill ( value [ , start [ , end ] ] ), https://tc39.es/ecma262/#sec-array.prototype.fill -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::fill) +// 23.1.3.30 Array.prototype.toLocaleString ( [ reserved1 [ , reserved2 ] ] ), https://tc39.es/ecma262/#sec-array.prototype.tolocalestring +// 19.5.1 Array.prototype.toLocaleString ( [ locales [ , options ] ] ), https://tc39.es/ecma402/#sup-array.prototype.tolocalestring +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_locale_string) { + auto locales = vm.argument(0); + auto options = vm.argument(1); + + // 1. Let array be ? ToObject(this value). auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - auto length = TRY(length_of_array_like(global_object, *this_object)); - - double relative_start = 0; - double relative_end = length; - - if (vm.argument_count() >= 2) { - relative_start = TRY(vm.argument(1).to_integer_or_infinity(global_object)); - if (Value(relative_start).is_negative_infinity()) - relative_start = 0; - } - - // If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). - if (vm.argument_count() >= 3 && !vm.argument(2).is_undefined()) { - relative_end = TRY(vm.argument(2).to_integer_or_infinity(global_object)); - if (Value(relative_end).is_negative_infinity()) - relative_end = 0; - } - - u64 from, to; - - if (relative_start < 0) - from = max(length + relative_start, 0L); - else - from = min(relative_start, length); - - if (relative_end < 0) - to = max(length + relative_end, 0L); - else - to = min(relative_end, length); - - for (u64 i = from; i < to; i++) - TRY(this_object->set(i, vm.argument(0), Object::ShouldThrowExceptions::Yes)); - - return this_object; -} - -// 23.1.3.33 Array.prototype.values ( ), https://tc39.es/ecma262/#sec-array.prototype.values -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::values) -{ - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - - return ArrayIterator::create(global_object, this_object, Object::PropertyKind::Value); -} - -// 23.1.3.5 Array.prototype.entries ( ), https://tc39.es/ecma262/#sec-array.prototype.entries -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::entries) -{ - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - - return ArrayIterator::create(global_object, this_object, Object::PropertyKind::KeyAndValue); -} - -// 23.1.3.17 Array.prototype.keys ( ), https://tc39.es/ecma262/#sec-array.prototype.keys -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::keys) -{ - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - - return ArrayIterator::create(global_object, this_object, Object::PropertyKind::Key); -} - -// 23.1.3.11.1 FlattenIntoArray ( target, source, sourceLen, start, depth [ , mapperFunction [ , thisArg ] ] ), https://tc39.es/ecma262/#sec-flattenintoarray -static ThrowCompletionOr flatten_into_array(GlobalObject& global_object, Object& new_array, Object& array, size_t array_length, size_t target_index, double depth, FunctionObject* mapper_func = {}, Value this_arg = {}) -{ - VERIFY(!mapper_func || (!this_arg.is_empty() && depth == 1)); - auto& vm = global_object.vm(); - - for (size_t j = 0; j < array_length; ++j) { - auto value_exists = TRY(array.has_property(j)); - - if (!value_exists) - continue; - auto value = TRY(array.get(j)); - - if (mapper_func) - value = TRY(call(global_object, *mapper_func, this_arg, value, Value(j), &array)); - - if (depth > 0 && TRY(value.is_array(global_object))) { - if (vm.did_reach_stack_space_limit()) - return vm.throw_completion(global_object, ErrorType::CallStackSizeExceeded); - - auto length = TRY(length_of_array_like(global_object, value.as_object())); - target_index = TRY(flatten_into_array(global_object, new_array, value.as_object(), length, target_index, depth - 1)); - continue; - } - - if (target_index >= MAX_ARRAY_LIKE_INDEX) - return vm.throw_completion(global_object, ErrorType::InvalidIndex); - - TRY(new_array.create_data_property_or_throw(target_index, value)); - - ++target_index; - } - return target_index; -} - -// 23.1.3.11 Array.prototype.flat ( [ depth ] ), https://tc39.es/ecma262/#sec-array.prototype.flat -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::flat) -{ - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - - auto length = TRY(length_of_array_like(global_object, *this_object)); - - double depth = 1; - if (!vm.argument(0).is_undefined()) { - auto depth_num = TRY(vm.argument(0).to_integer_or_infinity(global_object)); - depth = max(depth_num, 0.0); - } - - auto* new_array = TRY(array_species_create(global_object, *this_object, 0)); - - TRY(flatten_into_array(global_object, *new_array, *this_object, length, 0, depth)); - return new_array; -} - -// 23.1.3.12 Array.prototype.flatMap ( mapperFunction [ , thisArg ] ), https://tc39.es/ecma262/#sec-array.prototype.flatmap -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::flat_map) -{ - auto mapper_function = vm.argument(0); - auto this_arg = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let sourceLen be ? LengthOfArrayLike(O). - auto source_length = TRY(length_of_array_like(global_object, *object)); - - // 3. If IsCallable(mapperFunction) is false, throw a TypeError exception. - if (!mapper_function.is_function()) - return vm.throw_completion(global_object, ErrorType::NotAFunction, mapper_function.to_string_without_side_effects()); - - // 4. Let A be ? ArraySpeciesCreate(O, 0). - auto* array = TRY(array_species_create(global_object, *object, 0)); - - // 5. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, thisArg). - TRY(flatten_into_array(global_object, *array, *object, source_length, 0, 1, &mapper_function.as_function(), this_arg)); - - // 6. Return A. - return array; -} - -// 23.1.3.3 Array.prototype.copyWithin ( target, start [ , end ] ), https://tc39.es/ecma262/#sec-array.prototype.copywithin -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::copy_within) -{ - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - auto length = TRY(length_of_array_like(global_object, *this_object)); - - auto relative_target = TRY(vm.argument(0).to_integer_or_infinity(global_object)); - - double to; - if (relative_target < 0) - to = max(length + relative_target, 0.0); - else - to = min(relative_target, (double)length); - - auto relative_start = TRY(vm.argument(1).to_integer_or_infinity(global_object)); - - double from; - if (relative_start < 0) - from = max(length + relative_start, 0.0); - else - from = min(relative_start, (double)length); - - auto relative_end = vm.argument(2).is_undefined() ? length : TRY(vm.argument(2).to_integer_or_infinity(global_object)); - - double final; - if (relative_end < 0) - final = max(length + relative_end, 0.0); - else - final = min(relative_end, (double)length); - - double count = min(final - from, length - to); - - i32 direction = 1; - - if (from < to && to < from + count) { - direction = -1; - from = from + count - 1; - to = to + count - 1; - } - - if (count < 0) { - return this_object; - } - - size_t from_i = from; - size_t to_i = to; - size_t count_i = count; - - while (count_i > 0) { - auto from_present = TRY(this_object->has_property(from_i)); - - if (from_present) { - auto from_value = TRY(this_object->get(from_i)); - TRY(this_object->set(to_i, from_value, Object::ShouldThrowExceptions::Yes)); - } else { - TRY(this_object->delete_property_or_throw(to_i)); - } - - from_i += direction; - to_i += direction; - --count_i; - } - - return this_object; -} - -// 23.1.3.1 Array.prototype.at ( index ), https://tc39.es/ecma262/#sec-array.prototype.at -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::at) -{ - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - auto length = TRY(length_of_array_like(global_object, *this_object)); - auto relative_index = TRY(vm.argument(0).to_integer_or_infinity(global_object)); - if (Value(relative_index).is_infinity()) - return js_undefined(); - Checked index { 0 }; - if (relative_index >= 0) { - index += relative_index; - } else { - index += length; - index -= -relative_index; - } - if (index.has_overflow() || index.value() >= length) - return js_undefined(); - return TRY(this_object->get(index.value())); -} - -// 2.3 AddValueToKeyedGroup ( groups, key, value ), https://tc39.es/proposal-array-grouping/#sec-add-value-to-keyed-group -template -static void add_value_to_keyed_group(GlobalObject& global_object, GroupsType& groups, KeyType key, Value value) -{ - // 1. For each Record { [[Key]], [[Elements]] } g of groups, do - // a. If SameValue(g.[[Key]], key) is true, then - // NOTE: This is performed in KeyedGroupTraits::equals for groupToMap and Traits::equals for group. - auto existing_elements_iterator = groups.find(key); - if (existing_elements_iterator != groups.end()) { - // i. Assert: exactly one element of groups meets this criteria. - // NOTE: This is done on insertion into the hash map, as only `set` tells us if we overrode an entry. - - // ii. Append value as the last element of g.[[Elements]]. - existing_elements_iterator->value.append(value); - - // iii. Return unused. - return; - } - - // 2. Let group be the Record { [[Key]]: key, [[Elements]]: « value » }. - MarkedVector new_elements { global_object.heap() }; - new_elements.append(value); - - // 3. Append group as the last element of groups. - auto result = groups.set(key, move(new_elements)); - VERIFY(result == AK::HashSetResult::InsertedNewEntry); -} - -// 2.1 Array.prototype.group ( callbackfn [ , thisArg ] ), https://tc39.es/proposal-array-grouping/#sec-array.prototype.group -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::group) -{ - auto callback_function = vm.argument(0); - auto this_arg = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let len be ? LengthOfArrayLike(O). - auto length = TRY(length_of_array_like(global_object, *this_object)); - - // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - if (!callback_function.is_function()) - return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); - - // 5. Let groups be a new empty List. - OrderedHashMap> groups; - - // 4. Let k be 0. - // 6. Repeat, while k < len - for (size_t index = 0; index < length; ++index) { - // a. Let Pk be ! ToString(𝔽(k)). - auto index_property = PropertyKey { index }; - - // b. Let kValue be ? Get(O, Pk). - auto k_value = TRY(this_object->get(index_property)); - - // c. Let propertyKey be ? ToPropertyKey(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). - auto property_key_value = TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(index), this_object)); - auto property_key = TRY(property_key_value.to_property_key(global_object)); - - // d. Perform AddValueToKeyedGroup(groups, propertyKey, kValue). - add_value_to_keyed_group(global_object, groups, property_key, k_value); - - // e. Set k to k + 1. - } - - // 7. Let obj be OrdinaryObjectCreate(null). - auto* object = Object::create(global_object, nullptr); - - // 8. For each Record { [[Key]], [[Elements]] } g of groups, do - for (auto& group : groups) { - // a. Let elements be CreateArrayFromList(g.[[Elements]]). - auto* elements = Array::create_from(global_object, group.value); - - // b. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements). - MUST(object->create_data_property_or_throw(group.key, elements)); - } - - // 9. Return obj. - return object; -} - -// 2.2 Array.prototype.groupToMap ( callbackfn [ , thisArg ] ), https://tc39.es/proposal-array-grouping/#sec-array.prototype.grouptomap -JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::group_to_map) -{ - auto callback_function = vm.argument(0); - auto this_arg = vm.argument(1); - - // 1. Let O be ? ToObject(this value). - auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); - - // 2. Let len be ? LengthOfArrayLike(O). - auto length = TRY(length_of_array_like(global_object, *this_object)); - - // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. - if (!callback_function.is_function()) - return vm.throw_completion(global_object, ErrorType::NotAFunction, callback_function.to_string_without_side_effects()); - - struct KeyedGroupTraits : public Traits> { - static unsigned hash(Handle const& value_handle) - { - return ValueTraits::hash(value_handle.value()); - } - - static bool equals(Handle const& a, Handle const& b) - { - // AddValueToKeyedGroup uses SameValue on the keys on Step 1.a. - return same_value(a.value(), b.value()); - } + if (s_array_join_seen_objects.contains(this_object)) + return js_string(vm, ""); + s_array_join_seen_objects.set(this_object); + ArmedScopeGuard unsee_object_guard = [&] { + s_array_join_seen_objects.remove(this_object); }; - // 5. Let groups be a new empty List. - OrderedHashMap, MarkedVector, KeyedGroupTraits> groups; + // 2. Let len be ? ToLength(? Get(array, "length")). + auto length = TRY(length_of_array_like(global_object, *this_object)); - // 4. Let k be 0. - // 6. Repeat, while k < len - for (size_t index = 0; index < length; ++index) { - // a. Let Pk be ! ToString(𝔽(k)). - auto index_property = PropertyKey { index }; + // 3. Let separator be the implementation-defined list-separator String value appropriate for the host environment's current locale (such as ", "). + constexpr auto separator = ","sv; - // b. Let kValue be ? Get(O, Pk). - auto k_value = TRY(this_object->get(index_property)); + // 4. Let R be the empty String. + StringBuilder builder; - // c. Let key be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »). - auto key = TRY(call(global_object, callback_function.as_function(), this_arg, k_value, Value(index), this_object)); + // 5. Let k be 0. + // 6. Repeat, while k < len, + for (size_t i = 0; i < length; ++i) { + // a. If k > 0, then + if (i > 0) { + // i. Set R to the string-concatenation of R and separator. + builder.append(separator); + } - // d. If key is -0𝔽, set key to +0𝔽. - if (key.is_negative_zero()) - key = Value(0); + // b. Let nextElement be ? Get(array, ! ToString(k)). + auto value = TRY(this_object->get(i)); - // e. Perform AddValueToKeyedGroup(groups, key, kValue). - add_value_to_keyed_group(global_object, groups, make_handle(key), k_value); + // c. If nextElement is not undefined or null, then + if (!value.is_nullish()) { + // i. Let S be ? ToString(? Invoke(nextElement, "toLocaleString", « locales, options »)). + auto locale_string_result = TRY(value.invoke(global_object, vm.names.toLocaleString, locales, options)); - // f. Set k to k + 1. + // ii. Set R to the string-concatenation of R and S. + auto string = TRY(locale_string_result.to_string(global_object)); + builder.append(string); + } + + // d. Increase k by 1. } - // 7. Let map be ! Construct(%Map%). - auto* map = Map::create(global_object); - - // 8. For each Record { [[Key]], [[Elements]] } g of groups, do - for (auto& group : groups) { - // a. Let elements be CreateArrayFromList(g.[[Elements]]). - auto* elements = Array::create_from(global_object, group.value); - - // b. Let entry be the Record { [[Key]]: g.[[Key]], [[Value]]: elements }. - // c. Append entry as the last element of map.[[MapData]]. - map->map_set(group.key.value(), elements); - } - - // 9. Return map. - return map; + // 7. Return R. + return js_string(vm, builder.to_string()); } // 1.1.1.4 Array.prototype.toReversed ( ), https://tc39.es/proposal-change-array-by-copy/#sec-array.prototype.toReversed @@ -1992,6 +1936,63 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_spliced) return array; } +// 23.1.3.31 Array.prototype.toString ( ), https://tc39.es/ecma262/#sec-array.prototype.tostring +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::to_string) +{ + // 1. Let array be ? ToObject(this value). + auto* array = TRY(vm.this_value(global_object).to_object(global_object)); + + // 2. Let func be ? Get(array, "join"). + auto func = TRY(array->get(vm.names.join)); + + // 3. If IsCallable(func) is false, set func to the intrinsic function %Object.prototype.toString%. + if (!func.is_function()) + func = global_object.object_prototype_to_string_function(); + + // 4. Return ? Call(func, array). + return TRY(call(global_object, func.as_function(), array)); +} + +// 23.1.3.32 Array.prototype.unshift ( ...items ), https://tc39.es/ecma262/#sec-array.prototype.unshift +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::unshift) +{ + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + auto length = TRY(length_of_array_like(global_object, *this_object)); + auto arg_count = vm.argument_count(); + size_t new_length = length + arg_count; + if (arg_count > 0) { + if (new_length > MAX_ARRAY_LIKE_INDEX) + return vm.throw_completion(global_object, ErrorType::ArrayMaxSize); + + for (size_t k = length; k > 0; --k) { + auto from = k - 1; + auto to = k + arg_count - 1; + + bool from_present = TRY(this_object->has_property(from)); + if (from_present) { + auto from_value = TRY(this_object->get(from)); + TRY(this_object->set(to, from_value, Object::ShouldThrowExceptions::Yes)); + } else { + TRY(this_object->delete_property_or_throw(to)); + } + } + + for (size_t j = 0; j < arg_count; j++) + TRY(this_object->set(j, vm.argument(j), Object::ShouldThrowExceptions::Yes)); + } + + TRY(this_object->set(vm.names.length, Value(new_length), Object::ShouldThrowExceptions::Yes)); + return Value(new_length); +} + +// 23.1.3.33 Array.prototype.values ( ), https://tc39.es/ecma262/#sec-array.prototype.values +JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::values) +{ + auto* this_object = TRY(vm.this_value(global_object).to_object(global_object)); + + return ArrayIterator::create(global_object, this_object, Object::PropertyKind::Value); +} + // 1.1.1.7 Array.prototype.with ( index, value ), https://tc39.es/proposal-change-array-by-copy/#sec-array.prototype.with JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::with) { diff --git a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h index 2adc369b9b..b8674a88a3 100644 --- a/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/ArrayPrototype.h @@ -20,45 +20,45 @@ public: virtual ~ArrayPrototype() override = default; private: - JS_DECLARE_NATIVE_FUNCTION(filter); - JS_DECLARE_NATIVE_FUNCTION(for_each); - JS_DECLARE_NATIVE_FUNCTION(map); - JS_DECLARE_NATIVE_FUNCTION(pop); - JS_DECLARE_NATIVE_FUNCTION(push); - JS_DECLARE_NATIVE_FUNCTION(shift); - JS_DECLARE_NATIVE_FUNCTION(to_string); - JS_DECLARE_NATIVE_FUNCTION(to_locale_string); - JS_DECLARE_NATIVE_FUNCTION(unshift); - JS_DECLARE_NATIVE_FUNCTION(join); + JS_DECLARE_NATIVE_FUNCTION(at); JS_DECLARE_NATIVE_FUNCTION(concat); - JS_DECLARE_NATIVE_FUNCTION(slice); - JS_DECLARE_NATIVE_FUNCTION(index_of); - JS_DECLARE_NATIVE_FUNCTION(reduce); - JS_DECLARE_NATIVE_FUNCTION(reduce_right); - JS_DECLARE_NATIVE_FUNCTION(reverse); - JS_DECLARE_NATIVE_FUNCTION(sort); - JS_DECLARE_NATIVE_FUNCTION(last_index_of); - JS_DECLARE_NATIVE_FUNCTION(includes); + JS_DECLARE_NATIVE_FUNCTION(copy_within); + JS_DECLARE_NATIVE_FUNCTION(entries); + JS_DECLARE_NATIVE_FUNCTION(every); + JS_DECLARE_NATIVE_FUNCTION(fill); + JS_DECLARE_NATIVE_FUNCTION(filter); JS_DECLARE_NATIVE_FUNCTION(find); JS_DECLARE_NATIVE_FUNCTION(find_index); JS_DECLARE_NATIVE_FUNCTION(find_last); JS_DECLARE_NATIVE_FUNCTION(find_last_index); - JS_DECLARE_NATIVE_FUNCTION(some); - JS_DECLARE_NATIVE_FUNCTION(every); - JS_DECLARE_NATIVE_FUNCTION(splice); - JS_DECLARE_NATIVE_FUNCTION(fill); - JS_DECLARE_NATIVE_FUNCTION(values); JS_DECLARE_NATIVE_FUNCTION(flat); JS_DECLARE_NATIVE_FUNCTION(flat_map); - JS_DECLARE_NATIVE_FUNCTION(at); - JS_DECLARE_NATIVE_FUNCTION(keys); - JS_DECLARE_NATIVE_FUNCTION(entries); - JS_DECLARE_NATIVE_FUNCTION(copy_within); + JS_DECLARE_NATIVE_FUNCTION(for_each); JS_DECLARE_NATIVE_FUNCTION(group); JS_DECLARE_NATIVE_FUNCTION(group_to_map); + JS_DECLARE_NATIVE_FUNCTION(includes); + JS_DECLARE_NATIVE_FUNCTION(index_of); + JS_DECLARE_NATIVE_FUNCTION(join); + JS_DECLARE_NATIVE_FUNCTION(keys); + JS_DECLARE_NATIVE_FUNCTION(last_index_of); + JS_DECLARE_NATIVE_FUNCTION(map); + JS_DECLARE_NATIVE_FUNCTION(pop); + JS_DECLARE_NATIVE_FUNCTION(push); + JS_DECLARE_NATIVE_FUNCTION(reduce); + JS_DECLARE_NATIVE_FUNCTION(reduce_right); + JS_DECLARE_NATIVE_FUNCTION(reverse); + JS_DECLARE_NATIVE_FUNCTION(shift); + JS_DECLARE_NATIVE_FUNCTION(slice); + JS_DECLARE_NATIVE_FUNCTION(some); + JS_DECLARE_NATIVE_FUNCTION(sort); + JS_DECLARE_NATIVE_FUNCTION(splice); + JS_DECLARE_NATIVE_FUNCTION(to_locale_string); JS_DECLARE_NATIVE_FUNCTION(to_reversed); JS_DECLARE_NATIVE_FUNCTION(to_sorted); JS_DECLARE_NATIVE_FUNCTION(to_spliced); + JS_DECLARE_NATIVE_FUNCTION(to_string); + JS_DECLARE_NATIVE_FUNCTION(unshift); + JS_DECLARE_NATIVE_FUNCTION(values); JS_DECLARE_NATIVE_FUNCTION(with); };