diff options
author | Dominick Allen <djallen@librehumanitas.org> | 2024-10-28 23:49:50 -0500 |
---|---|---|
committer | Dominick Allen <djallen@librehumanitas.org> | 2024-10-28 23:49:50 -0500 |
commit | afc11065bb151349090d8ae89cb61d1c35bdddae (patch) | |
tree | 41a7133a6e143d6333594d899556831b5a914fc1 /include/fud_string.hpp | |
parent | c3cf6df863828798ed8230b0f0966bcf3b2d08dd (diff) |
Prepare for new SSO.
Diffstat (limited to 'include/fud_string.hpp')
-rw-r--r-- | include/fud_string.hpp | 194 |
1 files changed, 164 insertions, 30 deletions
diff --git a/include/fud_string.hpp b/include/fud_string.hpp index d2d3761..29b887a 100644 --- a/include/fud_string.hpp +++ b/include/fud_string.hpp @@ -32,36 +32,100 @@ static_assert(CHAR_BIT == 8); -namespace fud { +/** @file */ -constexpr size_t SSO_BUF_LENGTH = 15; -constexpr size_t SSO_BUF_SIZE = SSO_BUF_LENGTH + 1; - -using StringResult = Result<String, FudStatus>; +namespace fud { struct DrainResult { size_t bytesWritten; FudStatus status; }; +/** \brief The maximum length of a string using the small string optimization + * buffer. */ +constexpr size_t SSO_BUF_LENGTH = 15; + +/** \brief The size of the small string optimization buffer, to include space + * for the null terminator. */ +constexpr size_t SSO_BUF_SIZE = SSO_BUF_LENGTH + 1; + +class String; + +/** \brief A result containing a valid @String or the @FudStatus error + * encountered during its creation. */ +using StringResult = Result<String, FudStatus>; + +/** \brief A null terminated, growable, array of utf8 code points, with a custom + * allocator. */ class String { public: + /** \brief Create a string from a C String. + * + * \param [in] cString a pointer to a C string to populate the String. + * + * \returns String on success. + * \returns NullPointer if cString is null. + * \returns ArgumentInvalid if the length of cString is greater than or + * equal to SSIZE_MAX. + * \returns FudStatus::AllocFailure if the allocator fails. + */ static StringResult makeFromCString(const char* cString); + /** \brief Create a string from a C String, specifying the allocator. + * + * \param [in] cString a pointer to a C string to populate the String. + * \param [in] allocator the allocator the string will use. + * + * \returns String on success. + * \returns NullPointer if cString or allocator is null. + * \returns ArgumentInvalid if the length of cString is greater than or + * equal to SSIZE_MAX. + * \returns FudStatus::AllocFailure if the allocator fails. + */ static StringResult makeFromCString(const char* cString, Allocator* allocator); + /** \brief Create a string from concatenating multiple C Strings. + * + * This function uses the default globalFudAllocator. + * + * \tparam CStrings a parameter pack of types convertible to const char*. + * \param [in] cStrings pointers to C strings to concatenate into a single String. + * + * \returns String on success. + * \returns NullPointer if cString or allocator is null. + * \returns ArgumentInvalid if the length of cString is greater than or + * equal to SSIZE_MAX. + * \returns FudStatus::AllocFailure if the allocator fails. + */ template <typename... CStrings> static StringResult makeFromCStrings(CStrings... cStrings) { return makeFromCStringsAlloc(&globalFudAllocator, cStrings...); } + /** \brief Create a string from concatenating multiple C Strings. + * + * \tparam CStrings a parameter pack of types convertible to const char*. + * \param [in] cStrings pointers to C strings to concatenate into a single String. + * \param [in] allocator the allocator the string will use. + * + * \returns String on success. + * \returns NullPointer if cString or allocator is null. + * \returns ArgumentInvalid if the length of cString is greater than or + * equal to SSIZE_MAX. + * \returns FudStatus::AllocFailure if the allocator fails. + */ template <typename... CStrings> static StringResult makeFromCStringsAlloc(Allocator* allocator, CStrings... cStrings) { if (allocator == nullptr) { return StringResult::error(FudStatus::NullPointer); } + + if (!String::allocatorValid(allocator)) { + return StringResult::error(FudStatus::ArgumentInvalid); + } + size_t totalLength = 0; Array<size_t, sizeof...(cStrings)> lengths{}; Array<const char*, sizeof...(cStrings)> strPointers{}; @@ -96,14 +160,14 @@ class String { if (output.m_length >= output.m_capacity) { output.m_capacity = output.m_length + 1; /* Avoid using compiler expansions in headers */ - auto dataResult = output.m_allocator->allocate(output.m_capacity); + auto dataResult = output.allocator()->allocate(output.m_capacity); if (dataResult.isError()) { return StringResult::error(dataResult.getError()); } output.m_data = static_cast<utf8*>(dataResult.getOkay()); } - auto* data = output.data(); + auto* data = output.dataMut(); size_t cumulativeLength = 0; for (size_t idx = 0; idx < strPointers.size(); ++idx) { const auto* cString = strPointers[idx]; @@ -140,58 +204,46 @@ class String { FudStatus copy(const String& rhs); + /** \brief The raw length of the string's data, excluding the null terminator. */ [[nodiscard]] constexpr size_t length() const { return m_length; } + /** \brief Indicates if no characters are present in the string's data. */ [[nodiscard]] constexpr bool empty() const { return m_length == 0; } + /** \brief The total size of the string's data, including the null terminator. */ [[nodiscard]] constexpr size_t size() const { return m_length + 1; } + /** \brief The current capacity of the string, excluding the reserved slot + * for the null terminator. */ [[nodiscard]] constexpr size_t capacity() const { + fudAssert(m_capacity > 0); return m_capacity - 1; } - /** \brier The underlying data, guaranteed to have c string representation. */ + /** \brief The underlying data, guaranteed to have c string representation. */ [[nodiscard]] constexpr const utf8* data() const { return isLarge() ? m_data : m_buffer.data(); } - /** \brier The underlying data as an explicit c string. */ + /** \brief The underlying data as an explicit c string. */ [[nodiscard]] inline const char* c_str() const { - return isLarge() ? reinterpret_cast<const char*>(m_data) : reinterpret_cast<const char*>(m_buffer.data()); + return reinterpret_cast<const char*>(data()); } - /** \brier The underlying data, guaranteed to have c string representation. */ - [[nodiscard]] constexpr utf8* data() - { - return isLarge() ? m_data : m_buffer.data(); - } - - /** \brier The underlying data as an explicit c string. */ - [[nodiscard]] inline char* c_str() - { - return isLarge() ? reinterpret_cast<char*>(m_data) : reinterpret_cast<char*>(m_buffer.data()); - } - - [[nodiscard]] bool nullTerminated() const; - - [[nodiscard]] bool valid() const; - [[nodiscard]] bool utf8Valid() const; - FudStatus nullTerminate(); - FudStatus reserve(size_t newCapacity); [[nodiscard]] Option<utf8> back(); @@ -243,26 +295,108 @@ class String { const utf8* end() const; private: + static constexpr uint8_t capacityMask = 0x07; + + [[nodiscard]] static bool allocatorValid(Allocator* allocator) + { + return ((reinterpret_cast<uintptr_t>(allocator) & capacityMask) == 0); + } + + [[nodiscard]] bool nullTerminated() const; + + [[nodiscard]] bool valid() const; + + FudStatus nullTerminate(); + + Allocator* allocator() const + { + constexpr uintptr_t ALLOCATOR_MASK = ~static_cast<uintptr_t>(capacityMask); + const auto allocptr = reinterpret_cast<uintptr_t>(m_allocator); + return reinterpret_cast<Allocator*>(allocptr & ALLOCATOR_MASK); + } + + /** \brief The underlying data, guaranteed to have c string + * representation. */ + [[nodiscard]] constexpr utf8* dataMut() + { + return isLarge() ? m_data : m_buffer.data(); + } + void cleanup(); FudStatus resize(size_t newCapacity); + [[nodiscard]] bool optIsLarge() const + { + return (reinterpret_cast<uintptr_t>(m_allocator) & capacityMask) != 0; + } + + public: + static constexpr size_t OptBufSize = 2 * sizeof(size_t) + sizeof(utf8*) - 1; + using OptBufType = Array<utf8, OptBufSize>; + size_t optLength() const + { + if (optIsLarge()) { + return m_large.optLength; + } + return m_small.optLength; + } + + size_t optCapacity() const + { + if (optIsLarge()) { + return m_large.optCapacity; + } + return OptBufSize - 1U; + } + + private: + void setLength(size_t newLength) + { + m_length = newLength; + if (optIsLarge()) { + m_large.optLength = newLength; + } else { + m_small.optLength = static_cast<uint8_t>(newLength); + } + } + + /** \brief The allocator used to get storage for characters when the string + * is large. */ + Allocator* m_allocator{&globalFudAllocator}; + + union { + struct { + size_t optLength; + size_t optCapacity; + utf8* optData; + } m_large; + struct { + uint8_t optLength; + OptBufType optBuffer; + } m_small; + }; + using BufType = Array<utf8, SSO_BUF_SIZE>; union { + /** \brief The storage for string characters when using SSO. */ BufType m_buffer{BufType::constFill(0)}; + /** \brief The storage for string characters when the string is + * large. */ utf8* m_data; }; + /** \brief The length of the string excluding the null terminator. */ size_t m_length{0}; + /** \brief The capacity of the string, including the null terminator. */ size_t m_capacity{SSO_BUF_SIZE}; + /** \brief Whether or not the string must use its allocator for storage. */ [[nodiscard]] constexpr bool isLarge() const { return m_capacity > SSO_BUF_SIZE; } - - Allocator* m_allocator{&globalFudAllocator}; }; } // namespace fud |