diff options
author | Yuqian Yang <crupest@crupest.life> | 2025-10-17 14:06:48 +0800 |
---|---|---|
committer | Yuqian Yang <crupest@crupest.life> | 2025-10-17 14:06:48 +0800 |
commit | 5c5c496b605886b286d1b99e0f9e28ec02117ad5 (patch) | |
tree | c22b07e81ba179d7cc8790656abddbcc56b5d704 | |
parent | 32aa6f116acc6e3e20a1ec76cef45b29f7005ad7 (diff) | |
download | cru-5c5c496b605886b286d1b99e0f9e28ec02117ad5.tar.gz cru-5c5c496b605886b286d1b99e0f9e28ec02117ad5.tar.bz2 cru-5c5c496b605886b286d1b99e0f9e28ec02117ad5.zip |
Use std::from_chars.
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | include/cru/base/String.h | 47 | ||||
-rw-r--r-- | include/cru/base/StringToNumberConverter.h | 106 | ||||
-rw-r--r-- | include/cru/base/StringUtil.h | 88 | ||||
m--------- | lib/double-conversion | 0 | ||||
-rw-r--r-- | src/base/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/base/String.cpp | 121 | ||||
-rw-r--r-- | src/base/StringToNumberConverter.cpp | 170 | ||||
-rw-r--r-- | src/platform/graphics/Geometry.cpp | 14 | ||||
-rw-r--r-- | src/ui/components/Input.cpp | 23 | ||||
-rw-r--r-- | src/ui/mapper/ColorMapper.cpp | 6 | ||||
-rw-r--r-- | src/ui/mapper/FontMapper.cpp | 4 | ||||
-rw-r--r-- | src/ui/mapper/MeasureLengthMapper.cpp | 2 | ||||
-rw-r--r-- | src/ui/mapper/PointMapper.cpp | 5 | ||||
-rw-r--r-- | src/ui/mapper/SizeMapper.cpp | 5 | ||||
-rw-r--r-- | src/ui/mapper/ThicknessMapper.cpp | 4 | ||||
-rw-r--r-- | test/base/CMakeLists.txt | 1 | ||||
-rw-r--r-- | test/base/StringTest.cpp | 26 | ||||
-rw-r--r-- | test/base/StringToNumberConverterTest.cpp | 166 | ||||
-rw-r--r-- | test/base/StringUtilTest.cpp | 112 |
21 files changed, 231 insertions, 676 deletions
diff --git a/.gitmodules b/.gitmodules index c87b5454..d49fb062 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "lib/double-conversion"] - path = lib/double-conversion - url = https://github.com/google/double-conversion.git [submodule "lib/Catch2"] path = lib/Catch2 url = https://github.com/catchorg/Catch2.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 6da50c5b..980ff1ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,6 @@ if (APPLE) endif() add_subdirectory(lib/Catch2) -add_subdirectory(lib/double-conversion) enable_testing() diff --git a/include/cru/base/String.h b/include/cru/base/String.h index 7d462a6e..313a3ce2 100644 --- a/include/cru/base/String.h +++ b/include/cru/base/String.h @@ -3,7 +3,6 @@ #include "Buffer.h" #include "Range.h" -#include "StringToNumberConverter.h" #include "StringUtil.h" #include <filesystem> @@ -11,7 +10,6 @@ #include <iterator> #include <string> #include <string_view> -#include <type_traits> #include <vector> namespace cru { @@ -215,22 +213,6 @@ class CRU_BASE_API String { Range RangeFromCodeUnitToCodePoint(Range code_unit_range) const; Range RangeFromCodePointToCodeUnit(Range code_point_range) const; - template <typename TInteger> - std::enable_if_t<std::is_signed_v<TInteger>, TInteger> ParseToInteger( - Index* processed_characters_count, unsigned flags, int base) const; - - int ParseToInt(Index* processed_characters_count = nullptr, - StringToNumberFlag flags = {}, int base = 0) const; - long long ParseToLongLong(Index* processed_characters_count = nullptr, - StringToNumberFlag flags = {}, int base = 0) const; - - float ParseToFloat(Index* processed_characters_count = nullptr, - StringToNumberFlag flags = {}) const; - double ParseToDouble(Index* processed_characters_count = nullptr, - StringToNumberFlag flags = {}) const; - std::vector<float> ParseToFloatList(value_type separator = u' ') const; - std::vector<double> ParseToDoubleList(value_type separator = u' ') const; - #ifdef CRU_PLATFORM_WINDOWS const wchar_t* WinCStr() const { return reinterpret_cast<const wchar_t*>(c_str()); @@ -351,29 +333,6 @@ class CRU_BASE_API StringView { Range RangeFromCodeUnitToCodePoint(Range code_unit_range) const; Range RangeFromCodePointToCodeUnit(Range code_point_range) const; - template <typename TInteger> - std::enable_if_t<std::is_signed_v<TInteger>, TInteger> ParseToInteger( - Index* processed_characters_count, StringToNumberFlag flags, - int base) const { - auto utf8_string = ToUtf8(); - auto result = StringToIntegerConverter(flags, base) - .Parse(utf8_string.data(), utf8_string.size(), - processed_characters_count); - return result.negate ? -result.value : result.value; - } - - int ParseToInt(Index* processed_characters_count = nullptr, - StringToNumberFlag flags = {}, int base = 0) const; - long long ParseToLongLong(Index* processed_characters_count = nullptr, - StringToNumberFlag flags = {}, int base = 0) const; - - float ParseToFloat(Index* processed_characters_count = nullptr, - StringToNumberFlag flags = {}) const; - double ParseToDouble(Index* processed_characters_count = nullptr, - StringToNumberFlag flags = {}) const; - std::vector<float> ParseToFloatList(value_type separator = u' ') const; - std::vector<double> ParseToDoubleList(value_type separator = u' ') const; - std::string ToUtf8() const; Buffer ToUtf8Buffer(bool end_zero = true) const; @@ -464,12 +423,6 @@ inline Index Utf16NextWord(StringView str, Index position, String CRU_BASE_API ToLower(StringView s); String CRU_BASE_API ToUpper(StringView s); -template <typename TInteger> -std::enable_if_t<std::is_signed_v<TInteger>, TInteger> String::ParseToInteger( - Index* processed_characters_count, unsigned flags, int base) const { - View().ParseToInteger<TInteger>(processed_characters_count, flags, base); -} - } // namespace cru template <> diff --git a/include/cru/base/StringToNumberConverter.h b/include/cru/base/StringToNumberConverter.h deleted file mode 100644 index 64c29971..00000000 --- a/include/cru/base/StringToNumberConverter.h +++ /dev/null @@ -1,106 +0,0 @@ -#pragma once -#include "Base.h" -#include "Bitmask.h" - -#include <cstddef> -#include <ostream> - -namespace cru { -namespace details { -struct StringToNumberFlagTag {}; -} // namespace details - -using StringToNumberFlag = Bitmask<details::StringToNumberFlagTag>; - -struct StringToNumberFlags { - constexpr static StringToNumberFlag kAllowLeadingSpaces = - StringToNumberFlag::FromOffset(1); - constexpr static StringToNumberFlag kAllowTrailingSpaces = - StringToNumberFlag::FromOffset(2); - constexpr static StringToNumberFlag kAllowTrailingJunk = - StringToNumberFlag::FromOffset(3); - constexpr static StringToNumberFlag kAllowLeadingZeroForInteger = - StringToNumberFlag::FromOffset(4); - constexpr static StringToNumberFlag kThrowOnError = - StringToNumberFlag::FromOffset(5); -}; - -template <typename TResult> -struct IStringToNumberConverter : virtual Interface { - virtual TResult Parse(const char* str, Index size, - Index* processed_characters_count) const = 0; - - template <std::size_t Size> - TResult Parse(const char (&str)[Size], - Index* processed_characters_count) const { - return Parse(str, Size - 1, processed_characters_count); - } -}; - -struct CRU_BASE_API StringToIntegerResult { - StringToIntegerResult() = default; - StringToIntegerResult(bool negate, unsigned long long value) - : negate(negate), value(value) {} - - bool negate; - unsigned long long value; -}; - -inline bool CRU_BASE_API operator==(const StringToIntegerResult& left, - const StringToIntegerResult& right) { - return left.negate == right.negate && left.value == right.value; -} - -inline bool CRU_BASE_API operator!=(const StringToIntegerResult& left, - const StringToIntegerResult& right) { - return !(left == right); -} - -CRU_BASE_API inline std::ostream& operator<<(std::ostream& stream, const StringToIntegerResult& result) { - return stream << "StringToIntegerConverterImplResult(" - << (result.negate ? "-" : "") << result.value << ")"; -} - -/** - * \brief A converter that convert number into long long. - */ -struct CRU_BASE_API StringToIntegerConverter - : IStringToNumberConverter<StringToIntegerResult> { - public: - explicit StringToIntegerConverter(StringToNumberFlag flags, int base = 0) - : flags(flags), base(base) {} - - bool CheckParams() const; - - /** - * \brief Convert string to long long. - * \param str The string to convert. - * \param size The size of the string. - * \param processed_characters_count The number of characters that were - * processed. Or nullptr to not retrieve. - */ - StringToIntegerResult Parse(const char* str, Index size, - Index* processed_characters_count) const override; - using IStringToNumberConverter<StringToIntegerResult>::Parse; - - StringToNumberFlag flags; - /** - * \brief The base of the number used for parse or 0 for auto detect. - * \remarks Base can only be of range [2, 36] or 0. If base is 0, decimal is - * assumed by default ,or if str is started with "0x" or "0X" hexadecimal is - * assumed, or if str is started with a single "0" octoral is assumed, or if - * str is started with "0b" or "0B" binary is assumed. Otherwise it is an - * error. - */ - int base; -}; - -struct CRU_BASE_API StringToFloatConverter { - StringToFloatConverter(StringToNumberFlag flags) : flags(flags) {} - - double Parse(const char* str, Index size, - Index* processed_characters_count) const; - - StringToNumberFlag flags; -}; -} // namespace cru diff --git a/include/cru/base/StringUtil.h b/include/cru/base/StringUtil.h index 2b1b5914..258d884e 100644 --- a/include/cru/base/StringUtil.h +++ b/include/cru/base/StringUtil.h @@ -2,11 +2,15 @@ #include "Base.h" #include "Bitmask.h" +#include <algorithm> +#include <cctype> +#include <charconv> #include <compare> #include <format> #include <functional> #include <string> #include <string_view> +#include <system_error> #include <type_traits> #include <vector> @@ -31,6 +35,90 @@ struct SplitOptions { std::vector<std::string> Split(std::string_view str, std::string_view sep, SplitOption options = {}); +namespace details { +struct ParseToNumberFlagTag {}; +} // namespace details + +using ParseToNumberFlag = Bitmask<details::ParseToNumberFlagTag>; + +struct ParseToNumberFlags { + constexpr static ParseToNumberFlag AllowLeadingSpaces = + ParseToNumberFlag::FromOffset(1); + constexpr static ParseToNumberFlag AllowTrailingSpaces = + ParseToNumberFlag::FromOffset(2); + constexpr static ParseToNumberFlag AllowTrailingJunk = + ParseToNumberFlag::FromOffset(3); +}; + +template <typename T> +struct ParseToNumberResult { + bool valid; + T value; + Index processed_char_count; + std::string message; +}; + +template <typename T> +ParseToNumberResult<T> ParseToNumber(std::string_view str, + ParseToNumberFlag flags = {}) { + ParseToNumberResult<T> result{}; + + const char* ptr = str.data(); + const char* const begin = str.data(); + const char* const end = str.data() + str.size(); + if (flags.Has(ParseToNumberFlags::AllowLeadingSpaces)) { + while (ptr != str.data() + str.size() && isspace(*ptr)) { + ptr++; + } + } + + if (ptr == end) { + result.valid = false; + result.message = + "Parsing reached the end (after reading all leading spaces)."; + return result; + } + + auto parse_result = + std::from_chars(ptr, str.data() + str.size(), result.value); + if (parse_result.ec == std::errc::invalid_argument) { + result.valid = false; + result.message = "Not a valid number."; + return result; + } else if (parse_result.ec == std::errc::result_out_of_range) { + result.valid = false; + result.message = "Value out of range."; + return result; + } else { + if (parse_result.ptr == end || + flags.Has(ParseToNumberFlags::AllowTrailingJunk) || + (flags.Has(ParseToNumberFlags::AllowTrailingSpaces) && + IsSpace(std::string_view(parse_result.ptr, end)))) { + result.valid = true; + result.processed_char_count = parse_result.ptr - str.data(); + return result; + } else { + result.valid = false; + result.message = "There are junk trailing characters."; + return result; + } + } +} + +template <typename T> +std::vector<T> ParseToNumberList(std::string_view str, + std::string_view separator = " ") { + auto segs = Split(str, separator, SplitOptions::RemoveSpace); + std::vector<T> result; + for (const auto& seg : segs) { + result.push_back( + ParseToNumber<T>(Trim(seg), ParseToNumberFlags::AllowLeadingSpaces | + ParseToNumberFlags::AllowTrailingSpaces) + .value); + } + return result; +} + template <typename T> struct ImplementFormatterByToString { template <class ParseContext> diff --git a/lib/double-conversion b/lib/double-conversion deleted file mode 160000 -Subproject 7630f84a10f9428b041d0471e71a562141e9684 diff --git a/src/base/CMakeLists.txt b/src/base/CMakeLists.txt index 7e7b0127..685ceb8c 100644 --- a/src/base/CMakeLists.txt +++ b/src/base/CMakeLists.txt @@ -5,7 +5,6 @@ add_library(CruBase Format.cpp PropertyTree.cpp String.cpp - StringToNumberConverter.cpp StringUtil.cpp SubProcess.cpp io/AutoReadStream.cpp @@ -85,5 +84,3 @@ else() CRU_PLATFORM_UNIX ) endif() - -target_link_libraries(CruBase PUBLIC double-conversion) diff --git a/src/base/String.cpp b/src/base/String.cpp index c90b9a71..c96b898d 100644 --- a/src/base/String.cpp +++ b/src/base/String.cpp @@ -2,14 +2,9 @@ #include "cru/base/Buffer.h" #include "cru/base/Exception.h" -#include "cru/base/StringToNumberConverter.h" #include "cru/base/StringUtil.h" -#include <double-conversion/double-conversion.h> -#include <double-conversion/string-to-double.h> - #include <algorithm> -#include <cmath> #include <cstring> #include <functional> @@ -344,34 +339,6 @@ Range String::RangeFromCodePointToCodeUnit(Range code_point_range) const { return View().RangeFromCodePointToCodeUnit(code_point_range); } -int String::ParseToInt(Index* processed_characters_count, - StringToNumberFlag flags, int base) const { - return View().ParseToInt(processed_characters_count, flags, base); -} - -long long String::ParseToLongLong(Index* processed_characters_count, - StringToNumberFlag flags, int base) const { - return View().ParseToLongLong(processed_characters_count, flags, base); -} - -float String::ParseToFloat(Index* processed_characters_count, - StringToNumberFlag flags) const { - return View().ParseToFloat(processed_characters_count, flags); -} - -double String::ParseToDouble(Index* processed_characters_count, - StringToNumberFlag flags) const { - return View().ParseToDouble(processed_characters_count, flags); -} - -std::vector<float> String::ParseToFloatList(value_type separator) const { - return View().ParseToFloatList(separator); -} - -std::vector<double> String::ParseToDoubleList(value_type separator) const { - return View().ParseToDoubleList(separator); -} - std::ostream& operator<<(std::ostream& os, const String& value) { os << value.ToUtf8(); return os; @@ -583,94 +550,6 @@ Buffer StringView::ToUtf8Buffer(bool end_zero) const { return buffer; } -int StringView::ParseToInt(Index* processed_characters_count, - StringToNumberFlag flags, int base) const { - return ParseToInteger<int>(processed_characters_count, flags, base); -} - -long long StringView::ParseToLongLong(Index* processed_characters_count, - StringToNumberFlag flags, - int base) const { - return ParseToInteger<long long>(processed_characters_count, flags, base); -} - -static int MapStringToDoubleFlags(StringToNumberFlag flags) { - int f = double_conversion::StringToDoubleConverter::ALLOW_CASE_INSENSIBILITY; - if (flags & StringToNumberFlags::kAllowLeadingSpaces) { - f |= double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES; - } - if (flags & StringToNumberFlags::kAllowTrailingSpaces) { - f |= double_conversion::StringToDoubleConverter::ALLOW_TRAILING_SPACES; - } - if (flags & StringToNumberFlags::kAllowTrailingJunk) { - f |= double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK; - } - return f; -} - -static double_conversion::StringToDoubleConverter CreateStringToDoubleConverter( - StringToNumberFlag flags) { - return {MapStringToDoubleFlags(flags), 0.0, NAN, "inf", "nan"}; -} - -float StringView::ParseToFloat(Index* processed_characters_count, - StringToNumberFlag flags) const { - int pcc; - auto result = CreateStringToDoubleConverter(flags).StringToFloat( - reinterpret_cast<const uc16*>(ptr_), static_cast<int>(size_), &pcc); - if (processed_characters_count != nullptr) { - *processed_characters_count = pcc; - } - - if (flags & StringToNumberFlags::kThrowOnError && std::isnan(result)) { - throw Exception("Result of string to float conversion is NaN"); - } - - return result; -} - -double StringView::ParseToDouble(Index* processed_characters_count, - StringToNumberFlag flags) const { - int pcc; - auto result = CreateStringToDoubleConverter(flags).StringToDouble( - reinterpret_cast<const uc16*>(ptr_), static_cast<int>(size_), &pcc); - if (processed_characters_count != nullptr) { - *processed_characters_count = pcc; - } - - if (flags & StringToNumberFlags::kThrowOnError && std::isnan(result)) { - throw Exception("Result of string to double conversion is NaN"); - } - - return result; -} - -std::vector<float> StringView::ParseToFloatList(value_type separator) const { - std::vector<float> result; - auto list = Split(separator, true); - for (auto& item : list) { - auto value = item.ParseToFloat(); - if (std::isnan(value)) { - throw Exception("Invalid double value."); - } - result.push_back(value); - } - return result; -} - -std::vector<double> StringView::ParseToDoubleList(value_type separator) const { - std::vector<double> result; - auto list = Split(separator, true); - for (auto& item : list) { - auto value = item.ParseToDouble(); - if (std::isnan(value)) { - throw Exception("Invalid double value."); - } - result.push_back(value); - } - return result; -} - String ToLower(StringView s) { String result; for (auto c : s) result.push_back(ToLower(c)); diff --git a/src/base/StringToNumberConverter.cpp b/src/base/StringToNumberConverter.cpp deleted file mode 100644 index f7516630..00000000 --- a/src/base/StringToNumberConverter.cpp +++ /dev/null @@ -1,170 +0,0 @@ -#include "cru/base/StringToNumberConverter.h" -#include "cru/base/Exception.h" - -namespace cru { -bool StringToIntegerConverter::CheckParams() const { - return base == 0 || base >= 2 & base <= 36; -} - -static bool IsSpace(char c) { - return c == ' ' || c == '\t' || c == '\n' || c == '\r'; -} - -StringToIntegerResult StringToIntegerConverter::Parse( - const char* const str, const Index size, - Index* processed_characters_count) const { - if (str == nullptr) throw std::invalid_argument("Invalid str."); - if (size < 0) throw std::invalid_argument("Invalid size."); - if (!CheckParams()) throw std::invalid_argument("Invalid parsing flags."); - - const bool throw_on_error = flags.Has(StringToNumberFlags::kThrowOnError); - - auto const end = str + size; - - auto current = str; - - if (flags & StringToNumberFlags::kAllowLeadingSpaces) { - while (current != end && IsSpace(*current)) { - current++; - } - } - - if (current == end) { - if (processed_characters_count) { - *processed_characters_count = 0; - } - if (throw_on_error) { - throw Exception("Empty string (after reading leading spaces)."); - } else { - return {false, 0}; - } - } - - bool negate = false; - - if (*current == '-') { - ++current; - negate = true; - } else if (*current == '+') { - ++current; - } - - if (current == end) { - if (processed_characters_count) { - *processed_characters_count = 0; - } - if (throw_on_error) { - throw Exception("Empty string (after reading sign)."); - } else { - return {false, 0}; - } - } - - int real_base = base; - - if (real_base == 0) { - if (*current == '0') { - ++current; - if (current == end) { - if (processed_characters_count) { - *processed_characters_count = current - str; - } - return {negate, 0}; - } else if (*current == 'x' || *current == 'X') { - ++current; - real_base = 16; - } else if (*current == 'b' || *current == 'B') { - ++current; - real_base = 2; - } else { - real_base = 8; - } - } else { - real_base = 10; - } - } - - if (current == end) { - if (processed_characters_count) { - *processed_characters_count = 0; - } - if (throw_on_error) { - throw Exception("Empty string (after reading head base indicator)."); - } else { - return {false, 0}; - } - } - - const bool allow_leading_zero = - flags.Has(StringToNumberFlags::kAllowLeadingZeroForInteger); - - while (current != end && *current == '0') { - current++; - } - - if (current == end) { - if (processed_characters_count) { - *processed_characters_count = current - str; - } - return {negate, 0}; - } - - const bool allow_trailing_junk = - flags.Has(StringToNumberFlags::kAllowTrailingJunk); - const bool allow_trailing_spaces = - flags.Has(StringToNumberFlags::kAllowTrailingSpaces); - - unsigned long long result = 0; - - while (current != end) { - const char c = *current; - if (c >= '0' && c <= (real_base > 10 ? '9' : real_base + '0' - 1)) { - result = result * real_base + c - '0'; - current++; - } else if (real_base > 10 && c >= 'a' && c <= (real_base + 'a' - 10 - 1)) { - result = result * real_base + c - 'a' + 10; - current++; - } else if (real_base > 10 && c >= 'A' && c <= (real_base + 'A' - 10 - 1)) { - result = result * real_base + c - 'A' + 10; - current++; - } else if (allow_trailing_junk) { - break; - } else if (allow_trailing_spaces && IsSpace(c)) { - break; - } else { - if (processed_characters_count) { - *processed_characters_count = 0; - } - if (throw_on_error) { - throw Exception(std::string("Read invalid character '") + c + "'."); - } else { - return {false, 0}; - } - } - } - - if (allow_trailing_spaces) { - while (current != end && IsSpace(*current)) { - current++; - } - - if (current != end) { - if (processed_characters_count) { - *processed_characters_count = 0; - } - if (throw_on_error) { - throw Exception("There is trailing junk."); - } else { - return {false, 0}; - } - } - } - - if (processed_characters_count) { - *processed_characters_count = current - str; - } - - return {negate, result}; -} - -} // namespace cru diff --git a/src/platform/graphics/Geometry.cpp b/src/platform/graphics/Geometry.cpp index e88577ce..4189ef72 100644 --- a/src/platform/graphics/Geometry.cpp +++ b/src/platform/graphics/Geometry.cpp @@ -1,7 +1,7 @@ #include "cru/platform/graphics/Geometry.h" #include "cru/base/Exception.h" -#include "cru/base/String.h" +#include "cru/base/StringUtil.h" #include "cru/platform/Exception.h" #include "cru/platform/graphics/Factory.h" @@ -235,17 +235,15 @@ void IGeometryBuilder::ParseAndApplySvgPathData(std::string_view path_d) { ++position; } - Index processed_count = 0; - auto result = String::FromUtf8(path_d.substr(position)) - .ParseToFloat(&processed_count, - StringToNumberFlags::kAllowTrailingJunk); + auto result = cru::string::ParseToNumber<float>( + path_d.substr(position), cru::string::ParseToNumberFlags::AllowTrailingJunk); - if (std::isnan(result)) throw Exception("Invalid svg path data number."); + if (!result.valid) throw Exception("Invalid svg path data number."); - position += processed_count; + position += result.processed_char_count; - return result; + return result.value; }; auto read_point = [&] { diff --git a/src/ui/components/Input.cpp b/src/ui/components/Input.cpp index e75eccc5..0a14c7b8 100644 --- a/src/ui/components/Input.cpp +++ b/src/ui/components/Input.cpp @@ -1,8 +1,7 @@ #include "cru/ui/components/Input.h" -#include "cru/base/StringToNumberConverter.h" +#include "cru/base/StringUtil.h" #include "cru/ui/controls/Control.h" -#include <cmath> #include <optional> #include <string> @@ -44,18 +43,18 @@ InputValidateResult Input::GetLastValidateResult() const { } InputValidateResult FloatInputValidator::Validate(std::string_view text) const { - auto result = String::FromUtf8(text).ParseToFloat( - nullptr, StringToNumberFlags::kAllowLeadingSpaces & - StringToNumberFlags::kAllowTrailingSpaces); - if (std::isnan(result)) { + auto result = cru::string::ParseToNumber<float>( + text, cru::string::ParseToNumberFlags::AllowLeadingSpaces | + cru::string::ParseToNumberFlags::AllowTrailingSpaces); + if (!result.valid) { return InputValidateResult{false, "Invalid number."}; } - if (min && result < *min) { + if (min && result.value < *min) { return InputValidateResult{false, "Value is less than minimum."}; } - if (max && result > *max) { + if (max && result.value > *max) { return InputValidateResult{false, "Value is greater than maximum."}; } @@ -67,9 +66,11 @@ FloatInput::FloatInput() { ChangeEvent()->AddHandler([this](const InputChangeEventArgs& args) { if (args.valid) { - value_ = String::FromUtf8(args.text).ParseToFloat( - nullptr, StringToNumberFlags::kAllowLeadingSpaces & - StringToNumberFlags::kAllowTrailingSpaces); + value_ = cru::string::ParseToNumber<float>( + args.text, + cru::string::ParseToNumberFlags::AllowLeadingSpaces | + cru::string::ParseToNumberFlags::AllowTrailingSpaces) + .value; } }); } diff --git a/src/ui/mapper/ColorMapper.cpp b/src/ui/mapper/ColorMapper.cpp index 82e9dfbc..515eba97 100644 --- a/src/ui/mapper/ColorMapper.cpp +++ b/src/ui/mapper/ColorMapper.cpp @@ -1,6 +1,5 @@ #include "cru/ui/mapper/ColorMapper.h" - -#include <cru/base/String.h> +#include "cru/base/StringUtil.h" namespace cru::ui::mapper { bool ColorMapper::XmlElementIsOfThisType(xml::XmlElementNode* node) { @@ -25,7 +24,8 @@ Color ColorMapper::DoMapFromXml(xml::XmlElementNode* node) { auto alpha_value_attr = node->GetOptionalAttributeValueCaseInsensitive("alpha"); if (alpha_value_attr) { - result.alpha = String::FromUtf8(*alpha_value_attr).ParseToDouble() * 255; + result.alpha = + cru::string::ParseToNumber<double>(*alpha_value_attr).value * 255; } return result; diff --git a/src/ui/mapper/FontMapper.cpp b/src/ui/mapper/FontMapper.cpp index 1f749513..a0dcdd9f 100644 --- a/src/ui/mapper/FontMapper.cpp +++ b/src/ui/mapper/FontMapper.cpp @@ -1,6 +1,7 @@ #include "cru/ui/mapper/FontMapper.h" #include "../Helper.h" #include "cru/base/String.h" +#include "cru/base/StringUtil.h" #include "cru/platform/graphics/Factory.h" namespace cru::ui::mapper { @@ -15,7 +16,8 @@ std::shared_ptr<platform::graphics::IFont> FontMapper::DoMapFromXml( auto font_family = font_family_attr.value_or(""); auto font_size = - font_size_attr ? String::FromUtf8(*font_size_attr).ParseToFloat() : 24.0f; + font_size_attr ? cru::string::ParseToNumber<float>(*font_size_attr).value + : 24.0f; return GetGraphicsFactory()->CreateFont(font_family, font_size); } diff --git a/src/ui/mapper/MeasureLengthMapper.cpp b/src/ui/mapper/MeasureLengthMapper.cpp index 9f5c2a26..d36afb0e 100644 --- a/src/ui/mapper/MeasureLengthMapper.cpp +++ b/src/ui/mapper/MeasureLengthMapper.cpp @@ -15,7 +15,7 @@ render::MeasureLength MeasureLengthMapper::DoMapFromString(std::string str) { if (cru::string::CaseInsensitiveCompare(str, "unspecified") == 0) { return render::MeasureLength::NotSpecified(); } - auto value = String::FromUtf8(str).ParseToFloat(); + auto value = cru::string::ParseToNumber<float>(str).value; if (value < 0) { return render::MeasureLength::NotSpecified(); } diff --git a/src/ui/mapper/PointMapper.cpp b/src/ui/mapper/PointMapper.cpp index 12f000ef..a63e1b9e 100644 --- a/src/ui/mapper/PointMapper.cpp +++ b/src/ui/mapper/PointMapper.cpp @@ -1,6 +1,5 @@ #include "cru/ui/mapper/PointMapper.h" - -#include <cru/base/String.h> +#include "cru/base/StringUtil.h" namespace cru::ui::mapper { bool PointMapper::XmlElementIsOfThisType(xml::XmlElementNode* node) { @@ -8,7 +7,7 @@ bool PointMapper::XmlElementIsOfThisType(xml::XmlElementNode* node) { } Point PointMapper::DoMapFromString(std::string str) { - std::vector<float> values = String::FromUtf8(str).ParseToFloatList(); + std::vector<float> values = cru::string::ParseToNumberList<float>(str); if (values.size() == 2) { return {values[0], values[1]}; } else if (values.size() == 1) { diff --git a/src/ui/mapper/SizeMapper.cpp b/src/ui/mapper/SizeMapper.cpp index de8a0ded..d07d937d 100644 --- a/src/ui/mapper/SizeMapper.cpp +++ b/src/ui/mapper/SizeMapper.cpp @@ -1,6 +1,5 @@ #include "cru/ui/mapper/SizeMapper.h" - -#include <cru/base/String.h> +#include "cru/base/StringUtil.h" namespace cru::ui::mapper { bool SizeMapper::XmlElementIsOfThisType(xml::XmlElementNode* node) { @@ -8,7 +7,7 @@ bool SizeMapper::XmlElementIsOfThisType(xml::XmlElementNode* node) { } Size SizeMapper::DoMapFromString(std::string str) { - std::vector<float> values = String::FromUtf8(str).ParseToFloatList(); + std::vector<float> values = cru::string::ParseToNumberList<float>(str); if (values.size() == 2) { return {values[0], values[1]}; } else if (values.size() == 1) { diff --git a/src/ui/mapper/ThicknessMapper.cpp b/src/ui/mapper/ThicknessMapper.cpp index 96f016a7..e6557689 100644 --- a/src/ui/mapper/ThicknessMapper.cpp +++ b/src/ui/mapper/ThicknessMapper.cpp @@ -1,6 +1,6 @@ #include "cru/ui/mapper/ThicknessMapper.h" +#include "cru/base/StringUtil.h" #include "cru/xml/XmlNode.h" -#include "cru/base/String.h" namespace cru::ui::mapper { bool ThicknessMapper::XmlElementIsOfThisType(xml::XmlElementNode* node) { @@ -8,7 +8,7 @@ bool ThicknessMapper::XmlElementIsOfThisType(xml::XmlElementNode* node) { } Thickness ThicknessMapper::DoMapFromString(std::string str) { - std::vector<float> values = String::FromUtf8(str).ParseToFloatList(); + std::vector<float> values = cru::string::ParseToNumberList<float>(str); if (values.size() == 4) { return Thickness(values[0], values[1], values[2], values[3]); } else if (values.size() == 2) { diff --git a/test/base/CMakeLists.txt b/test/base/CMakeLists.txt index 95313ccb..13a0a2cb 100644 --- a/test/base/CMakeLists.txt +++ b/test/base/CMakeLists.txt @@ -4,7 +4,6 @@ add_executable(CruBaseTest PropertyTreeTest.cpp SelfResolvableTest.cpp StringTest.cpp - StringToNumberConverterTest.cpp StringUtilTest.cpp SubProcessTest.cpp ) diff --git a/test/base/StringTest.cpp b/test/base/StringTest.cpp index 9ed351e5..65fe0a99 100644 --- a/test/base/StringTest.cpp +++ b/test/base/StringTest.cpp @@ -84,29 +84,3 @@ TEST_CASE("String FromUtf8", "[string]") { REQUIRE(cru::String::FromUtf8(utf8_text) == utf16_text); } - -TEST_CASE("StringView ParseToDouble", "[string]") { - using cru::StringToNumberFlags; - using cru::StringView; - REQUIRE(StringView(u"3.14159").ParseToDouble() == 3.14159); - REQUIRE( - StringView(u" 3.14159") - .ParseToDouble(nullptr, StringToNumberFlags::kAllowLeadingSpaces) == - 3.14159); - REQUIRE(StringView(u" 3.14159 ") - .ParseToDouble(nullptr, - StringToNumberFlags::kAllowLeadingSpaces | - StringToNumberFlags::kAllowTrailingSpaces) == - 3.14159); -} - -TEST_CASE("String ParseToDoubleList", "[string]") { - using cru::StringView; - - auto list = StringView(u" 1.23 2.34 3.45 ").ParseToDoubleList(); - - REQUIRE(list.size() == 3); - REQUIRE(list[0] == 1.23); - REQUIRE(list[1] == 2.34); - REQUIRE(list[2] == 3.45); -} diff --git a/test/base/StringToNumberConverterTest.cpp b/test/base/StringToNumberConverterTest.cpp deleted file mode 100644 index 82062bdb..00000000 --- a/test/base/StringToNumberConverterTest.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#include "cru/base/Exception.h" -#include "cru/base/StringToNumberConverter.h" - -#include <catch2/catch_test_macros.hpp> - -TEST_CASE("StringToIntegerConverterImpl Base0", "[string]") { - using namespace cru; - StringToIntegerConverter converter({}, 0); - Index processed_characters_count; - - REQUIRE(converter.Parse("12345678", &processed_characters_count) == - StringToIntegerResult(false, 12345678)); - REQUIRE(processed_characters_count == 8); - - REQUIRE(converter.Parse("0", &processed_characters_count) == - StringToIntegerResult(false, 0)); - REQUIRE(processed_characters_count == 1); - - REQUIRE(converter.Parse("012", &processed_characters_count) == - StringToIntegerResult(false, 012)); - REQUIRE(processed_characters_count == 3); - - REQUIRE(converter.Parse("0x12", &processed_characters_count) == - StringToIntegerResult(false, 0x12)); - REQUIRE(processed_characters_count == 4); - - REQUIRE(converter.Parse("0X12", &processed_characters_count) == - StringToIntegerResult(false, 0x12)); - REQUIRE(processed_characters_count == 4); - - REQUIRE(converter.Parse("0b101", &processed_characters_count) == - StringToIntegerResult(false, 0b101)); - REQUIRE(processed_characters_count == 5); - - REQUIRE(converter.Parse("0B101", &processed_characters_count) == - StringToIntegerResult(false, 0b101)); - REQUIRE(processed_characters_count == 5); - - REQUIRE(converter.Parse("-123", &processed_characters_count) == - StringToIntegerResult(true, 123)); - REQUIRE(processed_characters_count == 4); - - REQUIRE(converter.Parse("-0x10", &processed_characters_count) == - StringToIntegerResult(true, 0x10)); - REQUIRE(processed_characters_count == 5); -} - -TEST_CASE("StringToIntegerConverterImpl Base0ForError", "[string]") { - using namespace cru; - StringToIntegerConverter converter({}, 0); - Index processed_characters_count; - - REQUIRE(converter.Parse("a", &processed_characters_count) == - StringToIntegerResult(false, 0)); - REQUIRE(processed_characters_count == 0); - - REQUIRE(converter.Parse("0a", &processed_characters_count) == - StringToIntegerResult(false, 0)); - REQUIRE(processed_characters_count == 0); - - REQUIRE(converter.Parse("0x", &processed_characters_count) == - StringToIntegerResult(false, 0)); - REQUIRE(processed_characters_count == 0); - - REQUIRE(converter.Parse("0xx", &processed_characters_count) == - StringToIntegerResult(false, 0)); - REQUIRE(processed_characters_count == 0); - - REQUIRE(converter.Parse(" 0", &processed_characters_count) == - StringToIntegerResult(false, 0)); - REQUIRE(processed_characters_count == 0); - - REQUIRE(converter.Parse("0 ", &processed_characters_count) == - StringToIntegerResult(false, 0)); - REQUIRE(processed_characters_count == 0); -} - -TEST_CASE("StringToIntegerConverterImpl ThrowOnErrorFlag", "[string]") { - using namespace cru; - StringToIntegerConverter converter(StringToNumberFlags::kThrowOnError, 0); - Index processed_characters_count; - REQUIRE_THROWS_AS(converter.Parse("?", &processed_characters_count), - Exception); -} - -TEST_CASE("StringToIntegerConverterImpl AllowLeadingZeroFlag", "[string]") { - using namespace cru; - StringToIntegerConverter converter( - StringToNumberFlags::kAllowLeadingSpaces, 0); - Index processed_characters_count; - REQUIRE(converter.Parse(" 123", &processed_characters_count) == - StringToIntegerResult(false, 123)); - REQUIRE(processed_characters_count == 6); -} - -TEST_CASE("StringToIntegerConverterImpl AllowTrailingZeroFlag", "[string]") { - using namespace cru; - StringToIntegerConverter converter( - StringToNumberFlags::kAllowTrailingSpaces, 0); - Index processed_characters_count; - REQUIRE(converter.Parse("123 ", &processed_characters_count) == - StringToIntegerResult(false, 123)); - REQUIRE(processed_characters_count == 6); -} - -TEST_CASE("StringToIntegerConverterImpl AllowTrailingJunk", "[string]") { - using namespace cru; - StringToIntegerConverter converter( - StringToNumberFlags::kAllowTrailingJunk, 0); - Index processed_characters_count; - REQUIRE(converter.Parse("123 12", &processed_characters_count) == - StringToIntegerResult(false, 123)); - REQUIRE(processed_characters_count == 3); -} - -TEST_CASE("StringToIntegerConverterImpl AllowLeadingZeroForInteger", - "[string]") { - using namespace cru; - StringToIntegerConverter converter( - StringToNumberFlags::kAllowLeadingZeroForInteger, 0); - Index processed_characters_count; - REQUIRE(converter.Parse("0x0012", &processed_characters_count) == - StringToIntegerResult(false, 0x12)); - REQUIRE(processed_characters_count == 6); - - REQUIRE(converter.Parse("000011", &processed_characters_count) == - StringToIntegerResult(false, 000011)); - REQUIRE(processed_characters_count == 6); - - REQUIRE(converter.Parse("0b0011", &processed_characters_count) == - StringToIntegerResult(false, 0b0011)); - REQUIRE(processed_characters_count == 6); -} - -TEST_CASE("StringToIntegerConverterImpl CompositeFlags", "[string]") { - using namespace cru; - StringToIntegerConverter converter( - StringToNumberFlags::kAllowLeadingSpaces | - StringToNumberFlags::kAllowTrailingJunk | - StringToNumberFlags::kAllowLeadingZeroForInteger, - 0); - Index processed_characters_count; - - REQUIRE(converter.Parse(" 0x00123!!!", &processed_characters_count) == - StringToIntegerResult(false, 0x00123)); - REQUIRE(processed_characters_count == 10); -} - -TEST_CASE("StringToIntegerConverterImpl OtherBase", "[string]") { - using namespace cru; - StringToIntegerConverter converter({}, 7); - Index processed_characters_count; - - REQUIRE(converter.Parse("12", &processed_characters_count) == - StringToIntegerResult(false, 9)); - REQUIRE(processed_characters_count == 2); - - REQUIRE(converter.Parse("-12", &processed_characters_count) == - StringToIntegerResult(true, 9)); - REQUIRE(processed_characters_count == 3); - - converter.base = 11; - REQUIRE(converter.Parse("1a", &processed_characters_count) == - StringToIntegerResult(false, 21)); - REQUIRE(processed_characters_count == 2); -} diff --git a/test/base/StringUtilTest.cpp b/test/base/StringUtilTest.cpp index 3ce4cff1..5951531d 100644 --- a/test/base/StringUtilTest.cpp +++ b/test/base/StringUtilTest.cpp @@ -101,6 +101,118 @@ TEST_CASE("StringUtil Utf16CodePointIterator", "[string]") { REQUIRE(code_points == expected_code_points); } +TEST_CASE("ParseToNumber Work", "[string]") { + using namespace cru::string; + + auto r1 = ParseToNumber<int>("123"); + REQUIRE(r1.valid); + REQUIRE(r1.value == 123); + REQUIRE(r1.processed_char_count == 3); + + auto r2 = ParseToNumber<int>("123.123"); + REQUIRE(!r2.valid); + + auto r3 = ParseToNumber<float>("123.123"); + REQUIRE(r3.valid); + REQUIRE(r3.value == 123.123f); + REQUIRE(r3.processed_char_count == 7); + + auto r4 = ParseToNumber<float>("a123"); + REQUIRE(!r4.valid); +} + +TEST_CASE("ParseToNumber AllowLeadingZeroFlag", "[string]") { + using namespace cru::string; + + auto r1 = ParseToNumber<int>(" 123"); + REQUIRE(!r1.valid); + + auto r2 = ParseToNumber<int>(" 123", ParseToNumberFlags::AllowLeadingSpaces); + REQUIRE(r2.valid); + REQUIRE(r2.value == 123); + REQUIRE(r2.processed_char_count == 5); + + auto r3 = ParseToNumber<float>(" 123.123"); + REQUIRE(!r3.valid); + + auto r4 = + ParseToNumber<float>(" 123.123", ParseToNumberFlags::AllowLeadingSpaces); + REQUIRE(r4.valid); + REQUIRE(r4.value == 123.123f); + REQUIRE(r4.processed_char_count == 9); +} + +TEST_CASE("StringToIntegerConverterImpl AllowTrailingSpacesFlag", "[string]") { + using namespace cru::string; + + auto r1 = ParseToNumber<int>("123 "); + REQUIRE(!r1.valid); + + auto r2 = + ParseToNumber<int>("123 ", ParseToNumberFlags::AllowTrailingSpaces); + REQUIRE(r2.valid); + REQUIRE(r2.value == 123); + REQUIRE(r2.processed_char_count == 3); + + auto r3 = ParseToNumber<float>("123.123 "); + REQUIRE(!r3.valid); + + auto r4 = ParseToNumber<float>("123.123 ", + ParseToNumberFlags::AllowTrailingSpaces); + REQUIRE(r4.valid); + REQUIRE(r4.value == 123.123f); + REQUIRE(r4.processed_char_count == 7); +} + +TEST_CASE("StringToIntegerConverterImpl AllowTrailingJunk", "[string]") { + using namespace cru::string; + + auto r1 = ParseToNumber<int>("123ab"); + REQUIRE(!r1.valid); + + auto r2 = ParseToNumber<int>("123ab", ParseToNumberFlags::AllowTrailingJunk); + REQUIRE(r2.valid); + REQUIRE(r2.value == 123); + REQUIRE(r2.processed_char_count == 3); + + auto r3 = ParseToNumber<float>("123.123ab"); + REQUIRE(!r3.valid); + + auto r4 = + ParseToNumber<float>("123.123ab", ParseToNumberFlags::AllowTrailingJunk); + REQUIRE(r4.valid); + REQUIRE(r4.value == 123.123f); + REQUIRE(r4.processed_char_count == 7); +} + +TEST_CASE("StringToIntegerConverterImpl CompositeFlags", "[string]") { + using namespace cru::string; + + auto r1 = + ParseToNumber<int>(" 123ab", ParseToNumberFlags::AllowLeadingSpaces | + ParseToNumberFlags::AllowTrailingJunk); + REQUIRE(r1.valid); + REQUIRE(r1.value == 123); + REQUIRE(r1.processed_char_count == 5); + + auto r2 = ParseToNumber<float>(" 123.123ab", + ParseToNumberFlags::AllowLeadingSpaces | + ParseToNumberFlags::AllowTrailingJunk); + REQUIRE(r2.valid); + REQUIRE(r2.value == 123.123f); + REQUIRE(r2.processed_char_count == 9); +} + +TEST_CASE("String ParseToNumberList", "[string]") { + using namespace cru::string; + + auto r1 = ParseToNumberList<int>("123 456 789"); + REQUIRE(r1 == std::vector<int>{123, 456, 789}); + + auto r2 = ParseToNumberList<float>("1.1 2.2 3.3"); + REQUIRE(r2 == std::vector<float>{1.1f, 2.2f, 3.3f}); +} + // TEST(WinString, IndexUtf8ToUtf16) { // using cru::platform::win::IndexUtf8ToUtf16; // std::string_view utf8_string = "aπ你🤣!"; |