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