diff options
-rw-r--r-- | include/cru/common/String.h | 17 | ||||
-rw-r--r-- | include/cru/common/StringToNumberConverter.h | 28 | ||||
-rw-r--r-- | src/common/StringToNumberConverter.cpp | 27 | ||||
-rw-r--r-- | test/common/CMakeLists.txt | 1 | ||||
-rw-r--r-- | test/common/StringToNumberConverterTest.cpp | 159 |
5 files changed, 218 insertions, 14 deletions
diff --git a/include/cru/common/String.h b/include/cru/common/String.h index 9ec494c6..7bbbecfb 100644 --- a/include/cru/common/String.h +++ b/include/cru/common/String.h @@ -356,7 +356,22 @@ class CRU_BASE_API StringView { CRU_DEFINE_COMPARE_OPERATORS(String) inline String operator+(const String& left, const String& right) { - String result(left); + String result; + result += left; + result += right; + return result; +} + +inline String operator+(String::value_type left, const String& right) { + String result; + result += left; + result += right; + return result; +} + +inline String operator+(const String& left, String::value_type right) { + String result; + result += left; result += right; return result; } diff --git a/include/cru/common/StringToNumberConverter.h b/include/cru/common/StringToNumberConverter.h index e68d12a6..b5118de6 100644 --- a/include/cru/common/StringToNumberConverter.h +++ b/include/cru/common/StringToNumberConverter.h @@ -1,6 +1,8 @@ #pragma once #include "Base.h" +#include <ostream> + namespace cru { struct StringToNumberFlags { constexpr static unsigned kNoFlags = 0; @@ -12,10 +14,30 @@ struct StringToNumberFlags { }; struct StringToIntegerConverterImplResult { + StringToIntegerConverterImplResult() = default; + StringToIntegerConverterImplResult(bool negate, unsigned long long value) + : negate(negate), value(value) {} + bool negate; unsigned long long value; }; +inline bool operator==(const StringToIntegerConverterImplResult& left, + const StringToIntegerConverterImplResult& right) { + return left.negate == right.negate && left.value == right.value; +} + +inline bool operator!=(const StringToIntegerConverterImplResult& left, + const StringToIntegerConverterImplResult& right) { + return !(left == right); +} + +inline std::ostream& operator<<( + std::ostream& stream, const StringToIntegerConverterImplResult& result) { + return stream << "StringToIntegerConverterImplResult(" + << (result.negate ? "-" : "") << result.value << ")"; +} + /** * \brief A converter that convert number into long long. */ @@ -36,6 +58,12 @@ struct StringToIntegerConverterImpl { StringToIntegerConverterImplResult Parse( const char* str, Index size, Index* processed_characters_count) const; + template <std::size_t Size> + StringToIntegerConverterImplResult Parse( + const char (&str)[Size], Index* processed_characters_count) const { + return Parse(str, Size - 1, processed_characters_count); + } + unsigned flags; /** * \brief The base of the number used for parse or 0 for auto detect. diff --git a/src/common/StringToNumberConverter.cpp b/src/common/StringToNumberConverter.cpp index d77f7886..526c2712 100644 --- a/src/common/StringToNumberConverter.cpp +++ b/src/common/StringToNumberConverter.cpp @@ -3,15 +3,7 @@ namespace cru { bool StringToIntegerConverterImpl::CheckParams() const { - if (base == 0) { - if (flags & StringToNumberFlags::kAllowLeadingZeroForInteger) { - return false; - } else { - return true; - } - } - - return base >= 2 & base <= 36; + return base == 0 || base >= 2 & base <= 36; } static bool IsSpace(char c) { @@ -74,6 +66,9 @@ StringToIntegerConverterImplResult StringToIntegerConverterImpl::Parse( 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; @@ -84,6 +79,8 @@ StringToIntegerConverterImplResult StringToIntegerConverterImpl::Parse( } else { base = 8; } + } else { + base = 10; } } @@ -106,6 +103,9 @@ StringToIntegerConverterImplResult StringToIntegerConverterImpl::Parse( } if (current == end) { + if (processed_characters_count) { + *processed_characters_count = current - str; + } return {negate, 0}; } @@ -119,10 +119,13 @@ StringToIntegerConverterImplResult StringToIntegerConverterImpl::Parse( 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') { + current++; + } else if (base > 10 && c >= 'a' && c <= (base + 'a' - 10 - 1)) { result = result * base + c - 'a' + 10; - } else if (base > 10 && c >= 'A' && c <= 'Z') { + current++; + } else if (base > 10 && c >= 'A' && c <= (base + 'A' - 10 - 1)) { result = result * base + c - 'A' + 10; + current++; } else if (allow_trailing_junk) { break; } else if (allow_trailing_spaces && IsSpace(c)) { @@ -132,7 +135,7 @@ StringToIntegerConverterImplResult StringToIntegerConverterImpl::Parse( *processed_characters_count = 0; } if (throw_on_error) { - throw Exception(u"Read invalid character."); + throw Exception(String(u"Read invalid character '") + c + u"'."); } else { return {false, 0}; } diff --git a/test/common/CMakeLists.txt b/test/common/CMakeLists.txt index 600c18c3..6b2b594b 100644 --- a/test/common/CMakeLists.txt +++ b/test/common/CMakeLists.txt @@ -2,6 +2,7 @@ add_executable(cru_base_test HandlerRegistryTest.cpp PropertyTreeTest.cpp StringTest.cpp + StringToNumberConverterTest.cpp StringUtilTest.cpp ) target_link_libraries(cru_base_test PRIVATE cru_base cru_test_base) diff --git a/test/common/StringToNumberConverterTest.cpp b/test/common/StringToNumberConverterTest.cpp index 5eb4e33b..7b009d1e 100644 --- a/test/common/StringToNumberConverterTest.cpp +++ b/test/common/StringToNumberConverterTest.cpp @@ -1,7 +1,164 @@ +#include "cru/common/Exception.h" #include "cru/common/StringToNumberConverter.h" #include <gtest/gtest.h> TEST(StringToIntegerConverterImpl, Base0) { - + using namespace cru; + StringToIntegerConverterImpl converter(0, 0); + Index processed_characters_count; + + ASSERT_EQ(converter.Parse("12345678", &processed_characters_count), + StringToIntegerConverterImplResult(false, 12345678)); + ASSERT_EQ(processed_characters_count, 8); + + ASSERT_EQ(converter.Parse("0", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0)); + ASSERT_EQ(processed_characters_count, 1); + + ASSERT_EQ(converter.Parse("012", &processed_characters_count), + StringToIntegerConverterImplResult(false, 012)); + ASSERT_EQ(processed_characters_count, 3); + + ASSERT_EQ(converter.Parse("0x12", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0x12)); + ASSERT_EQ(processed_characters_count, 4); + + ASSERT_EQ(converter.Parse("0X12", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0x12)); + ASSERT_EQ(processed_characters_count, 4); + + ASSERT_EQ(converter.Parse("0b101", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0b101)); + ASSERT_EQ(processed_characters_count, 5); + + ASSERT_EQ(converter.Parse("0B101", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0b101)); + ASSERT_EQ(processed_characters_count, 5); + + ASSERT_EQ(converter.Parse("-123", &processed_characters_count), + StringToIntegerConverterImplResult(true, 123)); + ASSERT_EQ(processed_characters_count, 4); + + ASSERT_EQ(converter.Parse("-0x10", &processed_characters_count), + StringToIntegerConverterImplResult(true, 0x10)); + ASSERT_EQ(processed_characters_count, 5); +} + +TEST(StringToIntegerConverterImpl, Base0ForError) { + using namespace cru; + StringToIntegerConverterImpl converter(0, 0); + Index processed_characters_count; + + ASSERT_EQ(converter.Parse("a", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0)); + ASSERT_EQ(processed_characters_count, 0); + + ASSERT_EQ(converter.Parse("0a", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0)); + ASSERT_EQ(processed_characters_count, 0); + + ASSERT_EQ(converter.Parse("0x", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0)); + ASSERT_EQ(processed_characters_count, 0); + + ASSERT_EQ(converter.Parse("0xx", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0)); + ASSERT_EQ(processed_characters_count, 0); + + ASSERT_EQ(converter.Parse(" 0", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0)); + ASSERT_EQ(processed_characters_count, 0); + + ASSERT_EQ(converter.Parse("0 ", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0)); + ASSERT_EQ(processed_characters_count, 0); +} + +TEST(StringToIntegerConverterImpl, ThrowOnErrorFlag) { + using namespace cru; + StringToIntegerConverterImpl converter(StringToNumberFlags::kThrowOnError, 0); + Index processed_characters_count; + ASSERT_THROW(converter.Parse("?", &processed_characters_count), Exception); +} + +TEST(StringToIntegerConverterImpl, AllowLeadingZeroFlag) { + using namespace cru; + StringToIntegerConverterImpl converter( + StringToNumberFlags::kAllowLeadingSpaces, 0); + Index processed_characters_count; + ASSERT_EQ(converter.Parse(" 123", &processed_characters_count), + StringToIntegerConverterImplResult(false, 123)); + ASSERT_EQ(processed_characters_count, 6); +} + +TEST(StringToIntegerConverterImpl, AllowTrailingZeroFlag) { + using namespace cru; + StringToIntegerConverterImpl converter( + StringToNumberFlags::kAllowTrailingSpaces, 0); + Index processed_characters_count; + ASSERT_EQ(converter.Parse("123 ", &processed_characters_count), + StringToIntegerConverterImplResult(false, 123)); + ASSERT_EQ(processed_characters_count, 6); +} + +TEST(StringToIntegerConverterImpl, AllowTrailingJunk) { + using namespace cru; + StringToIntegerConverterImpl converter( + StringToNumberFlags::kAllowLeadingZeroForInteger, 0); + Index processed_characters_count; + ASSERT_EQ(converter.Parse("123 12", &processed_characters_count), + StringToIntegerConverterImplResult(false, 123)); + ASSERT_EQ(processed_characters_count, 3); +} + +TEST(StringToIntegerConverterImpl, AllowLeadingZeroForInteger) { + using namespace cru; + StringToIntegerConverterImpl converter( + StringToNumberFlags::kAllowLeadingZeroForInteger, 0); + Index processed_characters_count; + ASSERT_EQ(converter.Parse("0x0012", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0x12)); + ASSERT_EQ(processed_characters_count, 6); + + ASSERT_EQ(converter.Parse("000011", &processed_characters_count), + StringToIntegerConverterImplResult(false, 000011)); + ASSERT_EQ(processed_characters_count, 6); + + ASSERT_EQ(converter.Parse("0b0011", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0b0011)); + ASSERT_EQ(processed_characters_count, 6); +} + +TEST(StringToIntegerConverterImpl, CompositeFlags) { + using namespace cru; + StringToIntegerConverterImpl converter( + StringToNumberFlags::kAllowLeadingSpaces | + StringToNumberFlags::kAllowTrailingJunk | + StringToNumberFlags::kAllowLeadingZeroForInteger, + 0); + Index processed_characters_count; + + ASSERT_EQ(converter.Parse(" 0x00123!!!", &processed_characters_count), + StringToIntegerConverterImplResult(false, 0x00123)); + ASSERT_EQ(processed_characters_count, 10); +} + +TEST(StringToIntegerConverterImpl, OtherBase) { + using namespace cru; + StringToIntegerConverterImpl converter(0, 7); + Index processed_characters_count; + + ASSERT_EQ(converter.Parse("12", &processed_characters_count), + StringToIntegerConverterImplResult(false, 9)); + ASSERT_EQ(processed_characters_count, 2); + + ASSERT_EQ(converter.Parse("-12", &processed_characters_count), + StringToIntegerConverterImplResult(true, 9)); + ASSERT_EQ(processed_characters_count, 3); + + converter.base = 11; + ASSERT_EQ(converter.Parse("1a", &processed_characters_count), + StringToIntegerConverterImplResult(false, 21)); + ASSERT_EQ(processed_characters_count, 2); } |