1 | /*============================================================================= |
2 | Copyright (c) 2018 Nikita Kniazev |
3 | |
4 | Distributed under the Boost Software License, Version 1.0. (See accompanying |
5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) |
6 | =============================================================================*/ |
7 | |
8 | #include <boost/spirit/home/qi/numeric/numeric_utils.hpp> |
9 | |
10 | #include <boost/core/lightweight_test.hpp> |
11 | #include <boost/static_assert.hpp> |
12 | #include <cmath> // for std::pow |
13 | #include <iosfwd> |
14 | #include <limits> |
15 | #include <sstream> |
16 | |
17 | #ifdef _MSC_VER |
18 | # pragma warning(disable: 4127) // conditional expression is constant |
19 | #endif |
20 | |
21 | template <int Min, int Max> |
22 | struct custom_int |
23 | { |
24 | BOOST_DEFAULTED_FUNCTION(custom_int(), {}) |
25 | BOOST_CONSTEXPR custom_int(int value) : value_(value) {} |
26 | |
27 | custom_int operator+(custom_int x) const { return value_ + x.value_; } |
28 | custom_int operator-(custom_int x) const { return value_ - x.value_; } |
29 | custom_int operator*(custom_int x) const { return value_ * x.value_; } |
30 | custom_int operator/(custom_int x) const { return value_ / x.value_; } |
31 | |
32 | custom_int& operator+=(custom_int x) { value_ += x.value_; return *this; } |
33 | custom_int& operator-=(custom_int x) { value_ -= x.value_; return *this; } |
34 | custom_int& operator*=(custom_int x) { value_ *= x.value_; return *this; } |
35 | custom_int& operator/=(custom_int x) { value_ /= x.value_; return *this; } |
36 | custom_int& operator++() { ++value_; return *this; } |
37 | custom_int& operator--() { --value_; return *this; } |
38 | custom_int operator++(int) { return value_++; } |
39 | custom_int operator--(int) { return value_--; } |
40 | |
41 | custom_int operator+() { return +value_; } |
42 | custom_int operator-() { return -value_; } |
43 | |
44 | bool operator< (custom_int x) const { return value_ < x.value_; } |
45 | bool operator> (custom_int x) const { return value_ > x.value_; } |
46 | bool operator<=(custom_int x) const { return value_ <= x.value_; } |
47 | bool operator>=(custom_int x) const { return value_ >= x.value_; } |
48 | bool operator==(custom_int x) const { return value_ == x.value_; } |
49 | bool operator!=(custom_int x) const { return value_ != x.value_; } |
50 | |
51 | template <typename Char, typename Traits> |
52 | friend std::basic_ostream<Char, Traits>& |
53 | operator<<(std::basic_ostream<Char, Traits>& os, custom_int x) { |
54 | return os << x.value_; |
55 | } |
56 | |
57 | BOOST_STATIC_CONSTEXPR int max = Max; |
58 | BOOST_STATIC_CONSTEXPR int min = Min; |
59 | |
60 | private: |
61 | int value_; |
62 | }; |
63 | |
64 | namespace utils { |
65 | |
66 | template <int Min, int Max> struct digits; |
67 | template <> struct digits<-9, 9> { BOOST_STATIC_CONSTEXPR int r2 = 3, r10 = 1; }; |
68 | template <> struct digits<-10, 10> { BOOST_STATIC_CONSTEXPR int r2 = 3, r10 = 1; }; |
69 | template <> struct digits<-15, 15> { BOOST_STATIC_CONSTEXPR int r2 = 3, r10 = 1; }; |
70 | |
71 | } |
72 | |
73 | namespace std { |
74 | |
75 | template <int Min, int Max> |
76 | class numeric_limits<custom_int<Min, Max> > : public numeric_limits<int> |
77 | { |
78 | public: |
79 | static BOOST_CONSTEXPR custom_int<Min, Max> max() BOOST_NOEXCEPT_OR_NOTHROW { return Max; } |
80 | static BOOST_CONSTEXPR custom_int<Min, Max> min() BOOST_NOEXCEPT_OR_NOTHROW { return Min; } |
81 | static BOOST_CONSTEXPR custom_int<Min, Max> lowest() BOOST_NOEXCEPT_OR_NOTHROW { return min(); } |
82 | BOOST_STATIC_ASSERT_MSG(numeric_limits<int>::radix == 2, "hardcoded for digits of radix 2" ); |
83 | BOOST_STATIC_CONSTEXPR int digits = utils::digits<Min, Max>::r2; |
84 | BOOST_STATIC_CONSTEXPR int digits10 = utils::digits<Min, Max>::r10; |
85 | }; |
86 | |
87 | } |
88 | |
89 | namespace qi = boost::spirit::qi; |
90 | |
91 | template <typename T, int Base, int MaxDigits> |
92 | void test_overflow_handling(char const* begin, char const* end, int i) |
93 | { |
94 | // Check that parser fails on overflow |
95 | BOOST_STATIC_ASSERT_MSG(std::numeric_limits<T>::is_bounded, "tests prerequest" ); |
96 | BOOST_ASSERT_MSG(MaxDigits == -1 || static_cast<int>(std::pow(float(Base), MaxDigits)) > T::max, |
97 | "test prerequest" ); |
98 | int initial = Base - i % Base; // just a 'random' non-equal to i number |
99 | T x(initial); |
100 | char const* it = begin; |
101 | bool r = qi::extract_int<T, Base, 1, MaxDigits>::call(it, end, x); |
102 | if (T::min <= i && i <= T::max) { |
103 | BOOST_TEST(r); |
104 | BOOST_TEST(it == end); |
105 | BOOST_TEST_EQ(x, i); |
106 | } |
107 | else { |
108 | BOOST_TEST(!r); |
109 | BOOST_TEST(it == begin); |
110 | } |
111 | } |
112 | |
113 | template <typename T, int Base> |
114 | void test_unparsed_digits_are_not_consumed(char const* it, char const* end, int i) |
115 | { |
116 | // Check that unparsed digits are not consumed |
117 | BOOST_STATIC_ASSERT_MSG(T::min <= -Base+1, "test prerequest" ); |
118 | BOOST_STATIC_ASSERT_MSG(T::max >= Base-1, "test prerequest" ); |
119 | bool has_sign = *it == '+' || *it == '-'; |
120 | char const* begin = it; |
121 | int initial = Base - i % Base; // just a 'random' non-equal to i number |
122 | T x(initial); |
123 | bool r = qi::extract_int<T, Base, 1, 1>::call(it, end, x); |
124 | BOOST_TEST(r); |
125 | if (-Base < i && i < Base) { |
126 | BOOST_TEST(it == end); |
127 | BOOST_TEST_EQ(x, i); |
128 | } |
129 | else { |
130 | BOOST_TEST_EQ(end - it, (end - begin) - 1 - has_sign); |
131 | BOOST_TEST_EQ(x, i / Base); |
132 | } |
133 | } |
134 | |
135 | template <typename T, int Base> |
136 | void test_ignore_overflow_digits(char const* it, char const* end, int i) |
137 | { |
138 | // TODO: Check accumulating too? |
139 | if (i < 0) return; // extract_int does not support IgnoreOverflowDigits |
140 | |
141 | bool has_sign = *it == '+' || *it == '-'; |
142 | char const* begin = it; |
143 | int initial = Base - i % Base; // just a 'random' non-equal to i number |
144 | T x(initial); |
145 | BOOST_TEST((qi::extract_uint<T, Base, 1, -1, false, true>::call(it, end, x))); |
146 | if (T::min <= i && i <= T::max) { |
147 | BOOST_TEST(it == end); |
148 | BOOST_TEST_EQ(x, i); |
149 | } |
150 | else { |
151 | BOOST_TEST_EQ(it - begin, (qi::detail::digits_traits<T, Base>::value) + has_sign); |
152 | if (Base == std::numeric_limits<T>::radix) |
153 | BOOST_TEST_EQ(it - begin, std::numeric_limits<T>::digits + has_sign); |
154 | if (Base == 10) |
155 | BOOST_TEST_EQ(it - begin, std::numeric_limits<T>::digits10 + has_sign); |
156 | int expected = i; |
157 | for (char const* p = it; p < end; ++p) expected /= Base; |
158 | BOOST_TEST_EQ(x, expected); |
159 | } |
160 | } |
161 | |
162 | template <typename T, int Base> |
163 | void run_tests(char const* begin, char const* end, int i) |
164 | { |
165 | // Check that parser fails on overflow |
166 | test_overflow_handling<T, Base, -1>(begin, end, i); |
167 | // Check that MaxDigits > digits10 behave like MaxDigits=-1 |
168 | test_overflow_handling<T, Base, 2>(begin, end, i); |
169 | // Check that unparsed digits are not consumed |
170 | test_unparsed_digits_are_not_consumed<T, Base>(begin, end, i); |
171 | // Check that IgnoreOverflowDigits does what we expect |
172 | test_ignore_overflow_digits<T, Base>(begin, end, i); |
173 | } |
174 | |
175 | int main() |
176 | { |
177 | for (int i = -30; i <= 30; ++i) { |
178 | std::ostringstream oss; |
179 | oss << i; |
180 | std::string s = oss.str(); |
181 | char const* begin = s.data(), *const end = begin + s.size(); |
182 | |
183 | // log(Base, abs(MinOrMax) + 1) == digits |
184 | run_tests<custom_int<-9, 9>, 10>(begin, end, i); |
185 | // (MinOrMax % Base) == 0 |
186 | run_tests<custom_int<-10, 10>, 10>(begin, end, i); |
187 | // (MinOrMax % Base) != 0 |
188 | run_tests<custom_int<-15, 15>, 10>(begin, end, i); |
189 | } |
190 | |
191 | return boost::report_errors(); |
192 | } |
193 | |