MIRROR: javascript for 🐜's, a tiny runtime with big ambitions
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <ctype.h>
5
6#include "ant.h"
7#include "repl.h"
8#include "readline.h"
9#include "reactor.h"
10#include "runtime.h"
11#include "internal.h"
12#include "descriptors.h"
13
14#include "silver/ast.h"
15#include "silver/engine.h"
16
17#include <crprintf.h>
18#include "modules/io.h"
19#include "highlight.h"
20#include "highlight/regex.h"
21
22typedef ant_history_t history_t;
23
24typedef enum {
25 CMD_OK,
26 CMD_EXIT,
27 CMD_NOT_FOUND
28} cmd_result_t;
29
30typedef struct {
31 const char *name;
32 const char *description;
33 bool has_arg;
34 cmd_result_t (*handler)(ant_t *js, history_t *history, const char *arg);
35} repl_command_t;
36
37typedef struct {
38 char *name;
39 size_t len;
40} repl_decl_name_t;
41
42typedef struct {
43 repl_decl_name_t *items;
44 size_t count;
45 size_t cap;
46} repl_decl_registry_t;
47
48typedef struct {
49 const char **names;
50 uint32_t *lens;
51 size_t count;
52 size_t cap;
53} repl_decl_pending_t;
54
55static repl_decl_registry_t *g_repl_decl_registry = NULL;
56
57static inline void repl_clear_exception_state(ant_t *js) {
58 js->thrown_exists = false;
59 js->thrown_value = js_mkundef();
60}
61
62static void repl_decl_registry_free(repl_decl_registry_t *reg) {
63 if (!reg) return;
64 for (size_t i = 0; i < reg->count; i++)
65 free(reg->items[i].name);
66 free(reg->items);
67 reg->items = NULL;
68 reg->count = 0;
69 reg->cap = 0;
70}
71
72static bool repl_decl_registry_contains(
73 const repl_decl_registry_t *reg,
74 const char *name, uint32_t len
75) {
76 if (!reg || !name) return false;
77 for (size_t i = 0; i < reg->count; i++) {
78 if (
79 reg->items[i].len == (size_t)len
80 && memcmp(reg->items[i].name, name, (size_t)len) == 0
81 ) return true;
82 }
83 return false;
84}
85
86static bool repl_decl_registry_add(
87 ant_t *js, repl_decl_registry_t *reg,
88 const char *name, uint32_t len
89) {
90 if (!reg || !name) return true;
91 if (repl_decl_registry_contains(reg, name, len)) return true;
92
93 if (reg->count >= reg->cap) {
94 size_t new_cap = reg->cap ? reg->cap * 2 : 32;
95 repl_decl_name_t *ni = realloc(reg->items, new_cap * sizeof(*ni));
96 if (!ni) {
97 js_mkerr_typed(js, JS_ERR_INTERNAL | JS_ERR_NO_STACK, "out of memory");
98 return false;
99 }
100 reg->items = ni;
101 reg->cap = new_cap;
102 }
103
104 char *copy = malloc((size_t)len + 1);
105 if (!copy) {
106 js_mkerr_typed(js, JS_ERR_INTERNAL | JS_ERR_NO_STACK, "out of memory");
107 return false;
108 }
109
110 memcpy(copy, name, (size_t)len);
111 copy[len] = '\0';
112 reg->items[reg->count++] = (repl_decl_name_t){ .name = copy, .len = (size_t)len };
113
114 return true;
115}
116
117static void repl_decl_pending_free(repl_decl_pending_t *p) {
118 if (!p) return;
119 free(p->names);
120 free(p->lens);
121 p->names = NULL;
122 p->lens = NULL;
123 p->count = 0;
124 p->cap = 0;
125}
126
127static bool repl_decl_pending_contains(
128 const repl_decl_pending_t *p,
129 const char *name, uint32_t len
130) {
131 if (!p || !name) return false;
132 for (size_t i = 0; i < p->count; i++)
133 if (p->lens[i] == len && memcmp(p->names[i], name, (size_t)len) == 0) return true;
134 return false;
135}
136
137static bool repl_decl_pending_push(
138 ant_t *js, repl_decl_pending_t *p,
139 const char *name, uint32_t len
140) {
141 if (!p || !name || len == 0) return true;
142 if (repl_decl_pending_contains(p, name, len)) return true;
143 if (p->count >= p->cap) {
144 size_t new_cap = p->cap ? p->cap * 2 : 16;
145 const char **nn = realloc(p->names, new_cap * sizeof(*nn));
146 if (!nn) {
147 js_mkerr_typed(js, JS_ERR_INTERNAL | JS_ERR_NO_STACK, "out of memory");
148 return false;
149 }
150 uint32_t *nl = realloc(p->lens, new_cap * sizeof(*nl));
151 if (!nl) {
152 p->names = nn;
153 js_mkerr_typed(js, JS_ERR_INTERNAL | JS_ERR_NO_STACK, "out of memory");
154 return false;
155 }
156 p->names = nn;
157 p->lens = nl;
158 p->cap = new_cap;
159 }
160 p->names[p->count] = name;
161 p->lens[p->count] = len;
162 p->count++;
163 return true;
164}
165
166static bool repl_collect_pattern_names(ant_t *js, sv_ast_t *pat, repl_decl_pending_t *p) {
167 if (!pat) return true;
168 switch (pat->type) {
169 case N_IDENT:
170 return repl_decl_pending_push(js, p, pat->str, pat->len);
171 case N_ASSIGN_PAT:
172 case N_ASSIGN:
173 return repl_collect_pattern_names(js, pat->left, p);
174 case N_REST:
175 case N_SPREAD:
176 return repl_collect_pattern_names(js, pat->right, p);
177 case N_ARRAY:
178 case N_ARRAY_PAT:
179 for (int i = 0; i < pat->args.count; i++) {
180 if (!repl_collect_pattern_names(js, pat->args.items[i], p)) return false;
181 }
182 return true;
183 case N_OBJECT:
184 case N_OBJECT_PAT:
185 for (int i = 0; i < pat->args.count; i++) {
186 sv_ast_t *prop = pat->args.items[i];
187 if (!prop) continue;
188 if (prop->type == N_PROPERTY) {
189 if (!repl_collect_pattern_names(js, prop->right, p)) return false;
190 } else if (prop->type == N_REST || prop->type == N_SPREAD) {
191 if (!repl_collect_pattern_names(js, prop->right, p)) return false;
192 }
193 }
194 return true;
195 default: return true;
196 }
197}
198
199static bool repl_collect_top_level_decls(ant_t *js, sv_ast_t *stmt, repl_decl_pending_t *p) {
200 if (!stmt) return true;
201 sv_ast_t *node = (stmt->type == N_EXPORT) ? stmt->left : stmt;
202 if (!node) return true;
203
204 if (node->type == N_VAR && node->var_kind != SV_VAR_VAR) {
205 for (int i = 0; i < node->args.count; i++) {
206 sv_ast_t *decl = node->args.items[i];
207 if (!decl || decl->type != N_VARDECL || !decl->left) continue;
208 if (!repl_collect_pattern_names(js, decl->left, p)) return false;
209 }
210 return true;
211 }
212
213 if (node->type == N_CLASS && node->str && node->len > 0)
214 return repl_decl_pending_push(js, p, node->str, node->len);
215
216 if (node->type == N_IMPORT_DECL) {
217 for (int i = 0; i < node->args.count; i++) {
218 sv_ast_t *spec = node->args.items[i];
219 if (!spec || spec->type != N_IMPORT_SPEC || !spec->right) continue;
220 if (spec->right->type != N_IDENT) continue;
221 if (!repl_decl_pending_push(js, p, spec->right->str, spec->right->len)) return false;
222 }
223 }
224
225 return true;
226}
227
228static bool repl_precheck_and_commit_lexicals(
229 ant_t *js, repl_decl_registry_t *reg,
230 const char *code, size_t len
231) {
232 if (!js || !reg || !code || len == 0) return true;
233
234 code_arena_mark_t mark = code_arena_mark();
235 repl_decl_pending_t pending = {0};
236 bool ok = true;
237
238 repl_clear_exception_state(js);
239 sv_ast_t *program = sv_parse(js, code, (ant_offset_t)len, false);
240
241 if (!program || js->thrown_exists) {
242 ok = true;
243 goto done;
244 }
245
246 for (int i = 0; i < program->args.count; i++) {
247 if (
248 !repl_collect_top_level_decls(
249 js, program->args.items[i], &pending)
250 ) { ok = false; goto done; }
251 }
252
253 for (size_t i = 0; i < pending.count; i++) {
254 if (repl_decl_registry_contains(reg, pending.names[i], pending.lens[i])) {
255 js_mkerr_typed(
256 js, JS_ERR_SYNTAX, "Identifier '%.*s' has already been declared",
257 (int)pending.lens[i], pending.names[i]
258 );
259 ok = false; goto done;
260 }
261 }
262
263 for (size_t i = 0; i < pending.count; i++) {
264 if (
265 !repl_decl_registry_add(
266 js, reg, pending.names[i], pending.lens[i])
267 ) { ok = false; goto done; }
268 }
269
270done:
271 code_arena_rewind(mark);
272 repl_decl_pending_free(&pending);
273
274 if (ok && js->thrown_exists)
275 repl_clear_exception_state(js);
276
277 return ok;
278}
279
280typedef enum {
281 REPL_PRINT_INTERACTIVE,
282 REPL_PRINT_LOAD,
283} repl_print_mode_t;
284
285static void repl_eval_chunk(
286 ant_t *js, repl_decl_registry_t *decl_registry,
287 const char *code, size_t len,
288 repl_print_mode_t print_mode
289) {
290 if (!repl_precheck_and_commit_lexicals(js, decl_registry, code, len)) {
291 if (js->thrown_exists) js_set(js, js_glob(js), "_error", js->thrown_value);
292 print_uncaught_throw(js);
293 return;
294 }
295
296 repl_clear_exception_state(js);
297 ant_value_t result = js_eval_bytecode_repl(js, code, len);
298 js_run_event_loop(js);
299
300 if (js->thrown_exists) {
301 js_set(js, js_glob(js), "_error", js->thrown_value);
302 if (print_uncaught_throw(js)) return;
303 }
304
305 if (print_mode == REPL_PRINT_INTERACTIVE) {
306 js_set(js, js_glob(js), "_", result);
307 print_repl_value(js, result, stdout);
308 return;
309 }
310
311 if (vtype(result) == T_ERR) fprintf(stderr, "%s\n", js_str(js, result));
312 else if (vtype(result) != T_UNDEF) printf("%s\n", js_str(js, result));
313}
314
315static cmd_result_t cmd_help(ant_t *js, history_t *history, const char *arg);
316static cmd_result_t cmd_exit(ant_t *js, history_t *history, const char *arg);
317static cmd_result_t cmd_load(ant_t *js, history_t *history, const char *arg);
318static cmd_result_t cmd_save(ant_t *js, history_t *history, const char *arg);
319static cmd_result_t cmd_stats(ant_t *js, history_t *history, const char *arg);
320static cmd_result_t cmd_copy(ant_t *js, history_t *history, const char *arg);
321static cmd_result_t cmd_clear(ant_t *js, history_t *history, const char *arg);
322static cmd_result_t cmd_history(ant_t *js, history_t *history, const char *arg);
323
324static const repl_command_t commands[] = {
325 { "help", "Show this help message", false, cmd_help },
326 { "exit", "Exit the REPL", false, cmd_exit },
327 { "clear", "Clear the screen", false, cmd_clear },
328 { "history", "Show command history", false, cmd_history },
329 { "load", "Load JS from a file into the REPL session", true, cmd_load },
330 { "save", "Save all evaluated commands in this REPL session to a file", true, cmd_save },
331 { "stats", "Show memory statistics", false, cmd_stats },
332 { "copy", "Evaluate expression and copy its value", true, cmd_copy },
333 { NULL, NULL, false, NULL }
334};
335
336static const char *repl_command_usage(const repl_command_t *cmd) {
337 if (!cmd || !cmd->name) return "";
338 if (strcmp(cmd->name, "copy") == 0) return ".copy [expr]";
339 if (strcmp(cmd->name, "load") == 0) return ".load <file>";
340 if (strcmp(cmd->name, "save") == 0) return ".save <file>";
341 if (strcmp(cmd->name, "history") == 0) return ".history";
342 if (strcmp(cmd->name, "clear") == 0) return ".clear";
343 if (strcmp(cmd->name, "stats") == 0) return ".stats";
344 if (strcmp(cmd->name, "exit") == 0) return ".exit";
345 if (strcmp(cmd->name, "help") == 0) return ".help";
346 return cmd->name;
347}
348
349static cmd_result_t cmd_help(ant_t *js, history_t *history, const char *arg) {
350 printf("\n%sREPL Commands:%s\n", C_BOLD, C_RESET);
351 for (const repl_command_t *cmd = commands; cmd->name; cmd++) {
352 const char *usage = repl_command_usage(cmd);
353 printf(" %s%-12s%s %s\n", C_CYAN, usage, C_RESET, cmd->description);
354 }
355 printf("\n%sKeybindings:%s\n", C_BOLD, C_RESET);
356 printf(" Ctrl+C Abort current expression (press twice to exit)\n");
357 printf(" Left/Right Move backward/forward one character\n");
358 printf(" Home/End Jump to start/end of line\n");
359 printf(" Up/Down Navigate history\n");
360 printf(" Backspace Delete character backward\n");
361 printf(" Delete Delete character under cursor\n");
362 printf(" Enter Submit input\n");
363 printf("\n%sSpecial Variables:%s\n", C_BOLD, C_RESET);
364 printf(" %s_%s Last expression result\n", C_CYAN, C_RESET);
365 printf(" %s_error%s Last error\n\n", C_CYAN, C_RESET);
366 return CMD_OK;
367}
368
369static cmd_result_t cmd_exit(ant_t *js, history_t *history, const char *arg) {
370 return CMD_EXIT;
371}
372
373static cmd_result_t cmd_load(ant_t *js, history_t *history, const char *arg) {
374 (void)history;
375 if (!arg || *arg == '\0') {
376 fprintf(stderr, "Usage: .load <filename>\n");
377 return CMD_OK;
378 }
379
380 FILE *fp = fopen(arg, "r");
381 if (fp == NULL) {
382 fprintf(stderr, "Failed to open file: %s\n", arg);
383 return CMD_OK;
384 }
385
386 fseek(fp, 0, SEEK_END);
387 long file_size = ftell(fp);
388 fseek(fp, 0, SEEK_SET);
389
390 char *file_buffer = malloc(file_size + 1);
391 if (file_buffer) {
392 size_t len = fread(file_buffer, 1, file_size, fp);
393 file_buffer[len] = '\0';
394 repl_eval_chunk(
395 js, g_repl_decl_registry,
396 file_buffer, len, REPL_PRINT_LOAD
397 );
398 free(file_buffer);
399 }
400 fclose(fp);
401 return CMD_OK;
402}
403
404static cmd_result_t cmd_save(ant_t *js, history_t *history, const char *arg) {
405 if (!arg || *arg == '\0') {
406 fprintf(stderr, "Usage: .save <filename>\n");
407 return CMD_OK;
408 }
409
410 FILE *fp = fopen(arg, "w");
411 if (fp == NULL) {
412 fprintf(stderr, "Failed to open file for writing: %s\n", arg);
413 return CMD_OK;
414 }
415
416 for (int i = 0; i < history->count; i++) {
417 fprintf(fp, "%s\n", history->lines[i]);
418 }
419 fclose(fp);
420 printf("Session saved to %s\n", arg);
421 return CMD_OK;
422}
423
424static cmd_result_t cmd_stats(ant_t *js, history_t *history, const char *arg) {
425 ant_value_t stats_fn = js_get(js, rt->ant_obj, "stats");
426 ant_value_t result = sv_vm_call(js->vm, js, stats_fn, js_mkundef(), NULL, 0, NULL, false);
427 console_emit(js, false, NULL, &result, 1);
428 return CMD_OK;
429}
430
431static cmd_result_t cmd_clear(ant_t *js, history_t *history, const char *arg) {
432 fputs("\033[2J\033[H", stdout);
433 fflush(stdout);
434 return CMD_OK;
435}
436
437static cmd_result_t cmd_history(ant_t *js, history_t *history, const char *arg) {
438 for (int i = 0; i < history->count; i++) {
439 printf("%4d %s\n", i + 1, history->lines[i]);
440 }
441 return CMD_OK;
442}
443
444#ifdef _WIN32
445static bool repl_copy_with_command(const char *data, size_t len) {
446 FILE *pipe = _popen("clip", "wb");
447 if (!pipe) return false;
448
449 size_t written = fwrite(data, 1, len, pipe);
450 int close_rc = _pclose(pipe);
451 return written == len && close_rc == 0;
452}
453#else
454static bool repl_copy_with_single_command(const char *cmd, const char *data, size_t len) {
455 FILE *pipe = popen(cmd, "w");
456 if (!pipe) return false;
457
458 size_t written = fwrite(data, 1, len, pipe);
459 int close_rc = pclose(pipe);
460 return written == len && close_rc == 0;
461}
462
463static bool repl_copy_with_command(const char *data, size_t len) {
464 static const char *cmds[] = {
465 "pbcopy",
466 "wl-copy",
467 "xclip -selection clipboard",
468 "xsel --clipboard --input",
469 };
470 for (size_t i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) {
471 if (repl_copy_with_single_command(cmds[i], data, len)) return true;
472 }
473 return false;
474}
475#endif
476
477static cmd_result_t cmd_copy(ant_t *js, history_t *history, const char *arg) {
478 (void)history;
479 if (!arg || *arg == '\0') {
480 fprintf(stderr, "Usage: .copy <expression>\n");
481 return CMD_OK;
482 }
483
484 repl_clear_exception_state(js);
485 ant_value_t result = js_eval_bytecode_repl(js, arg, strlen(arg));
486
487 js_run_event_loop(js);
488 if (js->thrown_exists) {
489 js_set(js, js_glob(js), "_error", js->thrown_value);
490 if (print_uncaught_throw(js)) return CMD_OK;
491 }
492
493 js_set(js, js_glob(js), "_", result);
494
495 char cbuf[512];
496 js_cstr_t cstr = js_to_cstr(js, result, cbuf, sizeof(cbuf));
497
498 bool copied_command = repl_copy_with_command(cstr.ptr, cstr.len);
499 if (cstr.needs_free) free((void *)cstr.ptr);
500
501 if (!copied_command) {
502 fprintf(stderr, "Failed to copy to clipboard (no clipboard command available).\n");
503 return CMD_OK;
504 }
505
506 printf("Copied to clipboard.\n");
507 return CMD_OK;
508}
509
510static cmd_result_t execute_command(ant_t *js, history_t *history, const char *line) {
511 const char *cmd_start = line + 1;
512
513 for (const repl_command_t *cmd = commands; cmd->name; cmd++) {
514 size_t n = strlen(cmd->name);
515 if (strncmp(cmd_start, cmd->name, n) != 0) continue;
516
517 char next = cmd_start[n];
518 if (cmd->has_arg && (next == ' ' || next == '\0')) {
519 const char *arg = cmd_start + n;
520 while (*arg == ' ') arg++;
521 return cmd->handler(js, history, arg);
522 }
523 if (!cmd->has_arg && next == '\0') return cmd->handler(js, history, NULL);
524 }
525
526 return CMD_NOT_FOUND;
527}
528
529typedef struct {
530 int paren, bracket, brace;
531 int *templates;
532 int template_count, template_cap;
533 char string_char;
534 bool in_string, escaped;
535 char last_code_char;
536} parse_state_t;
537
538static void push_template(parse_state_t *s) {
539 if (s->template_count >= s->template_cap) {
540 s->template_cap = s->template_cap ? s->template_cap * 2 : 8;
541 int *new_templates = realloc(s->templates, s->template_cap * sizeof(int));
542 if (!new_templates) { return; } s->templates = new_templates;
543 }
544 s->templates[s->template_count++] = s->brace;
545}
546
547static bool in_template_text(parse_state_t *s) {
548 return s->template_count > 0 && s->brace == s->templates[s->template_count - 1];
549}
550
551static bool is_incomplete_input(const char *code, size_t len) {
552 parse_state_t s = {0};
553
554 for (size_t i = 0; i < len; i++) {
555 char c = code[i];
556
557 if (s.escaped) { s.escaped = false; continue; }
558 if (c == '\\' && (s.in_string || s.template_count > 0)) { s.escaped = true; continue; }
559 if (s.in_string) { if (c == s.string_char) s.in_string = false; continue; }
560
561 if (in_template_text(&s)) {
562 if (c == '`') s.template_count--;
563 else if (c == '$' && i + 1 < len && code[i + 1] == '{') { s.brace++; i++; }
564 continue;
565 }
566
567 if (c == '/' && i + 1 < len) {
568 if (code[i + 1] == '/') { while (i < len && code[i] != '\n') i++; continue; }
569 if (code[i + 1] == '*') {
570 for (i += 2; i + 1 < len && !(code[i] == '*' && code[i + 1] == '/'); i++);
571 if (i + 1 >= len) { free(s.templates); return true; }
572 i++; continue;
573 }
574 if (js_regex_can_start(code, i)) {
575 size_t regex_end = 0;
576 if (!js_scan_regex_literal(code, len, i, ®ex_end)) { free(s.templates); return true; }
577 i = regex_end - 1;
578 continue;
579 }
580 }
581
582 switch (c) {
583 case '"': case '\'': s.in_string = true; s.string_char = c; break;
584 case '`': push_template(&s); break;
585 case '(': s.paren++; break; case ')': s.paren--; break;
586 case '[': s.bracket++; break; case ']': s.bracket--; break;
587 case '{': s.brace++; break; case '}': s.brace--; break;
588 }
589 if (!isspace((unsigned char)c)) s.last_code_char = c;
590 }
591
592 bool incomplete =
593 s.in_string || s.template_count > 0 ||
594 s.paren > 0 || s.bracket > 0 || s.brace > 0 ||
595 s.last_code_char == ',';
596
597 free(s.templates);
598 return incomplete;
599}
600
601void ant_repl_run() {
602 ant_t *js = rt->js;
603 ant_readline_install_signal_handler();
604
605 js_set_filename(js, "[repl]");
606 js_setup_import_meta(js, "[repl]");
607
608 crprintf(
609 "Welcome to <red+bold>Ant JavaScript</> v%s\n"
610 "Type <cyan>.copy [code]</cyan> to copy, <cyan>.help</cyan> for more information.\n\n",
611 ANT_VERSION
612 );
613
614 history_t history;
615 ant_history_init(&history, 512);
616 ant_history_load(&history);
617
618 repl_decl_registry_t decl_registry = {0};
619 g_repl_decl_registry = &decl_registry;
620
621 js_set(js, js_glob(js), "__dirname", js_mkstr(js, ".", 1));
622 js_set(js, js_glob(js), "__filename", js_mkstr(js, "[repl]", 6));
623
624 js_set(js, js_glob(js), "_", js_mkundef());
625 js_set(js, js_glob(js), "_error", js_mkundef());
626
627 js_set_descriptor(js, js_as_obj(js_glob(js)), "_", 1, JS_DESC_W | JS_DESC_C);
628 js_set_descriptor(js, js_as_obj(js_glob(js)), "_error", 6, JS_DESC_W | JS_DESC_C);
629
630 int prev_ctrl_c_count = 0;
631 char *multiline_buf = NULL;
632 size_t multiline_len = 0;
633 size_t multiline_cap = 0;
634
635 while (1) {
636 const char *prompt = multiline_buf ? "\x1b[2m|\x1b[0m " : "\x1b[2m❯\x1b[0m ";
637 highlight_state prefix_state = HL_STATE_INIT;
638 if (multiline_buf && multiline_len > 0) {
639 char scratch[8192];
640 ant_highlight_stateful(multiline_buf, multiline_len, scratch, sizeof(scratch), &prefix_state);
641 }
642
643 fputs(prompt, stdout);
644 fflush(stdout);
645
646 char *line = NULL;
647 ant_readline_result_t readline_status =
648 ant_readline(&history, prompt, prefix_state, &line);
649
650 if (readline_status == ANT_READLINE_INTERRUPT) {
651 if (multiline_buf) {
652 free(multiline_buf);
653 multiline_buf = NULL;
654 multiline_len = 0;
655 multiline_cap = 0;
656 prev_ctrl_c_count = 0;
657 if (line) free(line);
658 continue;
659 }
660 if (prev_ctrl_c_count > 0) {
661 if (line) free(line);
662 break;
663 }
664 printf("(To exit, press Ctrl+C again or type .exit)\n");
665 prev_ctrl_c_count++;
666 if (line) free(line);
667 continue;
668 }
669
670 if (readline_status == ANT_READLINE_EOF || line == NULL) {
671 if (multiline_buf) {
672 free(multiline_buf);
673 multiline_buf = NULL;
674 multiline_len = 0;
675 multiline_cap = 0;
676 continue;
677 }
678 break;
679 }
680
681 prev_ctrl_c_count = 0;
682 size_t line_len = strlen(line);
683
684 if (line_len == 0 && multiline_buf) {
685 if (multiline_len + 1 >= multiline_cap) {
686 multiline_cap = multiline_cap ? multiline_cap * 2 : 256;
687 multiline_buf = realloc(multiline_buf, multiline_cap);
688 }
689 multiline_buf[multiline_len++] = '\n';
690 multiline_buf[multiline_len] = '\0';
691 free(line);
692 continue;
693 }
694
695 if (line_len == 0) {
696 free(line);
697 continue;
698 }
699
700 if (!multiline_buf && line[0] == '.') {
701 cmd_result_t result = execute_command(js, &history, line);
702 if (result == CMD_EXIT) {
703 free(line);
704 break;
705 } else if (result == CMD_NOT_FOUND) {
706 printf("Unknown command: %s\n", line);
707 printf("Type \".help\" for more information.\n");
708 }
709 free(line);
710 continue;
711 }
712
713 size_t new_len = multiline_len + line_len + 1;
714 if (new_len >= multiline_cap || !multiline_buf) {
715 multiline_cap = multiline_cap ? multiline_cap * 2 : 256;
716 if (multiline_cap < new_len + 1) multiline_cap = new_len + 1;
717 multiline_buf = realloc(multiline_buf, multiline_cap);
718 }
719
720 if (multiline_len > 0) {
721 multiline_buf[multiline_len++] = '\n';
722 }
723 memcpy(multiline_buf + multiline_len, line, line_len);
724 multiline_len += line_len;
725 multiline_buf[multiline_len] = '\0';
726 free(line);
727
728 if (is_incomplete_input(multiline_buf, multiline_len)) continue;
729 ant_history_add(&history, multiline_buf);
730
731 repl_eval_chunk(
732 js, &decl_registry, multiline_buf,
733 multiline_len, REPL_PRINT_INTERACTIVE
734 );
735
736 free(multiline_buf);
737 multiline_buf = NULL;
738 multiline_len = 0;
739 multiline_cap = 0;
740 }
741
742 if (multiline_buf) free(multiline_buf);
743 ant_readline_shutdown();
744
745 repl_decl_registry_free(&decl_registry);
746 g_repl_decl_registry = NULL;
747
748 ant_history_save(&history);
749 ant_history_free(&history);
750}