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.

add multiline repl support

+217 -80
+217 -80
src/repl.c
··· 26 26 27 27 #define MAX_HISTORY 512 28 28 #define MAX_LINE_LENGTH 4096 29 + #define MAX_MULTILINE_LENGTH 65536 30 + #define INPUT char *line, int *pos, int *len, key_event_t *key, history_t *hist, const char *prompt 29 31 30 32 static volatile sig_atomic_t ctrl_c_pressed = 0; 31 33 ··· 48 50 bool has_arg; 49 51 cmd_result_t (*handler)(struct js *js, history_t *history, const char *arg); 50 52 } repl_command_t; 53 + 54 + static void sigint_handler(int sig) { 55 + (void)sig; 56 + ctrl_c_pressed++; 57 + } 51 58 52 59 static cmd_result_t cmd_help(struct js *js, history_t *history, const char *arg); 53 60 static cmd_result_t cmd_exit(struct js *js, history_t *history, const char *arg); ··· 163 170 const char *cmd_start = line + 1; 164 171 165 172 for (const repl_command_t *cmd = commands; cmd->name; cmd++) { 166 - size_t name_len = strlen(cmd->name); 173 + size_t n = strlen(cmd->name); 174 + if (strncmp(cmd_start, cmd->name, n) != 0) continue; 167 175 168 - if (cmd->has_arg) { 169 - if ( 170 - strncmp(cmd_start, cmd->name, name_len) == 0 && 171 - (cmd_start[name_len] == ' ' || cmd_start[name_len] == '\0') 172 - ) { 173 - const char *arg = cmd_start + name_len; 174 - while (*arg == ' ') arg++; 175 - return cmd->handler(js, history, arg); 176 - } 177 - } else { 178 - if (strcmp(cmd_start, cmd->name) == 0) return cmd->handler(js, history, NULL); 176 + char next = cmd_start[n]; 177 + if (cmd->has_arg && (next == ' ' || next == '\0')) { 178 + const char *arg = cmd_start + n; 179 + while (*arg == ' ') arg++; 180 + return cmd->handler(js, history, arg); 179 181 } 182 + if (!cmd->has_arg && next == '\0') return cmd->handler(js, history, NULL); 180 183 } 181 184 182 185 return CMD_NOT_FOUND; 183 - } 184 - 185 - static void sigint_handler(int sig) { 186 - (void)sig; 187 - ctrl_c_pressed++; 188 186 } 189 187 190 188 static void history_init(history_t *hist) { ··· 273 271 fclose(fp); 274 272 } 275 273 276 - typedef enum { KEY_NONE, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_BACKSPACE, KEY_ENTER, KEY_EOF, KEY_CHAR } key_type_t; 274 + typedef enum { 275 + KEY_NONE, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, 276 + KEY_BACKSPACE, KEY_ENTER, KEY_EOF, KEY_CHAR 277 + } key_type_t; 278 + 277 279 typedef struct { key_type_t type; int ch; } key_event_t; 280 + typedef void (*key_handler_t)(INPUT); 278 281 279 - static void line_set(char *line, int *pos, int *len, const char *str) { 280 - printf("\r\033[K> %s", str); 281 - fflush(stdout); 282 - strcpy(line, str); 283 - *len = strlen(line); 284 - *pos = *len; 282 + static void cursor_move(int *pos, int len, int dir) { 283 + if (dir < 0 && *pos > 0) { printf("\033[D"); fflush(stdout); (*pos)--; } 284 + else if (dir > 0 && *pos < len) { printf("\033[C"); fflush(stdout); (*pos)++; } 285 + } 286 + 287 + static void line_set(char *line, int *pos, int *len, const char *str, const char *prompt) { 288 + printf("\r\033[K%s%s", prompt, str); 289 + fflush(stdout); strcpy(line, str); 290 + *len = strlen(line); *pos = *len; 285 291 } 286 292 287 293 static void line_backspace(char *line, int *pos, int *len) { 288 - if (*pos > 0) { 289 - memmove(line + *pos - 1, line + *pos, *len - *pos + 1); 290 - (*pos)--; (*len)--; 291 - printf("\b\033[K%s", line + *pos); 292 - for (int i = 0; i < *len - *pos; i++) printf("\033[D"); 293 - fflush(stdout); 294 - } 294 + if (*pos <= 0) return; 295 + 296 + memmove(line + *pos - 1, line + *pos, *len - *pos + 1); 297 + (*pos)--; (*len)--; 298 + printf("\b\033[K%s", line + *pos); 299 + for (int i = 0; i < *len - *pos; i++) printf("\033[D"); 300 + fflush(stdout); 295 301 } 296 302 297 303 static void line_insert(char *line, int *pos, int *len, int c) { 298 - if (*len < MAX_LINE_LENGTH - 1) { 299 - memmove(line + *pos + 1, line + *pos, *len - *pos + 1); 300 - line[*pos] = c; 301 - (*pos)++; (*len)++; 302 - printf("%c%s", c, line + *pos); 303 - for (int i = 0; i < *len - *pos; i++) printf("\033[D"); 304 - fflush(stdout); 305 - } 306 - } 307 - 308 - static void cursor_move(int *pos, int len, int dir) { 309 - if (dir < 0 && *pos > 0) { printf("\033[D"); fflush(stdout); (*pos)--; } 310 - else if (dir > 0 && *pos < len) { printf("\033[C"); fflush(stdout); (*pos)++; } 304 + if (*len >= MAX_LINE_LENGTH - 1) return; 305 + 306 + memmove(line + *pos + 1, line + *pos, *len - *pos + 1); 307 + line[*pos] = c; 308 + (*pos)++; (*len)++; 309 + printf("%c%s", c, line + *pos); 310 + for (int i = 0; i < *len - *pos; i++) printf("\033[D"); 311 + fflush(stdout); 311 312 } 312 313 313 314 #ifdef _WIN32 ··· 338 339 static key_event_t read_key(void) { 339 340 if (ctrl_c_pressed > 0) return (key_event_t){ KEY_EOF, 0 }; 340 341 int c = getchar(); 342 + if (c == EOF && !feof(stdin)) { clearerr(stdin); return (key_event_t){ KEY_EOF, 0 }; } 341 343 if (c == EOF) return (key_event_t){ KEY_EOF, 0 }; 342 344 if (c == 27) { 343 345 if (getchar() == '[') { ··· 365 367 #define TERM_RESTORE() tcsetattr(STDIN_FILENO, TCSANOW, &saved_tio) 366 368 #endif 367 369 368 - static char* read_line_with_history(history_t *hist, struct js *js) { 369 - (void)js; 370 + static void handle_up(INPUT) { 371 + const char *h = history_prev(hist); 372 + if (h) line_set(line, pos, len, h, prompt); 373 + } 374 + 375 + static void handle_down(INPUT) { 376 + const char *h = history_next(hist); 377 + if (h) line_set(line, pos, len, h, prompt); 378 + } 379 + 380 + static void handle_left(INPUT) { cursor_move(pos, *len, -1); } 381 + static void handle_right(INPUT) { cursor_move(pos, *len, 1); } 382 + static void handle_backspace(INPUT) { line_backspace(line, pos, len); } 383 + static void handle_char(INPUT) { line_insert(line, pos, len, key->ch); } 384 + 385 + static key_handler_t handlers[] = { 386 + [KEY_UP] = handle_up, [KEY_DOWN] = handle_down, 387 + [KEY_LEFT] = handle_left, [KEY_RIGHT] = handle_right, 388 + [KEY_BACKSPACE] = handle_backspace, [KEY_CHAR] = handle_char, 389 + }; 390 + 391 + static char* read_line_with_history(history_t *hist, struct js *js, const char *prompt) { 370 392 char *line = malloc(MAX_LINE_LENGTH); 371 - int pos = 0, len = 0; 372 - line[0] = '\0'; 393 + int pos = 0, len = 0; line[0] = '\0'; 373 394 374 395 TERM_INIT(); 375 396 376 - while (1) { 397 + do { 377 398 key_event_t key = read_key(); 378 - const char *hist_line; 399 + 400 + if (key.type == KEY_ENTER) { 401 + printf("\n"); fflush(stdout); 402 + TERM_RESTORE(); return line; 403 + } 404 + 405 + if (key.type == KEY_EOF) { 406 + printf("\n"); fflush(stdout); 407 + TERM_RESTORE(); 408 + free(line); return NULL; 409 + } 410 + 411 + if (handlers[key.type]) { 412 + handlers[key.type](line, &pos, &len, &key, hist, prompt); 413 + } 414 + } while (1); 415 + } 416 + 417 + static bool is_incomplete_input(const char *code, size_t len) { 418 + int paren_depth = 0; 419 + int bracket_depth = 0; 420 + int brace_depth = 0; 421 + 422 + bool in_string = false; 423 + bool in_template = false; 424 + char string_char = 0; 425 + bool escape_next = false; 426 + 427 + for (size_t i = 0; i < len; i++) { 428 + char c = code[i]; 429 + 430 + if (escape_next) { 431 + escape_next = false; 432 + continue; 433 + } 434 + 435 + if (c == '\\' && (in_string || in_template)) { 436 + escape_next = true; 437 + continue; 438 + } 439 + 440 + if (in_string) { 441 + if (c == string_char) in_string = false; 442 + continue; 443 + } 444 + 445 + if (in_template) { 446 + if (c == '`') { 447 + in_template = false; 448 + } else if (c == '$' && i + 1 < len && code[i + 1] == '{') { 449 + brace_depth++; i++; 450 + } 451 + continue; 452 + } 379 453 380 - switch (key.type) { 381 - case KEY_UP: 382 - if ((hist_line = history_prev(hist))) line_set(line, &pos, &len, hist_line); 454 + if (c == '/' && i + 1 < len && code[i + 1] == '/') { 455 + while (i < len && code[i] != '\n') i++; 456 + continue; 457 + } 458 + 459 + if (c == '/' && i + 1 < len && code[i + 1] == '*') { 460 + i += 2; 461 + while (i + 1 < len && !(code[i] == '*' && code[i + 1] == '/')) i++; 462 + if (i + 1 >= len) return true; 463 + i++; continue; 464 + } 465 + 466 + switch (c) { 467 + case '"': 468 + case '\'': 469 + in_string = true; 470 + string_char = c; 383 471 break; 384 - case KEY_DOWN: 385 - if ((hist_line = history_next(hist))) line_set(line, &pos, &len, hist_line); 472 + case '`': 473 + in_template = true; 386 474 break; 387 - case KEY_LEFT: cursor_move(&pos, len, -1); break; 388 - case KEY_RIGHT: cursor_move(&pos, len, 1); break; 389 - case KEY_BACKSPACE: line_backspace(line, &pos, &len); break; 390 - case KEY_CHAR: line_insert(line, &pos, &len, key.ch); break; 391 - case KEY_ENTER: 392 - printf("\n"); fflush(stdout); 393 - TERM_RESTORE(); 394 - line[len] = '\0'; 395 - return line; 396 - case KEY_EOF: 397 - printf("\n"); fflush(stdout); 398 - TERM_RESTORE(); 399 - free(line); 400 - return NULL; 401 - case KEY_NONE: break; 475 + case '(': paren_depth++; break; 476 + case ')': paren_depth--; break; 477 + case '[': bracket_depth++; break; 478 + case ']': bracket_depth--; break; 479 + case '{': brace_depth++; break; 480 + case '}': brace_depth--; break; 402 481 } 403 482 } 483 + 484 + return in_string || in_template || paren_depth > 0 || bracket_depth > 0 || brace_depth > 0; 404 485 } 405 486 406 487 void ant_repl_run() { ··· 431 512 js_set(js, js_glob(js), "__filename", js_mkstr(js, "[repl]", 6)); 432 513 433 514 int prev_ctrl_c_count = 0; 515 + char *multiline_buf = NULL; 516 + size_t multiline_len = 0; 517 + size_t multiline_cap = 0; 434 518 435 519 while (1) { 436 - printf("> "); 520 + const char *prompt = multiline_buf ? "| " : "> "; 521 + printf("%s", prompt); 437 522 fflush(stdout); 438 523 439 524 ctrl_c_pressed = 0; 440 - char *line = read_line_with_history(&history, js); 525 + char *line = read_line_with_history(&history, js, prompt); 441 526 442 527 if (ctrl_c_pressed > 0) { 528 + if (multiline_buf) { 529 + free(multiline_buf); 530 + multiline_buf = NULL; 531 + multiline_len = 0; 532 + multiline_cap = 0; 533 + prev_ctrl_c_count = 0; 534 + if (line) free(line); 535 + continue; 536 + } 443 537 if (prev_ctrl_c_count > 0) { 444 - printf("\n"); 445 538 if (line) free(line); 446 539 break; 447 540 } ··· 451 544 continue; 452 545 } 453 546 454 - if (line == NULL) break; 547 + if (line == NULL) { 548 + if (multiline_buf) { 549 + free(multiline_buf); 550 + multiline_buf = NULL; 551 + multiline_len = 0; 552 + multiline_cap = 0; 553 + continue; 554 + } 555 + break; 556 + } 557 + 455 558 prev_ctrl_c_count = 0; 456 559 size_t line_len = strlen(line); 457 560 458 - if (line_len == 0) { 561 + if (line_len == 0 && multiline_buf) { 562 + if (multiline_len + 1 >= multiline_cap) { 563 + multiline_cap = multiline_cap ? multiline_cap * 2 : 256; 564 + multiline_buf = realloc(multiline_buf, multiline_cap); 565 + } 566 + multiline_buf[multiline_len++] = '\n'; 567 + multiline_buf[multiline_len] = '\0'; 459 568 free(line); 460 569 continue; 461 570 } 462 571 463 - history_add(&history, line); 572 + if (line_len == 0) { 573 + free(line); 574 + continue; 575 + } 464 576 465 - if (line[0] == '.') { 577 + if (!multiline_buf && line[0] == '.') { 466 578 cmd_result_t result = execute_command(js, &history, line); 467 579 if (result == CMD_EXIT) { 468 580 free(line); ··· 475 587 continue; 476 588 } 477 589 478 - jsval_t eval_result = js_eval(js, line, line_len); 590 + size_t new_len = multiline_len + line_len + 1; 591 + if (new_len >= multiline_cap || !multiline_buf) { 592 + multiline_cap = multiline_cap ? multiline_cap * 2 : 256; 593 + if (multiline_cap < new_len + 1) multiline_cap = new_len + 1; 594 + multiline_buf = realloc(multiline_buf, multiline_cap); 595 + } 596 + 597 + if (multiline_len > 0) { 598 + multiline_buf[multiline_len++] = '\n'; 599 + } 600 + memcpy(multiline_buf + multiline_len, line, line_len); 601 + multiline_len += line_len; 602 + multiline_buf[multiline_len] = '\0'; 603 + 604 + free(line); 605 + 606 + if (is_incomplete_input(multiline_buf, multiline_len)) continue; 607 + history_add(&history, multiline_buf); 608 + 609 + jsval_t eval_result = js_eval(js, multiline_buf, multiline_len); 479 610 js_run_event_loop(js); 480 611 481 - if (js_type(eval_result) == JS_ERR) fprintf(stderr, "%s\n", js_str(js, eval_result)); else { 612 + if (js_type(eval_result) == JS_ERR) { 613 + fprintf(stderr, "%s\n", js_str(js, eval_result)); 614 + } else { 482 615 const char *str = js_str(js, eval_result); 483 616 print_value_colored(str, stdout); 484 617 printf("\n"); 485 618 } 486 619 487 - free(line); 620 + free(multiline_buf); 621 + multiline_buf = NULL; 622 + multiline_len = 0; 623 + multiline_cap = 0; 488 624 } 489 625 626 + if (multiline_buf) free(multiline_buf); 490 627 history_save(&history); 491 628 history_free(&history); 492 629 }