Browse Source

LibJS: Implement 'Relative Indexing Method' proposal (.at())

Still stage 3, but already implemented in major engines and unlikely to
change - there isn't much to change here anyway. :^)

See:

- https://github.com/tc39/proposal-relative-indexing-method
- https://tc39.es/proposal-relative-indexing-method/
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at
Linus Groh 4 years ago
parent
commit
2d8362cceb

+ 28 - 1
Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp

@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
- * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * Copyright (c) 2020-2021, Linus Groh <mail@linusgroh.de>
  * Copyright (c) 2020, Marcin Gasperowicz <xnooga@gmail.com>
  * All rights reserved.
  *
@@ -81,6 +81,7 @@ void ArrayPrototype::initialize(GlobalObject& global_object)
     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.at, at, 1, attr);
 
     // Use define_property here instead of define_native_function so that
     // Object.is(Array.prototype[Symbol.iterator], Array.prototype.values)
@@ -1081,4 +1082,30 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::flat)
         return {};
     return new_array;
 }
+
+JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::at)
+{
+    auto* this_object = vm.this_value(global_object).to_object(global_object);
+    if (!this_object)
+        return {};
+    auto length = length_of_array_like(global_object, *this_object);
+    if (vm.exception())
+        return {};
+    auto relative_index = vm.argument(0).to_integer_or_infinity(global_object);
+    if (vm.exception())
+        return {};
+    if (Value(relative_index).is_infinity())
+        return js_undefined();
+    Checked<size_t> 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 this_object->get(index.value());
+}
+
 }

+ 2 - 1
Userland/Libraries/LibJS/Runtime/ArrayPrototype.h

@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
- * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * Copyright (c) 2020-2021, Linus Groh <mail@linusgroh.de>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -67,6 +67,7 @@ private:
     JS_DECLARE_NATIVE_FUNCTION(fill);
     JS_DECLARE_NATIVE_FUNCTION(values);
     JS_DECLARE_NATIVE_FUNCTION(flat);
+    JS_DECLARE_NATIVE_FUNCTION(at);
 };
 
 }

+ 1 - 0
Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h

@@ -68,6 +68,7 @@ namespace JS {
     P(asUintN)                               \
     P(asin)                                  \
     P(asinh)                                 \
+    P(at)                                    \
     P(atan)                                  \
     P(atan2)                                 \
     P(atanh)                                 \

+ 27 - 2
Userland/Libraries/LibJS/Runtime/StringPrototype.cpp

@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
- * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * Copyright (c) 2020-2021, Linus Groh <mail@linusgroh.de>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -25,6 +25,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#include <AK/Checked.h>
 #include <AK/Function.h>
 #include <AK/StringBuilder.h>
 #include <LibJS/Heap/Heap.h>
@@ -82,7 +83,7 @@ void StringPrototype::initialize(GlobalObject& global_object)
     StringObject::initialize(global_object);
     u8 attr = Attribute::Writable | Attribute::Configurable;
 
-    define_native_property(vm.names.length, length_getter, {}, 0);
+    define_native_property(vm.names.length, length_getter, nullptr, 0);
     define_native_function(vm.names.charAt, char_at, 1, attr);
     define_native_function(vm.names.charCodeAt, char_code_at, 1, attr);
     define_native_function(vm.names.repeat, repeat, 1, attr);
@@ -104,6 +105,7 @@ void StringPrototype::initialize(GlobalObject& global_object)
     define_native_function(vm.names.slice, slice, 2, attr);
     define_native_function(vm.names.split, split, 2, attr);
     define_native_function(vm.names.lastIndexOf, last_index_of, 1, attr);
+    define_native_function(vm.names.at, at, 1, attr);
     define_native_function(vm.well_known_symbol_iterator(), symbol_iterator, 0, attr);
 }
 
@@ -612,6 +614,29 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::last_index_of)
     return Value(-1);
 }
 
+JS_DEFINE_NATIVE_FUNCTION(StringPrototype::at)
+{
+    auto string = ak_string_from(vm, global_object);
+    if (string.is_null())
+        return {};
+    auto length = string.length();
+    auto relative_index = vm.argument(0).to_integer_or_infinity(global_object);
+    if (vm.exception())
+        return {};
+    if (Value(relative_index).is_infinity())
+        return js_undefined();
+    Checked<size_t> 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 js_string(vm, String::formatted("{}", string[index.value()]));
+}
+
 JS_DEFINE_NATIVE_FUNCTION(StringPrototype::symbol_iterator)
 {
     auto this_object = vm.this_value(global_object);

+ 3 - 3
Userland/Libraries/LibJS/Runtime/StringPrototype.h

@@ -39,6 +39,8 @@ public:
     virtual ~StringPrototype() override;
 
 private:
+    JS_DECLARE_NATIVE_GETTER(length_getter);
+
     JS_DECLARE_NATIVE_FUNCTION(char_at);
     JS_DECLARE_NATIVE_FUNCTION(char_code_at);
     JS_DECLARE_NATIVE_FUNCTION(repeat);
@@ -52,9 +54,6 @@ private:
     JS_DECLARE_NATIVE_FUNCTION(pad_end);
     JS_DECLARE_NATIVE_FUNCTION(substring);
     JS_DECLARE_NATIVE_FUNCTION(substr);
-
-    JS_DECLARE_NATIVE_GETTER(length_getter);
-
     JS_DECLARE_NATIVE_FUNCTION(trim);
     JS_DECLARE_NATIVE_FUNCTION(trim_start);
     JS_DECLARE_NATIVE_FUNCTION(trim_end);
@@ -63,6 +62,7 @@ private:
     JS_DECLARE_NATIVE_FUNCTION(slice);
     JS_DECLARE_NATIVE_FUNCTION(split);
     JS_DECLARE_NATIVE_FUNCTION(last_index_of);
+    JS_DECLARE_NATIVE_FUNCTION(at);
 
     JS_DECLARE_NATIVE_FUNCTION(symbol_iterator);
 };

+ 28 - 2
Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.cpp

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * Copyright (c) 2020-2021, Linus Groh <mail@linusgroh.de>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -39,8 +39,11 @@ void TypedArrayPrototype::initialize(GlobalObject& object)
 {
     auto& vm = this->vm();
     Object::initialize(object);
+    u8 attr = Attribute::Writable | Attribute::Configurable;
+
     // FIXME: This should be an accessor property
-    define_native_property(vm.names.length, length_getter, {}, Attribute::Configurable);
+    define_native_property(vm.names.length, length_getter, nullptr, Attribute::Configurable);
+    define_native_function(vm.names.at, at, 1, attr);
 }
 
 TypedArrayPrototype::~TypedArrayPrototype()
@@ -67,4 +70,27 @@ JS_DEFINE_NATIVE_GETTER(TypedArrayPrototype::length_getter)
     return Value(typed_array->array_length());
 }
 
+JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::at)
+{
+    auto typed_array = typed_array_from(vm, global_object);
+    if (!typed_array)
+        return {};
+    auto length = typed_array->array_length();
+    auto relative_index = vm.argument(0).to_integer_or_infinity(global_object);
+    if (vm.exception())
+        return {};
+    if (Value(relative_index).is_infinity())
+        return js_undefined();
+    Checked<size_t> 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 typed_array->get(index.value());
+}
+
 }

+ 3 - 1
Userland/Libraries/LibJS/Runtime/TypedArrayPrototype.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, Linus Groh <mail@linusgroh.de>
+ * Copyright (c) 2020-2021, Linus Groh <mail@linusgroh.de>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -40,6 +40,8 @@ public:
 
 private:
     JS_DECLARE_NATIVE_GETTER(length_getter);
+
+    JS_DECLARE_NATIVE_FUNCTION(at);
 };
 
 }

+ 15 - 0
Userland/Libraries/LibJS/Tests/builtins/Array/Array.prototype.at.js

@@ -0,0 +1,15 @@
+test("basic functionality", () => {
+    expect(Array.prototype.at).toHaveLength(1);
+
+    const array = ["a", "b", "c"];
+    expect(array.at(0)).toBe("a");
+    expect(array.at(1)).toBe("b");
+    expect(array.at(2)).toBe("c");
+    expect(array.at(3)).toBeUndefined();
+    expect(array.at(Infinity)).toBeUndefined();
+    expect(array.at(-1)).toBe("c");
+    expect(array.at(-2)).toBe("b");
+    expect(array.at(-3)).toBe("a");
+    expect(array.at(-4)).toBeUndefined();
+    expect(array.at(-Infinity)).toBeUndefined();
+});

+ 15 - 0
Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.at.js

@@ -0,0 +1,15 @@
+test("basic functionality", () => {
+    expect(String.prototype.at).toHaveLength(1);
+
+    const string = "abc";
+    expect(string.at(0)).toBe("a");
+    expect(string.at(1)).toBe("b");
+    expect(string.at(2)).toBe("c");
+    expect(string.at(3)).toBeUndefined();
+    expect(string.at(Infinity)).toBeUndefined();
+    expect(string.at(-1)).toBe("c");
+    expect(string.at(-2)).toBe("b");
+    expect(string.at(-3)).toBe("a");
+    expect(string.at(-4)).toBeUndefined();
+    expect(string.at(-Infinity)).toBeUndefined();
+});

+ 33 - 0
Userland/Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.prototype.at.js

@@ -0,0 +1,33 @@
+// Update when more typed arrays get added
+const TYPED_ARRAYS = [
+    Uint8Array,
+    Uint16Array,
+    Uint32Array,
+    Int8Array,
+    Int16Array,
+    Int32Array,
+    Float32Array,
+    Float64Array,
+];
+
+test("basic functionality", () => {
+    TYPED_ARRAYS.forEach(T => {
+        expect(T.prototype.at).toHaveLength(1);
+
+        const typedArray = new T(3);
+        typedArray[0] = 1;
+        typedArray[1] = 2;
+        typedArray[2] = 3;
+
+        expect(typedArray.at(0)).toBe(1);
+        expect(typedArray.at(1)).toBe(2);
+        expect(typedArray.at(2)).toBe(3);
+        expect(typedArray.at(3)).toBeUndefined();
+        expect(typedArray.at(Infinity)).toBeUndefined();
+        expect(typedArray.at(-1)).toBe(3);
+        expect(typedArray.at(-2)).toBe(2);
+        expect(typedArray.at(-3)).toBe(1);
+        expect(typedArray.at(-4)).toBeUndefined();
+        expect(typedArray.at(-Infinity)).toBeUndefined();
+    });
+});