diff --git a/CMakeLists.txt b/CMakeLists.txt index e16325b..a6448c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.61 LANGUAGES C) +project(fun VERSION 0.37.62 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/src/vm.c b/src/vm.c index 01864f8..16ac20b 100644 --- a/src/vm.c +++ b/src/vm.c @@ -235,6 +235,38 @@ static void fun_vm_exit(int code) { /* Redirect exit inside this TU (affects included opcode handlers) */ #define exit(code) fun_vm_exit(code) +/* Forward decl for stack push used by vm_raise_error */ +static void push_value(VM *vm, Value v); + +/* Raise a runtime error that respects try/catch/finally semantics. + * If a handler is installed for the current frame, jump to it and push + * an error string for the catch clause. Otherwise, print and stop VM. */ +void vm_raise_error(VM *vm, const char *msg) { + if (!vm || vm->fp < 0) { + fprintf(stderr, "Runtime error: %s\n", msg ? msg : ""); + return; + } + Frame *f = &vm->frames[vm->fp]; + if (f->try_sp >= 0) { + char buf[256]; + if (msg) { + snprintf(buf, sizeof(buf), "Runtime error: %s", msg); + } else { + snprintf(buf, sizeof(buf), "Runtime error"); + } + /* push error value and transfer control to handler target */ + Value err = make_string(buf); + push_value(vm, err); + int try_idx = f->try_stack[f->try_sp--]; + int target = f->fn->instructions[try_idx].operand; + f->ip = target; + return; + } + /* No handler: print annotated message and terminate VM */ + fprintf(stderr, "Runtime error: %s\n", msg ? msg : ""); + vm->fp = -1; /* stop execution */ +} + /* Opcode case include index (vm_case_*.inc): - Core/stack/frame: diff --git a/src/vm.h b/src/vm.h index 13ad548..a08a891 100644 --- a/src/vm.h +++ b/src/vm.h @@ -122,6 +122,12 @@ void vm_dump_globals(VM *vm); // run entry Bytecode (pushes first frame) void vm_run(VM *vm, Bytecode *entry); +/* Raise a runtime error that respects try/catch/finally. + * If a try handler is active in the current frame, control jumps to it + * with an error string pushed on the stack. Otherwise, prints the error + * (annotated with location) and terminates execution. */ +void vm_raise_error(VM *vm, const char *msg); + /* --- Debugger API --- */ void vm_debug_reset(VM *vm); int vm_debug_add_breakpoint(VM *vm, const char *file, int line); // returns id >=0 or -1 diff --git a/src/vm/arithmetic/div.c b/src/vm/arithmetic/div.c index 8c821ef..fa37615 100644 --- a/src/vm/arithmetic/div.c +++ b/src/vm/arithmetic/div.c @@ -40,8 +40,8 @@ case OP_DIV: { double da = (a.type == VAL_FLOAT) ? a.d : (double)a.i; double db = (b.type == VAL_FLOAT) ? b.d : (double)b.i; if (db == 0.0) { - fprintf(stderr, "Runtime error: division by zero\n"); - exit(1); + vm_raise_error(vm, "division by zero"); + break; } Value res = make_float(da / db); free_value(a); @@ -49,8 +49,8 @@ case OP_DIV: { push_value(vm, res); } else { if (b.i == 0) { - fprintf(stderr, "Runtime error: division by zero\n"); - exit(1); + vm_raise_error(vm, "division by zero"); + break; } Value res = make_int(a.i / b.i); free_value(a);