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(as2js::compiler::pointer_t c);
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 33 : int as2js_compiler::parse_command_line_options(int argc, char *argv[])
166 : {
167 33 : f_progname = snapdev::pathinfo::basename(std::string(argv[0]));
168 :
169 132 : for(int i(1); i < argc; ++i)
170 : {
171 99 : if(argv[i][0] == '-'
172 66 : && argv[i][1] != '\0')
173 : {
174 66 : 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 = true;
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 66 : std::size_t const max(strlen(argv[i]));
360 132 : for(std::size_t j(1); j < max; ++j)
361 : {
362 66 : switch(argv[i][j])
363 : {
364 33 : case 'b':
365 33 : set_output(command_t::COMMAND_BINARY);
366 33 : 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 33 : case 'o':
398 33 : 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 33 : ++j;
405 33 : if(j >= max)
406 : {
407 33 : ++i;
408 33 : 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 33 : f_output = argv[i];
415 : }
416 : else
417 : {
418 0 : f_output = argv[i] + j;
419 : }
420 33 : 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 66 : }
446 : else
447 : {
448 33 : char const * equal(strchr(argv[i], '='));
449 33 : if(equal == nullptr)
450 : {
451 33 : 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 33 : 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 33 : return output_error_count();
479 : }
480 :
481 :
482 66 : int as2js_compiler::output_error_count()
483 : {
484 66 : 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 66 : 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 33 : void as2js_compiler::set_output(command_t command)
550 : {
551 33 : 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 33 : 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 33 : int as2js_compiler::run()
609 : {
610 33 : 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 33 : 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 33 : compile();
641 33 : 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 33 : return output_error_count();
649 : }
650 :
651 :
652 33 : void as2js_compiler::compile()
653 : {
654 33 : 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 33 : 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 33 : 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 66 : for(auto const & filename : f_filenames)
676 : {
677 33 : as2js::reset_errors();
678 :
679 : // open file
680 : //
681 33 : as2js::base_stream::pointer_t input;
682 33 : if(filename == "-")
683 : {
684 0 : input = std::make_shared<as2js::cin_stream>();
685 0 : input->get_position().set_filename("-");
686 : }
687 : else
688 : {
689 33 : as2js::input_stream<std::ifstream>::pointer_t in(std::make_shared<as2js::input_stream<std::ifstream>>());
690 33 : in->get_position().set_filename(filename);
691 33 : in->open(filename);
692 33 : 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 33 : input = in;
702 33 : }
703 :
704 : // parse the source
705 : //
706 33 : as2js::parser::pointer_t parser(std::make_shared<as2js::parser>(input, f_options));
707 33 : f_root = parser->parse();
708 33 : 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 33 : 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 33 : as2js::compiler::pointer_t compiler(std::make_shared<as2js::compiler>(f_options));
729 33 : 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 33 : switch(f_command)
742 : {
743 0 : case command_t::COMMAND_COMPILER_TREE:
744 0 : std::cout << *f_root << "\n";
745 0 : break;
746 :
747 33 : case command_t::COMMAND_BINARY:
748 33 : generate_binary(compiler);
749 33 : 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 33 : }
775 : }
776 :
777 :
778 33 : void as2js_compiler::generate_binary(as2js::compiler::pointer_t compiler)
779 : {
780 : // TODO: add support for '-' (i.e. stdout)
781 : //
782 33 : as2js::output_stream<std::ofstream>::pointer_t output(std::make_shared<as2js::output_stream<std::ofstream>>());
783 33 : output->open(f_output);
784 33 : 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 33 : as2js::binary_assembler::pointer_t binary(
794 : std::make_shared<as2js::binary_assembler>(
795 : output
796 33 : , f_options
797 33 : , compiler));
798 33 : int const errcnt(binary->output(f_root));
799 33 : if(errcnt != 0)
800 : {
801 0 : ++f_error_count;
802 : std::cerr
803 0 : << "error: "
804 : << errcnt
805 0 : << " errors occured while transforming the tree to binary.\n";
806 : }
807 33 : }
808 :
809 :
810 0 : void as2js_compiler::binary_utils()
811 : {
812 0 : if(!f_output.empty())
813 : {
814 0 : ++f_error_count;
815 : std::cerr
816 : << "as2js:error: no output file ("
817 0 : << f_output
818 0 : << ") is supported with this command.\n";
819 0 : return;
820 : }
821 :
822 0 : if(f_filenames.empty())
823 : {
824 0 : f_filenames.push_back("a.out");
825 : }
826 :
827 : // open input file
828 : //
829 0 : std::ifstream in;
830 0 : in.open(f_filenames[0]);
831 0 : if(!in.is_open())
832 : {
833 0 : ++f_error_count;
834 : std::cerr
835 : << "as2js:error: could not open \""
836 0 : << f_filenames[0]
837 0 : << "\" as input binary file.\n";
838 0 : return;
839 : }
840 :
841 : // read the header
842 : //
843 0 : as2js::binary_header header;
844 0 : in.read(reinterpret_cast<char *>(&header), sizeof(header));
845 :
846 0 : if(header.f_magic[0] != as2js::BINARY_MAGIC_B0
847 0 : || header.f_magic[1] != as2js::BINARY_MAGIC_B1
848 0 : || header.f_magic[2] != as2js::BINARY_MAGIC_B2
849 0 : || header.f_magic[3] != as2js::BINARY_MAGIC_B3)
850 : {
851 0 : ++f_error_count;
852 : std::cerr << "as2js:error: file \""
853 0 : << f_filenames[0]
854 0 : << "\" does not look like an as2js binary file (invalid magic).\n";
855 0 : return;
856 : }
857 :
858 0 : switch(f_command)
859 : {
860 0 : case command_t::COMMAND_BINARY_VERSION:
861 : std::cout
862 0 : << static_cast<int>(header.f_version_major)
863 0 : << '.'
864 0 : << static_cast<int>(header.f_version_minor)
865 0 : << '\n';
866 0 : return;
867 :
868 0 : case command_t::COMMAND_IS_BINARY:
869 : // that was sufficient for this command, return now
870 0 : return;
871 :
872 0 : case command_t::COMMAND_TEXT_SECTION:
873 0 : std::cout << header.f_start << '\n';
874 0 : return;
875 :
876 0 : case command_t::COMMAND_DATA_SECTION:
877 0 : std::cout << header.f_variables << '\n';
878 0 : return;
879 :
880 0 : case command_t::COMMAND_END_SECTION:
881 : // this is the file size minus 4
882 : //
883 0 : in.seekg(0, std::ios_base::end);
884 0 : std::cout << in.tellg() - 4L << '\n';
885 0 : return;
886 :
887 0 : case command_t::COMMAND_VARIABLES:
888 0 : list_external_variables(in, header);
889 0 : return;
890 :
891 0 : default:
892 : throw as2js::internal_error("these cases were checked earlier and cannot happen here"); // LCOV_EXCL_LINE
893 :
894 : }
895 0 : }
896 :
897 :
898 0 : void as2js_compiler::list_external_variables(std::ifstream & in, as2js::binary_header & header)
899 : {
900 : // list external variables
901 : //
902 0 : in.seekg(header.f_variables, std::ios_base::beg);
903 0 : as2js::binary_variable::vector_t variables(header.f_variable_count);
904 0 : in.read(reinterpret_cast<char *>(variables.data()), sizeof(as2js::binary_variable) * header.f_variable_count);
905 0 : for(std::uint16_t idx(0); idx < header.f_variable_count; ++idx)
906 : {
907 0 : std::string name;
908 0 : if(variables[idx].f_name_size < sizeof(variables[idx].f_name))
909 : {
910 0 : name = std::string(
911 0 : reinterpret_cast<char const *>(&variables[idx].f_name)
912 0 : , variables[idx].f_name_size);
913 : }
914 : else
915 : {
916 0 : in.seekg(variables[idx].f_name, std::ios_base::beg);
917 0 : name = std::string(variables[idx].f_name_size, ' ');
918 0 : in.read(name.data(), variables[idx].f_name_size);
919 : }
920 0 : if(idx == 0)
921 : {
922 0 : std::cout << "var ";
923 : }
924 : else
925 : {
926 0 : std::cout << ",\n ";
927 : }
928 : #if 0
929 : // show the offset of where the variable data is defined
930 : // (for easy verification purposes, instead of having to read the binary)
931 : //
932 : std::cout
933 : << " /* offset: "
934 : << (variables[idx].f_data_size > sizeof(variables[idx].f_data)
935 : ? variables[idx].f_data
936 : : header.f_variables
937 : + idx * sizeof(as2js::binary_variable)
938 : + offsetof(as2js::binary_variable, f_data))
939 : << " */ ";
940 : #endif
941 0 : std::cout << name;
942 0 : switch(variables[idx].f_type)
943 : {
944 0 : case as2js::variable_type_t::VARIABLE_TYPE_BOOLEAN:
945 0 : std::cout << ": Boolean";
946 0 : if(variables[idx].f_data != 0)
947 : {
948 0 : std::cerr << " := true";
949 : }
950 0 : break;
951 :
952 0 : case as2js::variable_type_t::VARIABLE_TYPE_INTEGER:
953 0 : std::cout << ": Integer";
954 0 : if(variables[idx].f_data != 0)
955 : {
956 0 : std::cerr << " := " << variables[idx].f_data;
957 : }
958 0 : break;
959 :
960 0 : case as2js::variable_type_t::VARIABLE_TYPE_FLOATING_POINT:
961 0 : std::cout << ": Double";
962 0 : if(variables[idx].f_data != 0)
963 : {
964 0 : std::uint64_t const * ptr(&variables[idx].f_data);
965 0 : std::cerr << " := " << *reinterpret_cast<double const *>(ptr);
966 : }
967 0 : break;
968 :
969 0 : case as2js::variable_type_t::VARIABLE_TYPE_STRING:
970 0 : std::cout << ": String";
971 0 : if(variables[idx].f_data_size > 0)
972 : {
973 0 : std::string value;
974 0 : if(variables[idx].f_data_size < sizeof(variables[idx].f_data))
975 : {
976 0 : value = std::string(
977 0 : reinterpret_cast<char const *>(&variables[idx].f_data)
978 0 : , variables[idx].f_data_size);
979 : }
980 : else
981 : {
982 0 : in.seekg(variables[idx].f_data, std::ios_base::beg);
983 0 : value = std::string(variables[idx].f_data_size, ' ');
984 0 : in.read(value.data(), variables[idx].f_data_size);
985 : }
986 0 : std::cout << " = " << value;
987 0 : }
988 0 : break;
989 :
990 : //case as2js::variable_type_t::VARIABLE_TYPE_UNKNOWN:
991 0 : default:
992 0 : std::cout << " /* unknown type */ ";
993 0 : break;
994 :
995 : }
996 0 : }
997 0 : std::cout << ";\n";
998 0 : }
999 :
1000 :
1001 0 : void as2js_compiler::create_archive()
1002 : {
1003 0 : as2js::archive ar;
1004 0 : if(!ar.create(f_filenames))
1005 : {
1006 0 : ++f_error_count;
1007 : std::cerr
1008 0 : << "error: could not create archive file.\n";
1009 0 : return;
1010 : }
1011 :
1012 0 : as2js::output_stream<std::ofstream>::pointer_t output(std::make_shared<as2js::output_stream<std::ofstream>>());
1013 0 : output->open(f_output);
1014 0 : if(!output->is_open())
1015 : {
1016 0 : ++f_error_count;
1017 : std::cerr
1018 : << "error: could not open archive file \""
1019 0 : << f_output
1020 0 : << "\" to save run-time functions.\n";
1021 0 : return;
1022 : }
1023 :
1024 0 : if(!ar.save(output))
1025 : {
1026 0 : ++f_error_count;
1027 : std::cerr
1028 : << "error: could not save archive file \""
1029 0 : << f_output
1030 0 : << "\".\n";
1031 0 : return;
1032 : }
1033 0 : }
1034 :
1035 :
1036 0 : void as2js_compiler::list_archive()
1037 : {
1038 0 : if(f_filenames.size() != 1)
1039 : {
1040 0 : ++f_error_count;
1041 : std::cerr
1042 0 : << "error: expected exactly one filename with --list-archive or --extract-archive, found "
1043 0 : << f_filenames.size()
1044 0 : << " instead.\n";
1045 0 : return;
1046 : }
1047 :
1048 0 : as2js::input_stream<std::ifstream>::pointer_t in(std::make_shared<as2js::input_stream<std::ifstream>>());
1049 0 : in->open(f_filenames[0]);
1050 0 : if(!in->is_open())
1051 : {
1052 : // TODO: test again with .oar extension
1053 : //
1054 0 : ++f_error_count;
1055 : std::cerr
1056 : << "error: could not open archive \""
1057 0 : << f_filenames[0]
1058 0 : << "\".\n";
1059 0 : return;
1060 : }
1061 :
1062 0 : as2js::archive ar;
1063 0 : if(!ar.load(in))
1064 : {
1065 0 : ++f_error_count;
1066 : std::cerr
1067 : << "error: failed loading archive \""
1068 0 : << f_filenames[0]
1069 0 : << "\".\n";
1070 0 : return;
1071 : }
1072 :
1073 0 : as2js::rt_function::map_t functions(ar.get_functions());
1074 0 : std::size_t name_width(10);
1075 0 : for(auto const & f : functions)
1076 : {
1077 0 : name_width = std::max(f.first.length(), name_width);
1078 : }
1079 : std::cout
1080 0 : << " NAME" << std::setw(name_width - 4) << ' ' << "SIZE\n";
1081 0 : int pos(1);
1082 0 : for(auto const & f : functions)
1083 : {
1084 : std::cout
1085 0 : << std::setw(3) << pos
1086 0 : << ". " << f.first
1087 0 : << std::setw(name_width - f.first.length()) << ' '
1088 0 : << f.second->get_code().size()
1089 0 : << "\n";
1090 0 : ++pos;
1091 : }
1092 0 : }
1093 :
1094 :
1095 0 : void as2js_compiler::execute()
1096 : {
1097 0 : if(f_filenames.empty())
1098 : {
1099 0 : f_filenames.push_back("a.out");
1100 : }
1101 0 : else if(f_filenames.size() > 1)
1102 : {
1103 0 : ++f_error_count;
1104 : std::cerr
1105 0 : << "error: --execute expects exactly one filename.\n";
1106 0 : return;
1107 : }
1108 :
1109 0 : as2js::running_file script;
1110 0 : if(!script.load(f_filenames[0]))
1111 : {
1112 0 : ++f_error_count;
1113 : std::cerr
1114 : << "error: could not load \""
1115 0 : << f_filenames[0]
1116 0 : << "\" to execute code.\n";
1117 0 : return;
1118 : }
1119 :
1120 0 : for(auto const & var : f_variables)
1121 : {
1122 : //std::cerr << "--- var = [" << var.second << "]\n";
1123 : // TODO: support all types of variables
1124 : //
1125 0 : std::string value(var.second);
1126 0 : std::size_t pos(var.first.find(':'));
1127 0 : std::string name;
1128 0 : std::string type;
1129 0 : if(pos != std::string::npos)
1130 : {
1131 0 : name = var.first.substr(0, pos);
1132 0 : type = var.first.substr(pos + 1);
1133 : }
1134 : else
1135 : {
1136 0 : name = var.first;
1137 0 : if(value.empty())
1138 : {
1139 0 : type = "string";
1140 : }
1141 0 : else if(value.length() >= 2
1142 0 : && value[0] == '"'
1143 0 : && value[value.length() - 1] == '"')
1144 : {
1145 0 : type = "string";
1146 0 : value = value.substr(1, value.length() - 2);
1147 : }
1148 0 : else if(value.length() >= 2
1149 0 : && value[0] == '\''
1150 0 : && value[value.length() - 1] == '\'')
1151 : {
1152 0 : type = "string";
1153 0 : value = value.substr(1, value.length() - 2);
1154 : }
1155 : else
1156 : {
1157 0 : type = "integer";
1158 0 : std::size_t const max(value.length());
1159 0 : std::size_t idx(0);
1160 0 : if(max >= 2
1161 0 : && (value[idx] == '+' || value[idx] == '-'))
1162 : {
1163 0 : ++idx;
1164 : }
1165 0 : for(; idx < max; ++idx)
1166 : {
1167 0 : char const c(value[idx]);
1168 0 : if(c == '.')
1169 : {
1170 0 : if(type == "integer")
1171 : {
1172 0 : type = "double";
1173 : }
1174 : else
1175 : {
1176 0 : type = "string";
1177 0 : break;
1178 : }
1179 : }
1180 0 : else if(c < '0' || c > '9')
1181 : {
1182 0 : if(value == "false"
1183 0 : || value == "true")
1184 : {
1185 0 : type = "boolean";
1186 0 : break;
1187 : }
1188 0 : type = "string";
1189 0 : break;
1190 : }
1191 0 : else if(max > 0 && (c == 'e' || c == 'E'))
1192 : {
1193 0 : if(type == "integer")
1194 : {
1195 0 : type = "double";
1196 : }
1197 0 : else if(type != "double")
1198 : {
1199 0 : type = "string";
1200 0 : break;
1201 : }
1202 0 : ++idx;
1203 0 : if(value[idx] == '+' || value[idx] == '-')
1204 : {
1205 0 : ++idx;
1206 : }
1207 0 : if(idx >= max)
1208 : {
1209 0 : type = "string";
1210 0 : break;
1211 : }
1212 0 : for(; idx < max; ++idx)
1213 : {
1214 0 : char const e(value[idx]);
1215 0 : if(e < '0' || e > '9')
1216 : {
1217 0 : type = "string";
1218 0 : break;
1219 : }
1220 : }
1221 0 : break;
1222 : }
1223 : }
1224 : }
1225 : }
1226 0 : if(type == "string"
1227 0 : && f_three_underscores_to_space)
1228 : {
1229 0 : value = snapdev::string_replace_many(value, {{"___", " "}});
1230 : }
1231 0 : if(name.empty())
1232 : {
1233 0 : ++f_error_count;
1234 : std::cerr
1235 0 : << "error: a variable must have a name before its ':<type>' specification.\n";
1236 0 : return;
1237 : }
1238 :
1239 0 : if(!script.has_variable(name))
1240 : {
1241 0 : if(!f_ignore_unknown_variables)
1242 : {
1243 0 : ++f_error_count;
1244 : std::cerr
1245 : << "error: unknown variable \""
1246 : << name
1247 : << "\" in \""
1248 0 : << f_filenames[0]
1249 0 : << "\".\n";
1250 0 : return;
1251 : }
1252 : std::cerr
1253 : << "warning: variable \""
1254 : << name
1255 : << "\" not found in \""
1256 0 : << f_filenames[0]
1257 0 : << "\".\n";
1258 0 : continue;
1259 : }
1260 0 : type = snapdev::to_lower(type);
1261 0 : if(type == "integer")
1262 : {
1263 0 : std::int64_t const integer(std::stoll(value, nullptr, 0));
1264 0 : script.set_variable(name, integer);
1265 : }
1266 0 : else if(type == "boolean")
1267 : {
1268 0 : if(value == "true")
1269 : {
1270 0 : script.set_variable(name, true);
1271 : }
1272 0 : else if(value == "false")
1273 : {
1274 0 : script.set_variable(name, false);
1275 : }
1276 : else
1277 : {
1278 0 : ++f_error_count;
1279 : std::cerr
1280 0 : << "error: a boolean variable must be set to \"true\" or \"false\".\n";
1281 0 : return;
1282 : }
1283 : }
1284 0 : else if(type == "double")
1285 : {
1286 0 : double const floating_point(std::stod(value, nullptr));
1287 0 : script.set_variable(name, floating_point);
1288 : }
1289 0 : else if(type == "string")
1290 : {
1291 0 : script.set_variable(name, value);
1292 : }
1293 : else
1294 : {
1295 0 : ++f_error_count;
1296 : std::cerr
1297 : << "error: unknown variable type \""
1298 : << type
1299 : << "\" for \""
1300 : << name
1301 0 : << "\".\n";
1302 0 : return;
1303 : }
1304 0 : }
1305 :
1306 0 : as2js::binary_result result;
1307 :
1308 0 : script.run(result);
1309 :
1310 0 : if(!f_save_to_file.empty())
1311 : {
1312 0 : script.save(f_save_to_file);
1313 : }
1314 :
1315 0 : switch(result.get_type())
1316 : {
1317 0 : case as2js::variable_type_t::VARIABLE_TYPE_BOOLEAN:
1318 0 : std::cout << std::boolalpha << result.get_boolean() << "\n";
1319 0 : break;
1320 :
1321 0 : case as2js::variable_type_t::VARIABLE_TYPE_INTEGER:
1322 0 : std::cout << result.get_integer() << "\n";
1323 0 : break;
1324 :
1325 0 : case as2js::variable_type_t::VARIABLE_TYPE_FLOATING_POINT:
1326 0 : std::cout << result.get_floating_point() << "\n";
1327 0 : break;
1328 :
1329 0 : case as2js::variable_type_t::VARIABLE_TYPE_STRING:
1330 0 : std::cout << result.get_string() << "\n";
1331 0 : break;
1332 :
1333 0 : default:
1334 0 : std::cerr << "error: unknown result type in as2js_compiler.\n";
1335 0 : break;
1336 :
1337 : }
1338 :
1339 0 : if(f_show_all_results)
1340 : {
1341 0 : std::size_t const count(script.variable_size());
1342 0 : for(std::size_t idx(0); idx < count; ++idx)
1343 : {
1344 0 : std::string name;
1345 0 : as2js::binary_variable * var(script.get_variable(idx, name));
1346 :
1347 0 : std::cout << name << "=";
1348 :
1349 : std::uint64_t const * data;
1350 0 : if(var->f_data_size <= sizeof(var->f_data))
1351 : {
1352 0 : data = &var->f_data;
1353 : }
1354 : else
1355 : {
1356 0 : data = reinterpret_cast<std::uint64_t const *>(var->f_data);
1357 : }
1358 :
1359 0 : switch(var->f_type)
1360 : {
1361 0 : case as2js::variable_type_t::VARIABLE_TYPE_UNKNOWN:
1362 0 : std::cout << "<unknown variable type>\n";
1363 0 : break;
1364 :
1365 0 : case as2js::variable_type_t::VARIABLE_TYPE_BOOLEAN:
1366 0 : std::cout << std::boolalpha << ((*data & 255) != 0) << '\n';
1367 0 : break;
1368 :
1369 0 : case as2js::variable_type_t::VARIABLE_TYPE_INTEGER:
1370 0 : std::cout << static_cast<std::int64_t>(*data) << '\n';
1371 0 : break;
1372 :
1373 0 : case as2js::variable_type_t::VARIABLE_TYPE_FLOATING_POINT:
1374 0 : std::cout << *reinterpret_cast<double const *>(data) << '\n';
1375 0 : break;
1376 :
1377 0 : case as2js::variable_type_t::VARIABLE_TYPE_STRING:
1378 0 : if(data != nullptr)
1379 : {
1380 0 : std::cout << std::string(reinterpret_cast<char const *>(data), var->f_data_size) << '\n';
1381 : }
1382 : else
1383 : {
1384 0 : std::cout << "(null)\n";
1385 : }
1386 0 : break;
1387 :
1388 0 : case as2js::variable_type_t::VARIABLE_TYPE_ARRAY:
1389 : // TODO: implement this for() loop in a sub-function which
1390 : // we can call recursively
1391 : //
1392 0 : std::cout << "--- found array ---\n";
1393 0 : break;
1394 :
1395 0 : case as2js::variable_type_t::VARIABLE_TYPE_RANGE:
1396 0 : std::cout << "--- RANGE value not yet supported in as2js ---\n";
1397 0 : break;
1398 :
1399 : }
1400 0 : }
1401 : }
1402 0 : }
1403 :
1404 :
1405 :
1406 : } // no name namespace
1407 :
1408 :
1409 33 : int main(int argc, char *argv[])
1410 : {
1411 : try
1412 : {
1413 33 : as2js_compiler::pointer_t c(std::make_shared<as2js_compiler>());
1414 33 : if(c->parse_command_line_options(argc, argv) != 0)
1415 : {
1416 0 : return 1;
1417 : }
1418 33 : return c->run();
1419 33 : }
1420 0 : catch(std::exception const & e)
1421 : {
1422 0 : std::cerr << "as2js: exception: " << e.what() << std::endl;
1423 0 : exit(1);
1424 0 : }
1425 :
1426 : return 0;
1427 : }
1428 :
1429 :
1430 : // vim: ts=4 sw=4 et
|