From f920e121b36ff8cf09bf70d2269649ad103b13ea Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 19 Jul 2021 15:29:20 -0400 Subject: [PATCH] LibJS: Implement String.prototype.lastIndexOf with UTF-16 code units --- .../LibJS/Runtime/StringPrototype.cpp | 53 +++++++++++-------- .../String/String.prototype.lastIndexOf.js | 8 +++ 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp index 9bbadd5077..e3401dc188 100644 --- a/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/StringPrototype.cpp @@ -723,34 +723,45 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::split) // 22.1.3.9 String.prototype.lastIndexOf ( searchString [ , position ] ), https://tc39.es/ecma262/#sec-string.prototype.lastindexof JS_DEFINE_NATIVE_FUNCTION(StringPrototype::last_index_of) { - auto string = ak_string_from(vm, global_object); - if (!string.has_value()) - return {}; - auto search_string = vm.argument(0).to_string(global_object); + auto string = utf16_string_from(vm, global_object); if (vm.exception()) return {}; + + auto search_string = vm.argument(0).to_utf16_string(global_object); + if (vm.exception()) + return {}; + + Utf16View utf16_string_view { string }; + auto string_length = utf16_string_view.length_in_code_units(); + + Utf16View utf16_search_view { search_string }; + auto search_length = utf16_search_view.length_in_code_units(); + auto position = vm.argument(1).to_number(global_object); if (vm.exception()) return {}; - if (search_string.length() > string->length()) - return Value(-1); - auto max_index = string->length() - search_string.length(); - auto from_index = max_index; - if (!position.is_nan()) { - // FIXME: from_index should index a UTF-16 code_point view of the string. - auto p = position.to_integer_or_infinity(global_object); - if (vm.exception()) - return {}; - from_index = clamp(p, static_cast(0), static_cast(max_index)); + double pos = position.is_nan() ? static_cast(INFINITY) : position.to_integer_or_infinity(global_object); + if (vm.exception()) + return {}; + + size_t start = clamp(pos, static_cast(0), static_cast(string_length)); + Optional last_index; + + for (size_t k = 0; (k <= start) && (k + search_length <= string_length); ++k) { + bool is_match = true; + + for (size_t j = 0; j < search_length; ++j) { + if (utf16_string_view.code_unit_at(k + j) != utf16_search_view.code_unit_at(j)) { + is_match = false; + break; + } + } + + if (is_match) + last_index = k; } - for (i32 i = from_index; i >= 0; --i) { - auto part_view = string->substring_view(i, search_string.length()); - if (part_view == search_string) - return Value(i); - } - - return Value(-1); + return last_index.has_value() ? Value(*last_index) : Value(-1); } // 3.1 String.prototype.at ( index ), https://tc39.es/proposal-relative-indexing-method/#sec-string.prototype.at diff --git a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.lastIndexOf.js b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.lastIndexOf.js index ce69fc3214..5bb6a42f35 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.lastIndexOf.js +++ b/Userland/Libraries/LibJS/Tests/builtins/String/String.prototype.lastIndexOf.js @@ -20,3 +20,11 @@ test("basic functionality", () => { expect("hello friends serenity".lastIndexOf("s", 13)).toBe(12); expect("hello".lastIndexOf("serenity")).toBe(-1); }); + +test("UTF-16", () => { + var s = "😀"; + expect(s.lastIndexOf("😀")).toBe(0); + expect(s.lastIndexOf("\ud83d")).toBe(0); + expect(s.lastIndexOf("\ude00")).toBe(1); + expect(s.lastIndexOf("a")).toBe(-1); +});