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