diff options
author | Dominick Allen <djallen@librehumanitas.org> | 2024-10-23 13:21:10 -0500 |
---|---|---|
committer | Dominick Allen <djallen@librehumanitas.org> | 2024-10-23 13:21:10 -0500 |
commit | 5cc7cbc3704ec255eb5d0ac53b2cc0fcb1221d63 (patch) | |
tree | 169d4d2d8dffe014851712e31a55036deb0c7c0c /include | |
parent | b2dbcb55e2832c373fecb4033a3ed77e5dbc77aa (diff) |
String conversion and parsing format spec.
Diffstat (limited to 'include')
-rw-r--r-- | include/fud_format.hpp | 131 | ||||
-rw-r--r-- | include/fud_option.hpp | 45 | ||||
-rw-r--r-- | include/fud_string_convert.hpp | 456 | ||||
-rw-r--r-- | include/fud_string_view.hpp | 41 | ||||
-rw-r--r-- | include/fud_utf8.hpp | 116 |
5 files changed, 674 insertions, 115 deletions
diff --git a/include/fud_format.hpp b/include/fud_format.hpp index ea32bd8..2576eca 100644 --- a/include/fud_format.hpp +++ b/include/fud_format.hpp @@ -22,24 +22,30 @@ #include "fud_result.hpp" #include "fud_status.hpp" #include "fud_string_view.hpp" +#include "fud_option.hpp" #include "fud_utf8.hpp" #include <cstdint> -#include <optional> #include <variant> +#include <limits> namespace fud { struct FormatAlign { - enum class Value : uint8_t + enum class Value : utf8 { - Left, - Right, - Center + Left = '<', + Right = '>', + Center = '^', + Default = std::numeric_limits<utf8>::max() }; - constexpr static std::optional<FormatAlign> from(utf8 letter) + constexpr static FormatAlign makeDefault() noexcept { + return {Value::Default}; + } + + constexpr static Option<FormatAlign> from(utf8 letter) { FormatAlign formatAlign; switch (letter) { @@ -53,12 +59,16 @@ struct FormatAlign formatAlign.value = Value::Center; break; default: - return std::nullopt; + return NullOpt; } return formatAlign; } + Value operator()() const { + return value; + } + Value value; }; @@ -66,37 +76,50 @@ struct FormatFill { FormatAlign align; utf8 fill; - constexpr static Result<std::optional<FormatFill>, FudStatus> parse(StringView formatView) { - // "{:A<X}" - using RetType = Result<std::optional<FormatFill>, FudStatus>; - if (formatView.length() < 3) { - return RetType::okay(std::nullopt); + constexpr static FormatFill make() noexcept { + return FormatFill {FormatAlign::makeDefault(), ' '}; + } + + constexpr static Result<Option<FormatFill>, FudStatus> make(StringView formatView, size_t& length) noexcept { + using RetType = Result<Option<FormatFill>, FudStatus>; + if (formatView.length() < 1) { + return RetType::okay(NullOpt); } const auto* data = formatView.data(); - if (data[0] != 'A') { - return RetType::okay(std::nullopt); + auto align1 = FormatAlign::from(data[0]); + decltype(align1) align2 = NullOpt; + if (formatView.length() > 1) { + align2 = FormatAlign::from(data[1]); } - auto align = FormatAlign::from(data[1]); - if (!align.has_value()) { - return FudStatus::FormatInvalid; + if (align2.hasValue()) { + length = 2; + auto fill = data[0]; + if (not Ascii::valid(fill)) { + return FudStatus::Utf8Invalid; + } + if (fill == '{' || fill == '}') { + return FudStatus::FormatInvalid; + } + return RetType::okay(FormatFill{std::move(align2).value(), data[0]}); } - auto fill = data[2]; - if (!Ascii::valid(fill)) { - return FudStatus::Utf8Invalid; + if (align1.hasValue()) { + length = 1; + return RetType::okay(FormatFill{std::move(align1).value(), ' '}); } - return RetType::okay(FormatFill{*align, fill}); + return RetType::okay(NullOpt); } }; enum class FormatSign : uint8_t { - Plus, - Minus, - Space + Plus = '+', + Minus = '-', + Space = ' ', + Default = std::numeric_limits<utf8>::max() }; enum class FormatStringType : uint8_t @@ -155,6 +178,7 @@ enum class FormatPointerType : uint8_t HexUpper }; +/* using FormatType = std::variant< // break std::monostate, FormatStringType, @@ -163,33 +187,64 @@ using FormatType = std::variant< // break FormatBoolType, FormatFloatingType, FormatPointerType>; +*/ + +enum class FormatType : utf8 { + Unspecified = '\0', + String = 's', + Escaped = '?', + BinaryLower = 'b', + BinaryUpper = 'B', + Character = 'c', + Decimal = 'd', + Octal = 'o', + HexLower = 'x', + HexUpper = 'X', + FloatHexLower = 'a', + FloatHexUpper = 'A', + ScientificLower = 'e', + ScientificUpper = 'E', + FixedLower = 'f', + FixedUpper = 'F', + GeneralLower = 'g', + GeneralUpper = 'G', +}; struct FormatSpec; using FormatSpecResult = Result<FormatSpec, FudStatus>; struct FormatSpec { - size_t width; - size_t precision; - FormatFill fill; - FormatSign formatSign; + static constexpr uint32_t widthUnspecified = std::numeric_limits<uint32_t>::max(); + static constexpr uint32_t precisionUnspecified = std::numeric_limits<uint32_t>::max(); + static constexpr uint16_t positionUnspecified = std::numeric_limits<uint16_t>::max(); + static constexpr utf8 openBracket = '{'; + static constexpr utf8 closeBracket = '}'; + static constexpr utf8 formatTypeUnspecified = std::numeric_limits<utf8>::max(); + static constexpr utf8 localeChar = 'L'; + + uint32_t width{widthUnspecified}; + + uint32_t precision{precisionUnspecified}; + + uint16_t position{positionUnspecified}; + + FormatFill fill{FormatFill::make()}; - FormatType formatType; + FormatSign formatSign{FormatSign::Default}; - bool hasWidth; - bool takesWidth; + FormatType formatType{}; - bool hasPrecision; - bool takesPrecision; + bool takesWidth{false}; - bool hasFill; + bool takesPrecision{false}; - bool hasFormatSign; + bool alternateForm{false}; - bool alternateForm; + bool leadingZero{false}; - bool leadingZero; + bool hasLocale{false}; - static Result<FormatSpec, FudStatus> make(StringView& formatView, size_t specIndex); + static Result<FormatSpec, FudStatus> make(StringView formatView, size_t& specLength); }; } // namespace fud diff --git a/include/fud_option.hpp b/include/fud_option.hpp index ca3954f..931ef82 100644 --- a/include/fud_option.hpp +++ b/include/fud_option.hpp @@ -103,16 +103,16 @@ class Option { } } - constexpr Option(const Option& rhs) noexcept : m_engaged(rhs.m_engaged), m_data(rhs.m_data) + constexpr Option(const Option& rhs) noexcept : m_data(rhs.m_data), m_engaged(rhs.m_engaged) { } - constexpr Option(Option&& rhs) noexcept : m_engaged(rhs.m_engaged), m_data(std::move(rhs.m_data)) + constexpr Option(Option&& rhs) noexcept : m_data(std::move(rhs.m_data)), m_engaged(rhs.m_engaged) { rhs.cleanup(); } - ~Option() noexcept + constexpr ~Option() noexcept { destroy(); } @@ -142,6 +142,11 @@ class Option { return m_engaged; } + [[nodiscard]] bool isNone() const + { + return !m_engaged; + } + operator bool() const { return hasValue(); } @@ -170,7 +175,23 @@ class Option { { fudAssert(m_engaged); static_assert(!IsRef); - return *reinterpret_cast<const ValueType*>(m_data.data()); + return std::move(*reinterpret_cast<const ValueType*>(m_data.data())); + } + + [[nodiscard]] constexpr const ValueType& valueOr(const ValueType& alternative) const& + { + if (m_engaged) { + return value(); + } + return alternative; + } + + [[nodiscard]] constexpr ValueType&& valueOr(ValueType&& alternative) const&& + { + if (m_engaged) { + return value(); + } + return std::move(alternative); } template <typename F> @@ -203,27 +224,11 @@ class Option { m_data.clear(); } - // alignas(maxAlign) Array<uint8_t, maxSize> priv_m_data; - alignas(alignof(T)) option_detail::DataArray<Size> m_data{}; bool m_engaged; }; -namespace test { - -void testOption() -{ - Option<int> intOpt; - static_cast<void>(intOpt); - Option<int&> intRefNull; - static_cast<void>(intRefNull); - int value; - Option<int&> intRefValue{value}; -} - -} // namespace test - } // namespace fud #endif 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 diff --git a/include/fud_string_view.hpp b/include/fud_string_view.hpp index 8a47ae5..0852645 100644 --- a/include/fud_string_view.hpp +++ b/include/fud_string_view.hpp @@ -20,6 +20,8 @@ #include "fud_status.hpp" #include "fud_utf8.hpp" +#include "fud_config.hpp" +#include "fud_assert.hpp" #include <string_view> @@ -63,6 +65,25 @@ struct StringView { return m_data; } + constexpr const utf8& operator[](size_t index) const + { + if constexpr (fudBoundsChecking) { + fudAssert(m_data != nullptr); + fudAssert(index < m_length); + } + return m_data[index]; + } + + constexpr const utf8* begin() const noexcept + { + return m_data; + } + + constexpr const utf8* end() const noexcept + { + return m_data + m_length; + } + [[nodiscard]] bool nullTerminated() const; [[nodiscard]] bool utf8Valid() const; @@ -79,26 +100,6 @@ struct StringView { void advanceUnsafe(size_t size); - FudStatus toUint8(uint8_t& number, uint8_t specifiedRadix, size_t& strLen) const; - - FudStatus toUint16(uint16_t& number, uint8_t specifiedRadix, size_t& strLen) const; - - FudStatus toUint32(uint32_t& number, uint8_t specifiedRadix, size_t& strLen) const; - - FudStatus toUint64(uint64_t& number, uint8_t specifiedRadix, size_t& strLen) const; - - FudStatus toInt8(int8_t& number, uint8_t specifiedRadix, size_t& strLen) const; - - FudStatus toInt16(int16_t& number, uint8_t specifiedRadix, size_t& strLen) const; - - FudStatus toInt32(int32_t& number, uint8_t specifiedRadix, size_t& strLen) const; - - FudStatus toInt64(int64_t& number, uint8_t specifiedRadix, size_t& strLen) const; - - FudStatus toFloat(float& number, size_t& strLen) const; - - FudStatus toDouble(double& number, size_t& strLen) const; - size_t m_length{0}; const utf8* m_data{nullptr}; diff --git a/include/fud_utf8.hpp b/include/fud_utf8.hpp index 3b1a6b7..50e50aa 100644 --- a/include/fud_utf8.hpp +++ b/include/fud_utf8.hpp @@ -19,8 +19,8 @@ #define FUD_UTF8_HPP #include "fud_array.hpp" -#include "fud_unique_array.hpp" #include "fud_c_string.hpp" +#include "fud_unique_array.hpp" #include <cstdint> #include <optional> @@ -28,8 +28,6 @@ namespace fud { - - using utf8 = unsigned char; class String; @@ -113,8 +111,7 @@ struct Utf82Byte { { } - __attribute__((nonnull)) - constexpr Utf82Byte(const char* letterStr) noexcept : characters{} + __attribute__((nonnull)) constexpr Utf82Byte(const char* letterStr) noexcept : characters{} { auto length = cStringLength(letterStr, 2); if (length < 2) { @@ -159,8 +156,7 @@ struct Utf83Byte { { } - __attribute__((nonnull)) - constexpr Utf83Byte(const char* letterStr) noexcept : characters{} + __attribute__((nonnull)) constexpr Utf83Byte(const char* letterStr) noexcept : characters{} { auto length = cStringLength(letterStr, 3); if (length < 3) { @@ -213,8 +209,7 @@ struct Utf84Byte { { } - __attribute__((nonnull)) - constexpr Utf84Byte(const char* letterStr) noexcept : characters{} + __attribute__((nonnull)) constexpr Utf84Byte(const char* letterStr) noexcept : characters{} { auto length = cStringLength(letterStr, 4); if (length < 4) { @@ -327,7 +322,8 @@ struct FudUtf8 { return unicode; } - static constexpr FudUtf8 make(Utf8Variant utf8Variant) { + static constexpr FudUtf8 make(Utf8Variant utf8Variant) + { FudUtf8 unicode{}; unicode.m_variant = utf8Variant; if (!std::visit([](auto arg) { return arg.valid(); }, utf8Variant)) { @@ -513,82 +509,128 @@ struct FudUtf8 { } }; +namespace classify { + +using CharPredicate = bool (*)(char); +using Utf8Predicate = bool (*)(utf8); +using FudUtf8Predicate = bool (*)(FudUtf8); + /** \brief Checks if a character is ascii. */ -[[nodiscard]] bool charIsAscii(char character); +[[nodiscard]] bool isAscii(char character); -[[nodiscard]] bool utf8IsAscii(FudUtf8 character); +[[nodiscard]] bool isAscii(utf8 character); + +[[nodiscard]] bool isAscii(FudUtf8 character); /** \brief Checks if a character is alphanumeric. */ -[[nodiscard]] bool charIsAlphanumeric(char character); +[[nodiscard]] bool isAlphanumeric(char character); /** \brief Checks if a character is alphanumeric. */ -[[nodiscard]] bool utf8IsAlphanumeric(FudUtf8 character); +[[nodiscard]] bool isAlphanumeric(utf8 character); + +/** \brief Checks if a character is alphanumeric. */ +[[nodiscard]] bool isAlphanumeric(FudUtf8 character); + +/** \brief Checks if a character is alphabetic. */ +[[nodiscard]] bool isAlpha(char character); /** \brief Checks if a character is alphabetic. */ -[[nodiscard]] bool charIsAlpha(char character); +[[nodiscard]] bool isAlpha(utf8 character); /** \brief Checks if a character is alphabetic. */ -[[nodiscard]] bool utf8IsAlpha(FudUtf8 character); +[[nodiscard]] bool isAlpha(FudUtf8 character); + +/** \brief Checks if a character is lowercase. */ +[[nodiscard]] bool isLowercase(char character); /** \brief Checks if a character is lowercase. */ -[[nodiscard]] bool charIsLowercase(char character); +[[nodiscard]] bool isLowercase(utf8 character); /** \brief Checks if a character is lowercase. */ -[[nodiscard]] bool utf8IsLowercase(FudUtf8 character); +[[nodiscard]] bool isLowercase(FudUtf8 character); + +/** \brief Checks if a character is uppercase. */ +[[nodiscard]] bool isUppercase(char character); -/** \brief Checks if a character is an uppercase character. */ -[[nodiscard]] bool charIsUppercase(char character); +/** \brief Checks if a character is uppercase. */ +[[nodiscard]] bool isUppercase(utf8 character); /** \brief Checks if a character is uppercase. */ -[[nodiscard]] bool utf8IsUppercase(FudUtf8 character); +[[nodiscard]] bool isUppercase(FudUtf8 character); /** \brief Checks if a character is a digit. */ -[[nodiscard]] bool charIsDigit(char character); +[[nodiscard]] bool isDigit(char character); /** \brief Checks if a character is a digit. */ -[[nodiscard]] bool utf8IsDigit(FudUtf8 character); +[[nodiscard]] bool isDigit(utf8 character); + +/** \brief Checks if a character is a digit. */ +[[nodiscard]] bool isDigit(FudUtf8 character); /** \brief Checks if a character is a hexadecimal character. */ -[[nodiscard]] bool charIsHexDigit(char character); +[[nodiscard]] bool isHexDigit(char character); + +/** \brief Checks if a character is a hexadecimal character. */ +[[nodiscard]] bool isHexDigit(utf8 character); /** \brief Checks if a character is a hexadecimal digit. */ -[[nodiscard]] bool utf8IsHexDigit(FudUtf8 character); +[[nodiscard]] bool isHexDigit(FudUtf8 character); /** \brief Checks if a character is a control character. */ -[[nodiscard]] bool charIsControl(char character); +[[nodiscard]] bool isControl(char character); /** \brief Checks if a character is a control character. */ -[[nodiscard]] bool utf8IsControl(FudUtf8 character); +[[nodiscard]] bool isControl(utf8 character); + +/** \brief Checks if a character is a control character. */ +[[nodiscard]] bool isControl(FudUtf8 character); + +/** \brief Checks if a character is a graphical character. */ +[[nodiscard]] bool isGraphical(char character); /** \brief Checks if a character is a graphical character. */ -[[nodiscard]] bool charIsGraphical(char character); +[[nodiscard]] bool isGraphical(utf8 character); /** \brief Checks if a character is a graphical character. */ -[[nodiscard]] bool utf8IsGraphical(FudUtf8 character); +[[nodiscard]] bool isGraphical(FudUtf8 character); /** \brief Checks if a character is a space character. */ -[[nodiscard]] bool charIsSpace(char character); +[[nodiscard]] bool isSpace(char character); /** \brief Checks if a character is a space character. */ -[[nodiscard]] bool utf8IsSpace(FudUtf8 character); +[[nodiscard]] bool isSpace(utf8 character); + +/** \brief Checks if a character is a space character. */ +[[nodiscard]] bool isSpace(FudUtf8 character); + +/** \brief Checks if a character is a blank character. */ +[[nodiscard]] bool isBlank(char character); /** \brief Checks if a character is a blank character. */ -[[nodiscard]] bool charIsBlank(char character); +[[nodiscard]] bool isBlank(utf8 character); /** \brief Checks if a character is a blank character. */ -[[nodiscard]] bool utf8IsBlank(FudUtf8 character); +[[nodiscard]] bool isBlank(FudUtf8 character); + +/** \brief Checks if a character is a printable character. */ +[[nodiscard]] bool isPrintable(char character); /** \brief Checks if a character is a printable character. */ -[[nodiscard]] bool charIsPrintable(char character); +[[nodiscard]] bool isPrintable(utf8 character); /** \brief Checks if a character is a printable character. */ -[[nodiscard]] bool utf8IsPrintable(FudUtf8 character); +[[nodiscard]] bool isPrintable(FudUtf8 character); + +/** \brief Checks if a character is a punctuation character. */ +[[nodiscard]] bool isPunctuation(char character); /** \brief Checks if a character is a punctuation character. */ -[[nodiscard]] bool charIsPunctuation(char character); +[[nodiscard]] bool isPunctuation(utf8 character); /** \brief Checks if a character is a punctuation character. */ -[[nodiscard]] bool utf8IsPunctuation(FudUtf8 character); +[[nodiscard]] bool isPunctuation(FudUtf8 character); + +} // namespace classify /** \brief Converts character to lowercase if valid. */ uint8_t charToLower(uint8_t character); |