diff options
Diffstat (limited to 'include/fud_string_convert.hpp')
-rw-r--r-- | include/fud_string_convert.hpp | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/include/fud_string_convert.hpp b/include/fud_string_convert.hpp new file mode 100644 index 0000000..597c6a9 --- /dev/null +++ b/include/fud_string_convert.hpp @@ -0,0 +1,456 @@ +/* + * 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 <cmath> +#include <cstdint> + +namespace fud { + +enum class Radix : uint8_t +{ + Binary = 2, + Octal = 8, + Decimal = 10, + Hexadecimal = 16, +}; + +constexpr uint8_t RadixMax = 36; + +template <typename T> +struct ConvertValue { + size_t nextIndex; + T value; +}; + +template <typename T> +using StringConvertResult = Result<ConvertValue<T>, FudStatus>; + +template <typename T> +StringConvertResult<T> fromString(StringView inputView, Option<uint8_t> specifiedRadixOption = NullOpt); + +template <typename T> +StringConvertResult<T> fromString(StringView inputView, Radix specifiedRadixOption); + +template <typename T> +StringConvertResult<T> fromString(const String& inputView, Option<uint8_t> specifiedRadixOption = NullOpt); + +namespace impl { +constexpr 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>('1')] == 1); +static_assert(AsciiLookup[static_cast<size_t>('2')] == 2); +static_assert(AsciiLookup[static_cast<size_t>('3')] == 3); +static_assert(AsciiLookup[static_cast<size_t>('4')] == 4); +static_assert(AsciiLookup[static_cast<size_t>('5')] == 5); +static_assert(AsciiLookup[static_cast<size_t>('6')] == 6); +static_assert(AsciiLookup[static_cast<size_t>('7')] == 7); +static_assert(AsciiLookup[static_cast<size_t>('8')] == 8); +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>('b')] == 11); +static_assert(AsciiLookup[static_cast<size_t>('c')] == 12); +static_assert(AsciiLookup[static_cast<size_t>('d')] == 13); +static_assert(AsciiLookup[static_cast<size_t>('e')] == 14); +static_assert(AsciiLookup[static_cast<size_t>('f')] == 15); +static_assert(AsciiLookup[static_cast<size_t>('A')] == 10); +static_assert(AsciiLookup[static_cast<size_t>('B')] == 11); +static_assert(AsciiLookup[static_cast<size_t>('C')] == 12); +static_assert(AsciiLookup[static_cast<size_t>('D')] == 13); +static_assert(AsciiLookup[static_cast<size_t>('E')] == 14); +static_assert(AsciiLookup[static_cast<size_t>('F')] == 15); +static_assert(AsciiLookup[127] == -2); +static_assert(AsciiLookup[128] == -3); +// NOLINTEND(readability-magic-numbers) + +FudStatus checkPlusSigned(StringView& view, size_t& skipIndex); + +Result<Radix, FudStatus> determineRadix(StringView input, size_t& index); + +Result<bool, FudStatus> checkNegative(StringView& view, size_t& skipIndex); + +Result<uint8_t, FudStatus> getRadix(StringView& view, size_t& skipIndex, Option<uint8_t> specifiedRadixOption); + +template <typename T> +StringConvertResult<T> unsignedFromString(StringView nextView, size_t skipIndex, Option<uint8_t> specifiedRadixOption) +{ + static_assert(std::is_unsigned_v<T> && std::is_integral_v<T>); + 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<uint8_t>(digitResult); + if (std::numeric_limits<T>::max() / radix < num) { + return FudStatus::RangeError; + } + num *= radix; + if (std::numeric_limits<T>::max() - digit < num) { + return FudStatus::RangeError; + } + num += digit; + digitIndex++; + } + if (digitIndex < 1) { + return FudStatus::ArgumentInvalid; + } + + return ConvertValue{skipIndex + digitIndex, num}; +} + +template <typename T> +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<uint8_t>(digitResult); + if (std::numeric_limits<T>::max() / radix < num) { + return FudStatus::RangeError; + } + num = static_cast<T>(num * radix); + if (std::numeric_limits<T>::max() - digit < num) { + return FudStatus::RangeError; + } + num = static_cast<T>(num + digit); + digitIndex++; + } + + return FudStatus::Success; +} + +template <typename T> +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<uint8_t>(digitResult); + if ((std::numeric_limits<T>::min() / radix > num)) { + return FudStatus::RangeError; + } + num = static_cast<T>(num * radix); + if (std::numeric_limits<T>::min() + digit > num) { + return FudStatus::RangeError; + } + num = static_cast<T>(num - digit); + digitIndex++; + } + + return FudStatus::Success; +} + +template <typename T> +StringConvertResult<T> signedFromString(StringView nextView, size_t skipIndex, Option<uint8_t> specifiedRadixOption) +{ + static_assert(std::is_signed_v<T> && std::is_integral_v<T>); + 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 <typename T> +bool isNanOrInf(T& num, StringView& view, T& sign, size_t& digitIndex) +{ + if (view.length() >= 3) { + Array<uint8_t, 3> 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<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 == static_cast<uint8_t>(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<T>(digitResult) * sign; + num *= static_cast<T>(radix); + + num += digit; + digitIndex++; + } + return FudStatus::Success; +} + +template <typename T> +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<int32_t>(tempView, exponentLength, static_cast<uint8_t>(Radix::Decimal)); + if (convertResult.isError()) { + return convertResult.takeError(); + } + auto convertValue = convertResult.takeOkay(); + digitIndex += convertValue.nextIndex + exponentLength; + num = num * std::pow(static_cast<T>(radix), static_cast<T>(convertValue.value)); + 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 = static_cast<T>(1) / static_cast<T>(radix); + while (digitIndex < view.length()) { + auto nextChar = view.data()[digitIndex]; + if (radix == static_cast<uint8_t>(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<T>(digitResult) * sign; + num += digit * radixDiv; + radixDiv /= static_cast<T>(radix); + digitIndex++; + } + return FudStatus::Success; +} + +template <typename T> +StringConvertResult<T> floatFromString(StringView nextView, size_t skipIndex, Option<uint8_t> specifiedRadixOption) +{ + static_assert(std::is_floating_point_v<T>); + 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 <typename T> +StringConvertResult<T> fromString(StringView inputView, Radix specifiedRadixOption) +{ + return fromString<T>(inputView, static_cast<uint8_t>(specifiedRadixOption)); +} + +template <typename T> +StringConvertResult<T> fromString(StringView inputView, Option<uint8_t> 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<T> && std::is_integral_v<T>) { + return impl::unsignedFromString<T>(nextView, skipIndex, specifiedRadixOption); + } else if constexpr (std::is_signed_v<T> && std::is_integral_v<T>) { + return impl::signedFromString<T>(nextView, skipIndex, specifiedRadixOption); + } else if constexpr (std::is_floating_point_v<T>) { + return impl::floatFromString<T>(nextView, skipIndex, specifiedRadixOption); + } else { + return FudStatus::NotImplemented; + } +} + +} // namespace fud + +#endif |