diff --git a/Libraries/LibJS/Runtime/StringPrototype.cpp b/Libraries/LibJS/Runtime/StringPrototype.cpp index 596bedb6f1..7fa5292e89 100644 --- a/Libraries/LibJS/Runtime/StringPrototype.cpp +++ b/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,17 @@ static String ak_string_from(VM& vm, GlobalObject& global_object) return Value(this_object).to_string(global_object); } +static Optional 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(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); diff --git a/Libraries/LibJS/Runtime/StringPrototype.h b/Libraries/LibJS/Runtime/StringPrototype.h index f443f73c2d..5847ea2117 100644 --- a/Libraries/LibJS/Runtime/StringPrototype.h +++ b/Libraries/LibJS/Runtime/StringPrototype.h @@ -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); diff --git a/Libraries/LibJS/Runtime/Value.cpp b/Libraries/LibJS/Runtime/Value.cpp index a2139856cf..f5aea45e7c 100644 --- a/Libraries/LibJS/Runtime/Value.cpp +++ b/Libraries/LibJS/Runtime/Value.cpp @@ -470,6 +470,12 @@ i32 Value::as_i32() const return static_cast(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. diff --git a/Libraries/LibJS/Runtime/Value.h b/Libraries/LibJS/Runtime/Value.h index 2be347d09f..5cd209510d 100644 --- a/Libraries/LibJS/Runtime/Value.h +++ b/Libraries/LibJS/Runtime/Value.h @@ -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; diff --git a/Libraries/LibJS/Tests/builtins/String/String.prototype.split.js b/Libraries/LibJS/Tests/builtins/String/String.prototype.split.js new file mode 100644 index 0000000000..63d6c69a97 --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/String/String.prototype.split.js @@ -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"]); +});