summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorLuke <luke.wilde@live.co.uk>2021-07-09 06:20:45 +0100
committerLinus Groh <mail@linusgroh.de>2021-07-09 10:15:45 +0100
commitbc6f619344e10461ce03d4d996f2c81a2199992a (patch)
treedfe4d7c42ea172a74710fad0f4377b1ec4167149 /Userland
parent53bc3f8e3b5944438ef058fc84d32adf9ab2273d (diff)
downloadserenity-bc6f619344e10461ce03d4d996f2c81a2199992a.zip
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.
Diffstat (limited to 'Userland')
-rw-r--r--Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp105
-rw-r--r--Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h1
-rw-r--r--Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.filter.js217
3 files changed, 323 insertions, 0 deletions
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<ClassName>(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<TypeError>(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);
+ });
+ });
+});