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
14template<typename T>
15T 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
22template<typename T>
23void 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
41void 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
328void test_main(int /*argc*/, char** /*argv*/)
329{
330 test_plural_expr();
331}
332

source code of boost/libs/locale/test/test_catalog.cpp