/* * libfud * Copyright 2024 Dominick Allen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "fud_string.hpp" #include "fud_assert.hpp" #include namespace fud { StringResult String::makeFromCString(const char8_t* cString) { return makeFromCString(reinterpret_cast(cString)); } StringResult String::makeFromCString(const char* cString) { return makeFromCString(cString, &globalFudAllocator); } StringResult String::makeFromCString(const char8_t* cString, Allocator* allocator) { return makeFromCString(reinterpret_cast(cString), allocator); } StringResult String::makeFromCString(const char* cString, Allocator* allocator) { if (allocator == nullptr) { return StringResult::error(FudStatus::NullPointer); } if (!String::allocatorValid(allocator)) { return StringResult::error(FudStatus::ArgumentInvalid); } auto lenResult = cStringLength(cString); if (lenResult < 0 || lenResult >= std::numeric_limits::max()) { return StringResult::error(FudStatus::ArgumentInvalid); } auto outputLength = static_cast(lenResult); if constexpr (static_cast(std::numeric_limits::max()) > maxStringLength) { if (outputLength > maxStringLength) { return StringResult::error(FudStatus::ArgumentInvalid); } } String output{}; output.m_allocator = reinterpret_cast(allocator); utf8* outputData{nullptr}; size_t outputCapacity = outputLength + 1; bool isLarge = outputCapacity > SsoBufSize; if (isLarge) { auto status = output.makeLarge(outputCapacity, outputLength, outputData); if (status != FudStatus::Success) { return StringResult::error(status); } } else { output.makeSmall(outputCapacity, outputLength, outputData); } fudAssert(outputData != nullptr); auto copyStatus = copyMem(outputData, outputCapacity, cString, outputLength); fudAssert(copyStatus == FudStatus::Success); auto terminateStatus = output.nullTerminate(); fudAssert(terminateStatus == FudStatus::Success); return StringResult::okay(std::move(output)); } StringResult String::from(const String& rhs, Option allocatorOption) { if (!rhs.valid()) { return StringResult::error(FudStatus::ArgumentInvalid); } auto* allocator = allocatorOption.valueOr(rhs.allocator()); if (allocator == nullptr || !allocatorValid(allocator)) { return StringResult::error(FudStatus::ArgumentInvalid); } String output{}; output.m_allocator = reinterpret_cast(allocator); utf8* outputData{nullptr}; size_t outputCapacity{0}; size_t outputLength{0}; if (rhs.isLarge()) { auto status = output.makeLarge(outputCapacity, outputLength, outputData); if (status != FudStatus::Success) { return StringResult::error(status); } } else { output.makeSmall(outputCapacity, outputLength, outputData); } fudAssert(outputData != nullptr); auto copyResult = copyMem(output.dataMut(), outputCapacity, rhs.data(), outputLength); fudAssert(copyResult == FudStatus::Success); auto nullTerminateStatus = output.nullTerminate(); fudAssert(nullTerminateStatus == FudStatus::Success); return StringResult::okay(std::move(output)); } StringResult String::from(StringView view, Allocator* allocator) { if (allocator == nullptr || view.m_data == nullptr) { return StringResult::error(FudStatus::NullPointer); } if (!String::allocatorValid(allocator)) { return StringResult::error(FudStatus::ArgumentInvalid); } String output{}; output.m_allocator = reinterpret_cast(allocator); size_t outputCapacity = view.length() + 1U; bool isLarge = outputCapacity > SsoBufSize; utf8* data{nullptr}; if (isLarge) { auto status = output.makeLarge(outputCapacity, view.length(), data); if (status != FudStatus::Success) { return StringResult::error(status); } } else { output.makeSmall(outputCapacity, view.length(), data); } fudAssert(data != nullptr); auto copyStatus = copyMem(data, outputCapacity, view.m_data, view.length()); fudAssert(copyStatus == FudStatus::Success); auto terminateStatus = output.nullTerminate(); fudAssert(terminateStatus == FudStatus::Success); return StringResult::okay(std::move(output)); } String::String(String&& rhs) noexcept : m_allocator{rhs.m_allocator}, m_repr{std::move(rhs.m_repr)} { rhs.setSmall(); rhs.m_repr.small.length = 0; } String::~String() noexcept { cleanup(); } FudStatus String::copy(const String& rhs) { if (this == &rhs) { return FudStatus::Success; } if (!rhs.valid()) { return FudStatus::ArgumentInvalid; } cleanup(); m_allocator = rhs.m_allocator; m_repr = rhs.m_repr; utf8* outputData{nullptr}; size_t outputCapacity{}; size_t outputLength{}; if (isLarge()) { auto allocResult = allocator()->allocate(m_repr.large.capacity, 1); if (allocResult.isError()) { return allocResult.takeError(); } m_repr.large.data = reinterpret_cast(allocResult.takeOkay()); outputCapacity = m_repr.large.capacity; outputLength = m_repr.large.length; outputData = m_repr.large.data; } else { outputCapacity = SsoBufSize; outputLength = m_repr.small.length; outputData = m_repr.small.buffer.data(); } fudAssert(outputData != nullptr); auto copyResult = copyMem(dataMut(), outputCapacity, rhs.data(), outputLength); fudAssert(copyResult == FudStatus::Success); auto nullTerminateStatus = nullTerminate(); fudAssert(nullTerminateStatus == FudStatus::Success); return FudStatus::Success; } String& String::operator=(String&& rhs) noexcept { if (&rhs == this) { return *this; } cleanup(); m_allocator = rhs.m_allocator; m_repr = std::move(rhs.m_repr); if (isLarge()) { rhs.m_repr.large.data = nullptr; } return *this; } void String::cleanup() { const auto* allocPtr = allocator(); if (isLarge() && m_repr.large.data != nullptr && allocPtr != nullptr) { allocator()->deallocate(reinterpret_cast(m_repr.large.data), m_repr.large.capacity); m_repr.large.data = nullptr; } } FudStatus String::resize(size_t newCapacity) { if (!valid()) { return FudStatus::StringInvalid; } if (length() >= newCapacity) { return FudStatus::OperationInvalid; } if (!isLarge() && newCapacity <= SsoBufSize) { return FudStatus::Success; } if (newCapacity <= SsoBufSize) { fudAssert(isLarge()); auto len = static_cast(length()); BufType temp{BufType::constFill(0)}; auto copyResult = copyMem(dataMut(), temp.size(), temp.data(), len); fudAssert(copyResult == FudStatus::Success); allocator()->deallocate(reinterpret_cast(m_repr.large.data), m_repr.large.capacity); setSmall(); m_repr.small.length = len & smallStringLengthMask; copyMem(m_repr.small.buffer, temp); m_repr.small.buffer[len] = '\0'; return FudStatus::Success; } auto allocResult = allocator()->allocate(newCapacity, 1); if (allocResult.isError()) { return allocResult.takeError(); } auto* newData = reinterpret_cast(allocResult.takeOkay()); fudAssert(newData != nullptr); auto copyResult = copyMem(newData, newCapacity, data(), length()); fudAssert(copyResult == FudStatus::Success); if (isLarge()) { allocator()->deallocate(reinterpret_cast(m_repr.large.data), m_repr.large.capacity); } size_t len = length(); setLarge(); m_repr.large.capacity = newCapacity; m_repr.large.data = newData; m_repr.large.length = len; m_repr.large.data[m_repr.large.length] = '\0'; fudAssert(valid()); return FudStatus::Success; } bool String::nullTerminated() const { return data() != nullptr && length() <= capacity() && data()[length()] == '\0'; } bool String::valid() const { return allocator() != nullptr && nullTerminated(); } bool String::utf8Valid() const { if (!valid()) { return false; } StringView view{*this}; return view.utf8Valid(); } FudStatus String::nullTerminate() { if (length() <= capacity()) { dataMut()[length()] = '\0'; return FudStatus::Success; } return FudStatus::StringInvalid; } FudStatus String::grow() { auto cap = capacity(); if (cap >= maxStringLength) { return FudStatus::Full; } size_t additional = cap / 2; if (maxStringLength - additional < cap) { additional = maxStringLength - cap; } FudStatus reserveStatus{}; while (additional > 0) { reserveStatus = reserve(additional + cap); if (reserveStatus == FudStatus::Success) { break; } if (reserveStatus == FudStatus::AllocFailure) { additional /= 2; } else { return reserveStatus; } } if (reserveStatus != FudStatus::Success) { return reserveStatus; } fudAssert(isLarge()); return FudStatus::Success; } FudStatus String::reserve(size_t newCapacity) { if (!valid()) { return FudStatus::StringInvalid; } if (newCapacity < capacity()) { return FudStatus::Success; } return resize(newCapacity); } [[nodiscard]] Option String::front() { if (!valid() || length() < 1) { return NullOpt; } utf8 frontChar = dataMut()[0]; if (Ascii::valid(frontChar)) { return frontChar; } return NullOpt; } [[nodiscard]] Option String::back() { if (!valid() || length() < 1) { return NullOpt; } utf8 backChar = dataMut()[length() - 1]; if (Ascii::valid(backChar)) { return backChar; } return NullOpt; } Option String::pop() { if (!valid()) { 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'; } return letter; } FudStatus String::pushBack(char letter) { return pushBack(static_cast(letter)); } FudStatus String::pushBack(utf8 letter) { if (!valid()) { return FudStatus::StringInvalid; } if (remainingCapacity() < 1) { auto growStatus = grow(); if (growStatus != FudStatus::Success) { return growStatus; } fudAssert(isLarge()); } 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; } FudStatus String::pushBack(const Utf8& letter) { if (!valid()) { return FudStatus::StringInvalid; } if (!letter.valid()) { return FudStatus::ArgumentInvalid; } const auto* letterData = letter.data(); if (letterData == nullptr) { return FudStatus::ArgumentInvalid; } auto letterSize = letter.size(); if (remainingCapacity() < letterSize) { auto growStatus = grow(); if (growStatus != FudStatus::Success) { return growStatus; } fudAssert(isLarge()); } if (remainingCapacity() < letterSize) { 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(isLarge()); fudAssert(m_repr.large.capacity == newCapacity); } auto copyStatus = copyMem(dataMut() + length(), remainingCapacity(), letterData, letterSize); if (copyStatus != FudStatus::Success) { return copyStatus; } 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; } FudStatus String::append(const char* source) { auto lenResult = cStringLength(source); if (lenResult < 0 || lenResult >= SSIZE_MAX) { return FudStatus::ArgumentInvalid; } return this->append(StringView{static_cast(lenResult), source}); } FudStatus String::append(const String& source) { return append(source.asView()); } FudStatus String::append(StringView source) { if (!valid()) { return FudStatus::StringInvalid; } if (source.data() == nullptr) { return FudStatus::NullPointer; } const size_t newLength = length() + source.length(); if (newLength < length()) { return FudStatus::OperationInvalid; } const size_t newSize = newLength + 1; // cppcheck-suppress knownConditionTrueFalse if (newSize < newLength) { // cppcheck-suppress knownConditionTrueFalse return FudStatus::OperationInvalid; } 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(isLarge()); fudAssert(m_repr.large.capacity == newCapacity); } auto* destPtr = dataMut() + length(); auto status = copyMem(destPtr, remainingCapacity(), source.data(), source.length()); fudAssert(status == FudStatus::Success); 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; } DrainResult String::drain(const char* source) { auto lenResult = cStringLength(source); if (lenResult < 0 || lenResult >= SSIZE_MAX) { return {0, FudStatus::ArgumentInvalid}; } return drain(StringView{static_cast(lenResult), source}); } DrainResult String::drain(const String& source) { return drain(source.asView()); } DrainResult String::drain(StringView source) { DrainResult result{0, FudStatus::Success}; if (!valid()) { result.status = FudStatus::StringInvalid; return result; } if (source.data() == nullptr) { result.status = FudStatus::NullPointer; return result; } if (source.length() == 0) { return result; } if (remainingCapacity() > 0) { StringView firstPart{source}; if (source.length() > remainingCapacity()) { firstPart.m_length = remainingCapacity(); } auto* destPtr = dataMut() + length(); auto status = copyMem(destPtr, remainingCapacity(), firstPart.m_data, firstPart.m_length); fudAssert(status == FudStatus::Success); addToLength(firstPart.m_length); result.bytesDrained += firstPart.m_length; status = nullTerminate(); source.advanceUnsafe(firstPart.m_length); fudAssert(status == FudStatus::Success); } if (source.length() == 0) { return result; } const size_t newLength = length() + source.length(); if (newLength < length()) { result.status = FudStatus::OperationInvalid; return result; } const size_t newSize = newLength + 1; // cppcheck-suppress knownConditionTrueFalse if (newSize < newLength) { // cppcheck-suppress knownConditionTrueFalse result.status = FudStatus::OperationInvalid; return result; } 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(isLarge()); fudAssert(m_repr.large.capacity == newCapacity); } auto* destPtr = dataMut() + length(); result.status = copyMem(destPtr, remainingCapacity(), source.data(), source.length()); fudAssert(result.status == FudStatus::Success); result.bytesDrained += source.length(); setLength(newLength); result.status = nullTerminate(); return result; } StringResult String::catenate(const char* rhs) const { if (!valid()) { return StringResult::error(FudStatus::ArgumentInvalid); } auto lenResult = cStringLength(rhs); if (lenResult < 0 || lenResult >= static_cast(maxStringLength)) { return StringResult::error(FudStatus::ArgumentInvalid); } size_t rhsLength = static_cast(lenResult); if (maxStringLength - length() < rhsLength) { return StringResult::error(FudStatus::Failure); } size_t outputLength = rhsLength + length(); String output{}; output.m_allocator = m_allocator; size_t outputCapacity = outputLength + 1; utf8* outputData{nullptr}; if (outputCapacity > SsoBufSize) { auto status = output.makeLarge(outputCapacity, outputLength, outputData); if (status != FudStatus::Success) { return StringResult::error(status); } } else { output.makeSmall(outputCapacity, outputLength, outputData); } fudAssert(outputData != nullptr); auto copyStatus = copyMem(outputData, outputCapacity, data(), length()); fudAssert(copyStatus == FudStatus::Success); copyStatus = copyMem(outputData + length(), outputCapacity - length(), rhs, rhsLength); fudAssert(copyStatus == FudStatus::Success); auto terminateStatus = output.nullTerminate(); fudAssert(terminateStatus == FudStatus::Success); return StringResult::okay(std::move(output)); } StringResult String::catenate(const String& rhs) const { if (!valid() || !rhs.valid()) { return StringResult::error(FudStatus::ArgumentInvalid); } if (maxStringLength - length() < rhs.length()) { return StringResult::error(FudStatus::Failure); } String output{}; output.m_allocator = m_allocator; size_t outputLength = length() + rhs.length(); size_t outputCapacity = outputLength + 1; utf8* outputData{nullptr}; if (outputCapacity > SsoBufSize) { auto status = output.makeLarge(outputCapacity, outputLength, outputData); if (status != FudStatus::Success) { return StringResult::error(status); } } else { output.makeSmall(outputCapacity, outputLength, outputData); } auto status = copyMem(outputData, outputCapacity, data(), length()); fudAssert(status == FudStatus::Success); status = copyMem(outputData + length(), outputCapacity - length(), rhs.data(), rhs.length()); fudAssert(status == FudStatus::Success); auto terminateStatus = output.nullTerminate(); fudAssert(terminateStatus == FudStatus::Success); return StringResult::okay(std::move(output)); } bool String::compare(const String& rhs) const { if (!valid() || !rhs.valid()) { return false; } if (length() != rhs.length()) { return false; } if (isLarge() && data() == rhs.data()) { return true; } auto diffResult = compareMem(data(), length(), rhs.data(), rhs.length()); if (diffResult.isError()) { return false; } return diffResult.getOkay() == 0; } FudStatus String::clear() { if (!valid()) { return FudStatus::StringInvalid; } dataMut()[0] = '\0'; setLength(0); return FudStatus::Success; } const utf8* String::begin() const { return data(); } const utf8* String::end() const { return data() + size(); } void String::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((m_repr.small.length + augend)) & smallStringLengthMask; } } void String::setLength(size_t newLength) { if (isLarge()) { fudAssert(newLength < maxStringLength); m_repr.large.length = newLength; } else { fudAssert(newLength < maxSmallStringLength); m_repr.small.length = static_cast(newLength) & smallStringLengthMask; } } FudStatus String::makeLarge(size_t cap, size_t len, utf8*& outputData) { m_repr.large.capacity = cap; m_repr.large.length = len; auto dataResult = allocator()->allocate(cap, 1); if (dataResult.isError()) { return dataResult.getError(); } m_repr.large.data = reinterpret_cast(dataResult.getOkay()); outputData = m_repr.large.data; setLarge(); return FudStatus::Success; } void String::makeSmall(size_t& cap, size_t len, utf8*& outputData) { fudAssert(len < SsoBufSize); cap = SsoBufSize; static_assert(SsoBufSize < std::numeric_limits::max()); m_repr.small.length = len & smallStringLengthMask; outputData = m_repr.small.buffer.data(); setSmall(); } } // namespace fud