diff options
Diffstat (limited to 'source')
-rw-r--r-- | source/fud_string.cpp | 1231 | ||||
-rw-r--r-- | source/fud_string_view.cpp | 1131 |
2 files changed, 1243 insertions, 1119 deletions
diff --git a/source/fud_string.cpp b/source/fud_string.cpp index b10f6ee..c1937be 100644 --- a/source/fud_string.cpp +++ b/source/fud_string.cpp @@ -84,6 +84,9 @@ String::String(const String& rhs) : m_length{rhs.m_length}, m_capacity{rhs.m_cap String::String(String&& rhs) : m_length{rhs.m_length}, m_capacity{rhs.m_capacity} { + if (isLarge()) { + cleanup(); + } if (rhs.isLarge()) { m_data = rhs.m_data; rhs.m_data = nullptr; @@ -143,6 +146,37 @@ void String::cleanup() } } +FudStatus String::resize(size_t newCapacity) +{ + 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)}; + static_cast<void>(copyMem(data(), temp.size(), temp.data(), length())); + m_capacity = SSO_BUF_SIZE; + fudFree(m_data); + m_data = nullptr; + copyMem(m_buffer, temp); + + return FudStatus::Success; + } + + auto* newData = fudRealloc(m_data, newCapacity); + if (newData == nullptr) { + return FudStatus::AllocFailure; + } + m_capacity = newCapacity; + m_data = static_cast<utf8*>(newData); + return FudStatus::Success; +} + bool String::nullTerminated() const { return data() != nullptr && m_length < m_capacity && data()[m_length] == '\0'; @@ -172,6 +206,19 @@ FudStatus String::nullTerminate() 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<utf8> String::back() { if (!valid()) { @@ -188,18 +235,23 @@ FudStatus String::nullTerminate() std::optional<utf8> String::pop() { + if (!valid()) { + return std::nullopt; + } + if (m_length < 1) { return std::nullopt; } + m_length--; - auto letter = m_data[m_length]; - m_data[m_length] = '\0'; + auto letter = data()[m_length]; + data()[m_length] = '\0'; return letter; } FudStatus String::pushBack(char letter) { - return pushBack(static_cast<uint8_t>(letter)); + return pushBack(static_cast<utf8>(letter)); } FudStatus String::pushBack(utf8 letter) @@ -209,12 +261,17 @@ FudStatus String::pushBack(utf8 letter) } if (remainingLength() < 1) { - return FudStatus::OperationInvalid; + 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); } - m_data[m_length] = letter; + data()[m_length] = letter; m_length++; - m_data[m_length] = '\0'; + data()[m_length] = '\0'; return FudStatus::Success; } @@ -235,8 +292,16 @@ FudStatus String::pushBack(const FudUtf8& letter) } auto letterSize = letter.size(); - if (letterSize > remainingLength()) { - return FudStatus::OperationInvalid; + 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); @@ -246,11 +311,26 @@ FudStatus String::pushBack(const FudUtf8& letter) } m_length += letterSize; - m_data[m_length] = '\0'; + 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::InvalidInput; + } + + return this->append(StringView{static_cast<size_t>(lenResult), source}); +} + +FudStatus String::append(const String& source) +{ + return append(source.asView()); +} + FudStatus String::append(StringView source) { if (!valid()) { @@ -261,18 +341,31 @@ FudStatus String::append(StringView source) return FudStatus::Aliased; } - const auto newLength = m_length + source.length(); + const auto newLength = length() + source.length(); + if (newLength < length()) + { + return FudStatus::OperationInvalid; + } const auto newSize = newLength + 1; - if (newSize >= m_capacity) { + if (newSize < newLength) + { 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 = m_data + m_length; + auto* destPtr = data() + length(); auto status = copyMem(destPtr, m_capacity, source.data(), source.length()); - if (status == FudStatus::Success) { // likely - m_length += source.length(); - status = nullTerminate(); - } + fudAssert(status == FudStatus::Success); + + m_length += source.length(); + status = nullTerminate(); return status; } @@ -295,6 +388,9 @@ String String::catenate(const String& rhs) const 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*>(fudAlloc(output.m_capacity)); } @@ -342,1107 +438,4 @@ const utf8* String::end() const return data() + size(); } -bool StringView::nullTerminated() const -{ - return m_data != nullptr && m_data[m_length] == '\0'; -} - -bool StringView::utf8Valid() const -{ - if (m_data == nullptr) { - return false; - } - - for (size_t idx = 0; idx < m_length;) { - if (Ascii::valid(m_data[idx])) { - idx++; - } else if (idx + 1 < m_length && Utf82Byte::valid(m_data[idx], m_data[idx + 1])) { - idx += 2; - } else if (idx + 2 < m_length && Utf83Byte::valid(m_data[idx], m_data[idx + 1], m_data[idx + 2])) { - idx += 3; - } else if ( - idx + 3 < m_length && Utf84Byte::valid(m_data[idx], m_data[idx + 1], m_data[idx + 2], m_data[idx + 3])) { - idx += 4; - } else { - return false; - } - } - - return true; -} - -Result<size_t, FudStatus> StringView::skipWhitespace() -{ - using RetType = Result<size_t, FudStatus>; - if (m_data == nullptr) { - return RetType::error(FudStatus::NullPointer); - } - size_t index = 0; - while (m_length > 0 && char_is_space(static_cast<char>(m_data[0]))) { - m_data++; - m_length--; - index++; - } - - return RetType::okay(index); -} - -Result<size_t, FudStatus> StringView::trimWhitespace() -{ - using RetType = Result<size_t, FudStatus>; - if (m_data == nullptr) { - return RetType::error(FudStatus::NullPointer); - } - - size_t count = 0; - while (m_length > 0 && char_is_space(static_cast<char>(m_data[m_length - 1]))) { - m_length--; - count++; - } - - return RetType::okay(count); -} - -#if 0 - -FudStatus fud_string_truncate(ExtBasicString* source, ssize_t newLength) -{ - if (source == nullptr) { - return FudStatus::NullPointer; - } - StringBorrow wrapper{*source}; - if (!wrapper.valid()) { - return FudStatus::StringInvalid; - } - - if ((newLength > 0 && static_cast<size_t>(newLength) > source->m_length) || - (static_cast<size_t>(-newLength) > source->m_length)) { - return FudStatus::InvalidInput; - } - - if (newLength < 0) { - source->m_length = source->m_length - static_cast<size_t>(-newLength); - } else { - source->m_length = static_cast<size_t>(newLength); - } - - return wrapper.nullTerminate(); -} - -FudStatus fud_string_reverse(ExtBasicString* source) -{ - if (source == nullptr || source->m_data == nullptr) { - return FudStatus::NullPointer; - } - return fud_string_reverse_substring(source, StringView{source->m_length, source->m_data}); -} - -FudStatus fud_string_reverse_substring(ExtBasicString* source, StringView subString) -{ - auto dataOffset = subString.data - source->m_data; - if (dataOffset < 0 || static_cast<size_t>(dataOffset) > source->m_length) { - return FudStatus::InvalidInput; - } - if (static_cast<size_t>(dataOffset) + subString.length > source->m_length) { - return FudStatus::InvalidInput; - } - - if (source == nullptr || source->m_data == nullptr) { - return FudStatus::NullPointer; - } - - StringView view{subString}; - - size_t index = 0; - auto* data = source->m_data + dataOffset; - while (index < subString.length) { - if (ext_lib_char_is_ascii(static_cast<char>(data[index]))) { - index++; - continue; - } - auto utf8 = FudUtf8::fromStringView(view, index); - if (!utf8.valid()) { - return ExtUtf8Invalid; - } - const auto* utf8Data = utf8.data(); - if (utf8Data == nullptr) { - return ExtFailure; - } - auto utf8Size = utf8.size(); - switch (utf8Size) { - case 2: - data[index] = utf8Data[1]; - data[index + 1] = utf8Data[0]; - break; - case 3: - data[index] = utf8Data[2]; - data[index + 2] = utf8Data[0]; - break; - case 4: - data[index] = utf8Data[3]; - data[index + 1] = utf8Data[2]; - data[index + 2] = utf8Data[1]; - data[index + 3] = utf8Data[0]; - break; - default: - return ExtFailure; - } - index += utf8Size; - } - - ext_lib::DataView<uint8_t> dataView{subString.length, data}; - reverse(dataView); - - return FudStatus::Success; -} - -FudStatus fud_string_compare(StringView levo, StringView dextro, int* difference) -{ - if (anyAreNull(difference, levo.data, dextro.data)) { - return FudStatus::NullPointer; - } - - int diff = 0; - size_t index = 0; - while (diff == 0 && index < levo.length && index < dextro.length) { - diff = levo.data[index] - dextro.data[index]; - index++; - } - - if (diff != 0 || levo.length == dextro.length) { - /* nothing to do */ - } else if (levo.length > dextro.length) { - diff = static_cast<int>(levo.data[index]); - } else { - diff = -static_cast<int>(dextro.data[index]); - } - - *difference = diff; - return FudStatus::Success; -} - -FudStatus fud_string_chr(StringView extStringView, char character, size_t* index) -{ - if (anyAreNull(extStringView.data, index)) { - return FudStatus::NullPointer; - } - - bool found = false; - for (size_t localIndex = 0; localIndex < extStringView.length; ++localIndex) { - if (extStringView.data[localIndex] == static_cast<uint8_t>(character)) { - *index = localIndex; - found = true; - break; - } - } - - if (found) { - return FudStatus::Success; - } - - return ExtNotFound; -} - -FudStatus fud_string_unicode_chr(StringView extString, const ExtUtf8* unicode, size_t* index) -{ - if (anyAreNull(extString.data, unicode, index)) { - return FudStatus::NullPointer; - } - - if (!unicode->valid()) { - return ExtUtf8Invalid; - } - - size_t charSize = unicode->size(); - ExtDebugAssert(charSize != 0); - const uint8_t* dataMem = unicode->data(); - ExtDebugAssert(dataMem != nullptr); - - std::array<uint8_t, 4> localData{}; - auto copyStatus = ExtCopyMem(localData.data(), localData.size(), dataMem, charSize); - ExtDebugAssert(copyStatus == FudStatus::Success); - - for (size_t sIdx = 0; sIdx + charSize - 1 < extString.length;) { - - auto localChar = FudUtf8::fromStringView(extString, sIdx); - - if (!localChar.valid()) { - return ExtUtf8Invalid; - } - - if (localChar.m_variant == unicode->m_variant) { - *index = sIdx; - return FudStatus::Success; - } - - sIdx += localChar.size(); - } - - return ExtNotFound; -} - -namespace ext_lib { - -FudStatus fud_string_span_c_api( - const StringView& inputView, - const StringView& characterSetString, - StringView& result, - bool inSet) -{ - size_t firstIndex = inputView.length; - - size_t sIdx = 0; - while (sIdx < firstIndex) { - auto stringChar = FudUtf8::fromStringView(inputView, sIdx); - if (!stringChar.valid()) { - return ExtUtf8Invalid; - } - - size_t cIdx = 0; - bool found = false; - while (firstIndex > 0 && cIdx < firstIndex && cIdx < characterSetString.length) { - auto setChar = FudUtf8::fromStringView(characterSetString, cIdx); - if (!setChar.valid()) { - return ExtUtf8Invalid; - } - - if (stringChar == setChar) { - found = true; - } - - cIdx += setChar.size(); - } - - if (!inSet && found) { - firstIndex = sIdx; - } else if (inSet && !found) { - if (sIdx > 0) { - firstIndex = sIdx; - } - break; - } - - sIdx += stringChar.size(); - } - - if (firstIndex < inputView.length) { - result.length = inputView.length - firstIndex; - result.data = inputView.data + firstIndex; - return FudStatus::Success; - } - - return ExtNotFound; -} - -FudStatus fud_string_span_set(StringView inputView, const ExtUtf8Set* characterSet, StringView* stringView, bool inSet) -{ - if (anyAreNull(inputView.data, characterSet, stringView)) { - return FudStatus::NullPointer; - } - - if (!characterSet->valid()) { - return ExtUtf8Invalid; - } - - size_t firstIndex = inputView.length; - size_t sIdx = 0; - while (sIdx < firstIndex) { - auto localChar = FudUtf8::fromStringView(inputView, sIdx); - if (!localChar.valid()) { - return ExtUtf8Invalid; - } - - bool found = characterSet->contains(localChar); - - if (!inSet && found) { - firstIndex = sIdx; - } else if (inSet && !found) { - if (sIdx > 0) { - firstIndex = sIdx; - } - break; - } - - sIdx += localChar.size(); - } - - if (firstIndex < inputView.length) { - stringView->length = inputView.length - firstIndex; - stringView->data = inputView.data + firstIndex; - return FudStatus::Success; - } - - return ExtNotFound; -} - -} // namespace ext_lib - -FudStatus fud_string_span(StringView extString, StringView characterSetString, StringView* result) -{ - if (result == nullptr) { - return FudStatus::NullPointer; - } - - const StringView inputView{extString}; - const StringView characterSet{characterSetString}; - - return fud_string_span_c_api(inputView, characterSet, *result, true); -} - -FudStatus fud_string_c_span(StringView extString, StringView characterSetString, StringView* result) -{ - if (result == nullptr) { - return FudStatus::NullPointer; - } - - const StringView inputView{extString}; - const StringView characterSet{characterSetString}; - - return fud_string_span_c_api(inputView, characterSet, *result, false); -} - -FudStatus fud_string_span_set(StringView extString, const ExtUtf8Set* characterSet, StringView* stringView) -{ - return ext_lib::fud_string_span_set(extString, characterSet, stringView, true); -} - -FudStatus fud_string_c_span_set(StringView extString, const ExtUtf8Set* characterSet, StringView* stringView) -{ - return ext_lib::fud_string_span_set(extString, characterSet, stringView, false); -} - -FudStatus fud_string_find_substring(StringView haystack, StringView needle, StringView* stringView) -{ - if (anyAreNull(haystack.data, needle.data, stringView)) { - return FudStatus::NullPointer; - } - - if (needle.length > haystack.length) { - return ExtNotFound; - } - - if (needle.length == 1) { - size_t index = 0; - auto chrFindStatus = fud_string_chr(haystack, static_cast<char>(needle.data[0]), &index); - if (chrFindStatus == FudStatus::Success) { - stringView->data = haystack.data + index; - stringView->length = 1; - } - return chrFindStatus; - } - - size_t haystackIdx = 0; - while (haystackIdx < haystack.length - needle.length) { - StringView lhs; - lhs.data = haystack.data + haystackIdx; - lhs.length = haystack.length - haystackIdx; - size_t lhsIndex = 0; - auto chrFindStatus = fud_string_chr(lhs, static_cast<char>(needle.data[0]), &lhsIndex); - if (chrFindStatus != FudStatus::Success) { - return chrFindStatus; - } - haystackIdx += lhsIndex; - // GE or GT? - if (haystackIdx + needle.length >= haystack.length) { - break; - } - lhs.data = haystack.data + haystackIdx; - lhs.length = needle.length; - - int difference = -1; - auto cmpStatus = fud_string_compare(lhs, needle, &difference); - ExtDebugAssert(cmpStatus == FudStatus::Success); - if (difference == 0) { - stringView->data = lhs.data; - stringView->length = lhs.length; - return FudStatus::Success; - } - haystackIdx++; - } - - return ExtNotFound; -} - -namespace ext_lib { - -FudStatus skipWhitespace(StringView& view, size_t& skipIndex) -{ - auto skipResult = view.skipWhitespace(); - if (skipResult.isError()) { - return skipResult.getError(); - } - skipIndex = skipResult.getOkay(); - if (view.length < 1) { - return FudStatus::InvalidInput; - } - return FudStatus::Success; -} - -} // namespace ext_lib - -FudStatus fud_string_view_skip_whitespace(StringView* view) -{ - if (view == nullptr) { - return FudStatus::NullPointer; - } - - StringView sView{*view}; - auto skipResult = sView.skipWhitespace(); - if (skipResult.isError()) { - return skipResult.getError(); - } - view->data = sView.data; - view->length = sView.length; - return FudStatus::Success; -} - -FudStatus fud_string_view_trim_whitespace(StringView* view) -{ - if (view == nullptr) { - return FudStatus::NullPointer; - } - - StringView sView{*view}; - auto skipResult = sView.trimWhitespace(); - if (skipResult.isError()) { - return skipResult.getError(); - } - view->data = sView.data; - view->length = sView.length; - return FudStatus::Success; -} - -namespace impl { -constexpr ext_lib::Array<int8_t, 256> AsciiLookup{ - {-1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, - -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, - -2, -2, -2, -2, -2, -2, -2, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 32, 33, 34, 35, -2, -2, -2, -2, -2, -2, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, - 29, 30, 31, 32, 33, 34, 35, -2, -2, -2, -2, -2, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, - -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, - -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, - -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, - -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3}}; - -// NOLINTBEGIN(readability-magic-numbers) -static_assert(AsciiLookup[static_cast<size_t>('0')] == 0); -static_assert(AsciiLookup[static_cast<size_t>('9')] == 9); -static_assert(AsciiLookup[static_cast<size_t>('a')] == 10); -static_assert(AsciiLookup[static_cast<size_t>('A')] == 10); -static_assert(AsciiLookup[static_cast<size_t>('f')] == 15); -static_assert(AsciiLookup[static_cast<size_t>('F')] == 15); -static_assert(AsciiLookup[127] == -2); -static_assert(AsciiLookup[128] == -3); -static_assert(AsciiLookup[255] == -3); -// NOLINTEND(readability-magic-numbers) - -FudStatus determineRadix(StringView input, uint8_t& radix, size_t& index) -{ - if (input.length < 1) { - return FudStatus::InvalidInput; - } - - if (input.length == 1 && input.data[0] == '0') { - radix = ExtRadixOctal; - return FudStatus::Success; - } - - if (input.length == 1) { - radix = ExtRadixDecimal; - return FudStatus::Success; - } - - if (input.data[0] == '0' && (input.data[1] == 'x' || input.data[1] == 'X')) { - radix = ExtRadixHexadecimal; - index += 2; - return FudStatus::Success; - } - - if (input.data[0] == '0') { - auto nextChar = input.data[1]; - auto nextVal = AsciiLookup[nextChar]; - if (nextVal >= 0 && nextVal < ExtRadixOctal) { - radix = ExtRadixOctal; - return FudStatus::Success; - } - if (nextVal >= ExtRadixOctal) { - return FudStatus::InvalidInput; - } - } - - radix = ExtRadixDecimal; - return FudStatus::Success; -} - -FudStatus getRadix(StringView& view, uint8_t& radix, size_t& skipIndex) -{ - if (radix == 0) { - size_t radixIndex = 0; - auto status = determineRadix(view, radix, radixIndex); - if (status != FudStatus::Success) { - return status; - } - skipIndex += radixIndex; - view.data += radixIndex; - view.length -= radixIndex; - } else if (radix == ExtRadixHexadecimal && view.length > 2 && (view.data[1] == 'x' || view.data[1] == 'X')) { - skipIndex += 2; - view.data += 2; - view.length -= 2; - } - return FudStatus::Success; -} - -FudStatus checkNegative(StringView& view, bool& isNegative, size_t& skipIndex) -{ - isNegative = view.data[0] == '-'; - if (isNegative && view.length == 1) { - return FudStatus::InvalidInput; - } - if (isNegative) { - skipIndex += 1; - view.data++; - view.length--; - } - return FudStatus::Success; -} - -FudStatus checkPlusSigned(StringView& view, size_t& skipIndex) -{ - auto isPlusSigned = view.data[0] == '+'; - if (isPlusSigned && view.length == 1) { - return FudStatus::InvalidInput; - } - if (isPlusSigned) { - skipIndex += 1; - view.data++; - view.length--; - } - return FudStatus::Success; -} - -template <typename T> -FudStatus stringViewToUnsignedInteger(StringView input, T& number, uint8_t specifiedRadix, size_t& index) -{ - if (input.data == nullptr) { - return FudStatus::NullPointer; - } - - if (specifiedRadix == 1 || specifiedRadix > ExtMaxRadix || input.length < 1) { - return FudStatus::InvalidInput; - } - - uint8_t radix = specifiedRadix; - - StringView view{input}; - size_t skipIndex = 0; - auto status = ext_lib::skipWhitespace(view, skipIndex); - if (status != FudStatus::Success) { - return status; - } - - status = checkPlusSigned(view, skipIndex); - if (status != FudStatus::Success) { - return FudStatus::InvalidInput; - } - - status = getRadix(view, radix, skipIndex); - - T num = 0; - size_t digitIndex = 0; - - while (digitIndex < view.length) { - auto digitResult = impl::AsciiLookup[view.data[digitIndex]]; - if (digitResult >= radix || digitResult < 0) { - break; - } - - auto digit = static_cast<uint8_t>(digitResult); - if (std::numeric_limits<T>::max() / radix < num) { - return FudStatus::InvalidInput; - } - num *= radix; - if (std::numeric_limits<T>::max() - digit < num) { - return FudStatus::InvalidInput; - } - num += digit; - digitIndex++; - } - if (digitIndex < 1) { - return FudStatus::InvalidInput; - } - - index = skipIndex + digitIndex; - number = num; - - return FudStatus::Success; -} - -template <typename T> -FudStatus stringViewToUnsignedInteger(StringView input, T* number, uint8_t specifiedRadix, size_t* index) -{ - if (anyAreNull(input.data, number)) { - return FudStatus::NullPointer; - } - - size_t localIndex = 0; - - auto status = stringViewToUnsignedInteger(input, *number, specifiedRadix, localIndex); - if (status == FudStatus::Success && index != nullptr) { - *index = localIndex; - } - return status; -} - -template <typename T> -FudStatus viewToSignedIntPositive(StringView view, uint8_t radix, size_t& digitIndex, T& num) -{ - digitIndex = 0; - while (digitIndex < view.length) { - int8_t digitResult = impl::AsciiLookup[view.data[digitIndex]]; - if (digitResult >= radix) { - return FudStatus::InvalidInput; - } - if (digitResult < 0) { - break; - } - auto digit = static_cast<uint8_t>(digitResult); - if (std::numeric_limits<T>::max() / radix < num) { - return FudStatus::InvalidInput; - } - num = static_cast<T>(num * radix); - if (std::numeric_limits<T>::max() - digit < num) { - return FudStatus::InvalidInput; - } - num = static_cast<T>(num + digit); - digitIndex++; - } - - return FudStatus::Success; -} - -template <typename T> -FudStatus viewToSignedIntNegative(StringView view, uint8_t radix, size_t& digitIndex, T& num) -{ - digitIndex = 0; - while (digitIndex < view.length) { - int8_t digitResult = impl::AsciiLookup[view.data[digitIndex]]; - if (digitResult >= radix) { - return FudStatus::InvalidInput; - } - if (digitResult < 0) { - break; - } - auto digit = static_cast<uint8_t>(digitResult); - if ((std::numeric_limits<T>::min() / radix > num)) { - return FudStatus::InvalidInput; - } - num = static_cast<T>(num * radix); - if (std::numeric_limits<T>::min() + digit > num) { - return FudStatus::InvalidInput; - } - num = static_cast<T>(num - digit); - digitIndex++; - } - - return FudStatus::Success; -} - -template <typename T> -FudStatus stringViewToSignedInteger(StringView input, T& number, uint8_t specifiedRadix, size_t& index) -{ - if (input.data == nullptr) { - return FudStatus::NullPointer; - } - - auto radix = specifiedRadix; - - StringView view{input}; - size_t skipIndex = 0; - auto status = ext_lib::skipWhitespace(view, skipIndex); - if (status != FudStatus::Success) { - return status; - } - - bool isNegative = false; - status = checkNegative(view, isNegative, skipIndex); - if (status != FudStatus::Success) { - return FudStatus::InvalidInput; - } - - if (!isNegative) { - status = checkPlusSigned(view, skipIndex); - if (status != FudStatus::Success) { - return FudStatus::InvalidInput; - } - } - - status = getRadix(view, radix, skipIndex); - - T num = 0; - size_t digitIndex = 0; - - if (isNegative) { - status = viewToSignedIntNegative(view, radix, digitIndex, num); - } else { - status = viewToSignedIntPositive(view, radix, digitIndex, num); - } - if (status != FudStatus::Success) { - return status; - } - - if (digitIndex < 1) { - return FudStatus::InvalidInput; - } - - index = skipIndex + digitIndex; - number = num; - return FudStatus::Success; -} - -template <typename T> -FudStatus stringViewToSignedInteger(StringView input, T* number, uint8_t specifiedRadix, size_t* index) -{ - if (anyAreNull(input.data, number)) { - return FudStatus::NullPointer; - } - - if (specifiedRadix == 1 || specifiedRadix > ExtMaxRadix || input.length < 1) { - return FudStatus::InvalidInput; - } - - size_t localIndex = 0; - auto status = stringViewToSignedInteger(input, *number, specifiedRadix, localIndex); - if (status == FudStatus::Success && index != nullptr) { - *index = localIndex; - } - return status; -} - -} // namespace impl - -FudStatus fud_string_to_uint8(StringView input, uint8_t* number, uint8_t specifiedRadix, size_t* index) -{ - return impl::stringViewToUnsignedInteger(input, number, specifiedRadix, index); -} - -FudStatus fud_string_to_uint16(StringView input, uint16_t* number, uint8_t specifiedRadix, size_t* index) -{ - return impl::stringViewToUnsignedInteger(input, number, specifiedRadix, index); -} - -FudStatus fud_string_to_uint32(StringView input, uint32_t* number, uint8_t specifiedRadix, size_t* index) -{ - return impl::stringViewToUnsignedInteger(input, number, specifiedRadix, index); -} - -FudStatus fud_string_to_uint64(StringView input, uint64_t* number, uint8_t specifiedRadix, size_t* index) -{ - return impl::stringViewToUnsignedInteger(input, number, specifiedRadix, index); -} - -FudStatus fud_string_to_int8(StringView input, int8_t* number, uint8_t specifiedRadix, size_t* index) -{ - return impl::stringViewToSignedInteger(input, number, specifiedRadix, index); -} - -FudStatus fud_string_to_int16(StringView input, int16_t* number, uint8_t specifiedRadix, size_t* index) -{ - return impl::stringViewToSignedInteger(input, number, specifiedRadix, index); -} - -FudStatus fud_string_to_int32(StringView input, int32_t* number, uint8_t specifiedRadix, size_t* index) -{ - return impl::stringViewToSignedInteger(input, number, specifiedRadix, index); -} - -FudStatus fud_string_to_int64(StringView input, int64_t* number, uint8_t specifiedRadix, size_t* index) -{ - return impl::stringViewToSignedInteger(input, number, specifiedRadix, index); -} - -namespace impl { - -template <typename T> -bool isNanOrInf(T& num, StringView& view, T& sign, size_t& digitIndex) -{ - if (view.length >= 3) { - std::array<uint8_t, 3> letters{{view.data[0], view.data[1], view.data[2]}}; - ext_lib::mapMut(letters, ext_lib_char_to_lower); - if (letters[0] == 'i' && letters[1] == 'n' && letters[2] == 'f') { - num = sign * std::numeric_limits<T>::infinity(); - digitIndex = 3; - return true; - } - if (letters[0] == 'n' && letters[1] == 'a' && letters[2] == 'n') { - num = std::numeric_limits<T>::quiet_NaN(); - ; - digitIndex = 3; - return true; - } - } - return false; -} - -template <typename T> -FudStatus getWhole( - const StringView view, - size_t& digitIndex, - T& num, - T sign, - uint8_t radix, - bool& foundDecimal, - bool& foundExponent) -{ - while (digitIndex < view.length) { - auto nextChar = view.data[digitIndex]; - if (nextChar == '.') { - foundDecimal = true; - digitIndex++; - break; - } - - if (radix == ExtRadixDecimal && (nextChar == 'e' || nextChar == 'E')) { - foundExponent = true; - digitIndex++; - break; - } - - auto digitResult = impl::AsciiLookup[nextChar]; - if (digitResult >= radix) { - return FudStatus::InvalidInput; - } - if (digitResult < 0) { - break; - } - auto digit = static_cast<T>(digitResult) * sign; - num *= static_cast<T>(radix); - - num += digit; - digitIndex++; - } - return FudStatus::Success; -} - -template <typename T> -FudStatus getExponent(const StringView& view, size_t& digitIndex, T& num, uint8_t radix) -{ - int32_t exponent{}; - StringView tempView{view.length - digitIndex, view.data + digitIndex}; - size_t exponentLength{}; - auto status = tempView.toInt32(exponent, ExtRadixDecimal, exponentLength); - if (status != FudStatus::Success) { - return status; - } - digitIndex += exponentLength; - num = num * std::pow(static_cast<T>(radix), static_cast<T>(exponent)); - return FudStatus::Success; -} - -template <typename T> -FudStatus getFraction(const StringView view, size_t& digitIndex, T& num, T sign, uint8_t radix, bool& foundExponent) -{ - auto radixDiv = 1.0F / static_cast<T>(radix); - while (digitIndex < view.length) { - auto nextChar = view.data[digitIndex]; - if (radix == ExtRadixDecimal && (nextChar == 'e' || nextChar == 'E')) { - foundExponent = true; - digitIndex++; - break; - } - - auto digitResult = impl::AsciiLookup[nextChar]; - if (digitResult >= radix) { - return FudStatus::InvalidInput; - } - if (digitResult < 0) { - break; - } - auto digit = static_cast<T>(digitResult) * sign; - num += digit * radixDiv; - radixDiv /= static_cast<T>(radix); - digitIndex++; - } - return FudStatus::Success; -} - -template <typename T> -FudStatus stringViewToFloat(StringView input, T& number, size_t& index) -{ - if (input.data == nullptr) { - return FudStatus::NullPointer; - } - - if (input.length < 1) { - return FudStatus::InvalidInput; - } - - uint8_t radix = 0; - - StringView view{input}; - size_t skipIndex = 0; - - auto status = skipWhitespace(view, skipIndex); - if (status != FudStatus::Success) { - return status; - } - - T sign = 1.0; - bool isNegative = false; - status = impl::checkNegative(view, isNegative, skipIndex); - if (status != FudStatus::Success) { - return FudStatus::InvalidInput; - } - - if (!isNegative) { - status = checkPlusSigned(view, skipIndex); - } else { - sign = -1.0; - } - - if (status != FudStatus::Success) { - return FudStatus::InvalidInput; - } - - T num = 0; - size_t digitIndex = 0; - - auto retSuccess = [&]() { - index = skipIndex + digitIndex; - number = num; - return FudStatus::Success; - }; - - if (impl::isNanOrInf(num, view, sign, digitIndex)) { - return retSuccess(); - } - - status = impl::getRadix(view, radix, skipIndex); - if (status != FudStatus::Success) { - return status; - } - - bool foundDecimal = false; - bool foundExponent = false; - status = getWhole(view, digitIndex, num, sign, radix, foundDecimal, foundExponent); - - if (status == FudStatus::Success && foundExponent) { - status = getExponent(view, digitIndex, num, radix); - } - - if (status != FudStatus::Success) { - return status; - } - - if (!foundDecimal) { - if (digitIndex < 1) { - return FudStatus::InvalidInput; - } - - return retSuccess(); - } - - status = getFraction(view, digitIndex, num, sign, radix, foundExponent); - - if (foundExponent) { - status = getExponent(view, digitIndex, num, radix); - if (status != FudStatus::Success) { - return status; - } - } - - if (digitIndex < 1) { - return FudStatus::InvalidInput; - } - - if (std::isinf(num) || std::isnan(num)) // isnan is dubious here - likely unreachable - { - return ExtRangeError; - } - - return retSuccess(); -} - -template <typename T> -FudStatus stringViewToFloat(StringView input, T* number, size_t* index) -{ - if (anyAreNull(input.data, number)) { - return FudStatus::NullPointer; - } - - size_t localIndex{0}; - auto status = stringViewToFloat(input, *number, localIndex); - - if (status == FudStatus::Success && index != nullptr) { - *index = localIndex; - } - return status; -} - -} // namespace impl - -FudStatus fud_string_to_float(StringView input, float* number, size_t* index) -{ - return impl::stringViewToFloat(input, number, index); -} - -FudStatus fud_string_to_double(StringView input, double* number, size_t* index) -{ - return impl::stringViewToFloat(input, number, index); -} - -namespace fud { - -FudStatus StringView::toUint8(uint8_t& number, uint8_t specifiedRadix, size_t& strLen) const -{ - return ::impl::stringViewToUnsignedInteger(*this, number, specifiedRadix, strLen); -} - -FudStatus StringView::toUint16(uint16_t& number, uint8_t specifiedRadix, size_t& strLen) const -{ - return ::impl::stringViewToUnsignedInteger(*this, number, specifiedRadix, strLen); -} - -FudStatus StringView::toUint32(uint32_t& number, uint8_t specifiedRadix, size_t& strLen) const -{ - return ::impl::stringViewToUnsignedInteger(*this, number, specifiedRadix, strLen); -} - -FudStatus StringView::toUint64(uint64_t& number, uint8_t specifiedRadix, size_t& strLen) const -{ - return ::impl::stringViewToUnsignedInteger(*this, number, specifiedRadix, strLen); -} - -FudStatus StringView::toInt8(int8_t& number, uint8_t specifiedRadix, size_t& strLen) const -{ - return ::impl::stringViewToSignedInteger(*this, number, specifiedRadix, strLen); -} - -FudStatus StringView::toInt16(int16_t& number, uint8_t specifiedRadix, size_t& strLen) const -{ - return ::impl::stringViewToSignedInteger(*this, number, specifiedRadix, strLen); -} - -FudStatus StringView::toInt32(int32_t& number, uint8_t specifiedRadix, size_t& strLen) const -{ - return ::impl::stringViewToSignedInteger(*this, number, specifiedRadix, strLen); -} - -FudStatus StringView::toInt64(int64_t& number, uint8_t specifiedRadix, size_t& strLen) const -{ - return ::impl::stringViewToSignedInteger(*this, number, specifiedRadix, strLen); -} - -FudStatus StringView::toFloat(float& number, size_t& strLen) const -{ - return ::impl::stringViewToFloat(*this, number, strLen); -} - -FudStatus StringView::toDouble(double& number, size_t& strLen) const -{ - return ::impl::stringViewToFloat(*this, number, strLen); -} - -#endif - } // namespace fud diff --git a/source/fud_string_view.cpp b/source/fud_string_view.cpp new file mode 100644 index 0000000..4795cf2 --- /dev/null +++ b/source/fud_string_view.cpp @@ -0,0 +1,1131 @@ +/* + * 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_view.hpp" + +#include "fud_string.hpp" + +namespace fud { + +StringView::StringView(const String& fudString) noexcept : StringView(fudString.length(), fudString.data()) +{ +} + +bool StringView::nullTerminated() const +{ + return m_data != nullptr && m_data[m_length] == '\0'; +} + +bool StringView::utf8Valid() const +{ + if (m_data == nullptr) { + return false; + } + + for (size_t idx = 0; idx < m_length;) { + if (Ascii::valid(m_data[idx])) { + idx++; + } else if (idx + 1 < m_length && Utf82Byte::valid(m_data[idx], m_data[idx + 1])) { + idx += 2; + } else if (idx + 2 < m_length && Utf83Byte::valid(m_data[idx], m_data[idx + 1], m_data[idx + 2])) { + idx += 3; + } else if ( + idx + 3 < m_length && Utf84Byte::valid(m_data[idx], m_data[idx + 1], m_data[idx + 2], m_data[idx + 3])) { + idx += 4; + } else { + return false; + } + } + + return true; +} + +Result<size_t, FudStatus> StringView::skipWhitespace() +{ + using RetType = Result<size_t, FudStatus>; + if (m_data == nullptr) { + return RetType::error(FudStatus::NullPointer); + } + size_t index = 0; + while (m_length > 0 && char_is_space(static_cast<char>(m_data[0]))) { + m_data++; + m_length--; + index++; + } + + return RetType::okay(index); +} + +Result<size_t, FudStatus> StringView::trimWhitespace() +{ + using RetType = Result<size_t, FudStatus>; + if (m_data == nullptr) { + return RetType::error(FudStatus::NullPointer); + } + + size_t count = 0; + while (m_length > 0 && char_is_space(static_cast<char>(m_data[m_length - 1]))) { + m_length--; + count++; + } + + return RetType::okay(count); +} + +#if 0 + +FudStatus fud_string_truncate(ExtBasicString* source, ssize_t newLength) +{ + if (source == nullptr) { + return FudStatus::NullPointer; + } + StringBorrow wrapper{*source}; + if (!wrapper.valid()) { + return FudStatus::StringInvalid; + } + + if ((newLength > 0 && static_cast<size_t>(newLength) > source->m_length) || + (static_cast<size_t>(-newLength) > source->m_length)) { + return FudStatus::InvalidInput; + } + + if (newLength < 0) { + source->m_length = source->m_length - static_cast<size_t>(-newLength); + } else { + source->m_length = static_cast<size_t>(newLength); + } + + return wrapper.nullTerminate(); +} + +FudStatus fud_string_reverse(ExtBasicString* source) +{ + if (source == nullptr || source->m_data == nullptr) { + return FudStatus::NullPointer; + } + return fud_string_reverse_substring(source, StringView{source->m_length, source->m_data}); +} + +FudStatus fud_string_reverse_substring(ExtBasicString* source, StringView subString) +{ + auto dataOffset = subString.data - source->m_data; + if (dataOffset < 0 || static_cast<size_t>(dataOffset) > source->m_length) { + return FudStatus::InvalidInput; + } + if (static_cast<size_t>(dataOffset) + subString.length > source->m_length) { + return FudStatus::InvalidInput; + } + + if (source == nullptr || source->m_data == nullptr) { + return FudStatus::NullPointer; + } + + StringView view{subString}; + + size_t index = 0; + auto* data = source->m_data + dataOffset; + while (index < subString.length) { + if (ext_lib_char_is_ascii(static_cast<char>(data[index]))) { + index++; + continue; + } + auto utf8 = FudUtf8::fromStringView(view, index); + if (!utf8.valid()) { + return ExtUtf8Invalid; + } + const auto* utf8Data = utf8.data(); + if (utf8Data == nullptr) { + return ExtFailure; + } + auto utf8Size = utf8.size(); + switch (utf8Size) { + case 2: + data[index] = utf8Data[1]; + data[index + 1] = utf8Data[0]; + break; + case 3: + data[index] = utf8Data[2]; + data[index + 2] = utf8Data[0]; + break; + case 4: + data[index] = utf8Data[3]; + data[index + 1] = utf8Data[2]; + data[index + 2] = utf8Data[1]; + data[index + 3] = utf8Data[0]; + break; + default: + return ExtFailure; + } + index += utf8Size; + } + + ext_lib::DataView<uint8_t> dataView{subString.length, data}; + reverse(dataView); + + return FudStatus::Success; +} + +FudStatus fud_string_compare(StringView levo, StringView dextro, int* difference) +{ + if (anyAreNull(difference, levo.data, dextro.data)) { + return FudStatus::NullPointer; + } + + int diff = 0; + size_t index = 0; + while (diff == 0 && index < levo.length && index < dextro.length) { + diff = levo.data[index] - dextro.data[index]; + index++; + } + + if (diff != 0 || levo.length == dextro.length) { + /* nothing to do */ + } else if (levo.length > dextro.length) { + diff = static_cast<int>(levo.data[index]); + } else { + diff = -static_cast<int>(dextro.data[index]); + } + + *difference = diff; + return FudStatus::Success; +} + +FudStatus fud_string_chr(StringView extStringView, char character, size_t* index) +{ + if (anyAreNull(extStringView.data, index)) { + return FudStatus::NullPointer; + } + + bool found = false; + for (size_t localIndex = 0; localIndex < extStringView.length; ++localIndex) { + if (extStringView.data[localIndex] == static_cast<uint8_t>(character)) { + *index = localIndex; + found = true; + break; + } + } + + if (found) { + return FudStatus::Success; + } + + return ExtNotFound; +} + +FudStatus fud_string_unicode_chr(StringView extString, const ExtUtf8* unicode, size_t* index) +{ + if (anyAreNull(extString.data, unicode, index)) { + return FudStatus::NullPointer; + } + + if (!unicode->valid()) { + return ExtUtf8Invalid; + } + + size_t charSize = unicode->size(); + ExtDebugAssert(charSize != 0); + const uint8_t* dataMem = unicode->data(); + ExtDebugAssert(dataMem != nullptr); + + std::array<uint8_t, 4> localData{}; + auto copyStatus = ExtCopyMem(localData.data(), localData.size(), dataMem, charSize); + ExtDebugAssert(copyStatus == FudStatus::Success); + + for (size_t sIdx = 0; sIdx + charSize - 1 < extString.length;) { + + auto localChar = FudUtf8::fromStringView(extString, sIdx); + + if (!localChar.valid()) { + return ExtUtf8Invalid; + } + + if (localChar.m_variant == unicode->m_variant) { + *index = sIdx; + return FudStatus::Success; + } + + sIdx += localChar.size(); + } + + return ExtNotFound; +} + +namespace ext_lib { + +FudStatus fud_string_span_c_api( + const StringView& inputView, + const StringView& characterSetString, + StringView& result, + bool inSet) +{ + size_t firstIndex = inputView.length; + + size_t sIdx = 0; + while (sIdx < firstIndex) { + auto stringChar = FudUtf8::fromStringView(inputView, sIdx); + if (!stringChar.valid()) { + return ExtUtf8Invalid; + } + + size_t cIdx = 0; + bool found = false; + while (firstIndex > 0 && cIdx < firstIndex && cIdx < characterSetString.length) { + auto setChar = FudUtf8::fromStringView(characterSetString, cIdx); + if (!setChar.valid()) { + return ExtUtf8Invalid; + } + + if (stringChar == setChar) { + found = true; + } + + cIdx += setChar.size(); + } + + if (!inSet && found) { + firstIndex = sIdx; + } else if (inSet && !found) { + if (sIdx > 0) { + firstIndex = sIdx; + } + break; + } + + sIdx += stringChar.size(); + } + + if (firstIndex < inputView.length) { + result.length = inputView.length - firstIndex; + result.data = inputView.data + firstIndex; + return FudStatus::Success; + } + + return ExtNotFound; +} + +FudStatus fud_string_span_set(StringView inputView, const ExtUtf8Set* characterSet, StringView* stringView, bool inSet) +{ + if (anyAreNull(inputView.data, characterSet, stringView)) { + return FudStatus::NullPointer; + } + + if (!characterSet->valid()) { + return ExtUtf8Invalid; + } + + size_t firstIndex = inputView.length; + size_t sIdx = 0; + while (sIdx < firstIndex) { + auto localChar = FudUtf8::fromStringView(inputView, sIdx); + if (!localChar.valid()) { + return ExtUtf8Invalid; + } + + bool found = characterSet->contains(localChar); + + if (!inSet && found) { + firstIndex = sIdx; + } else if (inSet && !found) { + if (sIdx > 0) { + firstIndex = sIdx; + } + break; + } + + sIdx += localChar.size(); + } + + if (firstIndex < inputView.length) { + stringView->length = inputView.length - firstIndex; + stringView->data = inputView.data + firstIndex; + return FudStatus::Success; + } + + return ExtNotFound; +} + +} // namespace ext_lib + +FudStatus fud_string_span(StringView extString, StringView characterSetString, StringView* result) +{ + if (result == nullptr) { + return FudStatus::NullPointer; + } + + const StringView inputView{extString}; + const StringView characterSet{characterSetString}; + + return fud_string_span_c_api(inputView, characterSet, *result, true); +} + +FudStatus fud_string_c_span(StringView extString, StringView characterSetString, StringView* result) +{ + if (result == nullptr) { + return FudStatus::NullPointer; + } + + const StringView inputView{extString}; + const StringView characterSet{characterSetString}; + + return fud_string_span_c_api(inputView, characterSet, *result, false); +} + +FudStatus fud_string_span_set(StringView extString, const ExtUtf8Set* characterSet, StringView* stringView) +{ + return ext_lib::fud_string_span_set(extString, characterSet, stringView, true); +} + +FudStatus fud_string_c_span_set(StringView extString, const ExtUtf8Set* characterSet, StringView* stringView) +{ + return ext_lib::fud_string_span_set(extString, characterSet, stringView, false); +} + +FudStatus fud_string_find_substring(StringView haystack, StringView needle, StringView* stringView) +{ + if (anyAreNull(haystack.data, needle.data, stringView)) { + return FudStatus::NullPointer; + } + + if (needle.length > haystack.length) { + return ExtNotFound; + } + + if (needle.length == 1) { + size_t index = 0; + auto chrFindStatus = fud_string_chr(haystack, static_cast<char>(needle.data[0]), &index); + if (chrFindStatus == FudStatus::Success) { + stringView->data = haystack.data + index; + stringView->length = 1; + } + return chrFindStatus; + } + + size_t haystackIdx = 0; + while (haystackIdx < haystack.length - needle.length) { + StringView lhs; + lhs.data = haystack.data + haystackIdx; + lhs.length = haystack.length - haystackIdx; + size_t lhsIndex = 0; + auto chrFindStatus = fud_string_chr(lhs, static_cast<char>(needle.data[0]), &lhsIndex); + if (chrFindStatus != FudStatus::Success) { + return chrFindStatus; + } + haystackIdx += lhsIndex; + // GE or GT? + if (haystackIdx + needle.length >= haystack.length) { + break; + } + lhs.data = haystack.data + haystackIdx; + lhs.length = needle.length; + + int difference = -1; + auto cmpStatus = fud_string_compare(lhs, needle, &difference); + ExtDebugAssert(cmpStatus == FudStatus::Success); + if (difference == 0) { + stringView->data = lhs.data; + stringView->length = lhs.length; + return FudStatus::Success; + } + haystackIdx++; + } + + return ExtNotFound; +} + +namespace ext_lib { + +FudStatus skipWhitespace(StringView& view, size_t& skipIndex) +{ + auto skipResult = view.skipWhitespace(); + if (skipResult.isError()) { + return skipResult.getError(); + } + skipIndex = skipResult.getOkay(); + if (view.length < 1) { + return FudStatus::InvalidInput; + } + return FudStatus::Success; +} + +} // namespace ext_lib + +FudStatus fud_string_view_skip_whitespace(StringView* view) +{ + if (view == nullptr) { + return FudStatus::NullPointer; + } + + StringView sView{*view}; + auto skipResult = sView.skipWhitespace(); + if (skipResult.isError()) { + return skipResult.getError(); + } + view->data = sView.data; + view->length = sView.length; + return FudStatus::Success; +} + +FudStatus fud_string_view_trim_whitespace(StringView* view) +{ + if (view == nullptr) { + return FudStatus::NullPointer; + } + + StringView sView{*view}; + auto skipResult = sView.trimWhitespace(); + if (skipResult.isError()) { + return skipResult.getError(); + } + view->data = sView.data; + view->length = sView.length; + return FudStatus::Success; +} + +namespace impl { +constexpr ext_lib::Array<int8_t, 256> AsciiLookup{ + {-1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + -2, -2, -2, -2, -2, -2, -2, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, -2, -2, -2, -2, -2, -2, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, -2, -2, -2, -2, -2, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, + -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, + -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, + -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, + -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3}}; + +// NOLINTBEGIN(readability-magic-numbers) +static_assert(AsciiLookup[static_cast<size_t>('0')] == 0); +static_assert(AsciiLookup[static_cast<size_t>('9')] == 9); +static_assert(AsciiLookup[static_cast<size_t>('a')] == 10); +static_assert(AsciiLookup[static_cast<size_t>('A')] == 10); +static_assert(AsciiLookup[static_cast<size_t>('f')] == 15); +static_assert(AsciiLookup[static_cast<size_t>('F')] == 15); +static_assert(AsciiLookup[127] == -2); +static_assert(AsciiLookup[128] == -3); +static_assert(AsciiLookup[255] == -3); +// NOLINTEND(readability-magic-numbers) + +FudStatus determineRadix(StringView input, uint8_t& radix, size_t& index) +{ + if (input.length < 1) { + return FudStatus::InvalidInput; + } + + if (input.length == 1 && input.data[0] == '0') { + radix = ExtRadixOctal; + return FudStatus::Success; + } + + if (input.length == 1) { + radix = ExtRadixDecimal; + return FudStatus::Success; + } + + if (input.data[0] == '0' && (input.data[1] == 'x' || input.data[1] == 'X')) { + radix = ExtRadixHexadecimal; + index += 2; + return FudStatus::Success; + } + + if (input.data[0] == '0') { + auto nextChar = input.data[1]; + auto nextVal = AsciiLookup[nextChar]; + if (nextVal >= 0 && nextVal < ExtRadixOctal) { + radix = ExtRadixOctal; + return FudStatus::Success; + } + if (nextVal >= ExtRadixOctal) { + return FudStatus::InvalidInput; + } + } + + radix = ExtRadixDecimal; + return FudStatus::Success; +} + +FudStatus getRadix(StringView& view, uint8_t& radix, size_t& skipIndex) +{ + if (radix == 0) { + size_t radixIndex = 0; + auto status = determineRadix(view, radix, radixIndex); + if (status != FudStatus::Success) { + return status; + } + skipIndex += radixIndex; + view.data += radixIndex; + view.length -= radixIndex; + } else if (radix == ExtRadixHexadecimal && view.length > 2 && (view.data[1] == 'x' || view.data[1] == 'X')) { + skipIndex += 2; + view.data += 2; + view.length -= 2; + } + return FudStatus::Success; +} + +FudStatus checkNegative(StringView& view, bool& isNegative, size_t& skipIndex) +{ + isNegative = view.data[0] == '-'; + if (isNegative && view.length == 1) { + return FudStatus::InvalidInput; + } + if (isNegative) { + skipIndex += 1; + view.data++; + view.length--; + } + return FudStatus::Success; +} + +FudStatus checkPlusSigned(StringView& view, size_t& skipIndex) +{ + auto isPlusSigned = view.data[0] == '+'; + if (isPlusSigned && view.length == 1) { + return FudStatus::InvalidInput; + } + if (isPlusSigned) { + skipIndex += 1; + view.data++; + view.length--; + } + return FudStatus::Success; +} + +template <typename T> +FudStatus stringViewToUnsignedInteger(StringView input, T& number, uint8_t specifiedRadix, size_t& index) +{ + if (input.data == nullptr) { + return FudStatus::NullPointer; + } + + if (specifiedRadix == 1 || specifiedRadix > ExtMaxRadix || input.length < 1) { + return FudStatus::InvalidInput; + } + + uint8_t radix = specifiedRadix; + + StringView view{input}; + size_t skipIndex = 0; + auto status = ext_lib::skipWhitespace(view, skipIndex); + if (status != FudStatus::Success) { + return status; + } + + status = checkPlusSigned(view, skipIndex); + if (status != FudStatus::Success) { + return FudStatus::InvalidInput; + } + + status = getRadix(view, radix, skipIndex); + + T num = 0; + size_t digitIndex = 0; + + while (digitIndex < view.length) { + auto digitResult = impl::AsciiLookup[view.data[digitIndex]]; + if (digitResult >= radix || digitResult < 0) { + break; + } + + auto digit = static_cast<uint8_t>(digitResult); + if (std::numeric_limits<T>::max() / radix < num) { + return FudStatus::InvalidInput; + } + num *= radix; + if (std::numeric_limits<T>::max() - digit < num) { + return FudStatus::InvalidInput; + } + num += digit; + digitIndex++; + } + if (digitIndex < 1) { + return FudStatus::InvalidInput; + } + + index = skipIndex + digitIndex; + number = num; + + return FudStatus::Success; +} + +template <typename T> +FudStatus stringViewToUnsignedInteger(StringView input, T* number, uint8_t specifiedRadix, size_t* index) +{ + if (anyAreNull(input.data, number)) { + return FudStatus::NullPointer; + } + + size_t localIndex = 0; + + auto status = stringViewToUnsignedInteger(input, *number, specifiedRadix, localIndex); + if (status == FudStatus::Success && index != nullptr) { + *index = localIndex; + } + return status; +} + +template <typename T> +FudStatus viewToSignedIntPositive(StringView view, uint8_t radix, size_t& digitIndex, T& num) +{ + digitIndex = 0; + while (digitIndex < view.length) { + int8_t digitResult = impl::AsciiLookup[view.data[digitIndex]]; + if (digitResult >= radix) { + return FudStatus::InvalidInput; + } + if (digitResult < 0) { + break; + } + auto digit = static_cast<uint8_t>(digitResult); + if (std::numeric_limits<T>::max() / radix < num) { + return FudStatus::InvalidInput; + } + num = static_cast<T>(num * radix); + if (std::numeric_limits<T>::max() - digit < num) { + return FudStatus::InvalidInput; + } + num = static_cast<T>(num + digit); + digitIndex++; + } + + return FudStatus::Success; +} + +template <typename T> +FudStatus viewToSignedIntNegative(StringView view, uint8_t radix, size_t& digitIndex, T& num) +{ + digitIndex = 0; + while (digitIndex < view.length) { + int8_t digitResult = impl::AsciiLookup[view.data[digitIndex]]; + if (digitResult >= radix) { + return FudStatus::InvalidInput; + } + if (digitResult < 0) { + break; + } + auto digit = static_cast<uint8_t>(digitResult); + if ((std::numeric_limits<T>::min() / radix > num)) { + return FudStatus::InvalidInput; + } + num = static_cast<T>(num * radix); + if (std::numeric_limits<T>::min() + digit > num) { + return FudStatus::InvalidInput; + } + num = static_cast<T>(num - digit); + digitIndex++; + } + + return FudStatus::Success; +} + +template <typename T> +FudStatus stringViewToSignedInteger(StringView input, T& number, uint8_t specifiedRadix, size_t& index) +{ + if (input.data == nullptr) { + return FudStatus::NullPointer; + } + + auto radix = specifiedRadix; + + StringView view{input}; + size_t skipIndex = 0; + auto status = ext_lib::skipWhitespace(view, skipIndex); + if (status != FudStatus::Success) { + return status; + } + + bool isNegative = false; + status = checkNegative(view, isNegative, skipIndex); + if (status != FudStatus::Success) { + return FudStatus::InvalidInput; + } + + if (!isNegative) { + status = checkPlusSigned(view, skipIndex); + if (status != FudStatus::Success) { + return FudStatus::InvalidInput; + } + } + + status = getRadix(view, radix, skipIndex); + + T num = 0; + size_t digitIndex = 0; + + if (isNegative) { + status = viewToSignedIntNegative(view, radix, digitIndex, num); + } else { + status = viewToSignedIntPositive(view, radix, digitIndex, num); + } + if (status != FudStatus::Success) { + return status; + } + + if (digitIndex < 1) { + return FudStatus::InvalidInput; + } + + index = skipIndex + digitIndex; + number = num; + return FudStatus::Success; +} + +template <typename T> +FudStatus stringViewToSignedInteger(StringView input, T* number, uint8_t specifiedRadix, size_t* index) +{ + if (anyAreNull(input.data, number)) { + return FudStatus::NullPointer; + } + + if (specifiedRadix == 1 || specifiedRadix > ExtMaxRadix || input.length < 1) { + return FudStatus::InvalidInput; + } + + size_t localIndex = 0; + auto status = stringViewToSignedInteger(input, *number, specifiedRadix, localIndex); + if (status == FudStatus::Success && index != nullptr) { + *index = localIndex; + } + return status; +} + +} // namespace impl + +FudStatus fud_string_to_uint8(StringView input, uint8_t* number, uint8_t specifiedRadix, size_t* index) +{ + return impl::stringViewToUnsignedInteger(input, number, specifiedRadix, index); +} + +FudStatus fud_string_to_uint16(StringView input, uint16_t* number, uint8_t specifiedRadix, size_t* index) +{ + return impl::stringViewToUnsignedInteger(input, number, specifiedRadix, index); +} + +FudStatus fud_string_to_uint32(StringView input, uint32_t* number, uint8_t specifiedRadix, size_t* index) +{ + return impl::stringViewToUnsignedInteger(input, number, specifiedRadix, index); +} + +FudStatus fud_string_to_uint64(StringView input, uint64_t* number, uint8_t specifiedRadix, size_t* index) +{ + return impl::stringViewToUnsignedInteger(input, number, specifiedRadix, index); +} + +FudStatus fud_string_to_int8(StringView input, int8_t* number, uint8_t specifiedRadix, size_t* index) +{ + return impl::stringViewToSignedInteger(input, number, specifiedRadix, index); +} + +FudStatus fud_string_to_int16(StringView input, int16_t* number, uint8_t specifiedRadix, size_t* index) +{ + return impl::stringViewToSignedInteger(input, number, specifiedRadix, index); +} + +FudStatus fud_string_to_int32(StringView input, int32_t* number, uint8_t specifiedRadix, size_t* index) +{ + return impl::stringViewToSignedInteger(input, number, specifiedRadix, index); +} + +FudStatus fud_string_to_int64(StringView input, int64_t* number, uint8_t specifiedRadix, size_t* index) +{ + return impl::stringViewToSignedInteger(input, number, specifiedRadix, index); +} + +namespace impl { + +template <typename T> +bool isNanOrInf(T& num, StringView& view, T& sign, size_t& digitIndex) +{ + if (view.length >= 3) { + std::array<uint8_t, 3> letters{{view.data[0], view.data[1], view.data[2]}}; + ext_lib::mapMut(letters, ext_lib_char_to_lower); + if (letters[0] == 'i' && letters[1] == 'n' && letters[2] == 'f') { + num = sign * std::numeric_limits<T>::infinity(); + digitIndex = 3; + return true; + } + if (letters[0] == 'n' && letters[1] == 'a' && letters[2] == 'n') { + num = std::numeric_limits<T>::quiet_NaN(); + ; + digitIndex = 3; + return true; + } + } + return false; +} + +template <typename T> +FudStatus getWhole( + const StringView view, + size_t& digitIndex, + T& num, + T sign, + uint8_t radix, + bool& foundDecimal, + bool& foundExponent) +{ + while (digitIndex < view.length) { + auto nextChar = view.data[digitIndex]; + if (nextChar == '.') { + foundDecimal = true; + digitIndex++; + break; + } + + if (radix == ExtRadixDecimal && (nextChar == 'e' || nextChar == 'E')) { + foundExponent = true; + digitIndex++; + break; + } + + auto digitResult = impl::AsciiLookup[nextChar]; + if (digitResult >= radix) { + return FudStatus::InvalidInput; + } + if (digitResult < 0) { + break; + } + auto digit = static_cast<T>(digitResult) * sign; + num *= static_cast<T>(radix); + + num += digit; + digitIndex++; + } + return FudStatus::Success; +} + +template <typename T> +FudStatus getExponent(const StringView& view, size_t& digitIndex, T& num, uint8_t radix) +{ + int32_t exponent{}; + StringView tempView{view.length - digitIndex, view.data + digitIndex}; + size_t exponentLength{}; + auto status = tempView.toInt32(exponent, ExtRadixDecimal, exponentLength); + if (status != FudStatus::Success) { + return status; + } + digitIndex += exponentLength; + num = num * std::pow(static_cast<T>(radix), static_cast<T>(exponent)); + return FudStatus::Success; +} + +template <typename T> +FudStatus getFraction(const StringView view, size_t& digitIndex, T& num, T sign, uint8_t radix, bool& foundExponent) +{ + auto radixDiv = 1.0F / static_cast<T>(radix); + while (digitIndex < view.length) { + auto nextChar = view.data[digitIndex]; + if (radix == ExtRadixDecimal && (nextChar == 'e' || nextChar == 'E')) { + foundExponent = true; + digitIndex++; + break; + } + + auto digitResult = impl::AsciiLookup[nextChar]; + if (digitResult >= radix) { + return FudStatus::InvalidInput; + } + if (digitResult < 0) { + break; + } + auto digit = static_cast<T>(digitResult) * sign; + num += digit * radixDiv; + radixDiv /= static_cast<T>(radix); + digitIndex++; + } + return FudStatus::Success; +} + +template <typename T> +FudStatus stringViewToFloat(StringView input, T& number, size_t& index) +{ + if (input.data == nullptr) { + return FudStatus::NullPointer; + } + + if (input.length < 1) { + return FudStatus::InvalidInput; + } + + uint8_t radix = 0; + + StringView view{input}; + size_t skipIndex = 0; + + auto status = skipWhitespace(view, skipIndex); + if (status != FudStatus::Success) { + return status; + } + + T sign = 1.0; + bool isNegative = false; + status = impl::checkNegative(view, isNegative, skipIndex); + if (status != FudStatus::Success) { + return FudStatus::InvalidInput; + } + + if (!isNegative) { + status = checkPlusSigned(view, skipIndex); + } else { + sign = -1.0; + } + + if (status != FudStatus::Success) { + return FudStatus::InvalidInput; + } + + T num = 0; + size_t digitIndex = 0; + + auto retSuccess = [&]() { + index = skipIndex + digitIndex; + number = num; + return FudStatus::Success; + }; + + if (impl::isNanOrInf(num, view, sign, digitIndex)) { + return retSuccess(); + } + + status = impl::getRadix(view, radix, skipIndex); + if (status != FudStatus::Success) { + return status; + } + + bool foundDecimal = false; + bool foundExponent = false; + status = getWhole(view, digitIndex, num, sign, radix, foundDecimal, foundExponent); + + if (status == FudStatus::Success && foundExponent) { + status = getExponent(view, digitIndex, num, radix); + } + + if (status != FudStatus::Success) { + return status; + } + + if (!foundDecimal) { + if (digitIndex < 1) { + return FudStatus::InvalidInput; + } + + return retSuccess(); + } + + status = getFraction(view, digitIndex, num, sign, radix, foundExponent); + + if (foundExponent) { + status = getExponent(view, digitIndex, num, radix); + if (status != FudStatus::Success) { + return status; + } + } + + if (digitIndex < 1) { + return FudStatus::InvalidInput; + } + + if (std::isinf(num) || std::isnan(num)) // isnan is dubious here - likely unreachable + { + return ExtRangeError; + } + + return retSuccess(); +} + +template <typename T> +FudStatus stringViewToFloat(StringView input, T* number, size_t* index) +{ + if (anyAreNull(input.data, number)) { + return FudStatus::NullPointer; + } + + size_t localIndex{0}; + auto status = stringViewToFloat(input, *number, localIndex); + + if (status == FudStatus::Success && index != nullptr) { + *index = localIndex; + } + return status; +} + +} // namespace impl + +FudStatus fud_string_to_float(StringView input, float* number, size_t* index) +{ + return impl::stringViewToFloat(input, number, index); +} + +FudStatus fud_string_to_double(StringView input, double* number, size_t* index) +{ + return impl::stringViewToFloat(input, number, index); +} + +namespace fud { + +FudStatus StringView::toUint8(uint8_t& number, uint8_t specifiedRadix, size_t& strLen) const +{ + return ::impl::stringViewToUnsignedInteger(*this, number, specifiedRadix, strLen); +} + +FudStatus StringView::toUint16(uint16_t& number, uint8_t specifiedRadix, size_t& strLen) const +{ + return ::impl::stringViewToUnsignedInteger(*this, number, specifiedRadix, strLen); +} + +FudStatus StringView::toUint32(uint32_t& number, uint8_t specifiedRadix, size_t& strLen) const +{ + return ::impl::stringViewToUnsignedInteger(*this, number, specifiedRadix, strLen); +} + +FudStatus StringView::toUint64(uint64_t& number, uint8_t specifiedRadix, size_t& strLen) const +{ + return ::impl::stringViewToUnsignedInteger(*this, number, specifiedRadix, strLen); +} + +FudStatus StringView::toInt8(int8_t& number, uint8_t specifiedRadix, size_t& strLen) const +{ + return ::impl::stringViewToSignedInteger(*this, number, specifiedRadix, strLen); +} + +FudStatus StringView::toInt16(int16_t& number, uint8_t specifiedRadix, size_t& strLen) const +{ + return ::impl::stringViewToSignedInteger(*this, number, specifiedRadix, strLen); +} + +FudStatus StringView::toInt32(int32_t& number, uint8_t specifiedRadix, size_t& strLen) const +{ + return ::impl::stringViewToSignedInteger(*this, number, specifiedRadix, strLen); +} + +FudStatus StringView::toInt64(int64_t& number, uint8_t specifiedRadix, size_t& strLen) const +{ + return ::impl::stringViewToSignedInteger(*this, number, specifiedRadix, strLen); +} + +FudStatus StringView::toFloat(float& number, size_t& strLen) const +{ + return ::impl::stringViewToFloat(*this, number, strLen); +} + +FudStatus StringView::toDouble(double& number, size_t& strLen) const +{ + return ::impl::stringViewToFloat(*this, number, strLen); +} + +#endif + +} // namespace fud |