192 lines
7 KiB
C
192 lines
7 KiB
C
/**
|
|
* 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
|
|
*/
|
|
|
|
#ifndef FUN_VM_H
|
|
#define FUN_VM_H
|
|
|
|
#include "bytecode.h"
|
|
#include <stddef.h>
|
|
|
|
#define MAX_FRAMES 128
|
|
#define MAX_FRAME_LOCALS 64
|
|
#define MAX_GLOBALS 128
|
|
#define OUTPUT_SIZE 1024
|
|
#define STACK_SIZE 1024
|
|
|
|
static const char *opcode_names[] = {
|
|
"NOP","LOAD_CONST","LOAD_LOCAL","STORE_LOCAL",
|
|
"LOAD_GLOBAL","STORE_GLOBAL","ADD","SUB","MUL","DIV",
|
|
"LT","LTE","GT","GTE","EQ","NEQ","POP","JUMP",
|
|
"JUMP_IF_FALSE","CALL","RETURN","PRINT","ECHO","HALT",
|
|
"LINE",
|
|
"MOD","AND","OR","NOT","DUP","SWAP",
|
|
"MAKE_ARRAY","INDEX_GET","INDEX_SET",
|
|
"LEN","PUSH","APOP","SET","INSERT","REMOVE","SLICE",
|
|
"TO_NUMBER","TO_STRING","CAST","TYPEOF",
|
|
"SPLIT","JOIN","SUBSTR","FIND",
|
|
"REGEX_MATCH","REGEX_SEARCH","REGEX_REPLACE",
|
|
"CONTAINS","INDEX_OF","CLEAR",
|
|
"ENUMERATE","ZIP",
|
|
"MIN","MAX","CLAMP","ABS","POW","RANDOM_SEED","RANDOM_INT",
|
|
"MAKE_MAP","KEYS","VALUES","HAS_KEY",
|
|
"READ_FILE","WRITE_FILE","ENV","INPUT_LINE","PROC_RUN","PROC_SYSTEM",
|
|
"TIME_NOW_MS","CLOCK_MONO_MS","DATE_FORMAT",
|
|
"THREAD_SPAWN","THREAD_JOIN","SLEEP_MS",
|
|
"RANDOM_NUMBER",
|
|
"BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR",
|
|
"JSON_PARSE","JSON_STRINGIFY","JSON_FROM_FILE","JSON_TO_FILE",
|
|
"CURL_GET","CURL_POST","CURL_DOWNLOAD",
|
|
"SQLITE_OPEN","SQLITE_CLOSE","SQLITE_EXEC","SQLITE_QUERY",
|
|
"LIBSQL_OPEN","LIBSQL_CLOSE","LIBSQL_EXEC","LIBSQL_QUERY",
|
|
"PCSC_ESTABLISH","PCSC_RELEASE","PCSC_LIST_READERS","PCSC_CONNECT","PCSC_DISCONNECT","PCSC_TRANSMIT",
|
|
"PCRE2_TEST","PCRE2_MATCH","PCRE2_FINDALL",
|
|
"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",
|
|
"OS_LIST_DIR",
|
|
"TK_BIND",
|
|
"SERIAL_OPEN","SERIAL_CONFIG","SERIAL_SEND","SERIAL_RECV","SERIAL_CLOSE",
|
|
"TK_EVAL","TK_RESULT","TK_LOOP","TK_WM_TITLE","TK_LABEL","TK_BUTTON","TK_PACK",
|
|
"TRY_PUSH","TRY_POP","THROW",
|
|
"FMIN","FMAX",
|
|
/* Rust FFI demo */
|
|
"RUST_HELLO","RUST_HELLO_ARGS","RUST_HELLO_ARGS_RETURN","RUST_GET_SP","RUST_SET_EXIT",
|
|
/* C++ demo */
|
|
"CPP_ADD",
|
|
/* Notcurses TUI (optional) */
|
|
"NC_INIT","NC_SHUTDOWN","NC_CLEAR","NC_DRAW_TEXT","NC_GETCH"
|
|
};
|
|
|
|
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 {
|
|
Value stack[STACK_SIZE];
|
|
int sp;
|
|
|
|
Frame frames[MAX_FRAMES];
|
|
int fp; // frame pointer, -1 when no frame
|
|
|
|
Value globals[MAX_GLOBALS];
|
|
|
|
Value output[OUTPUT_SIZE]; // store printed values
|
|
int output_count;
|
|
int output_is_partial[OUTPUT_SIZE]; // 1 when the corresponding output entry should not end with newline (echo)
|
|
|
|
long long instr_count; // executed instructions in the last vm_run
|
|
|
|
int current_line; // last executed source line (debug)
|
|
|
|
int exit_code; // process exit code set by OP_EXIT
|
|
|
|
int trace_enabled; // when non-zero, print executed ops and stack
|
|
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
|
|
|
|
/* --- Debugger state --- */
|
|
int debug_step_mode; // 0 none, 1 step, 2 next, 3 finish
|
|
int debug_step_target_fp; // target frame pointer for next/finish
|
|
long long debug_step_start_ic; // instruction count snapshot when step/next requested
|
|
int debug_stop_requested; // force a pause at loop top
|
|
|
|
struct {
|
|
char *file; // strdup'ed file path
|
|
int line; // 1-based line
|
|
int active; // 1 if active
|
|
} breakpoints[64];
|
|
int break_count; // number of active breakpoints
|
|
};
|
|
|
|
typedef struct VM VM;
|
|
|
|
// initialize VM (zero state)
|
|
void vm_init(VM *vm);
|
|
|
|
// helper function to clear the output
|
|
void vm_clear_output(VM *vm);
|
|
void vm_print_output(VM *vm);
|
|
void vm_free(VM *vm);
|
|
|
|
// reset VM to initial state (free globals/locals/output; keep VM object)
|
|
void vm_reset(VM *vm);
|
|
|
|
// print non-nil globals (index and value) to stdout
|
|
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
|
|
int vm_debug_delete_breakpoint(VM *vm, int id); // returns 1 on success
|
|
void vm_debug_clear_breakpoints(VM *vm);
|
|
void vm_debug_list_breakpoints(VM *vm);
|
|
void vm_debug_request_step(VM *vm);
|
|
void vm_debug_request_next(VM *vm);
|
|
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_NC_GETCH; // all current opcodes (including optional NC_*)
|
|
}
|
|
|
|
/* --- Minimal C ABI helpers for FFI (Rust opcode experiments) --- */
|
|
/* Pop an int64 from VM stack (errors if not an int/float); returns integer-converted value. */
|
|
int64_t vm_pop_i64(VM *vm);
|
|
/* Push an int64 onto VM stack. */
|
|
void vm_push_i64(VM *vm, int64_t v);
|
|
|
|
/* Example Rust-implemented opcode (adds top two ints on stack) */
|
|
int fun_op_radd(VM *vm);
|
|
|
|
/* Example Rust function returning a demo C string (null-terminated). */
|
|
const char *fun_rust_get_string(void);
|
|
/* Rust function that prints a passed C string, returns 0 on success. */
|
|
int fun_rust_print_string(const char *msg);
|
|
/* Rust function that returns a newly allocated duplicate of the input C string. */
|
|
char *fun_rust_echo_string(const char *input);
|
|
/* Free a C string previously returned by fun_rust_echo_string. */
|
|
void fun_rust_string_free(char *ptr);
|
|
|
|
/* C++ demo opcode entry point (C ABI) */
|
|
int fun_op_cpp_add(struct VM *vm);
|
|
|
|
/* --- Extended C ABI for Rust to access VM internals (unsafe) --- */
|
|
/* Size helpers for Rust side to compute offsets and do pointer math */
|
|
size_t vm_sizeof(void);
|
|
size_t vm_value_sizeof(void);
|
|
|
|
/* Get a mutable byte pointer to the VM object. Extremely unsafe; intended for
|
|
* low-level FFI where Rust wants parity access with C code. */
|
|
void *vm_as_mut_ptr(VM *vm);
|
|
|
|
/* Offsets of commonly accessed VM fields to avoid re-declaring the struct layout in Rust */
|
|
size_t vm_offset_of_exit_code(void);
|
|
size_t vm_offset_of_sp(void);
|
|
size_t vm_offset_of_stack(void);
|
|
size_t vm_offset_of_globals(void);
|
|
|
|
/* Demo Rust ops using the extended ABI */
|
|
int fun_op_rget_sp(VM *vm);
|
|
int fun_op_rset_exit(VM *vm);
|
|
|
|
#endif
|