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