summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/fud_allocator.hpp5
-rw-r--r--include/fud_result.hpp4
-rw-r--r--include/fud_string.hpp175
-rw-r--r--source/fud_string.cpp364
4 files changed, 348 insertions, 200 deletions
diff --git a/include/fud_allocator.hpp b/include/fud_allocator.hpp
index 693111b..d4feccf 100644
--- a/include/fud_allocator.hpp
+++ b/include/fud_allocator.hpp
@@ -26,7 +26,7 @@
namespace fud {
-class alignas(size_t) Allocator {
+class alignas(std::max_align_t) Allocator {
public:
virtual ~Allocator() = default;
@@ -37,7 +37,8 @@ class alignas(size_t) Allocator {
virtual bool isEqual(const Allocator& rhs) const = 0;
};
-constexpr bool operator==(const Allocator& lhs, const Allocator& rhs) {
+constexpr bool operator==(const Allocator& lhs, const Allocator& rhs)
+{
return &lhs == &rhs;
}
diff --git a/include/fud_result.hpp b/include/fud_result.hpp
index 95f3e5c..497b007 100644
--- a/include/fud_result.hpp
+++ b/include/fud_result.hpp
@@ -18,8 +18,8 @@
#ifndef FUD_RESULT_HPP
#define FUD_RESULT_HPP
-#include <variant>
#include <utility>
+#include <variant>
namespace fud {
@@ -159,7 +159,7 @@ class [[nodiscard]] Result {
std::variant<T, E> m_value;
};
-#define M_TakeOrReturn(HYGIENE_EXPRESSION) \
+#define M_TakeOrReturn(HYGIENE_EXPRESSION) \
({ \
auto HYGIENE_RESULT{(HYGIENE_EXPRESSION)}; \
if (HYGIENE_RESULT.isError()) { \
diff --git a/include/fud_string.hpp b/include/fud_string.hpp
index 55b1e86..0020c67 100644
--- a/include/fud_string.hpp
+++ b/include/fud_string.hpp
@@ -41,6 +41,8 @@ struct DrainResult {
FudStatus status;
};
+/* TODO: make SSO_BUF_LENGTH user configurable. */
+
/** \brief The maximum length of a string using the small string optimization
* buffer. */
constexpr size_t SSO_BUF_LENGTH = 15;
@@ -49,6 +51,8 @@ constexpr size_t SSO_BUF_LENGTH = 15;
* for the null terminator. */
constexpr size_t SSO_BUF_SIZE = SSO_BUF_LENGTH + 1;
+static constexpr size_t SsoBufSize = 23;
+
class String;
/** \brief A result containing a valid @String or the @FudStatus error
@@ -130,6 +134,7 @@ class String {
Array<size_t, sizeof...(cStrings)> lengths{};
Array<const char*, sizeof...(cStrings)> strPointers{};
size_t index = 0;
+
for (const auto* cStringItem : {cStrings...}) {
const char* cString = nullptr;
if constexpr (std::is_same_v<decltype(cStringItem), const char*>) {
@@ -142,11 +147,12 @@ class String {
strPointers[index] = cString;
auto lengthResult = cStringLength(cString);
- if (lengthResult < 0 || lengthResult >= SSIZE_MAX) {
+
+ if (lengthResult < 0 || lengthResult >= std::numeric_limits<ssize_t>::max()) {
return StringResult::error(FudStatus::ArgumentInvalid);
}
auto stringLength = static_cast<size_t>(lengthResult);
- if (SIZE_MAX - totalLength < stringLength) {
+ if (maxStringLength - totalLength < stringLength) {
return StringResult::error(FudStatus::Failure);
}
totalLength += stringLength;
@@ -154,38 +160,48 @@ class String {
index++;
}
+ fudAssert(totalLength < maxStringLength);
+
String output{};
- output.m_length = totalLength;
output.m_allocator = allocator;
- if (output.m_length >= output.m_capacity) {
- output.m_capacity = output.m_length + 1;
- /* Avoid using compiler expansions in headers */
- auto dataResult = output.allocator()->allocate(output.m_capacity);
+ utf8* data{nullptr};
+ size_t capacity = totalLength + 1;
+ bool isLarge = capacity > SsoBufSize;
+ if (isLarge) {
+ output.m_repr.large.capacity = capacity & largeStringCapacitymask;
+ output.m_repr.large.length = totalLength;
+ auto dataResult = output.allocator()->allocate(output.m_repr.large.capacity);
if (dataResult.isError()) {
return StringResult::error(dataResult.getError());
}
- output.m_data = static_cast<utf8*>(dataResult.getOkay());
+ output.m_repr.large.data = static_cast<utf8*>(dataResult.getOkay());
+ output.m_repr.large.isLarge = 1;
+ data = output.m_repr.large.data;
+ } else {
+ capacity = SsoBufSize;
+ static_assert(SsoBufSize < std::numeric_limits<int8_t>::max());
+ output.m_repr.small.isLarge = 0;
+ output.m_repr.small.length = static_cast<uint8_t>(totalLength) & smallStringLengthMask;
+ data = output.m_repr.small.buffer.data();
}
- auto* data = output.dataMut();
+ fudAssert(data != nullptr);
+
size_t cumulativeLength = 0;
for (size_t idx = 0; idx < strPointers.size(); ++idx) {
const auto* cString = strPointers[idx];
- auto copyStatus = copyMem(
- data + cumulativeLength,
- output.m_capacity - cumulativeLength,
- cString,
- lengths[idx]);
+ auto copyStatus = copyMem(data + cumulativeLength, capacity - cumulativeLength, cString, lengths[idx]);
fudAssert(copyStatus == FudStatus::Success);
cumulativeLength += lengths[idx];
}
- auto terminateStatus = output.nullTerminate();
- fudAssert(terminateStatus == FudStatus::Success);
+ data[cumulativeLength] = '\0';
return StringResult::okay(std::move(output));
}
+ /** \brief Default constructs a small string of zero length using the global
+ * fud allocator. */
String() noexcept = default;
String(const String& rhs) = delete;
@@ -205,35 +221,40 @@ 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
+ [[nodiscard]] size_t length() const
{
- return m_length;
+ if (isLarge()) {
+ return m_repr.large.length;
+ }
+ return m_repr.small.length;
}
/** \brief Indicates if no characters are present in the string's data. */
- [[nodiscard]] constexpr bool empty() const
+ [[nodiscard]] bool empty() const
{
- return m_length == 0;
+ return length() == 0;
}
/** \brief The total size of the string's data, including the null terminator. */
- [[nodiscard]] constexpr size_t size() const
+ [[nodiscard]] size_t size() const
{
- return m_length + 1;
+ return length() + 1;
}
/** \brief The current capacity of the string, excluding the reserved slot
* for the null terminator. */
- [[nodiscard]] constexpr size_t capacity() const
+ [[nodiscard]] size_t capacity() const
{
- fudAssert(m_capacity > 0);
- return m_capacity - 1;
+ if (isLarge()) {
+ return m_repr.large.capacity - 1U;
+ }
+ return SsoBufSize - 1U;
}
/** \brief The underlying data, guaranteed to have c string representation. */
- [[nodiscard]] constexpr const utf8* data() const
+ [[nodiscard]] const utf8* data() const
{
- return isLarge() ? m_data : m_buffer.data();
+ return isLarge() ? m_repr.large.data : m_repr.small.buffer.data();
}
/** \brief The underlying data as an explicit c string. */
@@ -248,13 +269,13 @@ class String {
[[nodiscard]] Option<utf8> back();
- [[nodiscard]] constexpr size_t remainingLength() const
+ [[nodiscard]] size_t remainingLength() const
{
- if (m_length >= m_capacity) {
+ if (length() > capacity()) {
return 0;
}
- return m_capacity - 1U - m_length;
+ return capacity() - length();
}
[[nodiscard]] inline StringView asView() const
@@ -295,11 +316,19 @@ class String {
const utf8* end() const;
private:
- static constexpr uint8_t capacityMask = 0x07;
+ static constexpr size_t maxStringLength = (static_cast<size_t>(1) << 63) - 1;
+ static constexpr size_t largeStringCapacitymask = (static_cast<size_t>(1) << 63) - 1;
+ static constexpr uint8_t maxSmallStringLength = SsoBufSize;
+ static constexpr uint8_t smallStringLengthMask = 0x7F;
[[nodiscard]] static bool allocatorValid(Allocator* allocator)
{
- return ((reinterpret_cast<uintptr_t>(allocator) & capacityMask) == 0);
+ return allocator != nullptr;
+ }
+
+ Allocator* allocator() const
+ {
+ return m_allocator;
}
[[nodiscard]] bool nullTerminated() const;
@@ -308,52 +337,78 @@ class String {
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()
+ [[nodiscard]] utf8* dataMut()
{
- return isLarge() ? m_data : m_buffer.data();
+ return isLarge() ? m_repr.large.data : m_repr.small.buffer.data();
}
void cleanup();
FudStatus resize(size_t newCapacity);
- void setLength(size_t newLength)
- {
- m_length = newLength;
- }
-
/** \brief The allocator used to get storage for characters when the string
* is large. */
Allocator* m_allocator{&globalFudAllocator};
- using BufType = Array<utf8, SSO_BUF_SIZE>;
+ using BufType = Array<utf8, SsoBufSize>;
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;
- };
+ struct {
+ uint8_t isLarge : 1;
+ size_t capacity : 63;
+ size_t length;
+ utf8* data;
+ } large;
+ struct {
+ uint8_t isLarge : 1 = 0;
+ uint8_t length : 7 = 0;
+ BufType buffer{};
+ } small{};
+ } m_repr{};
+
+ /** \brief Whether or not the string must use its allocator for storage. */
+ [[nodiscard]] bool isLarge() const
+ {
+ struct {
+ uint8_t isLarge : 1;
+ uint8_t length : 7;
+ } determinant;
+ copyMem<1>(determinant, m_repr);
+ return determinant.isLarge;
+ }
- /** \brief The length of the string excluding the null terminator. */
- size_t m_length{0};
+ [[nodiscard]] size_t smallLength() const
+ {
+ struct {
+ uint8_t isLarge : 1;
+ uint8_t length : 7;
+ } determinant;
+ copyMem<1>(determinant, m_repr);
+ return determinant.isLarge;
+ }
- /** \brief The capacity of the string, including the null terminator. */
- size_t m_capacity{SSO_BUF_SIZE};
+ void addToLength(size_t augend)
+ {
+ if (isLarge()) {
+ fudAssert(m_repr.large.length + augend < maxStringLength);
+ m_repr.large.length += augend;
+ } else {
+ fudAssert(m_repr.small.length + augend < maxSmallStringLength);
+ m_repr.small.length = static_cast<decltype(m_repr.small.length)>((m_repr.small.length + augend)) &
+ smallStringLengthMask;
+ }
+ }
- /** \brief Whether or not the string must use its allocator for storage. */
- [[nodiscard]] constexpr bool isLarge() const
+ void setLength(size_t newLength)
{
- return m_capacity > SSO_BUF_SIZE;
+ if (isLarge()) {
+ fudAssert(newLength < maxStringLength);
+ m_repr.large.length = newLength;
+ } else {
+ fudAssert(newLength < maxSmallStringLength);
+ m_repr.small.length = static_cast<uint8_t>(newLength) & smallStringLengthMask;
+ }
}
};
diff --git a/source/fud_string.cpp b/source/fud_string.cpp
index 994689e..4943dac 100644
--- a/source/fud_string.cpp
+++ b/source/fud_string.cpp
@@ -39,22 +39,44 @@ StringResult String::makeFromCString(const char* cString, Allocator* allocator)
}
auto lenResult = cStringLength(cString);
- if (lenResult < 0 || lenResult >= SSIZE_MAX) {
+ if (lenResult < 0 || lenResult >= std::numeric_limits<ssize_t>::max()) {
return StringResult::error(FudStatus::ArgumentInvalid);
}
+ auto length = static_cast<size_t>(lenResult);
+ if constexpr (static_cast<size_t>(std::numeric_limits<ssize_t>::max()) > maxStringLength) {
+ if (length > maxStringLength) {
+ return StringResult::error(FudStatus::ArgumentInvalid);
+ }
+ }
+
String output{};
output.m_allocator = allocator;
- output.m_length = static_cast<size_t>(lenResult);
-
- if (output.m_length >= output.m_capacity) {
- output.m_capacity = output.m_length + 1;
- output.m_data = static_cast<utf8*>(M_TakeOrReturn(output.allocator()->allocate(output.m_capacity)));
- fudAssert(output.m_data != nullptr);
+ utf8* data{nullptr};
+ size_t capacity = length + 1;
+ bool isLarge = capacity > SsoBufSize;
+ if (isLarge) {
+ output.m_repr.large.capacity = capacity & largeStringCapacitymask;
+ output.m_repr.large.length = length;
+ auto dataResult = output.allocator()->allocate(output.m_repr.large.capacity);
+ if (dataResult.isError()) {
+ return StringResult::error(dataResult.getError());
+ }
+ output.m_repr.large.data = static_cast<utf8*>(dataResult.getOkay());
+ output.m_repr.large.isLarge = 1;
+ data = output.m_repr.large.data;
+ } else {
+ capacity = SsoBufSize;
+ static_assert(SsoBufSize < std::numeric_limits<int8_t>::max());
+ output.m_repr.small.isLarge = 0;
+ output.m_repr.small.length = static_cast<uint8_t>(length) & smallStringLengthMask;
+ data = output.m_repr.small.buffer.data();
}
- auto copyStatus = copyMem(output.dataMut(), output.m_capacity, cString, output.m_length);
+ fudAssert(data != nullptr);
+
+ auto copyStatus = copyMem(data, capacity, cString, length);
fudAssert(copyStatus == FudStatus::Success);
auto terminateStatus = output.nullTerminate();
@@ -70,16 +92,26 @@ StringResult String::from(const String& rhs)
}
String output{};
- output.m_length = rhs.m_length;
- output.m_capacity = rhs.m_capacity;
output.m_allocator = rhs.m_allocator;
-
+ utf8* data{nullptr};
+ size_t capacity{0};
+ size_t length{0};
if (rhs.isLarge()) {
- output.m_data = static_cast<utf8*>(M_TakeOrReturn(output.allocator()->allocate(output.m_capacity)));
- fudAssert(output.m_data != nullptr);
+ output.m_repr.large = rhs.m_repr.large;
+ output.m_repr.large.data = static_cast<utf8*>(
+ M_TakeOrReturn(output.allocator()->allocate(output.m_repr.large.capacity)));
+ data = output.m_repr.large.data;
+ capacity = output.m_repr.large.capacity;
+ length = output.m_repr.large.length;
+ } else {
+ output.m_repr.small = rhs.m_repr.small;
+ data = output.m_repr.small.buffer.data();
+ capacity = SsoBufSize;
+ length = output.m_repr.small.length;
}
+ fudAssert(data != nullptr);
- auto copyResult = copyMem(output.dataMut(), output.m_capacity, rhs.data(), output.m_length);
+ auto copyResult = copyMem(output.dataMut(), capacity, rhs.data(), length);
fudAssert(copyResult == FudStatus::Success);
auto nullTerminateStatus = output.nullTerminate();
@@ -98,18 +130,30 @@ StringResult String::from(StringView view, Allocator* allocator)
return StringResult::error(FudStatus::ArgumentInvalid);
}
- String output{};
- output.m_allocator = allocator;
-
- if (view.m_length >= output.capacity() + 1U) {
- output.m_capacity = view.m_length + 1;
-
- output.m_data = static_cast<utf8*>(M_TakeOrReturn(output.allocator()->allocate(output.m_capacity)));
- fudAssert(output.m_data != nullptr);
+ if (view.nullTerminated()) {
+ return StringResult::error(FudStatus::ArgumentInvalid);
}
- output.setLength(view.m_length);
- auto copyStatus = copyMem(output.dataMut(), output.m_capacity, view.m_data, output.m_length);
+ String output{};
+ output.m_allocator = allocator;
+ size_t capacity = view.length() + 1U;
+ bool isLarge = capacity > SsoBufSize;
+ utf8* data{nullptr};
+ if (isLarge) {
+ output.m_repr.large.capacity = capacity & largeStringCapacitymask;
+ output.m_repr.large.length = view.length();
+ output.m_repr.large.data = static_cast<utf8*>(M_TakeOrReturn(output.allocator()->allocate(capacity)));
+ output.m_repr.large.isLarge = 1;
+ data = output.m_repr.large.data;
+ } else {
+ capacity = SsoBufSize;
+ static_assert(SsoBufSize < std::numeric_limits<int8_t>::max());
+ output.m_repr.small.isLarge = 0;
+ output.m_repr.small.length = static_cast<uint8_t>(view.length()) & smallStringLengthMask;
+ data = output.m_repr.small.buffer.data();
+ }
+ fudAssert(data != nullptr);
+ auto copyStatus = copyMem(data, capacity, view.m_data, view.length());
fudAssert(copyStatus == FudStatus::Success);
auto terminateStatus = output.nullTerminate();
@@ -118,15 +162,10 @@ StringResult String::from(StringView view, Allocator* allocator)
return StringResult::okay(std::move(output));
}
-String::String(String&& rhs) noexcept : m_allocator{rhs.m_allocator}, m_length{rhs.m_length}, m_capacity{rhs.m_capacity}
+String::String(String&& rhs) noexcept : m_allocator{rhs.m_allocator}, m_repr{std::move(rhs.m_repr)}
{
- if (rhs.isLarge()) {
- m_data = rhs.m_data;
- rhs.m_data = nullptr;
- } else {
- m_buffer = rhs.m_buffer;
- auto terminateStatus = nullTerminate();
- fudAssert(terminateStatus == FudStatus::Success);
+ if (isLarge()) {
+ rhs.m_repr.large.data = nullptr;
}
}
@@ -147,15 +186,27 @@ FudStatus String::copy(const String& rhs)
cleanup();
- m_length = rhs.m_length;
- m_capacity = rhs.m_capacity;
m_allocator = rhs.m_allocator;
+ m_repr = rhs.m_repr;
+
+ utf8* data{nullptr};
+ size_t capacity{};
+ size_t length{};
if (isLarge()) {
- m_data = static_cast<utf8*>(M_TakeOrReturn(allocator()->allocate(m_capacity)));
+ m_repr.large.data = static_cast<utf8*>(M_TakeOrReturn(allocator()->allocate(m_repr.large.capacity)));
+ capacity = m_repr.large.capacity;
+ length = m_repr.large.length;
+ data = m_repr.large.data;
+ } else {
+ capacity = SsoBufSize;
+ length = m_repr.small.length;
+ data = m_repr.small.buffer.data();
}
- auto copyResult = copyMem(dataMut(), m_capacity, rhs.data(), m_length);
+ fudAssert(data != nullptr);
+
+ auto copyResult = copyMem(dataMut(), capacity, rhs.data(), length);
fudAssert(copyResult == FudStatus::Success);
auto nullTerminateStatus = nullTerminate();
fudAssert(nullTerminateStatus == FudStatus::Success);
@@ -171,15 +222,11 @@ String& String::operator=(String&& rhs) noexcept
cleanup();
- m_length = rhs.m_length;
- m_capacity = rhs.m_capacity;
m_allocator = rhs.m_allocator;
- if (rhs.isLarge()) {
- m_data = rhs.m_data;
- rhs.m_data = nullptr;
- } else {
- m_buffer = rhs.m_buffer;
- fudAssert(nullTerminate() == FudStatus::Success);
+ m_repr = std::move(rhs.m_repr);
+
+ if (isLarge()) {
+ rhs.m_repr.large.data = nullptr;
}
return *this;
}
@@ -187,10 +234,10 @@ String& String::operator=(String&& rhs) noexcept
void String::cleanup()
{
const auto* allocPtr = allocator();
- if (isLarge() && m_data != nullptr && allocPtr != nullptr) {
- auto deallocStatus = allocator()->deallocate(m_data, m_capacity);
+ if (isLarge() && m_repr.large.data != nullptr && allocPtr != nullptr) {
+ auto deallocStatus = allocator()->deallocate(m_repr.large.data, m_repr.large.capacity);
static_cast<void>(deallocStatus);
- m_data = nullptr;
+ m_repr.large.data = nullptr;
}
}
@@ -200,26 +247,27 @@ FudStatus String::resize(size_t newCapacity)
return FudStatus::StringInvalid;
}
- if (m_length >= newCapacity) {
+ if (length() >= newCapacity) {
return FudStatus::OperationInvalid;
}
if (!isLarge() && newCapacity <= SSO_BUF_SIZE) {
- m_capacity = SSO_BUF_SIZE;
return FudStatus::Success;
}
if (newCapacity <= SSO_BUF_SIZE) {
+ fudAssert(isLarge());
+
+ auto len = static_cast<uint8_t>(length());
BufType temp{BufType::constFill(0)};
- auto copyResult = copyMem(dataMut(), temp.size(), temp.data(), length());
+ auto copyResult = copyMem(dataMut(), temp.size(), temp.data(), len);
fudAssert(copyResult == FudStatus::Success);
- auto deallocStatus = allocator()->deallocate(m_data, m_capacity);
- m_capacity = SSO_BUF_SIZE;
- m_data = nullptr;
-
- copyMem(m_buffer, temp);
- dataMut()[m_length] = '\0';
+ auto deallocStatus = allocator()->deallocate(m_repr.large.data, m_repr.large.capacity);
+ m_repr.small.isLarge = 0;
+ m_repr.small.length = len & smallStringLengthMask;
+ copyMem(m_repr.small.buffer, temp);
+ m_repr.small.buffer[len] = '\0';
return deallocStatus != FudStatus::Success ? FudStatus::DeallocFailure : FudStatus::Success;
}
@@ -227,17 +275,21 @@ FudStatus String::resize(size_t newCapacity)
auto* newData = static_cast<utf8*>(M_TakeOrReturn(allocator()->allocate(newCapacity)));
fudAssert(newData != nullptr);
- auto copyResult = copyMem(newData, newCapacity, dataMut(), length());
+ auto copyResult = copyMem(newData, newCapacity, data(), length());
fudAssert(copyResult == FudStatus::Success);
auto deallocStatus = FudStatus::Success;
if (isLarge()) {
- deallocStatus = allocator()->deallocate(dataMut(), m_capacity);
+ deallocStatus = allocator()->deallocate(dataMut(), m_repr.large.capacity);
}
- m_capacity = newCapacity;
- m_data = newData;
- dataMut()[m_length] = '\0';
+ size_t len = length();
+
+ m_repr.large.isLarge = 1;
+ m_repr.large.capacity = newCapacity & largeStringCapacitymask;
+ m_repr.large.data = newData;
+ m_repr.large.length = len;
+ m_repr.large.data[m_repr.large.length] = '\0';
fudAssert(valid());
return deallocStatus != FudStatus::Success ? FudStatus::DeallocFailure : FudStatus::Success;
@@ -245,7 +297,7 @@ FudStatus String::resize(size_t newCapacity)
bool String::nullTerminated() const
{
- return data() != nullptr && m_length < m_capacity && data()[m_length] == '\0';
+ return data() != nullptr && length() <= capacity() && data()[length()] == '\0';
}
bool String::valid() const
@@ -265,8 +317,8 @@ bool String::utf8Valid() const
FudStatus String::nullTerminate()
{
- if (m_length < m_capacity) {
- dataMut()[m_length] = '\0';
+ if (length() <= capacity()) {
+ dataMut()[length()] = '\0';
return FudStatus::Success;
}
return FudStatus::StringInvalid;
@@ -278,7 +330,7 @@ FudStatus String::reserve(size_t newCapacity)
return FudStatus::StringInvalid;
}
- if (newCapacity < m_capacity) {
+ if (newCapacity < capacity()) {
return FudStatus::Success;
}
@@ -291,7 +343,7 @@ FudStatus String::reserve(size_t newCapacity)
return NullOpt;
}
- utf8 backChar = dataMut()[m_length - 1];
+ utf8 backChar = dataMut()[length() - 1];
if (Ascii::valid(backChar)) {
return backChar;
}
@@ -305,13 +357,23 @@ Option<utf8> String::pop()
return NullOpt;
}
- if (m_length < 1) {
- return NullOpt;
+ utf8 letter{};
+ if (isLarge()) {
+ if (m_repr.large.length < 1) {
+ return NullOpt;
+ }
+ m_repr.large.length--;
+ letter = m_repr.large.data[m_repr.large.length];
+ m_repr.large.data[m_repr.large.length] = '\0';
+ } else {
+ if (m_repr.small.length < 1) {
+ return NullOpt;
+ }
+ m_repr.small.length--;
+ letter = m_repr.small.buffer[m_repr.small.length];
+ m_repr.small.buffer[m_repr.small.length] = '\0';
}
- m_length--;
- auto letter = dataMut()[m_length];
- dataMut()[m_length] = '\0';
return letter;
}
@@ -327,17 +389,25 @@ FudStatus String::pushBack(utf8 letter)
}
if (remainingLength() < 1) {
- auto newCapacity = m_capacity < SIZE_MAX / 2 ? m_capacity * 2 : SIZE_MAX;
+ auto cap = capacity() + 1U;
+ auto newCapacity = cap < maxStringLength / 2 ? cap * 2 : maxStringLength - 1U;
const auto resizeStatus = resize(newCapacity);
if (resizeStatus != FudStatus::Success) {
return resizeStatus;
}
- fudAssert(m_capacity == newCapacity);
+ fudAssert(isLarge());
+ fudAssert(m_repr.large.capacity == newCapacity);
}
- dataMut()[m_length] = letter;
- m_length++;
- dataMut()[m_length] = '\0';
+ if (isLarge()) {
+ m_repr.large.data[m_repr.large.length] = letter;
+ m_repr.large.length++;
+ m_repr.large.data[m_repr.large.length] = '\0';
+ } else {
+ m_repr.small.buffer[m_repr.small.length] = letter;
+ m_repr.small.length++;
+ m_repr.small.buffer[m_repr.small.length] = '\0';
+ }
return FudStatus::Success;
}
@@ -359,25 +429,31 @@ FudStatus String::pushBack(const FudUtf8& letter)
auto letterSize = letter.size();
if (remainingLength() < letterSize) {
- auto newCapacity = m_capacity < SIZE_MAX / 2 ? m_capacity * 2 : SIZE_MAX;
- if ((newCapacity - m_capacity) < (letterSize + 1)) {
+ auto cap = capacity() + 1U;
+ auto newCapacity = cap < maxStringLength / 2 ? cap * 2 : maxStringLength - 1U;
+ if ((newCapacity - cap) < (letterSize + 1)) {
return FudStatus::OperationInvalid;
}
const auto resizeStatus = resize(newCapacity);
if (resizeStatus != FudStatus::Success) {
return resizeStatus;
}
- fudAssert(m_capacity == newCapacity);
+ fudAssert(isLarge());
+ fudAssert(m_repr.large.capacity == newCapacity);
}
- auto copyStatus = copyMem(m_data + m_length, remainingLength(), letterData, letterSize);
+ auto copyStatus = copyMem(dataMut() + length(), remainingLength(), letterData, letterSize);
if (copyStatus != FudStatus::Success) {
return copyStatus;
}
- m_length += letterSize;
- dataMut()[m_length] = '\0';
+ addToLength(letterSize);
+ if (isLarge()) {
+ m_repr.large.data[m_repr.large.length] = '\0';
+ } else {
+ m_repr.small.buffer[m_repr.small.length] = '\0';
+ }
return FudStatus::Success;
}
@@ -416,21 +492,26 @@ FudStatus String::append(StringView source)
if (newSize < newLength) { // cppcheck-suppress knownConditionTrueFalse
return FudStatus::OperationInvalid;
}
- if (newSize >= m_capacity) {
+ if (newSize > capacity()) {
auto newCapacity = newSize < SIZE_MAX / 2 ? newSize * 2 : SIZE_MAX;
const auto resizeStatus = resize(newCapacity);
if (resizeStatus != FudStatus::Success) {
return resizeStatus;
}
- fudAssert(m_capacity == newCapacity);
+ fudAssert(isLarge());
+ fudAssert(m_repr.large.capacity == newCapacity);
}
auto* destPtr = dataMut() + length();
auto status = copyMem(destPtr, remainingLength(), source.data(), source.length());
fudAssert(status == FudStatus::Success);
- m_length += source.length();
- status = nullTerminate();
+ addToLength(source.length());
+ if (isLarge()) {
+ m_repr.large.data[m_repr.large.length] = '\0';
+ } else {
+ m_repr.small.buffer[m_repr.small.length] = '\0';
+ }
return status;
}
@@ -475,7 +556,7 @@ DrainResult String::drain(StringView source)
auto* destPtr = dataMut() + length();
auto status = copyMem(destPtr, remainingLength(), firstPart.m_data, firstPart.m_length);
fudAssert(status == FudStatus::Success);
- m_length += firstPart.m_length;
+ addToLength(firstPart.m_length);
result.bytesWritten += firstPart.m_length;
status = nullTerminate();
source.advanceUnsafe(firstPart.m_length);
@@ -496,23 +577,24 @@ DrainResult String::drain(StringView source)
result.status = FudStatus::OperationInvalid;
return result;
}
- if (newSize >= m_capacity) {
+ if (newSize > capacity()) {
auto newCapacity = newSize < SIZE_MAX / 2 ? newSize * 2 : SIZE_MAX;
const auto resizeStatus = resize(newCapacity);
if (resizeStatus != FudStatus::Success) {
result.status = resizeStatus;
return result;
}
- fudAssert(m_capacity == newCapacity);
+ fudAssert(isLarge());
+ fudAssert(m_repr.large.capacity == newCapacity);
}
auto* destPtr = dataMut() + length();
- auto status = copyMem(destPtr, remainingLength(), source.data(), source.length());
- fudAssert(status == FudStatus::Success);
+ result.status = copyMem(destPtr, remainingLength(), source.data(), source.length());
+ fudAssert(result.status == FudStatus::Success);
result.bytesWritten += source.length();
- m_length = newLength;
- status = nullTerminate();
+ setLength(newLength);
+ result.status = nullTerminate();
return result;
}
@@ -524,45 +606,46 @@ StringResult String::catenate(const char* rhs) const
}
auto lenResult = cStringLength(rhs);
- if (lenResult < 0 || lenResult >= SSIZE_MAX) {
+ if (lenResult < 0 || lenResult >= static_cast<ssize_t>(maxStringLength)) {
return StringResult::error(FudStatus::ArgumentInvalid);
}
size_t rhsLength = static_cast<size_t>(lenResult);
- String output{};
-
- if (SIZE_MAX - m_length < rhsLength) {
+ if (maxStringLength - length() < rhsLength) {
return StringResult::error(FudStatus::Failure);
}
- output.m_allocator = m_allocator;
-
- output.m_length = m_length + rhsLength;
- if (output.m_length > output.m_capacity) {
- output.m_capacity = output.m_length + 1;
- }
-
- if (!output.isLarge()) {
- auto status = copyMem(output.m_buffer.data(), output.m_capacity, data(), length());
- fudAssert(status == FudStatus::Success);
-
- status = copyMem(output.m_buffer.data() + length(), output.m_capacity - length(), rhs, rhsLength);
- fudAssert(status == FudStatus::Success);
+ size_t outputLength = rhsLength + length();
- auto terminateStatus = output.nullTerminate();
- fudAssert(terminateStatus == FudStatus::Success);
-
- return StringResult::okay(std::move(output));
+ String output{};
+ output.m_allocator = m_allocator;
+ size_t outputCapacity = outputLength + 1;
+ utf8* outputData{nullptr};
+ if (outputCapacity > SsoBufSize) {
+ output.m_repr.large.capacity = outputCapacity & largeStringCapacitymask;
+ output.m_repr.large.length = outputLength;
+ auto dataResult = output.allocator()->allocate(output.m_repr.large.capacity);
+ if (dataResult.isError()) {
+ return StringResult::error(dataResult.getError());
+ }
+ output.m_repr.large.data = static_cast<utf8*>(dataResult.getOkay());
+ output.m_repr.large.isLarge = 1;
+ outputData = output.m_repr.large.data;
+ } else {
+ outputCapacity = SsoBufSize;
+ static_assert(SsoBufSize < std::numeric_limits<int8_t>::max());
+ output.m_repr.small.isLarge = 0;
+ output.m_repr.small.length = static_cast<uint8_t>(outputLength) & smallStringLengthMask;
+ outputData = output.m_repr.small.buffer.data();
}
- output.m_data = static_cast<utf8*>(M_TakeOrReturn(output.allocator()->allocate(output.m_capacity)));
- fudAssert(output.m_data != nullptr);
+ fudAssert(outputData != nullptr);
- auto status = copyMem(output.m_data, m_capacity, data(), length());
- fudAssert(status == FudStatus::Success);
+ auto copyStatus = copyMem(outputData, outputCapacity, data(), length());
+ fudAssert(copyStatus == FudStatus::Success);
- status = copyMem(output.m_data + length(), output.m_capacity - length(), rhs, rhsLength);
- fudAssert(status == FudStatus::Success);
+ copyStatus = copyMem(outputData + length(), outputCapacity - length(), rhs, rhsLength);
+ fudAssert(copyStatus == FudStatus::Success);
auto terminateStatus = output.nullTerminate();
fudAssert(terminateStatus == FudStatus::Success);
@@ -576,29 +659,37 @@ StringResult String::catenate(const String& rhs) const
return StringResult::error(FudStatus::ArgumentInvalid);
}
- if (SIZE_MAX - m_length < rhs.length()) {
+ if (maxStringLength - length() < rhs.length()) {
return StringResult::error(FudStatus::Failure);
}
String output{};
output.m_allocator = m_allocator;
- output.m_length = m_length + rhs.length();
- output.m_capacity = output.m_length + 1;
-
- if (output.m_capacity < SSO_BUF_SIZE) {
- output.m_capacity = SSO_BUF_SIZE;
- }
-
- if (output.isLarge()) {
- output.m_data = static_cast<utf8*>(M_TakeOrReturn(output.allocator()->allocate(output.m_capacity)));
- fudAssert(output.m_data != nullptr);
+ size_t outputLength = length() + rhs.length();
+ size_t outputCapacity = outputLength + 1;
+ utf8* outputData{nullptr};
+ if (outputCapacity > SsoBufSize) {
+ output.m_repr.large.capacity = outputCapacity & largeStringCapacitymask;
+ output.m_repr.large.length = outputLength;
+ auto dataResult = output.allocator()->allocate(output.m_repr.large.capacity);
+ if (dataResult.isError()) {
+ return StringResult::error(dataResult.getError());
+ }
+ output.m_repr.large.data = static_cast<utf8*>(dataResult.getOkay());
+ output.m_repr.large.isLarge = 1;
+ outputData = output.m_repr.large.data;
+ } else {
+ outputCapacity = SsoBufSize;
+ static_assert(SsoBufSize < std::numeric_limits<int8_t>::max());
+ output.m_repr.small.isLarge = 0;
+ output.m_repr.small.length = static_cast<uint8_t>(outputLength) & smallStringLengthMask;
+ outputData = output.m_repr.small.buffer.data();
}
- auto* destPtr = output.dataMut();
- auto status = copyMem(destPtr, m_capacity, data(), length());
+ auto status = copyMem(outputData, outputCapacity, data(), length());
fudAssert(status == FudStatus::Success);
- status = copyMem(destPtr + length(), output.m_capacity - length(), rhs.data(), rhs.length());
+ status = copyMem(outputData + length(), outputCapacity - length(), rhs.data(), rhs.length());
fudAssert(status == FudStatus::Success);
auto terminateStatus = output.nullTerminate();
@@ -629,13 +720,14 @@ bool String::compare(const String& rhs) const
return diffResult.getOkay() == 0;
}
-FudStatus String::clear() {
+FudStatus String::clear()
+{
if (!valid()) {
return FudStatus::StringInvalid;
}
dataMut()[0] = '\0';
- m_length = 0;
+ setLength(0);
return FudStatus::Success;
}