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/binary.h>
22 :
23 :
24 :
25 : // self
26 : //
27 : #include "catch_main.h"
28 :
29 :
30 : // snapdev
31 : //
32 : #include <snapdev/file_contents.h>
33 : #include <snapdev/glob_to_list.h>
34 : #include <snapdev/pathinfo.h>
35 : #include <snapdev/to_lower.h>
36 :
37 :
38 : // C++
39 : //
40 : #include <iomanip>
41 :
42 :
43 : // last include
44 : //
45 : #include <snapdev/poison.h>
46 :
47 :
48 :
49 : namespace
50 : {
51 :
52 :
53 :
54 25 : void run_script(std::string const & s)
55 : {
56 50 : std::string cmd("export AS2JS_RC='");
57 25 : cmd += SNAP_CATCH2_NAMESPACE::g_binary_dir();
58 25 : cmd += "' && ";
59 : //cmd += "gdb -ex \"catch throws\" -ex \"run\" -args ";
60 25 : cmd += SNAP_CATCH2_NAMESPACE::g_binary_dir();
61 25 : cmd += "/tools/as2js -b -o ";
62 25 : cmd += SNAP_CATCH2_NAMESPACE::g_binary_dir();
63 25 : cmd += "/tests/a.out ";
64 25 : cmd += s;
65 :
66 : // for the script to work, the compiler must find the scripts directory
67 : // which is defined in the rc file
68 : //
69 : {
70 25 : std::ofstream rc("as2js/as2js.rc");
71 : rc << "{\"scripts\":\""
72 25 : << SNAP_CATCH2_NAMESPACE::g_source_dir()
73 25 : << "/scripts\"}\n";
74 25 : }
75 :
76 : // first compile the file
77 : //
78 : std::cout
79 : << "--- compile script to binary with command \""
80 : << cmd
81 25 : << "\".\n";
82 25 : int const r(system(cmd.c_str()));
83 25 : CATCH_REQUIRE(r == 0);
84 50 : }
85 :
86 :
87 :
88 : enum class value_type_t : std::uint16_t
89 : {
90 : VALUE_TYPE_UNDEFINED,
91 : VALUE_TYPE_BOOLEAN,
92 : VALUE_TYPE_INTEGER,
93 : VALUE_TYPE_FLOATING_POINT,
94 : VALUE_TYPE_STRING,
95 : };
96 :
97 : typedef std::uint16_t flags_t;
98 :
99 : constexpr flags_t const VALUE_FLAG_IN = 0x0000;
100 : constexpr flags_t const VALUE_FLAG_OUT = 0x0001;
101 :
102 : struct value_flags
103 : {
104 : std::string f_value = std::string();
105 : value_type_t f_type = value_type_t::VALUE_TYPE_UNDEFINED;
106 : flags_t f_flags = 0;
107 :
108 460 : void set_type(value_type_t t)
109 : {
110 460 : CATCH_REQUIRE(f_type == value_type_t::VALUE_TYPE_UNDEFINED); // trying to set the type more than once?
111 460 : f_type = t;
112 460 : }
113 :
114 933 : void set_type(std::string t)
115 : {
116 933 : t = snapdev::to_lower(t);
117 933 : if(t == "boolean")
118 : {
119 218 : set_type(value_type_t::VALUE_TYPE_BOOLEAN);
120 : }
121 715 : else if(t == "integer")
122 : {
123 0 : set_type(value_type_t::VALUE_TYPE_INTEGER);
124 : }
125 715 : else if(t == "double")
126 : {
127 149 : set_type(value_type_t::VALUE_TYPE_FLOATING_POINT);
128 : }
129 566 : else if(t == "string")
130 : {
131 27 : set_type(value_type_t::VALUE_TYPE_STRING);
132 : }
133 539 : else if(t == "in")
134 : {
135 0 : f_flags |= VALUE_FLAG_IN;
136 : }
137 539 : else if(t == "out")
138 : {
139 539 : f_flags |= VALUE_FLAG_OUT;
140 : }
141 : else
142 : {
143 0 : CATCH_REQUIRE(t == "");
144 : }
145 933 : }
146 :
147 1374 : value_type_t get_type() const
148 : {
149 1374 : return f_type == value_type_t::VALUE_TYPE_UNDEFINED
150 1374 : ? value_type_t::VALUE_TYPE_INTEGER
151 1374 : : f_type;
152 : }
153 :
154 1846 : bool is_out() const
155 : {
156 1846 : return (f_flags & VALUE_FLAG_OUT) != 0;
157 : }
158 : };
159 :
160 :
161 : typedef std::map<std::string, value_flags> variable_t;
162 :
163 : struct meta
164 : {
165 : variable_t f_variables = variable_t();
166 : value_flags f_result = value_flags();
167 : };
168 :
169 :
170 25 : meta load_script_meta(std::string const & s)
171 : {
172 25 : meta result;
173 :
174 125 : std::string const filename(snapdev::pathinfo::replace_suffix(s, ".ajs", ".meta"));
175 :
176 25 : snapdev::file_contents meta(filename);
177 25 : CATCH_REQUIRE(meta.read_all());
178 :
179 25 : std::string const & str(meta.contents());
180 :
181 25 : char const * m(str.c_str());
182 1569 : while(*m != '\0')
183 : {
184 1544 : while(*m == ' ' || *m == '\t')
185 : {
186 0 : ++m;
187 : }
188 1544 : if(*m == '\0')
189 : {
190 0 : return result;
191 : }
192 1544 : if(*m == '#')
193 : {
194 : // skip comments
195 : //
196 654 : while(*m != '\n')
197 : {
198 595 : if(*m == '\0')
199 : {
200 0 : return result;
201 : }
202 595 : ++m;
203 : }
204 59 : continue;
205 : }
206 :
207 1485 : if(*m == '\n')
208 : {
209 : // empty line
210 : //
211 844 : ++m;
212 844 : continue;
213 : }
214 :
215 : // we have either a variable or a result preceeded by keywords:
216 : //
217 : // [<keyword>] (<result>)
218 : // [<keyword>] <name>=<value>
219 : //
220 641 : std::string name;
221 641 : value_flags value;
222 12955 : while(*m != '=' && *m != '(')
223 : {
224 12314 : CATCH_REQUIRE(*m != '\n');
225 12314 : CATCH_REQUIRE(*m != '\0');
226 12314 : if(isspace(*m))
227 : {
228 : // skip all spaces
229 : //
230 : do
231 : {
232 933 : ++m;
233 : }
234 933 : while(*m == ' ' || *m == '\t');
235 :
236 : // check whether it is the last word before the '=', if so,
237 : // it is taken as the variable name and not a <keyword>
238 : //
239 933 : if(*m == '=')
240 : {
241 0 : break;
242 : }
243 :
244 : // parse as a type or flag
245 : //
246 933 : value.set_type(name);
247 933 : name.clear();
248 : }
249 : else
250 : {
251 11381 : name += *m;
252 11381 : ++m;
253 : }
254 : }
255 :
256 641 : if(*m == '(')
257 : {
258 25 : CATCH_REQUIRE(name.empty());
259 25 : result.f_result = value;
260 :
261 : // expected result
262 : // (<value>)
263 : //
264 25 : ++m;
265 25 : CATCH_REQUIRE(result.f_result.f_value.empty()); // prevent double definition
266 336 : while(*m != ')')
267 : {
268 311 : CATCH_REQUIRE(*m != '\n');
269 311 : CATCH_REQUIRE(*m != '\0');
270 311 : result.f_result.f_value += *m;
271 311 : ++m;
272 : }
273 25 : ++m;
274 :
275 25 : if(result.f_result.f_value.length() >= 2
276 25 : && (result.f_result.f_value[0] == '"' || result.f_result.f_value[0] == '\'')
277 5 : && result.f_result.f_value[0] == result.f_result.f_value.back()
278 50 : && result.f_result.get_type() == value_type_t::VALUE_TYPE_INTEGER)
279 : {
280 : // default to INTEGER, unless value is surrounded by quotes
281 : //
282 1 : result.f_result.set_type(value_type_t::VALUE_TYPE_STRING);
283 : }
284 :
285 25 : if(result.f_result.get_type() == value_type_t::VALUE_TYPE_STRING
286 5 : && result.f_result.f_value.length() >= 2
287 5 : && (result.f_result.f_value[0] == '"' || result.f_result.f_value[0] == '\'')
288 30 : && result.f_result.f_value[0] == result.f_result.f_value.back())
289 : {
290 : // remove quotes around string
291 : //
292 5 : result.f_result.f_value = result.f_result.f_value.substr(1, result.f_result.f_value.length() - 2);
293 : }
294 :
295 25 : while(*m == ' ' || *m == '\t')
296 : {
297 : //std::cerr << "--- result [" << result.f_result << "] -> skipping [" << static_cast<int>(*m) << "]\n";
298 0 : ++m;
299 : }
300 25 : if(*m != '\n')
301 : {
302 0 : CATCH_REQUIRE(*m == '\0');
303 0 : return result;
304 : }
305 25 : ++m;
306 25 : continue;
307 25 : }
308 616 : ++m; // skip '='
309 :
310 616 : CATCH_REQUIRE_FALSE(name.empty());
311 :
312 5195 : while(*m != '\n' && *m != '\0')
313 : {
314 4579 : value.f_value += *m;
315 4579 : ++m;
316 : }
317 :
318 : // TODO: add support for spaces/tabs after the closing quotation
319 : //
320 616 : if(value.f_value.length() >= 2
321 574 : && (value.f_value[0] == '"' || value.f_value[0] == '\'')
322 88 : && value.f_value[0] == value.f_value.back()
323 1190 : && value.get_type() == value_type_t::VALUE_TYPE_INTEGER)
324 : {
325 : // default to INTEGER, unless value is surrounded by quotes
326 : //
327 65 : value.set_type(value_type_t::VALUE_TYPE_STRING);
328 : }
329 :
330 616 : if(value.get_type() == value_type_t::VALUE_TYPE_STRING
331 88 : && value.f_value.length() >= 2
332 88 : && (value.f_value[0] == '"' || value.f_value[0] == '\'')
333 704 : && value.f_value[0] == value.f_value.back())
334 : {
335 : // remove quotes around string
336 : //
337 88 : value.f_value = value.f_value.substr(1, value.f_value.length() - 2);
338 : }
339 :
340 : // when defining an "out" the name must be distinct otherwise it would
341 : // smash the "in"
342 : //
343 616 : if(value.is_out())
344 : {
345 539 : name = "<-" + name;
346 : }
347 : //std::cerr << "--- found name [" << name << "]\n";
348 616 : result.f_variables[name] = value;
349 666 : }
350 :
351 25 : return result;
352 25 : }
353 :
354 :
355 25 : void execute(meta const & m)
356 : {
357 25 : std::string filename(SNAP_CATCH2_NAMESPACE::g_binary_dir());
358 25 : filename += "/tests/a.out";
359 :
360 25 : as2js::running_file script;
361 25 : CATCH_REQUIRE(script.load(filename));
362 :
363 640 : for(auto const & var : m.f_variables)
364 : {
365 615 : if(!var.second.is_out())
366 : {
367 : // TODO: support all types of variables
368 : //
369 77 : switch(var.second.get_type())
370 : {
371 9 : case value_type_t::VALUE_TYPE_BOOLEAN:
372 : {
373 9 : bool value(false);
374 9 : if(var.second.f_value == "true")
375 : {
376 6 : value = true;
377 : }
378 : else
379 : {
380 : // value must be "true" or "false"
381 : //
382 3 : CATCH_REQUIRE(var.second.f_value == "false");
383 : }
384 9 : script.set_variable(var.first, value);
385 : }
386 9 : break;
387 :
388 26 : case value_type_t::VALUE_TYPE_INTEGER:
389 : {
390 26 : std::int64_t const value(std::stoll(var.second.f_value, nullptr, 0));
391 26 : script.set_variable(var.first, value);
392 : }
393 26 : break;
394 :
395 25 : case value_type_t::VALUE_TYPE_FLOATING_POINT:
396 : {
397 25 : double const value(std::stod(var.second.f_value, nullptr));
398 25 : script.set_variable(var.first, value);
399 : }
400 25 : break;
401 :
402 17 : case value_type_t::VALUE_TYPE_STRING:
403 17 : script.set_variable(var.first, var.second.f_value);
404 17 : break;
405 :
406 : //case value_type_t::VALUE_TYPE_UNDEFINED:
407 0 : default:
408 0 : CATCH_REQUIRE(!"variable type not yet implemented or somehow set to UNDEFINED.");
409 0 : break;
410 :
411 : }
412 : }
413 : }
414 :
415 50 : as2js::binary_result result;
416 :
417 25 : script.run(result);
418 :
419 25 : switch(m.f_result.get_type())
420 : {
421 6 : case value_type_t::VALUE_TYPE_BOOLEAN:
422 : {
423 6 : bool expected_result(false);
424 6 : if(m.f_result.f_value == "true")
425 : {
426 0 : expected_result = true;
427 : }
428 : else
429 : {
430 : // value must be "true" or "false"
431 : //
432 6 : CATCH_REQUIRE(m.f_result.f_value == "false");
433 : }
434 6 : CATCH_REQUIRE(result.get_boolean() == expected_result);
435 : }
436 6 : break;
437 :
438 7 : case value_type_t::VALUE_TYPE_INTEGER:
439 : {
440 7 : std::int64_t const expected_result(std::stoll(m.f_result.f_value, nullptr, 0));
441 7 : if(result.get_integer() != expected_result)
442 : {
443 0 : std::cerr << "--- (integer result) differs: " << result.get_integer() << " != " << expected_result << "\n";
444 : }
445 7 : CATCH_REQUIRE(result.get_integer() == expected_result);
446 : }
447 7 : break;
448 :
449 7 : case value_type_t::VALUE_TYPE_FLOATING_POINT:
450 : {
451 7 : double const expected_result(std::stod(m.f_result.f_value, nullptr));
452 7 : double const final_result(result.get_floating_point());
453 7 : if(!SNAP_CATCH2_NAMESPACE::nearly_equal(final_result, expected_result))
454 : {
455 0 : double const * result_ptr(&final_result);
456 0 : double const * expected_ptr(&expected_result);
457 : std::cerr
458 0 : << "--- (double result) differs: "
459 0 : << final_result
460 0 : << " != "
461 0 : << expected_result
462 : << " (0x"
463 0 : << std::setw(16) << std::setfill('0') << std::hex
464 0 : << *reinterpret_cast<std::uint64_t const *>(result_ptr)
465 : << " != 0x"
466 0 : << std::setw(16)
467 0 : << *reinterpret_cast<std::uint64_t const *>(expected_ptr)
468 0 : << std::dec
469 0 : << ")\n";
470 : }
471 7 : CATCH_REQUIRE(SNAP_CATCH2_NAMESPACE::nearly_equal(final_result, expected_result));
472 : }
473 : break;
474 :
475 5 : case value_type_t::VALUE_TYPE_STRING:
476 : {
477 5 : std::string const expected_result(m.f_result.f_value);
478 5 : if(result.get_string() != expected_result)
479 : {
480 0 : std::cerr << "--- (string result) differs: " << result.get_string() << " != " << expected_result << "\n";
481 : }
482 5 : CATCH_REQUIRE(result.get_string() == expected_result);
483 5 : }
484 : break;
485 :
486 : //case value_type_t::VALUE_TYPE_UNDEFINED:
487 0 : default:
488 0 : CATCH_REQUIRE(!"variable type not yet implemented or somehow set to UNDEFINED.");
489 0 : break;
490 :
491 : }
492 :
493 640 : for(auto const & var : m.f_variables)
494 : {
495 615 : if(var.second.is_out())
496 : {
497 : // TODO: support all types of variables
498 : //
499 538 : std::string const name(var.first.substr(2));
500 538 : switch(var.second.get_type())
501 : {
502 202 : case value_type_t::VALUE_TYPE_BOOLEAN:
503 : {
504 202 : bool expected_value(false);
505 202 : if(var.second.f_value == "true")
506 : {
507 100 : expected_value = true;
508 : }
509 : else
510 : {
511 : // value must be "true" or "false"
512 : //
513 102 : CATCH_REQUIRE(var.second.f_value == "false");
514 : }
515 202 : bool returned_value(0);
516 202 : script.get_variable(name, returned_value);
517 202 : if(returned_value != expected_value)
518 : {
519 : std::cerr
520 : << "--- invalid boolean result in \""
521 0 : << var.first
522 0 : << "\".\n";
523 : }
524 202 : CATCH_REQUIRE(returned_value == expected_value);
525 : }
526 : break;
527 :
528 148 : case value_type_t::VALUE_TYPE_INTEGER:
529 : {
530 148 : std::int64_t const expected_value(std::stoll(var.second.f_value, nullptr, 0));
531 148 : std::int64_t returned_value(0);
532 148 : script.get_variable(name, returned_value);
533 148 : if(returned_value != expected_value)
534 : {
535 : std::cerr
536 : << "--- invalid integer result in \""
537 0 : << var.first
538 0 : << "\".\n";
539 : }
540 148 : CATCH_REQUIRE(returned_value == expected_value);
541 : }
542 : break;
543 :
544 117 : case value_type_t::VALUE_TYPE_FLOATING_POINT:
545 : {
546 117 : double returned_value(0.0);
547 117 : script.get_variable(name, returned_value);
548 117 : if(var.second.f_value == "NaN")
549 : {
550 1 : CATCH_REQUIRE(std::isnan(returned_value));
551 : }
552 : else
553 : {
554 116 : constexpr double const epsilon(0.0000000000000033);
555 116 : double const expected_result(std::stod(var.second.f_value, nullptr));
556 116 : if(!SNAP_CATCH2_NAMESPACE::nearly_equal(returned_value, expected_result, epsilon))
557 : {
558 0 : double const * value_ptr(&returned_value);
559 0 : double const * expected_ptr(&expected_result);
560 : std::cerr
561 : << "--- invalid floating point result in \""
562 0 : << var.first
563 : << "\" -- "
564 0 : << std::setprecision(20)
565 0 : << returned_value
566 0 : << " != "
567 0 : << expected_result
568 : << " (0x"
569 0 : << std::setw(16) << std::setfill('0') << std::hex
570 0 : << *reinterpret_cast<std::uint64_t const *>(value_ptr)
571 : << " != 0x"
572 0 : << std::setw(16)
573 0 : << *reinterpret_cast<std::uint64_t const *>(expected_ptr)
574 0 : << std::dec
575 0 : << ")\n";
576 : }
577 116 : CATCH_REQUIRE(SNAP_CATCH2_NAMESPACE::nearly_equal(returned_value, expected_result, epsilon));
578 : }
579 : }
580 : break;
581 :
582 71 : case value_type_t::VALUE_TYPE_STRING:
583 : {
584 71 : std::string const expected_value(var.second.f_value);
585 71 : std::string returned_value;
586 71 : script.get_variable(name, returned_value);
587 71 : if(returned_value != expected_value)
588 : {
589 : std::cerr
590 : << "--- invalid string result in \""
591 0 : << var.first
592 0 : << "\".\n";
593 : }
594 71 : CATCH_REQUIRE(returned_value == expected_value);
595 71 : }
596 : break;
597 :
598 : //case value_type_t::VALUE_TYPE_UNDEFINED,
599 0 : default:
600 : std::cerr
601 0 : << "variable type "
602 0 : << static_cast<int>(var.second.get_type())
603 : << " not yet implemented in catch_binary.cpp ("
604 : << __FILE__
605 0 : << ':'
606 : << __LINE__
607 0 : << ")\n";
608 0 : CATCH_REQUIRE(!"variable type not yet implemented or somehow set to UNDEFINED.");
609 0 : break;
610 :
611 : }
612 538 : }
613 : }
614 50 : }
615 :
616 :
617 :
618 : }
619 :
620 :
621 :
622 :
623 :
624 :
625 :
626 1 : CATCH_TEST_CASE("binary_integer_operators", "[binary][integer]")
627 : {
628 1 : CATCH_START_SECTION("binary_integer_operators: test binary operators for integers")
629 : {
630 1 : snapdev::glob_to_list<std::list<std::string>> scripts;
631 1 : CATCH_REQUIRE(scripts.read_path<snapdev::glob_to_list_flag_t::GLOB_FLAG_NONE>
632 : (SNAP_CATCH2_NAMESPACE::g_source_dir()
633 : + "/tests/binary/integer_operator_*.ajs"));
634 9 : for(auto const & s : scripts)
635 : {
636 8 : run_script(s);
637 8 : meta m(load_script_meta(s));
638 8 : execute(m);
639 8 : }
640 1 : }
641 1 : CATCH_END_SECTION()
642 1 : }
643 :
644 1 : CATCH_TEST_CASE("binary_double_operators", "[binary][double][floating_point]")
645 : {
646 1 : CATCH_START_SECTION("binary_double_operators: test binary operators for doubles")
647 : {
648 1 : snapdev::glob_to_list<std::list<std::string>> scripts;
649 1 : CATCH_REQUIRE(scripts.read_path<snapdev::glob_to_list_flag_t::GLOB_FLAG_NONE>
650 : (SNAP_CATCH2_NAMESPACE::g_source_dir()
651 : + "/tests/binary/double_operator_*.ajs"));
652 9 : for(auto const & s : scripts)
653 : {
654 8 : run_script(s);
655 8 : meta m(load_script_meta(s));
656 8 : execute(m);
657 8 : }
658 1 : }
659 1 : CATCH_END_SECTION()
660 1 : }
661 :
662 1 : CATCH_TEST_CASE("binary_boolean_operators", "[binary][boolean]")
663 : {
664 1 : CATCH_START_SECTION("binary_boolean_operators: test binary operators for booleans")
665 : {
666 1 : snapdev::glob_to_list<std::list<std::string>> scripts;
667 1 : CATCH_REQUIRE(scripts.read_path<snapdev::glob_to_list_flag_t::GLOB_FLAG_NONE>
668 : (SNAP_CATCH2_NAMESPACE::g_source_dir()
669 : + "/tests/binary/boolean_operator_*.ajs"));
670 4 : for(auto const & s : scripts)
671 : {
672 3 : run_script(s);
673 3 : meta m(load_script_meta(s));
674 3 : execute(m);
675 3 : }
676 1 : }
677 1 : CATCH_END_SECTION()
678 1 : }
679 :
680 1 : CATCH_TEST_CASE("binary_string_operators", "[binary][string]")
681 : {
682 1 : CATCH_START_SECTION("binary_string_operators: test binary operators for strings")
683 : {
684 1 : snapdev::glob_to_list<std::list<std::string>> scripts;
685 1 : CATCH_REQUIRE(scripts.read_path<snapdev::glob_to_list_flag_t::GLOB_FLAG_NONE>
686 : (SNAP_CATCH2_NAMESPACE::g_source_dir()
687 : + "/tests/binary/string_operator_*.ajs"));
688 7 : for(auto const & s : scripts)
689 : {
690 6 : run_script(s);
691 6 : meta m(load_script_meta(s));
692 6 : execute(m);
693 6 : }
694 1 : }
695 1 : CATCH_END_SECTION()
696 1 : }
697 :
698 :
699 :
700 : // vim: ts=4 sw=4 et
|