quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

repl.js (49852B)


      1 /*
      2  * QuickJS Read Eval Print Loop
      3  *
      4  * Copyright (c) 2017-2020 Fabrice Bellard
      5  * Copyright (c) 2017-2020 Charlie Gordon
      6  *
      7  * Permission is hereby granted, free of charge, to any person obtaining a copy
      8  * of this software and associated documentation files (the "Software"), to deal
      9  * in the Software without restriction, including without limitation the rights
     10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     11  * copies of the Software, and to permit persons to whom the Software is
     12  * furnished to do so, subject to the following conditions:
     13  *
     14  * The above copyright notice and this permission notice shall be included in
     15  * all copies or substantial portions of the Software.
     16  *
     17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
     20  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
     23  * THE SOFTWARE.
     24  */
     25 "use strip";
     26 
     27 import * as std from "std";
     28 import * as os from "os";
     29 
     30 (function(g) {
     31     /* add 'os' and 'std' bindings */
     32     g.os = os;
     33     g.std = std;
     34 
     35     /* close global objects */
     36     var Object = g.Object;
     37     var String = g.String;
     38     var Array = g.Array;
     39     var Date = g.Date;
     40     var Math = g.Math;
     41     var isFinite = g.isFinite;
     42     var parseFloat = g.parseFloat;
     43 
     44     /* XXX: use preprocessor ? */
     45     var config_numcalc = (typeof os.open === "undefined");
     46     var has_jscalc = (typeof Fraction === "function");
     47     var has_bignum = (typeof BigFloat === "function");
     48 
     49     var colors = {
     50         none:    "\x1b[0m",
     51         black:   "\x1b[30m",
     52         red:     "\x1b[31m",
     53         green:   "\x1b[32m",
     54         yellow:  "\x1b[33m",
     55         blue:    "\x1b[34m",
     56         magenta: "\x1b[35m",
     57         cyan:    "\x1b[36m",
     58         white:   "\x1b[37m",
     59         gray:    "\x1b[30;1m",
     60         grey:    "\x1b[30;1m",
     61         bright_red:     "\x1b[31;1m",
     62         bright_green:   "\x1b[32;1m",
     63         bright_yellow:  "\x1b[33;1m",
     64         bright_blue:    "\x1b[34;1m",
     65         bright_magenta: "\x1b[35;1m",
     66         bright_cyan:    "\x1b[36;1m",
     67         bright_white:   "\x1b[37;1m",
     68     };
     69 
     70     var styles = {
     71         'default':    'bright_green',
     72         'comment':    'white',
     73         'string':     'bright_cyan',
     74         'regex':      'cyan',
     75         'number':     'green',
     76         'keyword':    'bright_white',
     77         'function':   'bright_yellow',
     78         'type':       'bright_magenta',
     79         'identifier': 'bright_green',
     80         'error':      'red',
     81         'result':     'bright_white',
     82         'error_msg':  'bright_red',
     83     };
     84 
     85     var history = [];
     86     var clip_board = "";
     87     var prec;
     88     var expBits;
     89     var log2_10;
     90 
     91     var pstate = "";
     92     var prompt = "";
     93     var plen = 0;
     94     var ps1 = "qjs > ";
     95     var ps2 = "  ... ";
     96     var utf8 = true;
     97     var show_time = false;
     98     var show_colors = true;
     99     var eval_start_time;
    100     var eval_time = 0;
    101 
    102     var mexpr = "";
    103     var level = 0;
    104     var cmd = "";
    105     var cursor_pos = 0;
    106     var last_cmd = "";
    107     var last_cursor_pos = 0;
    108     var history_index;
    109     var this_fun, last_fun;
    110     var quote_flag = false;
    111 
    112     var utf8_state = 0;
    113     var utf8_val = 0;
    114 
    115     var term_fd;
    116     var term_read_buf;
    117     var term_width;
    118     /* current X position of the cursor in the terminal */
    119     var term_cursor_x = 0;
    120 
    121     function termInit() {
    122         var tab;
    123         term_fd = std.in.fileno();
    124 
    125         /* get the terminal size */
    126         term_width = 80;
    127         if (os.isatty(term_fd)) {
    128             if (os.ttyGetWinSize) {
    129                 tab = os.ttyGetWinSize(term_fd);
    130                 if (tab)
    131                     term_width = tab[0];
    132             }
    133             if (os.ttySetRaw) {
    134                 /* set the TTY to raw mode */
    135                 os.ttySetRaw(term_fd);
    136             }
    137         }
    138 
    139         /* install a Ctrl-C signal handler */
    140         os.signal(os.SIGINT, sigint_handler);
    141 
    142         /* install a handler to read stdin */
    143         term_read_buf = new Uint8Array(64);
    144         os.setReadHandler(term_fd, term_read_handler);
    145     }
    146 
    147     function sigint_handler() {
    148         /* send Ctrl-C to readline */
    149         handle_byte(3);
    150     }
    151 
    152     function term_read_handler() {
    153         var l, i;
    154         l = os.read(term_fd, term_read_buf.buffer, 0, term_read_buf.length);
    155         for(i = 0; i < l; i++)
    156             handle_byte(term_read_buf[i]);
    157     }
    158 
    159     function handle_byte(c) {
    160         if (!utf8) {
    161             handle_char(c);
    162         } else if (utf8_state !== 0 && (c >= 0x80 && c < 0xc0)) {
    163             utf8_val = (utf8_val << 6) | (c & 0x3F);
    164             utf8_state--;
    165             if (utf8_state === 0) {
    166                 handle_char(utf8_val);
    167             }
    168         } else if (c >= 0xc0 && c < 0xf8) {
    169             utf8_state = 1 + (c >= 0xe0) + (c >= 0xf0);
    170             utf8_val = c & ((1 << (6 - utf8_state)) - 1);
    171         } else {
    172             utf8_state = 0;
    173             handle_char(c);
    174         }
    175     }
    176 
    177     function is_alpha(c) {
    178         return typeof c === "string" &&
    179             ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
    180     }
    181 
    182     function is_digit(c) {
    183         return typeof c === "string" && (c >= '0' && c <= '9');
    184     }
    185 
    186     function is_word(c) {
    187         return typeof c === "string" &&
    188             (is_alpha(c) || is_digit(c) || c == '_' || c == '$');
    189     }
    190 
    191     function ucs_length(str) {
    192         var len, c, i, str_len = str.length;
    193         len = 0;
    194         /* we never count the trailing surrogate to have the
    195          following property: ucs_length(str) =
    196          ucs_length(str.substring(0, a)) + ucs_length(str.substring(a,
    197          str.length)) for 0 <= a <= str.length */
    198         for(i = 0; i < str_len; i++) {
    199             c = str.charCodeAt(i);
    200             if (c < 0xdc00 || c >= 0xe000)
    201                 len++;
    202         }
    203         return len;
    204     }
    205 
    206     function is_trailing_surrogate(c)  {
    207         var d;
    208         if (typeof c !== "string")
    209             return false;
    210         d = c.codePointAt(0); /* can be NaN if empty string */
    211         return d >= 0xdc00 && d < 0xe000;
    212     }
    213 
    214     function is_balanced(a, b) {
    215         switch (a + b) {
    216         case "()":
    217         case "[]":
    218         case "{}":
    219             return true;
    220         }
    221         return false;
    222     }
    223 
    224     function print_color_text(str, start, style_names) {
    225         var i, j;
    226         for (j = start; j < str.length;) {
    227             var style = style_names[i = j];
    228             while (++j < str.length && style_names[j] == style)
    229                 continue;
    230             std.puts(colors[styles[style] || 'default']);
    231             std.puts(str.substring(i, j));
    232             std.puts(colors['none']);
    233         }
    234     }
    235 
    236     function print_csi(n, code) {
    237         std.puts("\x1b[" + ((n != 1) ? n : "") + code);
    238     }
    239 
    240     /* XXX: handle double-width characters */
    241     function move_cursor(delta) {
    242         var i, l;
    243         if (delta > 0) {
    244             while (delta != 0) {
    245                 if (term_cursor_x == (term_width - 1)) {
    246                     std.puts("\n"); /* translated to CRLF */
    247                     term_cursor_x = 0;
    248                     delta--;
    249                 } else {
    250                     l = Math.min(term_width - 1 - term_cursor_x, delta);
    251                     print_csi(l, "C"); /* right */
    252                     delta -= l;
    253                     term_cursor_x += l;
    254                 }
    255             }
    256         } else {
    257             delta = -delta;
    258             while (delta != 0) {
    259                 if (term_cursor_x == 0) {
    260                     print_csi(1, "A"); /* up */
    261                     print_csi(term_width - 1, "C"); /* right */
    262                     delta--;
    263                     term_cursor_x = term_width - 1;
    264                 } else {
    265                     l = Math.min(delta, term_cursor_x);
    266                     print_csi(l, "D"); /* left */
    267                     delta -= l;
    268                     term_cursor_x -= l;
    269                 }
    270             }
    271         }
    272     }
    273 
    274     function update() {
    275         var i, cmd_len;
    276         /* cursor_pos is the position in 16 bit characters inside the
    277            UTF-16 string 'cmd' */
    278         if (cmd != last_cmd) {
    279             if (!show_colors && last_cmd.substring(0, last_cursor_pos) == cmd.substring(0, last_cursor_pos)) {
    280                 /* optimize common case */
    281                 std.puts(cmd.substring(last_cursor_pos));
    282             } else {
    283                 /* goto the start of the line */
    284                 move_cursor(-ucs_length(last_cmd.substring(0, last_cursor_pos)));
    285                 if (show_colors) {
    286                     var str = mexpr ? mexpr + '\n' + cmd : cmd;
    287                     var start = str.length - cmd.length;
    288                     var colorstate = colorize_js(str);
    289                     print_color_text(str, start, colorstate[2]);
    290                 } else {
    291                     std.puts(cmd);
    292                 }
    293             }
    294             term_cursor_x = (term_cursor_x + ucs_length(cmd)) % term_width;
    295             if (term_cursor_x == 0) {
    296                 /* show the cursor on the next line */
    297                 std.puts(" \x08");
    298             }
    299             /* remove the trailing characters */
    300             std.puts("\x1b[J");
    301             last_cmd = cmd;
    302             last_cursor_pos = cmd.length;
    303         }
    304         if (cursor_pos > last_cursor_pos) {
    305             move_cursor(ucs_length(cmd.substring(last_cursor_pos, cursor_pos)));
    306         } else if (cursor_pos < last_cursor_pos) {
    307             move_cursor(-ucs_length(cmd.substring(cursor_pos, last_cursor_pos)));
    308         }
    309         last_cursor_pos = cursor_pos;
    310         std.out.flush();
    311     }
    312 
    313     /* editing commands */
    314     function insert(str) {
    315         if (str) {
    316             cmd = cmd.substring(0, cursor_pos) + str + cmd.substring(cursor_pos);
    317             cursor_pos += str.length;
    318         }
    319     }
    320 
    321     function quoted_insert() {
    322         quote_flag = true;
    323     }
    324 
    325     function abort() {
    326         cmd = "";
    327         cursor_pos = 0;
    328         return -2;
    329     }
    330 
    331     function alert() {
    332     }
    333 
    334     function beginning_of_line() {
    335         cursor_pos = 0;
    336     }
    337 
    338     function end_of_line() {
    339         cursor_pos = cmd.length;
    340     }
    341 
    342     function forward_char() {
    343         if (cursor_pos < cmd.length) {
    344             cursor_pos++;
    345             while (is_trailing_surrogate(cmd.charAt(cursor_pos)))
    346                 cursor_pos++;
    347         }
    348     }
    349 
    350     function backward_char() {
    351         if (cursor_pos > 0) {
    352             cursor_pos--;
    353             while (is_trailing_surrogate(cmd.charAt(cursor_pos)))
    354                 cursor_pos--;
    355         }
    356     }
    357 
    358     function skip_word_forward(pos) {
    359         while (pos < cmd.length && !is_word(cmd.charAt(pos)))
    360             pos++;
    361         while (pos < cmd.length && is_word(cmd.charAt(pos)))
    362             pos++;
    363         return pos;
    364     }
    365 
    366     function skip_word_backward(pos) {
    367         while (pos > 0 && !is_word(cmd.charAt(pos - 1)))
    368             pos--;
    369         while (pos > 0 && is_word(cmd.charAt(pos - 1)))
    370             pos--;
    371         return pos;
    372     }
    373 
    374     function forward_word() {
    375         cursor_pos = skip_word_forward(cursor_pos);
    376     }
    377 
    378     function backward_word() {
    379         cursor_pos = skip_word_backward(cursor_pos);
    380     }
    381 
    382     function accept_line() {
    383         std.puts("\n");
    384         history_add(cmd);
    385         return -1;
    386     }
    387 
    388     function history_add(str) {
    389         if (str) {
    390             history.push(str);
    391         }
    392         history_index = history.length;
    393     }
    394 
    395     function previous_history() {
    396         if (history_index > 0) {
    397             if (history_index == history.length) {
    398                 history.push(cmd);
    399             }
    400             history_index--;
    401             cmd = history[history_index];
    402             cursor_pos = cmd.length;
    403         }
    404     }
    405 
    406     function next_history() {
    407         if (history_index < history.length - 1) {
    408             history_index++;
    409             cmd = history[history_index];
    410             cursor_pos = cmd.length;
    411         }
    412     }
    413 
    414     function history_search(dir) {
    415         var pos = cursor_pos;
    416         for (var i = 1; i <= history.length; i++) {
    417             var index = (history.length + i * dir + history_index) % history.length;
    418             if (history[index].substring(0, pos) == cmd.substring(0, pos)) {
    419                 history_index = index;
    420                 cmd = history[index];
    421                 return;
    422             }
    423         }
    424     }
    425 
    426     function history_search_backward() {
    427         return history_search(-1);
    428     }
    429 
    430     function history_search_forward() {
    431         return history_search(1);
    432     }
    433 
    434     function delete_char_dir(dir) {
    435         var start, end;
    436 
    437         start = cursor_pos;
    438         if (dir < 0) {
    439             start--;
    440             while (is_trailing_surrogate(cmd.charAt(start)))
    441                 start--;
    442         }
    443         end = start + 1;
    444         while (is_trailing_surrogate(cmd.charAt(end)))
    445             end++;
    446 
    447         if (start >= 0 && start < cmd.length) {
    448             if (last_fun === kill_region) {
    449                 kill_region(start, end, dir);
    450             } else {
    451                 cmd = cmd.substring(0, start) + cmd.substring(end);
    452                 cursor_pos = start;
    453             }
    454         }
    455     }
    456 
    457     function delete_char() {
    458         delete_char_dir(1);
    459     }
    460 
    461     function control_d() {
    462         if (cmd.length == 0) {
    463             std.puts("\n");
    464             return -3; /* exit read eval print loop */
    465         } else {
    466             delete_char_dir(1);
    467         }
    468     }
    469 
    470     function backward_delete_char() {
    471         delete_char_dir(-1);
    472     }
    473 
    474     function transpose_chars() {
    475         var pos = cursor_pos;
    476         if (cmd.length > 1 && pos > 0) {
    477             if (pos == cmd.length)
    478                 pos--;
    479             cmd = cmd.substring(0, pos - 1) + cmd.substring(pos, pos + 1) +
    480                 cmd.substring(pos - 1, pos) + cmd.substring(pos + 1);
    481             cursor_pos = pos + 1;
    482         }
    483     }
    484 
    485     function transpose_words() {
    486         var p1 = skip_word_backward(cursor_pos);
    487         var p2 = skip_word_forward(p1);
    488         var p4 = skip_word_forward(cursor_pos);
    489         var p3 = skip_word_backward(p4);
    490 
    491         if (p1 < p2 && p2 <= cursor_pos && cursor_pos <= p3 && p3 < p4) {
    492             cmd = cmd.substring(0, p1) + cmd.substring(p3, p4) +
    493             cmd.substring(p2, p3) + cmd.substring(p1, p2);
    494             cursor_pos = p4;
    495         }
    496     }
    497 
    498     function upcase_word() {
    499         var end = skip_word_forward(cursor_pos);
    500         cmd = cmd.substring(0, cursor_pos) +
    501             cmd.substring(cursor_pos, end).toUpperCase() +
    502             cmd.substring(end);
    503     }
    504 
    505     function downcase_word() {
    506         var end = skip_word_forward(cursor_pos);
    507         cmd = cmd.substring(0, cursor_pos) +
    508             cmd.substring(cursor_pos, end).toLowerCase() +
    509             cmd.substring(end);
    510     }
    511 
    512     function kill_region(start, end, dir) {
    513         var s = cmd.substring(start, end);
    514         if (last_fun !== kill_region)
    515             clip_board = s;
    516         else if (dir < 0)
    517             clip_board = s + clip_board;
    518         else
    519             clip_board = clip_board + s;
    520 
    521         cmd = cmd.substring(0, start) + cmd.substring(end);
    522         if (cursor_pos > end)
    523             cursor_pos -= end - start;
    524         else if (cursor_pos > start)
    525             cursor_pos = start;
    526         this_fun = kill_region;
    527     }
    528 
    529     function kill_line() {
    530         kill_region(cursor_pos, cmd.length, 1);
    531     }
    532 
    533     function backward_kill_line() {
    534         kill_region(0, cursor_pos, -1);
    535     }
    536 
    537     function kill_word() {
    538         kill_region(cursor_pos, skip_word_forward(cursor_pos), 1);
    539     }
    540 
    541     function backward_kill_word() {
    542         kill_region(skip_word_backward(cursor_pos), cursor_pos, -1);
    543     }
    544 
    545     function yank() {
    546         insert(clip_board);
    547     }
    548 
    549     function control_c() {
    550         if (last_fun === control_c) {
    551             std.puts("\n");
    552             std.exit(0);
    553         } else {
    554             std.puts("\n(Press Ctrl-C again to quit)\n");
    555             reset();
    556             readline_print_prompt();
    557         }
    558     }
    559 
    560     function reset() {
    561         cmd = "";
    562         cursor_pos = 0;
    563     }
    564 
    565     function get_context_word(line, pos) {
    566         var s = "";
    567         while (pos > 0 && is_word(line[pos - 1])) {
    568             pos--;
    569             s = line[pos] + s;
    570         }
    571         return s;
    572     }
    573     function get_context_object(line, pos) {
    574         var obj, base, c;
    575         if (pos <= 0 || " ~!%^&*(-+={[|:;,<>?/".indexOf(line[pos - 1]) >= 0)
    576             return g;
    577         if (pos >= 2 && line[pos - 1] === ".") {
    578             pos--;
    579             obj = {};
    580             switch (c = line[pos - 1]) {
    581             case '\'':
    582             case '\"':
    583                 return "a";
    584             case ']':
    585                 return [];
    586             case '}':
    587                 return {};
    588             case '/':
    589                 return / /;
    590             default:
    591                 if (is_word(c)) {
    592                     base = get_context_word(line, pos);
    593                     if (["true", "false", "null", "this"].includes(base) || !isNaN(+base))
    594                         return eval(base);
    595                     // Check if `base` is a set of regexp flags
    596                     if (pos - base.length >= 3 && line[pos - base.length - 1] === '/')
    597                         return new RegExp('', base);
    598                     obj = get_context_object(line, pos - base.length);
    599                     if (obj === null || obj === void 0)
    600                         return obj;
    601                     if (obj === g && obj[base] === void 0)
    602                         return eval(base);
    603                     else
    604                         return obj[base];
    605                 }
    606                 return {};
    607             }
    608         }
    609         return void 0;
    610     }
    611 
    612     function get_completions(line, pos) {
    613         var s, obj, ctx_obj, r, i, j, paren;
    614 
    615         s = get_context_word(line, pos);
    616         ctx_obj = get_context_object(line, pos - s.length);
    617         r = [];
    618         /* enumerate properties from object and its prototype chain,
    619            add non-numeric regular properties with s as e prefix
    620          */
    621         for (i = 0, obj = ctx_obj; i < 10 && obj !== null && obj !== void 0; i++) {
    622             var props = Object.getOwnPropertyNames(obj);
    623             /* add non-numeric regular properties */
    624             for (j = 0; j < props.length; j++) {
    625                 var prop = props[j];
    626                 if (typeof prop == "string" && ""+(+prop) != prop && prop.startsWith(s))
    627                     r.push(prop);
    628             }
    629             obj = Object.getPrototypeOf(obj);
    630         }
    631         if (r.length > 1) {
    632             /* sort list with internal names last and remove duplicates */
    633             function symcmp(a, b) {
    634                 if (a[0] != b[0]) {
    635                     if (a[0] == '_')
    636                         return 1;
    637                     if (b[0] == '_')
    638                         return -1;
    639                 }
    640                 if (a < b)
    641                     return -1;
    642                 if (a > b)
    643                     return +1;
    644                 return 0;
    645             }
    646             r.sort(symcmp);
    647             for(i = j = 1; i < r.length; i++) {
    648                 if (r[i] != r[i - 1])
    649                     r[j++] = r[i];
    650             }
    651             r.length = j;
    652         }
    653         /* 'tab' = list of completions, 'pos' = cursor position inside
    654            the completions */
    655         return { tab: r, pos: s.length, ctx: ctx_obj };
    656     }
    657 
    658     function completion() {
    659         var tab, res, s, i, j, len, t, max_width, col, n_cols, row, n_rows;
    660         res = get_completions(cmd, cursor_pos);
    661         tab = res.tab;
    662         if (tab.length === 0)
    663             return;
    664         s = tab[0];
    665         len = s.length;
    666         /* add the chars which are identical in all the completions */
    667         for(i = 1; i < tab.length; i++) {
    668             t = tab[i];
    669             for(j = 0; j < len; j++) {
    670                 if (t[j] !== s[j]) {
    671                     len = j;
    672                     break;
    673                 }
    674             }
    675         }
    676         for(i = res.pos; i < len; i++) {
    677             insert(s[i]);
    678         }
    679         if (last_fun === completion && tab.length == 1) {
    680             /* append parentheses to function names */
    681             var m = res.ctx[tab[0]];
    682             if (typeof m == "function") {
    683                 insert('(');
    684                 if (m.length == 0)
    685                     insert(')');
    686             } else if (typeof m == "object") {
    687                 insert('.');
    688             }
    689         }
    690         /* show the possible completions */
    691         if (last_fun === completion && tab.length >= 2) {
    692             max_width = 0;
    693             for(i = 0; i < tab.length; i++)
    694                 max_width = Math.max(max_width, tab[i].length);
    695             max_width += 2;
    696             n_cols = Math.max(1, Math.floor((term_width + 1) / max_width));
    697             n_rows = Math.ceil(tab.length / n_cols);
    698             std.puts("\n");
    699             /* display the sorted list column-wise */
    700             for (row = 0; row < n_rows; row++) {
    701                 for (col = 0; col < n_cols; col++) {
    702                     i = col * n_rows + row;
    703                     if (i >= tab.length)
    704                         break;
    705                     s = tab[i];
    706                     if (col != n_cols - 1)
    707                         s = s.padEnd(max_width);
    708                     std.puts(s);
    709                 }
    710                 std.puts("\n");
    711             }
    712             /* show a new prompt */
    713             readline_print_prompt();
    714         }
    715     }
    716 
    717     var commands = {        /* command table */
    718         "\x01":     beginning_of_line,      /* ^A - bol */
    719         "\x02":     backward_char,          /* ^B - backward-char */
    720         "\x03":     control_c,              /* ^C - abort */
    721         "\x04":     control_d,              /* ^D - delete-char or exit */
    722         "\x05":     end_of_line,            /* ^E - eol */
    723         "\x06":     forward_char,           /* ^F - forward-char */
    724         "\x07":     abort,                  /* ^G - bell */
    725         "\x08":     backward_delete_char,   /* ^H - backspace */
    726         "\x09":     completion,             /* ^I - history-search-backward */
    727         "\x0a":     accept_line,            /* ^J - newline */
    728         "\x0b":     kill_line,              /* ^K - delete to end of line */
    729         "\x0d":     accept_line,            /* ^M - enter */
    730         "\x0e":     next_history,           /* ^N - down */
    731         "\x10":     previous_history,       /* ^P - up */
    732         "\x11":     quoted_insert,          /* ^Q - quoted-insert */
    733         "\x12":     alert,                  /* ^R - reverse-search */
    734         "\x13":     alert,                  /* ^S - search */
    735         "\x14":     transpose_chars,        /* ^T - transpose */
    736         "\x18":     reset,                  /* ^X - cancel */
    737         "\x19":     yank,                   /* ^Y - yank */
    738         "\x1bOA":   previous_history,       /* ^[OA - up */
    739         "\x1bOB":   next_history,           /* ^[OB - down */
    740         "\x1bOC":   forward_char,           /* ^[OC - right */
    741         "\x1bOD":   backward_char,          /* ^[OD - left */
    742         "\x1bOF":   forward_word,           /* ^[OF - ctrl-right */
    743         "\x1bOH":   backward_word,          /* ^[OH - ctrl-left */
    744         "\x1b[1;5C": forward_word,          /* ^[[1;5C - ctrl-right */
    745         "\x1b[1;5D": backward_word,         /* ^[[1;5D - ctrl-left */
    746         "\x1b[1~":  beginning_of_line,      /* ^[[1~ - bol */
    747         "\x1b[3~":  delete_char,            /* ^[[3~ - delete */
    748         "\x1b[4~":  end_of_line,            /* ^[[4~ - eol */
    749         "\x1b[5~":  history_search_backward,/* ^[[5~ - page up */
    750         "\x1b[6~":  history_search_forward, /* ^[[5~ - page down */
    751         "\x1b[A":   previous_history,       /* ^[[A - up */
    752         "\x1b[B":   next_history,           /* ^[[B - down */
    753         "\x1b[C":   forward_char,           /* ^[[C - right */
    754         "\x1b[D":   backward_char,          /* ^[[D - left */
    755         "\x1b[F":   end_of_line,            /* ^[[F - end */
    756         "\x1b[H":   beginning_of_line,      /* ^[[H - home */
    757         "\x1b\x7f": backward_kill_word,     /* M-C-? - backward_kill_word */
    758         "\x1bb":    backward_word,          /* M-b - backward_word */
    759         "\x1bd":    kill_word,              /* M-d - kill_word */
    760         "\x1bf":    forward_word,           /* M-f - backward_word */
    761         "\x1bk":    backward_kill_line,     /* M-k - backward_kill_line */
    762         "\x1bl":    downcase_word,          /* M-l - downcase_word */
    763         "\x1bt":    transpose_words,        /* M-t - transpose_words */
    764         "\x1bu":    upcase_word,            /* M-u - upcase_word */
    765         "\x7f":     backward_delete_char,   /* ^? - delete */
    766     };
    767 
    768     function dupstr(str, count) {
    769         var res = "";
    770         while (count-- > 0)
    771             res += str;
    772         return res;
    773     }
    774 
    775     var readline_keys;
    776     var readline_state;
    777     var readline_cb;
    778 
    779     function readline_print_prompt()
    780     {
    781         std.puts(prompt);
    782         term_cursor_x = ucs_length(prompt) % term_width;
    783         last_cmd = "";
    784         last_cursor_pos = 0;
    785     }
    786 
    787     function readline_start(defstr, cb) {
    788         cmd = defstr || "";
    789         cursor_pos = cmd.length;
    790         history_index = history.length;
    791         readline_cb = cb;
    792 
    793         prompt = pstate;
    794 
    795         if (mexpr) {
    796             prompt += dupstr(" ", plen - prompt.length);
    797             prompt += ps2;
    798         } else {
    799             if (show_time) {
    800                 var t = eval_time / 1000;
    801                 prompt += t.toFixed(6) + " ";
    802             }
    803             plen = prompt.length;
    804             prompt += ps1;
    805         }
    806         readline_print_prompt();
    807         update();
    808         readline_state = 0;
    809     }
    810 
    811     function handle_char(c1) {
    812         var c;
    813         c = String.fromCodePoint(c1);
    814         switch(readline_state) {
    815         case 0:
    816             if (c == '\x1b') {  /* '^[' - ESC */
    817                 readline_keys = c;
    818                 readline_state = 1;
    819             } else {
    820                 handle_key(c);
    821             }
    822             break;
    823         case 1: /* '^[ */
    824             readline_keys += c;
    825             if (c == '[') {
    826                 readline_state = 2;
    827             } else if (c == 'O') {
    828                 readline_state = 3;
    829             } else {
    830                 handle_key(readline_keys);
    831                 readline_state = 0;
    832             }
    833             break;
    834         case 2: /* '^[[' - CSI */
    835             readline_keys += c;
    836             if (!(c == ';' || (c >= '0' && c <= '9'))) {
    837                 handle_key(readline_keys);
    838                 readline_state = 0;
    839             }
    840             break;
    841         case 3: /* '^[O' - ESC2 */
    842             readline_keys += c;
    843             handle_key(readline_keys);
    844             readline_state = 0;
    845             break;
    846         }
    847     }
    848 
    849     function handle_key(keys) {
    850         var fun;
    851 
    852         if (quote_flag) {
    853             if (ucs_length(keys) === 1)
    854                 insert(keys);
    855             quote_flag = false;
    856         } else if (fun = commands[keys]) {
    857             this_fun = fun;
    858             switch (fun(keys)) {
    859             case -1:
    860                 readline_cb(cmd);
    861                 return;
    862             case -2:
    863                 readline_cb(null);
    864                 return;
    865             case -3:
    866                 /* uninstall a Ctrl-C signal handler */
    867                 os.signal(os.SIGINT, null);
    868                 /* uninstall the stdin read handler */
    869                 os.setReadHandler(term_fd, null);
    870                 return;
    871             }
    872             last_fun = this_fun;
    873         } else if (ucs_length(keys) === 1 && keys >= ' ') {
    874             insert(keys);
    875             last_fun = insert;
    876         } else {
    877             alert(); /* beep! */
    878         }
    879 
    880         cursor_pos = (cursor_pos < 0) ? 0 :
    881             (cursor_pos > cmd.length) ? cmd.length : cursor_pos;
    882         update();
    883     }
    884 
    885     var hex_mode = false;
    886     var eval_mode = "std";
    887 
    888     function number_to_string(a, radix) {
    889         var s;
    890         if (!isFinite(a)) {
    891             /* NaN, Infinite */
    892             return a.toString();
    893         } else {
    894             if (a == 0) {
    895                 if (1 / a < 0)
    896                     s = "-0";
    897                 else
    898                     s = "0";
    899             } else {
    900                 if (radix == 16 && a === Math.floor(a)) {
    901                     var s;
    902                     if (a < 0) {
    903                         a = -a;
    904                         s = "-";
    905                     } else {
    906                         s = "";
    907                     }
    908                     s += "0x" + a.toString(16);
    909                 } else {
    910                     s = a.toString();
    911                 }
    912             }
    913             return s;
    914         }
    915     }
    916 
    917     function bigfloat_to_string(a, radix) {
    918         var s;
    919         if (!BigFloat.isFinite(a)) {
    920             /* NaN, Infinite */
    921             if (eval_mode !== "math") {
    922                 return "BigFloat(" + a.toString() + ")";
    923             } else {
    924                 return a.toString();
    925             }
    926         } else {
    927             if (a == 0) {
    928                 if (1 / a < 0)
    929                     s = "-0";
    930                 else
    931                     s = "0";
    932             } else {
    933                 if (radix == 16) {
    934                     var s;
    935                     if (a < 0) {
    936                         a = -a;
    937                         s = "-";
    938                     } else {
    939                         s = "";
    940                     }
    941                     s += "0x" + a.toString(16);
    942                 } else {
    943                     s = a.toString();
    944                 }
    945             }
    946             if (typeof a === "bigfloat" && eval_mode !== "math") {
    947                 s += "l";
    948             } else if (eval_mode !== "std" && s.indexOf(".") < 0 &&
    949                 ((radix == 16 && s.indexOf("p") < 0) ||
    950                  (radix == 10 && s.indexOf("e") < 0))) {
    951                 /* add a decimal point so that the floating point type
    952                    is visible */
    953                 s += ".0";
    954             }
    955             return s;
    956         }
    957     }
    958 
    959     function bigint_to_string(a, radix) {
    960         var s;
    961         if (radix == 16) {
    962             var s;
    963             if (a < 0) {
    964                 a = -a;
    965                 s = "-";
    966             } else {
    967                 s = "";
    968             }
    969             s += "0x" + a.toString(16);
    970         } else {
    971             s = a.toString();
    972         }
    973         if (eval_mode === "std")
    974             s += "n";
    975         return s;
    976     }
    977 
    978     function print(a) {
    979         var stack = [];
    980 
    981         function print_rec(a) {
    982             var n, i, keys, key, type, s;
    983 
    984             type = typeof(a);
    985             if (type === "object") {
    986                 if (a === null) {
    987                     std.puts(a);
    988                 } else if (stack.indexOf(a) >= 0) {
    989                     std.puts("[circular]");
    990                 } else if (a instanceof Date) {
    991                     std.puts("Date " + a.toGMTString().__quote());
    992                 } else if (has_jscalc && (a instanceof Fraction ||
    993                                         a instanceof Complex ||
    994                                         a instanceof Mod ||
    995                                         a instanceof Polynomial ||
    996                                         a instanceof PolyMod ||
    997                                         a instanceof RationalFunction ||
    998                                         a instanceof Series)) {
    999                     std.puts(a.toString());
   1000                 } else {
   1001                     stack.push(a);
   1002                     if (Array.isArray(a)) {
   1003                         n = a.length;
   1004                         std.puts("[ ");
   1005                         for(i = 0; i < n; i++) {
   1006                             if (i !== 0)
   1007                                 std.puts(", ");
   1008                             if (i in a) {
   1009                                 print_rec(a[i]);
   1010                             } else {
   1011                                 std.puts("<empty>");
   1012                             }
   1013                             if (i > 20) {
   1014                                 std.puts("...");
   1015                                 break;
   1016                             }
   1017                         }
   1018                         std.puts(" ]");
   1019                     } else if (Object.__getClass(a) === "RegExp") {
   1020                         std.puts(a.toString());
   1021                     } else {
   1022                         keys = Object.keys(a);
   1023                         n = keys.length;
   1024                         std.puts("{ ");
   1025                         for(i = 0; i < n; i++) {
   1026                             if (i !== 0)
   1027                                 std.puts(", ");
   1028                             key = keys[i];
   1029                             std.puts(key, ": ");
   1030                             print_rec(a[key]);
   1031                         }
   1032                         std.puts(" }");
   1033                     }
   1034                     stack.pop(a);
   1035                 }
   1036             } else if (type === "string") {
   1037                 s = a.__quote();
   1038                 if (s.length > 79)
   1039                     s = s.substring(0, 75) + "...\"";
   1040                 std.puts(s);
   1041             } else if (type === "number") {
   1042                 std.puts(number_to_string(a, hex_mode ? 16 : 10));
   1043             } else if (type === "bigint") {
   1044                 std.puts(bigint_to_string(a, hex_mode ? 16 : 10));
   1045             } else if (type === "bigfloat") {
   1046                 std.puts(bigfloat_to_string(a, hex_mode ? 16 : 10));
   1047             } else if (type === "bigdecimal") {
   1048                 std.puts(a.toString() + "m");
   1049             } else if (type === "symbol") {
   1050                 std.puts(String(a));
   1051             } else if (type === "function") {
   1052                 std.puts("function " + a.name + "()");
   1053             } else {
   1054                 std.puts(a);
   1055             }
   1056         }
   1057         print_rec(a);
   1058     }
   1059 
   1060     function extract_directive(a) {
   1061         var pos;
   1062         if (a[0] !== '\\')
   1063             return "";
   1064         for (pos = 1; pos < a.length; pos++) {
   1065             if (!is_alpha(a[pos]))
   1066                 break;
   1067         }
   1068         return a.substring(1, pos);
   1069     }
   1070 
   1071     /* return true if the string after cmd can be evaluted as JS */
   1072     function handle_directive(cmd, expr) {
   1073         var param, prec1, expBits1;
   1074 
   1075         if (cmd === "h" || cmd === "?" || cmd == "help") {
   1076             help();
   1077         } else if (cmd === "load") {
   1078             var filename = expr.substring(cmd.length + 1).trim();
   1079             if (filename.lastIndexOf(".") <= filename.lastIndexOf("/"))
   1080                 filename += ".js";
   1081             std.loadScript(filename);
   1082             return false;
   1083         } else if (cmd === "x") {
   1084             hex_mode = true;
   1085         } else if (cmd === "d") {
   1086             hex_mode = false;
   1087         } else if (cmd === "t") {
   1088             show_time = !show_time;
   1089         } else if (has_bignum && cmd === "p") {
   1090             param = expr.substring(cmd.length + 1).trim().split(" ");
   1091             if (param.length === 1 && param[0] === "") {
   1092                 std.puts("BigFloat precision=" + prec + " bits (~" +
   1093                           Math.floor(prec / log2_10) +
   1094                           " digits), exponent size=" + expBits + " bits\n");
   1095             } else if (param[0] === "f16") {
   1096                 prec = 11;
   1097                 expBits = 5;
   1098             } else if (param[0] === "f32") {
   1099                 prec = 24;
   1100                 expBits = 8;
   1101             } else if (param[0] === "f64") {
   1102                 prec = 53;
   1103                 expBits = 11;
   1104             } else if (param[0] === "f128") {
   1105                 prec = 113;
   1106                 expBits = 15;
   1107             } else {
   1108                 prec1 = parseInt(param[0]);
   1109                 if (param.length >= 2)
   1110                     expBits1 = parseInt(param[1]);
   1111                 else
   1112                     expBits1 = BigFloatEnv.expBitsMax;
   1113                 if (Number.isNaN(prec1) ||
   1114                     prec1 < BigFloatEnv.precMin ||
   1115                     prec1 > BigFloatEnv.precMax) {
   1116                     std.puts("Invalid precision\n");
   1117                     return false;
   1118                 }
   1119                 if (Number.isNaN(expBits1) ||
   1120                     expBits1 < BigFloatEnv.expBitsMin ||
   1121                     expBits1 > BigFloatEnv.expBitsMax) {
   1122                     std.puts("Invalid exponent bits\n");
   1123                     return false;
   1124                 }
   1125                 prec = prec1;
   1126                 expBits = expBits1;
   1127             }
   1128             return false;
   1129         } else if (has_bignum && cmd === "digits") {
   1130             param = expr.substring(cmd.length + 1).trim();
   1131             prec1 = Math.ceil(parseFloat(param) * log2_10);
   1132             if (prec1 < BigFloatEnv.precMin ||
   1133                 prec1 > BigFloatEnv.precMax) {
   1134                 std.puts("Invalid precision\n");
   1135                 return false;
   1136             }
   1137             prec = prec1;
   1138             expBits = BigFloatEnv.expBitsMax;
   1139             return false;
   1140         } else if (has_bignum && cmd === "mode") {
   1141             param = expr.substring(cmd.length + 1).trim();
   1142             if (param === "") {
   1143                 std.puts("Running mode=" + eval_mode + "\n");
   1144             } else if (param === "std" || param === "math") {
   1145                 eval_mode = param;
   1146             } else {
   1147                 std.puts("Invalid mode\n");
   1148             }
   1149             return false;
   1150         } else if (cmd === "clear") {
   1151             std.puts("\x1b[H\x1b[J");
   1152         } else if (cmd === "q") {
   1153             std.exit(0);
   1154         } else if (has_jscalc && cmd === "a") {
   1155             algebraicMode = true;
   1156         } else if (has_jscalc && cmd === "n") {
   1157             algebraicMode = false;
   1158         } else {
   1159             std.puts("Unknown directive: " + cmd + "\n");
   1160             return false;
   1161         }
   1162         return true;
   1163     }
   1164 
   1165     if (config_numcalc) {
   1166         styles = {
   1167             'default':    'black',
   1168             'comment':    'white',
   1169             'string':     'green',
   1170             'regex':      'cyan',
   1171             'number':     'green',
   1172             'keyword':    'blue',
   1173             'function':   'gray',
   1174             'type':       'bright_magenta',
   1175             'identifier': 'yellow',
   1176             'error':      'bright_red',
   1177             'result':     'black',
   1178             'error_msg':  'bright_red',
   1179         };
   1180 
   1181         ps1 = "> ";
   1182 
   1183         /* called by the GUI */
   1184         g.execCmd = function (cmd) {
   1185             switch(cmd) {
   1186             case "dec":
   1187                 hex_mode = false;
   1188                 break;
   1189             case "hex":
   1190                 hex_mode = true;
   1191                 break;
   1192             case "num":
   1193                 algebraicMode = false;
   1194                 break;
   1195             case "alg":
   1196                 algebraicMode = true;
   1197                 break;
   1198             }
   1199         }
   1200     }
   1201 
   1202     function help() {
   1203         function sel(n) {
   1204             return n ? "*": " ";
   1205         }
   1206         std.puts("\\h          this help\n" +
   1207                  "\\x         " + sel(hex_mode) + "hexadecimal number display\n" +
   1208                  "\\d         " + sel(!hex_mode) + "decimal number display\n" +
   1209                  "\\t         " + sel(show_time) + "toggle timing display\n" +
   1210                   "\\clear      clear the terminal\n");
   1211         if (has_jscalc) {
   1212             std.puts("\\a         " + sel(algebraicMode) + "algebraic mode\n" +
   1213                      "\\n         " + sel(!algebraicMode) + "numeric mode\n");
   1214         }
   1215         if (has_bignum) {
   1216             std.puts("\\p [m [e]]  set the BigFloat precision to 'm' bits\n" +
   1217                      "\\digits n   set the BigFloat precision to 'ceil(n*log2(10))' bits\n");
   1218             if (!has_jscalc) {
   1219                 std.puts("\\mode [std|math] change the running mode (current = " + eval_mode + ")\n");
   1220             }
   1221         }
   1222         if (!config_numcalc) {
   1223             std.puts("\\q          exit\n");
   1224         }
   1225     }
   1226 
   1227     function cmd_start() {
   1228         if (!config_numcalc) {
   1229             if (has_jscalc)
   1230                 std.puts('QJSCalc - Type "\\h" for help\n');
   1231             else
   1232                 std.puts('QuickJS - Type "\\h" for help\n');
   1233         }
   1234         if (has_bignum) {
   1235             log2_10 = Math.log(10) / Math.log(2);
   1236             prec = 113;
   1237             expBits = 15;
   1238             if (has_jscalc) {
   1239                 eval_mode = "math";
   1240                 /* XXX: numeric mode should always be the default ? */
   1241                 g.algebraicMode = config_numcalc;
   1242             }
   1243         }
   1244 
   1245         cmd_readline_start();
   1246     }
   1247 
   1248     function cmd_readline_start() {
   1249         readline_start(dupstr("    ", level), readline_handle_cmd);
   1250     }
   1251 
   1252     function readline_handle_cmd(expr) {
   1253         if (!handle_cmd(expr)) {
   1254             cmd_readline_start();
   1255         }
   1256     }
   1257 
   1258     /* return true if async termination */
   1259     function handle_cmd(expr) {
   1260         var colorstate, cmd;
   1261 
   1262         if (expr === null) {
   1263             expr = "";
   1264             return false;
   1265         }
   1266         if (expr === "?") {
   1267             help();
   1268             return false;
   1269         }
   1270         cmd = extract_directive(expr);
   1271         if (cmd.length > 0) {
   1272             if (!handle_directive(cmd, expr)) {
   1273                 return false;
   1274             }
   1275             expr = expr.substring(cmd.length + 1);
   1276         }
   1277         if (expr === "")
   1278             return false;
   1279 
   1280         if (mexpr)
   1281             expr = mexpr + '\n' + expr;
   1282         colorstate = colorize_js(expr);
   1283         pstate = colorstate[0];
   1284         level = colorstate[1];
   1285         if (pstate) {
   1286             mexpr = expr;
   1287             return false;
   1288         }
   1289         mexpr = "";
   1290 
   1291         if (has_bignum) {
   1292             /* XXX: async is not supported in this case */
   1293             BigFloatEnv.setPrec(eval_and_print_start.bind(null, expr, false),
   1294                                 prec, expBits);
   1295         } else {
   1296             eval_and_print_start(expr, true);
   1297         }
   1298         return true;
   1299     }
   1300 
   1301     function eval_and_print_start(expr, is_async) {
   1302         var result;
   1303 
   1304         try {
   1305             if (eval_mode === "math")
   1306                 expr = '"use math"; void 0;' + expr;
   1307             eval_start_time = os.now();
   1308             /* eval as a script */
   1309             result = std.evalScript(expr, { backtrace_barrier: true, async: is_async });
   1310             if (is_async) {
   1311                 /* result is a promise */
   1312                 result.then(print_eval_result, print_eval_error);
   1313             } else {
   1314                 print_eval_result({ value: result });
   1315             }
   1316         } catch (error) {
   1317             print_eval_error(error);
   1318         }
   1319     }
   1320 
   1321     function print_eval_result(result) {
   1322         result = result.value;
   1323         eval_time = os.now() - eval_start_time;
   1324         std.puts(colors[styles.result]);
   1325         print(result);
   1326         std.puts("\n");
   1327         std.puts(colors.none);
   1328         /* set the last result */
   1329         g._ = result;
   1330 
   1331         handle_cmd_end();
   1332     }
   1333 
   1334     function print_eval_error(error) {
   1335         std.puts(colors[styles.error_msg]);
   1336         if (error instanceof Error) {
   1337             console.log(error);
   1338             if (error.stack) {
   1339                 std.puts(error.stack);
   1340             }
   1341         } else {
   1342             std.puts("Throw: ");
   1343             console.log(error);
   1344         }
   1345         std.puts(colors.none);
   1346 
   1347         handle_cmd_end();
   1348     }
   1349 
   1350     function handle_cmd_end() {
   1351         level = 0;
   1352         /* run the garbage collector after each command */
   1353         std.gc();
   1354         cmd_readline_start();
   1355     }
   1356 
   1357     function colorize_js(str) {
   1358         var i, c, start, n = str.length;
   1359         var style, state = "", level = 0;
   1360         var primary, can_regex = 1;
   1361         var r = [];
   1362 
   1363         function push_state(c) { state += c; }
   1364         function last_state(c) { return state.substring(state.length - 1); }
   1365         function pop_state(c) {
   1366             var c = last_state();
   1367             state = state.substring(0, state.length - 1);
   1368             return c;
   1369         }
   1370 
   1371         function parse_block_comment() {
   1372             style = 'comment';
   1373             push_state('/');
   1374             for (i++; i < n - 1; i++) {
   1375                 if (str[i] == '*' && str[i + 1] == '/') {
   1376                     i += 2;
   1377                     pop_state('/');
   1378                     break;
   1379                 }
   1380             }
   1381         }
   1382 
   1383         function parse_line_comment() {
   1384             style = 'comment';
   1385             for (i++; i < n; i++) {
   1386                 if (str[i] == '\n') {
   1387                     break;
   1388                 }
   1389             }
   1390         }
   1391 
   1392         function parse_string(delim) {
   1393             style = 'string';
   1394             push_state(delim);
   1395             while (i < n) {
   1396                 c = str[i++];
   1397                 if (c == '\n') {
   1398                     style = 'error';
   1399                     continue;
   1400                 }
   1401                 if (c == '\\') {
   1402                     if (i >= n)
   1403                         break;
   1404                     i++;
   1405                 } else
   1406                 if (c == delim) {
   1407                     pop_state();
   1408                     break;
   1409                 }
   1410             }
   1411         }
   1412 
   1413         function parse_regex() {
   1414             style = 'regex';
   1415             push_state('/');
   1416             while (i < n) {
   1417                 c = str[i++];
   1418                 if (c == '\n') {
   1419                     style = 'error';
   1420                     continue;
   1421                 }
   1422                 if (c == '\\') {
   1423                     if (i < n) {
   1424                         i++;
   1425                     }
   1426                     continue;
   1427                 }
   1428                 if (last_state() == '[') {
   1429                     if (c == ']') {
   1430                         pop_state()
   1431                     }
   1432                     // ECMA 5: ignore '/' inside char classes
   1433                     continue;
   1434                 }
   1435                 if (c == '[') {
   1436                     push_state('[');
   1437                     if (str[i] == '[' || str[i] == ']')
   1438                         i++;
   1439                     continue;
   1440                 }
   1441                 if (c == '/') {
   1442                     pop_state();
   1443                     while (i < n && is_word(str[i]))
   1444                         i++;
   1445                     break;
   1446                 }
   1447             }
   1448         }
   1449 
   1450         function parse_number() {
   1451             style = 'number';
   1452             while (i < n && (is_word(str[i]) || (str[i] == '.' && (i == n - 1 || str[i + 1] != '.')))) {
   1453                 i++;
   1454             }
   1455         }
   1456 
   1457         var js_keywords = "|" +
   1458             "break|case|catch|continue|debugger|default|delete|do|" +
   1459             "else|finally|for|function|if|in|instanceof|new|" +
   1460             "return|switch|this|throw|try|typeof|while|with|" +
   1461             "class|const|enum|import|export|extends|super|" +
   1462             "implements|interface|let|package|private|protected|" +
   1463             "public|static|yield|" +
   1464             "undefined|null|true|false|Infinity|NaN|" +
   1465             "eval|arguments|" +
   1466             "await|";
   1467 
   1468         var js_no_regex = "|this|super|undefined|null|true|false|Infinity|NaN|arguments|";
   1469         var js_types = "|void|var|";
   1470 
   1471         function parse_identifier() {
   1472             can_regex = 1;
   1473 
   1474             while (i < n && is_word(str[i]))
   1475                 i++;
   1476 
   1477             var w = '|' + str.substring(start, i) + '|';
   1478 
   1479             if (js_keywords.indexOf(w) >= 0) {
   1480                 style = 'keyword';
   1481                 if (js_no_regex.indexOf(w) >= 0)
   1482                     can_regex = 0;
   1483                 return;
   1484             }
   1485 
   1486             var i1 = i;
   1487             while (i1 < n && str[i1] == ' ')
   1488                 i1++;
   1489 
   1490             if (i1 < n && str[i1] == '(') {
   1491                 style = 'function';
   1492                 return;
   1493             }
   1494 
   1495             if (js_types.indexOf(w) >= 0) {
   1496                 style = 'type';
   1497                 return;
   1498             }
   1499 
   1500             style = 'identifier';
   1501             can_regex = 0;
   1502         }
   1503 
   1504         function set_style(from, to) {
   1505             while (r.length < from)
   1506                 r.push('default');
   1507             while (r.length < to)
   1508                 r.push(style);
   1509         }
   1510 
   1511         for (i = 0; i < n;) {
   1512             style = null;
   1513             start = i;
   1514             switch (c = str[i++]) {
   1515             case ' ':
   1516             case '\t':
   1517             case '\r':
   1518             case '\n':
   1519                 continue;
   1520             case '+':
   1521             case '-':
   1522                 if (i < n && str[i] == c) {
   1523                     i++;
   1524                     continue;
   1525                 }
   1526                 can_regex = 1;
   1527                 continue;
   1528             case '/':
   1529                 if (i < n && str[i] == '*') { // block comment
   1530                     parse_block_comment();
   1531                     break;
   1532                 }
   1533                 if (i < n && str[i] == '/') { // line comment
   1534                     parse_line_comment();
   1535                     break;
   1536                 }
   1537                 if (can_regex) {
   1538                     parse_regex();
   1539                     can_regex = 0;
   1540                     break;
   1541                 }
   1542                 can_regex = 1;
   1543                 continue;
   1544             case '\'':
   1545             case '\"':
   1546             case '`':
   1547                 parse_string(c);
   1548                 can_regex = 0;
   1549                 break;
   1550             case '(':
   1551             case '[':
   1552             case '{':
   1553                 can_regex = 1;
   1554                 level++;
   1555                 push_state(c);
   1556                 continue;
   1557             case ')':
   1558             case ']':
   1559             case '}':
   1560                 can_regex = 0;
   1561                 if (level > 0 && is_balanced(last_state(), c)) {
   1562                     level--;
   1563                     pop_state();
   1564                     continue;
   1565                 }
   1566                 style = 'error';
   1567                 break;
   1568             default:
   1569                 if (is_digit(c)) {
   1570                     parse_number();
   1571                     can_regex = 0;
   1572                     break;
   1573                 }
   1574                 if (is_word(c) || c == '$') {
   1575                     parse_identifier();
   1576                     break;
   1577                 }
   1578                 can_regex = 1;
   1579                 continue;
   1580             }
   1581             if (style)
   1582                 set_style(start, i);
   1583         }
   1584         set_style(n, n);
   1585         return [ state, level, r ];
   1586     }
   1587 
   1588     termInit();
   1589 
   1590     cmd_start();
   1591 
   1592 })(globalThis);