492 lines
20 KiB
C
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
|