浏览代码

LibJS: Implement String.prototype.lastIndexOf with UTF-16 code units

Timothy Flynn 4 年之前
父节点
当前提交
f920e121b3

+ 30 - 19
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())
+    auto string = utf16_string_from(vm, global_object);
+    if (vm.exception())
         return {};
-    auto search_string = vm.argument(0).to_string(global_object);
+
+    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<double>(0), static_cast<double>(max_index));
-    }
+    double pos = position.is_nan() ? static_cast<double>(INFINITY) : position.to_integer_or_infinity(global_object);
+    if (vm.exception())
+        return {};
+
+    size_t start = clamp(pos, static_cast<double>(0), static_cast<double>(string_length));
+    Optional<size_t> 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;
+            }
+        }
 
-    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);
+        if (is_match)
+            last_index = k;
     }
 
-    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

+ 8 - 0
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);
+});