Jelajahi Sumber

LibJS: Implement proposed TypedArray.prototype.findLast{,Index}

Proposal: https://tc39.es/proposal-array-find-from-last/
davidot 4 tahun lalu
induk
melakukan
871a29884d

+ 58 - 0
Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp

@@ -34,6 +34,8 @@ void TypedArrayPrototype::initialize(GlobalObject& object)
     define_native_function(vm.names.fill, fill, 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.forEach, for_each, 1, attr);
     define_native_function(vm.names.includes, includes, 1, attr);
     define_native_function(vm.names.indexOf, index_of, 1, attr);
@@ -129,6 +131,34 @@ static void for_each_item(VM& vm, GlobalObject& global_object, const String& nam
     }
 }
 
+static void for_each_item_from_last(VM& vm, GlobalObject& global_object, const String& name, Function<IterationDecision(size_t index, Value value, Value callback_result)> callback)
+{
+    auto* typed_array = validate_typed_array_from_this(global_object);
+    if (!typed_array)
+        return;
+
+    auto initial_length = typed_array->array_length();
+
+    auto* callback_function = callback_from_args(global_object, name);
+    if (!callback_function)
+        return;
+
+    auto this_value = vm.argument(1);
+
+    for (ssize_t i = (ssize_t)initial_length - 1; i >= 0; --i) {
+        auto value = typed_array->get(i);
+        if (vm.exception())
+            return;
+
+        auto callback_result = vm.call(*callback_function, this_value, value, Value((i32)i), typed_array);
+        if (vm.exception())
+            return;
+
+        if (callback(i, value, callback_result) == IterationDecision::Break)
+            break;
+    }
+}
+
 // 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)
 {
@@ -303,6 +333,34 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::find_index)
     return Value(result_index);
 }
 
+// 4 %TypedArray%.prototype.findLast ( predicate [ , thisArg ] ), https://tc39.es/proposal-array-find-from-last/index.html#sec-%typedarray%.prototype.findlast
+JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::find_last)
+{
+    auto result = js_undefined();
+    for_each_item_from_last(vm, global_object, "findLast", [&](auto, auto value, auto callback_result) {
+        if (callback_result.to_boolean()) {
+            result = value;
+            return IterationDecision::Break;
+        }
+        return IterationDecision::Continue;
+    });
+    return result;
+}
+
+// 5 %TypedArray%.prototype.findLastIndex ( predicate [ , thisArg ] ), https://tc39.es/proposal-array-find-from-last/index.html#sec-%typedarray%.prototype.findlastindex
+JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::find_last_index)
+{
+    auto result_index = -1;
+    for_each_item_from_last(vm, global_object, "findLastIndex", [&](auto index, auto, auto callback_result) {
+        if (callback_result.to_boolean()) {
+            result_index = index;
+            return IterationDecision::Break;
+        }
+        return IterationDecision::Continue;
+    });
+    return Value(result_index);
+}
+
 // 23.2.3.12 %TypedArray%.prototype.forEach ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.foreach
 JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::for_each)
 {

+ 2 - 0
Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h

@@ -30,6 +30,8 @@ private:
     JS_DECLARE_NATIVE_FUNCTION(fill);
     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(for_each);
     JS_DECLARE_NATIVE_FUNCTION(includes);
     JS_DECLARE_NATIVE_FUNCTION(index_of);

+ 121 - 0
Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLast.js

@@ -0,0 +1,121 @@
+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.findLast).toHaveLength(1);
+    });
+
+    BIGINT_TYPED_ARRAYS.forEach(T => {
+        expect(T.prototype.findLast).toHaveLength(1);
+    });
+});
+
+describe("errors", () => {
+    function errorTests(T) {
+        test(`requires at least one argument (${T.name})`, () => {
+            expect(() => {
+                new T().findLast();
+            }).toThrowWithMessage(
+                TypeError,
+                "TypedArray.prototype.findLast() requires at least one argument"
+            );
+        });
+
+        test(`callback must be a function (${T.name})`, () => {
+            expect(() => {
+                new T().findLast(undefined);
+            }).toThrowWithMessage(TypeError, "undefined is not a function");
+        });
+    }
+
+    TYPED_ARRAYS.forEach(T => errorTests(T));
+    BIGINT_TYPED_ARRAYS.forEach(T => errorTests(T));
+});
+
+describe("normal behaviour", () => {
+    test("basic functionality", () => {
+        TYPED_ARRAYS.forEach(T => {
+            const typedArray = new T([1, 2, 3]);
+
+            expect(typedArray.findLast(value => value === 1)).toBe(1);
+            expect(typedArray.findLast(value => value === 2)).toBe(2);
+            expect(typedArray.findLast(value => value === 3)).toBe(3);
+            expect(typedArray.findLast((value, index) => index === 1)).toBe(2);
+            expect(typedArray.findLast(value => value == "1")).toBe(1);
+            expect(typedArray.findLast(value => value === 10)).toBeUndefined();
+
+            const typedArrayDuplicates = new T([2, 1, 2, 3, 1]);
+
+            expect(typedArrayDuplicates.findLast(value => value < 3)).toBe(1);
+            expect(typedArrayDuplicates.findLast(value => value < 2)).toBe(1);
+            expect(typedArrayDuplicates.findLast(value => value > 1)).toBe(3);
+        });
+
+        BIGINT_TYPED_ARRAYS.forEach(T => {
+            const typedArray = new T([1n, 2n, 3n]);
+
+            expect(typedArray.findLast(value => value === 1n)).toBe(1n);
+            expect(typedArray.findLast(value => value === 2n)).toBe(2n);
+            expect(typedArray.findLast(value => value === 3n)).toBe(3n);
+            expect(typedArray.findLast((value, index) => index === 1)).toBe(2n);
+            expect(typedArray.findLast(value => value == 1)).toBe(1n);
+            expect(typedArray.findLast(value => value == "1")).toBe(1n);
+            expect(typedArray.findLast(value => value === 1)).toBeUndefined();
+
+            const typedArrayDuplicates = new T([2n, 1n, 2n, 3n, 1n]);
+
+            expect(typedArrayDuplicates.findLast(value => value < 3)).toBe(1n);
+            expect(typedArrayDuplicates.findLast(value => value < 2)).toBe(1n);
+            expect(typedArrayDuplicates.findLast(value => value > 1)).toBe(3n);
+        });
+    });
+
+    test("never calls callback with empty array", () => {
+        function emptyTest(T) {
+            var callbackCalled = 0;
+            expect(
+                new T().findLast(() => {
+                    callbackCalled++;
+                })
+            ).toBeUndefined();
+            expect(callbackCalled).toBe(0);
+        }
+
+        TYPED_ARRAYS.forEach(T => emptyTest(T));
+        BIGINT_TYPED_ARRAYS.forEach(T => emptyTest(T));
+    });
+
+    test("calls callback once for every item", () => {
+        TYPED_ARRAYS.forEach(T => {
+            var callbackCalled = 0;
+            expect(
+                new T([1, 2, 3]).findLast(() => {
+                    callbackCalled++;
+                })
+            ).toBeUndefined();
+            expect(callbackCalled).toBe(3);
+        });
+
+        BIGINT_TYPED_ARRAYS.forEach(T => {
+            var callbackCalled = 0;
+            expect(
+                new T([1n, 2n, 3n]).findLast(() => {
+                    callbackCalled++;
+                })
+            ).toBeUndefined();
+            expect(callbackCalled).toBe(3);
+        });
+    });
+});

+ 121 - 0
Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.findLastIndex.js

@@ -0,0 +1,121 @@
+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.findLastIndex).toHaveLength(1);
+    });
+
+    BIGINT_TYPED_ARRAYS.forEach(T => {
+        expect(T.prototype.findLastIndex).toHaveLength(1);
+    });
+});
+
+describe("errors", () => {
+    function errorTests(T) {
+        test(`requires at least one argument (${T.name})`, () => {
+            expect(() => {
+                new T().findLastIndex();
+            }).toThrowWithMessage(
+                TypeError,
+                "TypedArray.prototype.findLastIndex() requires at least one argument"
+            );
+        });
+
+        test(`callback must be a function (${T.name})`, () => {
+            expect(() => {
+                new T().findLastIndex(undefined);
+            }).toThrowWithMessage(TypeError, "undefined is not a function");
+        });
+    }
+
+    TYPED_ARRAYS.forEach(T => errorTests(T));
+    BIGINT_TYPED_ARRAYS.forEach(T => errorTests(T));
+});
+
+describe("normal behaviour", () => {
+    test("basic functionality", () => {
+        TYPED_ARRAYS.forEach(T => {
+            const typedArray = new T([1, 2, 3]);
+
+            expect(typedArray.findLastIndex(value => value === 1)).toBe(0);
+            expect(typedArray.findLastIndex(value => value === 2)).toBe(1);
+            expect(typedArray.findLastIndex(value => value === 3)).toBe(2);
+            expect(typedArray.findLastIndex((value, index) => index === 1)).toBe(1);
+            expect(typedArray.findLastIndex(value => value == "1")).toBe(0);
+            expect(typedArray.findLastIndex(value => value === 10)).toBe(-1);
+
+            const typedArrayDuplicates = new T([1, 2, 3, 1]);
+
+            expect(typedArrayDuplicates.findLastIndex(value => value === 1)).toBe(3);
+            expect(typedArrayDuplicates.findLastIndex(value => value === 2)).toBe(1);
+            expect(typedArrayDuplicates.findLastIndex(value => value === 3)).toBe(2);
+        });
+
+        BIGINT_TYPED_ARRAYS.forEach(T => {
+            const typedArray = new T([1n, 2n, 3n]);
+
+            expect(typedArray.findLastIndex(value => value === 1n)).toBe(0);
+            expect(typedArray.findLastIndex(value => value === 2n)).toBe(1);
+            expect(typedArray.findLastIndex(value => value === 3n)).toBe(2);
+            expect(typedArray.findLastIndex((value, index) => index === 1)).toBe(1);
+            expect(typedArray.findLastIndex(value => value == 1)).toBe(0);
+            expect(typedArray.findLastIndex(value => value == "1")).toBe(0);
+            expect(typedArray.findLastIndex(value => value === 1)).toBe(-1);
+
+            const typedArrayDuplicates = new T([1n, 2n, 3n, 1n]);
+
+            expect(typedArrayDuplicates.findLastIndex(value => value === 1n)).toBe(3);
+            expect(typedArrayDuplicates.findLastIndex(value => value === 2n)).toBe(1);
+            expect(typedArrayDuplicates.findLastIndex(value => value === 3n)).toBe(2);
+        });
+    });
+
+    test("never calls callback with empty array", () => {
+        function emptyTest(T) {
+            var callbackCalled = 0;
+            expect(
+                new T().findLastIndex(() => {
+                    callbackCalled++;
+                })
+            ).toBe(-1);
+            expect(callbackCalled).toBe(0);
+        }
+
+        TYPED_ARRAYS.forEach(T => emptyTest(T));
+        BIGINT_TYPED_ARRAYS.forEach(T => emptyTest(T));
+    });
+
+    test("calls callback once for every item", () => {
+        TYPED_ARRAYS.forEach(T => {
+            var callbackCalled = 0;
+            expect(
+                new T([1, 2, 3]).findLastIndex(() => {
+                    callbackCalled++;
+                })
+            ).toBe(-1);
+            expect(callbackCalled).toBe(3);
+        });
+
+        BIGINT_TYPED_ARRAYS.forEach(T => {
+            var callbackCalled = 0;
+            expect(
+                new T([1n, 2n, 3n]).findLastIndex(() => {
+                    callbackCalled++;
+                })
+            ).toBe(-1);
+            expect(callbackCalled).toBe(3);
+        });
+    });
+});