ladybird/AK/StringUtils.h
Andreas Kling a3e82eaad3 AK: Introduce the new String, replacement for DeprecatedString
DeprecatedString (formerly String) has been with us since the start,
and it has served us well. However, it has a number of shortcomings
that I'd like to address.

Some of these issues are hard if not impossible to solve incrementally
inside of DeprecatedString, so instead of doing that, let's build a new
String class and then incrementally move over to it instead.

Problems in DeprecatedString:

- It assumes string allocation never fails. This makes it impossible
  to use in allocation-sensitive contexts, and is the reason we had to
  ban DeprecatedString from the kernel entirely.

- The awkward null state. DeprecatedString can be null. It's different
  from the empty state, although null strings are considered empty.
  All code is immediately nicer when using Optional<DeprecatedString>
  but DeprecatedString came before Optional, which is how we ended up
  like this.

- The encoding of the underlying data is ambiguous. For the most part,
  we use it as if it's always UTF-8, but there have been cases where
  we pass around strings in other encodings (e.g ISO8859-1)

- operator[] and length() are used to iterate over DeprecatedString one
  byte at a time. This is done all over the codebase, and will *not*
  give the right results unless the string is all ASCII.

How we solve these issues in the new String:

- Functions that may allocate now return ErrorOr<String> so that ENOMEM
  errors can be passed to the caller.

- String has no null state. Use Optional<String> when needed.

- String is always UTF-8. This is validated when constructing a String.
  We may need to add a bypass for this in the future, for cases where
  you have a known-good string, but for now: validate all the things!

- There is no operator[] or length(). You can get the underlying data
  with bytes(), but for iterating over code points, you should be using
  an UTF-8 iterator.

Furthermore, it has two nifty new features:

- String implements a small string optimization (SSO) for strings that
  can fit entirely within a pointer. This means up to 3 bytes on 32-bit
  platforms, and 7 bytes on 64-bit platforms. Such small strings will
  not be heap-allocated.

- String can create substrings without making a deep copy of the
  substring. Instead, the superstring gets +1 refcount from the
  substring, and it acts like a view into the superstring. To make
  substrings like this, use the substring_with_shared_superstring() API.

One caveat:

- String does not guarantee that the underlying data is null-terminated
  like DeprecatedString does today. While this was nifty in a handful of
  places where we were calling C functions, it did stand in the way of
  shared-superstring substrings.
2022-12-06 15:21:26 +01:00

120 lines
3.4 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020, Fei Wu <f.eiwu@yahoo.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Concepts.h>
#include <AK/EnumBits.h>
#include <AK/Forward.h>
namespace AK {
namespace Detail {
template<Concepts::AnyString T, Concepts::AnyString U>
inline constexpr bool IsHashCompatible<T, U> = true;
}
enum class CaseSensitivity {
CaseInsensitive,
CaseSensitive,
};
enum class ReplaceMode {
All,
FirstOnly,
};
enum class TrimMode {
Left,
Right,
Both
};
enum class TrimWhitespace {
Yes,
No,
};
enum class SplitBehavior : unsigned {
// Neither keep empty substrings nor keep the trailing separator.
// This is the default behavior if unspecified.
Nothing = 0,
// If two separators follow each other without any characters
// in between, keep a "" in the resulting vector. (or only the
// separator if KeepTrailingSeparator is used)
KeepEmpty = 1,
// Do not strip off the separator at the end of the string.
KeepTrailingSeparator = 2,
};
AK_ENUM_BITWISE_OPERATORS(SplitBehavior);
struct MaskSpan {
size_t start;
size_t length;
bool operator==(MaskSpan const& other) const
{
return start == other.start && length == other.length;
}
};
namespace StringUtils {
bool matches(StringView str, StringView mask, CaseSensitivity = CaseSensitivity::CaseInsensitive, Vector<MaskSpan>* match_spans = nullptr);
template<typename T = int>
Optional<T> convert_to_int(StringView, TrimWhitespace = TrimWhitespace::Yes);
template<typename T = unsigned>
Optional<T> convert_to_uint(StringView, TrimWhitespace = TrimWhitespace::Yes);
template<typename T = unsigned>
Optional<T> convert_to_uint_from_hex(StringView, TrimWhitespace = TrimWhitespace::Yes);
template<typename T = unsigned>
Optional<T> convert_to_uint_from_octal(StringView, TrimWhitespace = TrimWhitespace::Yes);
#ifndef KERNEL
template<typename T>
Optional<T> convert_to_floating_point(StringView, TrimWhitespace = TrimWhitespace::Yes);
#endif
bool equals_ignoring_case(StringView, StringView);
bool ends_with(StringView a, StringView b, CaseSensitivity);
bool starts_with(StringView, StringView, CaseSensitivity);
bool contains(StringView, StringView, CaseSensitivity);
bool is_whitespace(StringView);
StringView trim(StringView string, StringView characters, TrimMode mode);
StringView trim_whitespace(StringView string, TrimMode mode);
Optional<size_t> find(StringView haystack, char needle, size_t start = 0);
Optional<size_t> find(StringView haystack, StringView needle, size_t start = 0);
Optional<size_t> find_last(StringView haystack, char needle);
Optional<size_t> find_last_not(StringView haystack, char needle);
Vector<size_t> find_all(StringView haystack, StringView needle);
enum class SearchDirection {
Forward,
Backward
};
Optional<size_t> find_any_of(StringView haystack, StringView needles, SearchDirection);
DeprecatedString to_snakecase(StringView);
DeprecatedString to_titlecase(StringView);
DeprecatedString invert_case(StringView);
DeprecatedString replace(StringView, StringView needle, StringView replacement, ReplaceMode);
ErrorOr<String> replace(String const&, StringView needle, StringView replacement, ReplaceMode);
size_t count(StringView, StringView needle);
}
}
#if USING_AK_GLOBALLY
using AK::CaseSensitivity;
using AK::ReplaceMode;
using AK::SplitBehavior;
using AK::TrimMode;
using AK::TrimWhitespace;
#endif