From 5cc7cbc3704ec255eb5d0ac53b2cc0fcb1221d63 Mon Sep 17 00:00:00 2001 From: Dominick Allen Date: Wed, 23 Oct 2024 13:21:10 -0500 Subject: String conversion and parsing format spec. --- CMakeLists.txt | 2 + include/fud_format.hpp | 131 +++++--- include/fud_option.hpp | 45 +-- include/fud_string_convert.hpp | 456 +++++++++++++++++++++++++++ include/fud_string_view.hpp | 41 +-- include/fud_utf8.hpp | 116 ++++--- source/fud_assert.cpp | 1 + source/fud_format.cpp | 515 +++++++++++++++++++++++++++++- source/fud_string.cpp | 2 +- source/fud_string_convert.cpp | 110 +++++++ source/fud_string_view.cpp | 701 ++--------------------------------------- source/fud_utf8.cpp | 207 +++++++----- test/CMakeLists.txt | 1 + test/test_format.cpp | 330 ++++++++++++++++++- test/test_string_convert.cpp | 186 +++++++++++ test/test_utf8.cpp | 219 +++++++------ tools/coverage.sh | 1 + tools/create-pyenv.sh | 1 + tools/run-cppcheck.sh | 1 + 19 files changed, 2091 insertions(+), 975 deletions(-) create mode 100644 include/fud_string_convert.hpp create mode 100644 source/fud_string_convert.cpp create mode 100644 test/test_string_convert.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a41872..12c0727 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ add_library(fud SHARED source/fud_memory.cpp source/fud_sqlite.cpp source/fud_string_view.cpp + source/fud_string_convert.cpp source/fud_string.cpp source/fud_utf8.cpp source/fud_utf8_iterator.cpp @@ -109,6 +110,7 @@ set(FUD_HEADERS "include/fud_span.hpp" "include/fud_status.hpp" "include/fud_string.hpp" + "include/fud_string_convert.hpp" "include/fud_string_view.hpp" "include/fud_sqlite.hpp" "include/fud_unique_array.hpp" 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 -#include #include +#include namespace fud { struct FormatAlign { - enum class Value : uint8_t + enum class Value : utf8 { - Left, - Right, - Center + Left = '<', + Right = '>', + Center = '^', + Default = std::numeric_limits::max() }; - constexpr static std::optional from(utf8 letter) + constexpr static FormatAlign makeDefault() noexcept { + return {Value::Default}; + } + + constexpr static Option 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, FudStatus> parse(StringView formatView) { - // "{:A, FudStatus>; - if (formatView.length() < 3) { - return RetType::okay(std::nullopt); + constexpr static FormatFill make() noexcept { + return FormatFill {FormatAlign::makeDefault(), ' '}; + } + + constexpr static Result, FudStatus> make(StringView formatView, size_t& length) noexcept { + using RetType = Result, 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::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; struct FormatSpec { - size_t width; - size_t precision; - FormatFill fill; - FormatSign formatSign; + static constexpr uint32_t widthUnspecified = std::numeric_limits::max(); + static constexpr uint32_t precisionUnspecified = std::numeric_limits::max(); + static constexpr uint16_t positionUnspecified = std::numeric_limits::max(); + static constexpr utf8 openBracket = '{'; + static constexpr utf8 closeBracket = '}'; + static constexpr utf8 formatTypeUnspecified = std::numeric_limits::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 make(StringView& formatView, size_t specIndex); + static Result 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(m_data.data()); + return std::move(*reinterpret_cast(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 @@ -203,27 +224,11 @@ class Option { m_data.clear(); } - // alignas(maxAlign) Array priv_m_data; - alignas(alignof(T)) option_detail::DataArray m_data{}; bool m_engaged; }; -namespace test { - -void testOption() -{ - Option intOpt; - static_cast(intOpt); - Option intRefNull; - static_cast(intRefNull); - int value; - Option 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 +#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 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 @@ -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 #include @@ -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); diff --git a/source/fud_assert.cpp b/source/fud_assert.cpp index 3df6734..ccc90a5 100644 --- a/source/fud_assert.cpp +++ b/source/fud_assert.cpp @@ -1,6 +1,7 @@ #include "fud_assert.hpp" // #include "fud_array.hpp" +// #include "fud_format.hpp" #include #include diff --git a/source/fud_format.cpp b/source/fud_format.cpp index ab1bb4f..04e611e 100644 --- a/source/fud_format.cpp +++ b/source/fud_format.cpp @@ -1,14 +1,515 @@ -// #include "fud_format.hpp" +#include "fud_format.hpp" + +#include "fud_string_convert.hpp" +#include "fud_utf8.hpp" namespace fud { -/* -Result FormatSpec::make(StringView view, size_t& length) +namespace impl { + +Result preScanSpec(StringView formatView, FormatSpec& spec, bool& hasPosition, bool& hasColon); + +FudStatus getPosition(StringView& formatView, FormatSpec& spec, bool& hasPosition); + +FudStatus getFill(StringView& formatView, FormatSpec& spec); + +FudStatus getSpecialOpts(StringView& formatView, FormatSpec& spec); + +FudStatus getWidth(StringView& formatView, FormatSpec& spec); + +FudStatus getPrecision(StringView& formatView, FormatSpec& spec); + +FudStatus getFormatSign(StringView& formatView, FormatSpec& spec); + +} // namespace impl + +Result FormatSpec::make(StringView formatView, size_t& specLength) +{ + static_cast(formatView); + static_cast(specLength); + + if (formatView[0] != FormatSpec::openBracket) { + return FudStatus::ArgumentInvalid; + } + + auto hasEnded = [](StringView& view) { + fudAssert(view.length() > 0); + return view[0] == FormatSpec::closeBracket; + }; + + FormatSpec spec{}; + size_t length = 1; + + formatView.advanceUnsafe(); + + bool hasPosition{false}; + bool hasColon{false}; + auto lengthResult = impl::preScanSpec(formatView, spec, hasPosition, hasColon); + if (lengthResult.isError()) { + return lengthResult.takeError(); + } + + length += lengthResult.takeOkay(); + if (length < 1) { + return FudStatus::FormatInvalid; + } + specLength = length; + + auto status = impl::getPosition(formatView, spec, hasPosition); + if (status != FudStatus::Success) { + return status; + } + + // check early ending + + if (!hasColon) { + if (hasEnded(formatView)) { + return spec; + } + return FudStatus::FormatInvalid; + } + + // check validity for being non-default spec and advance + + if (formatView[0] != ':') { + return FudStatus::FormatInvalid; + } + + formatView.advanceUnsafe(); + if (hasEnded(formatView)) { + return spec; + } + + // Spec + + status = impl::getFill(formatView, spec); + if (status != FudStatus::Success) { + return status; + } + + if (hasEnded(formatView)) { + return spec; + } + + // sign, alternate, leading zero + + status = impl::getSpecialOpts(formatView, spec); + if (status != FudStatus::Success) { + return status; + } + + if (hasEnded(formatView)) { + return spec; + } + + // Width + + status = impl::getWidth(formatView, spec); + if (status != FudStatus::Success) { + return status; + } + + if (hasEnded(formatView)) { + return spec; + } + + // Precision + + status = impl::getPrecision(formatView, spec); + if (status != FudStatus::Success) { + return status; + } + + if (hasEnded(formatView)) { + return spec; + } + + // Locale + + if (formatView[0] == FormatSpec::localeChar) { + spec.hasLocale = true; + formatView.advanceUnsafe(); + } + + if (hasEnded(formatView)) { + return spec; + } + + // Format Sign + status = impl::getFormatSign(formatView, spec); + if (status != FudStatus::Success) { + return status; + } + + if (!hasEnded(formatView)) { + return FudStatus::FormatInvalid; + } + + return spec; +} + +namespace impl { + +Result preScanSpec(StringView formatView, FormatSpec& spec, bool& hasPosition, bool& hasColon) +{ + int nesting = 0; + uint32_t captureGroups = 0; + size_t index = 0; + hasColon = false; + while (index < formatView.length() && (formatView[index] != FormatSpec::closeBracket || nesting > 0)) { + auto letter = formatView[index]; + bool isDigit = classify::isDigit(static_cast(letter)); + bool priorDot = index > 0 && formatView[index - 1] == '.'; + bool takesWidth = not spec.takesWidth and not priorDot; + bool takesPrecision = not spec.takesPrecision and priorDot; + switch (letter) { + case FormatSpec::openBracket: + if (not hasColon) { + return FudStatus::FormatInvalid; + } + if (takesWidth) { + spec.takesWidth = true; + } else if (takesPrecision) { + spec.takesPrecision = true; + } else { + return FudStatus::FormatInvalid; + } + nesting++; + captureGroups++; + break; + case FormatSpec::closeBracket: + nesting--; + break; + case ':': + hasColon = true; + break; + case '.': + break; + case 'L': + spec.hasLocale = true; + break; + default: + if (not hasColon and isDigit) { + hasPosition = true; + } + break; + } + + if (nesting > 1) { + return FudStatus::FormatInvalid; + } + + index++; + } + + if (nesting != 0 || captureGroups > 2 || index >= formatView.length() || + formatView[index] != FormatSpec::closeBracket) { + return FudStatus::FormatInvalid; + } + + return index + 1U; +} + +FudStatus getPosition(StringView& formatView, FormatSpec& spec, bool& hasPosition) { - static_cast(view); - static_cast(length); - return FudStatus::NotImplemented; + if (hasPosition) { + auto positionResult{fromString(formatView, Radix::Decimal)}; + if (positionResult.isError()) { + return positionResult.takeError(); + } + auto positionValue{positionResult.takeOkay()}; + spec.position = positionValue.value; + auto advanceStatus = formatView.advance(positionValue.nextIndex); + if (!advanceStatus) { + return FudStatus::Failure; + } + } + + return FudStatus::Success; } -*/ + +FudStatus getFill(StringView& formatView, FormatSpec& spec) +{ + Option fillOpt = NullOpt; + if (formatView[0] != FormatSpec::openBracket) { + size_t fillLength = 0; + auto fillResult = FormatFill::make(formatView, fillLength); + if (fillResult.isError()) { + return fillResult.takeError(); + } + fillOpt = fillResult.takeOkay(); + formatView.advanceUnsafe(fillLength); + } + spec.fill = fillOpt.valueOr(FormatFill::make()); + return FudStatus::Success; +} + +FudStatus getSpecialOpts(StringView& formatView, FormatSpec& spec) +{ + if (formatView.length() < 1) { + return FudStatus::ArgumentInvalid; + } + + bool done = false; + switch (formatView[0]) { + case static_cast(FormatSign::Plus): + spec.formatSign = FormatSign::Plus; + break; + case static_cast(FormatSign::Minus): + spec.formatSign = FormatSign::Minus; + break; + case static_cast(FormatSign::Space): + spec.formatSign = FormatSign::Space; + break; + case '#': + spec.alternateForm = true; + break; + case '0': + spec.leadingZero = true; + done = true; + break; + default: + return FudStatus::Success; + } + + formatView.advanceUnsafe(); + if (done || formatView.length() < 1) { + return FudStatus::Success; + } + + switch (formatView[0]) { + case '#': + if (spec.alternateForm) { + return FudStatus::FormatInvalid; + } + spec.alternateForm = true; + break; + case '0': + spec.leadingZero = true; + done = true; + break; + default: + return FudStatus::Success; + } + + formatView.advanceUnsafe(); + if (done || formatView.length() < 1) { + return FudStatus::Success; + } + + if (formatView[0] == '0') { + spec.leadingZero = true; + formatView.advanceUnsafe(); + } + + return FudStatus::Success; +} + +FudStatus getWidthTakesWidth(StringView& formatView, FormatSpec& spec); + +FudStatus getWidth(StringView& formatView, FormatSpec& spec) +{ + if (spec.takesWidth) { + return getWidthTakesWidth(formatView, spec); + } + + if (formatView.length() < 1 || not classify::isDigit(formatView[0])) { + return FudStatus::Success; + } + + auto widthResult{fromString(formatView, Radix::Decimal)}; + if (widthResult.isError()) { + return widthResult.takeError(); + } + auto widthValue{widthResult.takeOkay()}; + if (widthValue.value < 1) { + return FudStatus::FormatInvalid; + } + spec.width = widthValue.value; + if (formatView.length() < widthValue.nextIndex) { + return FudStatus::Failure; + } + formatView.advanceUnsafe(widthValue.nextIndex); + + return FudStatus::Success; +} + +FudStatus getWidthTakesWidth(StringView& formatView, FormatSpec& spec) +{ + if (!spec.takesWidth) { + return FudStatus::OperationInvalid; + } + + if (formatView.length() < 2 || formatView[0] != FormatSpec::openBracket) { + return FudStatus::ArgumentInvalid; + } + formatView.advanceUnsafe(); + if (formatView.length() > 0 && formatView[0] == FormatSpec::closeBracket && + spec.position == FormatSpec::positionUnspecified) { + formatView.advanceUnsafe(); + return FudStatus::Success; + } + + if (spec.position == FormatSpec::positionUnspecified) { + return FudStatus::FormatInvalid; + } + + auto positionResult{fromString(formatView, Radix::Decimal)}; + if (positionResult.isError()) { + return positionResult.takeError(); + } + auto positionValue{positionResult.takeOkay()}; + spec.width = positionValue.value; + if (formatView.length() + 1 < positionValue.nextIndex) { + return FudStatus::Failure; + } + if (formatView[positionValue.nextIndex + 1] != FormatSpec::closeBracket) { + return FudStatus::FormatInvalid; + } + formatView.advanceUnsafe(positionValue.nextIndex + 1); + return FudStatus::Success; +} + +FudStatus getPrecisionTakesPrecision(StringView& formatView, FormatSpec& spec); + +FudStatus getPrecision(StringView& formatView, FormatSpec& spec) +{ + if (formatView.length() < 1 || formatView[0] != '.') { + return FudStatus::Success; + } + + formatView.advanceUnsafe(); + + if (spec.takesPrecision) { + return getPrecisionTakesPrecision(formatView, spec); + } + + if (formatView.length() < 1 || not classify::isDigit(formatView[0])) { + return FudStatus::Success; + } + + auto precisionResult{fromString(formatView, Radix::Decimal)}; + if (precisionResult.isError()) { + return precisionResult.takeError(); + } + auto precisionValue{precisionResult.takeOkay()}; + if (precisionValue.value < 1) { + return FudStatus::FormatInvalid; + } + spec.precision = precisionValue.value; + if (formatView.length() < precisionValue.nextIndex) { + return FudStatus::Failure; + } + formatView.advanceUnsafe(precisionValue.nextIndex); + + return FudStatus::Success; +} + +FudStatus getPrecisionTakesPrecision(StringView& formatView, FormatSpec& spec) +{ + if (!spec.takesPrecision) { + return FudStatus::OperationInvalid; + } + + if (formatView.length() < 2 || formatView[0] != FormatSpec::openBracket) { + return FudStatus::ArgumentInvalid; + } + formatView.advanceUnsafe(); + if (formatView.length() > 0 && formatView[0] == FormatSpec::closeBracket && + spec.position == FormatSpec::positionUnspecified) { + formatView.advanceUnsafe(); + return FudStatus::Success; + } + + if (spec.position == FormatSpec::positionUnspecified) { + return FudStatus::FormatInvalid; + } + + auto positionResult{fromString(formatView, Radix::Decimal)}; + if (positionResult.isError()) { + return positionResult.takeError(); + } + auto positionValue{positionResult.takeOkay()}; + spec.precision = positionValue.value; + if (formatView.length() + 1 < positionValue.nextIndex) { + return FudStatus::Failure; + } + if (formatView[positionValue.nextIndex + 1] != FormatSpec::closeBracket) { + return FudStatus::FormatInvalid; + } + formatView.advanceUnsafe(positionValue.nextIndex + 1); + return FudStatus::Success; +} + +FudStatus getFormatSign(StringView& formatView, FormatSpec& spec) +{ + if (formatView.length() < 1) { + spec.formatType = FormatType::Unspecified; + return FudStatus::Success; + } + + auto letter = formatView[0]; + switch (letter) { + case static_cast(FormatType::String): + spec.formatType = FormatType::String; + break; + case static_cast(FormatType::Escaped): + spec.formatType = FormatType::Escaped; + break; + case static_cast(FormatType::BinaryLower): + spec.formatType = FormatType::BinaryLower; + break; + case static_cast(FormatType::BinaryUpper): + spec.formatType = FormatType::BinaryUpper; + break; + case static_cast(FormatType::Character): + spec.formatType = FormatType::Character; + break; + case static_cast(FormatType::Decimal): + spec.formatType = FormatType::Decimal; + break; + case static_cast(FormatType::Octal): + spec.formatType = FormatType::Octal; + break; + case static_cast(FormatType::HexLower): + spec.formatType = FormatType::HexLower; + break; + case static_cast(FormatType::HexUpper): + spec.formatType = FormatType::HexUpper; + break; + case static_cast(FormatType::FloatHexLower): + spec.formatType = FormatType::FloatHexLower; + break; + case static_cast(FormatType::FloatHexUpper): + spec.formatType = FormatType::FloatHexUpper; + break; + case static_cast(FormatType::ScientificLower): + spec.formatType = FormatType::ScientificLower; + break; + case static_cast(FormatType::ScientificUpper): + spec.formatType = FormatType::ScientificUpper; + break; + case static_cast(FormatType::FixedLower): + spec.formatType = FormatType::FixedLower; + break; + case static_cast(FormatType::FixedUpper): + spec.formatType = FormatType::FixedUpper; + break; + case static_cast(FormatType::GeneralLower): + spec.formatType = FormatType::GeneralLower; + break; + case static_cast(FormatType::GeneralUpper): + spec.formatType = FormatType::GeneralUpper; + break; + case static_cast(FormatType::Unspecified): + default: + return FudStatus::FormatInvalid; + } + + formatView.advanceUnsafe(); + return FudStatus::Success; +} + +} // namespace impl } // namespace fud diff --git a/source/fud_string.cpp b/source/fud_string.cpp index b714dfc..6e16741 100644 --- a/source/fud_string.cpp +++ b/source/fud_string.cpp @@ -1,5 +1,5 @@ /* - * LibFud + * libfud * Copyright 2024 Dominick Allen * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/source/fud_string_convert.cpp b/source/fud_string_convert.cpp new file mode 100644 index 0000000..428ab36 --- /dev/null +++ b/source/fud_string_convert.cpp @@ -0,0 +1,110 @@ +/* + * 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_convert.hpp" + +namespace fud::impl { + +FudStatus checkPlusSigned(StringView& view, size_t& skipIndex) +{ + auto isPlusSigned = view.data()[0] == '+'; + if (isPlusSigned && view.length() == 1) { + return FudStatus::ArgumentInvalid; + } + if (isPlusSigned) { + view.advanceUnsafe(); + skipIndex++; + } + return FudStatus::Success; +} + +Result checkNegative(StringView& view, size_t& skipIndex) +{ + bool isNegative = view.data()[0] == '-'; + if (isNegative && view.length() == 1) { + return FudStatus::ArgumentInvalid; + } + if (isNegative) { + skipIndex += 1; + view.advanceUnsafe(); + } + return isNegative; +} + +Result determineRadix(StringView input, size_t& index) +{ + if (input.length() < 1) { + return FudStatus::ArgumentInvalid; + } + + if (input.length() == 1 && input.data()[0] == '0') { + return Radix::Octal; + } + + if (input.length() == 1) { + return Radix::Decimal; + } + + if (input.data()[0] == '0' && (input.data()[1] == 'x' || input.data()[1] == 'X')) { + index += 2; + return Radix::Hexadecimal; + } + + if (input.data()[0] == '0') { + auto nextChar = input.data()[1]; + auto nextVal = AsciiLookup[nextChar]; + if (nextVal >= 0 && nextVal < static_cast(Radix::Octal)) { + return Radix::Octal; + } + if (nextVal >= static_cast(Radix::Octal)) { + return FudStatus::ArgumentInvalid; + } + } + + return Radix::Decimal; +} + +Result getRadix(StringView& view, size_t& skipIndex, Option specifiedRadixOption) +{ + if (specifiedRadixOption.isNone()) { + size_t radixIndex = 0; + auto status = determineRadix(view, radixIndex); + if (status.isOkay()) { + skipIndex += radixIndex; + view.advanceUnsafe(radixIndex); + return static_cast(status.takeOkay()); + } + return status.takeError(); + } + + auto radix = specifiedRadixOption.value(); + if (radix == static_cast(Radix::Hexadecimal) && view.length() > 2 && + (view.data()[1] == 'x' || view.data()[1] == 'X')) { + skipIndex += 2; + view.advanceUnsafe(2); + } else if (radix == static_cast(Radix::Binary) && view.length() > 2 && view.data()[1] == 'b') { + skipIndex += 2; + view.advanceUnsafe(2); + } else if (radix == static_cast(Radix::Octal) && view.length() > 2 && view.data()[1] == '0') { + skipIndex += 2; + view.advanceUnsafe(2); + } + + return radix; +} + +} // namespace fud::impl diff --git a/source/fud_string_view.cpp b/source/fud_string_view.cpp index 1cc73a6..090dd6d 100644 --- a/source/fud_string_view.cpp +++ b/source/fud_string_view.cpp @@ -1,5 +1,5 @@ /* - * LibFud + * libfud * Copyright 2024 Dominick Allen * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -61,7 +61,7 @@ Result StringView::skipWhitespace() return RetType::error(FudStatus::NullPointer); } size_t index = 0; - while (m_length > 0 && charIsSpace(static_cast(m_data[0]))) { + while (m_length > 0 && classify::isSpace(static_cast(m_data[0]))) { m_data++; m_length--; index++; @@ -78,7 +78,7 @@ Result StringView::trimWhitespace() } size_t count = 0; - while (m_length > 0 && charIsSpace(static_cast(m_data[m_length - 1]))) { + while (m_length > 0 && classify::isSpace(static_cast(m_data[m_length - 1]))) { m_length--; count++; } @@ -120,6 +120,19 @@ void StringView::advanceUnsafe(size_t size) m_data += size; } +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::ArgumentInvalid; + } + return FudStatus::Success; +} + #if 0 FudStatus fud_string_truncate(ExtBasicString* source, ssize_t newLength) @@ -134,7 +147,7 @@ FudStatus fud_string_truncate(ExtBasicString* source, ssize_t newLength) if ((newLength > 0 && static_cast(newLength) > source->m_length) || (static_cast(-newLength) > source->m_length)) { - return FudStatus::InvalidInput; + return FudStatus::ArgumentInvalid; } if (newLength < 0) { @@ -158,10 +171,10 @@ FudStatus fud_string_reverse_substring(ExtBasicString* source, StringView subStr { auto dataOffset = subString.data - source->m_data; if (dataOffset < 0 || static_cast(dataOffset) > source->m_length) { - return FudStatus::InvalidInput; + return FudStatus::ArgumentInvalid; } if (static_cast(dataOffset) + subString.length > source->m_length) { - return FudStatus::InvalidInput; + return FudStatus::ArgumentInvalid; } if (source == nullptr || source->m_data == nullptr) { @@ -476,682 +489,6 @@ FudStatus fud_string_find_substring(StringView haystack, StringView needle, Stri return ExtNotFound; } -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; -} - -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 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 = 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 = 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) { - Array letters{{view.data[0], view.data[1], view.data[2]}}; - mapMut(letters, fud_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(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 FudStatus::RangeError; - } - - 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 diff --git a/source/fud_utf8.cpp b/source/fud_utf8.cpp index 4d617da..bffb5c1 100644 --- a/source/fud_utf8.cpp +++ b/source/fud_utf8.cpp @@ -27,7 +27,6 @@ FudUtf8 FudUtf8::from(const String& fudString, size_t index) noexcept return invalidAscii(); } - return from(StringView{fudString}, index); } @@ -69,20 +68,26 @@ FudUtf8 FudUtf8::from(StringView view, size_t index) noexcept return invalidAscii(); } -bool charIsAscii(char character) +namespace classify { + +bool isAscii(char character) +{ + return isAscii(static_cast(character)); +} + +bool isAscii(utf8 character) { - return static_cast(character & ~ASCII_MASK) == 0; + return (character & ~ASCII_MASK) == 0; } -bool utf8IsAscii(FudUtf8 character) +bool isAscii(FudUtf8 character) { return character.getType() == Utf8Type::Ascii && character.valid(); } namespace impl { -template -bool isAsciiPredicate(FudUtf8 character, Predicate&& predicate) +bool isAsciiPredicate(FudUtf8 character, bool (*predicate)(char)) { auto maybeAscii = character.getAscii(); if (!maybeAscii.has_value()) { @@ -90,92 +95,122 @@ bool isAsciiPredicate(FudUtf8 character, Predicate&& predicate) } auto asciiChar = *maybeAscii; - return std::forward(predicate)(asciiChar.asChar()); + return predicate(asciiChar.asChar()); } } // namespace impl -bool charIsAlphanumeric(char character) +bool isAlphanumeric(char character) { - if (!charIsAscii(character)) { + return isAlphanumeric(static_cast(character)); +} + +bool isAlphanumeric(utf8 character) +{ + if (!isAscii(character)) { return false; } - if (charIsAlpha(character)) { + if (isAlpha(character)) { return true; } - return charIsDigit(character); + return isDigit(character); +} + +bool isAlphanumeric(FudUtf8 character) +{ + return impl::isAsciiPredicate(character, isAlphanumeric); } -bool utf8IsAlphanumeric(FudUtf8 character) +bool isAlpha(char character) { - return impl::isAsciiPredicate(character, charIsAlphanumeric); + return isAlpha(static_cast(character)); } -bool charIsAlpha(char character) +bool isAlpha(utf8 character) { - if (!charIsAscii(character)) { + if (!isAscii(character)) { return false; } - if (charIsUppercase(character)) { + if (isUppercase(character)) { return true; } - return charIsLowercase(character); + return isLowercase(character); } -bool utf8IsAlpha(FudUtf8 character) +bool isAlpha(FudUtf8 character) { - return impl::isAsciiPredicate(character, charIsAlpha); + return impl::isAsciiPredicate(character, isAlpha); } -bool charIsLowercase(char character) +bool isLowercase(char character) { - if (!charIsAscii(character)) { + return isLowercase(static_cast(character)); +} + +bool isLowercase(utf8 character) +{ + if (!isAscii(character)) { return false; } return 'a' <= character && character <= 'z'; } -bool utf8IsLowercase(FudUtf8 character) +bool isLowercase(FudUtf8 character) +{ + return impl::isAsciiPredicate(character, isLowercase); +} + +bool isUppercase(char character) { - return impl::isAsciiPredicate(character, charIsLowercase); + return isUppercase(static_cast(character)); } -bool charIsUppercase(char character) +bool isUppercase(utf8 character) { - if (!charIsAscii(character)) { + if (!isAscii(character)) { return false; } return 'A' <= character && character <= 'Z'; } -bool utf8IsUppercase(FudUtf8 character) +bool isUppercase(FudUtf8 character) { - return impl::isAsciiPredicate(character, charIsUppercase); + return impl::isAsciiPredicate(character, isUppercase); } -bool charIsDigit(char character) +bool isDigit(char character) { - if (!charIsAscii(character)) { + return isDigit(static_cast(character)); +} + +bool isDigit(utf8 character) +{ + if (!isAscii(character)) { return false; } return '0' <= character && character <= '9'; } -bool utf8IsDigit(FudUtf8 character) +bool isDigit(FudUtf8 character) +{ + return impl::isAsciiPredicate(character, isDigit); +} + +bool isHexDigit(char character) { - return impl::isAsciiPredicate(character, charIsDigit); + return isHexDigit(static_cast(character)); } -bool charIsHexDigit(char character) +bool isHexDigit(utf8 character) { - if (!charIsAscii(character)) { + if (!isAscii(character)) { return false; } @@ -183,86 +218,116 @@ bool charIsHexDigit(char character) ('A' <= character && character <= 'F'); } -bool utf8IsHexDigit(FudUtf8 character) +bool isHexDigit(FudUtf8 character) +{ + return impl::isAsciiPredicate(character, isHexDigit); +} + +bool isControl(char character) { - return impl::isAsciiPredicate(character, charIsHexDigit); + return isControl(static_cast(character)); } -bool charIsControl(char character) +bool isControl(utf8 character) { - if (!charIsAscii(character)) { + if (!isAscii(character)) { return false; } constexpr char maxControlChar = 0x1F; constexpr const char deleteChar = 0x7F; - return ((static_cast(character) <= maxControlChar)) || character == deleteChar; + return ((static_cast(character) <= maxControlChar)) || character == deleteChar; } -bool utf8IsControl(FudUtf8 character) +bool isControl(FudUtf8 character) { - return impl::isAsciiPredicate(character, charIsControl); + return impl::isAsciiPredicate(character, isControl); } -bool charIsGraphical(char character) +bool isGraphical(char character) { - if (!charIsAscii(character)) { + return isGraphical(static_cast(character)); +} + +bool isGraphical(utf8 character) +{ + if (!isAscii(character)) { return false; } - return charIsAlphanumeric(character) || charIsPunctuation(character); + return isAlphanumeric(character) || isPunctuation(character); +} + +bool isGraphical(FudUtf8 character) +{ + return impl::isAsciiPredicate(character, isGraphical); } -bool utf8IsGraphical(FudUtf8 character) +bool isSpace(char character) { - return impl::isAsciiPredicate(character, charIsGraphical); + return isSpace(static_cast(character)); } -bool charIsSpace(char character) +bool isSpace(utf8 character) { - if (!charIsAscii(character)) { + if (!isAscii(character)) { return false; } return character == ' ' || character == '\t' || character == '\n' || character == '\r' || character == '\v'; } -bool utf8IsSpace(FudUtf8 character) +bool isSpace(FudUtf8 character) { - return impl::isAsciiPredicate(character, charIsSpace); + return impl::isAsciiPredicate(character, isSpace); } -bool charIsBlank(char character) +bool isBlank(char character) { - if (!charIsAscii(character)) { + return isBlank(static_cast(character)); +} + +bool isBlank(utf8 character) +{ + if (!isAscii(character)) { return false; } return character == ' ' || character == '\t'; } -bool utf8IsBlank(FudUtf8 character) +bool isBlank(FudUtf8 character) +{ + return impl::isAsciiPredicate(character, isBlank); +} + +bool isPrintable(char character) { - return impl::isAsciiPredicate(character, charIsBlank); + return isPrintable(static_cast(character)); } -bool charIsPrintable(char character) +bool isPrintable(utf8 character) { - if (!charIsAscii(character)) { + if (!isAscii(character)) { return false; } return (character >= ' ' && character <= '~'); } -bool utf8IsPrintable(FudUtf8 character) +bool isPrintable(FudUtf8 character) { - return impl::isAsciiPredicate(character, charIsPrintable); + return impl::isAsciiPredicate(character, isPrintable); } -bool charIsPunctuation(char character) +bool isPunctuation(char character) { - if (!charIsAscii(character)) { + return isPunctuation(static_cast(character)); +} + +bool isPunctuation(utf8 character) +{ + if (!isAscii(character)) { return false; } @@ -270,45 +335,45 @@ bool charIsPunctuation(char character) (character >= '[' && character <= '`') || (character >= '{' && character <= '~'); } -bool utf8IsPunctuation(FudUtf8 character) +bool isPunctuation(FudUtf8 character) { - return impl::isAsciiPredicate(character, charIsPunctuation); + return impl::isAsciiPredicate(character, isPunctuation); } +} // namespace classify + uint8_t charToLower(uint8_t character) { - if (charIsUppercase(static_cast(character))) { + if (classify::isUppercase(static_cast(character))) { constexpr uint8_t lowerA = 'a'; constexpr uint8_t upperA = 'A'; - return static_cast(character - upperA) + lowerA; + return static_cast(character - upperA) + lowerA; } return character; } FudUtf8 utf8ToLower(FudUtf8 character) { - static_cast(character.transformAscii([](Ascii& ascii) { - ascii = Ascii{charToLower(static_cast(ascii.asChar()))}; - })); + static_cast( + character.transformAscii([](Ascii& ascii) { ascii = Ascii{charToLower(static_cast(ascii.asChar()))}; })); return character; } uint8_t charToUpper(uint8_t character) { - if (charIsLowercase(static_cast(character))) { + if (classify::isLowercase(static_cast(character))) { constexpr uint8_t lowerA = 'a'; constexpr uint8_t upperA = 'A'; - return static_cast(character - lowerA) + upperA; + return static_cast(character - lowerA) + upperA; } return character; } FudUtf8 utf8ToUpper(FudUtf8 character) { - static_cast(character.transformAscii([](Ascii& ascii) { - ascii = Ascii{charToUpper(static_cast(ascii.asChar()))}; - })); + static_cast( + character.transformAscii([](Ascii& ascii) { ascii = Ascii{charToUpper(static_cast(ascii.asChar()))}; })); return character; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 515ae16..788e4ba 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -69,6 +69,7 @@ fud_add_test(test_sqlite SOURCES test_sqlite.cpp) fud_add_test(test_string SOURCES test_string.cpp) fud_add_test(test_utf8 SOURCES test_utf8.cpp) fud_add_test(test_vector SOURCES test_vector.cpp) +fud_add_test(test_string_convert SOURCES test_string_convert.cpp) # fud_add_test(test_deserialize_number SOURCES test_deserialize_number.cpp) # fud_add_test(test_ext_algorithm SOURCES test_algorithm.cpp) diff --git a/test/test_format.cpp b/test/test_format.cpp index 319ed22..68c94cd 100644 --- a/test/test_format.cpp +++ b/test/test_format.cpp @@ -15,17 +15,339 @@ * limitations under the License. */ -// #include "fud_array.hpp" -// #include "fud_format.hpp" -// #include "fud_span.hpp" +#include "fud_string_view.hpp" +#include "fud_format.hpp" #include "gtest/gtest.h" namespace fud { -TEST(FormatTest, FormatSpecTest) +TEST(FormatTest, BasePositionalTest) { + size_t length = 0; + auto formatSpecResult = FormatSpec::make(StringView{" {1:}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.takeError(), FudStatus::ArgumentInvalid); + + formatSpecResult = FormatSpec::make(StringView{"{1}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + auto formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(length, 3); + + formatSpecResult = FormatSpec::make(StringView{"{1:}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 4); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Default); + EXPECT_EQ(formatSpec.fill.fill, ' '); + EXPECT_EQ(formatSpec.formatSign, FormatSign::Default); + EXPECT_FALSE(formatSpec.takesWidth); + EXPECT_FALSE(formatSpec.takesPrecision); + EXPECT_FALSE(formatSpec.alternateForm); + EXPECT_FALSE(formatSpec.leadingZero); + EXPECT_FALSE(formatSpec.hasLocale); + EXPECT_TRUE(std::holds_alternative(formatSpec.formatType)); +} + +TEST(FormatTest, AlignTest) +{ + size_t length = 0; + + auto formatSpecResult = FormatSpec::make(StringView{"{1:<}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + auto formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(length, 5); + EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Left); + EXPECT_EQ(formatSpec.fill.fill, ' '); + EXPECT_FALSE(formatSpec.takesWidth); + EXPECT_FALSE(formatSpec.takesPrecision); + EXPECT_FALSE(formatSpec.alternateForm); + EXPECT_FALSE(formatSpec.leadingZero); + EXPECT_FALSE(formatSpec.hasLocale); + EXPECT_TRUE(std::holds_alternative(formatSpec.formatType)); + + formatSpecResult = FormatSpec::make(StringView{"{1:<<}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Left); + EXPECT_EQ(formatSpec.fill.fill, '<'); + + formatSpecResult = FormatSpec::make(StringView{"{:<<}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 5); + EXPECT_EQ(formatSpec.position, FormatSpec::positionUnspecified); + EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Left); + EXPECT_EQ(formatSpec.fill.fill, '<'); + + formatSpecResult = FormatSpec::make(StringView{"{1:_<}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 6); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Left); + EXPECT_EQ(formatSpec.fill.fill, '_'); +} + +TEST(FormatTest, SpecialTest) +{ + size_t length = 0; + + auto formatSpecResult = FormatSpec::make(StringView{"{1:_< }"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + auto formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 7); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Left); + EXPECT_EQ(formatSpec.fill.fill, '_'); + EXPECT_EQ(formatSpec.formatSign, FormatSign::Space); + EXPECT_FALSE(formatSpec.alternateForm); + EXPECT_FALSE(formatSpec.leadingZero); + + formatSpecResult = FormatSpec::make(StringView{"{1:+}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + EXPECT_EQ(length, 5); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(formatSpec.formatSign, FormatSign::Plus); + EXPECT_FALSE(formatSpec.alternateForm); + EXPECT_FALSE(formatSpec.leadingZero); + + formatSpecResult = FormatSpec::make(StringView{"{1:-}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 5); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(formatSpec.formatSign, FormatSign::Minus); + EXPECT_FALSE(formatSpec.alternateForm); + EXPECT_FALSE(formatSpec.leadingZero); + + formatSpecResult = FormatSpec::make(StringView{"{1:_<#}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 7); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Left); + EXPECT_EQ(formatSpec.fill.fill, '_'); + EXPECT_EQ(formatSpec.formatSign, FormatSign::Default); + EXPECT_TRUE(formatSpec.alternateForm); + EXPECT_FALSE(formatSpec.leadingZero); + + formatSpecResult = FormatSpec::make(StringView{"{1:_<0}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 7); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Left); + EXPECT_EQ(formatSpec.fill.fill, '_'); + EXPECT_EQ(formatSpec.formatSign, FormatSign::Default); + EXPECT_FALSE(formatSpec.alternateForm); + EXPECT_TRUE(formatSpec.leadingZero); + + formatSpecResult = FormatSpec::make(StringView{"{1: #}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 6); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Default); + EXPECT_EQ(formatSpec.fill.fill, ' '); + EXPECT_EQ(formatSpec.formatSign, FormatSign::Space); + EXPECT_TRUE(formatSpec.alternateForm); + EXPECT_FALSE(formatSpec.leadingZero); + + formatSpecResult = FormatSpec::make(StringView{"{1:# }"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid); + + formatSpecResult = FormatSpec::make(StringView{"{1:##}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid); + + formatSpecResult = FormatSpec::make(StringView{"{1: 0}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 6); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Default); + EXPECT_EQ(formatSpec.fill.fill, ' '); + EXPECT_EQ(formatSpec.formatSign, FormatSign::Space); + EXPECT_FALSE(formatSpec.alternateForm); + EXPECT_TRUE(formatSpec.leadingZero); + + formatSpecResult = FormatSpec::make(StringView{"{1:0 }"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid); + + formatSpecResult = FormatSpec::make(StringView{"{1:0#}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid); + + formatSpecResult = FormatSpec::make(StringView{"{1:#00}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.takeError(), FudStatus::FormatInvalid); + + formatSpecResult = FormatSpec::make(StringView{"{1: #0}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 7); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_EQ(formatSpec.fill.align(), FormatAlign::Value::Default); + EXPECT_EQ(formatSpec.fill.fill, ' '); + EXPECT_EQ(formatSpec.formatSign, FormatSign::Space); + EXPECT_TRUE(formatSpec.alternateForm); + EXPECT_TRUE(formatSpec.leadingZero); +} + +TEST(FormatTest, WidthTest) +{ + size_t length = 0; + + auto formatSpecResult = FormatSpec::make(StringView{"{1:1}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + auto formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 5); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_FALSE(formatSpec.takesWidth); + EXPECT_EQ(formatSpec.width, 1); + + formatSpecResult = FormatSpec::make(StringView{"{1:543}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 7); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_FALSE(formatSpec.takesWidth); + EXPECT_EQ(formatSpec.width, 543); + + formatSpecResult = FormatSpec::make(StringView{"{:543}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 6); + EXPECT_EQ(formatSpec.position, FormatSpec::positionUnspecified); + EXPECT_FALSE(formatSpec.takesWidth); + EXPECT_EQ(formatSpec.width, 543); + + // leading zero + formatSpecResult = FormatSpec::make(StringView{"{1:00}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.getError(), FudStatus::FormatInvalid); + + // #x100000000 4294967296 + formatSpecResult = FormatSpec::make(StringView{"{1:4294967296}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.getError(), FudStatus::RangeError); + + formatSpecResult = FormatSpec::make(StringView{"{1:{1}}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 7); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_TRUE(formatSpec.takesWidth); + EXPECT_EQ(formatSpec.width, 1); + + // #x10000 65536 + formatSpecResult = FormatSpec::make(StringView{"{1:{65536}}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.getError(), FudStatus::RangeError); + + formatSpecResult = FormatSpec::make(StringView{"{:{1}}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.getError(), FudStatus::FormatInvalid); + + formatSpecResult = FormatSpec::make(StringView{"{:{}}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 5); + EXPECT_EQ(formatSpec.position, FormatSpec::positionUnspecified); + EXPECT_TRUE(formatSpec.takesWidth); + EXPECT_EQ(formatSpec.width, FormatSpec::widthUnspecified); +} + +TEST(FormatTest, PrecisionTest) +{ + size_t length = 0; + + auto formatSpecResult = FormatSpec::make(StringView{"{1:.1}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + auto formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 6); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_FALSE(formatSpec.takesPrecision); + EXPECT_EQ(formatSpec.precision, 1); + + formatSpecResult = FormatSpec::make(StringView{"{1:.543}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 8); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_FALSE(formatSpec.takesPrecision); + EXPECT_EQ(formatSpec.precision, 543); + + formatSpecResult = FormatSpec::make(StringView{"{:.543}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 7); + EXPECT_EQ(formatSpec.position, FormatSpec::positionUnspecified); + EXPECT_FALSE(formatSpec.takesPrecision); + EXPECT_EQ(formatSpec.precision, 543); + + // leading zero + formatSpecResult = FormatSpec::make(StringView{"{1:00}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.getError(), FudStatus::FormatInvalid); + + // #x100000000 4294967296 + formatSpecResult = FormatSpec::make(StringView{"{1:.4294967296}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.getError(), FudStatus::RangeError); + + formatSpecResult = FormatSpec::make(StringView{"{1:.{1}}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 8); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_TRUE(formatSpec.takesPrecision); + EXPECT_EQ(formatSpec.precision, 1); + + // #x10000 65536 + formatSpecResult = FormatSpec::make(StringView{"{1:.{65536}}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.getError(), FudStatus::RangeError); + + formatSpecResult = FormatSpec::make(StringView{"{:.{1}}"}, length); + ASSERT_TRUE(formatSpecResult.isError()); + EXPECT_EQ(formatSpecResult.getError(), FudStatus::FormatInvalid); + + formatSpecResult = FormatSpec::make(StringView{"{:.{}}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 6); + EXPECT_EQ(formatSpec.position, FormatSpec::positionUnspecified); + EXPECT_TRUE(formatSpec.takesPrecision); + EXPECT_EQ(formatSpec.precision, FormatSpec::widthUnspecified); +} + +TEST(FormatTest, LocaleTest) +{ + size_t length = 0; + + auto formatSpecResult = FormatSpec::make(StringView{"{1:L}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + auto formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 5); + EXPECT_EQ(formatSpec.position, 1); + EXPECT_TRUE(formatSpec.hasLocale); + + formatSpecResult = FormatSpec::make(StringView{"{:L}"}, length); + ASSERT_TRUE(formatSpecResult.isOkay()); + formatSpec = formatSpecResult.takeOkay(); + EXPECT_EQ(length, 4); + EXPECT_EQ(formatSpec.position, FormatSpec::positionUnspecified); + EXPECT_TRUE(formatSpec.hasLocale); } } // namespace fud diff --git a/test/test_string_convert.cpp b/test/test_string_convert.cpp new file mode 100644 index 0000000..38f5123 --- /dev/null +++ b/test/test_string_convert.cpp @@ -0,0 +1,186 @@ +/* + * 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_convert.hpp" + +#include "gtest/gtest.h" + +namespace fud { + +TEST(FudStringConvert, ConvertInt) +{ + auto intResult = fromString(StringView{"0"}); + ASSERT_TRUE(intResult.isOkay()); + auto convertState = intResult.getOkay(); + ASSERT_EQ(convertState.value, 0); + ASSERT_EQ(convertState.nextIndex, 1); + + intResult = fromString(StringView{"1"}); + ASSERT_TRUE(intResult.isOkay()); + convertState = intResult.getOkay(); + ASSERT_EQ(convertState.value, 1); + ASSERT_EQ(convertState.nextIndex, 1); + + intResult = fromString(StringView{"-1"}); + ASSERT_TRUE(intResult.isOkay()); + convertState = intResult.getOkay(); + ASSERT_EQ(convertState.value, -1); + ASSERT_EQ(convertState.nextIndex, 2); + + intResult = fromString(StringView{"+1"}); + ASSERT_TRUE(intResult.isOkay()); + convertState = intResult.getOkay(); + ASSERT_EQ(convertState.value, 1); + ASSERT_EQ(convertState.nextIndex, 2); + + intResult = fromString(StringView{" +42"}); + ASSERT_TRUE(intResult.isOkay()); + convertState = intResult.getOkay(); + ASSERT_EQ(convertState.value, 42); + ASSERT_EQ(convertState.nextIndex, 4); + + intResult = fromString(StringView{" +42Q"}); + ASSERT_TRUE(intResult.isOkay()); + convertState = intResult.getOkay(); + ASSERT_EQ(convertState.value, 42); + ASSERT_EQ(convertState.nextIndex, 4); +} + +TEST(FudStringConvert, ConvertUnsigned) +{ + auto unsignedResult = fromString(StringView{"0"}); + ASSERT_TRUE(unsignedResult.isOkay()); + auto convertState = unsignedResult.getOkay(); + ASSERT_EQ(convertState.value, 0); + ASSERT_EQ(convertState.nextIndex, 1); + + unsignedResult = fromString(StringView{"1"}); + ASSERT_TRUE(unsignedResult.isOkay()); + convertState = unsignedResult.getOkay(); + ASSERT_EQ(convertState.value, 1); + ASSERT_EQ(convertState.nextIndex, 1); + + unsignedResult = fromString(StringView{"-1"}); + ASSERT_TRUE(unsignedResult.isError()); + ASSERT_EQ(unsignedResult.getError(), FudStatus::ArgumentInvalid); + + unsignedResult = fromString(StringView{"0xFFFF"}); + ASSERT_TRUE(unsignedResult.isOkay()); + convertState = unsignedResult.getOkay(); + ASSERT_EQ(convertState.value, 0xFFFF); + ASSERT_EQ(convertState.nextIndex, 6); + + unsignedResult = fromString(StringView{"0x10000"}); + ASSERT_TRUE(unsignedResult.isError()); + ASSERT_EQ(unsignedResult.getError(), FudStatus::RangeError); + + unsignedResult = fromString(StringView{"+1"}); + ASSERT_TRUE(unsignedResult.isOkay()); + convertState = unsignedResult.getOkay(); + ASSERT_EQ(convertState.value, 1); + ASSERT_EQ(convertState.nextIndex, 2); + + unsignedResult = fromString(StringView{" +42"}); + ASSERT_TRUE(unsignedResult.isOkay()); + convertState = unsignedResult.getOkay(); + ASSERT_EQ(convertState.value, 42); + ASSERT_EQ(convertState.nextIndex, 4); + + unsignedResult = fromString(StringView{" +42Q"}); + ASSERT_TRUE(unsignedResult.isOkay()); + convertState = unsignedResult.getOkay(); + ASSERT_EQ(convertState.value, 42); + ASSERT_EQ(convertState.nextIndex, 4); +} + +TEST(FudStringConvert, ConvertFloat) +{ + auto floatResult = fromString(StringView{"0"}); + ASSERT_TRUE(floatResult.isOkay()); + auto convertState = floatResult.getOkay(); + ASSERT_EQ(convertState.value, 0.0); + ASSERT_EQ(convertState.nextIndex, 1); + + floatResult = fromString(StringView{"1.0"}); + ASSERT_TRUE(floatResult.isOkay()); + convertState = floatResult.getOkay(); + ASSERT_EQ(convertState.value, 1.0); + ASSERT_EQ(convertState.nextIndex, 3); + + floatResult = fromString(StringView{"1.5"}); + ASSERT_TRUE(floatResult.isOkay()); + convertState = floatResult.getOkay(); + ASSERT_EQ(convertState.value, 1.5); + ASSERT_EQ(convertState.nextIndex, 3); + + floatResult = fromString(StringView{"1.5 next"}); + ASSERT_TRUE(floatResult.isOkay()); + convertState = floatResult.getOkay(); + ASSERT_EQ(convertState.value, 1.5); + ASSERT_EQ(convertState.nextIndex, 3); + + floatResult = fromString(StringView{"-1.0"}); + ASSERT_TRUE(floatResult.isOkay()); + convertState = floatResult.getOkay(); + ASSERT_EQ(convertState.value, -1.0); + ASSERT_EQ(convertState.nextIndex, 4); + + floatResult = fromString(StringView{"-1.5 next"}); + ASSERT_TRUE(floatResult.isOkay()); + convertState = floatResult.getOkay(); + ASSERT_EQ(convertState.value, -1.5); + ASSERT_EQ(convertState.nextIndex, 4); + + floatResult = fromString(StringView{" -1.5 next"}); + ASSERT_TRUE(floatResult.isOkay()); + convertState = floatResult.getOkay(); + ASSERT_EQ(convertState.value, -1.5); + ASSERT_EQ(convertState.nextIndex, 5); + + floatResult = fromString(StringView{" -1.5E+3 next"}); + ASSERT_TRUE(floatResult.isOkay()); + convertState = floatResult.getOkay(); + ASSERT_EQ(convertState.value, -1.5E+3); + ASSERT_EQ(convertState.nextIndex, sizeof(" -1.5E+3") - 1); + + floatResult = fromString(StringView{" -15E+2 next"}); + ASSERT_TRUE(floatResult.isOkay()); + convertState = floatResult.getOkay(); + ASSERT_EQ(convertState.value, -1.5E+3); + ASSERT_EQ(convertState.nextIndex, sizeof(" -15E+2") - 1); + + floatResult = fromString(StringView{" -1EA next"}); + ASSERT_TRUE(floatResult.isError()); + ASSERT_EQ(floatResult.getError(), FudStatus::ArgumentInvalid); + + floatResult = fromString(StringView{" -1.5EA next"}); + ASSERT_TRUE(floatResult.isError()); + ASSERT_EQ(floatResult.getError(), FudStatus::ArgumentInvalid); + + floatResult = fromString(StringView{" -1.5EQ next"}); + ASSERT_TRUE(floatResult.isError()); + ASSERT_EQ(floatResult.getError(), FudStatus::ArgumentInvalid); + + floatResult = fromString(StringView{" -1.5E3Q next"}); + ASSERT_TRUE(floatResult.isOkay()); + convertState = floatResult.getOkay(); + ASSERT_EQ(convertState.value, -1.5E3); + ASSERT_EQ(convertState.nextIndex, sizeof(" -1.5E3") - 1); +} + +} // namespace fud diff --git a/test/test_utf8.cpp b/test/test_utf8.cpp index d1737f7..4b57b64 100644 --- a/test/test_utf8.cpp +++ b/test/test_utf8.cpp @@ -30,6 +30,35 @@ namespace fud { +using classify::CharPredicate; +using classify::FudUtf8Predicate; +using classify::isAscii; +using classify::isAscii; +using classify:: isAlphanumeric; +using classify:: isAlphanumeric; +using classify:: isAlpha; +using classify:: isAlpha; +using classify:: isLowercase; +using classify:: isLowercase; +using classify:: isUppercase; +using classify:: isUppercase; +using classify:: isDigit; +using classify:: isDigit; +using classify:: isHexDigit; +using classify:: isHexDigit; +using classify:: isControl; +using classify:: isControl; +using classify:: isGraphical; +using classify:: isGraphical; +using classify:: isSpace; +using classify:: isSpace; +using classify:: isBlank; +using classify:: isBlank; +using classify:: isPrintable; +using classify:: isPrintable; +using classify:: isPunctuation; +using classify:: isPunctuation; + constexpr size_t validAsciiSize = INT8_MAX + 1; constexpr size_t invalidAsciiSize = UINT8_MAX + 1 - validAsciiSize; @@ -194,24 +223,24 @@ TEST(Utf8Test, Utf8MultiByte) TEST(Utf8Test, Utf8IsAscii) { - ASSERT_FALSE(charIsAscii(invalidAscii)); + ASSERT_FALSE(isAscii(invalidAscii)); Iota charIota{0, 1, validAsciiSize}; - ASSERT_TRUE(allOf([&]() -> Option { return charIota().map(toLetter); }, charIsAscii)); + ASSERT_TRUE(allOf([&]() -> Option { return charIota().map(toLetter); }, static_cast(isAscii))); Iota invalidCharIota{validAsciiSize, 1, invalidAsciiSize}; - ASSERT_FALSE(anyOf([&]() -> Option { return invalidCharIota().map(toLetter); }, charIsAscii)); + ASSERT_FALSE(anyOf([&]() -> Option { return invalidCharIota().map(toLetter); }, static_cast(isAscii))); FudUtf8 unicode{FudUtf8::invalidAscii()}; - ASSERT_FALSE(utf8IsAscii(unicode)); + ASSERT_FALSE(isAscii(unicode)); charIota.set(0); - ASSERT_TRUE(allOf([&]() -> Option { return charIota().map(toUtf8); }, utf8IsAscii)); + ASSERT_TRUE(allOf([&]() -> Option { return charIota().map(toUtf8); }, static_cast(isAscii))); invalidCharIota.set(invalidAsciiSize); - ASSERT_FALSE(anyOf([&]() -> Option { return invalidCharIota().map(toUtf8); }, utf8IsAscii)); + ASSERT_FALSE(anyOf([&]() -> Option { return invalidCharIota().map(toUtf8); }, static_cast(isAscii))); } template @@ -241,22 +270,22 @@ TEST(Utf8Test, Utf8IsAlphanumeric) Array alphanumericChars{}; copyMem(alphanumericChars, alphanumericCharLiteral); - ASSERT_TRUE(allOf(alphanumericChars.span(), charIsAlphanumeric)); + ASSERT_TRUE(allOf(alphanumericChars.span(), static_cast(isAlphanumeric))); constexpr size_t numNonAlphanumericChars = validAsciiSize - numAlphanumericChars; Vector nonAlphanumericChars{}; for (char idx = 0; idx < INT8_MAX; ++idx) { - if (!charIsAlphanumeric(idx)) { + if (!isAlphanumeric(idx)) { ASSERT_EQ(nonAlphanumericChars.pushBack(idx), FudStatus::Success); } } auto nonAlphanumericSpan{nonAlphanumericChars.span().takeOkay()}; - ASSERT_FALSE(anyOf(nonAlphanumericSpan, charIsAlphanumeric)); + ASSERT_FALSE(anyOf(nonAlphanumericSpan, static_cast(isAlphanumeric))); auto invalidAsciiChars = invalidAsciiGenerator(); - ASSERT_FALSE(anyOf(invalidAsciiChars, charIsAlphanumeric)); + ASSERT_FALSE(anyOf(invalidAsciiChars, static_cast(isAlphanumeric))); - ASSERT_FALSE(utf8IsAlphanumeric(FudUtf8{Ascii{invalidAscii}})); + ASSERT_FALSE(isAlphanumeric(FudUtf8{Ascii{invalidAscii}})); auto iotaGenerator = invalidAsciiGenerator(); auto generator = generate( @@ -265,11 +294,11 @@ TEST(Utf8Test, Utf8IsAlphanumeric) SpanGenerator alphanumericGenerator{alphanumericChars.span()}; auto utf8AlphanumericGenerator = [&]() { return alphanumericGenerator().map(toUtf8); }; - ASSERT_TRUE(allOf(utf8AlphanumericGenerator, utf8IsAlphanumeric)); + ASSERT_TRUE(allOf(utf8AlphanumericGenerator, static_cast(isAlphanumeric))); SpanGenerator nonAlphanumericGenerator{nonAlphanumericChars.span().takeOkay()}; auto utf8NonAlphanumericGenerator = [&]() { return nonAlphanumericGenerator().map(toUtf8); }; - ASSERT_FALSE(anyOf(utf8NonAlphanumericGenerator, utf8IsAlphanumeric)); + ASSERT_FALSE(anyOf(utf8NonAlphanumericGenerator, static_cast(isAlphanumeric))); } TEST(Utf8Test, Utf8IsAlpha) @@ -279,29 +308,29 @@ TEST(Utf8Test, Utf8IsAlpha) Array alphaChars{}; copyMem(alphaChars, alphaCharLiteral); - ASSERT_TRUE(allOf(alphaChars.span(), charIsAlpha)); + ASSERT_TRUE(allOf(alphaChars.span(), static_cast(isAlpha))); constexpr size_t numNonAlphanumericChars = validAsciiSize - numAlphaChars; Vector nonAlphaChars{}; for (char idx = 0; idx < INT8_MAX; ++idx) { - if (!charIsAlphanumeric(idx)) { + if (!isAlphanumeric(idx)) { ASSERT_EQ(nonAlphaChars.pushBack(idx), FudStatus::Success); } } - ASSERT_FALSE(anyOf(nonAlphaChars.span().takeOkay(), charIsAlpha)); + ASSERT_FALSE(anyOf(nonAlphaChars.span().takeOkay(), static_cast(isAlpha))); auto invalidAsciiChars = invalidAsciiGenerator(); - ASSERT_FALSE(anyOf(invalidAsciiChars, charIsAlpha)); + ASSERT_FALSE(anyOf(invalidAsciiChars, static_cast(isAlpha))); - ASSERT_FALSE(utf8IsAlpha(FudUtf8{Ascii{invalidAscii}})); + ASSERT_FALSE(isAlpha(FudUtf8{Ascii{invalidAscii}})); SpanGenerator alphaGenerator{alphaChars.span()}; auto utf8AlphaGenerator = [&]() { return alphaGenerator().map(toUtf8); }; - ASSERT_TRUE(allOf(utf8AlphaGenerator, utf8IsAlpha)); + ASSERT_TRUE(allOf(utf8AlphaGenerator, static_cast(isAlpha))); SpanGenerator nonAlphaGenerator{nonAlphaChars.span().takeOkay()}; auto utf8NonAlphaGenerator = [&]() { return nonAlphaGenerator().map(toUtf8); }; - ASSERT_FALSE(anyOf(utf8NonAlphaGenerator, utf8IsAlpha)); + ASSERT_FALSE(anyOf(utf8NonAlphaGenerator, static_cast(isAlpha))); } TEST(Utf8Test, Utf8IsLower) @@ -311,29 +340,29 @@ TEST(Utf8Test, Utf8IsLower) Array lowerChars{}; copyMem(lowerChars, lowerCharLiteral); - ASSERT_TRUE(allOf(lowerChars.span(), charIsLowercase)); + ASSERT_TRUE(allOf(lowerChars.span(), static_cast(isLowercase))); constexpr size_t numNonLowerChars = validAsciiSize - numLowerChars; Vector nonLowerChars{}; for (char idx = 0; idx < INT8_MAX; ++idx) { - if (!charIsLowercase(idx)) { + if (!isLowercase(idx)) { ASSERT_EQ(nonLowerChars.pushBack(idx), FudStatus::Success); } } - ASSERT_FALSE(anyOf(nonLowerChars.span().takeOkay(), charIsLowercase)); + ASSERT_FALSE(anyOf(nonLowerChars.span().takeOkay(), static_cast(isLowercase))); auto invalidAsciiChars = invalidAsciiGenerator(); - ASSERT_FALSE(anyOf(invalidAsciiChars, charIsLowercase)); + ASSERT_FALSE(anyOf(invalidAsciiChars, static_cast(isLowercase))); - ASSERT_FALSE(utf8IsLowercase(FudUtf8{Ascii{invalidAscii}})); + ASSERT_FALSE(isLowercase(FudUtf8{Ascii{invalidAscii}})); SpanGenerator lowerGenerator{lowerChars.span()}; auto utf8LowerGenerator = [&]() { return lowerGenerator().map(toUtf8); }; - ASSERT_TRUE(allOf(utf8LowerGenerator, utf8IsLowercase)); + ASSERT_TRUE(allOf(utf8LowerGenerator, static_cast(isLowercase))); SpanGenerator nonLowerGenerator{nonLowerChars.span().takeOkay()}; auto utf8NonLowerGenerator = [&]() { return nonLowerGenerator().map(toUtf8); }; - ASSERT_FALSE(anyOf(utf8NonLowerGenerator, utf8IsLowercase)); + ASSERT_FALSE(anyOf(utf8NonLowerGenerator, static_cast(isLowercase))); } TEST(Utf8Test, Utf8IsUpper) @@ -343,29 +372,29 @@ TEST(Utf8Test, Utf8IsUpper) Array upperChars{}; copyMem(upperChars, upperCharLiteral); - ASSERT_TRUE(allOf(upperChars.span(), charIsUppercase)); + ASSERT_TRUE(allOf(upperChars.span(), static_cast(isUppercase))); constexpr size_t numNonUpperChars = validAsciiSize - numUpperChars; Vector nonUpperChars{}; for (char idx = 0; idx < INT8_MAX; ++idx) { - if (!charIsUppercase(idx)) { + if (!isUppercase(idx)) { ASSERT_EQ(nonUpperChars.pushBack(idx), FudStatus::Success); } } - ASSERT_FALSE(anyOf(nonUpperChars.span().takeOkay(), charIsUppercase)); + ASSERT_FALSE(anyOf(nonUpperChars.span().takeOkay(), static_cast(isUppercase))); auto invalidAsciiChars = invalidAsciiGenerator(); - ASSERT_FALSE(anyOf(invalidAsciiChars, charIsUppercase)); + ASSERT_FALSE(anyOf(invalidAsciiChars, static_cast(isUppercase))); - ASSERT_FALSE(utf8IsUppercase(FudUtf8{Ascii{invalidAscii}})); + ASSERT_FALSE(isUppercase(FudUtf8{Ascii{invalidAscii}})); SpanGenerator upperGenerator{upperChars.span()}; auto utf8UpperGenerator = [&]() { return upperGenerator().map(toUtf8); }; - ASSERT_TRUE(allOf(utf8UpperGenerator, utf8IsUppercase)); + ASSERT_TRUE(allOf(utf8UpperGenerator, static_cast(isUppercase))); SpanGenerator nonUpperGenerator{nonUpperChars.span().takeOkay()}; auto utf8NonUpperGenerator = [&]() { return nonUpperGenerator().map(toUtf8); }; - ASSERT_FALSE(anyOf(utf8NonUpperGenerator, utf8IsUppercase)); + ASSERT_FALSE(anyOf(utf8NonUpperGenerator, static_cast(isUppercase))); } TEST(Utf8Test, Utf8IsDigit) @@ -375,29 +404,29 @@ TEST(Utf8Test, Utf8IsDigit) Array digitChars{}; copyMem(digitChars, digitCharLiteral); - ASSERT_TRUE(allOf(digitChars.span(), charIsDigit)); + ASSERT_TRUE(allOf(digitChars.span(), static_cast(isDigit))); constexpr size_t numNonDigitChars = validAsciiSize - numDigitChars; Vector nonDigitChars{}; for (char idx = 0; idx < INT8_MAX; ++idx) { - if (!charIsDigit(idx)) { + if (!isDigit(idx)) { ASSERT_EQ(nonDigitChars.pushBack(idx), FudStatus::Success); } } - ASSERT_FALSE(anyOf(nonDigitChars.span().takeOkay(), charIsDigit)); + ASSERT_FALSE(anyOf(nonDigitChars.span().takeOkay(), static_cast(isDigit))); auto invalidAsciiChars = invalidAsciiGenerator(); - ASSERT_FALSE(anyOf(invalidAsciiChars, charIsDigit)); + ASSERT_FALSE(anyOf(invalidAsciiChars, static_cast(isDigit))); - ASSERT_FALSE(utf8IsDigit(FudUtf8{Ascii{invalidAscii}})); + ASSERT_FALSE(isDigit(FudUtf8{Ascii{invalidAscii}})); SpanGenerator digitGenerator{digitChars.span()}; auto utf8DigitGenerator = [&]() { return digitGenerator().map(toUtf8); }; - ASSERT_TRUE(allOf(utf8DigitGenerator, utf8IsDigit)); + ASSERT_TRUE(allOf(utf8DigitGenerator, static_cast(isDigit))); SpanGenerator nonDigitGenerator{nonDigitChars.span().takeOkay()}; auto utf8NonDigitGenerator = [&]() { return nonDigitGenerator().map(toUtf8); }; - ASSERT_FALSE(anyOf(utf8NonDigitGenerator, utf8IsDigit)); + ASSERT_FALSE(anyOf(utf8NonDigitGenerator, static_cast(isDigit))); } TEST(Utf8Test, Utf8IsHexDigit) @@ -407,29 +436,29 @@ TEST(Utf8Test, Utf8IsHexDigit) Array hexDigitChars{}; copyMem(hexDigitChars, hexDigitCharLiteral); - ASSERT_TRUE(allOf(hexDigitChars.span(), charIsHexDigit)); + ASSERT_TRUE(allOf(hexDigitChars.span(), static_cast(isHexDigit))); constexpr size_t numNonHexDigitChars = validAsciiSize - numHexDigitChars; Vector nonHexDigitChars{}; for (char idx = 0; idx < INT8_MAX; ++idx) { - if (!charIsHexDigit(idx)) { + if (!isHexDigit(idx)) { ASSERT_EQ(nonHexDigitChars.pushBack(idx), FudStatus::Success); } } - ASSERT_FALSE(anyOf(nonHexDigitChars.span().takeOkay(), charIsHexDigit)); + ASSERT_FALSE(anyOf(nonHexDigitChars.span().takeOkay(), static_cast(isHexDigit))); auto invalidAsciiChars = invalidAsciiGenerator(); - ASSERT_FALSE(anyOf(invalidAsciiChars, charIsHexDigit)); + ASSERT_FALSE(anyOf(invalidAsciiChars, static_cast(isHexDigit))); - ASSERT_FALSE(utf8IsHexDigit(FudUtf8{Ascii{invalidAscii}})); + ASSERT_FALSE(isHexDigit(FudUtf8{Ascii{invalidAscii}})); SpanGenerator hexDigitGenerator{hexDigitChars.span()}; auto utf8HexDigitGenerator = [&]() { return hexDigitGenerator().map(toUtf8); }; - ASSERT_TRUE(allOf(utf8HexDigitGenerator, utf8IsHexDigit)); + ASSERT_TRUE(allOf(utf8HexDigitGenerator, static_cast(isHexDigit))); SpanGenerator nonHexDigitGenerator{nonHexDigitChars.span().takeOkay()}; auto utf8NonHexDigitGenerator = [&]() { return nonHexDigitGenerator().map(toUtf8); }; - ASSERT_FALSE(anyOf(utf8NonHexDigitGenerator, utf8IsHexDigit)); + ASSERT_FALSE(anyOf(utf8NonHexDigitGenerator, static_cast(isHexDigit))); } TEST(Utf8Test, Utf8IsControl) @@ -439,7 +468,7 @@ TEST(Utf8Test, Utf8IsControl) constexpr const char deleteChar = 0x7F; controlChars.back() = deleteChar; - ASSERT_TRUE(allOf(controlChars.span(), charIsControl)); + ASSERT_TRUE(allOf(controlChars.span(), static_cast(isControl))); constexpr size_t numNonControlChars = INT8_MAX + 1 - numControlChars; Vector nonControlChars{}; @@ -447,21 +476,21 @@ TEST(Utf8Test, Utf8IsControl) for (auto idx = numControlChars - 1; idx < deleteChar; ++idx) { ASSERT_EQ(nonControlChars.pushBack(idx), FudStatus::Success); } - ASSERT_FALSE(anyOf(nonControlChars.span().takeOkay(), charIsControl)); - ASSERT_TRUE(allOf(nonControlChars.span().takeOkay(), charIsAscii)); + ASSERT_FALSE(anyOf(nonControlChars.span().takeOkay(), static_cast(isControl))); + ASSERT_TRUE(allOf(nonControlChars.span().takeOkay(), static_cast(isAscii))); auto invalidAsciiChars = invalidAsciiGenerator(); - ASSERT_FALSE(anyOf(invalidAsciiChars, charIsControl)); + ASSERT_FALSE(anyOf(invalidAsciiChars, static_cast(isControl))); - ASSERT_FALSE(utf8IsControl(FudUtf8{Ascii{invalidAscii}})); + ASSERT_FALSE(isControl(FudUtf8{Ascii{invalidAscii}})); SpanGenerator controlGenerator{controlChars.span()}; auto utf8ControlGenerator = [&]() { return controlGenerator().map(toUtf8); }; - ASSERT_TRUE(allOf(utf8ControlGenerator, utf8IsControl)); + ASSERT_TRUE(allOf(utf8ControlGenerator, static_cast(isControl))); SpanGenerator nonControlGenerator{nonControlChars.span().takeOkay()}; auto utf8NonControlGenerator = [&]() { return nonControlGenerator().map(toUtf8); }; - ASSERT_FALSE(anyOf(utf8NonControlGenerator, utf8IsControl)); + ASSERT_FALSE(anyOf(utf8NonControlGenerator, static_cast(isControl))); } TEST(Utf8Test, Utf8IsGraphical) @@ -471,32 +500,32 @@ TEST(Utf8Test, Utf8IsGraphical) Array graphicalChars{}; copyMem(graphicalChars, graphicalCharLiteral); - ASSERT_TRUE(allOf(graphicalChars.span(), charIsGraphical)); + ASSERT_TRUE(allOf(graphicalChars.span(), static_cast(isGraphical))); constexpr size_t numNonGraphicalChars = validAsciiSize - numGraphicalChars; Vector nonGraphicalChars{}; ASSERT_EQ(nonGraphicalChars.reserve(numNonGraphicalChars), FudStatus::Success); for (uint8_t idx = 0; idx < INT8_MAX + 1; ++idx) { - if (!charIsGraphical(static_cast(idx))) { + if (!isGraphical(static_cast(idx))) { ASSERT_EQ(nonGraphicalChars.pushBack(static_cast(idx)), FudStatus::Success); } } - ASSERT_FALSE(anyOf(nonGraphicalChars.span().takeOkay(), charIsGraphical)); - ASSERT_TRUE(allOf(nonGraphicalChars.span().takeOkay(), charIsAscii)); + ASSERT_FALSE(anyOf(nonGraphicalChars.span().takeOkay(), static_cast(isGraphical))); + ASSERT_TRUE(allOf(nonGraphicalChars.span().takeOkay(), static_cast(isAscii))); ASSERT_EQ(nonGraphicalChars.size() + graphicalChars.size(), INT8_MAX + 1); auto invalidAsciiChars = invalidAsciiGenerator(); - ASSERT_FALSE(anyOf(invalidAsciiChars, charIsGraphical)); + ASSERT_FALSE(anyOf(invalidAsciiChars, static_cast(isGraphical))); - ASSERT_FALSE(utf8IsGraphical(FudUtf8{Ascii{invalidAscii}})); + ASSERT_FALSE(isGraphical(FudUtf8{Ascii{invalidAscii}})); SpanGenerator graphicalGenerator{graphicalChars.span()}; auto utf8GraphicalGenerator = [&]() { return graphicalGenerator().map(toUtf8); }; - ASSERT_TRUE(allOf(utf8GraphicalGenerator, utf8IsGraphical)); + ASSERT_TRUE(allOf(utf8GraphicalGenerator, static_cast(isGraphical))); SpanGenerator nonGraphicalGenerator{nonGraphicalChars.span().takeOkay()}; auto utf8NonGraphicalGenerator = [&]() { return nonGraphicalGenerator().map(toUtf8); }; - ASSERT_FALSE(anyOf(utf8NonGraphicalGenerator, utf8IsGraphical)); + ASSERT_FALSE(anyOf(utf8NonGraphicalGenerator, static_cast(isGraphical))); } TEST(Utf8Test, Utf8IsSpace) @@ -506,32 +535,32 @@ TEST(Utf8Test, Utf8IsSpace) Array spaceChars{}; copyMem(spaceChars, spaceCharLiteral); - ASSERT_TRUE(allOf(spaceChars.span(), charIsSpace)); + ASSERT_TRUE(allOf(spaceChars.span(), static_cast(isSpace))); constexpr size_t numNonSpaceChars = validAsciiSize - numSpaceChars; Vector nonSpaceChars{}; ASSERT_EQ(nonSpaceChars.reserve(numNonSpaceChars), FudStatus::Success); for (uint8_t idx = 0; idx < INT8_MAX + 1; ++idx) { - if (!charIsSpace(static_cast(idx))) { + if (!isSpace(static_cast(idx))) { ASSERT_EQ(nonSpaceChars.pushBack(static_cast(idx)), FudStatus::Success); } } - ASSERT_FALSE(anyOf(nonSpaceChars.span().takeOkay(), charIsSpace)); - ASSERT_TRUE(allOf(nonSpaceChars.span().takeOkay(), charIsAscii)); + ASSERT_FALSE(anyOf(nonSpaceChars.span().takeOkay(), static_cast(isSpace))); + ASSERT_TRUE(allOf(nonSpaceChars.span().takeOkay(), static_cast(isAscii))); ASSERT_EQ(nonSpaceChars.size() + spaceChars.size(), INT8_MAX + 1); auto invalidAsciiChars = invalidAsciiGenerator(); - ASSERT_FALSE(anyOf(invalidAsciiChars, charIsSpace)); + ASSERT_FALSE(anyOf(invalidAsciiChars, static_cast(isSpace))); - ASSERT_FALSE(utf8IsSpace(FudUtf8{Ascii{invalidAscii}})); + ASSERT_FALSE(isSpace(FudUtf8{Ascii{invalidAscii}})); SpanGenerator spaceGenerator{spaceChars.span()}; auto utf8SpaceGenerator = [&]() { return spaceGenerator().map(toUtf8); }; - ASSERT_TRUE(allOf(utf8SpaceGenerator, utf8IsSpace)); + ASSERT_TRUE(allOf(utf8SpaceGenerator, static_cast(isSpace))); SpanGenerator nonSpaceGenerator{nonSpaceChars.span().takeOkay()}; auto utf8NonSpaceGenerator = [&]() { return nonSpaceGenerator().map(toUtf8); }; - ASSERT_FALSE(anyOf(utf8NonSpaceGenerator, utf8IsSpace)); + ASSERT_FALSE(anyOf(utf8NonSpaceGenerator, static_cast(isSpace))); } TEST(Utf8Test, Utf8IsBlank) @@ -541,32 +570,32 @@ TEST(Utf8Test, Utf8IsBlank) Array blankChars{}; copyMem(blankChars, blankCharLiteral); - ASSERT_TRUE(allOf(blankChars.span(), charIsBlank)); + ASSERT_TRUE(allOf(blankChars.span(), static_cast(isBlank))); constexpr size_t numNonBlankChars = validAsciiSize - numBlankChars; Vector nonBlankChars{}; ASSERT_EQ(nonBlankChars.reserve(numNonBlankChars), FudStatus::Success); for (uint8_t idx = 0; idx < INT8_MAX + 1; ++idx) { - if (!charIsBlank(static_cast(idx))) { + if (!isBlank(static_cast(idx))) { ASSERT_EQ(nonBlankChars.pushBack(static_cast(idx)), FudStatus::Success); } } - ASSERT_FALSE(anyOf(nonBlankChars.span().takeOkay(), charIsBlank)); - ASSERT_TRUE(allOf(nonBlankChars.span().takeOkay(), charIsAscii)); + ASSERT_FALSE(anyOf(nonBlankChars.span().takeOkay(), static_cast(isBlank))); + ASSERT_TRUE(allOf(nonBlankChars.span().takeOkay(), static_cast(isAscii))); ASSERT_EQ(nonBlankChars.size() + blankChars.size(), INT8_MAX + 1); auto invalidAsciiChars = invalidAsciiGenerator(); - ASSERT_FALSE(anyOf(invalidAsciiChars, charIsBlank)); + ASSERT_FALSE(anyOf(invalidAsciiChars, static_cast(isBlank))); - ASSERT_FALSE(utf8IsBlank(FudUtf8{Ascii{invalidAscii}})); + ASSERT_FALSE(isBlank(FudUtf8{Ascii{invalidAscii}})); SpanGenerator blankGenerator{blankChars.span()}; auto utf8BlankGenerator = [&]() { return blankGenerator().map(toUtf8); }; - ASSERT_TRUE(allOf(utf8BlankGenerator, utf8IsBlank)); + ASSERT_TRUE(allOf(utf8BlankGenerator, static_cast(isBlank))); SpanGenerator nonBlankGenerator{nonBlankChars.span().takeOkay()}; auto utf8NonBlankGenerator = [&]() { return nonBlankGenerator().map(toUtf8); }; - ASSERT_FALSE(anyOf(utf8NonBlankGenerator, utf8IsBlank)); + ASSERT_FALSE(anyOf(utf8NonBlankGenerator, static_cast(isBlank))); } TEST(Utf8Test, Utf8IsPrintable) @@ -576,32 +605,32 @@ TEST(Utf8Test, Utf8IsPrintable) Array printableChars{}; copyMem(printableChars, printableCharLiteral); - ASSERT_TRUE(allOf(printableChars.span(), charIsPrintable)); + ASSERT_TRUE(allOf(printableChars.span(), static_cast(isPrintable))); constexpr size_t numNonPrintableChars = validAsciiSize - numPrintableChars; Vector nonPrintableChars{}; ASSERT_EQ(nonPrintableChars.reserve(numNonPrintableChars), FudStatus::Success); for (uint8_t idx = 0; idx < INT8_MAX + 1; ++idx) { - if (!charIsPrintable(static_cast(idx))) { + if (!isPrintable(static_cast(idx))) { ASSERT_EQ(nonPrintableChars.pushBack(static_cast(idx)), FudStatus::Success); } } - ASSERT_FALSE(anyOf(nonPrintableChars.span().takeOkay(), charIsPrintable)); - ASSERT_TRUE(allOf(nonPrintableChars.span().takeOkay(), charIsAscii)); + ASSERT_FALSE(anyOf(nonPrintableChars.span().takeOkay(), static_cast(isPrintable))); + ASSERT_TRUE(allOf(nonPrintableChars.span().takeOkay(), static_cast(isAscii))); ASSERT_EQ(nonPrintableChars.size() + printableChars.size(), INT8_MAX + 1); auto invalidAsciiChars = invalidAsciiGenerator(); - ASSERT_FALSE(anyOf(invalidAsciiChars, charIsPrintable)); + ASSERT_FALSE(anyOf(invalidAsciiChars, static_cast(isPrintable))); - ASSERT_FALSE(utf8IsPrintable(FudUtf8{Ascii{invalidAscii}})); + ASSERT_FALSE(isPrintable(FudUtf8{Ascii{invalidAscii}})); SpanGenerator printableGenerator{printableChars.span()}; auto utf8PrintableGenerator = [&]() { return printableGenerator().map(toUtf8); }; - ASSERT_TRUE(allOf(utf8PrintableGenerator, utf8IsPrintable)); + ASSERT_TRUE(allOf(utf8PrintableGenerator, static_cast(isPrintable))); SpanGenerator nonPrintableGenerator{nonPrintableChars.span().takeOkay()}; auto utf8NonPrintableGenerator = [&]() { return nonPrintableGenerator().map(toUtf8); }; - ASSERT_FALSE(anyOf(utf8NonPrintableGenerator, utf8IsPrintable)); + ASSERT_FALSE(anyOf(utf8NonPrintableGenerator, static_cast(isPrintable))); } TEST(Utf8Test, Utf8IsPunctuation) @@ -611,32 +640,32 @@ TEST(Utf8Test, Utf8IsPunctuation) Array punctuationChars{}; copyMem(punctuationChars, punctuationCharLiteral); - ASSERT_TRUE(allOf(punctuationChars.span(), charIsPunctuation)); + ASSERT_TRUE(allOf(punctuationChars.span(), static_cast(isPunctuation))); constexpr size_t numNonPunctuationChars = validAsciiSize - numPunctuationChars; Vector nonPunctuationChars{}; ASSERT_EQ(nonPunctuationChars.reserve(numNonPunctuationChars), FudStatus::Success); for (uint8_t idx = 0; idx < INT8_MAX + 1; ++idx) { - if (!charIsPunctuation(static_cast(idx))) { + if (!isPunctuation(static_cast(idx))) { ASSERT_EQ(nonPunctuationChars.pushBack(static_cast(idx)), FudStatus::Success); } } - ASSERT_FALSE(anyOf(nonPunctuationChars.span().takeOkay(), charIsPunctuation)); - ASSERT_TRUE(allOf(nonPunctuationChars.span().takeOkay(), charIsAscii)); + ASSERT_FALSE(anyOf(nonPunctuationChars.span().takeOkay(), static_cast(isPunctuation))); + ASSERT_TRUE(allOf(nonPunctuationChars.span().takeOkay(), static_cast(isAscii))); ASSERT_EQ(nonPunctuationChars.size() + punctuationChars.size(), INT8_MAX + 1); auto invalidAsciiChars = invalidAsciiGenerator(); - ASSERT_FALSE(anyOf(invalidAsciiChars, charIsPunctuation)); + ASSERT_FALSE(anyOf(invalidAsciiChars, static_cast(isPunctuation))); - ASSERT_FALSE(utf8IsPunctuation(FudUtf8{Ascii{invalidAscii}})); + ASSERT_FALSE(isPunctuation(FudUtf8{Ascii{invalidAscii}})); SpanGenerator punctuationGenerator{punctuationChars.span()}; auto utf8PunctuationGenerator = [&]() { return punctuationGenerator().map(toUtf8); }; - ASSERT_TRUE(allOf(utf8PunctuationGenerator, utf8IsPunctuation)); + ASSERT_TRUE(allOf(utf8PunctuationGenerator, static_cast(isPunctuation))); SpanGenerator nonPunctuationGenerator{nonPunctuationChars.span().takeOkay()}; auto utf8NonPunctuationGenerator = [&]() { return nonPunctuationGenerator().map(toUtf8); }; - ASSERT_FALSE(anyOf(utf8NonPunctuationGenerator, utf8IsPunctuation)); + ASSERT_FALSE(anyOf(utf8NonPunctuationGenerator, static_cast(isPunctuation))); } } // namespace fud diff --git a/tools/coverage.sh b/tools/coverage.sh index 95eef03..b41c66e 100755 --- a/tools/coverage.sh +++ b/tools/coverage.sh @@ -1,4 +1,5 @@ #!/bin/sh +set -e PROJ_ROOT=$(git rev-parse --show-toplevel) cd $PROJ_ROOT diff --git a/tools/create-pyenv.sh b/tools/create-pyenv.sh index 64b1fbf..2c6d9d0 100755 --- a/tools/create-pyenv.sh +++ b/tools/create-pyenv.sh @@ -1,4 +1,5 @@ #!/bin/sh +set -e PROJ_ROOT=$(git rev-parse --show-toplevel) cd $PROJ_ROOT diff --git a/tools/run-cppcheck.sh b/tools/run-cppcheck.sh index 1f8ee7d..315eff4 100755 --- a/tools/run-cppcheck.sh +++ b/tools/run-cppcheck.sh @@ -14,6 +14,7 @@ # 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. +set -e PROJ_ROOT=$(git rev-parse --show-toplevel) cd $PROJ_ROOT -- cgit v1.2.3