// 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 #include // ============================================================ // 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_*). // Returns the native function's return value as a BasValueT. typedef BasValueT (*BasCallExternFnT)(void *ctx, void *funcPtr, 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 variable info (preserved in module for debugger display) typedef struct { char name[BAS_MAX_PROC_NAME]; 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; } 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) 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