summaryrefslogtreecommitdiff
path: root/include/fud_string.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'include/fud_string.hpp')
-rw-r--r--include/fud_string.hpp194
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