/* * 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" 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; } String::String(const utf8* cString) : String(reinterpret_cast(cString)) { } String::String(const char* cString) { auto lenResult = cStringLength(cString); if (lenResult < 0 || lenResult >= SSIZE_MAX) { m_length = 1; m_capacity = 0; } else if (static_cast(lenResult) < SSO_BUF_SIZE) { m_length = static_cast(lenResult); fudAssert(copyMem(m_buffer.data(), m_buffer.size(), cString, m_length) == FudStatus::Success); fudAssert(nullTerminate() == FudStatus::Success); } else { m_length = static_cast(lenResult); m_capacity = m_length + 1; m_data = static_cast(fudAlloc(m_capacity)); fudAssert(m_data != nullptr); fudAssert(copyMem(m_data, m_capacity, cString, m_length) == FudStatus::Success); fudAssert(nullTerminate() == FudStatus::Success); } } String::String(const String& rhs) : m_length{rhs.m_length}, m_capacity{rhs.m_capacity} { if (rhs.valid()) { if (isLarge()) { m_data = static_cast(fudAlloc(m_capacity)); fudAssert(m_data != nullptr); } fudAssert(copyMem(data(), m_capacity, rhs.data(), m_length) == FudStatus::Success); fudAssert(nullTerminate() == FudStatus::Success); } } String::String(String&& rhs) : m_length{rhs.m_length}, m_capacity{rhs.m_capacity} { if (rhs.isLarge()) { m_data = rhs.m_data; rhs.m_data = nullptr; } else { m_buffer = std::move(rhs.m_buffer); fudAssert(nullTerminate() == FudStatus::Success); } } String::~String() { if (isLarge() && m_data != nullptr) { fudFree(m_data); m_data = nullptr; } } String& String::operator=(const String& rhs) { m_length = rhs.m_length; m_capacity = rhs.m_capacity; if (rhs.valid()) { if (isLarge()) { m_data = static_cast(fudAlloc(m_capacity)); fudAssert(m_data != nullptr); } fudAssert(copyMem(data(), m_capacity, rhs.data(), m_length) == FudStatus::Success); fudAssert(nullTerminate() == FudStatus::Success); } return *this; } String& String::operator=(String&& rhs) { m_length = rhs.m_length; m_capacity = rhs.m_capacity; if (rhs.isLarge()) { m_data = rhs.m_data; rhs.m_data = nullptr; } else { m_buffer = std::move(rhs.m_buffer); fudAssert(nullTerminate() == FudStatus::Success); } return *this; } bool String::nullTerminated() const { return data() != nullptr && m_length < m_capacity && data()[m_length] == '\0'; } bool String::valid() const { return nullTerminated() && m_length < m_capacity; } 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; } [[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 (m_length < 1) { return std::nullopt; } m_length--; auto letter = m_data[m_length]; m_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) { return FudStatus::OperationInvalid; } m_data[m_length] = letter; m_length++; m_data[m_length] = '\0'; return FudStatus::Success; } FudStatus String::pushBack(const FudUtf8& letter) { if (!valid()) { return FudStatus::StringInvalid; } if (!letter.valid()) { return FudStatus::InvalidInput; } const auto* letterData = letter.data(); if (letterData == nullptr) { return FudStatus::InvalidInput; } auto letterSize = letter.size(); if (letterSize > remainingLength()) { return FudStatus::OperationInvalid; } auto copyStatus = copyMem(m_data + m_length, remainingLength(), letterData, letterSize); if (copyStatus != FudStatus::Success) { return copyStatus; } m_length += letterSize; m_data[m_length] = '\0'; return FudStatus::Success; } FudStatus String::append(StringView source) { if (!valid()) { return FudStatus::StringInvalid; } if (source.data() == m_data) { return FudStatus::Aliased; } const auto newLength = m_length + source.length(); const auto newSize = newLength + 1; if (newSize >= m_capacity) { return FudStatus::OperationInvalid; } auto* destPtr = m_data + m_length; auto status = copyMem(destPtr, m_capacity, source.data(), source.length()); if (status == FudStatus::Success) { // likely m_length += source.length(); status = nullTerminate(); } return status; } String String::catenate(const char* rhs) const { String rhsString{rhs}; return catenate(rhsString); } String String::catenate(const String& rhs) const { String output{}; output.m_length = 1; output.m_capacity = 0; if (!valid() || !rhs.valid()) { return output; } output.m_length = m_length + rhs.length(); output.m_capacity = output.m_length + 1; if (output.isLarge()) { output.m_data = static_cast(fudAlloc(output.m_capacity)); } 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); static_cast(status); fudAssert(output.nullTerminate() == FudStatus::Success); return 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(); } 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 StringView::skipWhitespace() { using RetType = Result; if (m_data == nullptr) { return RetType::error(FudStatus::NullPointer); } size_t index = 0; while (m_length > 0 && char_is_space(static_cast(m_data[0]))) { m_data++; m_length--; index++; } return RetType::okay(index); } Result StringView::trimWhitespace() { using RetType = Result; if (m_data == nullptr) { return RetType::error(FudStatus::NullPointer); } size_t count = 0; while (m_length > 0 && char_is_space(static_cast(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(newLength) > source->m_length) || (static_cast(-newLength) > source->m_length)) { return FudStatus::InvalidInput; } if (newLength < 0) { source->m_length = source->m_length - static_cast(-newLength); } else { source->m_length = static_cast(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(dataOffset) > source->m_length) { return FudStatus::InvalidInput; } if (static_cast(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(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 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(levo.data[index]); } else { diff = -static_cast(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(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 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(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(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 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('0')] == 0); static_assert(AsciiLookup[static_cast('9')] == 9); static_assert(AsciiLookup[static_cast('a')] == 10); static_assert(AsciiLookup[static_cast('A')] == 10); static_assert(AsciiLookup[static_cast('f')] == 15); static_assert(AsciiLookup[static_cast('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 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(digitResult); if (std::numeric_limits::max() / radix < num) { return FudStatus::InvalidInput; } num *= radix; if (std::numeric_limits::max() - digit < num) { return FudStatus::InvalidInput; } num += digit; digitIndex++; } if (digitIndex < 1) { return FudStatus::InvalidInput; } index = skipIndex + digitIndex; number = num; return FudStatus::Success; } template 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 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(digitResult); if (std::numeric_limits::max() / radix < num) { return FudStatus::InvalidInput; } num = static_cast(num * radix); if (std::numeric_limits::max() - digit < num) { return FudStatus::InvalidInput; } num = static_cast(num + digit); digitIndex++; } return FudStatus::Success; } template 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(digitResult); if ((std::numeric_limits::min() / radix > num)) { return FudStatus::InvalidInput; } num = static_cast(num * radix); if (std::numeric_limits::min() + digit > num) { return FudStatus::InvalidInput; } num = static_cast(num - digit); digitIndex++; } return FudStatus::Success; } template 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 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 bool isNanOrInf(T& num, StringView& view, T& sign, size_t& digitIndex) { if (view.length >= 3) { std::array 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::infinity(); digitIndex = 3; return true; } if (letters[0] == 'n' && letters[1] == 'a' && letters[2] == 'n') { num = std::numeric_limits::quiet_NaN(); ; digitIndex = 3; return true; } } return false; } template 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(digitResult) * sign; num *= static_cast(radix); num += digit; digitIndex++; } return FudStatus::Success; } template 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(radix), static_cast(exponent)); return FudStatus::Success; } template FudStatus getFraction(const StringView view, size_t& digitIndex, T& num, T sign, uint8_t radix, bool& foundExponent) { auto radixDiv = 1.0F / static_cast(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(digitResult) * sign; num += digit * radixDiv; radixDiv /= static_cast(radix); digitIndex++; } return FudStatus::Success; } template 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 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