1
0
Fork 0
forked from fun/fun

Jump into REPL when executing with --repl-on-error and an error occurs, when the REPL is builtin you will have access to the full stack then. (0.25.0)

This commit is contained in:
Johannes Findeisen 2025-10-06 04:10:36 +02:00
commit c71857a068
9 changed files with 309 additions and 7 deletions

View file

@ -24,11 +24,13 @@ static void print_usage(const char *prog) {
printf("Fun %s\n", FUN_VERSION);
printf("Usage:\n");
#ifdef FUN_WITH_REPL
printf(" %s [--trace|-t] [script.fun]\n", prog ? prog : "fun");
printf(" %s [--trace|-t] [--repl-on-error] [script.fun]\n", prog ? prog : "fun");
printf(" %s --help | -h\n", prog ? prog : "fun");
printf(" %s --version | -V\n", prog ? prog : "fun");
printf("\n");
printf("Options:\n --trace, -t Print executed ops and stack tops during run\n\n");
printf("Options:\n");
printf(" --trace, -t Print executed ops and stack tops during run\n");
printf(" --repl-on-error Enter interactive REPL on runtime error with stack preserved\n\n");
printf("When no script is provided, a REPL starts. Submit an empty line to execute the buffer.\n");
#else
printf(" %s [--trace|-t] <script.fun>\n", prog ? prog : "fun");
@ -59,6 +61,13 @@ int main(int argc, char **argv) {
vm.trace_enabled = 1;
continue;
}
#ifdef FUN_WITH_REPL
if (strcmp(arg, "--repl-on-error") == 0) {
vm.repl_on_error = 1;
vm.on_error_repl = fun_run_repl; /* provide REPL entry to core VM */
continue;
}
#endif
/* first non-option assumed to be script path */
break;
}

View file

@ -845,6 +845,9 @@ static void show_repl_help(void) {
printf(" :history [N] Show last N lines of history (default 50)\n");
printf(" :time on|off|toggle Toggle/enable/disable timing\n");
printf(" :env [NAME[=VALUE]] Get or set environment variable\n");
printf(" :backtrace | :bt Show backtrace of VM frames (most recent first)\n");
printf(" :stack [N] Show top N (default all) stack values\n");
printf(" :locals [FRAME] Show locals of frame (default: current top frame)\n");
}
static char *read_entire_file(const char *path, size_t *out_len) {
@ -1253,6 +1256,54 @@ int fun_run_repl(VM *vm) {
env_set(name, val);
}
continue;
} else if (strcmp(cmd, "backtrace") == 0 || strcmp(cmd, "bt") == 0) {
if (vm->fp < 0) { printf("(no frames)\n"); continue; }
printf("Backtrace (most recent call first):\n");
for (int i = vm->fp; i >= 0; --i) {
Frame *f = &vm->frames[i];
const char *fname = (f->fn && f->fn->name) ? f->fn->name : "<entry>";
const char *sfile = (f->fn && f->fn->source_file) ? f->fn->source_file : "<unknown>";
int ip = f->ip - 1;
printf(" #%d %s at %s ip=%d line=%d\n", i, fname, sfile, ip, vm->current_line);
}
continue;
} else if (strcmp(cmd, "stack") == 0) {
int n = -1;
const char *p = lstrip(arg);
if (p && *p) n = atoi(p);
int count = vm->sp + 1;
if (count <= 0) { printf("(stack empty)\n"); continue; }
int start = 0;
if (n > 0 && n < count) start = count - n;
printf("Stack size=%d\n", count);
for (int i = start; i < count; ++i) {
char *sv = value_to_string_alloc(&vm->stack[i]);
printf("[%d] %s\n", i, sv ? sv : "nil");
free(sv);
}
continue;
} else if (strcmp(cmd, "locals") == 0) {
int idx = vm->fp;
const char *p = lstrip(arg);
if (p && *p) {
int v = atoi(p);
if (v >= 0 && v <= vm->fp) idx = v;
}
if (idx < 0) { printf("(no current frame)\n"); continue; }
Frame *f = &vm->frames[idx];
const char *fname = (f->fn && f->fn->name) ? f->fn->name : "<entry>";
printf("Locals in frame #%d (%s):\n", idx, fname);
int any = 0;
for (int i = 0; i < MAX_FRAME_LOCALS; ++i) {
if (f->locals[i].type != VAL_NIL) {
char *sv = value_to_string_alloc(&f->locals[i]);
printf(" %d: %s\n", i, sv ? sv : "nil");
free(sv);
any = 1;
}
}
if (!any) printf(" (no non-nil locals)\n");
continue;
} else {
printf("Unknown command. Use :help\n");
continue;

View file

@ -77,6 +77,27 @@ static int fun_vm_fprintf(FILE *stream, const char *fmt, ...) {
/* Redirect fprintf within this translation unit so opcode handlers use our wrapper */
#define fprintf fun_vm_fprintf
/* Intercept exit() in this translation unit so VM errors don't terminate the process outright */
#include <setjmp.h>
static jmp_buf g_vm_err_jmp;
static void fun_vm_exit(int code) {
if (g_active_vm && g_active_vm->repl_on_error) {
/* Jump back to vm_run to allow dropping into the REPL with intact VM state */
longjmp(g_vm_err_jmp, code ? code : 1);
}
/* Fallback: terminate immediately if not in REPL-on-error mode */
#ifdef _WIN32
_exit(code);
#else
_Exit(code);
#endif
}
/* Redirect exit inside this TU (affects included opcode handlers) */
#define exit(code) fun_vm_exit(code)
/*
Opcode case include index (vm_case_*.inc):
- Core/stack/frame:
@ -202,6 +223,8 @@ void vm_init(VM *vm) {
vm->instr_count = 0;
vm->exit_code = 0;
vm->trace_enabled = 0;
vm->repl_on_error = 0;
vm->on_error_repl = NULL;
for (int i = 0; i < MAX_GLOBALS; ++i)
vm->globals[i] = make_nil();
}
@ -249,6 +272,20 @@ void vm_run(VM *vm, Bytecode *entry) {
vm->current_line = 1;
g_active_vm = vm;
/* set error trap if REPL-on-error is enabled */
if (vm->repl_on_error) {
int jcode = setjmp(g_vm_err_jmp);
if (jcode != 0) {
/* We got here from a trapped exit() in an error path */
fprintf(stderr, "Entering REPL due to runtime error (code %d)\n", jcode);
if (vm->on_error_repl) {
vm->on_error_repl(vm);
}
g_active_vm = NULL;
return;
}
}
/* start with entry frame (no args) */
vm_push_frame(vm, entry, 0, NULL);

View file

@ -49,7 +49,7 @@ typedef struct {
Value locals[MAX_FRAME_LOCALS];
} Frame;
typedef struct {
struct VM {
Value stack[STACK_SIZE];
int sp;
@ -68,7 +68,11 @@ typedef struct {
int exit_code; // process exit code set by OP_EXIT
int trace_enabled; // when non-zero, print executed ops and stack
} VM;
int repl_on_error; // when non-zero, enter REPL on runtime error (preserve stack)
int (*on_error_repl)(struct VM *vm); // optional hook to run REPL on error
};
typedef struct VM VM;
// initialize VM (zero state)
void vm_init(VM *vm);