1/* Copyright JS Foundation and other contributors, http://js.foundation
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include <assert.h>
17#include <stdlib.h>
18#include <string.h>
19
20#include "jerryscript.h"
21#include "jerryscript-port.h"
22#include "jerryscript-port-default.h"
23
24#include "cli.h"
25
26/**
27 * Maximum size for loaded snapshots
28 */
29#define JERRY_BUFFER_SIZE (1048576)
30
31/**
32 * Maximum number of loaded literals
33 */
34#define JERRY_LITERAL_LENGTH (4096)
35
36/**
37 * Standalone Jerry exit codes
38 */
39#define JERRY_STANDALONE_EXIT_CODE_OK   (0)
40#define JERRY_STANDALONE_EXIT_CODE_FAIL (1)
41
42#ifdef _WIN32
43#  ifdef _WIN64
44#    define PRI_SIZET "lu"
45#    define SIZE_T_TYPE unsigned long
46#  else
47#    define PRI_SIZET "zu"
48#    define SIZE_T_TYPE size_t
49#  endif
50#else
51#  define PRI_SIZET "zu"
52#  define SIZE_T_TYPE size_t
53#endif
54
55static uint8_t input_buffer[JERRY_BUFFER_SIZE];
56static uint32_t output_buffer[JERRY_BUFFER_SIZE / 4];
57static jerry_char_t literal_buffer[JERRY_BUFFER_SIZE];
58static const char *output_file_name_p = "js.snapshot";
59static jerry_length_t magic_string_lengths[JERRY_LITERAL_LENGTH];
60static const jerry_char_t *magic_string_items[JERRY_LITERAL_LENGTH];
61
62#if defined (JERRY_EXTERNAL_CONTEXT) && (JERRY_EXTERNAL_CONTEXT == 1)
63/**
64 * The alloc function passed to jerry_create_context
65 */
66static void *
67context_alloc (size_t size,
68               void *cb_data_p)
69{
70  (void) cb_data_p; /* unused */
71  return malloc (size);
72} /* context_alloc */
73
74/**
75 * Create and set the default external context.
76 */
77static void
78context_init (void)
79{
80  jerry_context_t *context_p = jerry_create_context (JERRY_GLOBAL_HEAP_SIZE * 1024, context_alloc, NULL);
81  jerry_port_default_set_current_context (context_p);
82} /* context_init */
83
84#endif /* defined (JERRY_EXTERNAL_CONTEXT) && (JERRY_EXTERNAL_CONTEXT == 1) */
85
86/**
87 * Check whether JerryScript has a requested feature enabled or not. If not,
88 * print a warning message.
89 *
90 * @return the status of the feature.
91 */
92static bool
93check_feature (jerry_feature_t feature, /**< feature to check */
94               const char *option) /**< command line option that triggered this check */
95{
96  if (!jerry_is_feature_enabled (feature))
97  {
98    jerry_port_default_set_log_level (JERRY_LOG_LEVEL_WARNING);
99    jerry_port_log (JERRY_LOG_LEVEL_WARNING, "Ignoring '%s' option because this feature is disabled!\n", option);
100    return false;
101  }
102  return true;
103} /* check_feature */
104
105/**
106 * Utility method to check and print error in the given cli state.
107 *
108 * @return true - if any error is detected
109 *         false - if there is no error in the cli state
110 */
111static bool
112check_cli_error (const cli_state_t *const cli_state_p)
113{
114  if (cli_state_p->error != NULL)
115  {
116    if (cli_state_p->arg != NULL)
117    {
118      jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s %s\n", cli_state_p->error, cli_state_p->arg);
119    }
120    else
121    {
122      jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", cli_state_p->error);
123    }
124
125    return true;
126  }
127
128  return false;
129} /* check_cli_error */
130
131/**
132 * Loading a single file into the memory.
133 *
134 * @return size of file - if loading is successful
135 *         0 - otherwise
136 */
137static size_t
138read_file (uint8_t *input_pos_p, /**< next position in the input buffer */
139           const char *file_name) /**< file name */
140{
141  FILE *file = fopen (file_name, "rb");
142
143  if (file == NULL)
144  {
145    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: failed to open file: %s\n", file_name);
146    return 0;
147  }
148
149  size_t max_size = (size_t) (input_buffer + JERRY_BUFFER_SIZE - input_pos_p);
150
151  size_t bytes_read = fread (input_pos_p, 1u, max_size, file);
152  fclose (file);
153
154  if (bytes_read == 0)
155  {
156    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: failed to read file: %s\n", file_name);
157    return 0;
158  }
159
160  if (bytes_read == max_size)
161  {
162    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: file too large: %s\n", file_name);
163    return 0;
164  }
165
166  printf ("Input file '%s' (%"PRI_SIZET" bytes) loaded.\n", file_name, (SIZE_T_TYPE)bytes_read);
167  return bytes_read;
168} /* read_file */
169
170/**
171 * Print error value
172 */
173static void
174print_unhandled_exception (jerry_value_t error_value) /**< error value */
175{
176  assert (!jerry_value_is_error (error_value));
177
178  jerry_value_t err_str_val = jerry_value_to_string (error_value);
179
180  if (jerry_value_is_error (err_str_val))
181  {
182    /* Avoid recursive error throws. */
183    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Snapshot error: [value cannot be converted to string]\n");
184    jerry_release_value (err_str_val);
185    return;
186  }
187
188  jerry_size_t err_str_size = jerry_get_utf8_string_size (err_str_val);
189
190  if (err_str_size >= 256)
191  {
192    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Snapshot error: [value cannot be converted to string]\n");
193    jerry_release_value (err_str_val);
194    return;
195  }
196
197  jerry_char_t err_str_buf[256];
198  jerry_size_t string_end = jerry_string_to_utf8_char_buffer (err_str_val, err_str_buf, err_str_size);
199  assert (string_end == err_str_size);
200  err_str_buf[string_end] = 0;
201
202  jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Snapshot error: %s\n", (char *) err_str_buf);
203  jerry_release_value (err_str_val);
204} /* print_unhandled_exception */
205
206/**
207 * Generate command line option IDs
208 */
209typedef enum
210{
211  OPT_GENERATE_HELP,
212  OPT_GENERATE_STATIC,
213  OPT_GENERATE_SHOW_OP,
214  OPT_GENERATE_FUNCTION,
215  OPT_GENERATE_OUT,
216  OPT_IMPORT_LITERAL_LIST
217} generate_opt_id_t;
218
219/**
220 * Generate command line options
221 */
222static const cli_opt_t generate_opts[] =
223{
224  CLI_OPT_DEF (.id = OPT_GENERATE_HELP, .opt = "h", .longopt = "help",
225               .help = "print this help and exit"),
226  CLI_OPT_DEF (.id = OPT_GENERATE_STATIC, .opt = "s", .longopt = "static",
227               .help = "generate static snapshot"),
228  CLI_OPT_DEF (.id = OPT_GENERATE_FUNCTION, .opt = "f", .longopt = "generate-function-snapshot",
229               .meta = "ARGUMENTS",
230               .help = "generate function snapshot with given arguments"),
231  CLI_OPT_DEF (.id = OPT_IMPORT_LITERAL_LIST, .longopt = "load-literals-list-format",
232               .meta = "FILE",
233               .help = "import literals from list format (for static snapshots)"),
234  CLI_OPT_DEF (.id = OPT_GENERATE_SHOW_OP, .longopt = "show-opcodes",
235               .help = "print generated opcodes"),
236  CLI_OPT_DEF (.id = OPT_GENERATE_OUT, .opt = "o",  .meta="FILE",
237               .help = "specify output file name (default: js.snapshot)"),
238  CLI_OPT_DEF (.id = CLI_OPT_DEFAULT, .meta = "FILE",
239               .help = "input source file")
240};
241
242/**
243 * Process 'generate' command.
244 *
245 * @return error code (0 - no error)
246 */
247static int
248process_generate (cli_state_t *cli_state_p, /**< cli state */
249                  int argc, /**< number of arguments */
250                  char *prog_name_p) /**< program name */
251{
252  (void) argc;
253
254  uint32_t snapshot_flags = 0;
255  jerry_init_flag_t flags = JERRY_INIT_EMPTY;
256
257  const char *file_name_p = NULL;
258  uint8_t *source_p = input_buffer;
259  size_t source_length = 0;
260  const char *literals_file_name_p = NULL;
261  const char *function_args_p = NULL;
262
263  cli_change_opts (cli_state_p, generate_opts);
264
265  for (int id = cli_consume_option (cli_state_p); id != CLI_OPT_END; id = cli_consume_option (cli_state_p))
266  {
267    switch (id)
268    {
269      case OPT_GENERATE_HELP:
270      {
271        cli_help (prog_name_p, "generate", generate_opts);
272        return JERRY_STANDALONE_EXIT_CODE_OK;
273      }
274      case OPT_GENERATE_STATIC:
275      {
276        snapshot_flags |= JERRY_SNAPSHOT_SAVE_STATIC;
277        break;
278      }
279      case OPT_GENERATE_FUNCTION:
280      {
281        function_args_p = cli_consume_string (cli_state_p);
282        break;
283      }
284      case OPT_IMPORT_LITERAL_LIST:
285      {
286        literals_file_name_p = cli_consume_string (cli_state_p);
287        break;
288      }
289      case OPT_GENERATE_SHOW_OP:
290      {
291        if (check_feature (JERRY_FEATURE_PARSER_DUMP, cli_state_p->arg))
292        {
293          jerry_port_default_set_log_level (JERRY_LOG_LEVEL_DEBUG);
294          flags |= JERRY_INIT_SHOW_OPCODES;
295        }
296        break;
297      }
298      case OPT_GENERATE_OUT:
299      {
300        output_file_name_p = cli_consume_string (cli_state_p);
301        break;
302      }
303      case CLI_OPT_DEFAULT:
304      {
305        if (file_name_p != NULL)
306        {
307          jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: Exactly one input file must be specified\n");
308          return JERRY_STANDALONE_EXIT_CODE_FAIL;
309        }
310
311        file_name_p = cli_consume_string (cli_state_p);
312
313        if (cli_state_p->error == NULL)
314        {
315          source_length = read_file (source_p, file_name_p);
316
317          if (source_length == 0)
318          {
319            jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Input file is empty\n");
320            return JERRY_STANDALONE_EXIT_CODE_FAIL;
321          }
322        }
323        break;
324      }
325      default:
326      {
327        cli_state_p->error = "Internal error";
328        break;
329      }
330    }
331  }
332
333  if (check_cli_error (cli_state_p))
334  {
335    return JERRY_STANDALONE_EXIT_CODE_FAIL;
336  }
337
338  if (file_name_p == NULL)
339  {
340    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: Exactly one input file must be specified\n");
341    return JERRY_STANDALONE_EXIT_CODE_FAIL;
342  }
343
344#if defined (JERRY_EXTERNAL_CONTEXT) && (JERRY_EXTERNAL_CONTEXT == 1)
345  context_init ();
346#endif /* defined (JERRY_EXTERNAL_CONTEXT) && (JERRY_EXTERNAL_CONTEXT == 1) */
347
348  jerry_init (flags);
349
350  if (!jerry_is_valid_utf8_string (source_p, (jerry_size_t) source_length))
351  {
352    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: Input must be a valid UTF-8 string.\n");
353    jerry_cleanup ();
354    return JERRY_STANDALONE_EXIT_CODE_FAIL;
355  }
356
357  if (literals_file_name_p != NULL)
358  {
359    /* Import literal list */
360    uint8_t *sp_buffer_start_p = source_p + source_length + 1;
361    size_t sp_buffer_size = read_file (sp_buffer_start_p, literals_file_name_p);
362
363    if (sp_buffer_size > 0)
364    {
365      const char *sp_buffer_p = (const char *) sp_buffer_start_p;
366      uint32_t num_of_lit = 0;
367
368      do
369      {
370        char *sp_buffer_end_p = NULL;
371        jerry_length_t mstr_size = (jerry_length_t) strtol (sp_buffer_p, &sp_buffer_end_p, 10);
372        if (mstr_size > 0)
373        {
374          magic_string_items[num_of_lit] = (jerry_char_t *) (sp_buffer_end_p + 1);
375          magic_string_lengths[num_of_lit] = mstr_size;
376          num_of_lit++;
377        }
378        sp_buffer_p = sp_buffer_end_p + mstr_size + 1;
379      }
380      while ((size_t) (sp_buffer_p - (char *) sp_buffer_start_p) < sp_buffer_size);
381
382      if (num_of_lit > 0)
383      {
384        jerry_register_magic_strings (magic_string_items, num_of_lit,
385                                      magic_string_lengths);
386      }
387    }
388  }
389
390  jerry_value_t snapshot_result;
391
392  if (function_args_p != NULL)
393  {
394    snapshot_result = jerry_generate_function_snapshot ((jerry_char_t *) file_name_p,
395                                                        (size_t) strlen (file_name_p),
396                                                        (jerry_char_t *) source_p,
397                                                        source_length,
398                                                        (const jerry_char_t *) function_args_p,
399                                                        strlen (function_args_p),
400                                                        snapshot_flags,
401                                                        output_buffer,
402                                                        sizeof (output_buffer) / sizeof (uint32_t));
403  }
404  else
405  {
406    snapshot_result = jerry_generate_snapshot ((jerry_char_t *) file_name_p,
407                                               (size_t) strlen (file_name_p),
408                                               (jerry_char_t *) source_p,
409                                               source_length,
410                                               snapshot_flags,
411                                               output_buffer,
412                                               sizeof (output_buffer) / sizeof (uint32_t));
413  }
414
415  if (jerry_value_is_error (snapshot_result))
416  {
417    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: Generating snapshot failed!\n");
418
419    snapshot_result = jerry_get_value_from_error (snapshot_result, true);
420
421    print_unhandled_exception (snapshot_result);
422
423    jerry_release_value (snapshot_result);
424    jerry_cleanup ();
425    return JERRY_STANDALONE_EXIT_CODE_FAIL;
426  }
427
428  size_t snapshot_size = (size_t) jerry_get_number_value (snapshot_result);
429  jerry_release_value (snapshot_result);
430
431  FILE *snapshot_file_p = fopen (output_file_name_p, "wb");
432  if (snapshot_file_p == NULL)
433  {
434    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: Unable to write snapshot file: '%s'\n", output_file_name_p);
435    jerry_cleanup ();
436    return JERRY_STANDALONE_EXIT_CODE_FAIL;
437  }
438
439  fwrite (output_buffer, sizeof (uint8_t), snapshot_size, snapshot_file_p);
440  fclose (snapshot_file_p);
441
442  printf ("Created snapshot file: '%s' (%zu bytes)\n", output_file_name_p, snapshot_size);
443
444  jerry_cleanup ();
445  return JERRY_STANDALONE_EXIT_CODE_OK;
446} /* process_generate */
447
448/**
449 * Literal dump command line option IDs
450 */
451typedef enum
452{
453  OPT_LITERAL_DUMP_HELP,
454  OPT_LITERAL_DUMP_FORMAT,
455  OPT_LITERAL_DUMP_OUT,
456} literal_dump_opt_id_t;
457
458/**
459 * Literal dump command line options
460 */
461static const cli_opt_t literal_dump_opts[] =
462{
463  CLI_OPT_DEF (.id = OPT_LITERAL_DUMP_HELP, .opt = "h", .longopt = "help",
464               .help = "print this help and exit"),
465  CLI_OPT_DEF (.id = OPT_LITERAL_DUMP_FORMAT, .longopt = "format",
466               .meta = "[c|list]",
467               .help = "specify output format (default: list)"),
468  CLI_OPT_DEF (.id = OPT_LITERAL_DUMP_OUT, .opt = "o",
469               .help = "specify output file name (default: literals.[h|list])"),
470  CLI_OPT_DEF (.id = CLI_OPT_DEFAULT, .meta = "FILE(S)",
471               .help = "input snapshot files")
472};
473
474/**
475 * Process 'litdump' command.
476 *
477 * @return error code (0 - no error)
478 */
479static int
480process_literal_dump (cli_state_t *cli_state_p, /**< cli state */
481                      int argc, /**< number of arguments */
482                      char *prog_name_p) /**< program name */
483{
484  uint8_t *input_pos_p = input_buffer;
485
486  cli_change_opts (cli_state_p, literal_dump_opts);
487
488  JERRY_VLA (const uint32_t *, snapshot_buffers, argc);
489  JERRY_VLA (size_t, snapshot_buffer_sizes, argc);
490  uint32_t number_of_files = 0;
491  const char *literals_file_name_p = NULL;
492  bool is_c_format = false;
493
494  for (int id = cli_consume_option (cli_state_p); id != CLI_OPT_END; id = cli_consume_option (cli_state_p))
495  {
496    switch (id)
497    {
498      case OPT_LITERAL_DUMP_HELP:
499      {
500        cli_help (prog_name_p, "litdump", literal_dump_opts);
501        return JERRY_STANDALONE_EXIT_CODE_OK;
502      }
503      case OPT_LITERAL_DUMP_FORMAT:
504      {
505        const char *fromat_str_p = cli_consume_string (cli_state_p);
506        if (!strcmp ("c", fromat_str_p))
507        {
508          is_c_format = true;
509        }
510        else if (!strcmp ("list", fromat_str_p))
511        {
512          is_c_format = false;
513        }
514        else
515        {
516          jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: Unsupported literal dump format.");
517          return JERRY_STANDALONE_EXIT_CODE_FAIL;
518        }
519        break;
520      }
521      case OPT_LITERAL_DUMP_OUT:
522      {
523        literals_file_name_p = cli_consume_string (cli_state_p);
524        break;
525      }
526      case CLI_OPT_DEFAULT:
527      {
528        const char *file_name_p = cli_consume_string (cli_state_p);
529
530        if (cli_state_p->error == NULL)
531        {
532          size_t size = read_file (input_pos_p, file_name_p);
533
534          if (size == 0)
535          {
536            return JERRY_STANDALONE_EXIT_CODE_FAIL;
537          }
538
539          snapshot_buffers[number_of_files] = (const uint32_t *) input_pos_p;
540          snapshot_buffer_sizes[number_of_files] = size;
541
542          number_of_files++;
543          const uintptr_t mask = sizeof (uint32_t) - 1;
544          input_pos_p = (uint8_t *) ((((uintptr_t) input_pos_p) + size + mask) & ~mask);
545        }
546        break;
547      }
548      default:
549      {
550        cli_state_p->error = "Internal error";
551        break;
552      }
553    }
554  }
555
556  if (check_cli_error (cli_state_p))
557  {
558    return JERRY_STANDALONE_EXIT_CODE_FAIL;
559  }
560
561  if (number_of_files < 1)
562  {
563    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: at least one input file must be specified.\n");
564    return JERRY_STANDALONE_EXIT_CODE_FAIL;
565  }
566
567#if defined (JERRY_EXTERNAL_CONTEXT) && (JERRY_EXTERNAL_CONTEXT == 1)
568  context_init ();
569#endif /* defined (JERRY_EXTERNAL_CONTEXT) && (JERRY_EXTERNAL_CONTEXT == 1) */
570
571  jerry_init (JERRY_INIT_EMPTY);
572
573  size_t lit_buf_sz = 0;
574  if (number_of_files == 1)
575  {
576    lit_buf_sz = jerry_get_literals_from_snapshot (snapshot_buffers[0],
577                                                   snapshot_buffer_sizes[0],
578                                                   literal_buffer,
579                                                   JERRY_BUFFER_SIZE,
580                                                   is_c_format);
581  }
582  else
583  {
584    /* The input contains more than one input snapshot file, so we must merge them first. */
585    const char *error_p = NULL;
586    size_t merged_snapshot_size = jerry_merge_snapshots (snapshot_buffers,
587                                                         snapshot_buffer_sizes,
588                                                         number_of_files,
589                                                         output_buffer,
590                                                         JERRY_BUFFER_SIZE,
591                                                         &error_p);
592
593    if (merged_snapshot_size == 0)
594    {
595      jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", error_p);
596      jerry_cleanup ();
597      return JERRY_STANDALONE_EXIT_CODE_FAIL;
598    }
599
600    printf ("Successfully merged the input snapshots (%"PRI_SIZET" bytes).\n", (SIZE_T_TYPE)merged_snapshot_size);
601
602    lit_buf_sz = jerry_get_literals_from_snapshot (output_buffer,
603                                                   merged_snapshot_size,
604                                                   literal_buffer,
605                                                   JERRY_BUFFER_SIZE,
606                                                   is_c_format);
607  }
608
609  if (lit_buf_sz == 0)
610  {
611    jerry_port_log (JERRY_LOG_LEVEL_ERROR,
612                    "Error: Literal saving failed! No literals were found in the input snapshot(s).\n");
613    jerry_cleanup ();
614    return JERRY_STANDALONE_EXIT_CODE_FAIL;
615  }
616
617  if (literals_file_name_p == NULL)
618  {
619    literals_file_name_p = is_c_format ? "literals.h" : "literals.list";
620  }
621
622  FILE *file_p = fopen (literals_file_name_p, "wb");
623
624  if (file_p == NULL)
625  {
626    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: cannot open file: '%s'\n", literals_file_name_p);
627    jerry_cleanup ();
628    return JERRY_STANDALONE_EXIT_CODE_FAIL;
629  }
630
631  fwrite (literal_buffer, sizeof (uint8_t), lit_buf_sz, file_p);
632  fclose (file_p);
633
634  printf ("Literals are saved into '%s' (%"PRI_SIZET" bytes).\n", literals_file_name_p, (SIZE_T_TYPE)lit_buf_sz);
635
636  jerry_cleanup ();
637  return JERRY_STANDALONE_EXIT_CODE_OK;
638} /* process_literal_dump */
639
640/**
641 * Merge command line option IDs
642 */
643typedef enum
644{
645  OPT_MERGE_HELP,
646  OPT_MERGE_OUT,
647} merge_opt_id_t;
648
649/**
650 * Merge command line options
651 */
652static const cli_opt_t merge_opts[] =
653{
654  CLI_OPT_DEF (.id = OPT_MERGE_HELP, .opt = "h", .longopt = "help",
655               .help = "print this help and exit"),
656  CLI_OPT_DEF (.id = OPT_MERGE_OUT, .opt = "o",
657               .help = "specify output file name (default: js.snapshot)"),
658  CLI_OPT_DEF (.id = CLI_OPT_DEFAULT, .meta = "FILE",
659               .help = "input snapshot files, minimum two")
660};
661
662/**
663 * Process 'merge' command.
664 *
665 * @return error code (0 - no error)
666 */
667static int
668process_merge (cli_state_t *cli_state_p, /**< cli state */
669               int argc, /**< number of arguments */
670               char *prog_name_p) /**< program name */
671{
672  uint8_t *input_pos_p = input_buffer;
673
674  cli_change_opts (cli_state_p, merge_opts);
675
676  JERRY_VLA (const uint32_t *, merge_buffers, argc);
677  JERRY_VLA (size_t, merge_buffer_sizes, argc);
678  uint32_t number_of_files = 0;
679
680  for (int id = cli_consume_option (cli_state_p); id != CLI_OPT_END; id = cli_consume_option (cli_state_p))
681  {
682    switch (id)
683    {
684      case OPT_MERGE_HELP:
685      {
686        cli_help (prog_name_p, "merge", merge_opts);
687        return JERRY_STANDALONE_EXIT_CODE_OK;
688      }
689      case OPT_MERGE_OUT:
690      {
691        output_file_name_p = cli_consume_string (cli_state_p);
692        break;
693      }
694      case CLI_OPT_DEFAULT:
695      {
696        const char *file_name_p = cli_consume_string (cli_state_p);
697
698        if (cli_state_p->error == NULL)
699        {
700          size_t size = read_file (input_pos_p, file_name_p);
701
702          if (size == 0)
703          {
704            return JERRY_STANDALONE_EXIT_CODE_FAIL;
705          }
706
707          merge_buffers[number_of_files] = (const uint32_t *) input_pos_p;
708          merge_buffer_sizes[number_of_files] = size;
709
710          number_of_files++;
711          const uintptr_t mask = sizeof (uint32_t) - 1;
712          input_pos_p = (uint8_t *) ((((uintptr_t) input_pos_p) + size + mask) & ~mask);
713        }
714        break;
715      }
716      default:
717      {
718        cli_state_p->error = "Internal error";
719        break;
720      }
721    }
722  }
723
724  if (check_cli_error (cli_state_p))
725  {
726    return JERRY_STANDALONE_EXIT_CODE_FAIL;
727  }
728
729  if (number_of_files < 2)
730  {
731    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: at least two input files must be passed.\n");
732    return JERRY_STANDALONE_EXIT_CODE_FAIL;
733  }
734
735#if defined (JERRY_EXTERNAL_CONTEXT) && (JERRY_EXTERNAL_CONTEXT == 1)
736  context_init ();
737#endif /* defined (JERRY_EXTERNAL_CONTEXT) && (JERRY_EXTERNAL_CONTEXT == 1) */
738
739  jerry_init (JERRY_INIT_EMPTY);
740
741  const char *error_p = NULL;
742  size_t merged_snapshot_size = jerry_merge_snapshots (merge_buffers,
743                                                       merge_buffer_sizes,
744                                                       number_of_files,
745                                                       output_buffer,
746                                                       JERRY_BUFFER_SIZE,
747                                                       &error_p);
748
749  if (merged_snapshot_size == 0)
750  {
751    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: %s\n", error_p);
752    jerry_cleanup ();
753    return JERRY_STANDALONE_EXIT_CODE_FAIL;
754  }
755
756  FILE *file_p = fopen (output_file_name_p, "wb");
757
758  if (file_p == NULL)
759  {
760    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: cannot open file: '%s'\n", output_file_name_p);
761    jerry_cleanup ();
762    return JERRY_STANDALONE_EXIT_CODE_FAIL;
763  }
764
765  fwrite (output_buffer, 1u, merged_snapshot_size, file_p);
766  fclose (file_p);
767
768  printf ("Merge is completed. Merged snapshot is saved into '%s' (%"PRI_SIZET" bytes).\n",
769          output_file_name_p,
770          (SIZE_T_TYPE)merged_snapshot_size);
771
772  jerry_cleanup ();
773  return JERRY_STANDALONE_EXIT_CODE_OK;
774} /* process_merge */
775
776/**
777 * Command line option IDs
778 */
779typedef enum
780{
781  OPT_HELP,
782} main_opt_id_t;
783
784/**
785 * Command line options
786 */
787static const cli_opt_t main_opts[] =
788{
789  CLI_OPT_DEF (.id = OPT_HELP, .opt = "h", .longopt = "help",
790               .help = "print this help and exit"),
791  CLI_OPT_DEF (.id = CLI_OPT_DEFAULT, .meta = "COMMAND",
792               .help = "specify the command")
793};
794
795/**
796 * Print available commands.
797 */
798static void
799print_commands (char *prog_name_p) /**< program name */
800{
801  cli_help (prog_name_p, NULL, main_opts);
802
803  printf ("\nAvailable commands:\n"
804          "  generate\n"
805          "  litdump\n"
806          "  merge\n"
807          "\nPassing -h or --help after a command displays its help.\n");
808} /* print_commands */
809
810/**
811 * Main function.
812 *
813 * @return error code (0 - no error)
814 */
815int
816main (int argc, /**< number of arguments */
817      char **argv) /**< argument list */
818{
819  cli_state_t cli_state = cli_init (main_opts, argc - 1, argv + 1);
820
821  for (int id = cli_consume_option (&cli_state); id != CLI_OPT_END; id = cli_consume_option (&cli_state))
822  {
823    switch (id)
824    {
825      case OPT_MERGE_HELP:
826      {
827        /* Help is always printed if no command is provided. */
828        break;
829      }
830      case CLI_OPT_DEFAULT:
831      {
832        const char *command_p = cli_consume_string (&cli_state);
833
834        if (cli_state.error != NULL)
835        {
836          break;
837        }
838
839        if (!strcmp ("merge", command_p))
840        {
841          return process_merge (&cli_state, argc, argv[0]);
842        }
843        else if (!strcmp ("litdump", command_p))
844        {
845          return process_literal_dump (&cli_state, argc, argv[0]);
846        }
847        else if (!strcmp ("generate", command_p))
848        {
849          return process_generate (&cli_state, argc, argv[0]);
850        }
851
852        jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: unknown command: %s\n\n", command_p);
853        print_commands (argv[0]);
854
855        return JERRY_STANDALONE_EXIT_CODE_FAIL;
856      }
857      default:
858      {
859        cli_state.error = "Internal error";
860        break;
861      }
862    }
863  }
864
865  if (check_cli_error (&cli_state))
866  {
867    return JERRY_STANDALONE_EXIT_CODE_FAIL;
868  }
869
870  print_commands (argv[0]);
871  return JERRY_STANDALONE_EXIT_CODE_OK;
872} /* main */
873