1 | // |
2 | // Copyright (c) 2009-2011 Artyom Beilis (Tonkikh) |
3 | // Copyright (c) 2021-2023 Alexander Grund |
4 | // |
5 | // Distributed under the Boost Software License, Version 1.0. |
6 | // https://www.boost.org/LICENSE_1_0.txt |
7 | |
8 | #include "../src/boost/locale/shared/mo_lambda.hpp" |
9 | #include "boostLocale/test/unit_test.hpp" |
10 | #include <iostream> |
11 | #include <limits> |
12 | #include <random> |
13 | |
14 | template<typename T> |
15 | T getRandValue(const T min, const T max) |
16 | { |
17 | static std::mt19937 gen{std::random_device{}()}; |
18 | std::uniform_int_distribution<T> distrib(min, max); |
19 | return distrib(gen); |
20 | } |
21 | |
22 | template<typename T> |
23 | void test_plural_expr_rand(const T& ref, const char* expr) |
24 | { |
25 | constexpr auto minVal = std::numeric_limits<long long>::min() / 1024; |
26 | constexpr auto maxVal = std::numeric_limits<long long>::max() / 1024; |
27 | const auto ptr = boost::locale::gnu_gettext::lambda::compile(c_expression: expr); |
28 | TEST(ptr); |
29 | constexpr int number_of_tries = 256; |
30 | for(int i = 0; i < number_of_tries; i++) { |
31 | const auto n = getRandValue(min: minVal, max: maxVal); |
32 | const auto result = ptr(n); |
33 | const auto refResult = ref(n); |
34 | if(result != refResult) { |
35 | std::cerr << "Expression: " << expr << "; n=" << n << '\n'; // LCOV_EXCL_LINE |
36 | TEST_EQ(result, refResult); // LCOV_EXCL_LINE |
37 | } |
38 | } |
39 | } |
40 | |
41 | void test_plural_expr() |
42 | { |
43 | using boost::locale::gnu_gettext::lambda::compile; |
44 | #define COMPILE_PLURAL_EXPR(expr) \ |
45 | []() { \ |
46 | auto ptr = compile(expr); \ |
47 | TEST(ptr); \ |
48 | return ptr; \ |
49 | }() |
50 | #define TEST_EQ_EXPR(expr, rhs) test_eq_impl(COMPILE_PLURAL_EXPR(expr)(0), rhs, expr, __LINE__) |
51 | // Number only |
52 | TEST_EQ_EXPR("0" , 0); |
53 | TEST_EQ_EXPR("42" , 42); |
54 | BOOST_LOCALE_START_CONST_CONDITION |
55 | if(sizeof(long) >= 4) { |
56 | BOOST_LOCALE_END_CONST_CONDITION |
57 | TEST_EQ_EXPR("2147483647" , 2147483647); // largest signed 4 byte value |
58 | } |
59 | BOOST_LOCALE_START_CONST_CONDITION |
60 | if(sizeof(long) > 4) { |
61 | BOOST_LOCALE_END_CONST_CONDITION |
62 | TEST_EQ_EXPR("4294967295" , 4294967295); // largest 4 byte value |
63 | TEST_EQ_EXPR("100000000000" , 100000000000); |
64 | } |
65 | |
66 | // Unary |
67 | TEST_EQ_EXPR("!0" , 1); |
68 | TEST_EQ_EXPR("!1" , 0); |
69 | TEST_EQ_EXPR("!42" , 0); |
70 | // Arithmetic |
71 | TEST_EQ_EXPR("30 / 5" , 6); |
72 | TEST_EQ_EXPR("3 * 7" , 21); |
73 | TEST_EQ_EXPR("14 % 3" , 2); |
74 | TEST_EQ_EXPR("2 + 5" , 7); |
75 | TEST_EQ_EXPR("7 - 5" , 2); |
76 | // Comparison |
77 | TEST_EQ_EXPR("30 > 5" , 1); |
78 | TEST_EQ_EXPR("5 > 5" , 0); |
79 | TEST_EQ_EXPR("4 > 5" , 0); |
80 | TEST_EQ_EXPR("30 < 5" , 0); |
81 | TEST_EQ_EXPR("5 < 5" , 0); |
82 | TEST_EQ_EXPR("4 < 5" , 1); |
83 | TEST_EQ_EXPR("30 >= 5" , 1); |
84 | TEST_EQ_EXPR("5 >= 5" , 1); |
85 | TEST_EQ_EXPR("4 >= 5" , 0); |
86 | TEST_EQ_EXPR("30 <= 5" , 0); |
87 | TEST_EQ_EXPR("5 <= 5" , 1); |
88 | TEST_EQ_EXPR("4 <= 5" , 1); |
89 | TEST_EQ_EXPR("5 == 5" , 1); |
90 | TEST_EQ_EXPR("4 == 5" , 0); |
91 | TEST_EQ_EXPR("5 != 5" , 0); |
92 | TEST_EQ_EXPR("4 != 5" , 1); |
93 | // Logicals |
94 | TEST_EQ_EXPR("0 || 0" , 0); |
95 | TEST_EQ_EXPR("0 || 1" , 1); |
96 | TEST_EQ_EXPR("1 || 0" , 1); |
97 | TEST_EQ_EXPR("1 || 1" , 1); |
98 | TEST_EQ_EXPR("0 && 0" , 0); |
99 | TEST_EQ_EXPR("0 && 1" , 0); |
100 | TEST_EQ_EXPR("1 && 0" , 0); |
101 | TEST_EQ_EXPR("1 && 1" , 1); |
102 | // Corner case for div/mod |
103 | TEST_EQ_EXPR("0 / 0" , 0); |
104 | TEST_EQ_EXPR("1 / 0" , 0); |
105 | TEST_EQ_EXPR("20 / 0" , 0); |
106 | TEST_EQ_EXPR("0 % 0" , 0); |
107 | TEST_EQ_EXPR("1 % 0" , 0); |
108 | TEST_EQ_EXPR("20 % 0" , 0); |
109 | // Operator precedence |
110 | { |
111 | // Unary over all |
112 | TEST_EQ_EXPR("5 * -3" , -15); |
113 | TEST_EQ_EXPR("2 + -3" , -1); |
114 | TEST_EQ_EXPR("-(1-6) % --2" , 1); |
115 | TEST_EQ_EXPR("!-1" , 0); |
116 | TEST_EQ_EXPR("-!0" , -1); |
117 | TEST_EQ_EXPR("-!1" , 0); |
118 | TEST_EQ_EXPR("5 * !3" , 0); |
119 | TEST_EQ_EXPR("5 * !0" , 5); |
120 | TEST_EQ_EXPR("2 + !3" , 2); |
121 | TEST_EQ_EXPR("2 + !0" , 3); |
122 | TEST_EQ_EXPR("!(1-6) % 10" , 0); |
123 | TEST_EQ_EXPR("!!(1-6) % 10" , 1); |
124 | TEST_EQ_EXPR("!0 == 1" , 1); |
125 | TEST_EQ_EXPR("!0 || 1" , 1); |
126 | // Mul over add/sub |
127 | TEST_EQ_EXPR("5 * 3 + 1" , 16); |
128 | TEST_EQ_EXPR("1 + 3 * 5" , 16); |
129 | TEST_EQ_EXPR("5 * 3 - 1" , 14); |
130 | TEST_EQ_EXPR("10 - 2 * 3" , 4); |
131 | // Div over add/sub |
132 | TEST_EQ_EXPR("30 / 2 + 1" , 16); |
133 | TEST_EQ_EXPR("1 + 30 / 2" , 16); |
134 | TEST_EQ_EXPR("30 / 2 - 1" , 14); |
135 | TEST_EQ_EXPR("10 - 30 / 2" , -5); |
136 | // Mod over add/sub |
137 | TEST_EQ_EXPR("5 % 3 + 1" , 3); |
138 | TEST_EQ_EXPR("1 + 5 % 3" , 3); |
139 | TEST_EQ_EXPR("5 % 4 - 1" , 0); |
140 | TEST_EQ_EXPR("5 - 5 % 3" , 3); |
141 | // Same precedence of add/sub |
142 | TEST_EQ_EXPR("10 - 4 - 2" , 4); |
143 | // Same precedence of mul/div/mod |
144 | TEST_EQ_EXPR("2 * 20 / 40" , 1); |
145 | TEST_EQ_EXPR("20 / 20 * 2" , 2); |
146 | TEST_EQ_EXPR("7 * 2 % 10" , 4); |
147 | TEST_EQ_EXPR("7 % 4 * 2" , 6); |
148 | // Comparison after operation |
149 | TEST_EQ_EXPR("5 * 2 == 5 + 5" , 1); |
150 | TEST_EQ_EXPR("50 / 5 != 40 % 40" , 1); |
151 | TEST_EQ_EXPR("5 * 2 < 5 + 6" , 1); |
152 | TEST_EQ_EXPR("5 * 2 <= 5 + 6" , 1); |
153 | TEST_EQ_EXPR("50 / 5 > 40 % 40" , 1); |
154 | TEST_EQ_EXPR("50 / 5 >= 40 % 40" , 1); |
155 | // Relational before equal with counter example |
156 | TEST_EQ_EXPR("1 < 2 == 1" , 1); |
157 | TEST_EQ_EXPR("1 < (2 == 1)" , 0); |
158 | TEST_EQ_EXPR("0 == 0 < 0" , 1); |
159 | TEST_EQ_EXPR("(0 == 0) < 0" , 0); |
160 | |
161 | TEST_EQ_EXPR("1 <= 2 == 1" , 1); |
162 | TEST_EQ_EXPR("1 <= (2 == 1)" , 0); |
163 | TEST_EQ_EXPR("0 == 0 <= -1" , 1); |
164 | TEST_EQ_EXPR("(0 == 0) <= -1" , 0); |
165 | |
166 | TEST_EQ_EXPR("0 > 0 == 0" , 1); |
167 | TEST_EQ_EXPR("0 > (0 == 0)" , 0); |
168 | TEST_EQ_EXPR("1 == 2 > 1" , 1); |
169 | TEST_EQ_EXPR("(1 == 2) > 1" , 0); |
170 | |
171 | TEST_EQ_EXPR("-1 >= 0 == 0" , 1); |
172 | TEST_EQ_EXPR("-1 >= (0 == 0)" , 0); |
173 | TEST_EQ_EXPR("1 == 2 >= 1" , 1); |
174 | TEST_EQ_EXPR("(1 == 2) >= 1" , 0); |
175 | // Comparison on both sides |
176 | TEST_EQ_EXPR("0 > -2 == -1 < 0" , 1); |
177 | TEST_EQ_EXPR("((0 > -2) == -1) < 0" , 0); // RHS late |
178 | TEST_EQ_EXPR("0 > (-2 == (-1 < 0))" , 0); // LHS late |
179 | TEST_EQ_EXPR("(0 > (-2 == -1)) < 0" , 0); // both late |
180 | // Logical after comparison with counter example |
181 | TEST_EQ_EXPR("1 && 3==3" , 1); |
182 | TEST_EQ_EXPR("(1 && 3)==3" , 0); |
183 | TEST_EQ_EXPR("3==3 && 1" , 1); |
184 | TEST_EQ_EXPR("3==(3 && 1)" , 0); |
185 | TEST_EQ_EXPR("0 || 3==3" , 1); |
186 | TEST_EQ_EXPR("(0 || 3)==3" , 0); |
187 | TEST_EQ_EXPR("3==3 || 0" , 1); |
188 | TEST_EQ_EXPR("3==(3 || 0)" , 0); |
189 | // OR after AND |
190 | TEST_EQ_EXPR("0 && 0 || 1" , 1); |
191 | TEST_EQ_EXPR("0 && (0 || 1)" , 0); |
192 | TEST_EQ_EXPR("1 || 0 && 0" , 1); |
193 | } |
194 | #undef TEST_EQ_EXPR |
195 | |
196 | // Random test using the variable comparing against C++ evaluated result |
197 | #define TEST_PLURAL_EXPR(expr) \ |
198 | test_plural_expr_rand( \ |
199 | [](long long n) { \ |
200 | (void)n; \ |
201 | return expr; \ |
202 | }, \ |
203 | #expr); |
204 | TEST_PLURAL_EXPR(42); |
205 | TEST_PLURAL_EXPR(1337); |
206 | TEST_PLURAL_EXPR(n + 3); |
207 | TEST_PLURAL_EXPR(n - 3); |
208 | TEST_PLURAL_EXPR(n * 5); |
209 | TEST_PLURAL_EXPR(n / 5); |
210 | TEST_PLURAL_EXPR(n % 8); |
211 | // Parentheses |
212 | TEST_PLURAL_EXPR((5 * n) + 3); |
213 | TEST_PLURAL_EXPR(5 * (n + 3)); |
214 | // Comparisons and ternary |
215 | TEST_PLURAL_EXPR(n % 2 == 0 ? n + 5 : n * 3); |
216 | TEST_PLURAL_EXPR(n % 2 != 0 ? n + 5 : n * 3); |
217 | TEST_PLURAL_EXPR(n % 4 < 2 ? n + 5 : n * 3); |
218 | TEST_PLURAL_EXPR(n % 4 <= 2 ? n + 5 : n * 3); |
219 | TEST_PLURAL_EXPR(n % 4 > 2 ? n + 5 : n * 3); |
220 | TEST_PLURAL_EXPR(n % 4 >= 2 ? n + 5 : n * 3); |
221 | // Complex expression (e.g. for Russian) |
222 | TEST_PLURAL_EXPR((n % 10 == 1 && n % 100 != 11 ? 0 : |
223 | n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : |
224 | 2)); |
225 | #undef TEST_PLURAL_EXPR |
226 | |
227 | constexpr auto minVal = std::numeric_limits<long long>::min(); |
228 | constexpr auto maxVal = std::numeric_limits<long long>::max(); |
229 | |
230 | // E.g. Japanese |
231 | { |
232 | const auto p = COMPILE_PLURAL_EXPR("0" ); |
233 | TEST_EQ(p(0), 0); |
234 | TEST_EQ(p(minVal), 0); |
235 | TEST_EQ(p(maxVal), 0); |
236 | } |
237 | // E.g. English |
238 | { |
239 | const auto p = COMPILE_PLURAL_EXPR("(n != 1)" ); |
240 | TEST_EQ(p(0), 1); |
241 | TEST_EQ(p(1), 0); |
242 | TEST_EQ(p(minVal), 1); |
243 | TEST_EQ(p(maxVal), 1); |
244 | } |
245 | // E.g. French |
246 | { |
247 | const auto p = COMPILE_PLURAL_EXPR("(n > 1)" ); |
248 | TEST_EQ(p(0), 0); |
249 | TEST_EQ(p(1), 0); |
250 | TEST_EQ(p(2), 1); |
251 | TEST_EQ(p(minVal), 0); |
252 | TEST_EQ(p(maxVal), 1); |
253 | } |
254 | // E.g. Latvian |
255 | { |
256 | const auto p = COMPILE_PLURAL_EXPR("(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2)" ); |
257 | TEST_EQ(p(0), 2); |
258 | TEST_EQ(p(1), 0); |
259 | TEST_EQ(p(2), 1); |
260 | TEST_EQ(p(3), 1); |
261 | TEST_EQ(p(11), 1); |
262 | TEST_EQ(p(12), 1); |
263 | TEST_EQ(p(21), 0); |
264 | TEST_EQ(p(31), 0); |
265 | TEST_EQ(p(101), 0); |
266 | TEST_EQ(p(111), 1); |
267 | } |
268 | // E.g. Irish |
269 | { |
270 | const auto p = COMPILE_PLURAL_EXPR("n == 1 ? 0 : n == 2 ? 1 : 2" ); |
271 | TEST_EQ(p(0), 2); |
272 | TEST_EQ(p(1), 0); |
273 | TEST_EQ(p(2), 1); |
274 | TEST_EQ(p(3), 2); |
275 | TEST_EQ(p(4), 2); |
276 | TEST_EQ(p(minVal), 2); |
277 | TEST_EQ(p(maxVal), 2); |
278 | } |
279 | // E.g. Czech |
280 | { |
281 | const auto p = COMPILE_PLURAL_EXPR("(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2" ); |
282 | TEST_EQ(p(0), 2); |
283 | TEST_EQ(p(1), 0); |
284 | TEST_EQ(p(2), 1); |
285 | TEST_EQ(p(3), 1); |
286 | TEST_EQ(p(4), 1); |
287 | TEST_EQ(p(5), 2); |
288 | TEST_EQ(p(minVal), 2); |
289 | TEST_EQ(p(maxVal), 2); |
290 | } |
291 | #undef COMPILE_PLURAL_EXPR |
292 | // Error cases |
293 | TEST(!compile("" ) && compile("n" )); // Empty |
294 | // Invalid comparison |
295 | TEST(!compile("n===1" ) && compile("n==1" )); |
296 | TEST(!compile("n!1" ) && compile("n!=1" )); |
297 | TEST(!compile("n!<1" ) && compile("n<1" )); |
298 | TEST(!compile("n<==1" ) && compile("n<=1" )); |
299 | TEST(!compile("n<>1" ) && compile("n>1" )); |
300 | // Incomplete ternary |
301 | TEST(!compile("n==1 ?" )); |
302 | TEST(!compile("n==1 ? 1" )); |
303 | TEST(!compile("n==1 ? 1 :" )); |
304 | TEST(compile("n==1 ? 1 : 0" )); |
305 | // Missing closing parenthesis |
306 | TEST(!compile("(n==1" ) && compile("(n==1)" )); |
307 | TEST(!compile("(n + 1" ) && compile("(n + 1)" )); |
308 | TEST(!compile("(n==1 ? 1 : 2" ) && compile("(n==1 ? 1 : 2)" )); |
309 | // Extra closing parenthesis |
310 | TEST(!compile("n==1)" ) && compile("(n==1)" )); |
311 | TEST(!compile("n + 1)" ) && compile("(n + 1)" )); |
312 | TEST(!compile("n==1 ? 1 : 2)" ) && compile("(n==1 ? 1 : 2)" )); |
313 | // Empty parenthesis |
314 | TEST(!compile("n==()1" )); |
315 | TEST(!compile("n==()" )); |
316 | // Missing operator for unary op |
317 | TEST(!compile("1==!" )); |
318 | TEST(!compile("1 + -" )); |
319 | // No bitwise ops |
320 | TEST(!compile("n << 1" )); |
321 | TEST(!compile("n >> 1" )); |
322 | TEST(!compile("n & 1" )); |
323 | TEST(!compile("n | 1" )); |
324 | TEST(!compile("n ^ 1" )); |
325 | TEST(!compile("~n" )); |
326 | } |
327 | |
328 | void test_main(int /*argc*/, char** /*argv*/) |
329 | { |
330 | test_plural_expr(); |
331 | } |
332 | |