From bc6f619344e10461ce03d4d996f2c81a2199992a Mon Sep 17 00:00:00 2001 From: Luke Date: Fri, 9 Jul 2021 06:20:45 +0100 Subject: LibJS: Add TypedArraySpeciesCreate and %TypedArray%.prototype.filter I'm not too happy with how I get the default constructor in typed_array_species_create, but it works for now. --- .../LibJS/Runtime/TypedArrayPrototype.cpp | 105 ++++++++++ .../Libraries/LibJS/Runtime/TypedArrayPrototype.h | 1 + .../TypedArray/TypedArray.prototype.filter.js | 217 +++++++++++++++++++++ 3 files changed, 323 insertions(+) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.filter.js (limited to 'Userland') diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp index a547767567..b58196ef25 100644 --- a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp @@ -47,6 +47,7 @@ void TypedArrayPrototype::initialize(GlobalObject& object) define_native_function(vm.names.set, set, 1, attr); define_native_function(vm.names.reverse, reverse, 0, attr); define_native_function(vm.names.copyWithin, copy_within, 2, attr); + define_native_function(vm.names.filter, filter, 1, attr); define_native_accessor(*vm.well_known_symbol_to_string_tag(), to_string_tag_getter, nullptr, Attribute::Configurable); @@ -118,6 +119,38 @@ static void for_each_item(VM& vm, GlobalObject& global_object, const String& nam } } +// 23.2.4.1 TypedArraySpeciesCreate ( exemplar, argumentList ), https://tc39.es/ecma262/#typedarray-species-create +static TypedArrayBase* typed_array_species_create(GlobalObject& global_object, TypedArrayBase const& exemplar, MarkedValueList arguments) +{ + auto& vm = global_object.vm(); + + TypedArrayConstructor* typed_array_default_constructor = nullptr; + + // FIXME: This kinda sucks. +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, Type) \ + if (is(exemplar)) \ + typed_array_default_constructor = global_object.snake_name##_constructor(); + JS_ENUMERATE_TYPED_ARRAYS +#undef __JS_ENUMERATE + + VERIFY(typed_array_default_constructor); + + auto* constructor = species_constructor(global_object, exemplar, *typed_array_default_constructor); + if (vm.exception()) + return nullptr; + + auto* result = typed_array_create(global_object, *constructor, move(arguments)); + if (vm.exception()) + return nullptr; + + if (result->content_type() != exemplar.content_type()) { + vm.throw_exception(global_object, ErrorType::TypedArrayContentTypeMismatch, result->class_name(), exemplar.class_name()); + return nullptr; + } + + return result; +} + // 23.2.3.18 get %TypedArray%.prototype.length, https://tc39.es/ecma262/#sec-get-%typedarray%.prototype.length JS_DEFINE_NATIVE_GETTER(TypedArrayPrototype::length_getter) { @@ -991,4 +1024,76 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::copy_within) return typed_array; } +// 23.2.3.9 %TypedArray%.prototype.filter ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.filter +JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::filter) +{ + // 1. Let O be the this value. + // 2. Perform ? ValidateTypedArray(O). + auto* typed_array = typed_array_from(vm, global_object); + if (!typed_array) + return {}; + + // 3. Let len be O.[[ArrayLength]]. + auto initial_length = typed_array->array_length(); + + // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. + auto* callback_function = callback_from_args(global_object, "filter"); + if (!callback_function) + return {}; + + // 5. Let kept be a new empty List. + MarkedValueList kept(vm.heap()); + + // 7. Let captured be 0. + size_t captured = 0; + + auto this_value = vm.argument(1); + + // 5. Let k be 0. + // 8. Repeat, while k < len, + for (size_t i = 0; i < initial_length; ++i) { + // a. Let Pk be ! ToString(𝔽(k)). + // b. Let kValue be ! Get(O, Pk). + auto value = typed_array->get(i); + + // c. Let selected be ! ToBoolean(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)). + auto callback_result = vm.call(*callback_function, this_value, value, Value((i32)i), typed_array); + if (vm.exception()) + return {}; + + // d. If selected is true, then + if (callback_result.to_boolean()) { + // i. Append kValue to the end of kept. + kept.append(value); + + // ii. Set captured to captured + 1. + ++captured; + } + + // e. Set k to k + 1. + } + + // 9. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(captured) »). + MarkedValueList arguments(vm.heap()); + arguments.empend(captured); + auto* filter_array = typed_array_species_create(global_object, *typed_array, move(arguments)); + if (vm.exception()) + return {}; + + // 10. Let n be 0. + size_t index = 0; + + // 11. For each element e of kept, do + for (auto& value : kept) { + // a. Perform ! Set(A, ! ToString(𝔽(n)), e, true). + filter_array->set(index, value, true); + + // b. Set n to n + 1. + ++index; + } + + // 12. Return A. + return filter_array; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h index 0e34c94080..71f2f9ad81 100644 --- a/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h @@ -44,6 +44,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(set); JS_DECLARE_NATIVE_FUNCTION(reverse); JS_DECLARE_NATIVE_FUNCTION(copy_within); + JS_DECLARE_NATIVE_FUNCTION(filter); }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.filter.js b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.filter.js new file mode 100644 index 0000000000..824aca9c3a --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.filter.js @@ -0,0 +1,217 @@ +const TYPED_ARRAYS = [ + Uint8Array, + Uint8ClampedArray, + Uint16Array, + Uint32Array, + Int8Array, + Int16Array, + Int32Array, + Float32Array, + Float64Array, +]; + +const BIGINT_TYPED_ARRAYS = [BigUint64Array, BigInt64Array]; + +test("length is 1", () => { + TYPED_ARRAYS.forEach(T => { + expect(T.prototype.filter).toHaveLength(1); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + expect(T.prototype.filter).toHaveLength(1); + }); +}); + +describe("errors", () => { + function argumentErrorTests(T) { + test(`requires at least one argument (${T.name})`, () => { + expect(() => { + new T().filter(); + }).toThrowWithMessage( + TypeError, + "TypedArray.prototype.filter() requires at least one argument" + ); + }); + + test(`callback must be a function (${T.name})`, () => { + expect(() => { + new T().filter(undefined); + }).toThrowWithMessage(TypeError, "undefined is not a function"); + }); + } + + TYPED_ARRAYS.forEach(T => argumentErrorTests(T)); + BIGINT_TYPED_ARRAYS.forEach(T => argumentErrorTests(T)); + + test("Symbol.species returns a typed array with a different content type", () => { + TYPED_ARRAYS.forEach(T => { + class TypedArray extends T { + static get [Symbol.species]() { + return BigUint64Array; + } + } + + let result; + + expect(() => { + result = new TypedArray().filter(() => {}); + }).toThrowWithMessage(TypeError, `Can't create BigUint64Array from ${T.name}`); + + expect(result).toBeUndefined(); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + class TypedArray extends T { + static get [Symbol.species]() { + return Uint32Array; + } + } + + let result; + + expect(() => { + result = new TypedArray().filter(() => {}); + }).toThrowWithMessage(TypeError, `Can't create Uint32Array from ${T.name}`); + + expect(result).toBeUndefined(); + }); + }); + + test("Symbol.species doesn't return a typed array", () => { + TYPED_ARRAYS.forEach(T => { + class TypedArray extends T { + static get [Symbol.species]() { + return Array; + } + } + + let result; + + expect(() => { + result = new TypedArray().filter(() => {}); + }).toThrowWithMessage(TypeError, "Not a TypedArray object"); + + expect(result).toBeUndefined(); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + class TypedArray extends T { + static get [Symbol.species]() { + return Array; + } + } + + let result; + + expect(() => { + result = new TypedArray().filter(() => {}); + }).toThrowWithMessage(TypeError, "Not a TypedArray object"); + + expect(result).toBeUndefined(); + }); + }); +}); + +describe("normal behaviour", () => { + test("never calls callback with empty array", () => { + TYPED_ARRAYS.forEach(T => { + let callbackCalled = 0; + expect( + new T([]).filter(() => { + callbackCalled++; + }) + ).toHaveLength(0); + expect(callbackCalled).toBe(0); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + let callbackCalled = 0; + expect( + new T([]).filter(() => { + callbackCalled++; + }) + ).toHaveLength(0); + expect(callbackCalled).toBe(0); + }); + }); + + test("calls callback once for every item", () => { + TYPED_ARRAYS.forEach(T => { + let callbackCalled = 0; + expect( + new T([1, 2, 3]).filter(() => { + callbackCalled++; + }) + ).toHaveLength(0); + expect(callbackCalled).toBe(3); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + let callbackCalled = 0; + expect( + new T([1n, 2n, 3n]).filter(() => { + callbackCalled++; + }) + ).toHaveLength(0); + expect(callbackCalled).toBe(3); + }); + }); + + test("can filter based on callback return value", () => { + TYPED_ARRAYS.forEach(T => { + const evenNumbers = new T([0, 1, 2, 3, 4, 5, 6, 7]).filter(x => x % 2 === 0); + expect(evenNumbers).toHaveLength(4); + expect(evenNumbers[0]).toBe(0); + expect(evenNumbers[1]).toBe(2); + expect(evenNumbers[2]).toBe(4); + expect(evenNumbers[3]).toBe(6); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + const evenNumbers = new T([0n, 1n, 2n, 3n, 4n, 5n, 6n, 7n]).filter(x => x % 2n === 0n); + expect(evenNumbers).toHaveLength(4); + expect(evenNumbers[0]).toBe(0n); + expect(evenNumbers[1]).toBe(2n); + expect(evenNumbers[2]).toBe(4n); + expect(evenNumbers[3]).toBe(6n); + }); + }); + + test("Symbol.species returns a typed array with a matching content type", () => { + TYPED_ARRAYS.forEach(T => { + class TypedArray extends T { + static get [Symbol.species]() { + return Uint32Array; + } + } + + let result; + + expect(() => { + result = new TypedArray([1, 2, 3]).filter(value => value % 2 === 0); + }).not.toThrowWithMessage(TypeError, `Can't create Uint32Array from ${T.name}`); + + expect(result).toBeInstanceOf(Uint32Array); + expect(result).toHaveLength(1); + expect(result[0]).toBe(2); + }); + + BIGINT_TYPED_ARRAYS.forEach(T => { + class TypedArray extends T { + static get [Symbol.species]() { + return BigUint64Array; + } + } + + let result; + + expect(() => { + result = new TypedArray([1n, 2n, 3n]).filter(value => value % 2n === 0n); + }).not.toThrowWithMessage(TypeError, `Can't create BigUint64Array from ${T.name}`); + + expect(result).toBeInstanceOf(BigUint64Array); + expect(result).toHaveLength(1); + expect(result[0]).toBe(2n); + }); + }); +}); -- cgit v1.2.3