/* * 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. */ #ifndef FUD_STRING_CONVERT_HPP #define FUD_STRING_CONVERT_HPP #include "fud_algorithm.hpp" #include "fud_option.hpp" #include "fud_result.hpp" #include "fud_status.hpp" #include "fud_string.hpp" #include "fud_string_view.hpp" #include #include namespace fud { enum class Radix : uint8_t { Binary = 2, Octal = 8, Decimal = 10, Hexadecimal = 16, }; constexpr uint8_t RadixMax = 36; template struct ConvertValue { size_t nextIndex; T value; }; template using StringConvertResult = Result, FudStatus>; template StringConvertResult fromString(StringView inputView, Option specifiedRadixOption = NullOpt); template StringConvertResult fromString(StringView inputView, Radix specifiedRadixOption); template StringConvertResult fromString(const String& inputView, Option specifiedRadixOption = NullOpt); namespace impl { constexpr 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('1')] == 1); static_assert(AsciiLookup[static_cast('2')] == 2); static_assert(AsciiLookup[static_cast('3')] == 3); static_assert(AsciiLookup[static_cast('4')] == 4); static_assert(AsciiLookup[static_cast('5')] == 5); static_assert(AsciiLookup[static_cast('6')] == 6); static_assert(AsciiLookup[static_cast('7')] == 7); static_assert(AsciiLookup[static_cast('8')] == 8); static_assert(AsciiLookup[static_cast('9')] == 9); static_assert(AsciiLookup[static_cast('a')] == 10); static_assert(AsciiLookup[static_cast('b')] == 11); static_assert(AsciiLookup[static_cast('c')] == 12); static_assert(AsciiLookup[static_cast('d')] == 13); static_assert(AsciiLookup[static_cast('e')] == 14); static_assert(AsciiLookup[static_cast('f')] == 15); static_assert(AsciiLookup[static_cast('A')] == 10); static_assert(AsciiLookup[static_cast('B')] == 11); static_assert(AsciiLookup[static_cast('C')] == 12); static_assert(AsciiLookup[static_cast('D')] == 13); static_assert(AsciiLookup[static_cast('E')] == 14); static_assert(AsciiLookup[static_cast('F')] == 15); static_assert(AsciiLookup[127] == -2); static_assert(AsciiLookup[128] == -3); // NOLINTEND(readability-magic-numbers) FudStatus checkPlusSigned(StringView& view, size_t& skipIndex); Result determineRadix(StringView input, size_t& index); Result checkNegative(StringView& view, size_t& skipIndex); Result getRadix(StringView& view, size_t& skipIndex, Option specifiedRadixOption); template StringConvertResult unsignedFromString(StringView nextView, size_t skipIndex, Option specifiedRadixOption) { static_assert(std::is_unsigned_v && std::is_integral_v); auto status = checkPlusSigned(nextView, skipIndex); if (status != FudStatus::Success) { return FudStatus::ArgumentInvalid; } auto radixResult = impl::getRadix(nextView, skipIndex, specifiedRadixOption); if (radixResult.isError()) { return radixResult.takeError(); } auto radix = radixResult.takeOkay(); T num = 0; size_t digitIndex = 0; while (digitIndex < nextView.length()) { auto digitResult = impl::AsciiLookup[nextView.data()[digitIndex]]; if (digitResult >= radix || digitResult < 0) { break; } auto digit = static_cast(digitResult); if (std::numeric_limits::max() / radix < num) { return FudStatus::RangeError; } num *= radix; if (std::numeric_limits::max() - digit < num) { return FudStatus::RangeError; } num += digit; digitIndex++; } if (digitIndex < 1) { return FudStatus::ArgumentInvalid; } return ConvertValue{skipIndex + digitIndex, num}; } template FudStatus signedPositiveFromString(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 || digitResult < 0) { break; } auto digit = static_cast(digitResult); if (std::numeric_limits::max() / radix < num) { return FudStatus::RangeError; } num = static_cast(num * radix); if (std::numeric_limits::max() - digit < num) { return FudStatus::RangeError; } num = static_cast(num + digit); digitIndex++; } return FudStatus::Success; } template FudStatus signedNegativeFromString(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 || digitResult < 0) { break; } auto digit = static_cast(digitResult); if ((std::numeric_limits::min() / radix > num)) { return FudStatus::RangeError; } num = static_cast(num * radix); if (std::numeric_limits::min() + digit > num) { return FudStatus::RangeError; } num = static_cast(num - digit); digitIndex++; } return FudStatus::Success; } template StringConvertResult signedFromString(StringView nextView, size_t skipIndex, Option specifiedRadixOption) { static_assert(std::is_signed_v && std::is_integral_v); auto status = impl::checkPlusSigned(nextView, skipIndex); if (status != FudStatus::Success) { return FudStatus::ArgumentInvalid; } auto radixResult = impl::getRadix(nextView, skipIndex, specifiedRadixOption); if (radixResult.isError()) { return radixResult.takeError(); } auto radix = radixResult.takeOkay(); T num = 0; size_t digitIndex = 0; auto isNegativeResult = checkNegative(nextView, skipIndex); if (isNegativeResult.isError()) { return isNegativeResult.takeError(); } const auto isNegative = isNegativeResult.takeOkay(); if (isNegative) { status = signedNegativeFromString(nextView, radix, digitIndex, num); } else { status = signedPositiveFromString(nextView, radix, digitIndex, num); } if (status != FudStatus::Success) { return status; } if (digitIndex < 1) { return FudStatus::ArgumentInvalid; } return ConvertValue{skipIndex + digitIndex, num}; } template bool isNanOrInf(T& num, StringView& view, T& sign, size_t& digitIndex) { if (view.length() >= 3) { Array letters{{view.data()[0], view.data()[1], view.data()[2]}}; forEach(letters.span(), charToLower); 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 == static_cast(Radix::Decimal) && (nextChar == 'e' || nextChar == 'E')) { foundExponent = true; digitIndex++; break; } auto digitResult = impl::AsciiLookup[nextChar]; if (digitResult >= radix) { return FudStatus::ArgumentInvalid; } if (digitResult < 0) { break; } auto digit = static_cast(digitResult) * sign; num *= static_cast(radix); num += digit; digitIndex++; } return FudStatus::Success; } template FudStatus getExponent(StringView view, size_t& digitIndex, T& num, uint8_t radix) { StringView tempView{view.length() - digitIndex, view.data() + digitIndex}; size_t exponentLength{}; auto convertResult = signedFromString(tempView, exponentLength, static_cast(Radix::Decimal)); if (convertResult.isError()) { return convertResult.takeError(); } auto convertValue = convertResult.takeOkay(); digitIndex += convertValue.nextIndex + exponentLength; num = num * std::pow(static_cast(radix), static_cast(convertValue.value)); return FudStatus::Success; } template FudStatus getFraction(const StringView view, size_t& digitIndex, T& num, T sign, uint8_t radix, bool& foundExponent) { auto radixDiv = static_cast(1) / static_cast(radix); while (digitIndex < view.length()) { auto nextChar = view.data()[digitIndex]; if (radix == static_cast(Radix::Decimal) && (nextChar == 'e' || nextChar == 'E')) { foundExponent = true; digitIndex++; break; } auto digitResult = impl::AsciiLookup[nextChar]; if (digitResult >= radix) { return FudStatus::ArgumentInvalid; } if (digitResult < 0) { break; } auto digit = static_cast(digitResult) * sign; num += digit * radixDiv; radixDiv /= static_cast(radix); digitIndex++; } return FudStatus::Success; } template StringConvertResult floatFromString(StringView nextView, size_t skipIndex, Option specifiedRadixOption) { static_assert(std::is_floating_point_v); if (nextView.length() < 1) { return FudStatus::ArgumentInvalid; } auto isNegativeResult = checkNegative(nextView, skipIndex); if (isNegativeResult.isError()) { return isNegativeResult.takeError(); } const auto isNegative = isNegativeResult.takeOkay(); if (!isNegative) { auto status = checkPlusSigned(nextView, skipIndex); if (status != FudStatus::Success) { return FudStatus::ArgumentInvalid; } } T sign = isNegative ? -1.0 : 1.0; T num = 0; size_t digitIndex = 0; auto retSuccess = [&]() { return ConvertValue{skipIndex + digitIndex, num}; }; if (impl::isNanOrInf(num, nextView, sign, digitIndex)) { return retSuccess(); } auto radixResult = impl::getRadix(nextView, skipIndex, specifiedRadixOption); if (radixResult.isError()) { return radixResult.takeError(); } auto radix = radixResult.takeOkay(); bool foundDecimal = false; bool foundExponent = false; auto status = getWhole(nextView, digitIndex, num, sign, radix, foundDecimal, foundExponent); if (status == FudStatus::Success && foundExponent) { status = getExponent(nextView, digitIndex, num, radix); } if (status != FudStatus::Success) { return status; } if (!foundDecimal) { if (digitIndex < 1) { return FudStatus::ArgumentInvalid; } return retSuccess(); } status = getFraction(nextView, digitIndex, num, sign, radix, foundExponent); if (foundExponent) { status = getExponent(nextView, digitIndex, num, radix); if (status != FudStatus::Success) { return status; } } if (digitIndex < 1) { return FudStatus::ArgumentInvalid; } if (std::isinf(num) || std::isnan(num)) // isnan is dubious here - likely unreachable { return FudStatus::RangeError; } return retSuccess(); } } // namespace impl template StringConvertResult fromString(StringView inputView, Radix specifiedRadixOption) { return fromString(inputView, static_cast(specifiedRadixOption)); } template StringConvertResult fromString(StringView inputView, Option specifiedRadixOption) { if (inputView.data() == nullptr) { return FudStatus::NullPointer; } StringView nextView{inputView}; auto skipResult = nextView.skipWhitespace(); if (skipResult.isError()) { return skipResult.takeError(); } size_t skipIndex = skipResult.takeOkay(); if constexpr (std::is_unsigned_v && std::is_integral_v) { return impl::unsignedFromString(nextView, skipIndex, specifiedRadixOption); } else if constexpr (std::is_signed_v && std::is_integral_v) { return impl::signedFromString(nextView, skipIndex, specifiedRadixOption); } else if constexpr (std::is_floating_point_v) { return impl::floatFromString(nextView, skipIndex, specifiedRadixOption); } else { return FudStatus::NotImplemented; } } } // namespace fud #endif