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