diff options
author | crupest <crupest@outlook.com> | 2022-03-09 23:12:26 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2022-03-09 23:12:26 +0800 |
commit | dceef749139061fdac4946df77219f1cc8aa6483 (patch) | |
tree | 0b413e605183226034fdaf342adfd6dc5e8fc08e | |
parent | 78f5221e8fbab510bb8b5ac268b7d42bed762961 (diff) | |
download | cru-dceef749139061fdac4946df77219f1cc8aa6483.tar.gz cru-dceef749139061fdac4946df77219f1cc8aa6483.tar.bz2 cru-dceef749139061fdac4946df77219f1cc8aa6483.zip |
...
-rw-r--r-- | include/cru/common/String.h | 16 | ||||
-rw-r--r-- | include/cru/common/StringToNumberConverter.h | 50 | ||||
-rw-r--r-- | src/common/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/common/String.cpp | 19 | ||||
-rw-r--r-- | src/common/StringToNumberConverter.cpp | 154 | ||||
-rw-r--r-- | src/platform/graphics/Geometry.cpp | 2 | ||||
-rw-r--r-- | test/common/StringTest.cpp | 8 | ||||
-rw-r--r-- | test/common/StringToNumberConverterTest.cpp | 7 |
8 files changed, 232 insertions, 25 deletions
diff --git a/include/cru/common/String.h b/include/cru/common/String.h index 60d0912e..9ec494c6 100644 --- a/include/cru/common/String.h +++ b/include/cru/common/String.h @@ -2,6 +2,7 @@ #include "Base.h" #include "Range.h" +#include "StringToNumberConverter.h" #include "StringUtil.h" #include <double-conversion/double-conversion.h> @@ -13,13 +14,6 @@ #include <vector> namespace cru { -struct StringToFloatFlags { - constexpr static int kNoFlags = 0; - constexpr static int kAllowLeadingSpaces = 1 << 0; - constexpr static int kAllowTrailingSpaces = 1 << 1; - constexpr static int kAllowTrailingJunk = 1 << 2; -}; - class StringView; class CRU_BASE_API String { @@ -219,9 +213,9 @@ class CRU_BASE_API String { Range RangeFromCodePointToCodeUnit(Range code_point_range) const; float ParseToFloat(Index* processed_characters_count = nullptr, - int flags = StringToFloatFlags::kNoFlags) const; + unsigned flags = StringToNumberFlags::kNoFlags) const; double ParseToDouble(Index* processed_characters_count = nullptr, - int flags = StringToFloatFlags::kNoFlags) const; + unsigned flags = StringToNumberFlags::kNoFlags) const; std::vector<float> ParseToFloatList(value_type separator = u' ') const; std::vector<double> ParseToDoubleList(value_type separator = u' ') const; @@ -346,9 +340,9 @@ class CRU_BASE_API StringView { Range RangeFromCodePointToCodeUnit(Range code_point_range) const; float ParseToFloat(Index* processed_characters_count = nullptr, - int flags = StringToFloatFlags::kNoFlags) const; + unsigned flags = StringToNumberFlags::kNoFlags) const; double ParseToDouble(Index* processed_characters_count = nullptr, - int flags = StringToFloatFlags::kNoFlags) const; + unsigned flags = StringToNumberFlags::kNoFlags) const; std::vector<float> ParseToFloatList(value_type separator = u' ') const; std::vector<double> ParseToDoubleList(value_type separator = u' ') const; diff --git a/include/cru/common/StringToNumberConverter.h b/include/cru/common/StringToNumberConverter.h new file mode 100644 index 00000000..e68d12a6 --- /dev/null +++ b/include/cru/common/StringToNumberConverter.h @@ -0,0 +1,50 @@ +#pragma once +#include "Base.h" + +namespace cru { +struct StringToNumberFlags { + constexpr static unsigned kNoFlags = 0; + constexpr static unsigned kAllowLeadingSpaces = 1 << 0; + constexpr static unsigned kAllowTrailingSpaces = 1 << 1; + constexpr static unsigned kAllowTrailingJunk = 1 << 2; + constexpr static unsigned kAllowLeadingZeroForInteger = 1 << 2; + constexpr static unsigned kThrowOnError = 1 << 3; +}; + +struct StringToIntegerConverterImplResult { + bool negate; + unsigned long long value; +}; + +/** + * \brief A converter that convert number into long long. + */ +struct StringToIntegerConverterImpl { + public: + explicit StringToIntegerConverterImpl(unsigned 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. + */ + StringToIntegerConverterImplResult Parse( + const char* str, Index size, Index* processed_characters_count) const; + + unsigned 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; +}; +} // namespace cru diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index ae418f6b..8bfcb867 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -4,6 +4,7 @@ add_library(cru_base SHARED Format.cpp PropertyTree.cpp String.cpp + StringToNumberConverter.cpp StringUtil.cpp io/Stream.cpp io/Resource.cpp diff --git a/src/common/String.cpp b/src/common/String.cpp index ed66b914..908f64b6 100644 --- a/src/common/String.cpp +++ b/src/common/String.cpp @@ -313,12 +313,13 @@ Range String::RangeFromCodePointToCodeUnit(Range code_point_range) const { return View().RangeFromCodePointToCodeUnit(code_point_range); } -float String::ParseToFloat(Index* processed_characters_count, int flags) const { +float String::ParseToFloat(Index* processed_characters_count, + unsigned flags) const { return View().ParseToFloat(processed_characters_count, flags); } double String::ParseToDouble(Index* processed_characters_count, - int flags) const { + unsigned flags) const { return View().ParseToDouble(processed_characters_count, flags); } @@ -516,15 +517,15 @@ std::string StringView::ToUtf8() const { return result; } -static int MapStringToFloatFlags(int flags) { +static int MapStringToDoubleFlags(int flags) { int f = double_conversion::StringToDoubleConverter::ALLOW_CASE_INSENSIBILITY; - if (flags & StringToFloatFlags::kAllowLeadingSpaces) { + if (flags & StringToNumberFlags::kAllowLeadingSpaces) { f |= double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES; } - if (flags & StringToFloatFlags::kAllowTrailingSpaces) { + if (flags & StringToNumberFlags::kAllowTrailingSpaces) { f |= double_conversion::StringToDoubleConverter::ALLOW_TRAILING_SPACES; } - if (flags & StringToFloatFlags::kAllowTrailingJunk) { + if (flags & StringToNumberFlags::kAllowTrailingJunk) { f |= double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK; } return f; @@ -532,11 +533,11 @@ static int MapStringToFloatFlags(int flags) { static double_conversion::StringToDoubleConverter CreateStringToDoubleConverter( int flags) { - return {MapStringToFloatFlags(flags), 0.0, NAN, "inf", "nan"}; + return {MapStringToDoubleFlags(flags), 0.0, NAN, "inf", "nan"}; } float StringView::ParseToFloat(Index* processed_characters_count, - int flags) const { + unsigned flags) const { int pcc; auto result = CreateStringToDoubleConverter(flags).StringToFloat( reinterpret_cast<const uc16*>(ptr_), static_cast<int>(size_), &pcc); @@ -547,7 +548,7 @@ float StringView::ParseToFloat(Index* processed_characters_count, } double StringView::ParseToDouble(Index* processed_characters_count, - int flags) const { + unsigned flags) const { int pcc; auto result = CreateStringToDoubleConverter(flags).StringToDouble( reinterpret_cast<const uc16*>(ptr_), static_cast<int>(size_), &pcc); diff --git a/src/common/StringToNumberConverter.cpp b/src/common/StringToNumberConverter.cpp new file mode 100644 index 00000000..259e39b0 --- /dev/null +++ b/src/common/StringToNumberConverter.cpp @@ -0,0 +1,154 @@ +#include "cru/common/StringToNumberConverter.h" +#include "cru/common/Exception.h" + +namespace cru { +bool StringToIntegerConverterImpl::CheckParams() const { + if (base == 0) { + if (flags & StringToNumberFlags::kAllowLeadingZeroForInteger) { + return false; + } else { + return true; + } + } + + return base >= 2 & base <= 36; +} + +static bool IsSpace(char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +StringToIntegerConverterImplResult StringToIntegerConverterImpl::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 & StringToNumberFlags::kThrowOnError) != 0; + + const char* const end = str + size; + + const char* 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(u"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(u"Empty string (after reading sign)."); + } else { + return {false, 0}; + } + } + + int base = this->base; + + if (base == 0) { + if (*current == '0') { + ++current; + if (current == end) { + return {negate, 0}; + } else if (*current == 'x' || *current == 'X') { + ++current; + base = 16; + } else if (*current == 'b' || *current == 'B') { + ++current; + base = 2; + } else { + base = 8; + } + } + } + + if (current == end) { + if (processed_characters_count) { + *processed_characters_count = 0; + } + if (throw_on_error) { + throw Exception(u"Empty string (after reading head base indicator)."); + } else { + return {false, 0}; + } + } + + const bool allow_leading_zero = + flags & StringToNumberFlags::kAllowLeadingZeroForInteger; + + while (current != end && *current == '0') { + current++; + } + + if (current == end) { + return {negate, 0}; + } + + const bool allow_trailing_junk = + flags & StringToNumberFlags::kAllowTrailingJunk; + const bool allow_trailing_spaces = + flags & StringToNumberFlags::kAllowTrailingSpaces; + unsigned long long result = 0; + + while (current != end) { + const char c = *current; + if (c >= '0' && c <= (base > 10 ? '9' : base + '0' - 1)) { + result = result * base + c - '0'; + } else if (base > 10 && c >= 'a' && c <= 'z') { + result = result * base + c - 'a' + 10; + } else if (base > 10 && c >= 'A' && c <= 'Z') { + result = result * base + c - 'A' + 10; + } 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(u"Read invalid character."); + } else { + return {false, 0}; + } + } + } + + if (allow_trailing_spaces) { + while (current != end && IsSpace(*current)) { + current++; + } + } + + if (current != end) { + throw Exception(u"There is trailing junk."); + } + + return {negate, result}; +} +} // namespace cru diff --git a/src/platform/graphics/Geometry.cpp b/src/platform/graphics/Geometry.cpp index 32ba0d6d..c215ad30 100644 --- a/src/platform/graphics/Geometry.cpp +++ b/src/platform/graphics/Geometry.cpp @@ -193,7 +193,7 @@ void IGeometryBuilder::ParseAndApplySvgPathData(StringView path_d) { Index processed_count = 0; auto result = path_d.substr(position).ParseToFloat( - &processed_count, StringToFloatFlags::kAllowTrailingJunk); + &processed_count, StringToNumberFlags::kAllowTrailingJunk); if (std::isnan(result)) throw Exception(u"Invalid svg path data number."); diff --git a/test/common/StringTest.cpp b/test/common/StringTest.cpp index e053400e..1faab1cf 100644 --- a/test/common/StringTest.cpp +++ b/test/common/StringTest.cpp @@ -86,17 +86,17 @@ TEST(String, FromUtf8) { } TEST(StringView, ParseToDouble) { - using cru::StringToFloatFlags; + using cru::StringToNumberFlags; using cru::StringView; ASSERT_EQ(StringView(u"3.14159").ParseToDouble(), 3.14159); ASSERT_EQ( StringView(u" 3.14159") - .ParseToDouble(nullptr, StringToFloatFlags::kAllowLeadingSpaces), + .ParseToDouble(nullptr, StringToNumberFlags::kAllowLeadingSpaces), 3.14159); ASSERT_EQ( StringView(u" 3.14159 ") - .ParseToDouble(nullptr, StringToFloatFlags::kAllowLeadingSpaces | - StringToFloatFlags::kAllowTrailingSpaces), + .ParseToDouble(nullptr, StringToNumberFlags::kAllowLeadingSpaces | + StringToNumberFlags::kAllowTrailingSpaces), 3.14159); } diff --git a/test/common/StringToNumberConverterTest.cpp b/test/common/StringToNumberConverterTest.cpp new file mode 100644 index 00000000..5eb4e33b --- /dev/null +++ b/test/common/StringToNumberConverterTest.cpp @@ -0,0 +1,7 @@ +#include "cru/common/StringToNumberConverter.h" + +#include <gtest/gtest.h> + +TEST(StringToIntegerConverterImpl, Base0) { + +} |