Line data Source code
1 : // Copyright (c) 2005-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 : /** \file
20 : * \brief This file is the actual as2js compiler/transpiler.
21 : *
22 : * The project includes a library which does 99% of the work. This is
23 : * the implementation of the as2js command line tool that handles
24 : * command line options, initializes the necessary objects to compile
25 : * .asj files in one of:
26 : *
27 : * \li a node tree (mainly for debug)
28 : * \li binary code (directly Intel/AMD instruction in binary--no specific
29 : * format but it uses the Linux ABI)
30 : * \li assembly language (64 bit Intel/AMD)
31 : * \li JavaScript (this was the point at first)
32 : * \li C++ (create C++ classes .cpp/.h files)
33 : *
34 : * The tree is very useful for me to debug the lexer, parser, and compiler
35 : * work before even trying to transform said tree to something else.
36 : *
37 : * The binary code is used in our other libraries so that way we do not need
38 : * to have any other compiler and/or assembler installed on your server. This
39 : * tool and library are enough for you to create a binary file that our
40 : * other tools (again using this library) can use to run complex expressions
41 : * directly in binary.
42 : */
43 :
44 : // self
45 : //
46 : #include "tools/license.h"
47 :
48 :
49 :
50 : // as2js lib
51 : //
52 : #include <as2js/archive.h>
53 : #include <as2js/binary.h>
54 : #include <as2js/compiler.h>
55 : #include <as2js/exception.h>
56 : #include <as2js/message.h>
57 : #include <as2js/parser.h>
58 : #include <as2js/version.h>
59 :
60 :
61 : // snapdev
62 : //
63 : #include <snapdev/pathinfo.h>
64 : #include <snapdev/string_replace_many.h>
65 : #include <snapdev/to_lower.h>
66 :
67 :
68 : // C++
69 : //
70 : #include <cstring>
71 : #include <iomanip>
72 : #include <set>
73 :
74 :
75 : // last include
76 : //
77 : #include <snapdev/poison.h>
78 :
79 :
80 :
81 :
82 :
83 :
84 : /** \brief Private implementations of the as2js compiler, the actual tool.
85 : *
86 : * This namespace is used to hide all the tool private functions to
87 : * avoid any conflicts.
88 : */
89 : namespace
90 : {
91 :
92 :
93 :
94 : enum class command_t
95 : {
96 : COMMAND_UNDEFINED,
97 :
98 : COMMAND_ASSEMBLY,
99 : COMMAND_BINARY,
100 : COMMAND_BINARY_VERSION,
101 : COMMAND_COMPILER_TREE,
102 : COMMAND_CPP,
103 : COMMAND_CREATE_ARCHIVE,
104 : COMMAND_DATA_SECTION,
105 : COMMAND_END_SECTION,
106 : COMMAND_EXECUTE,
107 : COMMAND_EXTRACT_ARCHIVE,
108 : COMMAND_IS_BINARY,
109 : COMMAND_JAVASCRIPT,
110 : COMMAND_LIST_ARCHIVE,
111 : COMMAND_PARSER_TREE,
112 : COMMAND_TEXT_SECTION,
113 : COMMAND_VARIABLES,
114 : };
115 :
116 :
117 : class as2js_compiler
118 : {
119 : public:
120 : typedef std::shared_ptr<as2js_compiler> pointer_t;
121 :
122 : int parse_command_line_options(int argc, char *argv[]);
123 : int run();
124 :
125 : private:
126 : void license();
127 : void usage();
128 : void version();
129 : void set_output(command_t output);
130 : void set_option(
131 : as2js::option_t option
132 : , int argc
133 : , char * argv[]
134 : , int & i);
135 : int output_error_count();
136 : void compile();
137 : void generate_binary();
138 : void binary_utils();
139 : void list_external_variables(
140 : std::ifstream & in
141 : , as2js::binary_header & header);
142 : void create_archive();
143 : void list_archive();
144 : void execute();
145 :
146 : typedef std::map<std::string, std::string> variable_t;
147 :
148 : int f_error_count = 0;
149 : std::string f_progname = std::string();
150 : std::vector<std::string> f_filenames = std::vector<std::string>();
151 : std::string f_save_to_file = std::string();
152 : std::string f_output = std::string();
153 : //std::string f_archive_path = std::string();
154 : variable_t f_variables = variable_t();
155 : command_t f_command = command_t::COMMAND_UNDEFINED;
156 : as2js::options::pointer_t f_options = std::make_shared<as2js::options>();
157 : std::set<as2js::option_t> f_option_defined = std::set<as2js::option_t>();
158 : as2js::node::pointer_t f_root = as2js::node::pointer_t();
159 : bool f_ignore_unknown_variables = false;
160 : bool f_show_all_results = false;
161 : bool f_three_underscores_to_space = false;
162 : };
163 :
164 :
165 25 : int as2js_compiler::parse_command_line_options(int argc, char *argv[])
166 : {
167 25 : f_progname = snapdev::pathinfo::basename(std::string(argv[0]));
168 :
169 100 : for(int i(1); i < argc; ++i)
170 : {
171 75 : if(argv[i][0] == '-'
172 50 : && argv[i][1] != '\0')
173 : {
174 50 : if(argv[i][1] == '-')
175 : {
176 0 : if(strcmp(argv[i] + 2, "help") == 0)
177 : {
178 0 : usage();
179 0 : return 1;
180 : }
181 0 : if(strcmp(argv[i] + 2, "license") == 0)
182 : {
183 0 : license();
184 0 : return 1;
185 : }
186 0 : if(strcmp(argv[i] + 2, "version") == 0)
187 : {
188 0 : version();
189 0 : return 1;
190 : }
191 :
192 0 : if(strcmp(argv[i] + 2, "log-level") == 0)
193 : {
194 0 : ++i;
195 0 : if(i >= argc)
196 : {
197 0 : ++f_error_count;
198 : std::cerr
199 0 : << "error: the \"--log-level\" option expects one option, the level as a number or its name.\n";
200 : }
201 : else
202 : {
203 0 : as2js::message_level_t const level(as2js::string_to_message_level(argv[i]));
204 0 : if(level != as2js::MESSAGE_LEVEL_INVALID)
205 : {
206 0 : set_message_level(level);
207 : }
208 : else
209 : {
210 0 : ++f_error_count;
211 : std::cerr
212 : << "error: unknown log level \""
213 0 : << argv[i]
214 0 : << "\".\n"; // TODO: add a command to list available levels
215 : }
216 : }
217 : }
218 0 : else if(strcmp(argv[i] + 2, "save-after-execute") == 0)
219 : {
220 0 : ++i;
221 0 : if(i >= argc)
222 : {
223 0 : ++f_error_count;
224 : std::cerr
225 0 : << "error: the \"--save-after-execute\" option expects a filename.\n";
226 : }
227 : else
228 : {
229 0 : f_save_to_file = argv[i];
230 : }
231 : }
232 0 : else if(strcmp(argv[i] + 2, "binary") == 0)
233 : {
234 0 : set_output(command_t::COMMAND_BINARY);
235 : }
236 0 : else if(strcmp(argv[i] + 2, "binary-version") == 0)
237 : {
238 0 : set_output(command_t::COMMAND_BINARY_VERSION);
239 : }
240 0 : else if(strcmp(argv[i] + 2, "create-archive") == 0)
241 : {
242 0 : set_output(command_t::COMMAND_CREATE_ARCHIVE);
243 : }
244 0 : else if(strcmp(argv[i] + 2, "data-section") == 0)
245 : {
246 0 : set_output(command_t::COMMAND_DATA_SECTION);
247 : }
248 0 : else if(strcmp(argv[i] + 2, "end-section") == 0)
249 : {
250 0 : set_output(command_t::COMMAND_END_SECTION);
251 : }
252 0 : else if(strcmp(argv[i] + 2, "execute") == 0)
253 : {
254 0 : set_output(command_t::COMMAND_EXECUTE);
255 : }
256 0 : else if(strcmp(argv[i] + 2, "extract-archive") == 0)
257 : {
258 0 : set_output(command_t::COMMAND_EXTRACT_ARCHIVE);
259 : }
260 0 : else if(strcmp(argv[i] + 2, "is-binary") == 0)
261 : {
262 0 : set_output(command_t::COMMAND_IS_BINARY);
263 : }
264 0 : else if(strcmp(argv[i] + 2, "list-archive") == 0)
265 : {
266 0 : set_output(command_t::COMMAND_LIST_ARCHIVE);
267 : }
268 0 : else if(strcmp(argv[i] + 2, "parser-tree") == 0)
269 : {
270 0 : set_output(command_t::COMMAND_PARSER_TREE);
271 : }
272 0 : else if(strcmp(argv[i] + 2, "text-section") == 0)
273 : {
274 0 : set_output(command_t::COMMAND_TEXT_SECTION);
275 : }
276 0 : else if(strcmp(argv[i] + 2, "compiler-tree") == 0)
277 : {
278 0 : set_output(command_t::COMMAND_COMPILER_TREE);
279 : }
280 0 : else if(strcmp(argv[i] + 2, "variables") == 0)
281 : {
282 0 : set_output(command_t::COMMAND_VARIABLES);
283 : }
284 0 : else if(strcmp(argv[i] + 2, "allow-with") == 0)
285 : {
286 0 : set_option(as2js::option_t::OPTION_ALLOW_WITH, argc, argv, i);
287 : }
288 0 : else if(strcmp(argv[i] + 2, "coverage") == 0)
289 : {
290 0 : set_option(as2js::option_t::OPTION_COVERAGE, argc, argv, i);
291 : }
292 0 : else if(strcmp(argv[i] + 2, "debug") == 0)
293 : {
294 0 : set_option(as2js::option_t::OPTION_DEBUG, argc, argv, i);
295 : }
296 0 : else if(strcmp(argv[i] + 2, "extended-escape-sequences") == 0)
297 : {
298 0 : set_option(as2js::option_t::OPTION_EXTENDED_ESCAPE_SEQUENCES, argc, argv, i);
299 : }
300 0 : else if(strcmp(argv[i] + 2, "extended-escape-operators") == 0)
301 : {
302 0 : set_option(as2js::option_t::OPTION_EXTENDED_OPERATORS, argc, argv, i);
303 : }
304 0 : else if(strcmp(argv[i] + 2, "extended-escape-statements") == 0)
305 : {
306 0 : set_option(as2js::option_t::OPTION_EXTENDED_STATEMENTS, argc, argv, i);
307 : }
308 0 : else if(strcmp(argv[i] + 2, "json") == 0)
309 : {
310 0 : set_option(as2js::option_t::OPTION_JSON, argc, argv, i);
311 : }
312 0 : else if(strcmp(argv[i] + 2, "octal") == 0)
313 : {
314 0 : set_option(as2js::option_t::OPTION_OCTAL, argc, argv, i);
315 : }
316 0 : else if(strcmp(argv[i] + 2, "strict") == 0)
317 : {
318 0 : set_option(as2js::option_t::OPTION_STRICT, argc, argv, i);
319 : }
320 0 : else if(strcmp(argv[i] + 2, "trace") == 0)
321 : {
322 0 : set_option(as2js::option_t::OPTION_TRACE, argc, argv, i);
323 : }
324 0 : else if(strcmp(argv[i] + 2, "unsafe-math") == 0)
325 : {
326 0 : set_option(as2js::option_t::OPTION_UNSAFE_MATH, argc, argv, i);
327 : }
328 0 : else if(strcmp(argv[i] + 2, "ignore-unknown-variables") == 0)
329 : {
330 0 : f_ignore_unknown_variables = true;
331 : }
332 0 : else if(strcmp(argv[i] + 2, "error-on-missing-variables") == 0)
333 : {
334 0 : f_ignore_unknown_variables = false;
335 : }
336 0 : else if(strcmp(argv[i] + 2, "show-all-results") == 0)
337 : {
338 0 : f_show_all_results = true;
339 : }
340 0 : else if(strcmp(argv[i] + 2, "hide-all-results") == 0)
341 : {
342 0 : f_show_all_results = false;
343 : }
344 0 : else if(strcmp(argv[i] + 2, "three-underscores-to-space") == 0)
345 : {
346 0 : f_three_underscores_to_space = false;
347 : }
348 : else
349 : {
350 0 : ++f_error_count;
351 : std::cerr
352 : << "error: unknown command line option \""
353 0 : << argv[i]
354 0 : << "\".\n";
355 : }
356 : }
357 : else
358 : {
359 50 : std::size_t const max(strlen(argv[i]));
360 100 : for(std::size_t j(1); j < max; ++j)
361 : {
362 50 : switch(argv[i][j])
363 : {
364 25 : case 'b':
365 25 : set_output(command_t::COMMAND_BINARY);
366 25 : break;
367 :
368 0 : case 'h':
369 0 : usage();
370 0 : return 1;
371 :
372 : //case 'L': -- replaced by "extern functions" table
373 : // if(!f_archive_path.empty())
374 : // {
375 : // ++f_error_count;
376 : // std::cerr
377 : // << "error: command line option \"-L\" cannot be used more than once.\n";
378 : // }
379 : // ++j;
380 : // if(j >= max)
381 : // {
382 : // ++i;
383 : // if(i >= argc)
384 : // {
385 : // ++f_error_count;
386 : // std::cerr
387 : // << "error: command line option \"-L\" is expected to be followed by one path.\n";
388 : // }
389 : // f_archive_path = argv[i];
390 : // }
391 : // else
392 : // {
393 : // f_archive_path = argv[i] + j;
394 : // }
395 : // break;
396 :
397 25 : case 'o':
398 25 : if(!f_output.empty())
399 : {
400 0 : ++f_error_count;
401 : std::cerr
402 0 : << "error: command line option \"-o\" cannot be used more than once.\n";
403 : }
404 25 : ++j;
405 25 : if(j >= max)
406 : {
407 25 : ++i;
408 25 : if(i >= argc)
409 : {
410 0 : ++f_error_count;
411 : std::cerr
412 0 : << "error: command line option \"-o\" is expected to be followed by one filename.\n";
413 : }
414 25 : f_output = argv[i];
415 : }
416 : else
417 : {
418 0 : f_output = argv[i] + j;
419 : }
420 25 : break;
421 :
422 0 : case 't':
423 0 : set_output(command_t::COMMAND_PARSER_TREE);
424 0 : break;
425 :
426 0 : case 'T':
427 0 : set_output(command_t::COMMAND_COMPILER_TREE);
428 0 : break;
429 :
430 0 : case 'V':
431 0 : version();
432 0 : return 1;
433 :
434 0 : default:
435 0 : ++f_error_count;
436 : std::cerr
437 : << "error: unknown command line option \"-"
438 0 : << argv[i][j]
439 0 : << "\".\n";
440 0 : break;
441 :
442 : }
443 : }
444 : }
445 50 : }
446 : else
447 : {
448 25 : char const * equal(strchr(argv[i], '='));
449 25 : if(equal == nullptr)
450 : {
451 25 : f_filenames.push_back(argv[i]);
452 : }
453 0 : else if(equal - argv[i] == 0)
454 : {
455 0 : ++f_error_count;
456 : std::cerr
457 : << "error: variable name missing in \""
458 0 : << argv[i]
459 0 : << "\".\n";
460 : }
461 : else
462 : {
463 0 : std::string const name(argv[i], equal - argv[i]);
464 0 : std::string const value(equal + 1);
465 0 : f_variables[name] = value;
466 0 : }
467 : }
468 : }
469 :
470 25 : if(f_command == command_t::COMMAND_UNDEFINED)
471 : {
472 : // this is what one wants by default (transliteration from
473 : // Alex Script to JavaScript)
474 : //
475 0 : f_command = command_t::COMMAND_JAVASCRIPT;
476 : }
477 :
478 25 : return output_error_count();
479 : }
480 :
481 :
482 50 : int as2js_compiler::output_error_count()
483 : {
484 50 : if(f_error_count != 0)
485 : {
486 : std::cerr
487 0 : << "error: found "
488 : << f_error_count
489 : << " error"
490 0 : << (f_error_count == 1 ? "" : "s")
491 0 : << " in the command line options.\n";
492 0 : return 1;
493 : }
494 :
495 50 : return 0;
496 : }
497 :
498 :
499 0 : void as2js_compiler::license()
500 : {
501 0 : std::cout << as2js_tools::license;
502 0 : }
503 :
504 :
505 0 : void as2js_compiler::usage()
506 : {
507 : std::cout
508 0 : << "Usage: " << f_progname << " [-opts] <filename>.ajs | <var>=<value> ...\n"
509 : "where -opts is one or more of:\n"
510 : "Commands (one of):\n"
511 : " -b | --binary generate a binary file.\n"
512 : " --binary-version output version of the binary file.\n"
513 : " --data-section position where the data section starts.\n"
514 : " --end-section position where the end section starts.\n"
515 : " --error-on-missing-variables\n"
516 : " variables defined on the command must exist.\n"
517 : " --execute execute the specified compiled script.\n"
518 : " -h | --help print out this help screen.\n"
519 : " --hide-all-results\n"
520 : " do not print the external values before exiting.\n"
521 : " --ignore-unknown-variables\n"
522 : " variables defined on the command that do not exist\n"
523 : " can be ignored if not defined in the script.\n"
524 : " --is-binary check whether a file is a binary file.\n"
525 : " --license print compiler's license.\n"
526 : " -t | --parser-tree output the tree of nodes.\n"
527 : " --show-all-results\n"
528 : " print all the external values before exiting.\n"
529 : " --text-section position where the text section starts.\n"
530 : " -T | --compiler-tree output the tree of nodes.\n"
531 : " --variables list external variables.\n"
532 : " -V | --version print version of the compiler.\n"
533 :
534 : "\n"
535 : "Options:\n"
536 0 : " -L <path> path to archive libraries.\n"
537 : ;
538 0 : }
539 :
540 :
541 0 : void as2js_compiler::version()
542 : {
543 : std::cout
544 0 : << f_progname << " v" << AS2JS_VERSION_STRING << std::endl
545 0 : << "libas2js v" << as2js::get_version_string() << std::endl;
546 0 : }
547 :
548 :
549 25 : void as2js_compiler::set_output(command_t command)
550 : {
551 25 : if(f_command != command_t::COMMAND_UNDEFINED)
552 : {
553 0 : ++f_error_count;
554 0 : std::cerr << "error: output type already set. Try only one of --binary, --tree, --is-binary, etc.\n";
555 0 : return;
556 : }
557 :
558 25 : f_command = command;
559 : }
560 :
561 :
562 0 : void as2js_compiler::set_option(as2js::option_t option, int argc, char * argv[], int & i)
563 : {
564 : // prevent duplication which will help people understand why something
565 : // would possibly not work
566 : {
567 0 : auto const unique(f_option_defined.insert(option));
568 0 : if(!unique.second)
569 : {
570 0 : ++f_error_count;
571 : std::cerr
572 : << "error: option \""
573 0 : << argv[i]
574 0 : << "\" was specified more than once.\n";
575 0 : return;
576 : }
577 : }
578 :
579 0 : as2js::option_value_t value(0);
580 0 : if(i + 1 < argc)
581 : {
582 0 : char * v(argv[i + 1]);
583 0 : if(strcmp(v, "true") == 0)
584 : {
585 0 : value = 1;
586 0 : ++i;
587 : }
588 0 : else if(strcmp(v, "false") == 0)
589 : {
590 0 : value = 0;
591 0 : ++i;
592 : }
593 0 : else if(as2js::is_integer(v, true))
594 : {
595 0 : value = as2js::to_integer(v);
596 0 : ++i;
597 : }
598 0 : else if(as2js::is_floating_point(v))
599 : {
600 0 : value = as2js::to_floating_point(v);
601 0 : ++i;
602 : }
603 : }
604 0 : f_options->set_option(option, value);
605 : }
606 :
607 :
608 25 : int as2js_compiler::run()
609 : {
610 25 : switch(f_command)
611 : {
612 0 : case command_t::COMMAND_BINARY_VERSION:
613 : case command_t::COMMAND_IS_BINARY:
614 : case command_t::COMMAND_TEXT_SECTION:
615 : case command_t::COMMAND_DATA_SECTION:
616 : case command_t::COMMAND_END_SECTION:
617 : case command_t::COMMAND_VARIABLES:
618 0 : binary_utils();
619 0 : break;
620 :
621 0 : case command_t::COMMAND_CREATE_ARCHIVE:
622 0 : create_archive();
623 0 : break;
624 :
625 0 : case command_t::COMMAND_LIST_ARCHIVE:
626 : case command_t::COMMAND_EXTRACT_ARCHIVE:
627 0 : list_archive();
628 0 : break;
629 :
630 0 : case command_t::COMMAND_EXECUTE:
631 0 : execute();
632 0 : break;
633 :
634 25 : case command_t::COMMAND_PARSER_TREE:
635 : case command_t::COMMAND_COMPILER_TREE:
636 : case command_t::COMMAND_BINARY:
637 : case command_t::COMMAND_ASSEMBLY:
638 : case command_t::COMMAND_JAVASCRIPT:
639 : case command_t::COMMAND_CPP:
640 25 : compile();
641 25 : break;
642 :
643 0 : case command_t::COMMAND_UNDEFINED:
644 0 : throw as2js::internal_error("found the \"undefined\" command when we should replace it with the default \"javascript\"?");
645 :
646 : }
647 :
648 25 : return output_error_count();
649 : }
650 :
651 :
652 25 : void as2js_compiler::compile()
653 : {
654 25 : if(f_filenames.empty())
655 : {
656 0 : ++f_error_count;
657 0 : std::cerr << "error: an input filename is required.\n";
658 0 : return;
659 : }
660 :
661 25 : if(f_output.empty())
662 : {
663 0 : f_output = "a.out";
664 : }
665 :
666 : // we are compiling a user script so mark it as such
667 : //
668 25 : f_options->set_option(as2js::option_t::OPTION_USER_SCRIPT, 1);
669 :
670 : // TODO: I'm pretty sure that the following loop would require me to
671 : // load all the _programs_ (user defined files) as children of a
672 : // NODE_ROOT as NODE_PROGRAM and then compile all the NODE_PROGRAM
673 : // nodes at once with one call to the compile() function
674 : //
675 50 : for(auto const & filename : f_filenames)
676 : {
677 25 : as2js::reset_errors();
678 :
679 : // open file
680 : //
681 25 : as2js::base_stream::pointer_t input;
682 25 : if(filename == "-")
683 : {
684 0 : input = std::make_shared<as2js::cin_stream>();
685 0 : input->get_position().set_filename("-");
686 : }
687 : else
688 : {
689 25 : as2js::input_stream<std::ifstream>::pointer_t in(std::make_shared<as2js::input_stream<std::ifstream>>());
690 25 : in->get_position().set_filename(filename);
691 25 : in->open(filename);
692 25 : if(!in->is_open())
693 : {
694 0 : ++f_error_count;
695 : std::cerr
696 : << "error: could not open file \""
697 : << filename
698 0 : << "\".\n";
699 0 : continue;
700 : }
701 25 : input = in;
702 25 : }
703 :
704 : // parse the source
705 : //
706 25 : as2js::parser::pointer_t parser(std::make_shared<as2js::parser>(input, f_options));
707 25 : f_root = parser->parse();
708 25 : if(as2js::error_count() != 0)
709 : {
710 0 : ++f_error_count;
711 : std::cerr
712 : << "error: parsing of input file \""
713 : << filename
714 0 : << "\" failed.\n";
715 0 : continue;
716 : }
717 :
718 25 : if(f_command == command_t::COMMAND_PARSER_TREE)
719 : {
720 : // user wants to see the parser tree, show that and try next file
721 : //
722 0 : std::cout << *f_root << "\n";
723 0 : continue;
724 : }
725 :
726 : // run the compiler
727 : //
728 50 : as2js::compiler compiler(f_options);
729 25 : if(compiler.compile(f_root) != 0)
730 : {
731 : // there were errors, skip
732 : //
733 0 : ++f_error_count;
734 : std::cerr
735 : << "error: parsing of input file \""
736 : << filename
737 0 : << "\" failed.\n";
738 0 : continue;
739 : }
740 :
741 25 : switch(f_command)
742 : {
743 0 : case command_t::COMMAND_COMPILER_TREE:
744 0 : std::cout << *f_root << "\n";
745 0 : break;
746 :
747 25 : case command_t::COMMAND_BINARY:
748 25 : generate_binary();
749 25 : break;
750 :
751 0 : case command_t::COMMAND_ASSEMBLY:
752 : case command_t::COMMAND_JAVASCRIPT:
753 : case command_t::COMMAND_CPP:
754 0 : ++f_error_count;
755 : std::cerr
756 0 : << "error: output command not yet implemented.\n";
757 0 : break;
758 :
759 0 : case command_t::COMMAND_BINARY_VERSION:
760 : case command_t::COMMAND_CREATE_ARCHIVE:
761 : case command_t::COMMAND_IS_BINARY:
762 : case command_t::COMMAND_DATA_SECTION:
763 : case command_t::COMMAND_END_SECTION:
764 : case command_t::COMMAND_EXECUTE:
765 : case command_t::COMMAND_EXTRACT_ARCHIVE:
766 : case command_t::COMMAND_LIST_ARCHIVE:
767 : case command_t::COMMAND_PARSER_TREE:
768 : case command_t::COMMAND_TEXT_SECTION:
769 : case command_t::COMMAND_UNDEFINED:
770 : case command_t::COMMAND_VARIABLES:
771 : throw as2js::internal_error("these cases were checked earlier and cannot happen here."); // LCOV_EXCL_LINE
772 :
773 : }
774 25 : }
775 : }
776 :
777 :
778 25 : void as2js_compiler::generate_binary()
779 : {
780 : // TODO: add support for '-' (i.e. stdout)
781 : //
782 25 : as2js::output_stream<std::ofstream>::pointer_t output(std::make_shared<as2js::output_stream<std::ofstream>>());
783 25 : output->open(f_output);
784 25 : if(!output->is_open())
785 : {
786 0 : ++f_error_count;
787 : std::cerr
788 : << "error: could not open output file \""
789 0 : << f_output
790 0 : << "\".\n";
791 0 : return;
792 : }
793 25 : as2js::binary_assembler::pointer_t binary(
794 : std::make_shared<as2js::binary_assembler>(
795 : output
796 25 : , f_options));
797 25 : int const errcnt(binary->output(f_root));
798 25 : if(errcnt != 0)
799 : {
800 0 : ++f_error_count;
801 : std::cerr
802 0 : << "error: "
803 : << errcnt
804 0 : << " errors occured while transforming the tree to binary.\n";
805 : }
806 25 : }
807 :
808 :
809 0 : void as2js_compiler::binary_utils()
810 : {
811 0 : if(!f_output.empty())
812 : {
813 0 : ++f_error_count;
814 : std::cerr
815 : << "as2js:error: no output file ("
816 0 : << f_output
817 0 : << ") is supported with this command.\n";
818 0 : return;
819 : }
820 :
821 0 : if(f_filenames.empty())
822 : {
823 0 : f_filenames.push_back("a.out");
824 : }
825 :
826 : // open input file
827 : //
828 0 : std::ifstream in;
829 0 : in.open(f_filenames[0]);
830 0 : if(!in.is_open())
831 : {
832 0 : ++f_error_count;
833 : std::cerr
834 : << "as2js:error: could not open \""
835 0 : << f_filenames[0]
836 0 : << "\" as input binary file.\n";
837 0 : return;
838 : }
839 :
840 : // read the header
841 : //
842 0 : as2js::binary_header header;
843 0 : in.read(reinterpret_cast<char *>(&header), sizeof(header));
844 :
845 0 : if(header.f_magic[0] != as2js::BINARY_MAGIC_B0
846 0 : || header.f_magic[1] != as2js::BINARY_MAGIC_B1
847 0 : || header.f_magic[2] != as2js::BINARY_MAGIC_B2
848 0 : || header.f_magic[3] != as2js::BINARY_MAGIC_B3)
849 : {
850 0 : ++f_error_count;
851 : std::cerr << "as2js:error: file \""
852 0 : << f_filenames[0]
853 0 : << "\" does not look like an as2js binary file (invalid magic).\n";
854 0 : return;
855 : }
856 :
857 0 : switch(f_command)
858 : {
859 0 : case command_t::COMMAND_BINARY_VERSION:
860 : std::cout
861 0 : << static_cast<int>(header.f_version_major)
862 0 : << '.'
863 0 : << static_cast<int>(header.f_version_minor)
864 0 : << '\n';
865 0 : return;
866 :
867 0 : case command_t::COMMAND_IS_BINARY:
868 : // that was sufficient for this command, return now
869 0 : return;
870 :
871 0 : case command_t::COMMAND_TEXT_SECTION:
872 0 : std::cout << header.f_start << '\n';
873 0 : return;
874 :
875 0 : case command_t::COMMAND_DATA_SECTION:
876 0 : std::cout << header.f_variables << '\n';
877 0 : return;
878 :
879 0 : case command_t::COMMAND_END_SECTION:
880 : // this is the file size minus 4
881 : //
882 0 : in.seekg(0, std::ios_base::end);
883 0 : std::cout << in.tellg() - 4L << '\n';
884 0 : return;
885 :
886 0 : case command_t::COMMAND_VARIABLES:
887 0 : list_external_variables(in, header);
888 0 : return;
889 :
890 0 : default:
891 : throw as2js::internal_error("these cases were checked earlier and cannot happen here"); // LCOV_EXCL_LINE
892 :
893 : }
894 0 : }
895 :
896 :
897 0 : void as2js_compiler::list_external_variables(std::ifstream & in, as2js::binary_header & header)
898 : {
899 : // list external variables
900 : //
901 0 : in.seekg(header.f_variables, std::ios_base::beg);
902 0 : as2js::binary_variable::vector_t variables(header.f_variable_count);
903 0 : in.read(reinterpret_cast<char *>(variables.data()), sizeof(as2js::binary_variable) * header.f_variable_count);
904 0 : for(std::uint16_t idx(0); idx < header.f_variable_count; ++idx)
905 : {
906 0 : std::string name;
907 0 : if(variables[idx].f_name_size < sizeof(variables[idx].f_name))
908 : {
909 0 : name = std::string(
910 0 : reinterpret_cast<char const *>(&variables[idx].f_name)
911 0 : , variables[idx].f_name_size);
912 : }
913 : else
914 : {
915 0 : in.seekg(variables[idx].f_name, std::ios_base::beg);
916 0 : name = std::string(variables[idx].f_name_size, ' ');
917 0 : in.read(name.data(), variables[idx].f_name_size);
918 : }
919 0 : if(idx == 0)
920 : {
921 0 : std::cout << "var ";
922 : }
923 : else
924 : {
925 0 : std::cout << ",\n ";
926 : }
927 : #if 0
928 : // show the offset of where the variable data is defined
929 : // (for easy verification purposes, instead of having to read the binary)
930 : //
931 : std::cout
932 : << " /* offset: "
933 : << (variables[idx].f_data_size > sizeof(variables[idx].f_data)
934 : ? variables[idx].f_data
935 : : header.f_variables
936 : + idx * sizeof(as2js::binary_variable)
937 : + offsetof(as2js::binary_variable, f_data))
938 : << " */ ";
939 : #endif
940 0 : std::cout << name;
941 0 : switch(variables[idx].f_type)
942 : {
943 0 : case as2js::variable_type_t::VARIABLE_TYPE_BOOLEAN:
944 0 : std::cout << ": Boolean";
945 0 : if(variables[idx].f_data != 0)
946 : {
947 0 : std::cerr << " := true";
948 : }
949 0 : break;
950 :
951 0 : case as2js::variable_type_t::VARIABLE_TYPE_INTEGER:
952 0 : std::cout << ": Integer";
953 0 : if(variables[idx].f_data != 0)
954 : {
955 0 : std::cerr << " := " << variables[idx].f_data;
956 : }
957 0 : break;
958 :
959 0 : case as2js::variable_type_t::VARIABLE_TYPE_FLOATING_POINT:
960 0 : std::cout << ": Double";
961 0 : if(variables[idx].f_data != 0)
962 : {
963 0 : std::uint64_t const * ptr(&variables[idx].f_data);
964 0 : std::cerr << " := " << *reinterpret_cast<double const *>(ptr);
965 : }
966 0 : break;
967 :
968 0 : case as2js::variable_type_t::VARIABLE_TYPE_STRING:
969 0 : std::cout << ": String";
970 0 : if(variables[idx].f_data_size > 0)
971 : {
972 0 : std::string value;
973 0 : if(variables[idx].f_data_size < sizeof(variables[idx].f_data))
974 : {
975 0 : value = std::string(
976 0 : reinterpret_cast<char const *>(&variables[idx].f_data)
977 0 : , variables[idx].f_data_size);
978 : }
979 : else
980 : {
981 0 : in.seekg(variables[idx].f_data, std::ios_base::beg);
982 0 : value = std::string(variables[idx].f_data_size, ' ');
983 0 : in.read(value.data(), variables[idx].f_data_size);
984 : }
985 0 : std::cout << " = " << value;
986 0 : }
987 0 : break;
988 :
989 : //case as2js::variable_type_t::VARIABLE_TYPE_UNKNOWN:
990 0 : default:
991 0 : std::cout << " /* unknown type */ ";
992 0 : break;
993 :
994 : }
995 0 : }
996 0 : std::cout << ";\n";
997 0 : }
998 :
999 :
1000 0 : void as2js_compiler::create_archive()
1001 : {
1002 0 : as2js::archive ar;
1003 0 : if(!ar.create(f_filenames))
1004 : {
1005 0 : ++f_error_count;
1006 : std::cerr
1007 0 : << "error: could not create archive file.\n";
1008 0 : return;
1009 : }
1010 :
1011 0 : as2js::output_stream<std::ofstream>::pointer_t output(std::make_shared<as2js::output_stream<std::ofstream>>());
1012 0 : output->open(f_output);
1013 0 : if(!output->is_open())
1014 : {
1015 0 : ++f_error_count;
1016 : std::cerr
1017 : << "error: could not open archive file \""
1018 0 : << f_output
1019 0 : << "\" to save run-time functions.\n";
1020 0 : return;
1021 : }
1022 :
1023 0 : if(!ar.save(output))
1024 : {
1025 0 : ++f_error_count;
1026 : std::cerr
1027 : << "error: could not save archive file \""
1028 0 : << f_output
1029 0 : << "\".\n";
1030 0 : return;
1031 : }
1032 0 : }
1033 :
1034 :
1035 0 : void as2js_compiler::list_archive()
1036 : {
1037 0 : if(f_filenames.size() != 1)
1038 : {
1039 0 : ++f_error_count;
1040 : std::cerr
1041 0 : << "error: expected exactly one filename with --list-archive or --extract-archive, found "
1042 0 : << f_filenames.size()
1043 0 : << " instead.\n";
1044 0 : return;
1045 : }
1046 :
1047 0 : as2js::input_stream<std::ifstream>::pointer_t in(std::make_shared<as2js::input_stream<std::ifstream>>());
1048 0 : in->open(f_filenames[0]);
1049 0 : if(!in->is_open())
1050 : {
1051 : // TODO: test again with .oar extension
1052 : //
1053 0 : ++f_error_count;
1054 : std::cerr
1055 : << "error: could not open archive \""
1056 0 : << f_filenames[0]
1057 0 : << "\".\n";
1058 0 : return;
1059 : }
1060 :
1061 0 : as2js::archive ar;
1062 0 : if(!ar.load(in))
1063 : {
1064 0 : ++f_error_count;
1065 : std::cerr
1066 : << "error: failed loading archive \""
1067 0 : << f_filenames[0]
1068 0 : << "\".\n";
1069 0 : return;
1070 : }
1071 :
1072 0 : as2js::rt_function::map_t functions(ar.get_functions());
1073 0 : std::size_t name_width(10);
1074 0 : for(auto const & f : functions)
1075 : {
1076 0 : name_width = std::max(f.first.length(), name_width);
1077 : }
1078 : std::cout
1079 0 : << " NAME" << std::setw(name_width - 4) << ' ' << "SIZE\n";
1080 0 : int pos(1);
1081 0 : for(auto const & f : functions)
1082 : {
1083 : std::cout
1084 0 : << std::setw(3) << pos
1085 0 : << ". " << f.first
1086 0 : << std::setw(name_width - f.first.length()) << ' '
1087 0 : << f.second->get_code().size()
1088 0 : << "\n";
1089 0 : ++pos;
1090 : }
1091 0 : }
1092 :
1093 :
1094 0 : void as2js_compiler::execute()
1095 : {
1096 0 : if(f_filenames.empty())
1097 : {
1098 0 : f_filenames.push_back("a.out");
1099 : }
1100 0 : else if(f_filenames.size() > 1)
1101 : {
1102 0 : ++f_error_count;
1103 : std::cerr
1104 0 : << "error: --execute expects exactly one filename.\n";
1105 0 : return;
1106 : }
1107 :
1108 0 : as2js::running_file script;
1109 0 : if(!script.load(f_filenames[0]))
1110 : {
1111 0 : ++f_error_count;
1112 : std::cerr
1113 : << "error: could not load \""
1114 0 : << f_filenames[0]
1115 0 : << "\" to execute code.\n";
1116 0 : return;
1117 : }
1118 :
1119 0 : for(auto const & var : f_variables)
1120 : {
1121 : //std::cerr << "--- var = [" << var.second << "]\n";
1122 : // TODO: support all types of variables
1123 : //
1124 0 : std::string value(var.second);
1125 0 : std::size_t pos(var.first.find(':'));
1126 0 : std::string name;
1127 0 : std::string type;
1128 0 : if(pos != std::string::npos)
1129 : {
1130 0 : name = var.first.substr(0, pos);
1131 0 : type = var.first.substr(pos + 1);
1132 : }
1133 : else
1134 : {
1135 0 : name = var.first;
1136 0 : if(value.empty())
1137 : {
1138 0 : type = "string";
1139 : }
1140 0 : else if(value.length() >= 2
1141 0 : && value[0] == '"'
1142 0 : && value[value.length() - 1] == '"')
1143 : {
1144 0 : type = "string";
1145 0 : value = value.substr(1, value.length() - 2);
1146 : }
1147 0 : else if(value.length() >= 2
1148 0 : && value[0] == '\''
1149 0 : && value[value.length() - 1] == '\'')
1150 : {
1151 0 : type = "string";
1152 0 : value = value.substr(1, value.length() - 2);
1153 : }
1154 : else
1155 : {
1156 0 : type = "integer";
1157 0 : std::size_t const max(value.length());
1158 0 : for(std::size_t idx(0); idx < max; ++idx)
1159 : {
1160 0 : char const c(value[idx]);
1161 0 : if(c == '.')
1162 : {
1163 0 : if(type == "integer")
1164 : {
1165 0 : type = "double";
1166 : }
1167 : else
1168 : {
1169 0 : type = "string";
1170 0 : break;
1171 : }
1172 : }
1173 0 : if(c < '0' || c > '9')
1174 : {
1175 0 : if(value == "false"
1176 0 : || value == "true")
1177 : {
1178 0 : type = "boolean";
1179 0 : break;
1180 : }
1181 0 : type = "string";
1182 0 : break;
1183 : }
1184 : }
1185 : }
1186 : }
1187 0 : if(type == "string"
1188 0 : && f_three_underscores_to_space)
1189 : {
1190 0 : value = snapdev::string_replace_many(value, {{"___", " "}});
1191 : }
1192 0 : if(name.empty())
1193 : {
1194 0 : ++f_error_count;
1195 : std::cerr
1196 0 : << "error: a variable must have a name before its ':<type>' specification.\n";
1197 0 : return;
1198 : }
1199 :
1200 0 : if(!script.has_variable(name))
1201 : {
1202 0 : if(!f_ignore_unknown_variables)
1203 : {
1204 0 : ++f_error_count;
1205 : std::cerr
1206 : << "error: unknown variable \""
1207 : << name
1208 : << "\" in \""
1209 0 : << f_filenames[0]
1210 0 : << "\".\n";
1211 0 : return;
1212 : }
1213 : std::cerr
1214 : << "warning: variable \""
1215 : << name
1216 : << "\" not found in \""
1217 0 : << f_filenames[0]
1218 0 : << "\".\n";
1219 0 : continue;
1220 : }
1221 0 : type = snapdev::to_lower(type);
1222 0 : if(type == "integer")
1223 : {
1224 0 : std::int64_t const integer(std::stoll(value, nullptr, 0));
1225 0 : script.set_variable(name, integer);
1226 : }
1227 0 : else if(type == "boolean")
1228 : {
1229 0 : if(value == "true")
1230 : {
1231 0 : script.set_variable(name, true);
1232 : }
1233 0 : else if(value == "false")
1234 : {
1235 0 : script.set_variable(name, false);
1236 : }
1237 : else
1238 : {
1239 0 : ++f_error_count;
1240 : std::cerr
1241 0 : << "error: a boolean variable must be set to \"true\" or \"false\".\n";
1242 0 : return;
1243 : }
1244 : }
1245 0 : else if(type == "double")
1246 : {
1247 0 : double const floating_point(std::stod(value, nullptr));
1248 0 : script.set_variable(name, floating_point);
1249 : }
1250 0 : else if(type == "string")
1251 : {
1252 0 : script.set_variable(name, value);
1253 : }
1254 : else
1255 : {
1256 0 : ++f_error_count;
1257 : std::cerr
1258 : << "error: unknown variable type \""
1259 : << type
1260 : << "\" for \""
1261 : << name
1262 0 : << "\".\n";
1263 0 : return;
1264 : }
1265 0 : }
1266 :
1267 0 : as2js::binary_result result;
1268 :
1269 0 : script.run(result);
1270 :
1271 0 : if(!f_save_to_file.empty())
1272 : {
1273 0 : script.save(f_save_to_file);
1274 : }
1275 :
1276 0 : switch(result.get_type())
1277 : {
1278 0 : case as2js::variable_type_t::VARIABLE_TYPE_BOOLEAN:
1279 0 : std::cout << std::boolalpha << result.get_boolean() << "\n";
1280 0 : break;
1281 :
1282 0 : case as2js::variable_type_t::VARIABLE_TYPE_INTEGER:
1283 0 : std::cout << result.get_integer() << "\n";
1284 0 : break;
1285 :
1286 0 : case as2js::variable_type_t::VARIABLE_TYPE_FLOATING_POINT:
1287 0 : std::cout << result.get_floating_point() << "\n";
1288 0 : break;
1289 :
1290 0 : case as2js::variable_type_t::VARIABLE_TYPE_STRING:
1291 0 : std::cout << result.get_string() << "\n";
1292 0 : break;
1293 :
1294 0 : default:
1295 0 : std::cerr << "error: unknown result type in as2js_compiler.\n";
1296 0 : break;
1297 :
1298 : }
1299 :
1300 0 : if(f_show_all_results)
1301 : {
1302 0 : std::size_t const count(script.variable_size());
1303 0 : for(std::size_t idx(0); idx < count; ++idx)
1304 : {
1305 0 : std::string name;
1306 0 : as2js::binary_variable * var(script.get_variable(idx, name));
1307 :
1308 0 : std::cout << name << "=";
1309 :
1310 : std::uint64_t const * data;
1311 0 : if(var->f_data_size <= sizeof(var->f_data))
1312 : {
1313 0 : data = &var->f_data;
1314 : }
1315 : else
1316 : {
1317 0 : data = reinterpret_cast<std::uint64_t const *>(var->f_data);
1318 : }
1319 :
1320 0 : switch(var->f_type)
1321 : {
1322 0 : case as2js::variable_type_t::VARIABLE_TYPE_UNKNOWN:
1323 0 : std::cout << "<unknown variable type>\n";
1324 0 : break;
1325 :
1326 0 : case as2js::variable_type_t::VARIABLE_TYPE_BOOLEAN:
1327 0 : std::cout << std::boolalpha << ((*data & 255) != 0) << '\n';
1328 0 : break;
1329 :
1330 0 : case as2js::variable_type_t::VARIABLE_TYPE_INTEGER:
1331 0 : std::cout << *data << '\n';
1332 0 : break;
1333 :
1334 0 : case as2js::variable_type_t::VARIABLE_TYPE_FLOATING_POINT:
1335 0 : std::cout << *reinterpret_cast<double const *>(data) << '\n';
1336 0 : break;
1337 :
1338 0 : case as2js::variable_type_t::VARIABLE_TYPE_STRING:
1339 0 : std::cout << reinterpret_cast<char const *>(data) << '\n';
1340 0 : break;
1341 :
1342 : }
1343 0 : }
1344 : }
1345 0 : }
1346 :
1347 :
1348 :
1349 : } // no name namespace
1350 :
1351 :
1352 25 : int main(int argc, char *argv[])
1353 : {
1354 : try
1355 : {
1356 25 : as2js_compiler::pointer_t c(std::make_shared<as2js_compiler>());
1357 25 : if(c->parse_command_line_options(argc, argv) != 0)
1358 : {
1359 0 : return 1;
1360 : }
1361 25 : return c->run();
1362 25 : }
1363 0 : catch(std::exception const & e)
1364 : {
1365 0 : std::cerr << "as2js: exception: " << e.what() << std::endl;
1366 0 : exit(1);
1367 0 : }
1368 :
1369 : return 0;
1370 : }
1371 :
1372 :
1373 : // vim: ts=4 sw=4 et
|