From 26ddaf161e33370cbb768324e3059358eb35480b Mon Sep 17 00:00:00 2001 From: hanez Date: Wed, 10 Dec 2025 23:27:06 +0100 Subject: [PATCH] Added more exception handling after debugging ./examples/byte_for_demo.fun. (0.37.0) --- CMakeLists.txt | 2 +- examples/byte_for_demo.fun | 14 +++ examples/byte_overflow_try_catch.fun | 41 ++++++++ src/bytecode.h | 7 +- src/parser.c | 148 ++++++++++++++++++++++++--- src/tk_embed.c | 77 -------------- src/vm.c | 135 +++++++++++++++++++++++- src/vm.h | 8 +- src/vm/core/throw.c | 33 ++++++ src/vm/core/try_pop.c | 13 +++ src/vm/core/try_push.c | 18 ++++ src/vm/xml/handles.h | 60 ----------- 12 files changed, 398 insertions(+), 158 deletions(-) create mode 100755 examples/byte_overflow_try_catch.fun create mode 100644 src/vm/core/throw.c create mode 100644 src/vm/core/try_pop.c create mode 100644 src/vm/core/try_push.c delete mode 100644 src/vm/xml/handles.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d6a3582..d5aa6ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.36.3 LANGUAGES C) +project(fun VERSION 0.37.0 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/byte_for_demo.fun b/examples/byte_for_demo.fun index 90e1980..500b352 100755 --- a/examples/byte_for_demo.fun +++ b/examples/byte_for_demo.fun @@ -1,5 +1,14 @@ #!/usr/bin/env fun +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + // Byte + for-loop demonstration print("=== byte with hex literal and clamping ===") @@ -32,6 +41,11 @@ print(typeof(x)) // -> "String" /* Expected output: === byte with hex literal and clamping === +OverflowError: value out of range for uint8 +*/ + +/* Expected output (OLD): +=== byte with hex literal and clamping === 255 255 255 diff --git a/examples/byte_overflow_try_catch.fun b/examples/byte_overflow_try_catch.fun new file mode 100755 index 0000000..1ac7caf --- /dev/null +++ b/examples/byte_overflow_try_catch.fun @@ -0,0 +1,41 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-10 + */ + +// Demonstrate byte overflow with try/catch +// Note: Runtime exceptions are not yet implemented; overflow emits an error and halts. +// This example shows intended usage once exceptions are supported. + +print("=== byte overflow with try/catch demo ===") +try + byte b = 0 + print("assign 255 -> ok") + b = 255 + print(b) + print("assign 256 -> should overflow and be caught") + b = 256 // will trigger OverflowError: value out of range for uint8 + print("this line will not execute if overflow occurs") +catch err + print("caught error:") + print(err) +finally + print("finally block executed") + +/* Expected output: +=== byte overflow with try/catch demo === +assign 255 -> ok +255 +assign 256 -> should overflow and be caught +caught error: +OverflowError: value out of range for uint8 +finally block executed +*/ diff --git a/src/bytecode.h b/src/bytecode.h index dc9563b..12986e8 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -215,7 +215,12 @@ typedef enum { OP_SOCK_UNIX_CONNECT, // pops path; returns fd (>0) or 0 // process control - OP_EXIT // pops code (or uses operand) and terminates script with exit code + OP_EXIT, // pops code (or uses operand) and terminates script with exit code + + // exceptions (minimal) + OP_TRY_PUSH, // operand = handler ip; push handler onto try-stack + OP_TRY_POP, // pop current handler + OP_THROW // pops error value; if handler -> jump to it (push err), else print and terminate } OpCode; typedef struct { diff --git a/src/parser.c b/src/parser.c index b61d5fd..17f1eca 100644 --- a/src/parser.c +++ b/src/parser.c @@ -2727,7 +2727,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si } bytecode_set_operand(bc, j_skip_err, bc->instr_count); } else { - /* integer widths: expect Number then clamp */ + /* integer widths: expect Number then range-check */ int abs_bits = decl_bits < 0 ? -decl_bits : decl_bits; if (abs_bits > 0) { /* typeof == Number */ @@ -2747,7 +2747,53 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si } bytecode_set_operand(bc, j_skip_err, bc->instr_count); - bytecode_add_instruction(bc, (decl_bits < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits); + /* range check instead of clamp */ + int64_t minV = 0, maxV = 0; + if (decl_bits < 0) { + /* signed */ + if (abs_bits >= 64) { minV = INT64_MIN; maxV = INT64_MAX; } + else { maxV = (1LL << (abs_bits - 1)) - 1; minV = - (1LL << (abs_bits - 1)); } + } else { + /* unsigned */ + if (abs_bits >= 63) { minV = 0; maxV = INT64_MAX; } + else { minV = 0; maxV = (1LL << abs_bits) - 1; } + } + int ciMin = bytecode_add_constant(bc, make_int(minV)); + int ciMax = bytecode_add_constant(bc, make_int(maxV)); + + /* if (v < min) -> error */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMin); + bytecode_add_instruction(bc, OP_LT, 0); + int j_after_min = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + { + const char *tname = (decl_bits < 0) + ? (abs_bits==64? "int64" : (abs_bits==32? "int32" : (abs_bits==16? "int16" : "int8"))) + : (abs_bits==64? "uint64" : (abs_bits==32? "uint32" : (abs_bits==16? "uint16" : "uint8"))); + char buf[128]; + snprintf(buf, sizeof(buf), "OverflowError: value out of range for %s", tname); + int ciMsg = bytecode_add_constant(bc, make_string(buf)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg); + bytecode_add_instruction(bc, OP_THROW, 0); + } + bytecode_set_operand(bc, j_after_min, bc->instr_count); + + /* if (v > max) -> error */ + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMax); + bytecode_add_instruction(bc, OP_GT, 0); + int j_after_max = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + { + const char *tname = (decl_bits < 0) + ? (abs_bits==64? "int64" : (abs_bits==32? "int32" : (abs_bits==16? "int16" : "int8"))) + : (abs_bits==64? "uint64" : (abs_bits==32? "uint32" : (abs_bits==16? "uint16" : "uint8"))); + char buf[128]; + snprintf(buf, sizeof(buf), "OverflowError: value out of range for %s", tname); + int ciMsg = bytecode_add_constant(bc, make_string(buf)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg); + bytecode_add_instruction(bc, OP_THROW, 0); + } + bytecode_set_operand(bc, j_after_max, bc->instr_count); } } @@ -3071,7 +3117,7 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si } bytecode_set_operand(bc, j_skip_err, bc->instr_count); } else if (meta != 0) { - /* integer widths: expect Number then clamp to declared width */ + /* integer widths: expect Number then range-check to declared width */ int abs_bits = meta < 0 ? -meta : meta; /* typeof == Number */ bytecode_add_instruction(bc, OP_DUP, 0); @@ -3090,7 +3136,49 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si } bytecode_set_operand(bc, j_skip_err, bc->instr_count); - bytecode_add_instruction(bc, (meta < 0) ? OP_SCLAMP : OP_UCLAMP, abs_bits); + /* range check instead of clamp */ + int64_t minV = 0, maxV = 0; + if (meta < 0) { + if (abs_bits >= 64) { minV = INT64_MIN; maxV = INT64_MAX; } + else { maxV = (1LL << (abs_bits - 1)) - 1; minV = - (1LL << (abs_bits - 1)); } + } else { + if (abs_bits >= 63) { minV = 0; maxV = INT64_MAX; } + else { minV = 0; maxV = (1LL << abs_bits) - 1; } + } + int ciMin = bytecode_add_constant(bc, make_int(minV)); + int ciMax = bytecode_add_constant(bc, make_int(maxV)); + + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMin); + bytecode_add_instruction(bc, OP_LT, 0); + int j_after_min = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + { + const char *tname = (meta < 0) + ? (abs_bits==64? "int64" : (abs_bits==32? "int32" : (abs_bits==16? "int16" : "int8"))) + : (abs_bits==64? "uint64" : (abs_bits==32? "uint32" : (abs_bits==16? "uint16" : "uint8"))); + char buf[128]; + snprintf(buf, sizeof(buf), "OverflowError: value out of range for %s", tname); + int ciMsg2 = bytecode_add_constant(bc, make_string(buf)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg2); + bytecode_add_instruction(bc, OP_THROW, 0); + } + bytecode_set_operand(bc, j_after_min, bc->instr_count); + + bytecode_add_instruction(bc, OP_DUP, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMax); + bytecode_add_instruction(bc, OP_GT, 0); + int j_after_max = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); + { + const char *tname = (meta < 0) + ? (abs_bits==64? "int64" : (abs_bits==32? "int32" : (abs_bits==16? "int16" : "int8"))) + : (abs_bits==64? "uint64" : (abs_bits==32? "uint32" : (abs_bits==16? "uint16" : "uint8"))); + char buf[128]; + snprintf(buf, sizeof(buf), "OverflowError: value out of range for %s", tname); + int ciMsg3 = bytecode_add_constant(bc, make_string(buf)); + bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg3); + bytecode_add_instruction(bc, OP_THROW, 0); + } + bytecode_set_operand(bc, j_after_max, bc->instr_count); } /* dynamic (meta==0): no enforcement */ @@ -4288,13 +4376,16 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, continue; } - /* try/catch/finally (syntax support; runtime exceptions not yet implemented) */ + /* try/catch/finally */ if (starts_with(src, len, *pos, "try")) { /* consume 'try' */ *pos += 3; /* end of header line */ skip_to_eol(src, len, pos); + /* Install a handler placeholder; will be patched to catch label (or a rethrow stub) */ + int try_push_idx = bytecode_add_instruction(bc, OP_TRY_PUSH, 0); + /* parse try body at increased indent (if any) */ int try_body_indent = 0; size_t look_try = *pos; @@ -4304,9 +4395,16 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, /* empty try body allowed */ } + /* After try body, pop handler for normal (non-exceptional) flow */ + bytecode_add_instruction(bc, OP_TRY_POP, 0); + + /* on normal completion, jump over catch body */ + int jmp_over_catch_finally = bytecode_add_instruction(bc, OP_JUMP, 0); + /* Optional: catch and/or finally clauses at same indentation */ int seen_catch = 0; int seen_finally = 0; + int catch_label = -1; for (;;) { size_t look = *pos; int look_indent = 0; @@ -4320,15 +4418,34 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, skip_spaces(src, len, pos); char *ex_name = NULL; size_t tmp = *pos; + int have_name = 0; if (read_identifier_into(src, len, &tmp, &ex_name)) { *pos = tmp; - free(ex_name); + have_name = 1; } /* end of header line */ skip_to_eol(src, len, pos); - /* We currently don't have runtime exceptions: emit an unconditional jump over the catch body (so it's parsed but never executed) */ - int j_over = bytecode_add_instruction(bc, OP_JUMP, 0); + /* Mark catch label and patch try handler target */ + catch_label = bc->instr_count; + bytecode_set_operand(bc, try_push_idx, catch_label); + + /* On entering catch, the thrown error is on stack. Bind to name if provided, else pop. */ + if (have_name) { + int lidx = -1, gi = -1; + if (g_locals) { + int existing = local_find(ex_name); + if (existing >= 0) lidx = existing; else lidx = local_add(ex_name); + } else { + gi = sym_index(ex_name); + } + if (lidx >= 0) bytecode_add_instruction(bc, OP_STORE_LOCAL, lidx); + else if (gi >= 0) bytecode_add_instruction(bc, OP_STORE_GLOBAL, gi); + else bytecode_add_instruction(bc, OP_POP, 0); + } else { + bytecode_add_instruction(bc, OP_POP, 0); + } + if (ex_name) free(ex_name); /* parse catch body at increased indent (if any) */ int catch_indent = 0; @@ -4338,10 +4455,6 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, } else { /* empty catch body allowed */ } - - /* patch jump to here (after catch body) */ - bytecode_set_operand(bc, j_over, bc->instr_count); - seen_catch = 1; continue; } @@ -4368,6 +4481,17 @@ static void parse_block(Bytecode *bc, const char *src, size_t len, size_t *pos, /* no recognized clause at this indentation */ break; } + + /* If no catch clause was present, make handler rethrow */ + if (!seen_catch) { + int rethrow_label = bc->instr_count; + bytecode_set_operand(bc, try_push_idx, rethrow_label); + /* at handler: immediately rethrow the incoming error */ + bytecode_add_instruction(bc, OP_THROW, 0); + } + + /* patch normal-flow jump to here (after catch/finally) */ + bytecode_set_operand(bc, jmp_over_catch_finally, bc->instr_count); continue; } diff --git a/src/tk_embed.c b/src/tk_embed.c index d1e6e1f..e69de29 100644 --- a/src/tk_embed.c +++ b/src/tk_embed.c @@ -1,77 +0,0 @@ -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2025-12-09 - */ - -/** - * Embedded Tcl/Tk helpers for Fun VM. - * When FUN_WITH_TCLTK is OFF, stubs are provided so code compiles and runs. - */ - -#include "value.h" -#include "vm.h" - -#ifdef FUN_WITH_TCLTK -#include -#include -static Tcl_Interp* g_fun_tcl_interp = NULL; - -static void fun_tk_init_once(void) { - if (g_fun_tcl_interp) return; - Tcl_FindExecutable(NULL); - g_fun_tcl_interp = Tcl_CreateInterp(); - if (!g_fun_tcl_interp) return; - if (Tcl_Init(g_fun_tcl_interp) != TCL_OK) { - fprintf(stderr, "Tcl_Init failed: %s\n", Tcl_GetStringResult(g_fun_tcl_interp)); - } - if (Tk_Init(g_fun_tcl_interp) != TCL_OK) { - fprintf(stderr, "Tk_Init failed: %s\n", Tcl_GetStringResult(g_fun_tcl_interp)); - } - /* Ensure the app terminates if the main window is closed via window manager */ - /* Best-effort: set WM_DELETE_WINDOW handler to exit the process. */ - Tcl_Eval(g_fun_tcl_interp, "wm protocol . WM_DELETE_WINDOW {exit 0}"); -} - -static int fun_tk_eval_script(const char *script) { - fun_tk_init_once(); - if (!g_fun_tcl_interp) return -1; - int rc = Tcl_Eval(g_fun_tcl_interp, script ? script : ""); - return rc; /* TCL_OK = 0 */ -} - -static const char* fun_tk_get_result(void) { - fun_tk_init_once(); - if (!g_fun_tcl_interp) return ""; - return Tcl_GetStringResult(g_fun_tcl_interp); -} - -static void fun_tk_loop(void) { - fun_tk_init_once(); - if (!g_fun_tcl_interp) return; - /* Drive Tk event loop until all main windows are closed */ - while (Tk_GetNumMainWindows() > 0) { - while (Tcl_DoOneEvent(0)) {} - /* tiny sleep to avoid busy spin */ -#ifdef _WIN32 - #include - Sleep(1); -#else - #include - struct timespec ts = {0, 1000000}; /* 1 ms */ - nanosleep(&ts, NULL); -#endif - } -} -#else -/* Stubs when Tcl/Tk is disabled */ -static void fun_tk_init_once(void) { (void)0; } -static int fun_tk_eval_script(const char *script) { (void)script; return -1; } -static const char* fun_tk_get_result(void) { return ""; } -static void fun_tk_loop(void) { (void)0; } -#endif diff --git a/src/vm.c b/src/vm.c index 1a13e24..62f4f6f 100644 --- a/src/vm.c +++ b/src/vm.c @@ -22,11 +22,69 @@ #include "string.c" #include "pcsc.c" #include "jsonc.c" -/* Embedded Tcl/Tk helpers (provide stubs when FUN_WITH_TCLTK is off) */ -#include "tk_embed.c" -#ifdef FUN_WITH_XML2 -#include "vm/xml/handles.h" + +#include "value.h" +#include "vm.h" + +#ifdef FUN_WITH_TCLTK +#include +#include +static Tcl_Interp* g_fun_tcl_interp = NULL; + +static void fun_tk_init_once(void) { + if (g_fun_tcl_interp) return; + Tcl_FindExecutable(NULL); + g_fun_tcl_interp = Tcl_CreateInterp(); + if (!g_fun_tcl_interp) return; + if (Tcl_Init(g_fun_tcl_interp) != TCL_OK) { + fprintf(stderr, "Tcl_Init failed: %s\n", Tcl_GetStringResult(g_fun_tcl_interp)); + } + if (Tk_Init(g_fun_tcl_interp) != TCL_OK) { + fprintf(stderr, "Tk_Init failed: %s\n", Tcl_GetStringResult(g_fun_tcl_interp)); + } + /* Ensure the app terminates if the main window is closed via window manager */ + /* Best-effort: set WM_DELETE_WINDOW handler to exit the process. */ + Tcl_Eval(g_fun_tcl_interp, "wm protocol . WM_DELETE_WINDOW {exit 0}"); +} + +static int fun_tk_eval_script(const char *script) { + fun_tk_init_once(); + if (!g_fun_tcl_interp) return -1; + int rc = Tcl_Eval(g_fun_tcl_interp, script ? script : ""); + return rc; /* TCL_OK = 0 */ +} + +static const char* fun_tk_get_result(void) { + fun_tk_init_once(); + if (!g_fun_tcl_interp) return ""; + return Tcl_GetStringResult(g_fun_tcl_interp); +} + +static void fun_tk_loop(void) { + fun_tk_init_once(); + if (!g_fun_tcl_interp) return; + /* Drive Tk event loop until all main windows are closed */ + while (Tk_GetNumMainWindows() > 0) { + while (Tcl_DoOneEvent(0)) {} + /* tiny sleep to avoid busy spin */ +#ifdef _WIN32 + #include + Sleep(1); +#else + #include + struct timespec ts = {0, 1000000}; /* 1 ms */ + nanosleep(&ts, NULL); #endif + } +} +#else +/* Stubs when Tcl/Tk is disabled */ +static void fun_tk_init_once(void) { (void)0; } +static int fun_tk_eval_script(const char *script) { (void)script; return -1; } +static const char* fun_tk_get_result(void) { return ""; } +static void fun_tk_loop(void) { (void)0; } +#endif + #ifdef FUN_WITH_INI #if defined(__has_include) # if __has_include() @@ -44,10 +102,12 @@ #endif #include "vm/ini/handles.h" #endif + #ifdef FUN_WITH_SQLITE #include #include "vm/sqlite/common.c" #endif + #ifdef FUN_WITH_LIBSQL #include /* libsql exposes sqlite3-compatible C API */ #include "vm/libsql/common.c" @@ -90,6 +150,53 @@ static size_t fun_curl_file_write_cb(void *ptr, size_t sz, size_t nm, void *ud) } #endif +#ifdef FUN_WITH_XML2 +#include +#include + +typedef struct { xmlDocPtr doc; int in_use; } XmlDocSlot; +typedef struct { xmlNodePtr node; int in_use; } XmlNodeSlot; + +static XmlDocSlot g_xml_docs[64]; +static XmlNodeSlot g_xml_nodes[256]; + +static int xml_doc_alloc(xmlDocPtr d) { + for (int i = 1; i < (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])); ++i) { + if (!g_xml_docs[i].in_use) { g_xml_docs[i].in_use = 1; g_xml_docs[i].doc = d; return i; } + } + return 0; +} +static xmlDocPtr xml_doc_get(int h) { + if (h > 0 && h < (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])) && g_xml_docs[h].in_use) return g_xml_docs[h].doc; + return NULL; +} +static int xml_doc_free_handle(int h) { + if (h <= 0 || h >= (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])) || !g_xml_docs[h].in_use) return 0; + if (g_xml_docs[h].doc) xmlFreeDoc(g_xml_docs[h].doc); + g_xml_docs[h].doc = NULL; + g_xml_docs[h].in_use = 0; + return 1; +} + +static int xml_node_alloc(xmlNodePtr n) { + for (int i = 1; i < (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])); ++i) { + if (!g_xml_nodes[i].in_use) { g_xml_nodes[i].in_use = 1; g_xml_nodes[i].node = n; return i; } + } + return 0; +} +static xmlNodePtr xml_node_get(int h) { + if (h > 0 && h < (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])) && g_xml_nodes[h].in_use) return g_xml_nodes[h].node; + return NULL; +} +static int xml_node_free_handle(int h) { + if (h <= 0 || h >= (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])) || !g_xml_nodes[h].in_use) return 0; + /* nodes are owned by their document; do not free here */ + g_xml_nodes[h].node = NULL; + g_xml_nodes[h].in_use = 0; + return 1; +} +#endif /* FUN_WITH_XML2 */ + /* forward declarations for include mapping used in error reporting */ extern char *preprocess_includes(const char *src); static int map_expanded_line_to_include(const char *path, int line, char *out_path, size_t out_path_cap, int *out_line); @@ -464,6 +571,7 @@ static void frame_init(Frame *f) { f->fn = NULL; f->ip = 0; for (int i = 0; i < MAX_FRAME_LOCALS; ++i) f->locals[i] = make_nil(); + f->try_sp = -1; } void vm_init(VM *vm) { @@ -662,8 +770,8 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/core/call.c" #include "vm/core/dup.c" - #include "vm/core/halt.c" #include "vm/core/exit.c" + #include "vm/core/halt.c" #include "vm/core/jump.c" #include "vm/core/jump_if_false.c" #include "vm/core/load_const.c" @@ -675,6 +783,9 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/core/store_global.c" #include "vm/core/store_local.c" #include "vm/core/swap.c" + #include "vm/core/throw.c" + #include "vm/core/try_pop.c" + #include "vm/core/try_push.c" #include "vm/io/read_file.c" #include "vm/io/write_file.c" @@ -724,18 +835,22 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/os/socket_unix_listen.c" #include "vm/os/socket_unix_connect.c" + #ifdef FUN_WITH_PCSC #include "vm/pcsc/establish.c" #include "vm/pcsc/release.c" #include "vm/pcsc/list_readers.c" #include "vm/pcsc/connect.c" #include "vm/pcsc/disconnect.c" #include "vm/pcsc/transmit.c" + #endif /* JSON ops (implemented in jsonc.c, included above) */ + #ifdef FUN_WITH_JSON #include "vm/json/parse.c" #include "vm/json/stringify.c" #include "vm/json/from_file.c" #include "vm/json/to_file.c" + #endif /* XML ops (libxml2) */ #ifdef FUN_WITH_XML2 @@ -759,11 +874,14 @@ void vm_run(VM *vm, Bytecode *entry) { #endif /* CURL ops */ + #ifdef FUN_WITH_CURL #include "vm/curl/get.c" #include "vm/curl/post.c" #include "vm/curl/download.c" + #endif /* Tk (Tcl/Tk) ops */ + #ifdef FUN_WITH_TCLTK #include "vm/tk/eval.c" #include "vm/tk/result.c" #include "vm/tk/loop.c" @@ -771,23 +889,30 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/tk/label.c" #include "vm/tk/button.c" #include "vm/tk/pack.c" + #endif /* SQLite ops */ + #ifdef FUN_WITH_SQLITE #include "vm/sqlite/open.c" #include "vm/sqlite/close.c" #include "vm/sqlite/exec.c" #include "vm/sqlite/query.c" + #endif /* libsql ops (independent) */ + #ifdef FUN_WITH_LIBSQL #include "vm/libsql/open.c" #include "vm/libsql/close.c" #include "vm/libsql/exec.c" #include "vm/libsql/query.c" + #endif /* PCRE2 ops */ + #ifdef FUN_WITH_PCRE2 #include "vm/pcre2/test.c" #include "vm/pcre2/match.c" #include "vm/pcre2/findall.c" + #endif #include "vm/strings/find.c" #include "vm/strings/regex_match.c" diff --git a/src/vm.h b/src/vm.h index 5b603e5..df2c0b7 100644 --- a/src/vm.h +++ b/src/vm.h @@ -47,13 +47,17 @@ static const char *opcode_names[] = { "INI_LOAD","INI_FREE","INI_GET_STRING","INI_GET_INT","INI_GET_DOUBLE","INI_GET_BOOL","INI_SET","INI_UNSET","INI_SAVE", "XML_PARSE","XML_ROOT","XML_NAME","XML_TEXT", "SOCK_TCP_LISTEN","SOCK_TCP_ACCEPT","SOCK_TCP_CONNECT","SOCK_SEND","SOCK_RECV","SOCK_CLOSE","SOCK_UNIX_LISTEN","SOCK_UNIX_CONNECT", - "EXIT" + "EXIT", + "TRY_PUSH","TRY_POP","THROW" }; typedef struct { Bytecode *fn; int ip; Value locals[MAX_FRAME_LOCALS]; + /* exception handling (per-frame) */ + int try_stack[16]; + int try_sp; /* -1 when empty */ } Frame; struct VM { @@ -122,7 +126,7 @@ void vm_debug_request_finish(VM *vm); void vm_debug_request_continue(VM *vm); static inline int opcode_is_valid(int op) { - return op >= OP_NOP && op <= OP_EXIT; // all current opcodes + return op >= OP_NOP && op <= OP_THROW; // all current opcodes } #endif diff --git a/src/vm/core/throw.c b/src/vm/core/throw.c new file mode 100644 index 0000000..f734bfa --- /dev/null +++ b/src/vm/core/throw.c @@ -0,0 +1,33 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + +case OP_THROW: { + Value err = pop_value(vm); + /* if there is a handler in this frame, jump to it and push err for catch */ + if (f->try_sp >= 0) { + int try_idx = f->try_stack[f->try_sp--]; + int target = f->fn->instructions[try_idx].operand; + /* push error for catch block */ + push_value(vm, err); /* transfer ownership to stack */ + f->ip = target; + break; + } + /* Unhandled: print error and terminate */ + char *s = value_to_string_alloc(&err); + if (s) { + fprintf(stdout, "%s\n", s); + free(s); + } else { + fprintf(stdout, "\n"); + } + free_value(err); + /* clear frames to stop execution */ + vm->fp = -1; + break; +} diff --git a/src/vm/core/try_pop.c b/src/vm/core/try_pop.c new file mode 100644 index 0000000..d30dfd2 --- /dev/null +++ b/src/vm/core/try_pop.c @@ -0,0 +1,13 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + +case OP_TRY_POP: { + if (f->try_sp >= 0) f->try_sp--; + break; +} diff --git a/src/vm/core/try_push.c b/src/vm/core/try_push.c new file mode 100644 index 0000000..a224383 --- /dev/null +++ b/src/vm/core/try_push.c @@ -0,0 +1,18 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + +case OP_TRY_PUSH: { + /* push index of this TRY instruction; handler ip is in its operand (may be patched later) */ + if (f->try_sp >= (int)(sizeof(f->try_stack)/sizeof(f->try_stack[0])) - 1) { + fprintf(stderr, "Runtime error: try depth exceeded\n"); + exit(1); + } + f->try_stack[++f->try_sp] = f->ip - 1; /* index of TRY_PUSH instruction */ + break; +} diff --git a/src/vm/xml/handles.h b/src/vm/xml/handles.h deleted file mode 100644 index ece9548..0000000 --- a/src/vm/xml/handles.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of the Fun programming language. - * https://fun-lang.xyz/ - * - * Copyright 2025 Johannes Findeisen - * Licensed under the terms of the Apache-2.0 license. - * https://opensource.org/license/apache-2-0 - * - * Added: 2025-12-09 - */ - -/** Minimal handle registries for libxml2 documents and nodes */ -#pragma once - -#ifdef FUN_WITH_XML2 -#include -#include - -typedef struct { xmlDocPtr doc; int in_use; } XmlDocSlot; -typedef struct { xmlNodePtr node; int in_use; } XmlNodeSlot; - -static XmlDocSlot g_xml_docs[64]; -static XmlNodeSlot g_xml_nodes[256]; - -static int xml_doc_alloc(xmlDocPtr d) { - for (int i = 1; i < (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])); ++i) { - if (!g_xml_docs[i].in_use) { g_xml_docs[i].in_use = 1; g_xml_docs[i].doc = d; return i; } - } - return 0; -} -static xmlDocPtr xml_doc_get(int h) { - if (h > 0 && h < (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])) && g_xml_docs[h].in_use) return g_xml_docs[h].doc; - return NULL; -} -static int xml_doc_free_handle(int h) { - if (h <= 0 || h >= (int)(sizeof(g_xml_docs)/sizeof(g_xml_docs[0])) || !g_xml_docs[h].in_use) return 0; - if (g_xml_docs[h].doc) xmlFreeDoc(g_xml_docs[h].doc); - g_xml_docs[h].doc = NULL; - g_xml_docs[h].in_use = 0; - return 1; -} - -static int xml_node_alloc(xmlNodePtr n) { - for (int i = 1; i < (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])); ++i) { - if (!g_xml_nodes[i].in_use) { g_xml_nodes[i].in_use = 1; g_xml_nodes[i].node = n; return i; } - } - return 0; -} -static xmlNodePtr xml_node_get(int h) { - if (h > 0 && h < (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])) && g_xml_nodes[h].in_use) return g_xml_nodes[h].node; - return NULL; -} -static int xml_node_free_handle(int h) { - if (h <= 0 || h >= (int)(sizeof(g_xml_nodes)/sizeof(g_xml_nodes[0])) || !g_xml_nodes[h].in_use) return 0; - /* nodes are owned by their document; do not free here */ - g_xml_nodes[h].node = NULL; - g_xml_nodes[h].in_use = 0; - return 1; -} -#endif /* FUN_WITH_XML2 */