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