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/optimizer.h>
22 :
23 : #include <as2js/exception.h>
24 : #include <as2js/json.h>
25 : #include <as2js/message.h>
26 : #include <as2js/parser.h>
27 :
28 :
29 : // self
30 : //
31 : #include "catch_main.h"
32 :
33 :
34 : // C++
35 : //
36 : #include <cstring>
37 : #include <algorithm>
38 : #include <iomanip>
39 :
40 :
41 : // C
42 : //
43 : #include <unistd.h>
44 : #include <sys/stat.h>
45 :
46 :
47 : // last include
48 : //
49 : #include <snapdev/poison.h>
50 :
51 :
52 :
53 : namespace
54 : {
55 :
56 :
57 :
58 :
59 :
60 : //
61 : // JSON data used to test the optimizer, most of the work is in this table
62 : // these are long JSON strings! It is actually generated using the
63 : // json_to_string tool and the optimizer_data/*.json source files.
64 : //
65 : // Note: the top entries are arrays so we can execute programs in the
66 : // order we define them...
67 : //
68 : char const g_optimizer_additive[] =
69 : #include "optimizer_data/additive.ci"
70 : ;
71 : char const g_optimizer_assignments[] =
72 : #include "optimizer_data/assignments.ci"
73 : ;
74 : char const g_optimizer_bitwise[] =
75 : #include "optimizer_data/bitwise.ci"
76 : ;
77 : char const g_optimizer_compare[] =
78 : #include "optimizer_data/compare.ci"
79 : ;
80 : char const g_optimizer_conditional[] =
81 : #include "optimizer_data/conditional.ci"
82 : ;
83 : char const g_optimizer_equality[] =
84 : #include "optimizer_data/equality.ci"
85 : ;
86 : char const g_optimizer_logical[] =
87 : #include "optimizer_data/logical.ci"
88 : ;
89 : char const g_optimizer_match[] =
90 : #include "optimizer_data/match.ci"
91 : ;
92 : char const g_optimizer_multiplicative[] =
93 : #include "optimizer_data/multiplicative.ci"
94 : ;
95 : char const g_optimizer_relational[] =
96 : #include "optimizer_data/relational.ci"
97 : ;
98 : char const g_optimizer_statements[] =
99 : #include "optimizer_data/statements.ci"
100 : ;
101 :
102 :
103 :
104 :
105 :
106 :
107 :
108 :
109 : // This function runs all the tests defined in the
110 : // string 'data'
111 11 : void run_tests(char const * input_data, char const * filename)
112 : {
113 11 : if(SNAP_CATCH2_NAMESPACE::g_save_parser_tests)
114 : {
115 0 : std::ofstream json_file;
116 0 : json_file.open(filename);
117 0 : CATCH_REQUIRE(json_file.is_open());
118 : json_file
119 : << "// To properly indent this JSON you may use http://json-indent.appspot.com/\n"
120 : << input_data
121 0 : << "\n";
122 0 : }
123 :
124 11 : as2js::input_stream<std::stringstream>::pointer_t in(std::make_shared<as2js::input_stream<std::stringstream>>());
125 11 : *in << input_data;
126 11 : as2js::json::pointer_t json_data(std::make_shared<as2js::json>());
127 22 : as2js::json::json_value::pointer_t json(json_data->parse(in));
128 :
129 : // verify that the optimizer() did not fail
130 11 : CATCH_REQUIRE(!!json);
131 11 : CATCH_REQUIRE(json->get_type() == as2js::json::json_value::type_t::JSON_TYPE_ARRAY);
132 :
133 22 : std::string const name_string("name");
134 22 : std::string const program_string("program");
135 22 : std::string const verbose_string("verbose");
136 22 : std::string const slow_string("slow");
137 22 : std::string const parser_result_string("parser result");
138 22 : std::string const optimizer_result_string("optimizer result");
139 22 : std::string const expected_messages_string("expected messages");
140 :
141 11 : as2js::json::json_value::array_t const& array(json->get_array());
142 11 : size_t const max_programs(array.size());
143 434 : for(size_t idx(0); idx < max_programs; ++idx)
144 : {
145 423 : as2js::json::json_value::pointer_t prog_obj(array[idx]);
146 423 : CATCH_REQUIRE(prog_obj->get_type() == as2js::json::json_value::type_t::JSON_TYPE_OBJECT);
147 423 : as2js::json::json_value::object_t const& prog(prog_obj->get_object());
148 :
149 423 : bool verbose(false);
150 423 : as2js::json::json_value::object_t::const_iterator verbose_it(prog.find(verbose_string));
151 423 : if(verbose_it != prog.end())
152 : {
153 0 : verbose = verbose_it->second->get_type() == as2js::json::json_value::type_t::JSON_TYPE_TRUE;
154 : }
155 :
156 423 : bool slow(false);
157 423 : as2js::json::json_value::object_t::const_iterator slow_it(prog.find(slow_string));
158 423 : if(slow_it != prog.end())
159 : {
160 0 : slow = slow_it->second->get_type() == as2js::json::json_value::type_t::JSON_TYPE_TRUE;
161 : }
162 :
163 : // got a program, try to compile it with all the possible options
164 423 : as2js::json::json_value::pointer_t name(prog.find(name_string)->second);
165 423 : std::cout << " -- working on \"" << name->get_string() << "\" " << (slow ? "" : "...") << std::flush;
166 :
167 : {
168 423 : as2js::json::json_value::pointer_t program_value(prog.find(program_string)->second);
169 : //std::cerr << "prog = [" << program_value->get_string() << "]\n";
170 423 : as2js::input_stream<std::stringstream>::pointer_t prog_text(std::make_shared<as2js::input_stream<std::stringstream>>());
171 423 : *prog_text << program_value->get_string();
172 423 : as2js::options::pointer_t options(std::make_shared<as2js::options>());
173 423 : as2js::parser::pointer_t parser(std::make_shared<as2js::parser>(prog_text, options));
174 :
175 423 : SNAP_CATCH2_NAMESPACE::test_callback tc(verbose);
176 :
177 : // no errors expected while parsing (if you want to test errors
178 : // in the parser, use the catch_parser.cpp test instead)
179 : //
180 423 : as2js::node::pointer_t root(parser->parse());
181 :
182 : // verify the parser result, that way we can make sure we are
183 : // testing the tree we want to test in the optimizer
184 : //
185 423 : SNAP_CATCH2_NAMESPACE::verify_result(parser_result_string, prog.find(parser_result_string)->second, root, verbose, false);
186 :
187 : // now the optimizer may end up generating messages...
188 : // (there are not many, mainly things like division by zero
189 : // and illegal operation.)
190 : //
191 423 : as2js::json::json_value::object_t::const_iterator expected_msg_it(prog.find(expected_messages_string));
192 423 : if(expected_msg_it != prog.end())
193 : {
194 : // the expected messages value must be an array
195 : //
196 14 : as2js::message_level_t message_level(as2js::message_level_t::MESSAGE_LEVEL_INFO);
197 14 : as2js::json::json_value::array_t const& msg_array(expected_msg_it->second->get_array());
198 14 : size_t const max_msgs(msg_array.size());
199 28 : for(size_t j(0); j < max_msgs; ++j)
200 : {
201 14 : as2js::json::json_value::pointer_t message_value(msg_array[j]);
202 14 : as2js::json::json_value::object_t const& message(message_value->get_object());
203 :
204 14 : as2js::json::json_value::object_t::const_iterator const message_options_iterator(message.find("options"));
205 14 : if(message_options_iterator != message.end())
206 : {
207 : //{
208 : //as2js::json::json_value::object_t::const_iterator line_it(message.find("line #"));
209 : //if(line_it != message.end())
210 : //{
211 : // int64_t lines(line_it->second->get_int64().get());
212 : //std::cerr << "_________\nLine #" << lines << "\n";
213 : //}
214 : //else
215 : //std::cerr << "_________\nLine #<undefined>\n";
216 : //}
217 14 : SNAP_CATCH2_NAMESPACE::test_callback::expected_t expected;
218 14 : expected.f_message_level = static_cast<as2js::message_level_t>(message.find("message level")->second->get_integer().get());
219 14 : expected.f_error_code = SNAP_CATCH2_NAMESPACE::str_to_error_code(message.find("error code")->second->get_string());
220 14 : expected.f_pos.set_filename("unknown-file");
221 14 : as2js::json::json_value::object_t::const_iterator func_it(message.find("function name"));
222 14 : if(func_it == message.end())
223 : {
224 14 : expected.f_pos.set_function("unknown-func");
225 : }
226 : else
227 : {
228 0 : expected.f_pos.set_function(func_it->second->get_string());
229 : }
230 14 : as2js::json::json_value::object_t::const_iterator line_it(message.find("line #"));
231 14 : if(line_it != message.end())
232 : {
233 14 : std::int64_t lines(line_it->second->get_integer().get());
234 14 : for(std::int64_t l(1); l < lines; ++l)
235 : {
236 0 : expected.f_pos.new_line();
237 : }
238 : }
239 14 : expected.f_message = message.find("message")->second->get_string();
240 14 : tc.f_expected.push_back(expected);
241 :
242 14 : message_level = std::min(message_level, expected.f_message_level);
243 14 : }
244 14 : }
245 :
246 : // the default message level is INFO, don't change if we have
247 : // a higher level here; however, if we have a lower level,
248 : // change the message level in the as2js library
249 : //
250 14 : if(message_level < as2js::message_level_t::MESSAGE_LEVEL_INFO)
251 : {
252 0 : as2js::set_message_level(message_level);
253 : }
254 : }
255 :
256 : // run the optimizer
257 423 : as2js::optimizer::optimize(root);
258 :
259 : // the result is object which can have children
260 : // which are represented by an array of objects
261 : //
262 423 : SNAP_CATCH2_NAMESPACE::verify_result(optimizer_result_string, prog.find(optimizer_result_string)->second, root, verbose, false);
263 :
264 423 : tc.got_called();
265 423 : }
266 :
267 423 : std::cout << " OK\n";
268 423 : }
269 :
270 11 : std::cout << "\n";
271 22 : }
272 :
273 :
274 : }
275 : // no name namespace
276 :
277 :
278 :
279 :
280 :
281 1 : CATCH_TEST_CASE("optimizer_invalid_nodes", "[optimizer][invalid]")
282 : {
283 : // empty node does nothing, return false
284 : {
285 1 : as2js::node::pointer_t node;
286 1 : CATCH_REQUIRE(!as2js::optimizer::optimize(node));
287 1 : }
288 :
289 : // unknown node does nothing, return false
290 : {
291 1 : as2js::node::pointer_t node(std::make_shared<as2js::node>(as2js::node_t::NODE_UNKNOWN));
292 1 : CATCH_REQUIRE(!as2js::optimizer::optimize(node));
293 1 : CATCH_REQUIRE(node->get_type() == as2js::node_t::NODE_UNKNOWN);
294 1 : CATCH_REQUIRE(node->get_children_size() == 0);
295 1 : }
296 :
297 : // a special case where an optimization occurs on a node without a parent
298 : // (something that should not occur in a real tree)
299 : {
300 : // ADD
301 : // INTEGER = 3
302 : // INTEGER = 20
303 1 : as2js::node::pointer_t node_add(std::make_shared<as2js::node>(as2js::node_t::NODE_ADD));
304 :
305 1 : as2js::node::pointer_t node_three(std::make_shared<as2js::node>(as2js::node_t::NODE_INTEGER));
306 1 : as2js::integer three;
307 1 : three.set(3);
308 1 : node_three->set_integer(three);
309 1 : node_add->append_child(node_three);
310 :
311 1 : as2js::node::pointer_t node_twenty(std::make_shared<as2js::node>(as2js::node_t::NODE_INTEGER));
312 1 : as2js::integer twenty;
313 1 : twenty.set(20);
314 1 : node_twenty->set_integer(twenty);
315 1 : node_add->append_child(node_twenty);
316 :
317 : // optimization does not happen
318 1 : CATCH_REQUIRE_THROWS_MATCHES(
319 : as2js::optimizer::optimize(node_add)
320 : , as2js::internal_error
321 : , Catch::Matchers::ExceptionMessage(
322 : "internal_error: somehow the optimizer is optimizing a node without a parent."));
323 :
324 : // verify that nothing changed
325 1 : CATCH_REQUIRE(node_add->get_type() == as2js::node_t::NODE_ADD);
326 1 : CATCH_REQUIRE(node_add->get_children_size() == 2);
327 1 : CATCH_REQUIRE(node_three->get_type() == as2js::node_t::NODE_INTEGER);
328 1 : CATCH_REQUIRE(node_three->get_children_size() == 0);
329 1 : CATCH_REQUIRE(node_three->get_integer().compare(three) == as2js::compare_t::COMPARE_EQUAL);
330 1 : CATCH_REQUIRE(node_twenty->get_type() == as2js::node_t::NODE_INTEGER);
331 1 : CATCH_REQUIRE(node_twenty->get_children_size() == 0);
332 1 : CATCH_REQUIRE(node_twenty->get_integer().compare(twenty) == as2js::compare_t::COMPARE_EQUAL);
333 1 : }
334 1 : }
335 :
336 :
337 1 : CATCH_TEST_CASE("optimizer_additive", "[optimizer]")
338 : {
339 1 : CATCH_START_SECTION("optimizer_additive: additive (+, -)")
340 : {
341 1 : run_tests(g_optimizer_additive, "optimizer/additive.json");
342 : }
343 1 : CATCH_END_SECTION()
344 1 : }
345 :
346 :
347 1 : CATCH_TEST_CASE("optimizer_assignments", "[optimizer]")
348 : {
349 1 : CATCH_START_SECTION("parser_array: assignments (=, +=, -=, etc.)")
350 : {
351 1 : run_tests(g_optimizer_assignments, "optimizer/assignments.json");
352 : }
353 1 : CATCH_END_SECTION()
354 1 : }
355 :
356 :
357 1 : CATCH_TEST_CASE("optimizer_bitwise", "[optimizer]")
358 : {
359 1 : CATCH_START_SECTION("optimizer_bitwise: bitwise (&, |, ^)")
360 : {
361 1 : run_tests(g_optimizer_bitwise, "optimizer/bitwise.json");
362 : }
363 1 : CATCH_END_SECTION()
364 1 : }
365 :
366 :
367 1 : CATCH_TEST_CASE("optimizer_compare", "[optimizer]")
368 : {
369 1 : CATCH_START_SECTION("optimizer_compare: compare (<=>)")
370 : {
371 1 : run_tests(g_optimizer_compare, "optimizer/compare.json");
372 : }
373 1 : CATCH_END_SECTION()
374 1 : }
375 :
376 :
377 1 : CATCH_TEST_CASE("optimizer_conditional", "[optimizer]")
378 : {
379 1 : CATCH_START_SECTION("optimizer_conditional: conditional (?:, <?, >?)")
380 : {
381 1 : run_tests(g_optimizer_conditional, "optimizer/conditional.json");
382 : }
383 1 : CATCH_END_SECTION()
384 1 : }
385 :
386 :
387 1 : CATCH_TEST_CASE("optimizer_equality", "[optimizer]")
388 : {
389 1 : CATCH_START_SECTION("optimizer_equality: equality (==, !=)")
390 : {
391 1 : run_tests(g_optimizer_equality, "optimizer/equality.json");
392 : }
393 1 : CATCH_END_SECTION()
394 1 : }
395 :
396 :
397 1 : CATCH_TEST_CASE("optimizer_logical", "[optimizer]")
398 : {
399 1 : CATCH_START_SECTION("optimizer_logical: logical (&&, ||, ^^)")
400 : {
401 1 : run_tests(g_optimizer_logical, "optimizer/logical.json");
402 : }
403 1 : CATCH_END_SECTION()
404 1 : }
405 :
406 :
407 1 : CATCH_TEST_CASE("optimizer_match", "[optimizer]")
408 : {
409 1 : CATCH_START_SECTION("optimizer_match: match (~=)")
410 : {
411 1 : run_tests(g_optimizer_match, "optimizer/match.json");
412 : }
413 1 : CATCH_END_SECTION()
414 1 : }
415 :
416 :
417 1 : CATCH_TEST_CASE("optimizer_multiplicative", "[optimizer]")
418 : {
419 1 : CATCH_START_SECTION("optimizer_multiplicative: multiplicative (*, /, %)")
420 : {
421 1 : run_tests(g_optimizer_multiplicative, "optimizer/multiplicative.json");
422 : }
423 1 : CATCH_END_SECTION()
424 1 : }
425 :
426 :
427 1 : CATCH_TEST_CASE("optimizer_relational", "[optimizer]")
428 : {
429 1 : CATCH_START_SECTION("optimizer_relational: relational (<, <=, >, >=)")
430 : {
431 1 : run_tests(g_optimizer_relational, "optimizer_relational.json");
432 : }
433 1 : CATCH_END_SECTION()
434 1 : }
435 :
436 :
437 1 : CATCH_TEST_CASE("optimizer_statements", "[optimizer]")
438 : {
439 1 : CATCH_START_SECTION("optimizer_statements: statement")
440 : {
441 1 : run_tests(g_optimizer_statements, "optimizer_statements.json");
442 : }
443 1 : CATCH_END_SECTION()
444 1 : }
445 :
446 :
447 :
448 :
449 :
450 :
451 :
452 :
453 : // vim: ts=4 sw=4 et
|