/* * 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 { ssize_t cStringLength(const char* str) { constexpr auto maxLength = SSIZE_MAX - 1; return cStringLength(str, maxLength); } ssize_t cStringLength(const char* str, size_t maxLength) { if (str == nullptr || maxLength > (SSIZE_MAX - 1)) { return -1; } ssize_t size = 0; while (str[size] != 0 && static_cast(size) < maxLength) { size++; } if (str[size] != 0 && static_cast(size) == maxLength) { return static_cast(maxLength) + 1; } return size; } StringResult String::makeFromCString(const char* cString) { return makeFromCString(cString, &globalFudAllocator); } StringResult String::makeFromCString(const char* cString, Allocator* allocator) { if (allocator == nullptr) { return StringResult::error(FudStatus::NullPointer); } auto lenResult = cStringLength(cString); if (lenResult < 0 || lenResult >= SSIZE_MAX) { return StringResult::error(FudStatus::ArgumentInvalid); } String output{}; output.m_allocator = allocator; output.m_length = static_cast(lenResult); if (output.m_length >= output.m_capacity) { output.m_capacity = output.m_length + 1; output.m_data = static_cast(M_TakeOrReturn(output.m_allocator->allocate(output.m_capacity))); fudAssert(output.m_data != nullptr); } auto copyStatus = copyMem(output.data(), output.m_capacity, cString, output.m_length); fudAssert(copyStatus == FudStatus::Success); auto terminateStatus = output.nullTerminate(); fudAssert(terminateStatus == FudStatus::Success); return StringResult::okay(std::move(output)); } StringResult String::from(const String& rhs) { if (!rhs.valid()) { return StringResult::error(FudStatus::ArgumentInvalid); } String output{}; output.m_length = rhs.m_length; output.m_capacity = rhs.m_capacity; output.m_allocator = rhs.m_allocator; if (rhs.isLarge()) { output.m_data = static_cast(M_TakeOrReturn(output.m_allocator->allocate(output.m_capacity))); fudAssert(output.m_data != nullptr); } auto copyResult = copyMem(output.data(), output.m_capacity, rhs.data(), output.m_length); fudAssert(copyResult == FudStatus::Success); auto nullTerminateStatus = output.nullTerminate(); fudAssert(nullTerminateStatus == FudStatus::Success); return StringResult::okay(std::move(output)); } String::String(String&& rhs) noexcept : 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; auto terminateStatus = nullTerminate(); fudAssert(terminateStatus == FudStatus::Success); } } String::~String() { cleanup(); } FudStatus String::copy(const String& rhs) { if (this == &rhs) { return FudStatus::Success; } if (!rhs.valid()) { return FudStatus::ArgumentInvalid; } cleanup(); m_length = rhs.m_length; m_capacity = rhs.m_capacity; m_allocator = rhs.m_allocator; if (isLarge()) { m_data = static_cast(M_TakeOrReturn(m_allocator->allocate(m_capacity))); } auto copyResult = copyMem(data(), m_capacity, rhs.data(), m_length); fudAssert(copyResult == FudStatus::Success); auto nullTerminateStatus = nullTerminate(); fudAssert(nullTerminateStatus == FudStatus::Success); return FudStatus::Success; } 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); } return *this; } void String::cleanup() { if (isLarge() && m_data != nullptr && m_allocator != nullptr) { auto deallocStatus = m_allocator->deallocate(m_data, m_capacity); static_cast(deallocStatus); m_data = nullptr; } } FudStatus String::resize(size_t newCapacity) { if (!valid()) { return FudStatus::StringInvalid; } if (m_length >= newCapacity) { return FudStatus::OperationInvalid; } if (!isLarge() && newCapacity <= SSO_BUF_SIZE) { m_capacity = SSO_BUF_SIZE; return FudStatus::Success; } if (newCapacity <= SSO_BUF_SIZE) { BufType temp{BufType::constFill(0)}; auto copyResult = copyMem(data(), temp.size(), temp.data(), length()); fudAssert(copyResult == FudStatus::Success); auto deallocStatus = m_allocator->deallocate(m_data, m_capacity); m_capacity = SSO_BUF_SIZE; m_data = nullptr; copyMem(m_buffer, temp); data()[m_length] = '\0'; return deallocStatus != FudStatus::Success ? FudStatus::DeallocFailure : FudStatus::Success; } auto* newData = static_cast(M_TakeOrReturn(m_allocator->allocate(newCapacity))); fudAssert(newData != nullptr); auto copyResult = copyMem(newData, newCapacity, data(), length()); fudAssert(copyResult == FudStatus::Success); auto deallocStatus = FudStatus::Success; if (isLarge()) { deallocStatus = m_allocator->deallocate(data(), m_capacity); } m_capacity = newCapacity; m_data = newData; data()[m_length] = '\0'; fudAssert(valid()); return deallocStatus != FudStatus::Success ? FudStatus::DeallocFailure : FudStatus::Success; } bool String::nullTerminated() const { return data() != nullptr && m_length < m_capacity && data()[m_length] == '\0'; } bool String::valid() const { return m_allocator != nullptr && nullTerminated(); } bool String::utf8Valid() const { if (!valid()) { return false; } StringView view{*this}; return view.utf8Valid(); } FudStatus String::nullTerminate() { if (m_length < m_capacity) { data()[m_length] = '\0'; return FudStatus::Success; } return FudStatus::StringInvalid; } FudStatus String::reserve(size_t newCapacity) { if (!valid()) { return FudStatus::StringInvalid; } if (newCapacity < m_capacity) { return FudStatus::Success; } return resize(newCapacity); } [[nodiscard]] std::optional String::back() { if (!valid()) { return std::nullopt; } utf8 backChar = data()[m_length - 1]; if (Ascii::valid(backChar)) { return backChar; } return std::nullopt; } std::optional String::pop() { if (!valid()) { return std::nullopt; } if (m_length < 1) { return std::nullopt; } m_length--; auto letter = data()[m_length]; data()[m_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 (remainingLength() < 1) { auto newCapacity = m_capacity < SIZE_MAX / 2 ? m_capacity * 2 : SIZE_MAX; const auto resizeStatus = resize(newCapacity); if (resizeStatus != FudStatus::Success) { return resizeStatus; } fudAssert(m_capacity == newCapacity); } data()[m_length] = letter; m_length++; data()[m_length] = '\0'; return FudStatus::Success; } FudStatus String::pushBack(const FudUtf8& 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 (remainingLength() < letterSize) { auto newCapacity = m_capacity < SIZE_MAX / 2 ? m_capacity * 2 : SIZE_MAX; if ((newCapacity - m_capacity) < (letterSize + 1)) { return FudStatus::OperationInvalid; } const auto resizeStatus = resize(newCapacity); if (resizeStatus != FudStatus::Success) { return resizeStatus; } fudAssert(m_capacity == newCapacity); } auto copyStatus = copyMem(m_data + m_length, remainingLength(), letterData, letterSize); if (copyStatus != FudStatus::Success) { return copyStatus; } m_length += letterSize; data()[m_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() == m_data) { return FudStatus::Aliased; } 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 >= m_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); } auto* destPtr = data() + length(); auto status = copyMem(destPtr, m_capacity, source.data(), source.length()); fudAssert(status == FudStatus::Success); m_length += source.length(); status = nullTerminate(); return status; } StringResult String::catenate(const char* rhs) const { if (!valid()) { return StringResult::error(FudStatus::ArgumentInvalid); } auto lenResult = cStringLength(rhs); if (lenResult < 0 || lenResult >= SSIZE_MAX) { return StringResult::error(FudStatus::ArgumentInvalid); } size_t rhsLength = static_cast(lenResult); String output{}; if (SIZE_MAX - m_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); auto terminateStatus = output.nullTerminate(); fudAssert(terminateStatus == FudStatus::Success); return StringResult::okay(std::move(output)); } output.m_data = static_cast(M_TakeOrReturn(output.m_allocator->allocate(output.m_capacity))); fudAssert(output.m_data != nullptr); auto status = copyMem(output.m_data, m_capacity, data(), length()); fudAssert(status == FudStatus::Success); status = copyMem(output.m_data + length(), output.m_capacity - length(), rhs, rhsLength); fudAssert(status == 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 (SIZE_MAX - m_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(M_TakeOrReturn(output.m_allocator->allocate(output.m_capacity))); fudAssert(output.m_data != nullptr); } auto* destPtr = output.data(); auto status = copyMem(destPtr, m_capacity, data(), length()); fudAssert(status == FudStatus::Success); status = copyMem(destPtr + length(), output.m_capacity - 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; } const utf8* String::begin() const { return data(); } const utf8* String::end() const { return data() + size(); } } // namespace fud