45#define FUN_VERSION "0.0.0-dev" 49enum { RL_HIST_MAX = 1000 };
50static char *rl_hist[RL_HIST_MAX];
51static int rl_count = 0;
57static const char *rl_hist_last(
void) {
58 if (rl_count <= 0)
return NULL;
59 return rl_hist[rl_count - 1];
67static void rl_hist_add(
const char *
s) {
71 while (
n > 0 && (
s[
n - 1] ==
'\n' ||
s[
n - 1] ==
'\r'))
75 const char *last = rl_hist_last();
76 if (last && strncmp(last,
s,
n) == 0 && last[
n] ==
'\0')
return;
79 char *cpy = (
char *)malloc(
n + 1);
84 if (rl_count == RL_HIST_MAX) {
86 memmove(&rl_hist[0], &rl_hist[1],
sizeof(rl_hist[0]) * (RL_HIST_MAX - 1));
89 rl_hist[rl_count++] = cpy;
96static void rl_hist_load_file(
const char *
path) {
98 FILE *
f = fopen(
path,
"r");
101 while (fgets(line,
sizeof(line),
f)) {
108static struct termios g_orig_tios;
109static int g_raw_enabled = 0;
114static void repl_disable_raw(
void) {
116 tcsetattr(STDIN_FILENO, TCSAFLUSH, &g_orig_tios);
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);
132 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &
raw) == -1)
return 0;
141static int is_dir_path(
const char *
path) {
143 if (stat(
path, &st) != 0)
return 0;
144 return S_ISDIR(st.st_mode);
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]);
158 while (base_len + cur < nlen) {
159 char c =
names[i][base_len + cur];
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) {
171 if (lcp == (
size_t)-1 || cur < lcp) lcp = cur;
173 return base_len + (lcp == (size_t)-1 ? 0 : lcp);
182static void rl_word_left(
const char *
out,
size_t len,
size_t *pos) {
184 if (!
out || !pos)
return;
185 if (*pos == 0)
return;
186 while (*pos > 0 &&
out[*pos - 1] ==
' ')
188 while (*pos > 0 &&
out[*pos - 1] !=
' ')
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] !=
' ')
199 while (*pos <
len &&
out[*pos] ==
' ')
208static int complete_load_path(
char *
buf,
size_t *len_io) {
209 size_t len = *len_io;
210 if (
len < 3)
return 0;
214 if (*
p !=
':')
return 0;
217 const char *cmd_start =
p;
218 while (*
p && ((*
p >=
'a' && *
p <=
'z') || (*
p >=
'A' && *
p <=
'Z')))
220 size_t cmd_len = (size_t)(
p - cmd_start);
221 if (cmd_len == 0)
return 0;
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)) {
230 if (!is_supported)
return 0;
231 while (*
p ==
' ' || *
p ==
'\t')
233 size_t arg_off = (size_t)(
p -
buf);
234 if (arg_off >
len)
return 0;
236 char prefix[PATH_MAX];
238 if (
len - arg_off >=
sizeof(prefix))
239 plen =
sizeof(prefix) - 1;
241 plen =
len - arg_off;
242 memcpy(prefix,
buf + arg_off, plen);
245 char expanded[PATH_MAX];
246 if (prefix[0] ==
'~') {
247 const char *home = getenv(
"HOME");
248 if (!home) home = getenv(
"USERPROFILE");
250 snprintf(expanded,
sizeof(expanded),
"%s%s", home, prefix + 1);
252 snprintf(expanded,
sizeof(expanded),
"%s", prefix);
254 snprintf(expanded,
sizeof(expanded),
"%s", prefix);
257 char dirpart[PATH_MAX],
base[PATH_MAX];
258 const char *slash = strrchr(expanded,
'/');
260 size_t dlen = (size_t)(slash - expanded);
262 strcpy(dirpart,
"/");
264 memcpy(dirpart, expanded, dlen);
265 dirpart[dlen] =
'\0';
267 snprintf(
base,
sizeof(
base),
"%s", slash + 1);
269 strcpy(dirpart,
".");
270 snprintf(
base,
sizeof(
base),
"%s", expanded);
273 DIR *dp = opendir(dirpart);
280 const char *
names[1024];
281 char storage[1024][NAME_MAX + 1];
284 size_t blen = strlen(
base);
285 while ((de = readdir(dp)) != NULL) {
286 if (blen == 0 || strncmp(de->d_name,
base, blen) == 0) {
288 snprintf(storage[
count],
sizeof(storage[
count]),
"%s", de->d_name);
302 size_t new_pref_len = lcp_suffix(
names,
count, blen);
305 char completed[PATH_MAX];
308 memcpy(head, expanded, (
size_t)(slash - expanded + 1));
309 head[slash - expanded + 1] =
'\0';
310 strncpy(completed, head,
sizeof(completed));
312 snprintf(completed,
sizeof(completed),
"%s",
"");
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));
319 }
else if (
count == 1) {
320 size_t extra = strlen(
names[0]) - blen;
321 strncat(completed,
names[0] + blen, extra);
328 snprintf(test,
sizeof(test),
"%.*s/%s", (
int)(slash - expanded), expanded,
names[0]);
330 snprintf(test,
sizeof(test),
"%s",
names[0]);
331 if (is_dir_path(test)) {
332 strncat(completed,
"/",
sizeof(completed) - strlen(completed) - 1);
336 if (!changed &&
count > 1) {
338 for (
int i = 0; i <
count; ++i) {
339 fputs(
names[i], stdout);
340 fputc(((i + 1) % 6 == 0) ?
'\n' :
'\t', stdout);
342 if (
count % 6 != 0) putchar(
'\n');
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;
356static char **g_std_syms = NULL;
357static int g_std_syms_count = 0;
358static int g_std_syms_cap = 0;
363static int is_ident_start(
int c) {
364 return (
c ==
'_' || (
c >=
'A' &&
c <=
'Z') || (
c >=
'a' &&
c <=
'z'));
369static int is_ident_char(
int c) {
370 return is_ident_start(
c) || (
c >=
'0' &&
c <=
'9');
376static void std_syms_add(
const char *
name) {
379 for (
int i = 0; i < g_std_syms_count; ++i) {
380 if (strcmp(g_std_syms[i],
name) == 0)
return;
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 *));
387 g_std_syms_cap = ncap;
389 size_t n = strlen(
name);
390 char *cpy = (
char *)malloc(
n + 1);
392 memcpy(cpy,
name,
n + 1);
393 g_std_syms[g_std_syms_count++] = cpy;
399static void scan_symbols_from_file(
const char *
path) {
400 FILE *
f = fopen(
path,
"r");
403 while (fgets(line,
sizeof(line),
f)) {
404 const char *
p = line;
405 while (*
p ==
' ' || *
p ==
'\t')
408 const char *kw = NULL;
409 if (strncmp(
p,
"fun ", 4) == 0) {
412 }
else if (strncmp(
p,
"class ", 6) == 0) {
415 }
else if (strncmp(
p,
"const ", 6) == 0) {
418 }
else if (strncmp(
p,
"var ", 4) == 0) {
423 while (*
p ==
' ' || *
p ==
'\t')
425 if (!is_ident_start((
unsigned char)*
p))
continue;
428 while (is_ident_char((
unsigned char)*
p) && ni + 1 <
sizeof(
name)) {
432 if (ni > 0) std_syms_add(
name);
440static void scan_dir_recursive(
const char *dir) {
441 DIR *dp = opendir(dir);
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);
450 if (stat(
path, &st) != 0)
continue;
451 if (S_ISDIR(st.st_mode)) {
452 scan_dir_recursive(
path);
454 size_t L = strlen(
n);
455 if (L >= 4 && strcmp(
n + (L - 4),
".fun") == 0) {
456 scan_symbols_from_file(
path);
466static void load_stdlib_symbols(
const char *libdir) {
467 if (!libdir || !*libdir)
return;
468 scan_dir_recursive(libdir);
474static void free_stdlib_symbols(
void) {
475 for (
int i = 0; i < g_std_syms_count; ++i)
479 g_std_syms_count = g_std_syms_cap = 0;
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;
492 if (
len == 0)
return 0;
496 while (
start > 0 && is_ident_char((
unsigned char)
buf[
start - 1]))
504 const char *matches[1024];
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];
518 size_t lcp = (size_t)-1;
519 for (
int i = 0; i < mcount; ++i) {
520 size_t nlen = strlen(matches[i]);
522 while (plen + cur < nlen) {
523 char c = matches[i][plen + cur];
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) {
535 if (lcp == (
size_t)-1 || cur < lcp) lcp = cur;
537 size_t new_pref_len = plen + (lcp == (size_t)-1 ? 0 : lcp);
541 size_t write_len = 0;
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);
548 strncat(comp, matches[0] + plen, new_pref_len - plen);
549 write_len = strlen(comp);
553 for (
int i = 0; i < mcount; ++i) {
554 fputs(matches[i], stdout);
555 fputc(((i + 1) % 6 == 0) ?
'\n' :
'\t', stdout);
557 if (mcount % 6 != 0) putchar(
'\n');
564 memmove(
buf +
start, comp, write_len);
565 *len_io =
start + write_len;
579static int read_line_edit(
char *
out,
size_t out_cap,
const char *prompt) {
581 if (prompt) fputs(prompt, stdout), fflush(stdout);
582 if (!fgets(
out, (
int)out_cap, stdin))
return 0;
585 if (prompt) fputs(prompt, stdout), fflush(stdout);
586 if (!repl_enable_raw()) {
587 if (!fgets(
out, (
int)out_cap, stdin))
return 0;
593 int hist_pos = rl_count;
594 char saved_current[4096];
599 static int prev_rows = 1;
606 for (size_t __i = 0; __i < len; ++__i) { \ 607 if (out[__i] == '\n') { \ 613 if (prev_rows > 1) { \ 614 fprintf(stdout, "\x1b[%dA", prev_rows - 1); \ 616 fputc('\r', stdout); \ 618 if (prompt) fputs(prompt, stdout); \ 619 fwrite(out, 1, len, stdout); \ 621 fputs("\x1b[J", stdout); \ 623 if (!has_nl && pos < len) { \ 624 size_t back = (size_t)(len - pos); \ 625 if (back > 0) fprintf(stdout, "\x1b[%zuD", back); \ 640 if (
ch ==
'\r' ||
ch ==
'\n') {
642 if (
len + 1 < out_cap) {
651 }
else if (
ch == 27) {
659 if (cx == EOF)
break;
660 if ((cx >=
'A' && cx <=
'Z') || (cx >=
'a' && cx <=
'z') || cx ==
'~') {
664 if (pi + 1 < (
int)
sizeof(params)) {
665 params[pi++] = (char)cx;
671 if (strstr(params,
";5") != NULL || strcmp(params,
"5") == 0 || strstr(params,
"1;5") != NULL) {
678 for (
size_t __i = 0; __i <
len; ++__i) {
679 if (
out[__i] ==
'\n') {
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';
694 const char *h = rl_hist[hist_pos];
695 size_t hl = strlen(h);
696 if (hl >= out_cap) hl = out_cap - 1;
706 }
else if (
final ==
'B') {
707 if (hist_pos < rl_count) {
709 if (hist_pos == rl_count) {
711 size_t hl = strlen(saved_current);
712 if (hl >= out_cap) hl = out_cap - 1;
713 memcpy(
out, saved_current, hl);
721 const char *h = rl_hist[hist_pos];
722 size_t hl = strlen(h);
723 if (hl >= out_cap) hl = out_cap - 1;
734 }
else if (
final ==
'C') {
742 rl_word_right(
out,
len, &pos);
752 fputs(
"\x1b[C", stdout);
759 }
else if (
final ==
'D') {
767 rl_word_left(
out,
len, &pos);
777 fputs(
"\x1b[D", stdout);
788 }
else if (
ch == 127 ||
ch == 8) {
790 memmove(
out + pos - 1,
out + pos,
len - pos);
799 }
else if (
ch ==
'\t') {
806 int changed = complete_load_path(
out, &newlen);
812 int res = complete_stdlib_ident(
out, &newlen, &pos, out_cap);
815 }
else if (
res == 2) {
824 }
else if (
ch == 15) {
825 if (
len + 1 < out_cap) {
826 memmove(
out + pos + 1,
out + pos,
len - pos);
837 }
else if (
ch >= 32 &&
ch <= 126) {
838 if (
len + 1 < out_cap) {
839 memmove(
out + pos + 1,
out + pos,
len - pos);
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;
873static const char *lstrip(
const char *
s) {
874 while (*
s ==
' ' || *
s ==
'\t')
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'))
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 ==
',') {
899static int compute_open_indent_blocks(
const char *
buf) {
900 int in_block_comment = 0;
902 int have_baseline = 0;
907 const char *line =
p;
908 while (*
p && *
p !=
'\n')
910 const char *line_end =
p;
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;
923 if (in_block_comment)
continue;
926 const char *
s = line;
928 while (
s < line_end && *
s ==
' ') {
932 while (
s < line_end && *
s ==
'\t') {
937 if (
t >= line_end)
continue;
939 if ((
t + 1) <= line_end &&
t[0] ==
'/' && (
t + 1 < line_end && (
t[1] ==
'/' ||
t[1] ==
'*'))) {
942 }
else if (
t[1] ==
'*') {
943 in_block_comment = 1;
948 int lvl = spaces / 2;
949 if (!have_baseline) {
956 }
else if (lvl < cur) {
957 int dec = (cur - lvl);
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;
979 const char *last_sig_line = NULL;
984 if (in_line_comment) {
985 if (
c ==
'\n') in_line_comment = 0;
989 if (in_block_comment) {
990 if (
c ==
'*' &&
p[1] ==
'/') {
991 in_block_comment = 0;
999 if (!in_single && !in_double) {
1000 if (
c ==
'/' &&
p[1] ==
'/') {
1001 in_line_comment = 1;
1005 if (
c ==
'/' &&
p[1] ==
'*') {
1006 in_block_comment = 1;
1013 if (!escape &&
c ==
'\\') {
1018 if (!escape &&
c ==
'\'') {
1026 }
else if (in_double) {
1027 if (!escape &&
c ==
'\\') {
1032 if (!escape &&
c ==
'"') {
1057 if (paren > 0) paren--;
1064 const char *q =
p + 1;
1065 while (*q ==
' ' || *q ==
'\t')
1067 if (*q && *q !=
'\n') last_sig_line = q;
1072 if (!last_sig_line) {
1073 const char *q =
buf;
1074 const char *candidate = NULL;
1076 const char *line_start = q;
1077 while (*q && *q !=
'\n')
1079 const char *
t = line_start;
1080 while (*
t ==
' ' || *
t ==
'\t')
1082 if (*
t && *
t !=
'\n' && *
t !=
'\r') candidate =
t;
1083 if (*q ==
'\n') q++;
1085 last_sig_line = candidate;
1088 if (in_single || in_double || in_block_comment || paren > 0)
return 1;
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) {
1098 if (ends_with_opener(last_sig_line))
return 1;
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");
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) {
1163 char *
buf = (
char *)malloc((
size_t)
sz + 1);
1168 size_t n = fread(
buf, 1, (
size_t)
sz,
f);
1171 if (out_len) *out_len =
n;
1179static int write_entire_file(
const char *
path,
const char *data,
size_t len) {
1180 FILE *
f = fopen(
path,
"wb");
1182 size_t n = fwrite(data, 1,
len,
f);
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;
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;
1208 for (
size_t i = 0; i <
len; i += width) {
1209 size_t chunk = (
len - i < width) ? (
len - i) : width;
1213 for (
size_t j = 0; j < width; ++j) {
1216 unsigned char c = data[i + j];
1217 ascii[j] = (
c >= 32 &&
c <= 126) ? (
char)
c :
'.';
1222 if (j == 7) fputc(
' ',
out);
1224 ascii[chunk < width ? chunk : width] =
'\0';
1232static void print_last_n_lines(
const char *
path,
int n) {
1235 char *content = read_entire_file(
path, &flen);
1237 printf(
"No history available.\n");
1241 for (
size_t i = flen; i > 0; --i) {
1242 if (content[i - 1] ==
'\n') {
1246 printf(
"%s", content + i);
1252 printf(
"%s", content);
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);
1270static void env_show_usage(
void) {
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");
1280static void env_get(
const char *
name) {
1281 const char *
v = getenv(
name);
1283 printf(
"%s=%s\n",
name,
v);
1285 printf(
"%s is not set\n",
name);
1291static void env_set(
const char *
name,
const char *value) {
1293 if (_putenv_s(
name, value ? value :
"") != 0) {
1294 printf(
"Failed to set %s\n",
name);
1297 if (setenv(
name, value ? value :
"", 1) != 0) {
1298 printf(
"Failed to set %s\n",
name);
1310int fun_run_repl(
VM *vm) {
1311 int repl_timing = 0;
1312 int selected_frame = -1;
1315 printf(
"Type :help for commands. Submit an empty line to run.\n");
1319 char libdir[PATH_MAX];
1320 const char *envlib = getenv(
"FUN_LIB_DIR");
1321 if (envlib && *envlib) {
1322 snprintf(libdir,
sizeof(libdir),
"%s", envlib);
1324#ifdef DEFAULT_LIB_DIR 1327 snprintf(libdir,
sizeof(libdir),
"%s",
"lib");
1331 size_t L = strlen(libdir);
1332 if (L > 1 && libdir[L - 1] ==
'/') libdir[L - 1] =
'\0';
1333 load_stdlib_symbols(libdir);
1336 char *buffer = NULL;
1341 char hist_path[1024];
1343 const char *home = getenv(
"HOME");
1344 if (!home) home = getenv(
"USERPROFILE");
1346 snprintf(hist_path,
sizeof(hist_path),
"%s/.fun_history", home);
1347 rl_hist_load_file(hist_path);
1348 hist = fopen(hist_path,
"a+");
1350 snprintf(hist_path,
sizeof(hist_path),
".fun_history");
1351 rl_hist_load_file(hist_path);
1352 hist = fopen(hist_path,
"a+");
1356 if (buflen + 1 > bufcap) {
1357 size_t newcap = bufcap == 0 ? 1024 : bufcap * 2;
1358 while (newcap < buflen + 1)
1360 buffer = (
char *)realloc(buffer, newcap);
1363 buffer[buflen] =
'\0';
1364 int indent_debt = (buflen > 0) ? compute_open_indent_blocks(buffer) : 0;
1368 snprintf(prompt,
sizeof(prompt),
"fun> ");
1369 }
else if (indent_debt > 0) {
1370 snprintf(prompt,
sizeof(prompt),
"...%d> ", indent_debt);
1372 snprintf(prompt,
sizeof(prompt),
"... ");
1377 if (!read_line_edit(line,
sizeof(line), prompt)) {
1382 fputs(prompt, stdout);
1384 if (!fgets(line,
sizeof(line), stdin)) {
1390 const char *lp = line;
1393 if (*lp !=
'\n' && *lp !=
'\r') {
1399 if (!only_nl) append_history(hist, line);
1402 if (line[0] ==
':') {
1404 char arg[2048] = {0};
1405 sscanf(line,
":%63s %2047[^\n]",
cmd, arg);
1407 if (cmd_is_one_of(
cmd, (
const char *[]){
"quit",
"q",
"qu",
"exit", NULL})) {
1409 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"help",
"h", NULL})) {
1412 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"reset",
"re", NULL})) {
1414 printf(
"VM state reset.\n");
1416 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"dump",
"du", NULL})) {
1419 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"globals",
"vars",
"gl",
"v",
"va", NULL})) {
1420 const char *
pattern = lstrip(arg);
1422 printf(
"=== globals%s%s ===\n", filtered ?
" matching '" :
"", filtered ?
pattern :
"");
1423 if (filtered) printf(
"'\n");
1427 if (!filtered || (sv && strstr(sv,
pattern))) {
1428 printf(
"[%d] %s\n", i, sv ? sv :
"nil");
1432 printf(
"===============\n");
1434 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"clear",
"cl", NULL})) {
1436 printf(
"(buffer cleared)\n");
1438 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"print",
"pr", NULL})) {
1440 printf(
"(buffer empty)\n");
1442 if (buflen >= bufcap) {
1443 buffer = (
char *)realloc(buffer, buflen + 1);
1444 bufcap = buflen + 1;
1446 buffer[buflen] =
'\0';
1447 printf(
"%s", buffer);
1448 if (buflen > 0 && buffer[buflen - 1] !=
'\n') printf(
"\n");
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});
1455 int from_file = (arg[0] !=
'\0');
1457 const char *src = NULL;
1458 char *filebuf = NULL;
1461 filebuf = read_entire_file(arg, &flen);
1463 printf(
"Failed to load '%s'\n", arg);
1469 printf(
"(buffer empty)\n");
1472 if (buflen + 1 > bufcap) {
1473 buffer = (
char *)realloc(buffer, buflen + 1);
1474 bufcap = buflen + 1;
1476 buffer[buflen] =
'\0';
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();
1486 if (repl_timing || is_profile) t_run0 = clock();
1488 if (repl_timing || is_profile) t_run1 = clock();
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);
1503 if (!from_file) append_history(hist, buffer);
1505 int line_no = 0, col_no = 0;
1508 printf(
"Parse error at %d:%d: %s\n", line_no, col_no, emsg);
1510 const char *
p = src ? src : buffer;
1511 while (*
p && cur_line < line_no) {
1512 if (*
p ==
'\n') cur_line++;
1515 const char *line_start =
p;
1516 while (*
p && *
p !=
'\n')
1518 fwrite(line_start, 1, (
size_t)(
p - line_start), stdout);
1520 for (
int i = 1; i < col_no; ++i)
1524 if (hist && !from_file) {
1525 fprintf(hist,
"// ERROR %d:%d: %s\n", line_no, col_no, emsg);
1530 printf(
"Parse error.\n");
1532 if (hist && !from_file) {
1533 fprintf(hist,
"// ERROR: parse error\n");
1545 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"save",
"sa", NULL})) {
1546 if (arg[0] ==
'\0') {
1547 printf(
"Usage: :save <file>\n");
1551 printf(
"(buffer empty)\n");
1554 if (!write_entire_file(arg, buffer, buflen)) {
1555 printf(
"Failed to save to '%s'\n", arg);
1557 printf(
"Saved %zu bytes to '%s'\n", buflen, arg);
1560 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"load",
"lo", NULL})) {
1561 if (arg[0] ==
'\0') {
1562 printf(
"Usage: :load <file>\n");
1566 char *filebuf = read_entire_file(arg, &flen);
1568 printf(
"Failed to load '%s'\n", arg);
1571 if (flen + 1 > bufcap) {
1572 size_t newcap = flen + 1;
1573 buffer = (
char *)realloc(buffer, newcap);
1576 memcpy(buffer, filebuf, flen);
1578 buffer[buflen] =
'\0';
1580 printf(
"Loaded %zu bytes into buffer. Use :run or submit an empty line to execute.\n", buflen);
1582 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"paste",
"pa", NULL})) {
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]" :
"");
1588 fputs(
"... paste> ", stdout);
1591 if (!fgets(pline,
sizeof(pline), stdin)) {
1595 if ((strcmp(pline,
".\n") == 0) || (strcmp(pline,
".\r\n") == 0) || (strcmp(pline,
".") == 0)) {
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)
1603 buffer = (
char *)realloc(buffer, newcap);
1606 memcpy(buffer + buflen, pline, pl);
1610 if (buflen + 1 > bufcap) {
1611 buffer = (
char *)realloc(buffer, buflen + 1);
1612 bufcap = buflen + 1;
1614 buffer[buflen] =
'\0';
1617 clock_t t0 = clock();
1619 clock_t t1 = clock();
1620 double ms = (double)(t1 - t0) * 1000.0 / (double)CLOCKS_PER_SEC;
1621 printf(
"[time] %.2f ms\n",
ms);
1625 append_history(hist, buffer);
1627 int line_no = 0, col_no = 0;
1630 printf(
"Parse error at %d:%d: %s\n", line_no, col_no, emsg);
1633 fprintf(hist,
"// ERROR %d:%d: %s\n", line_no, col_no, emsg);
1638 printf(
"Parse error.\n");
1641 fprintf(hist,
"// ERROR: parse error\n");
1649 printf(
"(pasted %zu bytes into buffer)\n", buflen);
1652 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"history",
"hi", NULL})) {
1654 if (arg[0] !=
'\0')
n = atoi(arg);
1656 print_last_n_lines(hist_path,
n);
1658 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"time",
"ti", NULL})) {
1659 if (strcmp(lstrip(arg),
"on") == 0)
1661 else if (strcmp(lstrip(arg),
"off") == 0)
1663 else if (strcmp(lstrip(arg),
"toggle") == 0)
1664 repl_timing = !repl_timing;
1666 printf(
"Usage: :time on|off|toggle (currently %s)\n", repl_timing ?
"on" :
"off");
1669 printf(
"Timing %s\n", repl_timing ?
"enabled" :
"disabled");
1671 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"env",
"en", NULL})) {
1672 const char *spec = lstrip(arg);
1673 if (!spec || *spec ==
'\0') {
1677 const char *
eq = strchr(spec,
'=');
1682 size_t nlen = (size_t)(
eq - spec);
1683 if (nlen >=
sizeof(
name)) nlen =
sizeof(
name) - 1;
1684 memcpy(
name, spec, nlen);
1686 const char *
val =
eq + 1;
1690 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"backtrace",
"bt",
"ba", NULL})) {
1692 printf(
"(no frames)\n");
1695 printf(
"Backtrace (most recent call first):\n");
1696 for (
int i = vm->
fp; i >= 0; --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>";
1701 printf(
" #%d %s at %s ip=%d line=%d\n", i, fname, sfile, ip, vm->
current_line);
1704 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"stack",
"st", NULL})) {
1706 const char *
p = lstrip(arg);
1707 if (
p && *
p)
n = atoi(
p);
1710 printf(
"(stack empty)\n");
1715 printf(
"Stack size=%d\n",
count);
1718 printf(
"[%d] %s\n", i, sv ? sv :
"nil");
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);
1730 printf(
"(no current frame)\n");
1734 const char *fname = (
f->fn &&
f->fn->name) ?
f->fn->name :
"<entry>";
1735 printf(
"Locals in frame #%d (%s):\n",
idx, fname);
1738 if (
f->locals[i].type !=
VAL_NIL) {
1740 printf(
" %d: %s\n", i, sv ? sv :
"nil");
1745 if (!
any) printf(
" (no non-nil locals)\n");
1747 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"frame",
"fr", NULL})) {
1748 const char *
p = lstrip(arg);
1750 printf(
"Usage: :frame N\n");
1755 printf(
"Invalid frame index. Current top is %d\n", vm->
fp);
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);
1764 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"list",
"li", NULL})) {
1766 const char *
p = lstrip(arg);
1767 if (
p && *
p)
k = atoi(
p);
1769 int idx = (selected_frame >= 0 && selected_frame <= vm->
fp) ? selected_frame : vm->
fp;
1771 printf(
"(no current frame)\n");
1775 if (!
f->fn || !
f->fn->source_file) {
1776 printf(
"(no source info)\n");
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) {
1787 const char *
path =
f->fn->source_file;
1789 char mapped_path[1024];
1790 int mapped_line = line;
1796 char *src = read_entire_file(
path, &flen);
1798 printf(
"Unable to read %s\n",
path);
1805 const char *
s = src;
1806 while (*
s && cur <= end) {
1808 while (*
s && *
s !=
'\n')
1810 int print = (cur >=
start && cur <= end);
1812 printf(
"%c %5d | ", (cur == line ?
'>' :
' '), cur);
1813 fwrite(ls, 1, (
size_t)(
s - ls), stdout);
1816 if (*
s ==
'\n')
s++;
1821 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"disasm",
"disassemble",
"di", NULL})) {
1823 const char *
p = lstrip(arg);
1824 if (
p && *
p)
n = atoi(
p);
1826 int idx = (selected_frame >= 0 && selected_frame <= vm->
fp) ? selected_frame : vm->
fp;
1828 printf(
"(no current frame)\n");
1833 printf(
"(no function)\n");
1838 if (
f->fn->instr_count <= 0 ||
f->fn->instructions == NULL) {
1839 printf(
"(no instructions)\n");
1843 int count =
f->fn->instr_count;
1844 int curip =
f->ip - 1;
1845 if (curip < 0) curip = 0;
1848 int from = curip -
n;
1849 if (from < 0) from = 0;
1856 for (
int i = from; i <=
to; ++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);
1862 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"mdump",
"md", NULL})) {
1866 const char *
p = lstrip(arg);
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");
1876 if (sscanf(
p,
"%31s %n", what, &consumed) != 1) {
1877 printf(
"Usage: :mdump WHAT [offset [len]] [raw] [to <file>]\n");
1883 size_t len = (size_t)-1;
1887 while (*
p ==
' ' || *
p ==
'\t')
1889 if (*
p && (isdigit((
unsigned char)*
p))) {
1891 long long v = strtoll(
p, &endp, 10);
1892 if (endp && endp !=
p &&
v >= 0) {
1898 while (*
p ==
' ' || *
p ==
'\t')
1900 if (*
p && (isdigit((
unsigned char)*
p))) {
1902 long long v = strtoll(
p, &endp, 10);
1903 if (endp && endp !=
p &&
v >= 0) {
1910 while (*
p ==
' ' || *
p ==
'\t')
1912 if (strncmp(
p,
"raw", 3) == 0 && (
p[3] ==
'\0' || isspace((
unsigned char)
p[3]))) {
1918 while (*
p ==
' ' || *
p ==
'\t')
1921 char path[PATH_MAX];
1923 if (strncmp(
p,
"to", 2) == 0 && (
p[2] ==
'\0' || isspace((
unsigned char)
p[2]))) {
1925 while (*
p ==
' ' || *
p ==
'\t')
1928 printf(
"Missing <file> after 'to'\n");
1933 while (*
p && !isspace((
unsigned char)*
p) && i + 1 <
sizeof(
path))
1936 if (
path[0] ==
'\0') {
1937 printf(
"Invalid file path\n");
1943 const unsigned char *
base = NULL;
1947 if (strcmp(what,
"code") == 0) {
1948 int idx = (selected_frame >= 0 && selected_frame <= vm->
fp) ? selected_frame : vm->
fp;
1950 printf(
"(no current frame)\n");
1955 printf(
"(no code to dump)\n");
1960 }
else if (strcmp(what,
"consts") == 0) {
1961 int idx = (selected_frame >= 0 && selected_frame <= vm->
fp) ? selected_frame : vm->
fp;
1963 printf(
"(no current frame)\n");
1968 printf(
"(no constants to dump)\n");
1973 }
else if (strcmp(what,
"stack") == 0) {
1976 printf(
"(stack empty)\n");
1979 base = (
const unsigned char *)vm->
stack;
1981 }
else if (strcmp(what,
"globals") == 0) {
1989 printf(
"Unknown region '%s'. Use one of: code, stack, globals, consts\n", what);
1992 if (!
base || total == 0) {
1993 printf(
"(nothing to dump)\n");
1998 printf(
"(empty range: offset beyond end)\n");
2001 size_t avail = total - off;
2002 if (
len == (
size_t)-1 ||
len == 0 ||
len > avail) {
2004 len = avail < 256 ? avail : 256;
2010 fwrite(
base + off, 1,
len, stdout);
2013 printf(
"Hexdump %s: total=%zu, offset=%zu, len=%zu\n", what, total, off,
len);
2014 hexdump_to(stdout,
base + off,
len, off);
2017 FILE *fout = fopen(
path,
"wb");
2019 printf(
"Failed to open '%s' for writing\n",
path);
2023 fwrite(
base + off, 1,
len, fout);
2025 printf(
"Wrote raw bytes (%zu from %s) to %s\n",
len, what,
path);
2027 hexdump_to(fout,
base + off,
len, off);
2029 printf(
"Wrote hexdump (%zu bytes from %s) to %s\n",
len, what,
path);
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");
2040 if (sscanf(spec,
"local[%d]", &
idx) == 1) {
2041 int fidx = (selected_frame >= 0 && selected_frame <= vm->
fp) ? selected_frame : vm->
fp;
2043 printf(
"(out of range)\n");
2048 printf(
"%s\n", sv ? sv :
"nil");
2050 }
else if (sscanf(spec,
"stack[%d]", &
idx) == 1) {
2052 printf(
"(out of range)\n");
2056 printf(
"%s\n", sv ? sv :
"nil");
2058 }
else if (sscanf(spec,
"global[%d]", &
idx) == 1) {
2060 printf(
"(out of range)\n");
2064 printf(
"%s\n", sv ? sv :
"nil");
2067 printf(
"Usage: :printv local[i] | stack[i] | global[i]\n");
2070 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"top",
"to", NULL})) {
2072 printf(
"(stack empty)\n");
2076 printf(
"%s\n", sv ? sv :
"nil");
2079 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"break",
"br", NULL})) {
2080 const char *
p = lstrip(arg);
2082 printf(
"Usage: :break [file:]line\n");
2085 const char *colon = strchr(
p,
':');
2089 size_t fl = (size_t)(colon -
p);
2090 if (fl >=
sizeof(filebuf)) fl =
sizeof(filebuf) - 1;
2091 memcpy(filebuf,
p, fl);
2093 line = atoi(colon + 1);
2095 int idxf = (selected_frame >= 0 && selected_frame <= vm->
fp) ? selected_frame : vm->
fp;
2097 printf(
"(no current frame)\n");
2101 const char *sf = (
f->fn &&
f->fn->source_file) ?
f->fn->source_file : NULL;
2103 printf(
"(no current source file)\n");
2106 snprintf(filebuf,
sizeof(filebuf),
"%s", sf);
2110 printf(
"Invalid line\n");
2115 printf(
"Breakpoint %d set at %s:%d\n",
id, filebuf, line);
2117 printf(
"Failed to set breakpoint\n");
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) {
2124 printf(
"Usage: :info breaks\n");
2127 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"delete",
"de", NULL})) {
2128 int id = atoi(lstrip(arg));
2130 printf(
"Deleted breakpoint %d\n",
id);
2132 printf(
"No such breakpoint %d\n",
id);
2134 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"cb", NULL})) {
2136 printf(
"Cleared all breakpoints\n");
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");
2144 printf(
"Usage: :clear breaks\n");
2147 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"cont",
"continue",
"co", NULL})) {
2149 printf(
"Continuing...\n");
2152 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"step",
"sp", NULL})) {
2154 printf(
"Stepping one instruction...\n");
2157 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"next",
"ne", NULL})) {
2159 printf(
"Stepping over...\n");
2162 }
else if (cmd_is_one_of(
cmd, (
const char *[]){
"finish",
"fi", NULL})) {
2164 printf(
"Running until current frame returns...\n");
2168 printf(
"Unknown command. Use :help\n");
2173 if (is_blank_line(line)) {
2174 if (buflen == 0)
continue;
2176 if (buflen + 1 > bufcap) {
2177 buffer = (
char *)realloc(buffer, buflen + 1);
2178 bufcap = buflen + 1;
2180 buffer[buflen] =
'\0';
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);
2187 printf(
"(incomplete, continue typing)\n");
2194 clock_t t0 = 0, t1 = 0;
2195 if (repl_timing) t0 = clock();
2199 double ms = (double)(t1 - t0) * 1000.0 / (double)CLOCKS_PER_SEC;
2200 printf(
"[time] %.2f ms\n",
ms);
2205 append_history(hist, buffer);
2207 int line_no = 0, col_no = 0;
2210 printf(
"Parse error at %d:%d: %s\n", line_no, col_no, emsg);
2212 const char *
p = buffer;
2213 while (*
p && cur_line < line_no) {
2214 if (*
p ==
'\n') cur_line++;
2217 const char *line_start =
p;
2218 while (*
p && *
p !=
'\n')
2220 fwrite(line_start, 1, (
size_t)(
p - line_start), stdout);
2222 for (
int i = 1; i < col_no; ++i)
2227 fprintf(hist,
"// ERROR %d:%d: %s\n", line_no, col_no, emsg);
2232 printf(
"Parse error.\n");
2235 fprintf(hist,
"// ERROR: parse error\n");
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)
2250 buffer = (
char *)realloc(buffer, newcap);
2253 memcpy(buffer + buflen, line, linelen);
2258 free_stdlib_symbols();
void bytecode_free(Bytecode *bc)
Free a Bytecode and all memory it owns.
Definitions for the Fun VM bytecode: opcodes, instruction format, and bytecode container API.
char * names[MAX_GLOBALS]
Bytecode * parse_string_to_bytecode(const char *source)
Parse a source string and return compiled bytecode.
int parser_last_error(char *msgBuf, unsigned long msgCap, int *outLine, int *outCol)
Retrieve the last parser/compiler error information, if any.
Public API for parsing Fun source into bytecode.
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.
Instruction * instructions
Call frame representing one active function invocation.
The Fun virtual machine state.
int(* on_error_repl)(struct VM *vm)
Value globals[MAX_GLOBALS]
Tagged union representing a Fun value.
char * value_to_string_alloc(const Value *v)
Allocate a printable C string for a Value.
Defines the Value type and associated functions for the Fun VM.
void vm_debug_request_finish(VM *vm)
Request finish (run until the current frame returns).
void vm_debug_clear_breakpoints(VM *vm)
Remove all breakpoints from the VM.
void vm_debug_request_continue(VM *vm)
Resume normal execution (clear stepping state and stop flag).
void vm_debug_request_step(VM *vm)
Request single-step execution (stop after next instruction).
int vm_debug_delete_breakpoint(VM *vm, int id)
Delete a breakpoint by id.
void vm_debug_request_next(VM *vm)
Request step-over (stop after next instruction in current frame).
int vm_debug_add_breakpoint(VM *vm, const char *file, int line)
Add a source breakpoint.
void vm_clear_output(VM *vm)
Clear the VM's buffered output values and partial flags.
void vm_reset(VM *vm)
Reset the VM to a clean state.
void vm_debug_list_breakpoints(VM *vm)
Print active breakpoints to stdout.
void vm_print_output(VM *vm)
Print the VM's buffered output values to stdout.
void vm_dump_globals(VM *vm)
Print all non-nil global variables to stdout for debugging.
Core virtual machine data structures and public VM API.
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...