Line data Source code
1 : // Copyright (c) 2011-2023 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/as2js
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : // as2js
20 : //
21 : #include <as2js/string.h>
22 : #include <as2js/exception.h>
23 :
24 :
25 : // self
26 : //
27 : #include "catch_main.h"
28 :
29 :
30 : // C++
31 : //
32 : #include <algorithm>
33 : #include <cstring>
34 : #include <iomanip>
35 :
36 :
37 : // last include
38 : //
39 : #include <snapdev/poison.h>
40 :
41 :
42 :
43 : namespace
44 : {
45 :
46 :
47 :
48 10051 : bool close_double(double a, double b, double epsilon)
49 : {
50 10051 : return a >= b - epsilon && a <= b + epsilon;
51 : }
52 :
53 :
54 :
55 : }
56 : // no name namespace
57 :
58 :
59 :
60 :
61 1 : CATCH_TEST_CASE("string_empty", "[string][type]")
62 : {
63 1 : CATCH_START_SECTION("string: empty string validity")
64 : {
65 : // a little extra test, make sure a string is empty on
66 : // creation without anything
67 : //
68 1 : std::string str1;
69 1 : CATCH_REQUIRE(as2js::valid(str1));
70 1 : }
71 1 : CATCH_END_SECTION()
72 1 : }
73 :
74 :
75 1 : CATCH_TEST_CASE("string_bad_utf8", "[string][type]")
76 : {
77 1 : CATCH_START_SECTION("string: bad UTF-8 sequences")
78 : {
79 : // UTF-8 starts with 0xC0 to 0xF4 and those must be followed by
80 : // 0x80 to 0xBF bytes anything else is incorrect
81 : //
82 54 : for(int i(0xC0); i <= 0xF4; ++i)
83 : {
84 : // too small a number just after 0xA0 to 0xF4
85 : //
86 6784 : for(int j(0x01); j <= 0x7F; ++j)
87 : {
88 6731 : char const buf[3]{
89 : static_cast<char>(i),
90 : static_cast<char>(j),
91 : '\0'
92 6731 : };
93 13462 : std::string const bad_utf8(buf);
94 6731 : CATCH_REQUIRE_FALSE(as2js::valid(bad_utf8));
95 :
96 6731 : std::string const start_string(SNAP_CATCH2_NAMESPACE::random_string(1, 100, SNAP_CATCH2_NAMESPACE::character_t::CHARACTER_ASCII));
97 6731 : std::string const end_string(SNAP_CATCH2_NAMESPACE::random_string(1, 100, SNAP_CATCH2_NAMESPACE::character_t::CHARACTER_ASCII));
98 6731 : CATCH_REQUIRE(as2js::valid(start_string));
99 6731 : CATCH_REQUIRE(as2js::valid(end_string));
100 :
101 : // make sure it gets caught anywhere in a string
102 : //
103 13462 : std::string const complete_string(start_string + bad_utf8 + end_string);
104 6731 : CATCH_REQUIRE_FALSE(as2js::valid(complete_string));
105 6731 : }
106 :
107 : // too large a number just after 0xA0 to 0xFF
108 : //
109 3445 : for(int j(0xC0); j <= 0xFF; ++j)
110 : {
111 3392 : char buf[3]{
112 : static_cast<char>(i),
113 : static_cast<char>(j),
114 : '\0'
115 3392 : };
116 6784 : std::string const bad_utf8(buf);
117 3392 : CATCH_REQUIRE_FALSE(as2js::valid(bad_utf8));
118 :
119 3392 : std::string const start_string(SNAP_CATCH2_NAMESPACE::random_string(1, 100, SNAP_CATCH2_NAMESPACE::character_t::CHARACTER_ASCII));
120 3392 : std::string const end_string(SNAP_CATCH2_NAMESPACE::random_string(1, 100, SNAP_CATCH2_NAMESPACE::character_t::CHARACTER_ASCII));
121 3392 : CATCH_REQUIRE(as2js::valid(start_string));
122 3392 : CATCH_REQUIRE(as2js::valid(end_string));
123 :
124 : // make sure it gets caught anywhere in a string
125 : //
126 6784 : std::string const complete_string(start_string + bad_utf8 + end_string);
127 3392 : CATCH_REQUIRE_FALSE(as2js::valid(complete_string));
128 3392 : }
129 :
130 : // note: the libutf8 already has many tests so I won't check
131 : // every single possibility of invalid UTF-8; if there is
132 : // an issue with such, we should verify with libutf8 instead
133 : }
134 : }
135 1 : CATCH_END_SECTION()
136 1 : }
137 :
138 :
139 3 : CATCH_TEST_CASE("string", "[string][type]")
140 : {
141 3 : CATCH_START_SECTION("string: check valid characters")
142 : {
143 1112065 : for(char32_t c(0); c < 0x110000; ++c)
144 : {
145 : // skip the surrogates at once
146 : //
147 1112064 : if(c == 0xD800)
148 : {
149 1 : c = 0xE000;
150 : }
151 1112064 : CATCH_REQUIRE(as2js::valid_character(c));
152 : }
153 : }
154 3 : CATCH_END_SECTION()
155 :
156 3 : CATCH_START_SECTION("string: check surrogates (not valid UTF-32)")
157 : {
158 2049 : for(char32_t c(0xD800); c < 0xE000; ++c)
159 : {
160 2048 : CATCH_REQUIRE_FALSE(as2js::valid_character(c));
161 : }
162 : }
163 3 : CATCH_END_SECTION()
164 :
165 3 : CATCH_START_SECTION("string: check outside range (not valid UTF-32)")
166 : {
167 1001 : for(int i(0); i < 1000; ++i)
168 : {
169 1000 : char32_t c(0);
170 : do
171 : {
172 1000 : SNAP_CATCH2_NAMESPACE::random(c);
173 : }
174 1000 : while(c < 0x110000);
175 1000 : CATCH_REQUIRE_FALSE(as2js::valid_character(c));
176 : }
177 : }
178 3 : CATCH_END_SECTION()
179 3 : }
180 :
181 :
182 15 : CATCH_TEST_CASE("string_number", "[string][type][number]")
183 : {
184 15 : CATCH_START_SECTION("string_number: empty string is 0, 0.0, and false")
185 : {
186 1 : std::string str;
187 1 : CATCH_REQUIRE(as2js::is_integer(str));
188 1 : CATCH_REQUIRE(as2js::is_floating_point(str));
189 1 : CATCH_REQUIRE(as2js::is_number(str));
190 1 : CATCH_REQUIRE(as2js::to_integer(str) == 0);
191 : #pragma GCC diagnostic push
192 : #pragma GCC diagnostic ignored "-Wfloat-equal"
193 1 : bool const is_equal(as2js::to_floating_point(str) == 0.0);
194 : #pragma GCC diagnostic pop
195 1 : CATCH_REQUIRE(is_equal);
196 1 : CATCH_REQUIRE_FALSE(as2js::is_true(str));
197 1 : }
198 15 : CATCH_END_SECTION()
199 :
200 15 : CATCH_START_SECTION("string_number: a lone sign (+ or -)")
201 : {
202 2 : std::string str("+");
203 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
204 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
205 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
206 1 : CATCH_REQUIRE(as2js::is_true(str));
207 :
208 1 : str = "-";
209 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
210 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
211 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
212 1 : CATCH_REQUIRE(as2js::is_true(str));
213 1 : }
214 15 : CATCH_END_SECTION()
215 :
216 15 : CATCH_START_SECTION("string_number: a period alone is not a floating point number")
217 : {
218 2 : std::string str(".");
219 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
220 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
221 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
222 1 : CATCH_REQUIRE(as2js::is_true(str));
223 :
224 1 : str = "+.";
225 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
226 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
227 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
228 1 : CATCH_REQUIRE(as2js::is_true(str));
229 :
230 1 : str = "-.";
231 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
232 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
233 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
234 1 : CATCH_REQUIRE(as2js::is_true(str));
235 :
236 1 : str = "!.5";
237 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
238 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
239 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
240 1 : CATCH_REQUIRE(as2js::is_true(str));
241 1 : }
242 15 : CATCH_END_SECTION()
243 :
244 15 : CATCH_START_SECTION("string_number: just one letter, even an hexadecimal letter, fails")
245 : {
246 7 : for(int c('a'); c <= 'f'; ++c)
247 : {
248 12 : std::string lower(1, c);
249 6 : CATCH_REQUIRE_FALSE(as2js::is_integer(lower));
250 6 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(lower));
251 6 : CATCH_REQUIRE_FALSE(as2js::is_number(lower));
252 6 : CATCH_REQUIRE(as2js::is_true(lower));
253 :
254 12 : std::string upper(1, c & ~0x20);
255 6 : CATCH_REQUIRE_FALSE(as2js::is_integer(upper));
256 6 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(upper));
257 6 : CATCH_REQUIRE_FALSE(as2js::is_number(upper));
258 6 : CATCH_REQUIRE(as2js::is_true(upper));
259 6 : }
260 : }
261 15 : CATCH_END_SECTION()
262 :
263 15 : CATCH_START_SECTION("string_number: no integral part means not a number (lowercase)")
264 : {
265 2 : std::string str("xyz");
266 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
267 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
268 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
269 1 : CATCH_REQUIRE(as2js::is_true(str));
270 1 : }
271 15 : CATCH_END_SECTION()
272 :
273 15 : CATCH_START_SECTION("string_number: no integral part means not a number (uppercase)")
274 : {
275 2 : std::string str("XYZ");
276 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
277 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
278 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
279 1 : CATCH_REQUIRE(as2js::is_true(str));
280 1 : }
281 15 : CATCH_END_SECTION()
282 :
283 15 : CATCH_START_SECTION("string_number: an exponent must be followed by a number")
284 : {
285 2 : std::string str("31.4159e");
286 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
287 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
288 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
289 1 : CATCH_REQUIRE(as2js::is_true(str));
290 :
291 : // adding a sign is not enough
292 : //
293 1 : str += '+';
294 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
295 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
296 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
297 1 : CATCH_REQUIRE(as2js::is_true(str));
298 :
299 1 : str[str.length() - 1] = '-';
300 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
301 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
302 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
303 1 : CATCH_REQUIRE(as2js::is_true(str));
304 1 : }
305 15 : CATCH_END_SECTION()
306 :
307 15 : CATCH_START_SECTION("string_number: 0x and 0X are not hexadecimal numbers")
308 : {
309 : {
310 2 : std::string str("0x");
311 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
312 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
313 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
314 1 : CATCH_REQUIRE_THROWS_MATCHES(
315 : as2js::to_integer(str)
316 : , as2js::internal_error
317 : , Catch::Matchers::ExceptionMessage(
318 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
319 1 : CATCH_REQUIRE(std::isnan(as2js::to_floating_point(str)));
320 1 : CATCH_REQUIRE(as2js::is_true(str));
321 1 : }
322 :
323 : {
324 2 : std::string str("0X");
325 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
326 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
327 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
328 1 : CATCH_REQUIRE_THROWS_MATCHES(
329 : as2js::to_integer(str)
330 : , as2js::internal_error
331 : , Catch::Matchers::ExceptionMessage(
332 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
333 1 : CATCH_REQUIRE(std::isnan(as2js::to_floating_point(str)));
334 1 : CATCH_REQUIRE(as2js::is_true(str));
335 1 : }
336 : }
337 15 : CATCH_END_SECTION()
338 :
339 15 : CATCH_START_SECTION("string_number: strings with a <utf-8 char: a+aron> are not numbers")
340 : {
341 : { // straight UTF-8 char not a digit at all
342 2 : std::string str("\xC3\xA5");
343 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
344 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
345 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
346 1 : CATCH_REQUIRE_THROWS_MATCHES(
347 : as2js::to_integer(str)
348 : , as2js::internal_error
349 : , Catch::Matchers::ExceptionMessage(
350 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
351 1 : CATCH_REQUIRE(std::isnan(as2js::to_floating_point(str)));
352 1 : CATCH_REQUIRE(as2js::is_true(str));
353 1 : }
354 :
355 : {
356 2 : std::string str("0xABC\xC3\xA5");
357 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
358 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
359 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
360 1 : CATCH_REQUIRE_THROWS_MATCHES(
361 : as2js::to_integer(str)
362 : , as2js::internal_error
363 : , Catch::Matchers::ExceptionMessage(
364 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
365 1 : CATCH_REQUIRE(std::isnan(as2js::to_floating_point(str)));
366 1 : CATCH_REQUIRE(as2js::is_true(str));
367 1 : }
368 : }
369 15 : CATCH_END_SECTION()
370 :
371 15 : CATCH_START_SECTION("string_number: 0g/0z and 0G/0Z represents nothing useful")
372 : {
373 : {
374 2 : std::string str("0g");
375 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
376 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
377 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
378 1 : CATCH_REQUIRE_THROWS_MATCHES(
379 : as2js::to_integer(str)
380 : , as2js::internal_error
381 : , Catch::Matchers::ExceptionMessage(
382 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
383 1 : CATCH_REQUIRE(std::isnan(as2js::to_floating_point(str)));
384 1 : CATCH_REQUIRE(as2js::is_true(str));
385 1 : }
386 :
387 : {
388 2 : std::string str("0z");
389 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
390 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
391 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
392 1 : CATCH_REQUIRE_THROWS_MATCHES(
393 : as2js::to_integer(str)
394 : , as2js::internal_error
395 : , Catch::Matchers::ExceptionMessage(
396 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
397 1 : CATCH_REQUIRE(std::isnan(as2js::to_floating_point(str)));
398 1 : CATCH_REQUIRE(as2js::is_true(str));
399 1 : }
400 :
401 : {
402 2 : std::string str("0G");
403 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
404 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
405 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
406 1 : CATCH_REQUIRE_THROWS_MATCHES(
407 : as2js::to_integer(str)
408 : , as2js::internal_error
409 : , Catch::Matchers::ExceptionMessage(
410 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
411 1 : CATCH_REQUIRE(std::isnan(as2js::to_floating_point(str)));
412 1 : CATCH_REQUIRE(as2js::is_true(str));
413 1 : }
414 :
415 : {
416 2 : std::string str("0Z");
417 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
418 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
419 1 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
420 1 : CATCH_REQUIRE_THROWS_MATCHES(
421 : as2js::to_integer(str)
422 : , as2js::internal_error
423 : , Catch::Matchers::ExceptionMessage(
424 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
425 1 : CATCH_REQUIRE(std::isnan(as2js::to_floating_point(str)));
426 1 : CATCH_REQUIRE(as2js::is_true(str));
427 1 : }
428 : }
429 15 : CATCH_END_SECTION()
430 :
431 15 : CATCH_START_SECTION("string_number: octal is not detected; we have only decimal and hexadecimal")
432 : {
433 : // octal is not supported here, show that the string is
434 : // seen as a decimal number
435 : //
436 2 : std::string str("071");
437 1 : CATCH_REQUIRE(as2js::is_integer(str));
438 1 : CATCH_REQUIRE(as2js::is_floating_point(str));
439 1 : CATCH_REQUIRE(as2js::is_number(str));
440 1 : CATCH_REQUIRE(as2js::to_integer(str) == 71);
441 : #pragma GCC diagnostic push
442 : #pragma GCC diagnostic ignored "-Wfloat-equal"
443 1 : bool const is_equal(as2js::to_floating_point(str) == 71.0);
444 : #pragma GCC diagnostic pop
445 1 : CATCH_REQUIRE(is_equal);
446 1 : CATCH_REQUIRE(as2js::is_true(str));
447 1 : }
448 15 : CATCH_END_SECTION()
449 :
450 15 : CATCH_START_SECTION("string_number: integers -100,000 to +100,000")
451 : {
452 200002 : for(int64_t i(-100'000); i <= 100'000; ++i)
453 : {
454 : // decimal
455 : {
456 200001 : std::stringstream ss;
457 200001 : ss << (i >= 0 && (rand() & 1) ? "+" : "") << i;
458 200001 : std::string str(ss.str());
459 200001 : CATCH_REQUIRE(as2js::is_integer(str));
460 200001 : CATCH_REQUIRE(as2js::is_floating_point(str));
461 200001 : CATCH_REQUIRE(as2js::is_number(str));
462 200001 : CATCH_REQUIRE(as2js::to_integer(str) == i);
463 : #pragma GCC diagnostic push
464 : #pragma GCC diagnostic ignored "-Wfloat-equal"
465 200001 : bool const is_equal(as2js::to_floating_point(str) == static_cast<double>(i));
466 : #pragma GCC diagnostic pop
467 200001 : CATCH_REQUIRE(is_equal);
468 200001 : CATCH_REQUIRE(as2js::is_true(str));
469 :
470 : // no letter can follow an integer
471 : //
472 200001 : ss << (rand() % 26 + 'a');
473 200001 : CATCH_REQUIRE(as2js::is_integer(ss.str()));
474 200001 : }
475 :
476 : // hexadecimal
477 : {
478 : // not that in C/C++ hexadecimal numbers cannot really be
479 : // negative; in JavaScript it's fine
480 : //
481 200001 : std::stringstream ss;
482 200001 : ss << (i < 0 ? "-" : (rand() & 1 ? "+" : "")) << "0" << (rand() & 1 ? "x" : "X") << std::hex << labs(i);
483 200001 : std::string const str(ss.str());
484 200001 : CATCH_REQUIRE(as2js::is_integer(str));
485 200001 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
486 200001 : CATCH_REQUIRE(as2js::is_number(str));
487 200001 : CATCH_REQUIRE(as2js::to_integer(str) == i);
488 200001 : CATCH_REQUIRE(std::isnan(as2js::to_floating_point(str)));
489 200001 : CATCH_REQUIRE(as2js::is_true(str));
490 :
491 : // add an 'h' at the end and it fails the integer test
492 : //
493 200001 : ss << 'h';
494 200001 : CATCH_REQUIRE_FALSE(as2js::is_integer(ss.str()));
495 200001 : }
496 :
497 200001 : if(i >= 0)
498 : {
499 : // some characters at the start mean this is not a number
500 : //
501 100001 : std::stringstream ss;
502 100001 : ss << ',' << i;
503 100001 : std::string str(ss.str());
504 100001 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
505 100001 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
506 100001 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
507 :
508 100001 : str[0] = '/';
509 100001 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
510 100001 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
511 100001 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
512 :
513 100001 : str[0] = '|';
514 100001 : CATCH_REQUIRE_FALSE(as2js::is_integer(str));
515 100001 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str));
516 100001 : CATCH_REQUIRE_FALSE(as2js::is_number(str));
517 100001 : }
518 : }
519 : }
520 15 : CATCH_END_SECTION()
521 :
522 15 : CATCH_START_SECTION("string_number: floating points")
523 : {
524 3351 : for(double i(-1000.00); i <= 1000.00; i += (rand() % 120) / 100.0)
525 : {
526 3350 : std::stringstream ss;
527 3350 : ss << i;
528 6700 : if(ss.str().find('e') != std::string::npos
529 6700 : || ss.str().find('E') != std::string::npos)
530 : {
531 : // this happens with numbers very close to zero and the
532 : // system decides to write them as '1e-12' for example
533 : //
534 : // Note: does not matter if it does not happen
535 : //
536 0 : continue;
537 : }
538 3350 : std::string const str1(ss.str());
539 3350 : std::int64_t const integer1(lrint(i));
540 3350 : bool const is_integer1(std::find(str1.cbegin(), str1.cend(), '.') == str1.cend());
541 3350 : std::string str1_without0;
542 :
543 3350 : CATCH_REQUIRE(as2js::is_integer(str1) == is_integer1);
544 3350 : CATCH_REQUIRE(as2js::is_floating_point(str1));
545 3350 : if(str1.length() >= 3
546 3349 : && str1[0] == '0'
547 6699 : && str1[1] == '.')
548 : {
549 : // test without the starting '0' and it works too
550 : //
551 1 : str1_without0 = str1.substr(1);
552 1 : CATCH_REQUIRE(as2js::is_floating_point(str1_without0));
553 : }
554 3350 : CATCH_REQUIRE(as2js::is_number(str1));
555 3350 : CATCH_REQUIRE(as2js::is_true(str1));
556 3350 : if(is_integer1)
557 : {
558 35 : CATCH_REQUIRE(as2js::to_integer(str1) == integer1);
559 : }
560 : else
561 : {
562 3315 : CATCH_REQUIRE_THROWS_MATCHES(
563 : as2js::to_integer(str1)
564 : , as2js::internal_error
565 : , Catch::Matchers::ExceptionMessage(
566 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
567 : }
568 3350 : CATCH_REQUIRE(close_double(as2js::to_floating_point(str1), i, 0.01));
569 3350 : if(!str1_without0.empty())
570 : {
571 1 : CATCH_REQUIRE(close_double(as2js::to_floating_point(str1_without0), i, 0.01));
572 : }
573 :
574 : // add x 1000 as an exponent
575 3350 : ss << "e" << ((rand() & 1) != 0 ? "+" : "") << "3";
576 3350 : std::string const str2(ss.str());
577 : // the 'e' "breaks" the integer test in JavaScript
578 3350 : CATCH_REQUIRE_FALSE(as2js::is_integer(str2));
579 3350 : CATCH_REQUIRE(as2js::is_floating_point(str2));
580 3350 : CATCH_REQUIRE(as2js::is_number(str2));
581 3350 : CATCH_REQUIRE(as2js::is_true(str2));
582 3350 : CATCH_REQUIRE_THROWS_MATCHES(
583 : as2js::to_integer(str2)
584 : , as2js::internal_error
585 : , Catch::Matchers::ExceptionMessage(
586 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
587 3350 : CATCH_REQUIRE(close_double(as2js::to_floating_point(str2), i * 1000.0, 0.01));
588 :
589 : // add / 1000 as an exponent
590 3350 : ss.str(""); // reset the string
591 3350 : ss << i << "e-3";
592 3350 : std::string const str3(ss.str());
593 : // the 'e' "breaks" the integer test in JavaScript
594 3350 : CATCH_REQUIRE_FALSE(as2js::is_integer(str3));
595 3350 : CATCH_REQUIRE(as2js::is_floating_point(str3));
596 3350 : CATCH_REQUIRE(as2js::is_number(str3));
597 3350 : CATCH_REQUIRE(as2js::is_true(str3));
598 3350 : CATCH_REQUIRE_THROWS_MATCHES(
599 : as2js::to_integer(str3)
600 : , as2js::internal_error
601 : , Catch::Matchers::ExceptionMessage(
602 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
603 3350 : CATCH_REQUIRE(close_double(as2js::to_floating_point(str3), i / 1000.0, 0.00001));
604 3350 : }
605 :
606 : // the exponent must start with e and + or -, other characters are
607 : // not valid
608 : //
609 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("3.5e,7"));
610 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("-7.02E|9"));
611 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("3.5e!7"));
612 1 : CATCH_REQUIRE(as2js::is_floating_point("3.5e09")); // this one is valid, the exponent can start with a '0'!
613 1 : CATCH_REQUIRE(as2js::is_floating_point("3.5e90")); // this one is an edge case, number starting with '9'
614 1 : CATCH_REQUIRE(as2js::is_floating_point("3.5e0123456789")); // another edge case
615 :
616 : // without at least one digit, it's not a valid floating point
617 : //
618 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("-"));
619 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("+"));
620 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("-."));
621 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("+."));
622 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("-e"));
623 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("+e"));
624 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("-E"));
625 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("+E"));
626 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("-.e"));
627 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("+.e"));
628 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("-.E"));
629 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("+.E"));
630 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("e-3"));
631 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("e+4"));
632 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("E-5"));
633 1 : CATCH_REQUIRE_FALSE(as2js::is_floating_point("E+6"));
634 : }
635 15 : CATCH_END_SECTION()
636 :
637 15 : CATCH_START_SECTION("string_number: random 64 bits integers")
638 : {
639 : // a few more using random
640 100001 : for(int i(0); i < 100000; ++i)
641 : {
642 100000 : std::int64_t value(0);
643 100000 : SNAP_CATCH2_NAMESPACE::random(value);
644 100000 : std::stringstream ss;
645 100000 : ss << value;
646 100000 : std::string const str(ss.str());
647 100000 : CATCH_REQUIRE(as2js::is_integer(str));
648 100000 : CATCH_REQUIRE(as2js::is_floating_point(str));
649 100000 : CATCH_REQUIRE(as2js::is_number(str));
650 100000 : CATCH_REQUIRE(as2js::is_true(str));
651 100000 : CATCH_REQUIRE(as2js::to_integer(str) == value);
652 :
653 : // this is important since double mantissa is only 52 bits
654 : // and here our integral numbers are 64 bits
655 : //
656 100000 : as2js::floating_point const flt1(as2js::to_floating_point(str));
657 100000 : as2js::floating_point const flt2(static_cast<as2js::floating_point::value_type>(value));
658 100000 : CATCH_REQUIRE(flt1.nearly_equal(flt2, 0.0001));
659 100000 : CATCH_REQUIRE(flt2.nearly_equal(flt1, 0.0001));
660 100000 : }
661 : }
662 15 : CATCH_END_SECTION()
663 :
664 15 : CATCH_START_SECTION("string_number: NULL value")
665 : {
666 : // test a few non-hexadecimal numbers
667 : //
668 101 : for(int i(0); i < 100; ++i)
669 : {
670 : // get a character which is not a valid hex digit and not '\0'
671 : // and not 0x7F (Del)
672 : //
673 : char c;
674 : do
675 : {
676 124 : c = static_cast<char>(rand() % 0x7D + 1);
677 : }
678 69 : while((c >= '0' && c <= '9')
679 112 : || (c >= 'a' && c <= 'f')
680 230 : || (c >= 'A' && c <= 'F'));
681 :
682 : // bad character is right at the beginning of the hex number
683 100 : std::stringstream ss1;
684 100 : ss1 << "0" << (rand() & 1 ? "x" : "X") << c << "123ABC";
685 100 : std::string str1(ss1.str());
686 100 : CATCH_REQUIRE_FALSE(as2js::is_integer(str1));
687 100 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str1));
688 100 : CATCH_REQUIRE_FALSE(as2js::is_number(str1));
689 100 : CATCH_REQUIRE(as2js::is_true(str1));
690 100 : CATCH_REQUIRE_THROWS_MATCHES(
691 : as2js::to_integer(str1)
692 : , as2js::internal_error
693 : , Catch::Matchers::ExceptionMessage(
694 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
695 100 : CATCH_REQUIRE(std::isnan(as2js::to_floating_point(str1)));
696 :
697 : // invalid character is in the middle of the hex number
698 : //
699 100 : std::stringstream ss2;
700 100 : ss2 << "0" << (rand() & 1 ? "x" : "X") << "123" << c << "ABC";
701 100 : std::string str2(ss2.str());
702 100 : CATCH_REQUIRE_FALSE(as2js::is_integer(str2));
703 100 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str2));
704 100 : CATCH_REQUIRE_FALSE(as2js::is_number(str2));
705 100 : CATCH_REQUIRE(as2js::is_true(str2));
706 100 : CATCH_REQUIRE_THROWS_MATCHES(
707 : as2js::to_integer(str2)
708 : , as2js::internal_error
709 : , Catch::Matchers::ExceptionMessage(
710 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
711 100 : CATCH_REQUIRE(std::isnan(as2js::to_floating_point(str2)));
712 :
713 : // invalid character is at the very end of the hex number
714 : //
715 100 : std::stringstream ss3;
716 100 : ss3 << "0" << (rand() & 1 ? "x" : "X") << "123ABC" << c;
717 100 : std::string str3(ss3.str());
718 100 : CATCH_REQUIRE_FALSE(as2js::is_integer(str3));
719 100 : CATCH_REQUIRE_FALSE(as2js::is_floating_point(str3));
720 100 : CATCH_REQUIRE_FALSE(as2js::is_number(str3));
721 100 : CATCH_REQUIRE(as2js::is_true(str3));
722 100 : CATCH_REQUIRE_THROWS_MATCHES(
723 : as2js::to_integer(str3)
724 : , as2js::internal_error
725 : , Catch::Matchers::ExceptionMessage(
726 : "internal_error: to_integer(std::string const & s) called with an invalid integer."));
727 100 : CATCH_REQUIRE(std::isnan(as2js::to_floating_point(str3)));
728 100 : }
729 : }
730 15 : CATCH_END_SECTION()
731 15 : }
732 :
733 :
734 18 : CATCH_TEST_CASE("string_simplify", "[string][type]")
735 : {
736 18 : CATCH_START_SECTION("string_simplify: only spaces")
737 : {
738 2 : std::string const str(" ");
739 1 : std::string const simplified(as2js::simplify(str));
740 1 : CATCH_REQUIRE(simplified == "0");
741 1 : }
742 18 : CATCH_END_SECTION()
743 :
744 18 : CATCH_START_SECTION("string_simplify: starting spaces")
745 : {
746 2 : std::string const str(" blah");
747 1 : std::string const simplified(as2js::simplify(str));
748 1 : CATCH_REQUIRE(simplified == "blah");
749 1 : }
750 18 : CATCH_END_SECTION()
751 :
752 18 : CATCH_START_SECTION("string_simplify: ending spaces")
753 : {
754 2 : std::string const str("blah ");
755 1 : std::string const simplified(as2js::simplify(str));
756 1 : CATCH_REQUIRE(simplified == "blah");
757 1 : }
758 18 : CATCH_END_SECTION()
759 :
760 18 : CATCH_START_SECTION("string_simplify: starting & ending spaces")
761 : {
762 2 : std::string const str(" blah ");
763 1 : std::string const simplified(as2js::simplify(str));
764 1 : CATCH_REQUIRE(simplified == "blah");
765 1 : }
766 18 : CATCH_END_SECTION()
767 :
768 18 : CATCH_START_SECTION("string_simplify: inside spaces")
769 : {
770 2 : std::string const str("blah foo");
771 1 : std::string const simplified(as2js::simplify(str));
772 1 : CATCH_REQUIRE(simplified == "blah foo");
773 1 : }
774 18 : CATCH_END_SECTION()
775 :
776 18 : CATCH_START_SECTION("string_simplify: simplify starting, inside, and ending spaces")
777 : {
778 2 : std::string const str(" blah foo ");
779 1 : std::string const simplified(as2js::simplify(str));
780 1 : CATCH_REQUIRE(simplified == "blah foo");
781 1 : }
782 18 : CATCH_END_SECTION()
783 :
784 18 : CATCH_START_SECTION("string_simplify: simplify spaces including newlines")
785 : {
786 2 : std::string const str("blah \n foo");
787 1 : std::string const simplified(as2js::simplify(str));
788 1 : CATCH_REQUIRE(simplified == "blah foo");
789 1 : }
790 18 : CATCH_END_SECTION()
791 :
792 18 : CATCH_START_SECTION("string_simplify: empty string becomes zero")
793 : {
794 1 : std::string const str;
795 1 : std::string const simplified(as2js::simplify(str));
796 1 : CATCH_REQUIRE(simplified == "0");
797 1 : }
798 18 : CATCH_END_SECTION()
799 :
800 18 : CATCH_START_SECTION("string_simplify: spaces only string becomes zero")
801 : {
802 2 : std::string const str(" ");
803 1 : std::string const simplified(as2js::simplify(str));
804 1 : CATCH_REQUIRE(simplified == "0");
805 1 : }
806 18 : CATCH_END_SECTION()
807 :
808 18 : CATCH_START_SECTION("string_simplify: simplify number with spaces around")
809 : {
810 2 : std::string const str(" 3.14159 ");
811 1 : std::string const simplified(as2js::simplify(str));
812 1 : CATCH_REQUIRE(simplified == "3.14159");
813 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(simplified));
814 1 : CATCH_REQUIRE(as2js::is_floating_point(simplified));
815 1 : CATCH_REQUIRE(as2js::is_number(simplified));
816 1 : CATCH_REQUIRE(as2js::floating_point(as2js::to_floating_point(simplified)).nearly_equal(3.14159, 1.0e-8));
817 1 : }
818 18 : CATCH_END_SECTION()
819 :
820 18 : CATCH_START_SECTION("string_simplify: simplify number with left over")
821 : {
822 2 : std::string const str(" 3.14159 ignore that part ");
823 1 : std::string const simplified(as2js::simplify(str));
824 1 : CATCH_REQUIRE(simplified == "3.14159");
825 1 : CATCH_REQUIRE_FALSE(as2js::is_integer(simplified));
826 1 : CATCH_REQUIRE(as2js::is_floating_point(simplified));
827 1 : CATCH_REQUIRE(as2js::is_number(simplified));
828 1 : CATCH_REQUIRE(as2js::floating_point(as2js::to_floating_point(simplified)).nearly_equal(3.14159, 1.0e-8));
829 1 : }
830 18 : CATCH_END_SECTION()
831 :
832 18 : CATCH_START_SECTION("string_simplify: simplify positive number with left over")
833 : {
834 2 : std::string const str(" +3.14159 ignore that part ");
835 1 : std::string const simplified(as2js::simplify(str));
836 1 : CATCH_REQUIRE(simplified == "+3.14159");
837 1 : CATCH_REQUIRE(as2js::is_floating_point(simplified));
838 1 : CATCH_REQUIRE(as2js::is_number(simplified));
839 1 : CATCH_REQUIRE(as2js::floating_point(as2js::to_floating_point(simplified)).nearly_equal(3.14159, 1.0e-8));
840 1 : }
841 18 : CATCH_END_SECTION()
842 :
843 18 : CATCH_START_SECTION("string_simplify: simplify negative integer with left over")
844 : {
845 2 : std::string const str(" -314159 ignore that part ");
846 1 : std::string const simplified(as2js::simplify(str));
847 1 : CATCH_REQUIRE(simplified == "-314159");
848 1 : CATCH_REQUIRE(as2js::is_integer(simplified));
849 1 : CATCH_REQUIRE(as2js::to_integer(simplified) == -314159);
850 1 : CATCH_REQUIRE(as2js::is_floating_point(simplified));
851 1 : CATCH_REQUIRE(as2js::is_number(simplified));
852 1 : CATCH_REQUIRE(as2js::floating_point(as2js::to_floating_point(simplified)).nearly_equal(-314159.0, 1.0e-8));
853 1 : }
854 18 : CATCH_END_SECTION()
855 :
856 18 : CATCH_START_SECTION("string_simplify: simplify the positive number with exponent and left over")
857 : {
858 2 : std::string const str(" +0.00314159e3 ignore that part ");
859 1 : std::string const simplified(as2js::simplify(str));
860 1 : CATCH_REQUIRE(simplified == "+0.00314159e3");
861 1 : CATCH_REQUIRE(as2js::is_floating_point(simplified));
862 1 : CATCH_REQUIRE(as2js::is_number(simplified));
863 1 : CATCH_REQUIRE(as2js::floating_point(as2js::to_floating_point(simplified)).nearly_equal(3.14159, 1e-8));
864 1 : }
865 18 : CATCH_END_SECTION()
866 :
867 18 : CATCH_START_SECTION("string_simplify: simplify the positive number with positive exponent and left over")
868 : {
869 2 : std::string const str(" +0.00314159e+3 ignore that part ");
870 1 : std::string const simplified(as2js::simplify(str));
871 1 : CATCH_REQUIRE(simplified == "+0.00314159e+3");
872 1 : CATCH_REQUIRE(as2js::is_floating_point(simplified));
873 1 : CATCH_REQUIRE(as2js::is_number(simplified));
874 1 : CATCH_REQUIRE(as2js::floating_point(as2js::to_floating_point(simplified)).nearly_equal(3.14159, 1e-8));
875 1 : }
876 18 : CATCH_END_SECTION()
877 :
878 18 : CATCH_START_SECTION("string_simplify: simplify the negative number with negative exponent and left over")
879 : {
880 2 : std::string const str(" -314159e-5 ignore that part ");
881 1 : std::string const simplified(as2js::simplify(str));
882 1 : CATCH_REQUIRE(simplified == "-314159");
883 1 : CATCH_REQUIRE(as2js::is_integer(simplified));
884 1 : CATCH_REQUIRE(as2js::to_integer(simplified) == -314159);
885 1 : CATCH_REQUIRE(as2js::is_floating_point(simplified));
886 1 : CATCH_REQUIRE(as2js::is_number(simplified));
887 1 : CATCH_REQUIRE(as2js::floating_point(as2js::to_floating_point(simplified)).nearly_equal(-314159, 1e-8));
888 1 : }
889 18 : CATCH_END_SECTION()
890 :
891 18 : CATCH_START_SECTION("string_simplify: simplify negative number with negative exponent and left over")
892 : {
893 2 : std::string str(" -314159.e-5 ignore that part ");
894 1 : std::string simplified(as2js::simplify(str));
895 1 : CATCH_REQUIRE(simplified == "-314159.e-5");
896 1 : CATCH_REQUIRE(as2js::is_floating_point(simplified));
897 1 : CATCH_REQUIRE(as2js::is_number(simplified));
898 1 : CATCH_REQUIRE(as2js::floating_point(as2js::to_floating_point(simplified)).nearly_equal(-3.14159, 1e-8));
899 1 : }
900 18 : CATCH_END_SECTION()
901 :
902 18 : CATCH_START_SECTION("string_simplify: simplify negative number with large negative exponent and left over")
903 : {
904 2 : std::string str(" -314159.0e-105ignorethatpart");
905 1 : std::string simplified(as2js::simplify(str));
906 1 : CATCH_REQUIRE(simplified == "-314159.0e-105");
907 1 : CATCH_REQUIRE(as2js::is_floating_point(simplified));
908 1 : CATCH_REQUIRE(as2js::is_number(simplified));
909 1 : CATCH_REQUIRE(as2js::floating_point(as2js::to_floating_point(simplified)).nearly_equal(-3.14159e-100, 1e-8));
910 1 : }
911 18 : CATCH_END_SECTION()
912 18 : }
913 :
914 :
915 :
916 : // vim: ts=4 sw=4 et
|