quickjs-tart

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

repl.js (39185B)


      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 import * as std from "std";
     26 import * as os from "os";
     27 
     28 (function(g) {
     29     /* add 'os' and 'std' bindings */
     30     g.os = os;
     31     g.std = std;
     32 
     33     /* close global objects */
     34     var Object = g.Object;
     35     var String = g.String;
     36     var Array = g.Array;
     37     var Date = g.Date;
     38     var Math = g.Math;
     39     var isFinite = g.isFinite;
     40     var parseFloat = g.parseFloat;
     41 
     42     var colors = {
     43         none:    "\x1b[0m",
     44         black:   "\x1b[30m",
     45         red:     "\x1b[31m",
     46         green:   "\x1b[32m",
     47         yellow:  "\x1b[33m",
     48         blue:    "\x1b[34m",
     49         magenta: "\x1b[35m",
     50         cyan:    "\x1b[36m",
     51         white:   "\x1b[37m",
     52         gray:    "\x1b[30;1m",
     53         grey:    "\x1b[30;1m",
     54         bright_red:     "\x1b[31;1m",
     55         bright_green:   "\x1b[32;1m",
     56         bright_yellow:  "\x1b[33;1m",
     57         bright_blue:    "\x1b[34;1m",
     58         bright_magenta: "\x1b[35;1m",
     59         bright_cyan:    "\x1b[36;1m",
     60         bright_white:   "\x1b[37;1m",
     61     };
     62 
     63     var styles = {
     64         'default':    'bright_green',
     65         'comment':    'white',
     66         'string':     'bright_cyan',
     67         'regex':      'cyan',
     68         'number':     'green',
     69         'keyword':    'bright_white',
     70         'function':   'bright_yellow',
     71         'type':       'bright_magenta',
     72         'identifier': 'bright_green',
     73         'error':      'red',
     74         'result':     'bright_white',
     75         'error_msg':  'bright_red',
     76     };
     77 
     78     var history = [];
     79     var clip_board = "";
     80     var prec;
     81     var expBits;
     82     var log2_10;
     83 
     84     var pstate = "";
     85     var prompt = "";
     86     var plen = 0;
     87     var ps1 = "qjs > ";
     88     var ps2 = "  ... ";
     89     var utf8 = true;
     90     var show_time = false;
     91     var show_colors = true;
     92     var eval_start_time;
     93     var eval_time = 0;
     94 
     95     var mexpr = "";
     96     var level = 0;
     97     var cmd = "";
     98     var cursor_pos = 0;
     99     var last_cmd = "";
    100     var last_cursor_pos = 0;
    101     var history_index;
    102     var this_fun, last_fun;
    103     var quote_flag = false;
    104 
    105     var utf8_state = 0;
    106     var utf8_val = 0;
    107 
    108     var term_fd;
    109     var term_read_buf;
    110     var term_width;
    111     /* current X position of the cursor in the terminal */
    112     var term_cursor_x = 0;
    113 
    114     function termInit() {
    115         var tab;
    116         term_fd = std.in.fileno();
    117 
    118         /* get the terminal size */
    119         term_width = 80;
    120         if (os.isatty(term_fd)) {
    121             if (os.ttyGetWinSize) {
    122                 tab = os.ttyGetWinSize(term_fd);
    123                 if (tab)
    124                     term_width = tab[0];
    125             }
    126             if (os.ttySetRaw) {
    127                 /* set the TTY to raw mode */
    128                 os.ttySetRaw(term_fd);
    129             }
    130         }
    131 
    132         /* install a Ctrl-C signal handler */
    133         os.signal(os.SIGINT, sigint_handler);
    134 
    135         /* install a handler to read stdin */
    136         term_read_buf = new Uint8Array(64);
    137         os.setReadHandler(term_fd, term_read_handler);
    138     }
    139 
    140     function sigint_handler() {
    141         /* send Ctrl-C to readline */
    142         handle_byte(3);
    143     }
    144 
    145     function term_read_handler() {
    146         var l, i;
    147         l = os.read(term_fd, term_read_buf.buffer, 0, term_read_buf.length);
    148         for(i = 0; i < l; i++)
    149             handle_byte(term_read_buf[i]);
    150     }
    151 
    152     function handle_byte(c) {
    153         if (!utf8) {
    154             handle_char(c);
    155         } else if (utf8_state !== 0 && (c >= 0x80 && c < 0xc0)) {
    156             utf8_val = (utf8_val << 6) | (c & 0x3F);
    157             utf8_state--;
    158             if (utf8_state === 0) {
    159                 handle_char(utf8_val);
    160             }
    161         } else if (c >= 0xc0 && c < 0xf8) {
    162             utf8_state = 1 + (c >= 0xe0) + (c >= 0xf0);
    163             utf8_val = c & ((1 << (6 - utf8_state)) - 1);
    164         } else {
    165             utf8_state = 0;
    166             handle_char(c);
    167         }
    168     }
    169 
    170     function is_alpha(c) {
    171         return typeof c === "string" &&
    172             ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
    173     }
    174 
    175     function is_digit(c) {
    176         return typeof c === "string" && (c >= '0' && c <= '9');
    177     }
    178 
    179     function is_word(c) {
    180         return typeof c === "string" &&
    181             (is_alpha(c) || is_digit(c) || c == '_' || c == '$');
    182     }
    183 
    184     function ucs_length(str) {
    185         var len, c, i, str_len = str.length;
    186         len = 0;
    187         /* we never count the trailing surrogate to have the
    188          following property: ucs_length(str) =
    189          ucs_length(str.substring(0, a)) + ucs_length(str.substring(a,
    190          str.length)) for 0 <= a <= str.length */
    191         for(i = 0; i < str_len; i++) {
    192             c = str.charCodeAt(i);
    193             if (c < 0xdc00 || c >= 0xe000)
    194                 len++;
    195         }
    196         return len;
    197     }
    198 
    199     function is_trailing_surrogate(c)  {
    200         var d;
    201         if (typeof c !== "string")
    202             return false;
    203         d = c.codePointAt(0); /* can be NaN if empty string */
    204         return d >= 0xdc00 && d < 0xe000;
    205     }
    206 
    207     function is_balanced(a, b) {
    208         switch (a + b) {
    209         case "()":
    210         case "[]":
    211         case "{}":
    212             return true;
    213         }
    214         return false;
    215     }
    216 
    217     function print_color_text(str, start, style_names) {
    218         var i, j;
    219         for (j = start; j < str.length;) {
    220             var style = style_names[i = j];
    221             while (++j < str.length && style_names[j] == style)
    222                 continue;
    223             std.puts(colors[styles[style] || 'default']);
    224             std.puts(str.substring(i, j));
    225             std.puts(colors['none']);
    226         }
    227     }
    228 
    229     function print_csi(n, code) {
    230         std.puts("\x1b[" + ((n != 1) ? n : "") + code);
    231     }
    232 
    233     /* XXX: handle double-width characters */
    234     function move_cursor(delta) {
    235         var i, l;
    236         if (delta > 0) {
    237             while (delta != 0) {
    238                 if (term_cursor_x == (term_width - 1)) {
    239                     std.puts("\n"); /* translated to CRLF */
    240                     term_cursor_x = 0;
    241                     delta--;
    242                 } else {
    243                     l = Math.min(term_width - 1 - term_cursor_x, delta);
    244                     print_csi(l, "C"); /* right */
    245                     delta -= l;
    246                     term_cursor_x += l;
    247                 }
    248             }
    249         } else {
    250             delta = -delta;
    251             while (delta != 0) {
    252                 if (term_cursor_x == 0) {
    253                     print_csi(1, "A"); /* up */
    254                     print_csi(term_width - 1, "C"); /* right */
    255                     delta--;
    256                     term_cursor_x = term_width - 1;
    257                 } else {
    258                     l = Math.min(delta, term_cursor_x);
    259                     print_csi(l, "D"); /* left */
    260                     delta -= l;
    261                     term_cursor_x -= l;
    262                 }
    263             }
    264         }
    265     }
    266 
    267     function update() {
    268         var i, cmd_len;
    269         /* cursor_pos is the position in 16 bit characters inside the
    270            UTF-16 string 'cmd' */
    271         if (cmd != last_cmd) {
    272             if (!show_colors && last_cmd.substring(0, last_cursor_pos) == cmd.substring(0, last_cursor_pos)) {
    273                 /* optimize common case */
    274                 std.puts(cmd.substring(last_cursor_pos));
    275             } else {
    276                 /* goto the start of the line */
    277                 move_cursor(-ucs_length(last_cmd.substring(0, last_cursor_pos)));
    278                 if (show_colors) {
    279                     var str = mexpr ? mexpr + '\n' + cmd : cmd;
    280                     var start = str.length - cmd.length;
    281                     var colorstate = colorize_js(str);
    282                     print_color_text(str, start, colorstate[2]);
    283                 } else {
    284                     std.puts(cmd);
    285                 }
    286             }
    287             term_cursor_x = (term_cursor_x + ucs_length(cmd)) % term_width;
    288             if (term_cursor_x == 0) {
    289                 /* show the cursor on the next line */
    290                 std.puts(" \x08");
    291             }
    292             /* remove the trailing characters */
    293             std.puts("\x1b[J");
    294             last_cmd = cmd;
    295             last_cursor_pos = cmd.length;
    296         }
    297         if (cursor_pos > last_cursor_pos) {
    298             move_cursor(ucs_length(cmd.substring(last_cursor_pos, cursor_pos)));
    299         } else if (cursor_pos < last_cursor_pos) {
    300             move_cursor(-ucs_length(cmd.substring(cursor_pos, last_cursor_pos)));
    301         }
    302         last_cursor_pos = cursor_pos;
    303         std.out.flush();
    304     }
    305 
    306     /* editing commands */
    307     function insert(str) {
    308         if (str) {
    309             cmd = cmd.substring(0, cursor_pos) + str + cmd.substring(cursor_pos);
    310             cursor_pos += str.length;
    311         }
    312     }
    313 
    314     function quoted_insert() {
    315         quote_flag = true;
    316     }
    317 
    318     function abort() {
    319         cmd = "";
    320         cursor_pos = 0;
    321         return -2;
    322     }
    323 
    324     function alert() {
    325     }
    326 
    327     function beginning_of_line() {
    328         cursor_pos = 0;
    329     }
    330 
    331     function end_of_line() {
    332         cursor_pos = cmd.length;
    333     }
    334 
    335     function forward_char() {
    336         if (cursor_pos < cmd.length) {
    337             cursor_pos++;
    338             while (is_trailing_surrogate(cmd.charAt(cursor_pos)))
    339                 cursor_pos++;
    340         }
    341     }
    342 
    343     function backward_char() {
    344         if (cursor_pos > 0) {
    345             cursor_pos--;
    346             while (is_trailing_surrogate(cmd.charAt(cursor_pos)))
    347                 cursor_pos--;
    348         }
    349     }
    350 
    351     function skip_word_forward(pos) {
    352         while (pos < cmd.length && !is_word(cmd.charAt(pos)))
    353             pos++;
    354         while (pos < cmd.length && is_word(cmd.charAt(pos)))
    355             pos++;
    356         return pos;
    357     }
    358 
    359     function skip_word_backward(pos) {
    360         while (pos > 0 && !is_word(cmd.charAt(pos - 1)))
    361             pos--;
    362         while (pos > 0 && is_word(cmd.charAt(pos - 1)))
    363             pos--;
    364         return pos;
    365     }
    366 
    367     function forward_word() {
    368         cursor_pos = skip_word_forward(cursor_pos);
    369     }
    370 
    371     function backward_word() {
    372         cursor_pos = skip_word_backward(cursor_pos);
    373     }
    374 
    375     function accept_line() {
    376         std.puts("\n");
    377         history_add(cmd);
    378         return -1;
    379     }
    380 
    381     function history_add(str) {
    382         if (str) {
    383             history.push(str);
    384         }
    385         history_index = history.length;
    386     }
    387 
    388     function previous_history() {
    389         if (history_index > 0) {
    390             if (history_index == history.length) {
    391                 history.push(cmd);
    392             }
    393             history_index--;
    394             cmd = history[history_index];
    395             cursor_pos = cmd.length;
    396         }
    397     }
    398 
    399     function next_history() {
    400         if (history_index < history.length - 1) {
    401             history_index++;
    402             cmd = history[history_index];
    403             cursor_pos = cmd.length;
    404         }
    405     }
    406 
    407     function history_search(dir) {
    408         var pos = cursor_pos;
    409         for (var i = 1; i <= history.length; i++) {
    410             var index = (history.length + i * dir + history_index) % history.length;
    411             if (history[index].substring(0, pos) == cmd.substring(0, pos)) {
    412                 history_index = index;
    413                 cmd = history[index];
    414                 return;
    415             }
    416         }
    417     }
    418 
    419     function history_search_backward() {
    420         return history_search(-1);
    421     }
    422 
    423     function history_search_forward() {
    424         return history_search(1);
    425     }
    426 
    427     function delete_char_dir(dir) {
    428         var start, end;
    429 
    430         start = cursor_pos;
    431         if (dir < 0) {
    432             start--;
    433             while (is_trailing_surrogate(cmd.charAt(start)))
    434                 start--;
    435         }
    436         end = start + 1;
    437         while (is_trailing_surrogate(cmd.charAt(end)))
    438             end++;
    439 
    440         if (start >= 0 && start < cmd.length) {
    441             if (last_fun === kill_region) {
    442                 kill_region(start, end, dir);
    443             } else {
    444                 cmd = cmd.substring(0, start) + cmd.substring(end);
    445                 cursor_pos = start;
    446             }
    447         }
    448     }
    449 
    450     function delete_char() {
    451         delete_char_dir(1);
    452     }
    453 
    454     function control_d() {
    455         if (cmd.length == 0) {
    456             std.puts("\n");
    457             return -3; /* exit read eval print loop */
    458         } else {
    459             delete_char_dir(1);
    460         }
    461     }
    462 
    463     function backward_delete_char() {
    464         delete_char_dir(-1);
    465     }
    466 
    467     function transpose_chars() {
    468         var pos = cursor_pos;
    469         if (cmd.length > 1 && pos > 0) {
    470             if (pos == cmd.length)
    471                 pos--;
    472             cmd = cmd.substring(0, pos - 1) + cmd.substring(pos, pos + 1) +
    473                 cmd.substring(pos - 1, pos) + cmd.substring(pos + 1);
    474             cursor_pos = pos + 1;
    475         }
    476     }
    477 
    478     function transpose_words() {
    479         var p1 = skip_word_backward(cursor_pos);
    480         var p2 = skip_word_forward(p1);
    481         var p4 = skip_word_forward(cursor_pos);
    482         var p3 = skip_word_backward(p4);
    483 
    484         if (p1 < p2 && p2 <= cursor_pos && cursor_pos <= p3 && p3 < p4) {
    485             cmd = cmd.substring(0, p1) + cmd.substring(p3, p4) +
    486             cmd.substring(p2, p3) + cmd.substring(p1, p2);
    487             cursor_pos = p4;
    488         }
    489     }
    490 
    491     function upcase_word() {
    492         var end = skip_word_forward(cursor_pos);
    493         cmd = cmd.substring(0, cursor_pos) +
    494             cmd.substring(cursor_pos, end).toUpperCase() +
    495             cmd.substring(end);
    496     }
    497 
    498     function downcase_word() {
    499         var end = skip_word_forward(cursor_pos);
    500         cmd = cmd.substring(0, cursor_pos) +
    501             cmd.substring(cursor_pos, end).toLowerCase() +
    502             cmd.substring(end);
    503     }
    504 
    505     function kill_region(start, end, dir) {
    506         var s = cmd.substring(start, end);
    507         if (last_fun !== kill_region)
    508             clip_board = s;
    509         else if (dir < 0)
    510             clip_board = s + clip_board;
    511         else
    512             clip_board = clip_board + s;
    513 
    514         cmd = cmd.substring(0, start) + cmd.substring(end);
    515         if (cursor_pos > end)
    516             cursor_pos -= end - start;
    517         else if (cursor_pos > start)
    518             cursor_pos = start;
    519         this_fun = kill_region;
    520     }
    521 
    522     function kill_line() {
    523         kill_region(cursor_pos, cmd.length, 1);
    524     }
    525 
    526     function backward_kill_line() {
    527         kill_region(0, cursor_pos, -1);
    528     }
    529 
    530     function kill_word() {
    531         kill_region(cursor_pos, skip_word_forward(cursor_pos), 1);
    532     }
    533 
    534     function backward_kill_word() {
    535         kill_region(skip_word_backward(cursor_pos), cursor_pos, -1);
    536     }
    537 
    538     function yank() {
    539         insert(clip_board);
    540     }
    541 
    542     function control_c() {
    543         if (last_fun === control_c) {
    544             std.puts("\n");
    545             std.exit(0);
    546         } else {
    547             std.puts("\n(Press Ctrl-C again to quit)\n");
    548             reset();
    549             readline_print_prompt();
    550         }
    551     }
    552 
    553     function reset() {
    554         cmd = "";
    555         cursor_pos = 0;
    556     }
    557 
    558     function get_context_word(line, pos) {
    559         var s = "";
    560         while (pos > 0 && is_word(line[pos - 1])) {
    561             pos--;
    562             s = line[pos] + s;
    563         }
    564         return s;
    565     }
    566     function get_context_object(line, pos) {
    567         var obj, base, c;
    568         if (pos <= 0 || " ~!%^&*(-+={[|:;,<>?/".indexOf(line[pos - 1]) >= 0)
    569             return g;
    570         if (pos >= 2 && line[pos - 1] === ".") {
    571             pos--;
    572             obj = {};
    573             switch (c = line[pos - 1]) {
    574             case '\'':
    575             case '\"':
    576                 return "a";
    577             case ']':
    578                 return [];
    579             case '}':
    580                 return {};
    581             case '/':
    582                 return / /;
    583             default:
    584                 if (is_word(c)) {
    585                     base = get_context_word(line, pos);
    586                     if (["true", "false", "null", "this"].includes(base) || !isNaN(+base))
    587                         return eval(base);
    588                     // Check if `base` is a set of regexp flags
    589                     if (pos - base.length >= 3 && line[pos - base.length - 1] === '/')
    590                         return new RegExp('', base);
    591                     obj = get_context_object(line, pos - base.length);
    592                     if (obj === null || obj === void 0)
    593                         return obj;
    594                     if (obj === g && obj[base] === void 0)
    595                         return eval(base);
    596                     else
    597                         return obj[base];
    598                 }
    599                 return {};
    600             }
    601         }
    602         return void 0;
    603     }
    604 
    605     function get_completions(line, pos) {
    606         var s, obj, ctx_obj, r, i, j, paren;
    607 
    608         s = get_context_word(line, pos);
    609         ctx_obj = get_context_object(line, pos - s.length);
    610         r = [];
    611         /* enumerate properties from object and its prototype chain,
    612            add non-numeric regular properties with s as e prefix
    613          */
    614         for (i = 0, obj = ctx_obj; i < 10 && obj !== null && obj !== void 0; i++) {
    615             var props = Object.getOwnPropertyNames(obj);
    616             /* add non-numeric regular properties */
    617             for (j = 0; j < props.length; j++) {
    618                 var prop = props[j];
    619                 if (typeof prop == "string" && ""+(+prop) != prop && prop.startsWith(s))
    620                     r.push(prop);
    621             }
    622             obj = Object.getPrototypeOf(obj);
    623         }
    624         if (r.length > 1) {
    625             /* sort list with internal names last and remove duplicates */
    626             function symcmp(a, b) {
    627                 if (a[0] != b[0]) {
    628                     if (a[0] == '_')
    629                         return 1;
    630                     if (b[0] == '_')
    631                         return -1;
    632                 }
    633                 if (a < b)
    634                     return -1;
    635                 if (a > b)
    636                     return +1;
    637                 return 0;
    638             }
    639             r.sort(symcmp);
    640             for(i = j = 1; i < r.length; i++) {
    641                 if (r[i] != r[i - 1])
    642                     r[j++] = r[i];
    643             }
    644             r.length = j;
    645         }
    646         /* 'tab' = list of completions, 'pos' = cursor position inside
    647            the completions */
    648         return { tab: r, pos: s.length, ctx: ctx_obj };
    649     }
    650 
    651     function completion() {
    652         var tab, res, s, i, j, len, t, max_width, col, n_cols, row, n_rows;
    653         res = get_completions(cmd, cursor_pos);
    654         tab = res.tab;
    655         if (tab.length === 0)
    656             return;
    657         s = tab[0];
    658         len = s.length;
    659         /* add the chars which are identical in all the completions */
    660         for(i = 1; i < tab.length; i++) {
    661             t = tab[i];
    662             for(j = 0; j < len; j++) {
    663                 if (t[j] !== s[j]) {
    664                     len = j;
    665                     break;
    666                 }
    667             }
    668         }
    669         for(i = res.pos; i < len; i++) {
    670             insert(s[i]);
    671         }
    672         if (last_fun === completion && tab.length == 1) {
    673             /* append parentheses to function names */
    674             var m = res.ctx[tab[0]];
    675             if (typeof m == "function") {
    676                 insert('(');
    677                 if (m.length == 0)
    678                     insert(')');
    679             } else if (typeof m == "object") {
    680                 insert('.');
    681             }
    682         }
    683         /* show the possible completions */
    684         if (last_fun === completion && tab.length >= 2) {
    685             max_width = 0;
    686             for(i = 0; i < tab.length; i++)
    687                 max_width = Math.max(max_width, tab[i].length);
    688             max_width += 2;
    689             n_cols = Math.max(1, Math.floor((term_width + 1) / max_width));
    690             n_rows = Math.ceil(tab.length / n_cols);
    691             std.puts("\n");
    692             /* display the sorted list column-wise */
    693             for (row = 0; row < n_rows; row++) {
    694                 for (col = 0; col < n_cols; col++) {
    695                     i = col * n_rows + row;
    696                     if (i >= tab.length)
    697                         break;
    698                     s = tab[i];
    699                     if (col != n_cols - 1)
    700                         s = s.padEnd(max_width);
    701                     std.puts(s);
    702                 }
    703                 std.puts("\n");
    704             }
    705             /* show a new prompt */
    706             readline_print_prompt();
    707         }
    708     }
    709 
    710     var commands = {        /* command table */
    711         "\x01":     beginning_of_line,      /* ^A - bol */
    712         "\x02":     backward_char,          /* ^B - backward-char */
    713         "\x03":     control_c,              /* ^C - abort */
    714         "\x04":     control_d,              /* ^D - delete-char or exit */
    715         "\x05":     end_of_line,            /* ^E - eol */
    716         "\x06":     forward_char,           /* ^F - forward-char */
    717         "\x07":     abort,                  /* ^G - bell */
    718         "\x08":     backward_delete_char,   /* ^H - backspace */
    719         "\x09":     completion,             /* ^I - history-search-backward */
    720         "\x0a":     accept_line,            /* ^J - newline */
    721         "\x0b":     kill_line,              /* ^K - delete to end of line */
    722         "\x0d":     accept_line,            /* ^M - enter */
    723         "\x0e":     next_history,           /* ^N - down */
    724         "\x10":     previous_history,       /* ^P - up */
    725         "\x11":     quoted_insert,          /* ^Q - quoted-insert */
    726         "\x12":     alert,                  /* ^R - reverse-search */
    727         "\x13":     alert,                  /* ^S - search */
    728         "\x14":     transpose_chars,        /* ^T - transpose */
    729         "\x18":     reset,                  /* ^X - cancel */
    730         "\x19":     yank,                   /* ^Y - yank */
    731         "\x1bOA":   previous_history,       /* ^[OA - up */
    732         "\x1bOB":   next_history,           /* ^[OB - down */
    733         "\x1bOC":   forward_char,           /* ^[OC - right */
    734         "\x1bOD":   backward_char,          /* ^[OD - left */
    735         "\x1bOF":   forward_word,           /* ^[OF - ctrl-right */
    736         "\x1bOH":   backward_word,          /* ^[OH - ctrl-left */
    737         "\x1b[1;5C": forward_word,          /* ^[[1;5C - ctrl-right */
    738         "\x1b[1;5D": backward_word,         /* ^[[1;5D - ctrl-left */
    739         "\x1b[1~":  beginning_of_line,      /* ^[[1~ - bol */
    740         "\x1b[3~":  delete_char,            /* ^[[3~ - delete */
    741         "\x1b[4~":  end_of_line,            /* ^[[4~ - eol */
    742         "\x1b[5~":  history_search_backward,/* ^[[5~ - page up */
    743         "\x1b[6~":  history_search_forward, /* ^[[5~ - page down */
    744         "\x1b[A":   previous_history,       /* ^[[A - up */
    745         "\x1b[B":   next_history,           /* ^[[B - down */
    746         "\x1b[C":   forward_char,           /* ^[[C - right */
    747         "\x1b[D":   backward_char,          /* ^[[D - left */
    748         "\x1b[F":   end_of_line,            /* ^[[F - end */
    749         "\x1b[H":   beginning_of_line,      /* ^[[H - home */
    750         "\x1b\x7f": backward_kill_word,     /* M-C-? - backward_kill_word */
    751         "\x1bb":    backward_word,          /* M-b - backward_word */
    752         "\x1bd":    kill_word,              /* M-d - kill_word */
    753         "\x1bf":    forward_word,           /* M-f - backward_word */
    754         "\x1bk":    backward_kill_line,     /* M-k - backward_kill_line */
    755         "\x1bl":    downcase_word,          /* M-l - downcase_word */
    756         "\x1bt":    transpose_words,        /* M-t - transpose_words */
    757         "\x1bu":    upcase_word,            /* M-u - upcase_word */
    758         "\x7f":     backward_delete_char,   /* ^? - delete */
    759     };
    760 
    761     function dupstr(str, count) {
    762         var res = "";
    763         while (count-- > 0)
    764             res += str;
    765         return res;
    766     }
    767 
    768     var readline_keys;
    769     var readline_state;
    770     var readline_cb;
    771 
    772     function readline_print_prompt()
    773     {
    774         std.puts(prompt);
    775         term_cursor_x = ucs_length(prompt) % term_width;
    776         last_cmd = "";
    777         last_cursor_pos = 0;
    778     }
    779 
    780     function readline_start(defstr, cb) {
    781         cmd = defstr || "";
    782         cursor_pos = cmd.length;
    783         history_index = history.length;
    784         readline_cb = cb;
    785 
    786         prompt = pstate;
    787 
    788         if (mexpr) {
    789             prompt += dupstr(" ", plen - prompt.length);
    790             prompt += ps2;
    791         } else {
    792             if (show_time) {
    793                 var t = eval_time / 1000;
    794                 prompt += t.toFixed(6) + " ";
    795             }
    796             plen = prompt.length;
    797             prompt += ps1;
    798         }
    799         readline_print_prompt();
    800         update();
    801         readline_state = 0;
    802     }
    803 
    804     function handle_char(c1) {
    805         var c;
    806         c = String.fromCodePoint(c1);
    807         switch(readline_state) {
    808         case 0:
    809             if (c == '\x1b') {  /* '^[' - ESC */
    810                 readline_keys = c;
    811                 readline_state = 1;
    812             } else {
    813                 handle_key(c);
    814             }
    815             break;
    816         case 1: /* '^[ */
    817             readline_keys += c;
    818             if (c == '[') {
    819                 readline_state = 2;
    820             } else if (c == 'O') {
    821                 readline_state = 3;
    822             } else {
    823                 handle_key(readline_keys);
    824                 readline_state = 0;
    825             }
    826             break;
    827         case 2: /* '^[[' - CSI */
    828             readline_keys += c;
    829             if (!(c == ';' || (c >= '0' && c <= '9'))) {
    830                 handle_key(readline_keys);
    831                 readline_state = 0;
    832             }
    833             break;
    834         case 3: /* '^[O' - ESC2 */
    835             readline_keys += c;
    836             handle_key(readline_keys);
    837             readline_state = 0;
    838             break;
    839         }
    840     }
    841 
    842     function handle_key(keys) {
    843         var fun;
    844 
    845         if (quote_flag) {
    846             if (ucs_length(keys) === 1)
    847                 insert(keys);
    848             quote_flag = false;
    849         } else if (fun = commands[keys]) {
    850             this_fun = fun;
    851             switch (fun(keys)) {
    852             case -1:
    853                 readline_cb(cmd);
    854                 return;
    855             case -2:
    856                 readline_cb(null);
    857                 return;
    858             case -3:
    859                 /* uninstall a Ctrl-C signal handler */
    860                 os.signal(os.SIGINT, null);
    861                 /* uninstall the stdin read handler */
    862                 os.setReadHandler(term_fd, null);
    863                 return;
    864             }
    865             last_fun = this_fun;
    866         } else if (ucs_length(keys) === 1 && keys >= ' ') {
    867             insert(keys);
    868             last_fun = insert;
    869         } else {
    870             alert(); /* beep! */
    871         }
    872 
    873         cursor_pos = (cursor_pos < 0) ? 0 :
    874             (cursor_pos > cmd.length) ? cmd.length : cursor_pos;
    875         update();
    876     }
    877 
    878     var hex_mode = false;
    879 
    880     function number_to_string_hex(a) {
    881         var s;
    882         if (a < 0) {
    883             a = -a;
    884             s = "-";
    885         } else {
    886             s = "";
    887         }
    888         s += "0x" + a.toString(16);
    889         return s;
    890     }
    891     
    892     function extract_directive(a) {
    893         var pos;
    894         if (a[0] !== '\\')
    895             return "";
    896         for (pos = 1; pos < a.length; pos++) {
    897             if (!is_alpha(a[pos]))
    898                 break;
    899         }
    900         return a.substring(1, pos);
    901     }
    902 
    903     /* return true if the string after cmd can be evaluted as JS */
    904     function handle_directive(cmd, expr) {
    905         var param, prec1, expBits1;
    906 
    907         if (cmd === "h" || cmd === "?" || cmd == "help") {
    908             help();
    909         } else if (cmd === "load") {
    910             var filename = expr.substring(cmd.length + 1).trim();
    911             if (filename.lastIndexOf(".") <= filename.lastIndexOf("/"))
    912                 filename += ".js";
    913             std.loadScript(filename);
    914             return false;
    915         } else if (cmd === "x") {
    916             hex_mode = true;
    917         } else if (cmd === "d") {
    918             hex_mode = false;
    919         } else if (cmd === "t") {
    920             show_time = !show_time;
    921         } else if (cmd === "clear") {
    922             std.puts("\x1b[H\x1b[J");
    923         } else if (cmd === "q") {
    924             std.exit(0);
    925         } else {
    926             std.puts("Unknown directive: " + cmd + "\n");
    927             return false;
    928         }
    929         return true;
    930     }
    931 
    932     function help() {
    933         function sel(n) {
    934             return n ? "*": " ";
    935         }
    936         std.puts("\\h          this help\n" +
    937                  "\\x         " + sel(hex_mode) + "hexadecimal number display\n" +
    938                  "\\d         " + sel(!hex_mode) + "decimal number display\n" +
    939                  "\\t         " + sel(show_time) + "toggle timing display\n" +
    940                   "\\clear      clear the terminal\n" +
    941                  "\\q          exit\n");
    942     }
    943 
    944     function cmd_start() {
    945         std.puts('QuickJS - Type "\\h" for help\n');
    946 
    947         cmd_readline_start();
    948     }
    949 
    950     function cmd_readline_start() {
    951         readline_start(dupstr("    ", level), readline_handle_cmd);
    952     }
    953 
    954     function readline_handle_cmd(expr) {
    955         if (!handle_cmd(expr)) {
    956             cmd_readline_start();
    957         }
    958     }
    959 
    960     /* return true if async termination */
    961     function handle_cmd(expr) {
    962         var colorstate, cmd;
    963 
    964         if (expr === null) {
    965             expr = "";
    966             return false;
    967         }
    968         if (expr === "?") {
    969             help();
    970             return false;
    971         }
    972         cmd = extract_directive(expr);
    973         if (cmd.length > 0) {
    974             if (!handle_directive(cmd, expr)) {
    975                 return false;
    976             }
    977             expr = expr.substring(cmd.length + 1);
    978         }
    979         if (expr === "")
    980             return false;
    981 
    982         if (mexpr)
    983             expr = mexpr + '\n' + expr;
    984         colorstate = colorize_js(expr);
    985         pstate = colorstate[0];
    986         level = colorstate[1];
    987         if (pstate) {
    988             mexpr = expr;
    989             return false;
    990         }
    991         mexpr = "";
    992 
    993         eval_and_print_start(expr);
    994 
    995         return true;
    996     }
    997 
    998     function eval_and_print_start(expr) {
    999         var result;
   1000 
   1001         try {
   1002             eval_start_time = os.now();
   1003             /* eval as a script */
   1004             result = std.evalScript(expr, { backtrace_barrier: true, async: true });
   1005             /* result is a promise */
   1006             result.then(print_eval_result, print_eval_error);
   1007         } catch (error) {
   1008             print_eval_error(error);
   1009         }
   1010     }
   1011 
   1012     function print_eval_result(result) {
   1013         var default_print = true;
   1014 
   1015         result = result.value;
   1016         eval_time = os.now() - eval_start_time;
   1017         std.puts(colors[styles.result]);
   1018         if (hex_mode) {
   1019             if (typeof result == "number" &&
   1020                 result === Math.floor(result)) {
   1021                 std.puts(number_to_string_hex(result));
   1022                 default_print = false;
   1023             } else if (typeof result == "bigint") {
   1024                 std.puts(number_to_string_hex(result));
   1025                 std.puts("n");
   1026                 default_print = false;
   1027             }
   1028         }
   1029         if (default_print) {
   1030             std.__printObject(result);
   1031         }
   1032         std.puts("\n");
   1033         std.puts(colors.none);
   1034         /* set the last result */
   1035         g._ = result;
   1036 
   1037         handle_cmd_end();
   1038     }
   1039 
   1040     function print_eval_error(error) {
   1041         std.puts(colors[styles.error_msg]);
   1042         if (!(error instanceof Error))
   1043             std.puts("Throw: ");
   1044         std.__printObject(error);
   1045         std.puts("\n");
   1046         std.puts(colors.none);
   1047 
   1048         handle_cmd_end();
   1049     }
   1050 
   1051     function handle_cmd_end() {
   1052         level = 0;
   1053         /* run the garbage collector after each command */
   1054         std.gc();
   1055         cmd_readline_start();
   1056     }
   1057 
   1058     function colorize_js(str) {
   1059         var i, c, start, n = str.length;
   1060         var style, state = "", level = 0;
   1061         var primary, can_regex = 1;
   1062         var r = [];
   1063 
   1064         function push_state(c) { state += c; }
   1065         function last_state(c) { return state.substring(state.length - 1); }
   1066         function pop_state(c) {
   1067             var c = last_state();
   1068             state = state.substring(0, state.length - 1);
   1069             return c;
   1070         }
   1071 
   1072         function parse_block_comment() {
   1073             style = 'comment';
   1074             push_state('/');
   1075             for (i++; i < n - 1; i++) {
   1076                 if (str[i] == '*' && str[i + 1] == '/') {
   1077                     i += 2;
   1078                     pop_state('/');
   1079                     break;
   1080                 }
   1081             }
   1082         }
   1083 
   1084         function parse_line_comment() {
   1085             style = 'comment';
   1086             for (i++; i < n; i++) {
   1087                 if (str[i] == '\n') {
   1088                     break;
   1089                 }
   1090             }
   1091         }
   1092 
   1093         function parse_string(delim) {
   1094             style = 'string';
   1095             push_state(delim);
   1096             while (i < n) {
   1097                 c = str[i++];
   1098                 if (c == '\n') {
   1099                     style = 'error';
   1100                     continue;
   1101                 }
   1102                 if (c == '\\') {
   1103                     if (i >= n)
   1104                         break;
   1105                     i++;
   1106                 } else
   1107                 if (c == delim) {
   1108                     pop_state();
   1109                     break;
   1110                 }
   1111             }
   1112         }
   1113 
   1114         function parse_regex() {
   1115             style = 'regex';
   1116             push_state('/');
   1117             while (i < n) {
   1118                 c = str[i++];
   1119                 if (c == '\n') {
   1120                     style = 'error';
   1121                     continue;
   1122                 }
   1123                 if (c == '\\') {
   1124                     if (i < n) {
   1125                         i++;
   1126                     }
   1127                     continue;
   1128                 }
   1129                 if (last_state() == '[') {
   1130                     if (c == ']') {
   1131                         pop_state()
   1132                     }
   1133                     // ECMA 5: ignore '/' inside char classes
   1134                     continue;
   1135                 }
   1136                 if (c == '[') {
   1137                     push_state('[');
   1138                     if (str[i] == '[' || str[i] == ']')
   1139                         i++;
   1140                     continue;
   1141                 }
   1142                 if (c == '/') {
   1143                     pop_state();
   1144                     while (i < n && is_word(str[i]))
   1145                         i++;
   1146                     break;
   1147                 }
   1148             }
   1149         }
   1150 
   1151         function parse_number() {
   1152             style = 'number';
   1153             while (i < n && (is_word(str[i]) || (str[i] == '.' && (i == n - 1 || str[i + 1] != '.')))) {
   1154                 i++;
   1155             }
   1156         }
   1157 
   1158         var js_keywords = "|" +
   1159             "break|case|catch|continue|debugger|default|delete|do|" +
   1160             "else|finally|for|function|if|in|instanceof|new|" +
   1161             "return|switch|this|throw|try|typeof|while|with|" +
   1162             "class|const|enum|import|export|extends|super|" +
   1163             "implements|interface|let|package|private|protected|" +
   1164             "public|static|yield|" +
   1165             "undefined|null|true|false|Infinity|NaN|" +
   1166             "eval|arguments|" +
   1167             "await|";
   1168 
   1169         var js_no_regex = "|this|super|undefined|null|true|false|Infinity|NaN|arguments|";
   1170         var js_types = "|void|var|";
   1171 
   1172         function parse_identifier() {
   1173             can_regex = 1;
   1174 
   1175             while (i < n && is_word(str[i]))
   1176                 i++;
   1177 
   1178             var w = '|' + str.substring(start, i) + '|';
   1179 
   1180             if (js_keywords.indexOf(w) >= 0) {
   1181                 style = 'keyword';
   1182                 if (js_no_regex.indexOf(w) >= 0)
   1183                     can_regex = 0;
   1184                 return;
   1185             }
   1186 
   1187             var i1 = i;
   1188             while (i1 < n && str[i1] == ' ')
   1189                 i1++;
   1190 
   1191             if (i1 < n && str[i1] == '(') {
   1192                 style = 'function';
   1193                 return;
   1194             }
   1195 
   1196             if (js_types.indexOf(w) >= 0) {
   1197                 style = 'type';
   1198                 return;
   1199             }
   1200 
   1201             style = 'identifier';
   1202             can_regex = 0;
   1203         }
   1204 
   1205         function set_style(from, to) {
   1206             while (r.length < from)
   1207                 r.push('default');
   1208             while (r.length < to)
   1209                 r.push(style);
   1210         }
   1211 
   1212         for (i = 0; i < n;) {
   1213             style = null;
   1214             start = i;
   1215             switch (c = str[i++]) {
   1216             case ' ':
   1217             case '\t':
   1218             case '\r':
   1219             case '\n':
   1220                 continue;
   1221             case '+':
   1222             case '-':
   1223                 if (i < n && str[i] == c) {
   1224                     i++;
   1225                     continue;
   1226                 }
   1227                 can_regex = 1;
   1228                 continue;
   1229             case '/':
   1230                 if (i < n && str[i] == '*') { // block comment
   1231                     parse_block_comment();
   1232                     break;
   1233                 }
   1234                 if (i < n && str[i] == '/') { // line comment
   1235                     parse_line_comment();
   1236                     break;
   1237                 }
   1238                 if (can_regex) {
   1239                     parse_regex();
   1240                     can_regex = 0;
   1241                     break;
   1242                 }
   1243                 can_regex = 1;
   1244                 continue;
   1245             case '\'':
   1246             case '\"':
   1247             case '`':
   1248                 parse_string(c);
   1249                 can_regex = 0;
   1250                 break;
   1251             case '(':
   1252             case '[':
   1253             case '{':
   1254                 can_regex = 1;
   1255                 level++;
   1256                 push_state(c);
   1257                 continue;
   1258             case ')':
   1259             case ']':
   1260             case '}':
   1261                 can_regex = 0;
   1262                 if (level > 0 && is_balanced(last_state(), c)) {
   1263                     level--;
   1264                     pop_state();
   1265                     continue;
   1266                 }
   1267                 style = 'error';
   1268                 break;
   1269             default:
   1270                 if (is_digit(c)) {
   1271                     parse_number();
   1272                     can_regex = 0;
   1273                     break;
   1274                 }
   1275                 if (is_word(c) || c == '$') {
   1276                     parse_identifier();
   1277                     break;
   1278                 }
   1279                 can_regex = 1;
   1280                 continue;
   1281             }
   1282             if (style)
   1283                 set_style(start, i);
   1284         }
   1285         set_style(n, n);
   1286         return [ state, level, r ];
   1287     }
   1288 
   1289     termInit();
   1290 
   1291     cmd_start();
   1292 
   1293 })(globalThis);