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 <stdio.h>
17#include <string.h>
18#include <stdlib.h>
19
20#include "jerryscript.h"
21#include "jerryscript-ext/debugger.h"
22#include "jerryscript-ext/handler.h"
23#include "jerryscript-port.h"
24#include "setjmp.h"
25
26/**
27 * Maximum command line arguments number.
28 */
29#define JERRY_MAX_COMMAND_LINE_ARGS (16)
30
31/**
32 * Standalone Jerry exit codes.
33 */
34#define JERRY_STANDALONE_EXIT_CODE_OK   (0)
35#define JERRY_STANDALONE_EXIT_CODE_FAIL (1)
36
37/**
38 * Context size of the SYNTAX_ERROR
39 */
40#define SYNTAX_ERROR_CONTEXT_SIZE 2
41
42void set_log_level (jerry_log_level_t level);
43
44/**
45 * Print usage and available options
46 */
47static void
48print_help (char *name)
49{
50  printf ("Usage: %s [OPTION]... [FILE]...\n"
51          "\n"
52          "Options:\n"
53          "  --log-level [0-3]\n"
54          "  --mem-stats\n"
55          "  --mem-stats-separate\n"
56          "  --show-opcodes\n"
57          "  --start-debug-server\n"
58          "  --debug-server-port [port]\n"
59          "\n",
60          name);
61} /* print_help */
62
63/**
64 * Read source code into buffer.
65 *
66 * Returned value must be freed with jmem_heap_free_block if it's not NULL.
67 * @return NULL, if read or allocation has failed
68 *         pointer to the allocated memory block, otherwise
69 */
70static const uint8_t *
71read_file (const char *file_name, /**< source code */
72           size_t *out_size_p) /**< [out] number of bytes successfully read from source */
73{
74  FILE *file = fopen (file_name, "r");
75  if (file == NULL)
76  {
77    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: cannot open file: %s\n", file_name);
78    return NULL;
79  }
80
81  int fseek_status = fseek (file, 0, SEEK_END);
82  if (fseek_status != 0)
83  {
84    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Failed to seek (error: %d)\n", fseek_status);
85    fclose (file);
86    return NULL;
87  }
88
89  long script_len = ftell (file);
90  if (script_len < 0)
91  {
92    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Failed to get the file size(error %ld)\n", script_len);
93    fclose (file);
94    return NULL;
95  }
96
97  rewind (file);
98
99  uint8_t *buffer = (uint8_t *) malloc (script_len);
100
101  if (buffer == NULL)
102  {
103    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Out of memory error\n");
104    fclose (file);
105    return NULL;
106  }
107
108  size_t bytes_read = fread (buffer, 1u, script_len, file);
109
110  if (!bytes_read || bytes_read != script_len)
111  {
112    jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: failed to read file: %s\n", file_name);
113    free ((void*) buffer);
114
115    fclose (file);
116    return NULL;
117  }
118
119  fclose (file);
120
121  *out_size_p = bytes_read;
122  return (const uint8_t *) buffer;
123} /* read_file */
124
125/**
126 * Convert string into unsigned integer
127 *
128 * @return converted number
129 */
130static uint32_t
131str_to_uint (const char *num_str_p, /**< string to convert */
132             char **out_p) /**< [out] end of the number */
133{
134  assert (jerry_is_feature_enabled (JERRY_FEATURE_ERROR_MESSAGES));
135
136  uint32_t result = 0;
137
138  while (*num_str_p >= '0' && *num_str_p <= '9')
139  {
140    result *= 10;
141    result += (uint32_t) (*num_str_p - '0');
142    num_str_p++;
143  }
144
145  if (out_p != NULL)
146  {
147    *out_p = num_str_p;
148  }
149
150  return result;
151} /* str_to_uint */
152
153/**
154 * Print error value
155 */
156static void
157print_unhandled_exception (jerry_value_t error_value) /**< error value */
158{
159  assert (jerry_value_is_error (error_value));
160
161  error_value = jerry_get_value_from_error (error_value, false);
162  jerry_value_t err_str_val = jerry_value_to_string (error_value);
163  jerry_size_t err_str_size = jerry_get_utf8_string_size (err_str_val);
164  jerry_char_t err_str_buf[256];
165
166  jerry_release_value (error_value);
167
168  if (err_str_size >= 256)
169  {
170    const char msg[] = "[Error message too long]";
171    err_str_size = sizeof (msg) / sizeof (char) - 1;
172    memcpy (err_str_buf, msg, err_str_size);
173  }
174  else
175  {
176    jerry_size_t sz = jerry_string_to_utf8_char_buffer (err_str_val, err_str_buf, err_str_size);
177    assert (sz == err_str_size);
178    err_str_buf[err_str_size] = 0;
179
180    if (jerry_is_feature_enabled (JERRY_FEATURE_ERROR_MESSAGES)
181        && jerry_get_error_type (error_value) == JERRY_ERROR_SYNTAX)
182    {
183      jerry_char_t *string_end_p = err_str_buf + sz;
184      uint32_t err_line = 0;
185      uint32_t err_col = 0;
186      char *path_str_p = NULL;
187      char *path_str_end_p = NULL;
188
189      /* 1. parse column and line information */
190      for (jerry_char_t *current_p = err_str_buf; current_p < string_end_p; current_p++)
191      {
192        if (*current_p == '[')
193        {
194          current_p++;
195
196          if (*current_p == '<')
197          {
198            break;
199          }
200
201          path_str_p = (char *) current_p;
202          while (current_p < string_end_p && *current_p != ':')
203          {
204            current_p++;
205          }
206
207          path_str_end_p = (char *) current_p++;
208
209          err_line = str_to_uint ((char *) current_p, (char **) &current_p);
210
211          current_p++;
212
213          err_col = str_to_uint ((char *) current_p, NULL);
214          break;
215        }
216      } /* for */
217
218      if (err_line != 0 && err_col != 0)
219      {
220        uint32_t curr_line = 1;
221
222        bool is_printing_context = false;
223        uint32_t pos = 0;
224
225        /* Temporarily modify the error message, so we can use the path. */
226        *path_str_end_p = '\0';
227
228        size_t source_size;
229        const jerry_char_t *source_p = read_file (path_str_p, &source_size);
230
231        /* Revert the error message. */
232        *path_str_end_p = ':';
233
234        /* 2. seek and print */
235        while (source_p[pos] != '\0')
236        {
237          if (source_p[pos] == '\n')
238          {
239            curr_line++;
240          }
241
242          if (err_line < SYNTAX_ERROR_CONTEXT_SIZE
243              || (err_line >= curr_line
244                  && (err_line - curr_line) <= SYNTAX_ERROR_CONTEXT_SIZE))
245          {
246            /* context must be printed */
247            is_printing_context = true;
248          }
249
250          if (curr_line > err_line)
251          {
252            break;
253          }
254
255          if (is_printing_context)
256          {
257            jerry_port_log (JERRY_LOG_LEVEL_ERROR, "%c", source_p[pos]);
258          }
259
260          pos++;
261        }
262
263        jerry_port_log (JERRY_LOG_LEVEL_ERROR, "\n");
264
265        while (--err_col)
266        {
267          jerry_port_log (JERRY_LOG_LEVEL_ERROR, "~");
268        }
269
270        jerry_port_log (JERRY_LOG_LEVEL_ERROR, "^\n");
271      }
272    }
273  }
274
275  jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Script Error: %s\n", err_str_buf);
276  jerry_release_value (err_str_val);
277} /* print_unhandled_exception */
278
279/**
280 * Register a JavaScript function in the global object.
281 */
282static void
283register_js_function (const char *name_p, /**< name of the function */
284                      jerry_external_handler_t handler_p) /**< function callback */
285{
286  jerry_value_t result_val = jerryx_handler_register_global ((const jerry_char_t *) name_p, handler_p);
287
288  if (jerry_value_is_error (result_val))
289  {
290    jerry_port_log (JERRY_LOG_LEVEL_WARNING, "Warning: failed to register '%s' method.", name_p);
291  }
292
293  jerry_release_value (result_val);
294} /* register_js_function */
295
296
297/**
298 * Main program.
299 *
300 * @return 0 if success, error code otherwise
301 */
302#ifdef CONFIG_BUILD_KERNEL
303int main (int argc, FAR char *argv[])
304#else
305int jerry_main (int argc, char *argv[])
306#endif
307{
308  if (argc > JERRY_MAX_COMMAND_LINE_ARGS)
309  {
310    jerry_port_log (JERRY_LOG_LEVEL_ERROR,
311                    "Too many command line arguments. Current maximum is %d\n",
312                    JERRY_MAX_COMMAND_LINE_ARGS);
313
314    return JERRY_STANDALONE_EXIT_CODE_FAIL;
315  }
316
317  const char *file_names[JERRY_MAX_COMMAND_LINE_ARGS];
318  int i;
319  int files_counter = 0;
320  bool start_debug_server = false;
321  uint16_t debug_port = 5001;
322
323  jerry_init_flag_t flags = JERRY_INIT_EMPTY;
324
325  for (i = 1; i < argc; i++)
326  {
327    if (!strcmp ("-h", argv[i]) || !strcmp ("--help", argv[i]))
328    {
329      print_help (argv[0]);
330      return JERRY_STANDALONE_EXIT_CODE_OK;
331    }
332    else if (!strcmp ("--mem-stats", argv[i]))
333    {
334      flags |= JERRY_INIT_MEM_STATS;
335      set_log_level (JERRY_LOG_LEVEL_DEBUG);
336    }
337    else if (!strcmp ("--mem-stats-separate", argv[i]))
338    {
339      flags |= JERRY_INIT_MEM_STATS_SEPARATE;
340      set_log_level (JERRY_LOG_LEVEL_DEBUG);
341    }
342    else if (!strcmp ("--show-opcodes", argv[i]))
343    {
344      flags |= JERRY_INIT_SHOW_OPCODES | JERRY_INIT_SHOW_REGEXP_OPCODES;
345      set_log_level (JERRY_LOG_LEVEL_DEBUG);
346    }
347    else if (!strcmp ("--log-level", argv[i]))
348    {
349      if (++i < argc && strlen (argv[i]) == 1 && argv[i][0] >='0' && argv[i][0] <= '3')
350      {
351        set_log_level (argv[i][0] - '0');
352      }
353      else
354      {
355        jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: wrong format or invalid argument\n");
356        return JERRY_STANDALONE_EXIT_CODE_FAIL;
357      }
358    }
359    else if (!strcmp ("--start-debug-server", argv[i]))
360    {
361      start_debug_server = true;
362    }
363    else if (!strcmp ("--debug-server-port", argv[i]))
364    {
365      if (++i < argc)
366      {
367        debug_port = str_to_uint (argv[i], NULL);
368      }
369      else
370      {
371        jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Error: wrong format or invalid argument\n");
372        return JERRY_STANDALONE_EXIT_CODE_FAIL;
373      }
374    }
375    else
376    {
377      file_names[files_counter++] = argv[i];
378    }
379  }
380
381  jerry_init (flags);
382
383  if (start_debug_server)
384  {
385    jerryx_debugger_after_connect (jerryx_debugger_tcp_create (debug_port)
386                                   && jerryx_debugger_ws_create ());
387  }
388
389  register_js_function ("assert", jerryx_handler_assert);
390  register_js_function ("gc", jerryx_handler_gc);
391  register_js_function ("print", jerryx_handler_print);
392
393  jerry_value_t ret_value = jerry_create_undefined ();
394
395  if (files_counter == 0)
396  {
397    printf ("No input files, running a hello world demo:\n");
398    const jerry_char_t script[] = "var str = 'Hello World'; print(str + ' from JerryScript')";
399
400    ret_value = jerry_parse (NULL, 0, script, sizeof (script) - 1, JERRY_PARSE_NO_OPTS);
401
402    if (!jerry_value_is_error (ret_value))
403    {
404      ret_value = jerry_run (ret_value);
405    }
406  }
407  else
408  {
409    for (i = 0; i < files_counter; i++)
410    {
411      size_t source_size;
412      const jerry_char_t *source_p = read_file (file_names[i], &source_size);
413
414      if (source_p == NULL)
415      {
416        jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Source file load error\n");
417        return JERRY_STANDALONE_EXIT_CODE_FAIL;
418      }
419
420      ret_value = jerry_parse ((jerry_char_t *) file_names[i],
421                               strlen (file_names[i]),
422                               source_p,
423                               source_size,
424                               JERRY_PARSE_NO_OPTS);
425      free ((void*) source_p);
426
427      if (!jerry_value_is_error (ret_value))
428      {
429        jerry_value_t func_val = ret_value;
430        ret_value = jerry_run (func_val);
431        jerry_release_value (func_val);
432      }
433
434      if (jerry_value_is_error (ret_value))
435      {
436        print_unhandled_exception (ret_value);
437        break;
438      }
439
440      jerry_release_value (ret_value);
441      ret_value = jerry_create_undefined ();
442    }
443  }
444
445  int ret_code = JERRY_STANDALONE_EXIT_CODE_OK;
446
447  if (jerry_value_is_error (ret_value))
448  {
449    ret_code = JERRY_STANDALONE_EXIT_CODE_FAIL;
450  }
451
452  jerry_release_value (ret_value);
453
454  ret_value = jerry_run_all_enqueued_jobs ();
455
456  if (jerry_value_is_error (ret_value))
457  {
458    ret_code = JERRY_STANDALONE_EXIT_CODE_FAIL;
459  }
460
461  jerry_release_value (ret_value);
462  jerry_cleanup ();
463
464  return ret_code;
465} /* main */
466