MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

improve repl ergonomics

+127 -15
+127 -15
src/repl.c
··· 25 25 #include "reactor.h" 26 26 #include "runtime.h" 27 27 #include "internal.h" 28 + #include "utf8.h" 28 29 29 30 #include "silver/ast.h" 30 31 #include "silver/engine.h" ··· 318 319 repl_print_mode_t print_mode 319 320 ) { 320 321 if (!repl_precheck_and_commit_lexicals(js, decl_registry, code, len)) { 322 + if (js->thrown_exists) js_set(js, js_glob(js), "_error", js->thrown_value); 321 323 print_uncaught_throw(js); 322 324 return; 323 325 } ··· 326 328 jsval_t result = js_eval_bytecode_repl(js, code, len); 327 329 js_run_event_loop(js); 328 330 329 - if (print_uncaught_throw(js)) return; 331 + if (js->thrown_exists) { 332 + js_set(js, js_glob(js), "_error", js->thrown_value); 333 + if (print_uncaught_throw(js)) return; 334 + } 335 + 330 336 if (print_mode == REPL_PRINT_INTERACTIVE) { 337 + js_set(js, js_glob(js), "_", result); 331 338 print_repl_value(js, result, stdout); 332 339 return; 333 340 } ··· 342 349 static cmd_result_t cmd_save(ant_t *js, history_t *history, const char *arg); 343 350 static cmd_result_t cmd_stats(ant_t *js, history_t *history, const char *arg); 344 351 static cmd_result_t cmd_copy(ant_t *js, history_t *history, const char *arg); 352 + static cmd_result_t cmd_clear(ant_t *js, history_t *history, const char *arg); 353 + static cmd_result_t cmd_history(ant_t *js, history_t *history, const char *arg); 345 354 346 355 static const repl_command_t commands[] = { 347 - { "help", "Show this help message", false, cmd_help }, 348 - { "exit", "Exit the REPL", false, cmd_exit }, 349 - { "load", "Load JS from a file into the REPL session", true, cmd_load }, 350 - { "save", "Save all evaluated commands in this REPL session to a file", true, cmd_save }, 351 - { "stats", "Show memory statistics", false, cmd_stats }, 352 - { "copy", "Evaluate expression and copy its value", true, cmd_copy }, 356 + { "help", "Show this help message", false, cmd_help }, 357 + { "exit", "Exit the REPL", false, cmd_exit }, 358 + { "clear", "Clear the screen", false, cmd_clear }, 359 + { "history", "Show command history", false, cmd_history }, 360 + { "load", "Load JS from a file into the REPL session", true, cmd_load }, 361 + { "save", "Save all evaluated commands in this REPL session to a file", true, cmd_save }, 362 + { "stats", "Show memory statistics", false, cmd_stats }, 363 + { "copy", "Evaluate expression and copy its value", true, cmd_copy }, 353 364 { NULL, NULL, false, NULL } 354 365 }; 355 366 367 + static const char *repl_command_usage(const repl_command_t *cmd) { 368 + if (!cmd || !cmd->name) return ""; 369 + if (strcmp(cmd->name, "copy") == 0) return ".copy [expr]"; 370 + if (strcmp(cmd->name, "load") == 0) return ".load <file>"; 371 + if (strcmp(cmd->name, "save") == 0) return ".save <file>"; 372 + if (strcmp(cmd->name, "history") == 0) return ".history"; 373 + if (strcmp(cmd->name, "clear") == 0) return ".clear"; 374 + if (strcmp(cmd->name, "stats") == 0) return ".stats"; 375 + if (strcmp(cmd->name, "exit") == 0) return ".exit"; 376 + if (strcmp(cmd->name, "help") == 0) return ".help"; 377 + return cmd->name; 378 + } 379 + 356 380 static cmd_result_t cmd_help(ant_t *js, history_t *history, const char *arg) { 381 + printf("\n%sREPL Commands:%s\n", C_BOLD, C_RESET); 357 382 for (const repl_command_t *cmd = commands; cmd->name; cmd++) { 358 - printf(" .%-7s - %s\n", cmd->name, cmd->description); 383 + const char *usage = repl_command_usage(cmd); 384 + printf(" %s%-12s%s %s\n", C_CYAN, usage, C_RESET, cmd->description); 359 385 } 360 - printf("\nPress Ctrl+C to abort current expression.\n"); 386 + printf("\n%sKeybindings:%s\n", C_BOLD, C_RESET); 387 + printf(" Ctrl+C Abort current expression (press twice to exit)\n"); 388 + printf(" Left/Right Move backward/forward one character\n"); 389 + printf(" Up/Down Navigate history\n"); 390 + printf(" Backspace Delete character backward\n"); 391 + printf(" Enter Submit input\n"); 392 + printf("\n%sSpecial Variables:%s\n", C_BOLD, C_RESET); 393 + printf(" %s_%s Last expression result\n", C_CYAN, C_RESET); 394 + printf(" %s_error%s Last error\n\n", C_CYAN, C_RESET); 361 395 return CMD_OK; 362 396 } 363 397 ··· 424 458 return CMD_OK; 425 459 } 426 460 461 + static cmd_result_t cmd_clear(ant_t *js, history_t *history, const char *arg) { 462 + fputs("\033[2J\033[H", stdout); 463 + fflush(stdout); 464 + return CMD_OK; 465 + } 466 + 467 + static cmd_result_t cmd_history(ant_t *js, history_t *history, const char *arg) { 468 + for (int i = 0; i < history->count; i++) { 469 + printf("%4d %s\n", i + 1, history->lines[i]); 470 + } 471 + return CMD_OK; 472 + } 473 + 427 474 #ifdef _WIN32 428 475 static bool repl_copy_with_command(const char *data, size_t len) { 429 476 FILE *pipe = _popen("clip", "wb"); ··· 468 515 jsval_t result = js_eval_bytecode_repl(js, arg, strlen(arg)); 469 516 470 517 js_run_event_loop(js); 471 - if (print_uncaught_throw(js)) return CMD_OK; 518 + if (js->thrown_exists) { 519 + js_set(js, js_glob(js), "_error", js->thrown_value); 520 + if (print_uncaught_throw(js)) return CMD_OK; 521 + } 522 + 523 + js_set(js, js_glob(js), "_", result); 472 524 473 525 char cbuf[512]; 474 526 js_cstr_t cstr = js_to_cstr(js, result, cbuf, sizeof(cbuf)); ··· 618 670 return cols > 0 ? cols : 80; 619 671 } 620 672 673 + static size_t repl_skip_ansi_escape(const char *s, size_t len, size_t i) { 674 + if (i >= len || (unsigned char)s[i] != 0x1B) return i; 675 + if (i + 1 >= len) return i + 1; 676 + 677 + unsigned char next = (unsigned char)s[i + 1]; 678 + 679 + if (next == '[') { 680 + i += 2; 681 + while (i < len) { 682 + unsigned char ch = (unsigned char)s[i++]; 683 + if (ch >= 0x40 && ch <= 0x7E) break; 684 + } 685 + return i; 686 + } 687 + 688 + if (next == ']') { 689 + i += 2; 690 + while (i < len) { 691 + unsigned char ch = (unsigned char)s[i]; 692 + if (ch == '\a') return i + 1; 693 + if (ch == 0x1B && i + 1 < len && s[i + 1] == '\\') return i + 2; 694 + i++; 695 + } 696 + return i; 697 + } 698 + 699 + return i + 2; 700 + } 701 + 702 + static int repl_display_width(const char *s) { 703 + if (!s) return 0; 704 + 705 + int width = 0; 706 + size_t len = strlen(s); 707 + size_t i = 0; 708 + 709 + while (i < len) { 710 + if ((unsigned char)s[i] == 0x1B) { 711 + i = repl_skip_ansi_escape(s, len, i); 712 + continue; 713 + } 714 + 715 + utf8proc_int32_t cp = 0; 716 + utf8proc_ssize_t n = utf8_next((const utf8proc_uint8_t *)(s + i), (utf8proc_ssize_t)(len - i), &cp); 717 + if (n <= 0) n = 1; 718 + 719 + int w = utf8proc_charwidth(cp); 720 + if (w > 0) width += w; 721 + 722 + i += (size_t)n; 723 + } 724 + 725 + return width; 726 + } 727 + 621 728 static void repl_move_to_line_start(const char *prompt, int pos, int cols) { 622 - int prompt_len = (int)strlen(prompt); 729 + int prompt_len = repl_display_width(prompt); 623 730 int cursor_cols = prompt_len + pos; 624 731 int cursor_row = cursor_cols / cols; 625 732 if (cursor_cols > 0 && cursor_cols % cols == 0) cursor_row--; ··· 634 741 635 742 static void refresh_line(const char *line, int len, int pos, const char *prompt) { 636 743 int cols = repl_terminal_cols(); 637 - int prompt_len = (int)strlen(prompt); 744 + int prompt_len = repl_display_width(prompt); 638 745 int line_cols = prompt_len + len; 639 746 int current_rows = line_cols > 0 ? (line_cols - 1) / cols + 1 : 1; 640 747 int rows = repl_last_render_rows > current_rows ? repl_last_render_rows : current_rows; ··· 649 756 650 757 fputs(prompt, stdout); 651 758 652 - if (crprintf_get_color() && len > 0 && len <= 2048) { 759 + if (crprintf_get_color() && len > 0) { 760 + if (len <= 2048) { 653 761 char tagged[8192]; 654 762 char rendered[8192]; 655 763 ··· 663 771 crprintf_state_free(rs); 664 772 665 773 fputs(rendered, stdout); 774 + } else fwrite(line, 1, (size_t)len, stdout); 666 775 } else if (len > 0) fwrite(line, 1, (size_t)len, stdout); 667 776 668 777 int end_cols = prompt_len + len; ··· 916 1025 917 1026 crprintf( 918 1027 "Welcome to <red+bold>Ant JavaScript</> v%s\n" 919 - "Type <cyan>.copy [code]</cyan> to copy, <cyan>.help</cyan> for more information.\n", 1028 + "Type <cyan>.copy [code]</cyan> to copy, <cyan>.help</cyan> for more information.\n\n", 920 1029 ANT_VERSION 921 1030 ); 922 1031 ··· 939 1048 940 1049 js_set(js, js_glob(js), "__dirname", js_mkstr(js, ".", 1)); 941 1050 js_set(js, js_glob(js), "__filename", js_mkstr(js, "[repl]", 6)); 1051 + 1052 + js_set(js, js_glob(js), "_", js_mkundef()); 1053 + js_set(js, js_glob(js), "_error", js_mkundef()); 942 1054 943 1055 int prev_ctrl_c_count = 0; 944 1056 char *multiline_buf = NULL; ··· 946 1058 size_t multiline_cap = 0; 947 1059 948 1060 while (1) { 949 - const char *prompt = multiline_buf ? "| " : "> "; 1061 + const char *prompt = multiline_buf ? "\x1b[2m|\x1b[0m " : "\x1b[2mโฏ\x1b[0m "; 950 1062 951 1063 if (multiline_buf && multiline_len > 0) { 952 1064 char scratch[8192];