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