2023-01-11 13:26:49 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
|
|
*/
|
|
|
|
|
2023-03-04 20:02:58 +00:00
|
|
|
#include <AK/DeprecatedFlyString.h>
|
2023-01-11 13:26:49 +00:00
|
|
|
#include <AK/FlyString.h>
|
|
|
|
#include <AK/HashMap.h>
|
|
|
|
#include <AK/Singleton.h>
|
2024-03-23 13:54:23 +00:00
|
|
|
#include <AK/String.h>
|
|
|
|
#include <AK/StringData.h>
|
2023-01-11 13:26:49 +00:00
|
|
|
#include <AK/StringView.h>
|
|
|
|
#include <AK/Utf8View.h>
|
|
|
|
|
|
|
|
namespace AK {
|
|
|
|
|
2024-03-23 13:54:23 +00:00
|
|
|
struct FlyStringTableHashTraits : public Traits<Detail::StringData const*> {
|
|
|
|
static u32 hash(Detail::StringData const* string) { return string->hash(); }
|
|
|
|
static bool equals(Detail::StringData const* a, Detail::StringData const* b) { return *a == *b; }
|
|
|
|
};
|
|
|
|
|
2023-01-11 13:26:49 +00:00
|
|
|
static auto& all_fly_strings()
|
|
|
|
{
|
2024-03-23 13:54:23 +00:00
|
|
|
static Singleton<HashTable<Detail::StringData const*, FlyStringTableHashTraits>> table;
|
2023-01-11 13:26:49 +00:00
|
|
|
return *table;
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorOr<FlyString> FlyString::from_utf8(StringView string)
|
|
|
|
{
|
2024-03-23 19:03:38 +00:00
|
|
|
if (string.is_empty())
|
|
|
|
return FlyString {};
|
|
|
|
if (string.length() <= Detail::MAX_SHORT_STRING_BYTE_COUNT)
|
|
|
|
return FlyString { TRY(String::from_utf8(string)) };
|
|
|
|
if (auto it = all_fly_strings().find(string.hash(), [&](auto& entry) { return entry->bytes_as_string_view() == string; }); it != all_fly_strings().end())
|
|
|
|
return FlyString { Detail::StringBase(**it) };
|
2023-01-11 13:26:49 +00:00
|
|
|
return FlyString { TRY(String::from_utf8(string)) };
|
|
|
|
}
|
|
|
|
|
2024-03-23 10:33:26 +00:00
|
|
|
FlyString FlyString::from_utf8_without_validation(ReadonlyBytes string)
|
|
|
|
{
|
2024-03-23 19:03:38 +00:00
|
|
|
if (string.is_empty())
|
|
|
|
return FlyString {};
|
|
|
|
if (string.size() <= Detail::MAX_SHORT_STRING_BYTE_COUNT)
|
|
|
|
return FlyString { String::from_utf8_without_validation(string) };
|
|
|
|
if (auto it = all_fly_strings().find(StringView(string).hash(), [&](auto& entry) { return entry->bytes_as_string_view() == string; }); it != all_fly_strings().end())
|
|
|
|
return FlyString { Detail::StringBase(**it) };
|
2024-03-23 10:33:26 +00:00
|
|
|
return FlyString { String::from_utf8_without_validation(string) };
|
|
|
|
}
|
|
|
|
|
2023-01-11 13:26:49 +00:00
|
|
|
FlyString::FlyString(String const& string)
|
|
|
|
{
|
2024-10-28 21:53:16 +00:00
|
|
|
ASSERT(!string.is_invalid());
|
|
|
|
|
2023-01-11 13:26:49 +00:00
|
|
|
if (string.is_short_string()) {
|
2023-10-28 22:58:29 +00:00
|
|
|
m_data = string;
|
2023-01-11 13:26:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-23 13:54:23 +00:00
|
|
|
if (string.m_data->is_fly_string()) {
|
2023-10-28 22:58:29 +00:00
|
|
|
m_data = string;
|
2024-03-23 13:54:23 +00:00
|
|
|
return;
|
|
|
|
}
|
2023-01-11 13:26:49 +00:00
|
|
|
|
2024-03-23 13:54:23 +00:00
|
|
|
auto it = all_fly_strings().find(string.m_data);
|
|
|
|
if (it == all_fly_strings().end()) {
|
|
|
|
m_data = string;
|
|
|
|
all_fly_strings().set(string.m_data);
|
|
|
|
string.m_data->set_fly_string(true);
|
2023-01-11 13:26:49 +00:00
|
|
|
} else {
|
2024-03-23 13:54:23 +00:00
|
|
|
m_data.m_data = *it;
|
|
|
|
m_data.m_data->ref();
|
2023-01-11 13:26:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-14 14:21:55 +00:00
|
|
|
FlyString& FlyString::operator=(String const& string)
|
|
|
|
{
|
|
|
|
*this = FlyString { string };
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
2023-01-11 13:26:49 +00:00
|
|
|
bool FlyString::is_empty() const
|
|
|
|
{
|
|
|
|
return bytes_as_string_view().is_empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned FlyString::hash() const
|
|
|
|
{
|
2023-10-28 22:58:29 +00:00
|
|
|
return m_data.hash();
|
2023-01-11 13:26:49 +00:00
|
|
|
}
|
|
|
|
|
2023-09-05 17:55:21 +00:00
|
|
|
u32 FlyString::ascii_case_insensitive_hash() const
|
|
|
|
{
|
|
|
|
return case_insensitive_string_hash(reinterpret_cast<char const*>(bytes().data()), bytes().size());
|
|
|
|
}
|
|
|
|
|
2023-01-11 13:26:49 +00:00
|
|
|
FlyString::operator String() const
|
|
|
|
{
|
|
|
|
return to_string();
|
|
|
|
}
|
|
|
|
|
|
|
|
String FlyString::to_string() const
|
|
|
|
{
|
2023-10-28 22:58:29 +00:00
|
|
|
Detail::StringBase copy = m_data;
|
|
|
|
return String(move(copy));
|
2023-01-11 13:26:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Utf8View FlyString::code_points() const
|
|
|
|
{
|
|
|
|
return Utf8View { bytes_as_string_view() };
|
|
|
|
}
|
|
|
|
|
|
|
|
ReadonlyBytes FlyString::bytes() const
|
|
|
|
{
|
|
|
|
return bytes_as_string_view().bytes();
|
|
|
|
}
|
|
|
|
|
|
|
|
StringView FlyString::bytes_as_string_view() const
|
|
|
|
{
|
2023-10-28 22:58:29 +00:00
|
|
|
return m_data.bytes();
|
2023-01-11 13:26:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool FlyString::operator==(String const& other) const
|
|
|
|
{
|
2023-10-28 22:58:29 +00:00
|
|
|
return m_data == other;
|
2023-01-11 13:26:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool FlyString::operator==(StringView string) const
|
|
|
|
{
|
|
|
|
return bytes_as_string_view() == string;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FlyString::operator==(char const* string) const
|
|
|
|
{
|
|
|
|
return bytes_as_string_view() == string;
|
|
|
|
}
|
|
|
|
|
2024-03-23 13:54:23 +00:00
|
|
|
void FlyString::did_destroy_fly_string_data(Badge<Detail::StringData>, Detail::StringData const& string_data)
|
2023-01-11 13:26:49 +00:00
|
|
|
{
|
2024-03-23 13:54:23 +00:00
|
|
|
all_fly_strings().remove(&string_data);
|
2023-01-11 13:26:49 +00:00
|
|
|
}
|
|
|
|
|
2023-10-28 22:58:29 +00:00
|
|
|
Detail::StringBase FlyString::data(Badge<String>) const
|
2023-01-11 13:26:49 +00:00
|
|
|
{
|
|
|
|
return m_data;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t FlyString::number_of_fly_strings()
|
|
|
|
{
|
|
|
|
return all_fly_strings().size();
|
|
|
|
}
|
|
|
|
|
2023-03-04 20:02:58 +00:00
|
|
|
DeprecatedFlyString FlyString::to_deprecated_fly_string() const
|
|
|
|
{
|
|
|
|
return DeprecatedFlyString(bytes_as_string_view());
|
|
|
|
}
|
|
|
|
|
2023-03-09 16:45:33 +00:00
|
|
|
ErrorOr<FlyString> FlyString::from_deprecated_fly_string(DeprecatedFlyString const& deprecated_fly_string)
|
|
|
|
{
|
|
|
|
return FlyString::from_utf8(deprecated_fly_string.view());
|
|
|
|
}
|
|
|
|
|
2023-01-11 13:26:49 +00:00
|
|
|
unsigned Traits<FlyString>::hash(FlyString const& fly_string)
|
|
|
|
{
|
2023-03-08 22:11:59 +00:00
|
|
|
return fly_string.hash();
|
2023-01-11 13:26:49 +00:00
|
|
|
}
|
|
|
|
|
2023-09-05 18:05:54 +00:00
|
|
|
int FlyString::operator<=>(FlyString const& other) const
|
|
|
|
{
|
|
|
|
return bytes_as_string_view().compare(other.bytes_as_string_view());
|
|
|
|
}
|
|
|
|
|
2023-01-11 13:26:49 +00:00
|
|
|
ErrorOr<void> Formatter<FlyString>::format(FormatBuilder& builder, FlyString const& fly_string)
|
|
|
|
{
|
|
|
|
return Formatter<StringView>::format(builder, fly_string.bytes_as_string_view());
|
|
|
|
}
|
|
|
|
|
2024-10-14 08:51:15 +00:00
|
|
|
FlyString FlyString::to_ascii_lowercase() const
|
|
|
|
{
|
|
|
|
bool const has_ascii_uppercase = [&] {
|
|
|
|
for (u8 const byte : bytes()) {
|
|
|
|
if (AK::is_ascii_upper_alpha(byte))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}();
|
|
|
|
|
|
|
|
if (!has_ascii_uppercase)
|
|
|
|
return *this;
|
|
|
|
|
|
|
|
Vector<u8> lowercase_bytes;
|
|
|
|
lowercase_bytes.ensure_capacity(bytes().size());
|
|
|
|
for (u8 const byte : bytes()) {
|
|
|
|
if (AK::is_ascii_upper_alpha(byte))
|
|
|
|
lowercase_bytes.unchecked_append(AK::to_ascii_lowercase(byte));
|
|
|
|
else
|
|
|
|
lowercase_bytes.unchecked_append(byte);
|
|
|
|
}
|
|
|
|
return String::from_utf8_without_validation(lowercase_bytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
FlyString FlyString::to_ascii_uppercase() const
|
|
|
|
{
|
|
|
|
bool const has_ascii_lowercase = [&] {
|
|
|
|
for (u8 const byte : bytes()) {
|
|
|
|
if (AK::is_ascii_lower_alpha(byte))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}();
|
|
|
|
|
|
|
|
if (!has_ascii_lowercase)
|
|
|
|
return *this;
|
|
|
|
|
|
|
|
Vector<u8> uppercase_bytes;
|
|
|
|
uppercase_bytes.ensure_capacity(bytes().size());
|
|
|
|
for (u8 const byte : bytes()) {
|
|
|
|
if (AK::is_ascii_lower_alpha(byte))
|
|
|
|
uppercase_bytes.unchecked_append(AK::to_ascii_uppercase(byte));
|
|
|
|
else
|
|
|
|
uppercase_bytes.unchecked_append(byte);
|
|
|
|
}
|
|
|
|
return String::from_utf8_without_validation(uppercase_bytes);
|
|
|
|
}
|
|
|
|
|
2023-03-07 18:53:21 +00:00
|
|
|
bool FlyString::equals_ignoring_ascii_case(FlyString const& other) const
|
|
|
|
{
|
|
|
|
if (*this == other)
|
|
|
|
return true;
|
2023-03-10 07:48:54 +00:00
|
|
|
return StringUtils::equals_ignoring_ascii_case(bytes_as_string_view(), other.bytes_as_string_view());
|
2023-03-07 18:53:21 +00:00
|
|
|
}
|
|
|
|
|
2023-09-04 09:49:29 +00:00
|
|
|
bool FlyString::equals_ignoring_ascii_case(StringView other) const
|
|
|
|
{
|
|
|
|
return StringUtils::equals_ignoring_ascii_case(bytes_as_string_view(), other);
|
|
|
|
}
|
|
|
|
|
2023-11-06 10:59:15 +00:00
|
|
|
bool FlyString::starts_with_bytes(StringView bytes, CaseSensitivity case_sensitivity) const
|
|
|
|
{
|
|
|
|
return bytes_as_string_view().starts_with(bytes, case_sensitivity);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool FlyString::ends_with_bytes(StringView bytes, CaseSensitivity case_sensitivity) const
|
|
|
|
{
|
|
|
|
return bytes_as_string_view().ends_with(bytes, case_sensitivity);
|
|
|
|
}
|
|
|
|
|
2023-01-11 13:26:49 +00:00
|
|
|
}
|