1
0
Fork 0
forked from fun/fun

Added more exception handling after debugging ./examples/byte_for_demo.fun. (0.37.0)

This commit is contained in:
Johannes Findeisen 2025-12-10 23:27:06 +01:00
commit 26ddaf161e
12 changed files with 398 additions and 158 deletions

View file

@ -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)

View file

@ -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 <you@hanez.org>
* 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

View file

@ -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 <you@hanez.org>
* 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
*/

View file

@ -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 {

View file

@ -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;
}

View file

@ -1,77 +0,0 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* 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 <tcl.h>
#include <tk.h>
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 <windows.h>
Sleep(1);
#else
#include <time.h>
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

135
src/vm.c
View file

@ -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 <tcl.h>
#include <tk.h>
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 <windows.h>
Sleep(1);
#else
#include <time.h>
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(<iniparser/iniparser.h>)
@ -44,10 +102,12 @@
#endif
#include "vm/ini/handles.h"
#endif
#ifdef FUN_WITH_SQLITE
#include <sqlite3.h>
#include "vm/sqlite/common.c"
#endif
#ifdef FUN_WITH_LIBSQL
#include <sqlite3.h> /* 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 <libxml/parser.h>
#include <libxml/tree.h>
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"

View file

@ -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

33
src/vm/core/throw.c Normal file
View file

@ -0,0 +1,33 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* 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, "<error>\n");
}
free_value(err);
/* clear frames to stop execution */
vm->fp = -1;
break;
}

13
src/vm/core/try_pop.c Normal file
View file

@ -0,0 +1,13 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* 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;
}

18
src/vm/core/try_push.c Normal file
View file

@ -0,0 +1,18 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* 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;
}

View file

@ -1,60 +0,0 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* 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 <libxml/parser.h>
#include <libxml/tree.h>
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 */