DVX_GUI/apps/dvxbasic/runtime/vm.h

492 lines
20 KiB
C

// vm.h -- DVX BASIC virtual machine
//
// Stack-based p-code interpreter. Executes compiled BASIC bytecode.
// Embeddable: the host provides I/O callbacks. No DVX dependencies.
//
// Usage:
// BasVmT *vm = basVmCreate();
// basVmSetPrintCallback(vm, myPrintFn, myCtx);
// basVmSetInputCallback(vm, myInputFn, myCtx);
// basVmLoadModule(vm, compiledCode, codeLen, constants, numConsts);
// BasVmResultE result = basVmRun(vm);
// basVmDestroy(vm);
#ifndef DVXBASIC_VM_H
#define DVXBASIC_VM_H
#include "values.h"
#include "dvxTypes.h"
#include "dvxPlat.h"
#include <stdint.h>
#include <stdbool.h>
// ============================================================
// Limits
// ============================================================
#define BAS_VM_STACK_SIZE 256 // evaluation stack depth
#define BAS_VM_CALL_STACK_SIZE 64 // max call nesting
#define BAS_VM_MAX_GLOBALS 512 // global variable slots
#define BAS_VM_MAX_LOCALS 64 // locals per stack frame
#define BAS_VM_MAX_FOR_DEPTH 32 // nested FOR loops
#define BAS_VM_MAX_FILES 16 // open file channels
// ============================================================
// Result codes
// ============================================================
typedef enum {
BAS_VM_OK, // program completed normally
BAS_VM_HALTED, // HALT instruction reached
BAS_VM_YIELDED, // DoEvents yielded control
BAS_VM_ERROR, // runtime error
BAS_VM_STACK_OVERFLOW,
BAS_VM_STACK_UNDERFLOW,
BAS_VM_CALL_OVERFLOW,
BAS_VM_DIV_BY_ZERO,
BAS_VM_TYPE_MISMATCH,
BAS_VM_OUT_OF_MEMORY,
BAS_VM_BAD_OPCODE,
BAS_VM_FILE_ERROR,
BAS_VM_SUBSCRIPT_RANGE,
BAS_VM_USER_ERROR, // ON ERROR raised
BAS_VM_STEP_LIMIT, // step limit reached (not an error)
BAS_VM_BREAKPOINT // hit breakpoint or step completed (not an error)
} BasVmResultE;
// ============================================================
// I/O callbacks (host-provided)
// ============================================================
// Print callback: called for PRINT output.
// text is a null-terminated string. newline indicates whether
// to advance to the next line after printing.
typedef void (*BasPrintFnT)(void *ctx, const char *text, bool newline);
// Input callback: called for INPUT statement.
// prompt is the text to display. The callback must fill buf
// (up to bufSize-1 chars, null-terminated). Returns true on
// success, false on cancel/error.
typedef bool (*BasInputFnT)(void *ctx, const char *prompt, char *buf, int32_t bufSize);
// DoEvents callback: called for DoEvents statement.
// The host should process pending events and return. Returns
// true to continue execution, false to stop the program.
typedef bool (*BasDoEventsFnT)(void *ctx);
// Breakpoint callback: called when a breakpoint fires inside a
// nested sub call (runSubLoop). The host should update debug UI
// (highlight current line, update locals/call stack) and return.
typedef void (*BasBreakpointFnT)(void *ctx, int32_t line);
// ============================================================
// UI callbacks (host-provided, for form/control system)
// ============================================================
//
// The VM resolves all UI operations through these callbacks.
// Control types, property names, and method names are passed
// as strings -- the host maps them to its native widget system.
// This keeps the VM independent of any specific GUI toolkit.
// Get a property from a control. Returns the value.
// ctrlRef is an opaque pointer previously returned by createControl/findControl.
typedef BasValueT (*BasUiGetPropFnT)(void *ctx, void *ctrlRef, const char *propName);
// Set a property on a control.
typedef void (*BasUiSetPropFnT)(void *ctx, void *ctrlRef, const char *propName, BasValueT value);
// Call a method on a control. args[0..argc-1] are the arguments.
// Returns the method's return value (or a zero-value for void methods).
typedef BasValueT (*BasUiCallMethodFnT)(void *ctx, void *ctrlRef, const char *methodName, BasValueT *args, int32_t argc);
// Create a control on a form. typeName is the widget type ("Button",
// "TextBox", etc.). ctrlName is the VB control name ("Command1").
// Returns an opaque control reference.
typedef void *(*BasUiCreateCtrlFnT)(void *ctx, void *formRef, const char *typeName, const char *ctrlName);
// Find an existing control by name on a form. Returns NULL if not found.
typedef void *(*BasUiFindCtrlFnT)(void *ctx, void *formRef, const char *ctrlName);
// Find a control array element by name and index. Returns NULL if not found.
typedef void *(*BasUiFindCtrlIdxFnT)(void *ctx, void *formRef, const char *ctrlName, int32_t index);
// Load a form by name. Returns an opaque form reference.
typedef void *(*BasUiLoadFormFnT)(void *ctx, const char *formName);
// Unload a form.
typedef void (*BasUiUnloadFormFnT)(void *ctx, void *formRef);
// Show a form. modal=true for modal display.
typedef void (*BasUiShowFormFnT)(void *ctx, void *formRef, bool modal);
// Hide a form (keep in memory).
typedef void (*BasUiHideFormFnT)(void *ctx, void *formRef);
// Display a message box. Returns the button clicked (1=OK, 6=Yes, 7=No, 2=Cancel).
typedef int32_t (*BasUiMsgBoxFnT)(void *ctx, const char *message, int32_t flags);
// Display an input box. Returns the entered string (empty on cancel).
typedef BasStringT *(*BasUiInputBoxFnT)(void *ctx, const char *prompt, const char *title, const char *defaultText);
// Collected UI callbacks
typedef struct {
BasUiGetPropFnT getProp;
BasUiSetPropFnT setProp;
BasUiCallMethodFnT callMethod;
BasUiCreateCtrlFnT createCtrl;
BasUiFindCtrlFnT findCtrl;
BasUiFindCtrlIdxFnT findCtrlIdx;
BasUiLoadFormFnT loadForm;
BasUiUnloadFormFnT unloadForm;
BasUiShowFormFnT showForm;
BasUiHideFormFnT hideForm;
BasUiMsgBoxFnT msgBox;
BasUiInputBoxFnT inputBox;
void *ctx; // passed as first arg to all callbacks
} BasUiCallbacksT;
// ============================================================
// SQL callbacks (host-provided)
// ============================================================
typedef struct {
int32_t (*sqlOpen)(const char *path);
void (*sqlClose)(int32_t db);
bool (*sqlExec)(int32_t db, const char *sql);
const char *(*sqlError)(int32_t db);
int32_t (*sqlQuery)(int32_t db, const char *sql);
bool (*sqlNext)(int32_t rs);
bool (*sqlEof)(int32_t rs);
int32_t (*sqlFieldCount)(int32_t rs);
const char *(*sqlFieldName)(int32_t rs, int32_t col);
const char *(*sqlFieldText)(int32_t rs, int32_t col);
const char *(*sqlFieldByName)(int32_t rs, const char *name);
int32_t (*sqlFieldInt)(int32_t rs, int32_t col);
double (*sqlFieldDbl)(int32_t rs, int32_t col);
void (*sqlFreeResult)(int32_t rs);
int32_t (*sqlAffectedRows)(int32_t db);
} BasSqlCallbacksT;
// ============================================================
// External library callbacks (host-provided)
// ============================================================
//
// The VM resolves external functions by library name + function
// name at runtime. The host uses dlsym() or equivalent to find
// the native function pointer. A second callback marshals the
// call -- converting BasValueT arguments to native C types and
// the return value back to BasValueT.
// Resolve a function by library and symbol name.
// Returns an opaque function pointer, or NULL if not found.
// The VM caches the result so this is called only once per
// unique (library, function) pair.
typedef void *(*BasResolveExternFnT)(void *ctx, const char *libName, const char *funcName);
// Call a resolved native function. funcPtr is the pointer returned
// by resolveExtern. args[0..argc-1] are the BASIC arguments.
// retType is the expected return type (BAS_TYPE_*).
// libName is the DECLARE LIBRARY name (e.g. "dvx"); the host may use
// it to inject hidden parameters (e.g. AppContextT for "dvx" library).
// Returns the native function's return value as a BasValueT.
typedef BasValueT (*BasCallExternFnT)(void *ctx, void *funcPtr, const char *libName, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType);
typedef struct {
BasResolveExternFnT resolveExtern;
BasCallExternFnT callExtern;
void *ctx;
} BasExternCallbacksT;
// ============================================================
// Extern function cache (resolved on first call)
// ============================================================
#define BAS_EXTERN_CACHE_SIZE 128
typedef struct {
uint16_t libNameIdx; // constant pool index for library name
uint16_t funcNameIdx; // constant pool index for function name
void *funcPtr; // resolved native function pointer (NULL = not yet resolved)
} BasExternCacheEntryT;
// ============================================================
// Call stack frame
// ============================================================
typedef struct {
int32_t returnPc; // instruction to return to
int32_t baseSlot; // base index in locals array
int32_t localCount; // number of locals in this frame
BasValueT locals[BAS_VM_MAX_LOCALS];
} BasCallFrameT;
// ============================================================
// FOR loop state
// ============================================================
typedef struct {
int32_t varIdx; // loop variable slot index
uint8_t scopeTag; // 0 = global, 1 = local, 2 = form
BasValueT limit; // upper bound
BasValueT step; // step value
int32_t loopTop; // PC of the loop body start
} BasForStateT;
// ============================================================
// File channel
// ============================================================
typedef struct {
void *handle; // FILE* or platform-specific
int32_t mode; // 0=closed, 1=input, 2=output, 3=append, 4=random, 5=binary
} BasFileChannelT;
// ============================================================
// Procedure table entry (retained from symbol table for runtime)
// ============================================================
#define BAS_MAX_PROC_NAME 64
typedef struct {
char name[BAS_MAX_PROC_NAME]; // SUB/FUNCTION name (case-preserved)
int32_t codeAddr; // entry point in code[]
int32_t paramCount; // number of parameters
int32_t localCount; // number of local variables (for debugger)
uint8_t returnType; // BAS_TYPE_* (0 for SUB)
bool isFunction; // true = FUNCTION, false = SUB
} BasProcEntryT;
// Debug UDT field definition (preserved for debugger watch)
typedef struct {
char name[BAS_MAX_PROC_NAME];
uint8_t dataType;
} BasDebugFieldT;
// Debug UDT type definition (preserved for debugger watch)
typedef struct {
char name[BAS_MAX_PROC_NAME];
int32_t typeId; // matches BasUdtT.typeId
BasDebugFieldT *fields; // malloc'd array
int32_t fieldCount;
} BasDebugUdtDefT;
// Debug variable info (preserved in module for debugger display)
typedef struct {
char name[BAS_MAX_PROC_NAME];
char formName[BAS_MAX_PROC_NAME]; // form name for SCOPE_FORM vars (empty for others)
uint8_t scope; // SCOPE_GLOBAL, SCOPE_LOCAL, SCOPE_FORM
uint8_t dataType; // BAS_TYPE_*
int32_t index; // variable slot index
int32_t procIndex; // -1 for globals, else index into procs[]
} BasDebugVarT;
// ============================================================
// Per-form variable info (how many form-scoped vars each form needs)
// ============================================================
typedef struct {
char formName[BAS_MAX_PROC_NAME];
int32_t varCount;
int32_t initCodeAddr; // offset in module->code for per-form init (-1 = none)
int32_t initCodeLen; // length of init bytecode
} BasFormVarInfoT;
// ============================================================
// Compiled module (output of the compiler)
// ============================================================
typedef struct {
uint8_t *code; // p-code bytecode
int32_t codeLen;
BasStringT **constants; // string constant pool
int32_t constCount;
int32_t globalCount; // number of global variable slots needed
int32_t entryPoint; // PC of the first instruction (module-level code)
BasValueT *dataPool; // DATA statement value pool
int32_t dataCount; // number of values in the data pool
BasProcEntryT *procs; // procedure table (SUBs and FUNCTIONs)
int32_t procCount;
BasFormVarInfoT *formVarInfo; // per-form variable counts
int32_t formVarInfoCount;
BasDebugVarT *debugVars; // variable names for debugger
int32_t debugVarCount;
BasDebugUdtDefT *debugUdtDefs; // UDT type definitions for debugger
int32_t debugUdtDefCount;
} BasModuleT;
// ============================================================
// VM state
// ============================================================
typedef struct {
// Program
BasModuleT *module;
// Execution
int32_t pc; // program counter
bool running;
bool ended; // END statement executed -- program should terminate
bool yielded;
int32_t stepLimit; // max steps per basVmRun (0 = unlimited)
int32_t stepCount; // steps executed in last basVmRun
int32_t currentLine; // source line from last OP_LINE (debugger)
// Debug state
int32_t *breakpoints; // sorted line numbers (host-managed)
int32_t breakpointCount;
bool debugBreak; // break at next OP_LINE (step into)
bool debugPaused; // true = reject basVmCallSub (break mode)
int32_t stepOverDepth; // call depth target for step over (-1 = off)
int32_t stepOutDepth; // call depth target for step out (-1 = off)
int32_t runToCursorLine; // target line for run-to-cursor (-1 = off)
// Evaluation stack
BasValueT stack[BAS_VM_STACK_SIZE];
int32_t sp; // stack pointer (index of next free slot)
// Call stack
BasCallFrameT callStack[BAS_VM_CALL_STACK_SIZE];
int32_t callDepth;
// FOR loop stack
BasForStateT forStack[BAS_VM_MAX_FOR_DEPTH];
int32_t forDepth;
// Global variables
BasValueT globals[BAS_VM_MAX_GLOBALS];
// File channels (1-based, index 0 unused)
BasFileChannelT files[BAS_VM_MAX_FILES];
// DATA/READ pointer
int32_t dataPtr; // current READ position in data pool
// String comparison mode
bool compareTextMode; // true = case-insensitive comparisons
// Error handling
int32_t errorHandler; // PC of ON ERROR GOTO handler (0 = none)
int32_t errorNumber; // current Err number
int32_t errorPc; // PC of the instruction that caused the error (for RESUME)
int32_t errorNextPc; // PC of the next instruction after error (for RESUME NEXT)
bool inErrorHandler; // true when executing error handler code
char errorMsg[256]; // current error description
// I/O callbacks
BasPrintFnT printFn;
void *printCtx;
BasInputFnT inputFn;
void *inputCtx;
BasDoEventsFnT doEventsFn;
void *doEventsCtx;
BasBreakpointFnT breakpointFn;
void *breakpointCtx;
// UI callbacks (set by host for form/control support)
BasUiCallbacksT ui;
BasSqlCallbacksT sql;
// External library callbacks (set by host)
BasExternCallbacksT ext;
// Extern function cache (resolved on first call)
BasExternCacheEntryT externCache[BAS_EXTERN_CACHE_SIZE];
int32_t externCacheCount;
// Current form reference (set during event dispatch)
void *currentForm;
BasValueT *currentFormVars; // points to current form's variable storage
int32_t currentFormVarCount; // number of form variables
// App object paths (set by host)
char appPath[DVX_MAX_PATH]; // App.Path -- project/app directory
char appConfig[DVX_MAX_PATH]; // App.Config -- writable config directory
char appData[DVX_MAX_PATH]; // App.Data -- writable data directory
} BasVmT;
// ============================================================
// API
// ============================================================
// Create a new VM instance.
BasVmT *basVmCreate(void);
// Destroy a VM instance and free all resources.
void basVmDestroy(BasVmT *vm);
// Load a compiled module into the VM.
void basVmLoadModule(BasVmT *vm, BasModuleT *module);
// Execute the loaded module. Returns when the program ends,
// halts, yields, or hits an error.
BasVmResultE basVmRun(BasVmT *vm);
// Execute a single instruction. Returns the result.
// Useful for stepping/debugging.
BasVmResultE basVmStep(BasVmT *vm);
// Reset the VM to initial state (clear stack, globals, PC).
void basVmReset(BasVmT *vm);
// Set I/O callbacks.
void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx);
void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx);
void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx);
// Set UI callbacks (for form/control system).
void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui);
void basVmSetSqlCallbacks(BasVmT *vm, const BasSqlCallbacksT *sql);
// Set external library callbacks (for DECLARE LIBRARY support).
void basVmSetExternCallbacks(BasVmT *vm, const BasExternCallbacksT *ext);
// Set the current form context (called by host during event dispatch).
void basVmSetCurrentForm(BasVmT *vm, void *formRef);
void basVmSetCurrentFormVars(BasVmT *vm, BasValueT *vars, int32_t count);
// Set the step limit for basVmRun. 0 = unlimited (default).
// When the limit is reached, basVmRun returns BAS_VM_STEP_LIMIT.
// The VM remains in a runnable state -- call basVmRun again to continue.
void basVmSetStepLimit(BasVmT *vm, int32_t limit);
// Push/pop values on the evaluation stack (for host integration).
bool basVmPush(BasVmT *vm, BasValueT val);
bool basVmPop(BasVmT *vm, BasValueT *val);
// Get the current error message.
const char *basVmGetError(const BasVmT *vm);
// ---- Debugger API ----
// Set the breakpoint list (sorted array of source line numbers, host-owned).
void basVmSetBreakpoints(BasVmT *vm, int32_t *lines, int32_t count);
// Step into: break at the next OP_LINE instruction.
void basVmStepInto(BasVmT *vm);
// Step over: break when call depth returns to current level.
void basVmStepOver(BasVmT *vm);
// Step out: break when call depth drops below current level.
void basVmStepOut(BasVmT *vm);
// Run to cursor: break when reaching the specified source line.
void basVmRunToCursor(BasVmT *vm, int32_t line);
// Get the current source line (from the last OP_LINE instruction).
int32_t basVmGetCurrentLine(const BasVmT *vm);
// Call a SUB by code address from the host.
// Pushes a call frame, runs until the SUB returns, then restores
// the previous execution state. Returns true if the SUB was called
// and returned normally, false on error or if the VM was not idle.
bool basVmCallSub(BasVmT *vm, int32_t codeAddr);
bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount);
// Call a SUB and read back modified argument values.
// outArgs receives copies of the locals after the SUB returns.
// outCount specifies how many args to read back.
bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount, BasValueT *outArgs, int32_t outCount);
#endif // DVXBASIC_VM_H