Added more exception handling after debugging ./examples/byte_for_demo.fun. (0.37.0)
This commit is contained in:
parent
ccd03d3bec
commit
26ddaf161e
12 changed files with 398 additions and 158 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
41
examples/byte_overflow_try_catch.fun
Executable file
41
examples/byte_overflow_try_catch.fun
Executable 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
|
||||
*/
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
148
src/parser.c
148
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
135
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 <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"
|
||||
|
|
|
|||
8
src/vm.h
8
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
|
||||
|
|
|
|||
33
src/vm/core/throw.c
Normal file
33
src/vm/core/throw.c
Normal 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
13
src/vm/core/try_pop.c
Normal 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
18
src/vm/core/try_push.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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 */
|
||||
Loading…
Add table
Add a link
Reference in a new issue