diff --git a/AK/Utf16StringBase.h b/AK/Utf16StringBase.h index ef57b607710..f245c99ecf3 100644 --- a/AK/Utf16StringBase.h +++ b/AK/Utf16StringBase.h @@ -220,6 +220,7 @@ public: [[nodiscard]] ALWAYS_INLINE bool contains(Utf16View const& needle) const { return find_code_unit_offset(needle).has_value(); } [[nodiscard]] ALWAYS_INLINE bool starts_with(Utf16View const& needle) const { return utf16_view().starts_with(needle); } + [[nodiscard]] ALWAYS_INLINE bool ends_with(Utf16View const& needle) const { return utf16_view().ends_with(needle); } // This is primarily interesting to unit tests. [[nodiscard]] constexpr bool has_short_ascii_storage() const diff --git a/AK/Utf16View.h b/AK/Utf16View.h index 24ce71d62e4..4103b676996 100644 --- a/AK/Utf16View.h +++ b/AK/Utf16View.h @@ -456,24 +456,24 @@ public: [[nodiscard]] constexpr bool starts_with(Utf16View const& needle) const { - if (needle.is_empty()) + auto needle_length = needle.length_in_code_units(); + if (needle_length == 0) return true; - if (is_empty()) - return false; - if (needle.length_in_code_units() > length_in_code_units()) + if (is_empty() || needle_length > length_in_code_units()) return false; - if (has_ascii_storage() && needle.has_ascii_storage()) - return ascii_span().starts_with(needle.ascii_span()); - if (!has_ascii_storage() && !needle.has_ascii_storage()) - return utf16_span().starts_with(needle.utf16_span()); + return substring_view(0, needle_length) == needle; + } - for (auto this_it = begin(), needle_it = needle.begin(); needle_it != needle.end(); ++needle_it, ++this_it) { - if (*this_it != *needle_it) - return false; - } + [[nodiscard]] constexpr bool ends_with(Utf16View const& needle) const + { + auto needle_length = needle.length_in_code_units(); + if (needle_length == 0) + return true; + if (is_empty() || needle_length > length_in_code_units()) + return false; - return true; + return substring_view(length_in_code_units() - needle_length, needle_length) == needle; } // https://infra.spec.whatwg.org/#code-unit-less-than diff --git a/Tests/AK/TestUtf16View.cpp b/Tests/AK/TestUtf16View.cpp index 0b50522a5ac..3a3bd759f9c 100644 --- a/Tests/AK/TestUtf16View.cpp +++ b/Tests/AK/TestUtf16View.cpp @@ -568,6 +568,32 @@ TEST_CASE(starts_with) EXPECT(!emoji.starts_with(u"πŸ™ƒ"sv)); } +TEST_CASE(ends_with) +{ + EXPECT(Utf16View {}.ends_with(u""sv)); + EXPECT(!Utf16View {}.ends_with(u" "sv)); + + EXPECT(u"a"sv.ends_with(u""sv)); + EXPECT(u"a"sv.ends_with(u"a"sv)); + EXPECT(!u"a"sv.ends_with(u"b"sv)); + EXPECT(!u"a"sv.ends_with(u"ab"sv)); + + EXPECT(u"abc"sv.ends_with(u""sv)); + EXPECT(u"abc"sv.ends_with(u"c"sv)); + EXPECT(u"abc"sv.ends_with(u"bc"sv)); + EXPECT(u"abc"sv.ends_with(u"abc"sv)); + EXPECT(!u"abc"sv.ends_with(u"b"sv)); + EXPECT(!u"abc"sv.ends_with(u"ab"sv)); + + auto emoji = u"πŸ˜€πŸ™ƒ"sv; + + EXPECT(emoji.ends_with(u""sv)); + EXPECT(emoji.ends_with(u"πŸ™ƒ"sv)); + EXPECT(emoji.ends_with(u"πŸ˜€πŸ™ƒ"sv)); + EXPECT(!emoji.ends_with(u"a"sv)); + EXPECT(!emoji.ends_with(u"πŸ˜€"sv)); +} + TEST_CASE(find_code_unit_offset) { auto conversion_result = Utf16String::from_utf8("πŸ˜€fooπŸ˜€bar"sv);