LibJS: Implement String.prototype.split

This adds a String.prototype.split implementation modelled after 
ECMA262 specification. 

Additionally, `Value::to_u32` was added as an implementation of
the standard `ToUint32` abstract operation.

There is a tiny kludge for when the separator is an empty string. 
Basic tests and visiting google.com prove that this is working.
This commit is contained in:
Marcin Gasperowicz 2021-01-09 21:52:47 +01:00 committed by Andreas Kling
parent b53664a8ef
commit b24ce0b5ee
Notes: sideshowbarker 2024-07-18 23:57:27 +09:00
5 changed files with 140 additions and 0 deletions

View file

@ -28,6 +28,7 @@
#include <AK/Function.h>
#include <AK/StringBuilder.h>
#include <LibJS/Heap/Heap.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/PrimitiveString.h>
@ -59,6 +60,17 @@ static String ak_string_from(VM& vm, GlobalObject& global_object)
return Value(this_object).to_string(global_object);
}
static Optional<size_t> split_match(const String& haystack, size_t start, const String& needle)
{
auto r = needle.length();
auto s = haystack.length();
if (start + r > s)
return {};
if (!haystack.substring_view(start).starts_with(needle))
return {};
return start + r;
}
StringPrototype::StringPrototype(GlobalObject& global_object)
: StringObject(*js_string(global_object.heap(), String::empty()), *global_object.object_prototype())
{
@ -90,6 +102,7 @@ void StringPrototype::initialize(GlobalObject& global_object)
define_native_function(vm.names.substring, substring, 2, attr);
define_native_function(vm.names.includes, includes, 1, attr);
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.well_known_symbol_iterator(), symbol_iterator, 0, attr);
}
@ -507,6 +520,74 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::slice)
return js_string(vm, string_part);
}
JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split)
{
// FIXME Implement the @@split part
auto string = ak_string_from(vm, global_object);
if (string.is_null())
return {};
auto* result = Array::create(global_object);
size_t result_len = 0;
auto limit = static_cast<u32>(MAX_U32);
if (!vm.argument(1).is_undefined()) {
limit = vm.argument(1).to_u32(global_object);
if (vm.exception())
return {};
}
auto separator = vm.argument(0).to_string(global_object);
if (vm.exception())
return {};
if (limit == 0)
return result;
if (vm.argument(0).is_undefined()) {
result->define_property(0, js_string(vm, string));
return result;
}
auto len = string.length();
auto separator_len = separator.length();
if (len == 0) {
if (separator_len > 0)
result->define_property(0, js_string(vm, string));
return result;
}
size_t start = 0;
auto pos = start;
if (separator_len == 0) {
for (pos = 0; pos < len; pos++)
result->define_property(pos, js_string(vm, string.substring(pos, 1)));
return result;
}
while (pos != len) {
auto e = split_match(string, pos, separator);
if (!e.has_value()) {
pos += 1;
continue;
}
auto segment = string.substring_view(start, pos - start);
result->define_property(result_len, js_string(vm, segment));
result_len++;
if (result_len == limit)
return result;
start = e.value();
pos = start;
}
auto rest = string.substring(start, len - start);
result->define_property(result_len, js_string(vm, rest));
return result;
}
JS_DEFINE_NATIVE_FUNCTION(StringPrototype::last_index_of)
{
auto string = ak_string_from(vm, global_object);

View file

@ -61,6 +61,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(concat);
JS_DECLARE_NATIVE_FUNCTION(includes);
JS_DECLARE_NATIVE_FUNCTION(slice);
JS_DECLARE_NATIVE_FUNCTION(split);
JS_DECLARE_NATIVE_FUNCTION(last_index_of);
JS_DECLARE_NATIVE_FUNCTION(symbol_iterator);

View file

@ -470,6 +470,12 @@ i32 Value::as_i32() const
return static_cast<i32>(as_double());
}
u32 Value::as_u32() const
{
ASSERT(as_double() >= 0);
return min((double)as_i32(), MAX_U32);
}
size_t Value::as_size_t() const
{
ASSERT(as_double() >= 0);
@ -494,6 +500,19 @@ i32 Value::to_i32(GlobalObject& global_object) const
return number.as_i32();
}
u32 Value::to_u32(GlobalObject& global_object) const
{
// 7.1.7 ToUint32, https://tc39.es/ecma262/#sec-touint32
auto number = to_number(global_object);
if (global_object.vm().exception())
return INVALID;
if (number.is_nan() || number.is_infinity())
return 0;
if (number.as_double() <= 0)
return 0;
return number.as_u32();
}
size_t Value::to_size_t(GlobalObject& global_object) const
{
// FIXME: Replace uses of this function with to_length/to_index for correct behaviour and remove this eventually.

View file

@ -36,6 +36,8 @@
// 2 ** 53 - 1
static constexpr double MAX_ARRAY_LIKE_INDEX = 9007199254740991.0;
// 2 ** 32 - 1
static constexpr double MAX_U32 = 4294967295.0;
namespace JS {
@ -241,6 +243,7 @@ public:
Function& as_function();
i32 as_i32() const;
u32 as_u32() const;
size_t as_size_t() const;
String to_string(GlobalObject&, bool legacy_null_to_empty_string = false) const;
@ -252,6 +255,7 @@ public:
BigInt* to_bigint(GlobalObject&) const;
double to_double(GlobalObject&) const;
i32 to_i32(GlobalObject&) const;
u32 to_u32(GlobalObject&) const;
size_t to_size_t(GlobalObject&) const;
size_t to_length(GlobalObject&) const;
size_t to_index(GlobalObject&) const;

View file

@ -0,0 +1,35 @@
test("basic functionality", () => {
expect(String.prototype.split).toHaveLength(2);
expect("hello friends".split()).toEqual(["hello friends"]);
expect("hello friends".split("")).toEqual([
"h",
"e",
"l",
"l",
"o",
" ",
"f",
"r",
"i",
"e",
"n",
"d",
"s",
]);
expect("hello friends".split(" ")).toEqual(["hello", "friends"]);
expect("a,b,c,d".split(",")).toEqual(["a", "b", "c", "d"]);
expect(",a,b,c,d".split(",")).toEqual(["", "a", "b", "c", "d"]);
expect("a,b,c,d,".split(",")).toEqual(["a", "b", "c", "d", ""]);
expect("a,b,,c,d".split(",")).toEqual(["a", "b", "", "c", "d"]);
expect(",a,b,,c,d,".split(",")).toEqual(["", "a", "b", "", "c", "d", ""]);
expect(",a,b,,,c,d,".split(",,")).toEqual([",a,b", ",c,d,"]);
});
test("limits", () => {
expect("a b c d".split(" ", 0)).toEqual([]);
expect("a b c d".split(" ", 1)).toEqual(["a"]);
expect("a b c d".split(" ", 3)).toEqual(["a", "b", "c"]);
expect("a b c d".split(" ", 100)).toEqual(["a", "b", "c", "d"]);
});