From afc11065bb151349090d8ae89cb61d1c35bdddae Mon Sep 17 00:00:00 2001 From: Dominick Allen Date: Mon, 28 Oct 2024 23:49:50 -0500 Subject: Prepare for new SSO. --- include/fud_string.hpp | 194 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 164 insertions(+), 30 deletions(-) (limited to 'include/fud_string.hpp') 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; +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; + +/** \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 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 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 lengths{}; Array 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(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(m_data) : reinterpret_cast(m_buffer.data()); + return reinterpret_cast(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(m_data) : reinterpret_cast(m_buffer.data()); - } - - [[nodiscard]] bool nullTerminated() const; - - [[nodiscard]] bool valid() const; - [[nodiscard]] bool utf8Valid() const; - FudStatus nullTerminate(); - FudStatus reserve(size_t newCapacity); [[nodiscard]] Option 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(allocator) & capacityMask) == 0); + } + + [[nodiscard]] bool nullTerminated() const; + + [[nodiscard]] bool valid() const; + + FudStatus nullTerminate(); + + Allocator* allocator() const + { + constexpr uintptr_t ALLOCATOR_MASK = ~static_cast(capacityMask); + const auto allocptr = reinterpret_cast(m_allocator); + return reinterpret_cast(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(m_allocator) & capacityMask) != 0; + } + + public: + static constexpr size_t OptBufSize = 2 * sizeof(size_t) + sizeof(utf8*) - 1; + using OptBufType = Array; + 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(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; 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 -- cgit v1.2.3