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
This commit is contained in:
Linus Groh 2021-03-12 18:13:12 +01:00 committed by Andreas Kling
parent 9769542bc2
commit 2d8362cceb
Notes: sideshowbarker 2024-07-18 21:27:58 +09:00
10 changed files with 155 additions and 10 deletions

View file

@ -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());
}
}

View file

@ -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);
};
}

View file

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

View file

@ -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);

View file

@ -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);
};

View file

@ -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());
}
}

View file

@ -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);
};
}

View file

@ -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();
});

View file

@ -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();
});

View file

@ -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();
});
});