Fun 0.41.5
The programming language that makes You have fun
Loading...
Searching...
No Matches
repl.c
Go to the documentation of this file.
1/*
2 * This file is part of the Fun programming language.
3 * https://fun-lang.xyz/
4 *
5 * Copyright 2025 Johannes Findeisen <you@hanez.org>
6 * Licensed under the terms of the Apache-2.0 license.
7 * https://opensource.org/license/apache-2-0
8 */
9
18
19#include "repl.h"
20#include "bytecode.h"
21#include "parser.h"
22#include "value.h"
23#include "vm.h"
24
25/* include mapping helper from parser_utils.c */
26extern int map_expanded_line_to_include_path(const char *path, int line, char *out_path, size_t out_path_cap, int *out_line);
27
28#include <ctype.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <time.h>
33
34#ifndef _WIN32
35#include <dirent.h>
36#include <limits.h>
37#include <sys/stat.h>
38#include <termios.h>
39#include <unistd.h>
40#endif
41
42#ifdef FUN_WITH_REPL
43
44#ifndef FUN_VERSION
45#define FUN_VERSION "0.0.0-dev"
46#endif
47
48/* --------- REPL history (in-memory + file integration) ---------- */
49enum { RL_HIST_MAX = 1000 };
50static char *rl_hist[RL_HIST_MAX];
51static int rl_count = 0;
52
53/* last history entry (or NULL) */
57static const char *rl_hist_last(void) {
58 if (rl_count <= 0) return NULL;
59 return rl_hist[rl_count - 1];
60}
61
62/* add one line to in-memory history (without trailing newline), dedup consecutive */
67static void rl_hist_add(const char *s) {
68 if (!s) return;
69 size_t n = strlen(s);
70 /* strip single trailing newline if present */
71 while (n > 0 && (s[n - 1] == '\n' || s[n - 1] == '\r'))
72 n--;
73 if (n == 0) return;
74 /* dedupe consecutive identical */
75 const char *last = rl_hist_last();
76 if (last && strncmp(last, s, n) == 0 && last[n] == '\0') return;
77
78 /* allocate and copy */
79 char *cpy = (char *)malloc(n + 1);
80 if (!cpy) return;
81 memcpy(cpy, s, n);
82 cpy[n] = '\0';
83
84 if (rl_count == RL_HIST_MAX) {
85 free(rl_hist[0]);
86 memmove(&rl_hist[0], &rl_hist[1], sizeof(rl_hist[0]) * (RL_HIST_MAX - 1));
87 rl_count--;
88 }
89 rl_hist[rl_count++] = cpy;
90}
91
92/* preload history from file (one line per entry) */
96static void rl_hist_load_file(const char *path) {
97 if (!path) return;
98 FILE *f = fopen(path, "r");
99 if (!f) return;
100 char line[4096];
101 while (fgets(line, sizeof(line), f)) {
102 rl_hist_add(line);
103 }
104 fclose(f);
105}
106
107#ifndef _WIN32
108static struct termios g_orig_tios;
109static int g_raw_enabled = 0;
110
114static void repl_disable_raw(void) {
115 if (g_raw_enabled) {
116 tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_orig_tios);
117 g_raw_enabled = 0;
118 }
119}
124static int repl_enable_raw(void) {
125 if (!isatty(STDIN_FILENO)) return 0;
126 if (g_raw_enabled) return 1;
127 if (tcgetattr(STDIN_FILENO, &g_orig_tios) == -1) return 0;
128 struct termios raw = g_orig_tios;
129 raw.c_lflag &= ~(ICANON | ECHO);
130 raw.c_cc[VMIN] = 1;
131 raw.c_cc[VTIME] = 0;
132 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) return 0;
133 g_raw_enabled = 1;
134 return 1;
135}
136
137/* return 1 if path is a directory, else 0 */
141static int is_dir_path(const char *path) {
142 struct stat st;
143 if (stat(path, &st) != 0) return 0;
144 return S_ISDIR(st.st_mode);
145}
146
147/* Compute longest common prefix of a set of strings (starting from offset base_len) */
151static size_t lcp_suffix(const char **names, int count, size_t base_len) {
152 if (count <= 0) return base_len;
153 size_t lcp = (size_t)-1;
154 for (int i = 0; i < count; ++i) {
155 size_t nlen = strlen(names[i]);
156 size_t cur = 0;
157 /* compare suffix part starting at base_len */
158 while (base_len + cur < nlen) {
159 char c = names[i][base_len + cur];
160 int ok = 1;
161 for (int j = 0; j < count; ++j) {
162 size_t jlen = strlen(names[j]);
163 if (base_len + cur >= jlen || names[j][base_len + cur] != c) {
164 ok = 0;
165 break;
166 }
167 }
168 if (!ok) break;
169 cur++;
170 }
171 if (lcp == (size_t)-1 || cur < lcp) lcp = cur;
172 }
173 return base_len + (lcp == (size_t)-1 ? 0 : lcp);
174}
175
176/* Word-jump helpers (used by Ctrl+Left/Right in the REPL editor).
177 * Words are runs of non-space characters; separators are spaces.
178 */
182static void rl_word_left(const char *out, size_t len, size_t *pos) {
183 (void)len;
184 if (!out || !pos) return;
185 if (*pos == 0) return;
186 while (*pos > 0 && out[*pos - 1] == ' ')
187 (*pos)--;
188 while (*pos > 0 && out[*pos - 1] != ' ')
189 (*pos)--;
190}
194static void rl_word_right(const char *out, size_t len, size_t *pos) {
195 if (!out || !pos) return;
196 if (*pos >= len) return;
197 while (*pos < len && out[*pos] != ' ')
198 (*pos)++;
199 while (*pos < len && out[*pos] == ' ')
200 (*pos)++;
201}
202
203/* Expand file path for path-taking REPL commands (e.g., :load, :run) in-place; returns 1 if buffer changed (redraw) */
208static int complete_load_path(char *buf, size_t *len_io) {
209 size_t len = *len_io;
210 if (len < 3) return 0; /* minimally ":x" */
211 const char *p = buf;
212 while (*p == ' ')
213 p++;
214 if (*p != ':') return 0;
215 p++;
216 /* Parse command token (letters only) */
217 const char *cmd_start = p;
218 while (*p && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')))
219 p++;
220 size_t cmd_len = (size_t)(p - cmd_start);
221 if (cmd_len == 0) return 0;
222 /* Accept both full and short aliases for :load and :run */
223 int is_supported = 0;
224 if ((cmd_len == 4 && strncmp(cmd_start, "load", 4) == 0) ||
225 (cmd_len == 2 && strncmp(cmd_start, "lo", 2) == 0) ||
226 (cmd_len == 3 && strncmp(cmd_start, "run", 3) == 0) ||
227 (cmd_len == 2 && strncmp(cmd_start, "ru", 2) == 0)) {
228 is_supported = 1;
229 }
230 if (!is_supported) return 0;
231 while (*p == ' ' || *p == '\t')
232 p++;
233 size_t arg_off = (size_t)(p - buf);
234 if (arg_off > len) return 0;
235
236 char prefix[PATH_MAX];
237 size_t plen = 0;
238 if (len - arg_off >= sizeof(prefix))
239 plen = sizeof(prefix) - 1;
240 else
241 plen = len - arg_off;
242 memcpy(prefix, buf + arg_off, plen);
243 prefix[plen] = '\0';
244
245 char expanded[PATH_MAX];
246 if (prefix[0] == '~') {
247 const char *home = getenv("HOME");
248 if (!home) home = getenv("USERPROFILE");
249 if (home)
250 snprintf(expanded, sizeof(expanded), "%s%s", home, prefix + 1);
251 else
252 snprintf(expanded, sizeof(expanded), "%s", prefix);
253 } else {
254 snprintf(expanded, sizeof(expanded), "%s", prefix);
255 }
256
257 char dirpart[PATH_MAX], base[PATH_MAX];
258 const char *slash = strrchr(expanded, '/');
259 if (slash) {
260 size_t dlen = (size_t)(slash - expanded);
261 if (dlen == 0) {
262 strcpy(dirpart, "/");
263 } else {
264 memcpy(dirpart, expanded, dlen);
265 dirpart[dlen] = '\0';
266 }
267 snprintf(base, sizeof(base), "%s", slash + 1);
268 } else {
269 strcpy(dirpart, ".");
270 snprintf(base, sizeof(base), "%s", expanded);
271 }
272
273 DIR *dp = opendir(dirpart);
274 if (!dp) {
275 fputc('\a', stdout);
276 fflush(stdout);
277 return 0;
278 }
279
280 const char *names[1024];
281 char storage[1024][NAME_MAX + 1];
282 int count = 0;
283 struct dirent *de;
284 size_t blen = strlen(base);
285 while ((de = readdir(dp)) != NULL) {
286 if (blen == 0 || strncmp(de->d_name, base, blen) == 0) {
287 if (count < (int)(sizeof(names) / sizeof(names[0]))) {
288 snprintf(storage[count], sizeof(storage[count]), "%s", de->d_name);
289 names[count] = storage[count];
290 count++;
291 }
292 }
293 }
294 closedir(dp);
295
296 if (count == 0) {
297 fputc('\a', stdout);
298 fflush(stdout);
299 return 0;
300 }
301
302 size_t new_pref_len = lcp_suffix(names, count, blen);
303 int changed = 0;
304
305 char completed[PATH_MAX];
306 if (slash) {
307 char head[PATH_MAX];
308 memcpy(head, expanded, (size_t)(slash - expanded + 1));
309 head[slash - expanded + 1] = '\0';
310 strncpy(completed, head, sizeof(completed));
311 } else {
312 snprintf(completed, sizeof(completed), "%s", "");
313 }
314
315 strncat(completed, base, sizeof(completed) - strlen(completed) - 1);
316 if (new_pref_len > blen) {
317 strncat(completed, names[0] + blen, (new_pref_len - blen));
318 changed = 1;
319 } else if (count == 1) {
320 size_t extra = strlen(names[0]) - blen;
321 strncat(completed, names[0] + blen, extra);
322 changed = 1;
323 }
324
325 if (count == 1) {
326 char test[PATH_MAX];
327 if (slash)
328 snprintf(test, sizeof(test), "%.*s/%s", (int)(slash - expanded), expanded, names[0]);
329 else
330 snprintf(test, sizeof(test), "%s", names[0]);
331 if (is_dir_path(test)) {
332 strncat(completed, "/", sizeof(completed) - strlen(completed) - 1);
333 }
334 }
335
336 if (!changed && count > 1) {
337 putchar('\n');
338 for (int i = 0; i < count; ++i) {
339 fputs(names[i], stdout);
340 fputc(((i + 1) % 6 == 0) ? '\n' : '\t', stdout);
341 }
342 if (count % 6 != 0) putchar('\n');
343 fflush(stdout);
344 return 0;
345 }
346
347 size_t comp_len = strlen(completed);
348 if (arg_off + comp_len >= PATH_MAX - 1) comp_len = PATH_MAX - 2 - arg_off;
349 memcpy(buf + arg_off, completed, comp_len);
350 *len_io = arg_off + comp_len;
351 buf[*len_io] = '\0';
352 return 1;
353}
354
355/* ---------- Stdlib symbol completion ---------- */
356static char **g_std_syms = NULL;
357static int g_std_syms_count = 0;
358static int g_std_syms_cap = 0;
359
363static int is_ident_start(int c) {
364 return (c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
365}
369static int is_ident_char(int c) {
370 return is_ident_start(c) || (c >= '0' && c <= '9');
371}
372
376static void std_syms_add(const char *name) {
377 if (!name || !*name) return;
378 /* dedupe */
379 for (int i = 0; i < g_std_syms_count; ++i) {
380 if (strcmp(g_std_syms[i], name) == 0) return;
381 }
382 if (g_std_syms_count == g_std_syms_cap) {
383 int ncap = g_std_syms_cap == 0 ? 256 : g_std_syms_cap * 2;
384 char **nn = (char **)realloc(g_std_syms, (size_t)ncap * sizeof(char *));
385 if (!nn) return;
386 g_std_syms = nn;
387 g_std_syms_cap = ncap;
388 }
389 size_t n = strlen(name);
390 char *cpy = (char *)malloc(n + 1);
391 if (!cpy) return;
392 memcpy(cpy, name, n + 1);
393 g_std_syms[g_std_syms_count++] = cpy;
394}
395
399static void scan_symbols_from_file(const char *path) {
400 FILE *f = fopen(path, "r");
401 if (!f) return;
402 char line[4096];
403 while (fgets(line, sizeof(line), f)) {
404 const char *p = line;
405 while (*p == ' ' || *p == '\t')
406 p++;
407 /* Patterns: fun NAME( ; class NAME ; const NAME ; var NAME */
408 const char *kw = NULL;
409 if (strncmp(p, "fun ", 4) == 0) {
410 kw = "fun";
411 p += 4;
412 } else if (strncmp(p, "class ", 6) == 0) {
413 kw = "class";
414 p += 6;
415 } else if (strncmp(p, "const ", 6) == 0) {
416 kw = "const";
417 p += 6;
418 } else if (strncmp(p, "var ", 4) == 0) {
419 kw = "var";
420 p += 4;
421 }
422 if (!kw) continue;
423 while (*p == ' ' || *p == '\t')
424 p++;
425 if (!is_ident_start((unsigned char)*p)) continue;
426 char name[256];
427 size_t ni = 0;
428 while (is_ident_char((unsigned char)*p) && ni + 1 < sizeof(name)) {
429 name[ni++] = *p++;
430 }
431 name[ni] = '\0';
432 if (ni > 0) std_syms_add(name);
433 }
434 fclose(f);
435}
436
440static void scan_dir_recursive(const char *dir) {
441 DIR *dp = opendir(dir);
442 if (!dp) return;
443 struct dirent *de;
444 char path[PATH_MAX];
445 while ((de = readdir(dp)) != NULL) {
446 const char *n = de->d_name;
447 if (strcmp(n, ".") == 0 || strcmp(n, "..") == 0) continue;
448 snprintf(path, sizeof(path), "%s/%s", dir, n);
449 struct stat st;
450 if (stat(path, &st) != 0) continue;
451 if (S_ISDIR(st.st_mode)) {
452 scan_dir_recursive(path);
453 } else {
454 size_t L = strlen(n);
455 if (L >= 4 && strcmp(n + (L - 4), ".fun") == 0) {
456 scan_symbols_from_file(path);
457 }
458 }
459 }
460 closedir(dp);
461}
462
466static void load_stdlib_symbols(const char *libdir) {
467 if (!libdir || !*libdir) return;
468 scan_dir_recursive(libdir);
469}
470
474static void free_stdlib_symbols(void) {
475 for (int i = 0; i < g_std_syms_count; ++i)
476 free(g_std_syms[i]);
477 free(g_std_syms);
478 g_std_syms = NULL;
479 g_std_syms_count = g_std_syms_cap = 0;
480}
481
482/* Complete the trailing identifier in 'buf' using stdlib symbols.
483 Returns 1 if buffer changed, 2 if a menu was printed, 0 otherwise. */
488static int complete_stdlib_ident(char *buf, size_t *len_io, size_t *pos_io, size_t cap) {
489 size_t len = *len_io;
490 size_t pos = *pos_io;
491 if (pos != len) return 0; /* complete only at end */
492 if (len == 0) return 0;
493
494 /* find start of identifier */
495 size_t start = len;
496 while (start > 0 && is_ident_char((unsigned char)buf[start - 1]))
497 start--;
498 if (start == len) return 0;
499
500 const char *prefix = buf + start;
501 size_t plen = len - start;
502
503 /* collect matches */
504 const char *matches[1024];
505 int mcount = 0;
506 for (int i = 0; i < g_std_syms_count && mcount < (int)(sizeof(matches) / sizeof(matches[0])); ++i) {
507 if (strncmp(g_std_syms[i], prefix, plen) == 0) {
508 matches[mcount++] = g_std_syms[i];
509 }
510 }
511 if (mcount == 0) {
512 fputc('\a', stdout);
513 fflush(stdout);
514 return 0;
515 }
516
517 /* compute longest common extension */
518 size_t lcp = (size_t)-1;
519 for (int i = 0; i < mcount; ++i) {
520 size_t nlen = strlen(matches[i]);
521 size_t cur = 0;
522 while (plen + cur < nlen) {
523 char c = matches[i][plen + cur];
524 int ok = 1;
525 for (int j = 0; j < mcount; ++j) {
526 size_t jlen = strlen(matches[j]);
527 if (plen + cur >= jlen || matches[j][plen + cur] != c) {
528 ok = 0;
529 break;
530 }
531 }
532 if (!ok) break;
533 cur++;
534 }
535 if (lcp == (size_t)-1 || cur < lcp) lcp = cur;
536 }
537 size_t new_pref_len = plen + (lcp == (size_t)-1 ? 0 : lcp);
538
539 /* build completion text */
540 char comp[512];
541 size_t write_len = 0;
542 if (mcount == 1) {
543 snprintf(comp, sizeof(comp), "%s", matches[0]);
544 write_len = strlen(comp);
545 } else if (new_pref_len > plen) {
546 strncpy(comp, prefix, plen);
547 comp[plen] = '\0';
548 strncat(comp, matches[0] + plen, new_pref_len - plen);
549 write_len = strlen(comp);
550 } else {
551 /* list candidates */
552 putchar('\n');
553 for (int i = 0; i < mcount; ++i) {
554 fputs(matches[i], stdout);
555 fputc(((i + 1) % 6 == 0) ? '\n' : '\t', stdout);
556 }
557 if (mcount % 6 != 0) putchar('\n');
558 fflush(stdout);
559 return 2;
560 }
561
562 /* replace tail from 'start' with comp */
563 if (start + write_len >= cap) write_len = cap - 1 - start;
564 memmove(buf + start, comp, write_len);
565 *len_io = start + write_len;
566 buf[*len_io] = '\0';
567 *pos_io = *len_io;
568 return 1;
569}
570
571/* Read one line with prompt, handling backspace, Up/Down history and :load path completion (now with multi-line editing via Ctrl+O) */
579static int read_line_edit(char *out, size_t out_cap, const char *prompt) {
580#ifdef _WIN32
581 if (prompt) fputs(prompt, stdout), fflush(stdout);
582 if (!fgets(out, (int)out_cap, stdin)) return 0;
583 return 1;
584#else
585 if (prompt) fputs(prompt, stdout), fflush(stdout);
586 if (!repl_enable_raw()) {
587 if (!fgets(out, (int)out_cap, stdin)) return 0;
588 return 1;
589 }
590
591 size_t len = 0;
592 size_t pos = 0;
593 int hist_pos = rl_count;
594 char saved_current[4096];
595 int has_saved = 0;
596
597 /* How many terminal rows were drawn in the previous repaint (prompt + lines in 'out').
598 We assume no automatic wrapping (only explicit '\n'). */
599 static int prev_rows = 1;
600
601#define RL_REDRAW() \
602 do { \
603 /* Count explicit newlines to estimate rows to draw (prompt line + content lines) */ \
604 int rows = 1; \
605 int has_nl = 0; \
606 for (size_t __i = 0; __i < len; ++__i) { \
607 if (out[__i] == '\n') { \
608 rows++; \
609 has_nl = 1; \
610 } \
611 } \
612 /* Move cursor to the start (top) of the previous render block */ \
613 if (prev_rows > 1) { \
614 fprintf(stdout, "\x1b[%dA", prev_rows - 1); \
615 } \
616 fputc('\r', stdout); \
617 /* Repaint prompt + content */ \
618 if (prompt) fputs(prompt, stdout); \
619 fwrite(out, 1, len, stdout); \
620 /* Clear everything below the current cursor (removes remnants of older, longer renders) */ \
621 fputs("\x1b[J", stdout); \
622 /* Cursor policy: keep at end for multi-line; for single-line respect pos */ \
623 if (!has_nl && pos < len) { \
624 size_t back = (size_t)(len - pos); \
625 if (back > 0) fprintf(stdout, "\x1b[%zuD", back); \
626 } else { \
627 pos = len; \
628 } \
629 prev_rows = rows; \
630 fflush(stdout); \
631 } while (0)
632
633 for (;;) {
634 int ch = getchar();
635 if (ch == EOF) {
636 repl_disable_raw();
637 return 0;
638 }
639
640 if (ch == '\r' || ch == '\n') {
641 fputc('\n', stdout);
642 if (len + 1 < out_cap) {
643 out[len++] = '\n';
644 out[len] = '\0';
645 } else {
646 out[len] = '\0';
647 }
648 rl_hist_add(out);
649 repl_disable_raw();
650 return 1;
651 } else if (ch == 27) {
652 int c1 = getchar();
653 if (c1 == '[') {
654 char params[16];
655 int pi = 0;
656 int final = 0;
657 for (;;) {
658 int cx = getchar();
659 if (cx == EOF) break;
660 if ((cx >= 'A' && cx <= 'Z') || (cx >= 'a' && cx <= 'z') || cx == '~') {
661 final = cx;
662 break;
663 }
664 if (pi + 1 < (int)sizeof(params)) {
665 params[pi++] = (char)cx;
666 params[pi] = '\0';
667 }
668 }
669 int ctrl = 0;
670 if (pi > 0) {
671 if (strstr(params, ";5") != NULL || strcmp(params, "5") == 0 || strstr(params, "1;5") != NULL) {
672 ctrl = 1;
673 }
674 }
675
676 /* disallow fine-grained horizontal movement in multi-line mode (cursor stays at end) */
677 int has_nl = 0;
678 for (size_t __i = 0; __i < len; ++__i) {
679 if (out[__i] == '\n') {
680 has_nl = 1;
681 break;
682 }
683 }
684
685 if (final == 'A') {
686 if (!has_saved) {
687 size_t sl = len < sizeof(saved_current) - 1 ? len : sizeof(saved_current) - 1;
688 memcpy(saved_current, out, sl);
689 saved_current[sl] = '\0';
690 has_saved = 1;
691 }
692 if (hist_pos > 0) {
693 hist_pos--;
694 const char *h = rl_hist[hist_pos];
695 size_t hl = strlen(h);
696 if (hl >= out_cap) hl = out_cap - 1;
697 memcpy(out, h, hl);
698 out[hl] = '\0';
699 len = hl;
700 pos = len;
701 RL_REDRAW();
702 } else {
703 fputc('\a', stdout);
704 fflush(stdout);
705 }
706 } else if (final == 'B') {
707 if (hist_pos < rl_count) {
708 hist_pos++;
709 if (hist_pos == rl_count) {
710 if (has_saved) {
711 size_t hl = strlen(saved_current);
712 if (hl >= out_cap) hl = out_cap - 1;
713 memcpy(out, saved_current, hl);
714 out[hl] = '\0';
715 len = hl;
716 } else {
717 len = 0;
718 out[0] = '\0';
719 }
720 } else {
721 const char *h = rl_hist[hist_pos];
722 size_t hl = strlen(h);
723 if (hl >= out_cap) hl = out_cap - 1;
724 memcpy(out, h, hl);
725 out[hl] = '\0';
726 len = hl;
727 }
728 pos = len;
729 RL_REDRAW();
730 } else {
731 fputc('\a', stdout);
732 fflush(stdout);
733 }
734 } else if (final == 'C') {
735 if (has_nl) {
736 fputc('\a', stdout);
737 fflush(stdout);
738 pos = len;
739 RL_REDRAW();
740 } else if (ctrl) {
741 size_t old = pos;
742 rl_word_right(out, len, &pos);
743 if (pos != old)
744 RL_REDRAW();
745 else {
746 fputc('\a', stdout);
747 fflush(stdout);
748 }
749 } else {
750 if (pos < len) {
751 pos++;
752 fputs("\x1b[C", stdout);
753 fflush(stdout);
754 } else {
755 fputc('\a', stdout);
756 fflush(stdout);
757 }
758 }
759 } else if (final == 'D') {
760 if (has_nl) {
761 fputc('\a', stdout);
762 fflush(stdout);
763 pos = len;
764 RL_REDRAW();
765 } else if (ctrl) {
766 size_t old = pos;
767 rl_word_left(out, len, &pos);
768 if (pos != old)
769 RL_REDRAW();
770 else {
771 fputc('\a', stdout);
772 fflush(stdout);
773 }
774 } else {
775 if (pos > 0) {
776 pos--;
777 fputs("\x1b[D", stdout);
778 fflush(stdout);
779 } else {
780 fputc('\a', stdout);
781 fflush(stdout);
782 }
783 }
784 } else {
785 /* ignore other CSI sequences */
786 }
787 }
788 } else if (ch == 127 || ch == 8) {
789 if (pos > 0) {
790 memmove(out + pos - 1, out + pos, len - pos);
791 len--;
792 pos--;
793 out[len] = '\0';
794 RL_REDRAW();
795 } else {
796 fputc('\a', stdout);
797 fflush(stdout);
798 }
799 } else if (ch == '\t') {
800 if (pos != len) {
801 fputc('\a', stdout);
802 fflush(stdout);
803 } else {
804 out[len] = '\0';
805 size_t newlen = len;
806 int changed = complete_load_path(out, &newlen);
807 if (changed) {
808 len = newlen;
809 pos = len;
810 } else {
811 /* try stdlib symbol completion */
812 int res = complete_stdlib_ident(out, &newlen, &pos, out_cap);
813 if (res == 1) {
814 len = newlen;
815 } else if (res == 2) {
816 /* menu printed — do not modify buffer, just redraw prompt+line */
817 } else {
818 fputc('\a', stdout);
819 fflush(stdout);
820 }
821 }
822 RL_REDRAW();
823 }
824 } else if (ch == 15) { /* Ctrl+O -> insert newline at cursor (multi-line editing) */
825 if (len + 1 < out_cap) {
826 memmove(out + pos + 1, out + pos, len - pos);
827 out[pos] = '\n';
828 len++;
829 pos++;
830 out[len] = '\0';
831 hist_pos = rl_count;
832 RL_REDRAW();
833 } else {
834 fputc('\a', stdout);
835 fflush(stdout);
836 }
837 } else if (ch >= 32 && ch <= 126) {
838 if (len + 1 < out_cap) {
839 memmove(out + pos + 1, out + pos, len - pos);
840 out[pos] = (char)ch;
841 len++;
842 pos++;
843 out[len] = '\0';
844 hist_pos = rl_count;
845 RL_REDRAW();
846 } else {
847 fputc('\a', stdout);
848 fflush(stdout);
849 }
850 } else {
851 /* ignore other control characters */
852 }
853 }
854#endif
855}
856#endif /* !_WIN32 */
857
858/* ---------- Small utilities ---------- */
859
860static int is_blank_line(const char *s) {
864 for (const char *p = s; *p; ++p) {
865 if (*p != ' ' && *p != '\t' && *p != '\r' && *p != '\n') return 0;
866 }
867 return 1;
868}
869
873static const char *lstrip(const char *s) {
874 while (*s == ' ' || *s == '\t')
875 s++;
876 return s;
877}
878
882static int ends_with_opener(const char *line) {
883 size_t n = strlen(line);
884 while (n > 0 && (line[n - 1] == ' ' || line[n - 1] == '\t' || line[n - 1] == '\r' || line[n - 1] == '\n'))
885 n--;
886 if (n == 0) return 0;
887 char c = line[n - 1];
888 if (c == '+' || c == '-' || c == '*' || c == '/' || c == '%' ||
889 c == '<' || c == '>' || c == '=' || c == '!' || c == '&' || c == '|' || c == ',') {
890 return 1;
891 }
892 return 0;
893}
894
895/* Compute how many indentation levels (2 spaces per level) are still open. */
899static int compute_open_indent_blocks(const char *buf) {
900 int in_block_comment = 0;
901 int open = 0;
902 int have_baseline = 0;
903 int cur = 0;
904
905 const char *p = buf;
906 while (*p) {
907 const char *line = p;
908 while (*p && *p != '\n')
909 p++;
910 const char *line_end = p;
911 if (*p == '\n') p++;
912
913 if (in_block_comment) {
914 const char *q = line;
915 while (q < line_end) {
916 if (q + 1 < line_end && q[0] == '*' && q[1] == '/') {
917 in_block_comment = 0;
918 q += 2;
919 break;
920 }
921 q++;
922 }
923 if (in_block_comment) continue;
924 }
925
926 const char *s = line;
927 int spaces = 0;
928 while (s < line_end && *s == ' ') {
929 spaces++;
930 s++;
931 }
932 while (s < line_end && *s == '\t') {
933 s++;
934 }
935
936 const char *t = s;
937 if (t >= line_end) continue;
938
939 if ((t + 1) <= line_end && t[0] == '/' && (t + 1 < line_end && (t[1] == '/' || t[1] == '*'))) {
940 if (t[1] == '/') {
941 continue;
942 } else if (t[1] == '*') {
943 in_block_comment = 1;
944 continue;
945 }
946 }
947
948 int lvl = spaces / 2;
949 if (!have_baseline) {
950 cur = lvl;
951 have_baseline = 1;
952 continue;
953 }
954 if (lvl > cur) {
955 open += (lvl - cur);
956 } else if (lvl < cur) {
957 int dec = (cur - lvl);
958 if (dec > open)
959 open = 0;
960 else
961 open -= dec;
962 }
963 cur = lvl;
964 }
965 return open;
966}
967
968/* Detect if current buffer looks incomplete. */
973static int buffer_looks_incomplete(const char *buf) {
974 int in_single = 0, in_double = 0, escape = 0;
975 int in_block_comment = 0, in_line_comment = 0;
976 int paren = 0;
977
978 const char *p = buf;
979 const char *last_sig_line = NULL;
980
981 while (*p) {
982 char c = *p;
983
984 if (in_line_comment) {
985 if (c == '\n') in_line_comment = 0;
986 p++;
987 continue;
988 }
989 if (in_block_comment) {
990 if (c == '*' && p[1] == '/') {
991 in_block_comment = 0;
992 p += 2;
993 continue;
994 }
995 p++;
996 continue;
997 }
998
999 if (!in_single && !in_double) {
1000 if (c == '/' && p[1] == '/') {
1001 in_line_comment = 1;
1002 p += 2;
1003 continue;
1004 }
1005 if (c == '/' && p[1] == '*') {
1006 in_block_comment = 1;
1007 p += 2;
1008 continue;
1009 }
1010 }
1011
1012 if (in_single) {
1013 if (!escape && c == '\\') {
1014 escape = 1;
1015 p++;
1016 continue;
1017 }
1018 if (!escape && c == '\'') {
1019 in_single = 0;
1020 p++;
1021 continue;
1022 }
1023 escape = 0;
1024 p++;
1025 continue;
1026 } else if (in_double) {
1027 if (!escape && c == '\\') {
1028 escape = 1;
1029 p++;
1030 continue;
1031 }
1032 if (!escape && c == '"') {
1033 in_double = 0;
1034 p++;
1035 continue;
1036 }
1037 escape = 0;
1038 p++;
1039 continue;
1040 } else {
1041 if (c == '\'') {
1042 in_single = 1;
1043 p++;
1044 continue;
1045 }
1046 if (c == '"') {
1047 in_double = 1;
1048 p++;
1049 continue;
1050 }
1051 if (c == '(') {
1052 paren++;
1053 p++;
1054 continue;
1055 }
1056 if (c == ')') {
1057 if (paren > 0) paren--;
1058 p++;
1059 continue;
1060 }
1061 }
1062
1063 if (c == '\n') {
1064 const char *q = p + 1;
1065 while (*q == ' ' || *q == '\t')
1066 q++;
1067 if (*q && *q != '\n') last_sig_line = q;
1068 }
1069 p++;
1070 }
1071
1072 if (!last_sig_line) {
1073 const char *q = buf;
1074 const char *candidate = NULL;
1075 while (*q) {
1076 const char *line_start = q;
1077 while (*q && *q != '\n')
1078 q++;
1079 const char *t = line_start;
1080 while (*t == ' ' || *t == '\t')
1081 t++;
1082 if (*t && *t != '\n' && *t != '\r') candidate = t;
1083 if (*q == '\n') q++;
1084 }
1085 last_sig_line = candidate;
1086 }
1087
1088 if (in_single || in_double || in_block_comment || paren > 0) return 1;
1089
1090 if (last_sig_line) {
1091 if (strncmp(lstrip(last_sig_line), "if", 2) == 0 ||
1092 strncmp(lstrip(last_sig_line), "else", 4) == 0 ||
1093 strncmp(lstrip(last_sig_line), "while", 5) == 0 ||
1094 strncmp(lstrip(last_sig_line), "for", 3) == 0 ||
1095 strncmp(lstrip(last_sig_line), "fun", 3) == 0) {
1096 return 1;
1097 }
1098 if (ends_with_opener(last_sig_line)) return 1;
1099 }
1100
1101 return 0;
1102}
1103
1107static void show_repl_help(void) {
1108 printf("Commands:\n");
1109 printf(" :help | :h Show this help\n");
1110 printf(" :quit | :q | :exit Exit the REPL\n");
1111 printf(" :reset | :re Reset VM state (clears globals)\n");
1112 printf(" :dump | :du | :globals | :gl Dump current globals\n");
1113 printf(" :globals [pattern] Dump globals filtering by value substring\n");
1114 printf(" :vars | :v [pattern] Alias for :globals\n");
1115 printf(" :clear | :cl Clear current input buffer\n");
1116 printf(" :print | :pr Show current buffer\n");
1117 printf(" :run | :ru [file] Execute current buffer or the given file immediately\n");
1118 printf(" :profile | :pf Execute buffer and show timing + instruction count\n");
1119 printf(" :save | :sa <file> Save current buffer to file\n");
1120 printf(" :load | :lo <file> Load file into buffer (does not run)\n");
1121 printf(" :paste | :pa [run] Enter paste mode; end with a single '.' line (optional 'run')\n");
1122 printf(" :history | :hi [N] Show last N lines of history (default 50)\n");
1123 printf(" :time | :ti on|off|toggle Toggle/enable/disable timing\n");
1124 printf(" :env | :en [NAME[=VALUE]] Get or set environment variable\n");
1125 printf(" :backtrace | :bt | :ba Show backtrace of VM frames (most recent first)\n");
1126 printf(" :frame | :fr N Select frame N for :locals/:list/:disasm (default: top)\n");
1127 printf(" :list | :li [±K] Show K lines of source around current frame line (default 5)\n");
1128 printf(" :disasm | :di [±N] Disassemble around current frame ip (default 5)\n");
1129 printf(" :mdump | :md WHAT [offset [len]] [raw] [to <file>] Dump VM memory region\n");
1130 printf(" WHAT = code | stack | globals | consts\n");
1131 printf(" 'raw' writes binary bytes instead of a formatted hexdump\n");
1132 printf(" :stack | :st [N] Show top N (default all) stack values\n");
1133 printf(" :top | :to Show the top of the VM stack\n");
1134 printf(" :locals | :lc [FRAME] Show locals of frame (default: selected frame)\n");
1135 printf(" :printv | :pv WHAT Print value: local[i] | stack[i] | global[i]\n");
1136 printf(" :break | :br [file:]line Set a breakpoint (default file = current frame file)\n");
1137 printf(" :info | :in breaks List breakpoints\n");
1138 printf(" :delete | :de ID Delete breakpoint by ID\n");
1139 printf(" :clear breaks | :cb Remove all breakpoints\n");
1140 printf(" :cont | :co Continue execution (exit REPL if in debug stop)\n");
1141 printf(" :step | :sp Step one instruction\n");
1142 printf(" :next | :ne Step over (current frame)\n");
1143 printf(" :finish | :fi Run until the current frame returns\n");
1144}
1145
1150static char *read_entire_file(const char *path, size_t *out_len) {
1151 FILE *f = fopen(path, "rb");
1152 if (!f) return NULL;
1153 if (fseek(f, 0, SEEK_END) != 0) {
1154 fclose(f);
1155 return NULL;
1156 }
1157 long sz = ftell(f);
1158 if (sz < 0) {
1159 fclose(f);
1160 return NULL;
1161 }
1162 rewind(f);
1163 char *buf = (char *)malloc((size_t)sz + 1);
1164 if (!buf) {
1165 fclose(f);
1166 return NULL;
1167 }
1168 size_t n = fread(buf, 1, (size_t)sz, f);
1169 fclose(f);
1170 buf[n] = '\0';
1171 if (out_len) *out_len = n;
1172 return buf;
1173}
1174
1179static int write_entire_file(const char *path, const char *data, size_t len) {
1180 FILE *f = fopen(path, "wb");
1181 if (!f) return 0;
1182 size_t n = fwrite(data, 1, len, f);
1183 fclose(f);
1184 return n == len;
1185}
1186
1187/* ---------- REPL command matching helper ---------- */
1191static int cmd_is_one_of(const char *cmd, const char *const names[]) {
1192 if (!cmd || !*cmd) return 0;
1193 for (int i = 0; names[i] != NULL; ++i) {
1194 if (strcmp(cmd, names[i]) == 0) return 1;
1195 }
1196 return 0;
1197}
1198
1199/* ---------- Hexdump helper ---------- */
1203static void hexdump_to(FILE *out, const unsigned char *data, size_t len, size_t base_off) {
1204 if (!out || !data || len == 0) return;
1205 const size_t width = 16;
1206 char ascii[17];
1207 ascii[16] = '\0';
1208 for (size_t i = 0; i < len; i += width) {
1209 size_t chunk = (len - i < width) ? (len - i) : width;
1210 /* address */
1211 fprintf(out, "%08zx ", base_off + i);
1212 /* hex bytes */
1213 for (size_t j = 0; j < width; ++j) {
1214 if (j < chunk) {
1215 fprintf(out, "%02x ", data[i + j]);
1216 unsigned char c = data[i + j];
1217 ascii[j] = (c >= 32 && c <= 126) ? (char)c : '.';
1218 } else {
1219 fputs(" ", out);
1220 ascii[j] = ' ';
1221 }
1222 if (j == 7) fputc(' ', out); /* extra space between 8-byte halves */
1223 }
1224 ascii[chunk < width ? chunk : width] = '\0';
1225 fprintf(out, " |%s|\n", ascii);
1226 }
1227}
1228
1232static void print_last_n_lines(const char *path, int n) {
1233 if (n <= 0) n = 50;
1234 size_t flen = 0;
1235 char *content = read_entire_file(path, &flen);
1236 if (!content) {
1237 printf("No history available.\n");
1238 return;
1239 }
1240 int lines = 0;
1241 for (size_t i = flen; i > 0; --i) {
1242 if (content[i - 1] == '\n') {
1243 lines++;
1244 if (lines > n) {
1245 content[i] = '\0';
1246 printf("%s", content + i);
1247 free(content);
1248 return;
1249 }
1250 }
1251 }
1252 printf("%s", content);
1253 free(content);
1254}
1255
1259static void append_history(FILE *hist, const char *buffer) {
1260 if (!hist || !buffer) return;
1261 fputs(buffer, hist);
1262 if (buffer[0] && buffer[strlen(buffer) - 1] != '\n') fputc('\n', hist);
1263 fflush(hist);
1264}
1265
1266/* ---------- Env helpers ---------- */
1270static void env_show_usage(void) {
1271 printf("Usage:\n");
1272 printf(" :env NAME Show environment variable NAME\n");
1273 printf(" :env NAME=VALUE Set environment variable NAME to VALUE\n");
1274 printf(" :env Show this usage\n");
1275}
1276
1280static void env_get(const char *name) {
1281 const char *v = getenv(name);
1282 if (v)
1283 printf("%s=%s\n", name, v);
1284 else
1285 printf("%s is not set\n", name);
1286}
1287
1291static void env_set(const char *name, const char *value) {
1292#ifdef _WIN32
1293 if (_putenv_s(name, value ? value : "") != 0) {
1294 printf("Failed to set %s\n", name);
1295 }
1296#else
1297 if (setenv(name, value ? value : "", 1) != 0) {
1298 printf("Failed to set %s\n", name);
1299 }
1300#endif
1301}
1302
1303/* ---------- REPL Entry ---------- */
1304
1310int fun_run_repl(VM *vm) {
1311 int repl_timing = 0;
1312 int selected_frame = -1; /* -1 means use current top frame */
1313
1314 printf("Fun %s REPL\n", FUN_VERSION);
1315 printf("Type :help for commands. Submit an empty line to run.\n");
1316
1317 /* Load stdlib symbols for completion */
1318 {
1319 char libdir[PATH_MAX];
1320 const char *envlib = getenv("FUN_LIB_DIR");
1321 if (envlib && *envlib) {
1322 snprintf(libdir, sizeof(libdir), "%s", envlib);
1323 } else {
1324#ifdef DEFAULT_LIB_DIR
1325 snprintf(libdir, sizeof(libdir), "%s", DEFAULT_LIB_DIR);
1326#else
1327 snprintf(libdir, sizeof(libdir), "%s", "lib");
1328#endif
1329 }
1330 /* strip trailing slash if present, not strictly necessary */
1331 size_t L = strlen(libdir);
1332 if (L > 1 && libdir[L - 1] == '/') libdir[L - 1] = '\0';
1333 load_stdlib_symbols(libdir);
1334 }
1335
1336 char *buffer = NULL;
1337 size_t bufcap = 0;
1338 size_t buflen = 0;
1339
1340 /* History setup */
1341 char hist_path[1024];
1342 FILE *hist = NULL;
1343 const char *home = getenv("HOME");
1344 if (!home) home = getenv("USERPROFILE");
1345 if (home) {
1346 snprintf(hist_path, sizeof(hist_path), "%s/.fun_history", home);
1347 rl_hist_load_file(hist_path);
1348 hist = fopen(hist_path, "a+");
1349 } else {
1350 snprintf(hist_path, sizeof(hist_path), ".fun_history");
1351 rl_hist_load_file(hist_path);
1352 hist = fopen(hist_path, "a+");
1353 }
1354
1355 for (;;) {
1356 if (buflen + 1 > bufcap) {
1357 size_t newcap = bufcap == 0 ? 1024 : bufcap * 2;
1358 while (newcap < buflen + 1)
1359 newcap *= 2;
1360 buffer = (char *)realloc(buffer, newcap);
1361 bufcap = newcap;
1362 }
1363 buffer[buflen] = '\0';
1364 int indent_debt = (buflen > 0) ? compute_open_indent_blocks(buffer) : 0;
1365
1366 char prompt[64];
1367 if (buflen == 0) {
1368 snprintf(prompt, sizeof(prompt), "fun> ");
1369 } else if (indent_debt > 0) {
1370 snprintf(prompt, sizeof(prompt), "...%d> ", indent_debt);
1371 } else {
1372 snprintf(prompt, sizeof(prompt), "... ");
1373 }
1374
1375 char line[4096];
1376#ifndef _WIN32
1377 if (!read_line_edit(line, sizeof(line), prompt)) {
1378 puts("");
1379 break; // EOF
1380 }
1381#else
1382 fputs(prompt, stdout);
1383 fflush(stdout);
1384 if (!fgets(line, sizeof(line), stdin)) {
1385 puts("");
1386 break;
1387 }
1388#endif
1389 if (hist) {
1390 const char *lp = line;
1391 int only_nl = 1;
1392 while (*lp) {
1393 if (*lp != '\n' && *lp != '\r') {
1394 only_nl = 0;
1395 break;
1396 }
1397 lp++;
1398 }
1399 if (!only_nl) append_history(hist, line);
1400 }
1401
1402 if (line[0] == ':') {
1403 char cmd[64] = {0};
1404 char arg[2048] = {0};
1405 sscanf(line, ":%63s %2047[^\n]", cmd, arg);
1406
1407 if (cmd_is_one_of(cmd, (const char *[]){"quit", "q", "qu", "exit", NULL})) {
1408 break;
1409 } else if (cmd_is_one_of(cmd, (const char *[]){"help", "h", NULL})) {
1410 show_repl_help();
1411 continue;
1412 } else if (cmd_is_one_of(cmd, (const char *[]){"reset", "re", NULL})) {
1413 vm_reset(vm);
1414 printf("VM state reset.\n");
1415 continue;
1416 } else if (cmd_is_one_of(cmd, (const char *[]){"dump", "du", NULL})) {
1417 vm_dump_globals(vm);
1418 continue;
1419 } else if (cmd_is_one_of(cmd, (const char *[]){"globals", "vars", "gl", "v", "va", NULL})) {
1420 const char *pattern = lstrip(arg);
1421 int filtered = (pattern && *pattern);
1422 printf("=== globals%s%s ===\n", filtered ? " matching '" : "", filtered ? pattern : "");
1423 if (filtered) printf("'\n");
1424 for (int i = 0; i < MAX_GLOBALS; ++i) {
1425 if (vm->globals[i].type == VAL_NIL) continue;
1426 char *sv = value_to_string_alloc(&vm->globals[i]);
1427 if (!filtered || (sv && strstr(sv, pattern))) {
1428 printf("[%d] %s\n", i, sv ? sv : "nil");
1429 }
1430 free(sv);
1431 }
1432 printf("===============\n");
1433 continue;
1434 } else if (cmd_is_one_of(cmd, (const char *[]){"clear", "cl", NULL})) {
1435 buflen = 0;
1436 printf("(buffer cleared)\n");
1437 continue;
1438 } else if (cmd_is_one_of(cmd, (const char *[]){"print", "pr", NULL})) {
1439 if (buflen == 0)
1440 printf("(buffer empty)\n");
1441 else {
1442 if (buflen >= bufcap) {
1443 buffer = (char *)realloc(buffer, buflen + 1);
1444 bufcap = buflen + 1;
1445 }
1446 buffer[buflen] = '\0';
1447 printf("%s", buffer);
1448 if (buflen > 0 && buffer[buflen - 1] != '\n') printf("\n");
1449 }
1450 continue;
1451 } else if (cmd_is_one_of(cmd, (const char *[]){"run", "ru", "profile", "pf", NULL})) {
1452 int is_profile = cmd_is_one_of(cmd, (const char *[]){"profile", "pf", NULL});
1453 // 'arg' is a fixed-size local array, so its address is always non-null.
1454 // Only check whether it contains a non-empty string.
1455 int from_file = (arg[0] != '\0');
1456
1457 const char *src = NULL;
1458 char *filebuf = NULL;
1459 if (from_file) {
1460 size_t flen = 0;
1461 filebuf = read_entire_file(arg, &flen);
1462 if (!filebuf) {
1463 printf("Failed to load '%s'\n", arg);
1464 continue;
1465 }
1466 src = filebuf;
1467 } else {
1468 if (buflen == 0) {
1469 printf("(buffer empty)\n");
1470 continue;
1471 }
1472 if (buflen + 1 > bufcap) {
1473 buffer = (char *)realloc(buffer, buflen + 1);
1474 bufcap = buflen + 1;
1475 }
1476 buffer[buflen] = '\0';
1477 src = buffer;
1478 }
1479
1480 clock_t t_parse0 = 0, t_parse1 = 0, t_run0 = 0, t_run1 = 0;
1481 if (is_profile) t_parse0 = clock();
1483 if (is_profile) t_parse1 = clock();
1484
1485 if (bc) {
1486 if (repl_timing || is_profile) t_run0 = clock();
1487 vm_run(vm, bc);
1488 if (repl_timing || is_profile) t_run1 = clock();
1489
1490 if (is_profile) {
1491 double ms_parse = (double)(t_parse1 - t_parse0) * 1000.0 / (double)CLOCKS_PER_SEC;
1492 double ms_run = (double)(t_run1 - t_run0) * 1000.0 / (double)CLOCKS_PER_SEC;
1493 printf("[profile] parse: %.2f ms, run: %.2f ms, total: %.2f ms, instr: %lld\n",
1494 ms_parse, ms_run, ms_parse + ms_run, vm->instr_count);
1495 } else if (repl_timing) {
1496 double ms = (double)(t_run1 - t_run0) * 1000.0 / (double)CLOCKS_PER_SEC;
1497 printf("[time] %.2f ms\n", ms);
1498 }
1499
1500 vm_print_output(vm);
1501 vm_clear_output(vm);
1502 bytecode_free(bc);
1503 if (!from_file) append_history(hist, buffer);
1504 } else {
1505 int line_no = 0, col_no = 0;
1506 char emsg[256];
1507 if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) {
1508 printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg);
1509 int cur_line = 1;
1510 const char *p = src ? src : buffer;
1511 while (*p && cur_line < line_no) {
1512 if (*p == '\n') cur_line++;
1513 p++;
1514 }
1515 const char *line_start = p;
1516 while (*p && *p != '\n')
1517 p++;
1518 fwrite(line_start, 1, (size_t)(p - line_start), stdout);
1519 printf("\n");
1520 for (int i = 1; i < col_no; ++i)
1521 putchar(' ');
1522 printf("^\n");
1523#ifdef FUN_DEBUG
1524 if (hist && !from_file) {
1525 fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg);
1526 fflush(hist);
1527 }
1528#endif
1529 } else {
1530 printf("Parse error.\n");
1531#ifdef FUN_DEBUG
1532 if (hist && !from_file) {
1533 fprintf(hist, "// ERROR: parse error\n");
1534 fflush(hist);
1535 }
1536#endif
1537 }
1538 }
1539 if (from_file) {
1540 free(filebuf);
1541 } else {
1542 buflen = 0; /* keep behavior: clear buffer only when running current buffer */
1543 }
1544 continue;
1545 } else if (cmd_is_one_of(cmd, (const char *[]){"save", "sa", NULL})) {
1546 if (arg[0] == '\0') {
1547 printf("Usage: :save <file>\n");
1548 continue;
1549 }
1550 if (buflen == 0) {
1551 printf("(buffer empty)\n");
1552 continue;
1553 }
1554 if (!write_entire_file(arg, buffer, buflen)) {
1555 printf("Failed to save to '%s'\n", arg);
1556 } else {
1557 printf("Saved %zu bytes to '%s'\n", buflen, arg);
1558 }
1559 continue;
1560 } else if (cmd_is_one_of(cmd, (const char *[]){"load", "lo", NULL})) {
1561 if (arg[0] == '\0') {
1562 printf("Usage: :load <file>\n");
1563 continue;
1564 }
1565 size_t flen = 0;
1566 char *filebuf = read_entire_file(arg, &flen);
1567 if (!filebuf) {
1568 printf("Failed to load '%s'\n", arg);
1569 continue;
1570 }
1571 if (flen + 1 > bufcap) {
1572 size_t newcap = flen + 1;
1573 buffer = (char *)realloc(buffer, newcap);
1574 bufcap = newcap;
1575 }
1576 memcpy(buffer, filebuf, flen);
1577 buflen = flen;
1578 buffer[buflen] = '\0';
1579 free(filebuf);
1580 printf("Loaded %zu bytes into buffer. Use :run or submit an empty line to execute.\n", buflen);
1581 continue;
1582 } else if (cmd_is_one_of(cmd, (const char *[]){"paste", "pa", NULL})) {
1583 int run_after = 0;
1584 const char *opt = lstrip(arg);
1585 if (opt && (strcmp(opt, "run") == 0 || strcmp(opt, "exec") == 0)) run_after = 1;
1586 printf("(paste mode: end with single '.' line)%s\n", run_after ? " [will run]" : "");
1587 for (;;) {
1588 fputs("... paste> ", stdout);
1589 fflush(stdout);
1590 char pline[8192];
1591 if (!fgets(pline, sizeof(pline), stdin)) {
1592 puts("");
1593 break;
1594 }
1595 if ((strcmp(pline, ".\n") == 0) || (strcmp(pline, ".\r\n") == 0) || (strcmp(pline, ".") == 0)) {
1596 break;
1597 }
1598 size_t pl = strlen(pline);
1599 if (buflen + pl + 1 > bufcap) {
1600 size_t newcap = bufcap == 0 ? 1024 : bufcap * 2;
1601 while (newcap < buflen + pl + 1)
1602 newcap *= 2;
1603 buffer = (char *)realloc(buffer, newcap);
1604 bufcap = newcap;
1605 }
1606 memcpy(buffer + buflen, pline, pl);
1607 buflen += pl;
1608 }
1609 if (run_after) {
1610 if (buflen + 1 > bufcap) {
1611 buffer = (char *)realloc(buffer, buflen + 1);
1612 bufcap = buflen + 1;
1613 }
1614 buffer[buflen] = '\0';
1616 if (bc) {
1617 clock_t t0 = clock();
1618 vm_run(vm, bc);
1619 clock_t t1 = clock();
1620 double ms = (double)(t1 - t0) * 1000.0 / (double)CLOCKS_PER_SEC;
1621 printf("[time] %.2f ms\n", ms);
1622 vm_print_output(vm);
1623 vm_clear_output(vm);
1624 bytecode_free(bc);
1625 append_history(hist, buffer);
1626 } else {
1627 int line_no = 0, col_no = 0;
1628 char emsg[256];
1629 if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) {
1630 printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg);
1631#ifdef FUN_DEBUG
1632 if (hist) {
1633 fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg);
1634 fflush(hist);
1635 }
1636#endif
1637 } else {
1638 printf("Parse error.\n");
1639#ifdef FUN_DEBUG
1640 if (hist) {
1641 fprintf(hist, "// ERROR: parse error\n");
1642 fflush(hist);
1643 }
1644#endif
1645 }
1646 }
1647 buflen = 0;
1648 } else {
1649 printf("(pasted %zu bytes into buffer)\n", buflen);
1650 }
1651 continue;
1652 } else if (cmd_is_one_of(cmd, (const char *[]){"history", "hi", NULL})) {
1653 int n = 50;
1654 if (arg[0] != '\0') n = atoi(arg);
1655 if (n <= 0) n = 50;
1656 print_last_n_lines(hist_path, n);
1657 continue;
1658 } else if (cmd_is_one_of(cmd, (const char *[]){"time", "ti", NULL})) {
1659 if (strcmp(lstrip(arg), "on") == 0)
1660 repl_timing = 1;
1661 else if (strcmp(lstrip(arg), "off") == 0)
1662 repl_timing = 0;
1663 else if (strcmp(lstrip(arg), "toggle") == 0)
1664 repl_timing = !repl_timing;
1665 else {
1666 printf("Usage: :time on|off|toggle (currently %s)\n", repl_timing ? "on" : "off");
1667 continue;
1668 }
1669 printf("Timing %s\n", repl_timing ? "enabled" : "disabled");
1670 continue;
1671 } else if (cmd_is_one_of(cmd, (const char *[]){"env", "en", NULL})) {
1672 const char *spec = lstrip(arg);
1673 if (!spec || *spec == '\0') {
1674 env_show_usage();
1675 continue;
1676 }
1677 const char *eq = strchr(spec, '=');
1678 if (!eq) {
1679 env_get(spec);
1680 } else {
1681 char name[256];
1682 size_t nlen = (size_t)(eq - spec);
1683 if (nlen >= sizeof(name)) nlen = sizeof(name) - 1;
1684 memcpy(name, spec, nlen);
1685 name[nlen] = '\0';
1686 const char *val = eq + 1;
1687 env_set(name, val);
1688 }
1689 continue;
1690 } else if (cmd_is_one_of(cmd, (const char *[]){"backtrace", "bt", "ba", NULL})) {
1691 if (vm->fp < 0) {
1692 printf("(no frames)\n");
1693 continue;
1694 }
1695 printf("Backtrace (most recent call first):\n");
1696 for (int i = vm->fp; i >= 0; --i) {
1697 Frame *f = &vm->frames[i];
1698 const char *fname = (f->fn && f->fn->name) ? f->fn->name : "<entry>";
1699 const char *sfile = (f->fn && f->fn->source_file) ? f->fn->source_file : "<unknown>";
1700 int ip = f->ip - 1;
1701 printf(" #%d %s at %s ip=%d line=%d\n", i, fname, sfile, ip, vm->current_line);
1702 }
1703 continue;
1704 } else if (cmd_is_one_of(cmd, (const char *[]){"stack", "st", NULL})) {
1705 int n = -1;
1706 const char *p = lstrip(arg);
1707 if (p && *p) n = atoi(p);
1708 int count = vm->sp + 1;
1709 if (count <= 0) {
1710 printf("(stack empty)\n");
1711 continue;
1712 }
1713 int start = 0;
1714 if (n > 0 && n < count) start = count - n;
1715 printf("Stack size=%d\n", count);
1716 for (int i = start; i < count; ++i) {
1717 char *sv = value_to_string_alloc(&vm->stack[i]);
1718 printf("[%d] %s\n", i, sv ? sv : "nil");
1719 free(sv);
1720 }
1721 continue;
1722 } else if (cmd_is_one_of(cmd, (const char *[]){"locals", "lc", NULL})) {
1723 int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp;
1724 const char *p = lstrip(arg);
1725 if (p && *p) {
1726 int v = atoi(p);
1727 if (v >= 0 && v <= vm->fp) idx = v;
1728 }
1729 if (idx < 0) {
1730 printf("(no current frame)\n");
1731 continue;
1732 }
1733 Frame *f = &vm->frames[idx];
1734 const char *fname = (f->fn && f->fn->name) ? f->fn->name : "<entry>";
1735 printf("Locals in frame #%d (%s):\n", idx, fname);
1736 int any = 0;
1737 for (int i = 0; i < MAX_FRAME_LOCALS; ++i) {
1738 if (f->locals[i].type != VAL_NIL) {
1739 char *sv = value_to_string_alloc(&f->locals[i]);
1740 printf(" %d: %s\n", i, sv ? sv : "nil");
1741 free(sv);
1742 any = 1;
1743 }
1744 }
1745 if (!any) printf(" (no non-nil locals)\n");
1746 continue;
1747 } else if (cmd_is_one_of(cmd, (const char *[]){"frame", "fr", NULL})) {
1748 const char *p = lstrip(arg);
1749 if (!p || !*p) {
1750 printf("Usage: :frame N\n");
1751 continue;
1752 }
1753 int v = atoi(p);
1754 if (v < 0 || v > vm->fp) {
1755 printf("Invalid frame index. Current top is %d\n", vm->fp);
1756 continue;
1757 }
1758 selected_frame = v;
1759 Frame *f = &vm->frames[selected_frame];
1760 const char *fname = (f->fn && f->fn->name) ? f->fn->name : "<entry>";
1761 const char *sfile = (f->fn && f->fn->source_file) ? f->fn->source_file : "<unknown>";
1762 printf("Selected frame #%d: %s (%s)\n", selected_frame, fname, sfile);
1763 continue;
1764 } else if (cmd_is_one_of(cmd, (const char *[]){"list", "li", NULL})) {
1765 int k = 5;
1766 const char *p = lstrip(arg);
1767 if (p && *p) k = atoi(p);
1768 if (k <= 0) k = 5;
1769 int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp;
1770 if (idx < 0) {
1771 printf("(no current frame)\n");
1772 continue;
1773 }
1774 Frame *f = &vm->frames[idx];
1775 if (!f->fn || !f->fn->source_file) {
1776 printf("(no source info)\n");
1777 continue;
1778 }
1779 /* derive current line for this frame by scanning LINE markers up to ip-1 */
1780 int line = vm->current_line;
1781 int upto = f->ip - 1;
1782 if (upto < 0) upto = 0;
1783 for (int i = 0; i <= upto && i < f->fn->instr_count; ++i) {
1784 Instruction ins = f->fn->instructions[i];
1785 if (ins.op == OP_LINE) line = ins.operand;
1786 }
1787 const char *path = f->fn->source_file;
1788 /* Map to included file if the current line belongs to an included chunk */
1789 char mapped_path[1024];
1790 int mapped_line = line;
1791 if (map_expanded_line_to_include_path(path, line, mapped_path, sizeof(mapped_path), &mapped_line)) {
1792 path = mapped_path;
1793 line = mapped_line;
1794 }
1795 size_t flen = 0;
1796 char *src = read_entire_file(path, &flen);
1797 if (!src) {
1798 printf("Unable to read %s\n", path);
1799 continue;
1800 }
1801 int start = line - k;
1802 if (start < 1) start = 1;
1803 int end = line + k;
1804 int cur = 1;
1805 const char *s = src;
1806 while (*s && cur <= end) {
1807 const char *ls = s;
1808 while (*s && *s != '\n')
1809 s++;
1810 int print = (cur >= start && cur <= end);
1811 if (print) {
1812 printf("%c %5d | ", (cur == line ? '>' : ' '), cur);
1813 fwrite(ls, 1, (size_t)(s - ls), stdout);
1814 printf("\n");
1815 }
1816 if (*s == '\n') s++;
1817 cur++;
1818 }
1819 free(src);
1820 continue;
1821 } else if (cmd_is_one_of(cmd, (const char *[]){"disasm", "disassemble", "di", NULL})) {
1822 int n = 5;
1823 const char *p = lstrip(arg);
1824 if (p && *p) n = atoi(p);
1825 if (n <= 0) n = 5;
1826 int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp;
1827 if (idx < 0) {
1828 printf("(no current frame)\n");
1829 continue;
1830 }
1831 Frame *f = &vm->frames[idx];
1832 if (!f->fn) {
1833 printf("(no function)\n");
1834 continue;
1835 }
1836
1837 /* Ensure we have a valid instruction buffer */
1838 if (f->fn->instr_count <= 0 || f->fn->instructions == NULL) {
1839 printf("(no instructions)\n");
1840 continue;
1841 }
1842
1843 int count = f->fn->instr_count;
1844 int curip = f->ip - 1;
1845 if (curip < 0) curip = 0;
1846 if (curip >= count) curip = count - 1;
1847
1848 int from = curip - n;
1849 if (from < 0) from = 0;
1850 int to = curip + n;
1851 if (to >= count) to = count - 1;
1852 if (to < from) { /* nothing to show */
1853 continue;
1854 }
1855
1856 for (int i = from; i <= to; ++i) {
1857 Instruction ins = f->fn->instructions[i];
1858 const char *opname = opcode_is_valid(ins.op) ? opcode_names[ins.op] : "???";
1859 printf("%c %6d: %-14s %d\n", (i == curip ? '>' : ' '), i, opname, ins.operand);
1860 }
1861 continue;
1862 } else if (cmd_is_one_of(cmd, (const char *[]){"mdump", "md", NULL})) {
1863 /* Syntax: :mdump WHAT [offset [len]] [raw] [to <file>]
1864 WHAT: code | stack | globals | consts
1865 'raw' writes binary bytes instead of a formatted hexdump */
1866 const char *p = lstrip(arg);
1867 if (!p || !*p) {
1868 printf("Usage: :mdump WHAT [offset [len]] [raw] [to <file>]\n");
1869 printf(" WHAT = code | stack | globals | consts\n");
1870 printf(" 'raw' writes binary bytes instead of a formatted hexdump\n");
1871 continue;
1872 }
1873
1874 char what[32];
1875 int consumed = 0;
1876 if (sscanf(p, "%31s %n", what, &consumed) != 1) {
1877 printf("Usage: :mdump WHAT [offset [len]] [raw] [to <file>]\n");
1878 continue;
1879 }
1880 p += consumed;
1881
1882 size_t off = 0;
1883 size_t len = (size_t)-1; /* default later to clamp */
1884 int want_raw = 0; /* output raw bytes instead of hexdump */
1885
1886 /* parse optional off */
1887 while (*p == ' ' || *p == '\t')
1888 p++;
1889 if (*p && (isdigit((unsigned char)*p))) {
1890 char *endp = NULL;
1891 long long v = strtoll(p, &endp, 10);
1892 if (endp && endp != p && v >= 0) {
1893 off = (size_t)v;
1894 p = endp;
1895 }
1896 }
1897 /* parse optional len */
1898 while (*p == ' ' || *p == '\t')
1899 p++;
1900 if (*p && (isdigit((unsigned char)*p))) {
1901 char *endp = NULL;
1902 long long v = strtoll(p, &endp, 10);
1903 if (endp && endp != p && v >= 0) {
1904 len = (size_t)v;
1905 p = endp;
1906 }
1907 }
1908
1909 /* optional: 'raw' keyword */
1910 while (*p == ' ' || *p == '\t')
1911 p++;
1912 if (strncmp(p, "raw", 3) == 0 && (p[3] == '\0' || isspace((unsigned char)p[3]))) {
1913 want_raw = 1;
1914 p += 3;
1915 }
1916
1917 /* optional: to <file> */
1918 while (*p == ' ' || *p == '\t')
1919 p++;
1920 int to_file = 0;
1921 char path[PATH_MAX];
1922 path[0] = '\0';
1923 if (strncmp(p, "to", 2) == 0 && (p[2] == '\0' || isspace((unsigned char)p[2]))) {
1924 p += 2;
1925 while (*p == ' ' || *p == '\t')
1926 p++;
1927 if (!*p) {
1928 printf("Missing <file> after 'to'\n");
1929 continue;
1930 }
1931 /* read until whitespace end or end of string; simple paths without spaces */
1932 size_t i = 0;
1933 while (*p && !isspace((unsigned char)*p) && i + 1 < sizeof(path))
1934 path[i++] = *p++;
1935 path[i] = '\0';
1936 if (path[0] == '\0') {
1937 printf("Invalid file path\n");
1938 continue;
1939 }
1940 to_file = 1;
1941 }
1942
1943 const unsigned char *base = NULL;
1944 size_t total = 0;
1945 int ok_region = 1;
1946
1947 if (strcmp(what, "code") == 0) {
1948 int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp;
1949 if (idx < 0) {
1950 printf("(no current frame)\n");
1951 continue;
1952 }
1953 Frame *fr = &vm->frames[idx];
1954 if (!fr->fn || fr->fn->instr_count <= 0 || fr->fn->instructions == NULL) {
1955 printf("(no code to dump)\n");
1956 continue;
1957 }
1958 base = (const unsigned char *)fr->fn->instructions;
1959 total = (size_t)fr->fn->instr_count * sizeof(Instruction);
1960 } else if (strcmp(what, "consts") == 0) {
1961 int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp;
1962 if (idx < 0) {
1963 printf("(no current frame)\n");
1964 continue;
1965 }
1966 Frame *fr = &vm->frames[idx];
1967 if (!fr->fn || fr->fn->const_count <= 0 || fr->fn->constants == NULL) {
1968 printf("(no constants to dump)\n");
1969 continue;
1970 }
1971 base = (const unsigned char *)fr->fn->constants;
1972 total = (size_t)fr->fn->const_count * sizeof(Value);
1973 } else if (strcmp(what, "stack") == 0) {
1974 int count = vm->sp + 1;
1975 if (count <= 0) {
1976 printf("(stack empty)\n");
1977 continue;
1978 }
1979 base = (const unsigned char *)vm->stack;
1980 total = (size_t)count * sizeof(Value);
1981 } else if (strcmp(what, "globals") == 0) {
1982 base = (const unsigned char *)vm->globals;
1983 total = (size_t)MAX_GLOBALS * sizeof(Value);
1984 } else {
1985 ok_region = 0;
1986 }
1987
1988 if (!ok_region) {
1989 printf("Unknown region '%s'. Use one of: code, stack, globals, consts\n", what);
1990 continue;
1991 }
1992 if (!base || total == 0) {
1993 printf("(nothing to dump)\n");
1994 continue;
1995 }
1996
1997 if (off >= total) {
1998 printf("(empty range: offset beyond end)\n");
1999 continue;
2000 }
2001 size_t avail = total - off;
2002 if (len == (size_t)-1 || len == 0 || len > avail) {
2003 /* default to min(256, avail) */
2004 len = avail < 256 ? avail : 256;
2005 }
2006
2007 if (!to_file) {
2008 if (want_raw) {
2009 /* Write raw bytes directly to stdout with no header */
2010 fwrite(base + off, 1, len, stdout);
2011 fflush(stdout);
2012 } else {
2013 printf("Hexdump %s: total=%zu, offset=%zu, len=%zu\n", what, total, off, len);
2014 hexdump_to(stdout, base + off, len, off);
2015 }
2016 } else {
2017 FILE *fout = fopen(path, "wb");
2018 if (!fout) {
2019 printf("Failed to open '%s' for writing\n", path);
2020 continue;
2021 }
2022 if (want_raw) {
2023 fwrite(base + off, 1, len, fout);
2024 fclose(fout);
2025 printf("Wrote raw bytes (%zu from %s) to %s\n", len, what, path);
2026 } else {
2027 hexdump_to(fout, base + off, len, off);
2028 fclose(fout);
2029 printf("Wrote hexdump (%zu bytes from %s) to %s\n", len, what, path);
2030 }
2031 }
2032 continue;
2033 } else if (cmd_is_one_of(cmd, (const char *[]){"printv", "pv", NULL})) {
2034 const char *spec = lstrip(arg);
2035 if (!spec || !*spec) {
2036 printf("Usage: :printv local[i] | stack[i] | global[i]\n");
2037 continue;
2038 }
2039 int idx = -1;
2040 if (sscanf(spec, "local[%d]", &idx) == 1) {
2041 int fidx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp;
2042 if (fidx < 0 || idx < 0 || idx >= MAX_FRAME_LOCALS) {
2043 printf("(out of range)\n");
2044 continue;
2045 }
2046 Frame *f = &vm->frames[fidx];
2047 char *sv = value_to_string_alloc(&f->locals[idx]);
2048 printf("%s\n", sv ? sv : "nil");
2049 free(sv);
2050 } else if (sscanf(spec, "stack[%d]", &idx) == 1) {
2051 if (idx < 0 || idx > vm->sp) {
2052 printf("(out of range)\n");
2053 continue;
2054 }
2055 char *sv = value_to_string_alloc(&vm->stack[idx]);
2056 printf("%s\n", sv ? sv : "nil");
2057 free(sv);
2058 } else if (sscanf(spec, "global[%d]", &idx) == 1) {
2060 printf("(out of range)\n");
2061 continue;
2062 }
2063 char *sv = value_to_string_alloc(&vm->globals[idx]);
2064 printf("%s\n", sv ? sv : "nil");
2065 free(sv);
2066 } else {
2067 printf("Usage: :printv local[i] | stack[i] | global[i]\n");
2068 }
2069 continue;
2070 } else if (cmd_is_one_of(cmd, (const char *[]){"top", "to", NULL})) {
2071 if (vm->sp < 0) {
2072 printf("(stack empty)\n");
2073 continue;
2074 }
2075 char *sv = value_to_string_alloc(&vm->stack[vm->sp]);
2076 printf("%s\n", sv ? sv : "nil");
2077 free(sv);
2078 continue;
2079 } else if (cmd_is_one_of(cmd, (const char *[]){"break", "br", NULL})) {
2080 const char *p = lstrip(arg);
2081 if (!p || !*p) {
2082 printf("Usage: :break [file:]line\n");
2083 continue;
2084 }
2085 const char *colon = strchr(p, ':');
2086 char filebuf[1024];
2087 int line = 0;
2088 if (colon) {
2089 size_t fl = (size_t)(colon - p);
2090 if (fl >= sizeof(filebuf)) fl = sizeof(filebuf) - 1;
2091 memcpy(filebuf, p, fl);
2092 filebuf[fl] = '\0';
2093 line = atoi(colon + 1);
2094 } else {
2095 int idxf = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp;
2096 if (idxf < 0) {
2097 printf("(no current frame)\n");
2098 continue;
2099 }
2100 Frame *f = &vm->frames[idxf];
2101 const char *sf = (f->fn && f->fn->source_file) ? f->fn->source_file : NULL;
2102 if (!sf) {
2103 printf("(no current source file)\n");
2104 continue;
2105 }
2106 snprintf(filebuf, sizeof(filebuf), "%s", sf);
2107 line = atoi(p);
2108 }
2109 if (line <= 0) {
2110 printf("Invalid line\n");
2111 continue;
2112 }
2113 int id = vm_debug_add_breakpoint(vm, filebuf, line);
2114 if (id >= 0)
2115 printf("Breakpoint %d set at %s:%d\n", id, filebuf, line);
2116 else
2117 printf("Failed to set breakpoint\n");
2118 continue;
2119 } else if (cmd_is_one_of(cmd, (const char *[]){"info", "in", NULL})) {
2120 const char *what = lstrip(arg);
2121 if (what && strcmp(what, "breaks") == 0) {
2123 } else {
2124 printf("Usage: :info breaks\n");
2125 }
2126 continue;
2127 } else if (cmd_is_one_of(cmd, (const char *[]){"delete", "de", NULL})) {
2128 int id = atoi(lstrip(arg));
2129 if (vm_debug_delete_breakpoint(vm, id))
2130 printf("Deleted breakpoint %d\n", id);
2131 else
2132 printf("No such breakpoint %d\n", id);
2133 continue;
2134 } else if (cmd_is_one_of(cmd, (const char *[]){"cb", NULL})) {
2136 printf("Cleared all breakpoints\n");
2137 continue;
2138 } else if (strcmp(cmd, "clear") == 0) {
2139 const char *what = lstrip(arg);
2140 if (what && strcmp(what, "breaks") == 0) {
2142 printf("Cleared all breakpoints\n");
2143 } else {
2144 printf("Usage: :clear breaks\n");
2145 }
2146 continue;
2147 } else if (cmd_is_one_of(cmd, (const char *[]){"cont", "continue", "co", NULL})) {
2149 printf("Continuing...\n");
2150 if (vm->on_error_repl) return 0; /* exit REPL to continue execution */
2151 continue;
2152 } else if (cmd_is_one_of(cmd, (const char *[]){"step", "sp", NULL})) {
2154 printf("Stepping one instruction...\n");
2155 if (vm->on_error_repl) return 0;
2156 continue;
2157 } else if (cmd_is_one_of(cmd, (const char *[]){"next", "ne", NULL})) {
2159 printf("Stepping over...\n");
2160 if (vm->on_error_repl) return 0;
2161 continue;
2162 } else if (cmd_is_one_of(cmd, (const char *[]){"finish", "fi", NULL})) {
2164 printf("Running until current frame returns...\n");
2165 if (vm->on_error_repl) return 0;
2166 continue;
2167 } else {
2168 printf("Unknown command. Use :help\n");
2169 continue;
2170 }
2171 }
2172
2173 if (is_blank_line(line)) {
2174 if (buflen == 0) continue;
2175
2176 if (buflen + 1 > bufcap) {
2177 buffer = (char *)realloc(buffer, buflen + 1);
2178 bufcap = buflen + 1;
2179 }
2180 buffer[buflen] = '\0';
2181
2182 int indent_debt2 = compute_open_indent_blocks(buffer);
2183 if (buffer_looks_incomplete(buffer) || indent_debt2 > 0) {
2184 if (indent_debt2 > 0) {
2185 printf("(incomplete, open block indent +%d)\n", indent_debt2);
2186 } else {
2187 printf("(incomplete, continue typing)\n");
2188 }
2189 continue;
2190 }
2191
2193 if (bc) {
2194 clock_t t0 = 0, t1 = 0;
2195 if (repl_timing) t0 = clock();
2196 vm_run(vm, bc);
2197 if (repl_timing) {
2198 t1 = clock();
2199 double ms = (double)(t1 - t0) * 1000.0 / (double)CLOCKS_PER_SEC;
2200 printf("[time] %.2f ms\n", ms);
2201 }
2202 vm_print_output(vm);
2203 vm_clear_output(vm);
2204 bytecode_free(bc);
2205 append_history(hist, buffer);
2206 } else {
2207 int line_no = 0, col_no = 0;
2208 char emsg[256];
2209 if (parser_last_error(emsg, sizeof(emsg), &line_no, &col_no)) {
2210 printf("Parse error at %d:%d: %s\n", line_no, col_no, emsg);
2211 int cur_line = 1;
2212 const char *p = buffer;
2213 while (*p && cur_line < line_no) {
2214 if (*p == '\n') cur_line++;
2215 p++;
2216 }
2217 const char *line_start = p;
2218 while (*p && *p != '\n')
2219 p++;
2220 fwrite(line_start, 1, (size_t)(p - line_start), stdout);
2221 printf("\n");
2222 for (int i = 1; i < col_no; ++i)
2223 putchar(' ');
2224 printf("^\n");
2225#ifdef FUN_DEBUG
2226 if (hist) {
2227 fprintf(hist, "// ERROR %d:%d: %s\n", line_no, col_no, emsg);
2228 fflush(hist);
2229 }
2230#endif
2231 } else {
2232 printf("Parse error.\n");
2233#ifdef FUN_DEBUG
2234 if (hist) {
2235 fprintf(hist, "// ERROR: parse error\n");
2236 fflush(hist);
2237 }
2238#endif
2239 }
2240 }
2241 buflen = 0;
2242 continue;
2243 }
2244
2245 size_t linelen = strlen(line);
2246 if (buflen + linelen + 1 > bufcap) {
2247 size_t newcap = bufcap == 0 ? 1024 : bufcap * 2;
2248 while (newcap < buflen + linelen + 1)
2249 newcap *= 2;
2250 buffer = (char *)realloc(buffer, newcap);
2251 bufcap = newcap;
2252 }
2253 memcpy(buffer + buflen, line, linelen);
2254 buflen += linelen;
2255 }
2256
2257 if (hist) fclose(hist);
2258 free_stdlib_symbols();
2259 free(buffer);
2260 return 0;
2261}
2262
2263#endif /* FUN_WITH_REPL */
int res
Definition and.c:34
Value out
Definition apop.c:38
void bytecode_free(Bytecode *bc)
Free a Bytecode and all memory it owns.
Definition bytecode.c:92
Definitions for the Fun VM bytecode: opcodes, instruction format, and bytecode container API.
@ OP_LINE
Definition bytecode.h:67
Value v
Definition cast.c:22
int k
Definition cast.c:29
int ok
Definition contains.c:38
const char * name
Definition env.c:29
int eq
Definition eq.c:34
void to
#define FUN_VERSION
Definition fun.c:32
int idx
Definition index_of.c:38
int ch
Definition input_line.c:111
size_t len
Definition input_line.c:102
size_t cap
Definition input_line.c:101
char * buf
Definition input_line.c:103
int n
Definition insert.c:41
Value c
Definition load_const.c:31
Value val
Definition load_local.c:36
free(vals)
char * names[MAX_GLOBALS]
Definition parser.c:263
Bytecode * parse_string_to_bytecode(const char *source)
Parse a source string and return compiled bytecode.
Definition parser.c:7714
int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *outCol)
Retrieve the last parser/compiler error information, if any.
Definition parser.c:7770
int count
Definition parser.c:266
Public API for parsing Fun source into bytecode.
#define DEFAULT_LIB_DIR
int64_t base
Definition pow.c:38
char * cmd
Definition proc_run.c:33
unsigned char * raw
rewind(f)
fclose(f)
FILE * f
Definition read_file.c:38
const char * p
Definition read_file.c:37
long sz
Definition read_file.c:50
Value pattern
int map_expanded_line_to_include_path(const char *path, int line, char *out_path, size_t out_path_cap, int *out_line)
Map a line number in expanded source back to original include path/line.
Interactive Read-Eval-Print Loop (REPL) entry point.
uint32_t s
Definition rol.c:31
long t
Definition sleep_ms.c:32
Value start
Definition slice.c:34
Value any
Definition stringify.c:48
Instruction * instructions
Definition bytecode.h:294
Value * constants
Definition bytecode.h:297
int const_count
Definition bytecode.h:298
int instr_count
Definition bytecode.h:295
Call frame representing one active function invocation.
Definition vm.h:93
Bytecode * fn
Definition vm.h:94
OpCode op
Definition bytecode.h:289
int32_t operand
Definition bytecode.h:290
The Fun virtual machine state.
Definition vm.h:110
int sp
Definition vm.h:112
long long instr_count
Definition vm.h:123
int current_line
Definition vm.h:125
int fp
Definition vm.h:115
int(* on_error_repl)(struct VM *vm)
Definition vm.h:131
Value globals[MAX_GLOBALS]
Definition vm.h:117
Frame frames[MAX_FRAMES]
Definition vm.h:114
Value stack[STACK_SIZE]
Definition vm.h:111
Tagged union representing a Fun value.
Definition value.h:68
ValueType type
Definition value.h:69
vm fp
Definition throw.c:57
char * value_to_string_alloc(const Value *v)
Allocate a printable C string for a Value.
Definition value.c:641
Defines the Value type and associated functions for the Fun VM.
@ VAL_NIL
Definition value.h:57
void vm_debug_request_finish(VM *vm)
Request finish (run until the current frame returns).
Definition vm.c:540
void vm_debug_clear_breakpoints(VM *vm)
Remove all breakpoints from the VM.
Definition vm.c:492
void vm_debug_request_continue(VM *vm)
Resume normal execution (clear stepping state and stop flag).
Definition vm.c:551
void vm_debug_request_step(VM *vm)
Request single-step execution (stop after next instruction).
Definition vm.c:517
int vm_debug_delete_breakpoint(VM *vm, int id)
Delete a breakpoint by id.
Definition vm.c:472
void vm_debug_request_next(VM *vm)
Request step-over (stop after next instruction in current frame).
Definition vm.c:528
#define fprintf
Definition vm.c:200
int vm_debug_add_breakpoint(VM *vm, const char *file, int line)
Add a source breakpoint.
Definition vm.c:453
void vm_clear_output(VM *vm)
Clear the VM's buffered output values and partial flags.
Definition vm.c:349
void vm_reset(VM *vm)
Reset the VM to a clean state.
Definition vm.c:382
void vm_debug_list_breakpoints(VM *vm)
Print active breakpoints to stdout.
Definition vm.c:501
void vm_print_output(VM *vm)
Print the VM's buffered output values to stdout.
Definition vm.c:797
void vm_dump_globals(VM *vm)
Print all non-nil global variables to stdout for debugging.
Definition vm.c:408
Core virtual machine data structures and public VM API.
#define MAX_GLOBALS
Definition vm.h:34
void vm_run(VM *vm, Bytecode *entry)
Execute the provided entry bytecode in the VM. Pushes an initial frame and runs until HALT or an unre...
#define MAX_FRAME_LOCALS
Definition vm.h:30
Value path
Definition write_file.c:33