// The MIT License (MIT) // // Copyright (C) 2026 Scott Duensing // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // vm.h -- DVX BASIC virtual machine // // Stack-based p-code interpreter. Executes compiled BASIC bytecode. // Embeddable: the host provides PRINT/INPUT/UI/extern callbacks. File, // directory and path handling use the C library plus the DVX platform // helpers (dvxPlat.h), so the VM links against the platform layer. // // Usage: // BasVmT *vm = basVmCreate(); // basVmSetPrintCallback(vm, myPrintFn, myCtx); // basVmSetInputCallback(vm, myInputFn, myCtx); // basVmLoadModule(vm, module); // BasVmResultE result = basVmRun(vm); // basVmDestroy(vm); #ifndef DVXBASIC_VM_H #define DVXBASIC_VM_H #include "values.h" #include "../compiler/basEvents.h" #include "../compiler/opcodes.h" #include "../../../../libs/kpunch/sql/dvxSql.h" #include "dvxTypes.h" #include "dvxPlat.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_MEMBER_VARS 65536 // member variables per object, variables per form (all a 16-bit opcode index can number) #define BAS_VM_MAX_FOR_DEPTH 32 // nested FOR loops #define BAS_VM_GOSUB_STACK_SIZE 256 // pending GOSUB returns (all call frames together) #define BAS_VM_MAX_FILES 16 // file channel array size; index 0 unused, so 15 usable channels (1..15) #define BAS_VM_RANDOM_RECORD_SIZE 128 // RANDOM-mode record length when OPEN has no LEN clause (bytes) #define BAS_VM_MAX_RECORD_LEN 32767 // largest LEN= a RANDOM-mode OPEN accepts #define BAS_VM_DEFAULT_STEP_SLICE 30000 // bytecode steps per yield (basVmRun slices and nested-call doEvents): about 50 ms on a 486 DX2-66 with the fast loop, as 10000 were before it #define BAS_VM_MAX_CALL_ARGS 16 // max args to OP_CALL_METHOD / OP_CALL_EXTERN #define BAS_VM_GC_MIN_ALLOCS 1000 // objects made between two cycle collections, at least (more when many objects live) #define BAS_RGB_COMPONENT_MAX 255 // RGB(r,g,b): max per-channel value #define BAS_PRINT_SPC_MAX 255 // PRINT SPC()/TAB(): max column count #define BAS_PRINT_ZONE_WIDTH 14 // PRINT comma: width of one print zone (columns) #define BAS_INPUT_LINE_LEN 1024 // INPUT / LINE INPUT: longest line read // Benchmark build only (-DBAS_BENCH, src/test/basic/basBench.c): every // instruction the VM executes is counted per opcode. Every other build // compiles the counter out entirely. #ifdef BAS_BENCH #define BAS_BENCH_OPCODES (UINT8_MAX + 1) // one counter per opcode byte value extern uint64_t basBenchOpCounts[BAS_BENCH_OPCODES]; #endif #define BAS_VM_ERROR_MSG_LEN 256 // runtime error description buffer #define BAS_VM_ERROR_TEXT_SEP ": " // ends the "Runtime error N ..." prefix of a runtime error message, before its text #define BAS_VM_ERROR_SOURCE_LEN 64 // Err.Source buffer #define BAS_VM_HANDLER_RESUME_NEXT (-1) // BasCallFrameT.errorHandler under ON ERROR RESUME NEXT // A FUNCTION's result slot: the compiler stores the return value in local // 0 and the epilogue is LOAD_LOCAL 0 + RET_VAL. #define BAS_FUNC_RESULT_SLOT 0 // Longest instruction basVmEvalPure runs: OP_BUILTIN [fn] [argc]. #define BAS_EVAL_PURE_CODE_LEN 3 // ============================================================ // Result codes // ============================================================ typedef enum { BAS_VM_OK, // program completed normally BAS_VM_HALTED, // HALT instruction reached 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_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); // RaiseEvent in the code of a UserControl class (BAS_CLASS_USERCONTROL): // after the VM has queued its WithEvents handlers it hands the event to // the host (the form runtime's control bridge), which passes it to the // widget's container. obj is the raising object, eventIdx its class's // event number, args the argument values (argc of them, the caller's // copies); the host may change them, and what it leaves in a ByRef // parameter's place is stored back into the variable the code passed. typedef void (*BasEventHostFnT)(void *ctx, const BasObjectT *obj, int32_t eventIdx, BasValueT *args, int32_t argc); // ============================================================ // 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, const char *title); // 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); // Create a form programmatically (no .frm file). Returns form reference. typedef void *(*BasUiCreateFormFnT)(void *ctx, const char *formName, int32_t width, int32_t height); // New FormName: load a new copy of a form of the program (its own // window, controls, menus and form-level variables; Load fires). Returns // the copy's form reference, NULL when it could not be made. typedef void *(*BasUiNewFormFnT)(void *ctx, const char *formName); // Create a control with an explicit parent container. parentRef may be NULL // to use the form's root content box. typedef void *(*BasUiCreateCtrlExFnT)(void *ctx, void *formRef, const char *typeName, const char *ctrlName, void *parentRef); // Override the event handler for a specific control + event pair. typedef void (*BasUiSetEventFnT)(void *ctx, void *ctrlRef, const char *eventName, const char *handlerName); // Remove a control from a form by name. typedef void (*BasUiRemoveCtrlFnT)(void *ctx, void *formRef, const char *ctrlName); // Type name of a host object for TypeName / TypeOf: "Form" for a form, // the control's type ("TextBox", "CommandButton" ...) for a control, NULL // when unknown. typedef const char *(*BasUiTypeNameFnT)(void *ctx, void *objRef); // The default property of a host object, read where the object stands // for a value (Print Text1, Text1 = "x" ... : Text for a TextBox, Caption // for a Label, a Picture's file name): the name getProp / setProp take // for it ("" for a Picture), or NULL when the object has none (a form). typedef const char *(*BasUiDefaultPropFnT)(void *ctx, void *objRef); // LoadPicture(path): a host object standing for the picture in file path // ("" = an empty picture), usable for picture properties. NULL with // *outErr set to a BASIC error (file not found, invalid picture) when it // cannot be read. typedef void *(*BasUiLoadPictureFnT)(void *ctx, const char *path, int32_t *outErr); // LoadPictureData(bytes): a Picture object for the len bytes of a picture // file ("" = an empty picture; the same bytes give the same object). NULL // with *outErr set to a BASIC error (invalid picture, out of memory) when // they are not a picture DVX can show. typedef void *(*BasUiLoadPictureDataFnT)(void *ctx, const char *data, int32_t len, int32_t *outErr); // LoadFormText(text): a form loaded from .frm text (its own copy when a // form of its name is loaded), NULL when the text does not load. typedef void *(*BasUiLoadFormTextFnT)(void *ctx, const char *text); // 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; BasUiCreateFormFnT createForm; BasUiNewFormFnT newForm; BasUiCreateCtrlExFnT createCtrlEx; BasUiSetEventFnT setEvent; BasUiRemoveCtrlFnT removeCtrl; BasUiTypeNameFnT typeName; BasUiLoadPictureFnT loadPicture; BasUiDefaultPropFnT defaultProp; BasUiLoadFormTextFnT loadFormText; BasUiLoadPictureDataFnT loadPictureData; void *ctx; // passed as first arg to all callbacks } BasUiCallbacksT; // ============================================================ // SQL callbacks (host-provided: the dvxsql library's functions, by type) // ============================================================ typedef struct { __typeof__(dvxSqlOpen) *sqlOpen; __typeof__(dvxSqlClose) *sqlClose; __typeof__(dvxSqlExec) *sqlExec; __typeof__(dvxSqlError) *sqlError; __typeof__(dvxSqlQuery) *sqlQuery; __typeof__(dvxSqlNext) *sqlNext; __typeof__(dvxSqlEof) *sqlEof; __typeof__(dvxSqlFieldCount) *sqlFieldCount; __typeof__(dvxSqlFieldName) *sqlFieldName; __typeof__(dvxSqlFieldText) *sqlFieldText; __typeof__(dvxSqlFieldByName) *sqlFieldByName; __typeof__(dvxSqlFieldInt) *sqlFieldInt; __typeof__(dvxSqlFieldDbl) *sqlFieldDbl; __typeof__(dvxSqlFreeResult) *sqlFreeResult; __typeof__(dvxSqlAffectedRows) *sqlAffectedRows; __typeof__(dvxSqlSetPump) *sqlSetPump; __typeof__(dvxSqlEscape) *sqlEscape; } BasSqlCallbacksT; // ============================================================ // Printer callbacks (host-provided) // ============================================================ // // LPRINT and OPEN "LPT1:" / "LPT2:" / "LPT3:" / "PRN" reach the printer // through these. open starts a job on LPT port 0..BAS_PRINTER_PORTS-1 // or on BAS_PRINTER_DEFAULT, the printer chosen in the settings (NULL = // no printer there); write returns false once the output failed or was // cancelled; close finishes the job and returns false when anything // failed. Without open, LPRINT and a printer OPEN raise "Device // unavailable". #define BAS_PRINTER_DEFAULT (-1) #define BAS_PRINTER_PORTS 3 typedef struct { void *(*open)(void *ctx, int32_t port); bool (*write)(void *ctx, void *job, const char *data, int32_t len); bool (*close)(void *ctx, void *job); void *ctx; } BasPrinterCallbacksT; // ============================================================ // Sound, joystick and frame callbacks (host-provided) // ============================================================ // // BEEP, SOUND, PLAY and PLAY(n), STICK and STRIG, and FRAMEWAIT reach the // host's sound, joystick and game libraries through these. play returns // false for a macro string with a mistake (Illegal function call). After // BEEP, SOUND and PLAY the VM keeps the host's events running while // soundBusy says the statement must still wait (foreground music); after // frameStart(hz) (the host's frame clock moves on to its next frame at hz // frames a second) likewise while frameBusy says that frame has not // begun. SHELL hands its command line to shellStart (the host runs it // with DVX stepped aside: false when it cannot), keeps the host's events // running while shellBusy, and returns shellResult (the exit code, -1 when // it did not run). Without beep / sound / play the statements do nothing // and PLAY(n) is 0, without stick / strig they read 0, FRAMEWAIT without // frameStart raises "Device unavailable", and SHELL without shellStart // runs the command with system() (BASIC outside DVX: the runner, BASCOMP). // QBasic's ranges: SOUND frequency (Hz) and duration (clock ticks, 18.2 // a second), STICK(0..3), STRIG(0..7) #define BAS_SOUND_HZ_MIN 37 #define BAS_SOUND_HZ_MAX 32767 #define BAS_SOUND_TICKS_MAX 65535 #define BAS_STICK_COUNT 4 #define BAS_STRIG_COUNT 8 typedef struct { void (*beep)(void *ctx); void (*sound)(void *ctx, double hz, double ticks); bool (*play)(void *ctx, const char *macro); int32_t (*playCount)(void *ctx); bool (*soundBusy)(void *ctx); int32_t (*stick)(void *ctx, int32_t n); int32_t (*strig)(void *ctx, int32_t n); void (*frameStart)(void *ctx, int32_t hz); bool (*frameBusy)(void *ctx); bool (*shellStart)(void *ctx, const char *command); bool (*shellBusy)(void *ctx); int32_t (*shellResult)(void *ctx); void *ctx; } BasDeviceCallbacksT; // ============================================================ // Program screen callbacks (host-provided) // ============================================================ // // The program screen (screen.h) lives in the VM from the program's first // console or graphics statement. The host shows it: modeChanged when it // is made or changes mode (size, palette and text layout), present when // its pixels or palette changed (the VM calls it whenever it gives the // host a turn; the host shows the dirty rectangle, then basScreenClean), // fullScreen for FULLSCREEN ON / OFF (the host sets scr->fullScreen to // what it managed). Keys reach INKEY$ and INPUT through basScreenPushKey. // Without callbacks the screen still works, unseen. struct BasScreenT; typedef struct { void (*modeChanged)(void *ctx, struct BasScreenT *scr); void (*present)(void *ctx, struct BasScreenT *scr); void (*fullScreen)(void *ctx, struct BasScreenT *scr, bool on); void *ctx; } BasScreenHostT; // ============================================================ // 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 per function-name constant, so this is // called 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). // binaryMask marks the BINARY STRING arguments (bit i) and result // (BAS_EXTERN_BINARY_RET): those pass as pointer + length. // 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, uint32_t binaryMask); typedef struct { BasResolveExternFnT resolveExtern; BasCallExternFnT callExtern; void *ctx; } BasExternCallbacksT; // ============================================================ // Extern function cache (resolved on first call) // ============================================================ // One slot per constant-pool entry, indexed by the function-name constant. typedef struct { void *funcPtr; // resolved native function pointer (NULL = not yet resolved) uint16_t libNameIdx; // constant pool index of the library the pointer came from } BasExternCacheEntryT; // ============================================================ // Call stack frame // ============================================================ // What a call frame's caller gets back (BasCallFrameT.resultMode): a // FUNCTION's value and nothing from a SUB (a direct call), Empty from a // SUB or Property Let / Set reached where a value is expected (an object // member read), or nothing at all (an object method called as a // statement: a FUNCTION's value is dropped). typedef enum { BAS_RESULT_AS_IS = 0, BAS_RESULT_EMPTY = 1, BAS_RESULT_DROP = 2, BAS_RESULT_SLOT = 3 // the value replaces stack slot resultSlot (an object operand's default member read) and the caller's instruction runs again } BasResultModeE; typedef struct { int32_t returnPc; // instruction to return to int32_t localCount; // number of locals in this frame int32_t errorHandler; // ON ERROR GOTO target in this SUB (0 = none, BAS_VM_HANDLER_RESUME_NEXT) int32_t resumePc; // RESUME target while this frame's handler runs int32_t resumeNextPc; // RESUME NEXT target while this frame's handler runs (-1 = no safe continuation) int32_t savedForDepth; // vm->forDepth when this frame was pushed (restored on return so EXIT SUB/FUNCTION inside FOR bodies cannot leak FOR frames) int32_t savedStmtPc; // caller's vm->stmtPc at call time (restored on return) int32_t savedStmtSp; // caller's vm->stmtSp at call time (restored on return) int32_t savedGosubDepth; // vm->gosubDepth when this frame was pushed: its own GOSUB returns sit above it (restored on return) bool inHandler; // this frame's ON ERROR handler trapped an error and has not RESUMEd yet uint8_t resultMode; // BasResultModeE: what the caller gets back when the procedure returns int32_t resultSlot; // BAS_RESULT_SLOT: the eval stack slot the returned value goes into bool restoreForm; // an event handler: the form context below is put back when it returns int32_t savedFormVarCount; // restoreForm: vm->currentFormVarCount to put back void *savedForm; // restoreForm: vm->currentForm to put back BasValueT *savedFormVars; // restoreForm: vm->currentFormVars to put back BasValueT me; // class module code: the object the procedure runs for (a counted BAS_TYPE_INSTANCE), else zero BasValueT locals[BAS_VM_MAX_LOCALS]; } BasCallFrameT; // ============================================================ // FOR loop state // ============================================================ typedef struct { int32_t varIdx; // loop variable slot index uint8_t scopeTag; // BasScopeE value bool integral; // limit and step are both INTEGER/LONG (integer NEXT fast path) bool stepFractional; // step has a fractional part (integer counters must widen to DOUBLE) BasValueT limit; // upper bound BasValueT step; // step value int32_t loopTop; // PC of the loop body start int32_t limitInt; // integral: limit and step as numbers (the fast loop's NEXT) int32_t stepInt; BasValueT *counter; // the counter's value (a ByRef counter's target) when it is a local or global, else NULL (the fast loop's NEXT) } BasForStateT; // ============================================================ // File channel // ============================================================ typedef struct { void *handle; // FILE* or platform-specific void *printJob; // printer channel: the job the output (spooled in handle) goes to at CLOSE int32_t mode; // BasFileModeE value int32_t recLen; // RANDOM-mode record length (OPEN ... LEN=) int32_t column; // PRINT #: output column of the current line (0-based; zones, TAB, WIDTH) int32_t width; // WIDTH #n line width (0: no limit) } BasFileChannelT; // ============================================================ // Procedure table entry (retained from symbol table for runtime) // ============================================================ // Buffer size for every identifier the compiler, runtime, form runtime // and IDE store by name: procedures, symbols, forms, controls, events. // One define so a name can never fit one table and truncate in another. #define BAS_MAX_IDENT 64 typedef struct { char name[BAS_MAX_IDENT]; // SUB/FUNCTION name (case-preserved) char formName[BAS_MAX_IDENT]; // owning form (for form-scope vars), "" if global int32_t codeAddr; // entry point in code[] int32_t paramCount; // number of parameters int32_t localCount; // local slots the procedure uses (call-frame size and debugger) int32_t initFirst; // its typed locals' starting values: module localInits[initFirst .. int32_t initCount; // .. initFirst + initCount - 1] 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_IDENT]; uint8_t dataType; } BasDebugFieldT; // Debug UDT type definition (preserved for debugger watch) typedef struct { char name[BAS_MAX_IDENT]; 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_IDENT]; char formName[BAS_MAX_IDENT]; // form name for SCOPE_FORM vars (empty for others) uint8_t scope; // SCOPE_GLOBAL, SCOPE_LOCAL, SCOPE_FORM uint8_t dataType; // BAS_TYPE_* bool shared; // module variable (procIndex -1) that SUBs/FUNCTIONs see (DIM SHARED) int32_t index; // variable slot index int32_t procIndex; // -1 for module variables, else index into procs[]: a local, or a // SHARED / STATIC name of that procedure for a module slot (scope GLOBAL/FORM) } BasDebugVarT; // ============================================================ // Per-form variable info (how many form-scoped vars each form needs) // ============================================================ typedef struct { char formName[BAS_MAX_IDENT]; int32_t varCount; int32_t initCodeAddr; // offset in module->code for per-form init (-1 = none) int32_t initCodeLen; // length of init bytecode } BasFormVarInfoT; // ============================================================ // Class modules // ============================================================ // What a public member of a class is. Part of the module format. typedef enum { BAS_MEMBER_VAR = 0, // Public variable BAS_MEMBER_SUB = 1, BAS_MEMBER_FUNCTION = 2, BAS_MEMBER_GET = 3, // Property Get BAS_MEMBER_LET = 4, // Property Let BAS_MEMBER_SET = 5 // Property Set } BasMemberKindE; // No class module (a WithEvents variable outside classes, an As Object // variable in the compiler). #define BAS_CLASS_NONE (-1) // Separator of a qualified member name: the interface member // Interface.Name a class that Implements Interface provides (and the // compiler's "Class.Member" symbols). #define BAS_MEMBER_QUALIFIER "." // Accesses of an object member: a read (or a call), or an assignment // made with Let (obj.p = v) or Set (Set obj.p = v). #define BAS_MEMBER_OP_GET 0 #define BAS_MEMBER_OP_LET 1 #define BAS_MEMBER_OP_SET 2 // BasClassMemberT flags. #define BAS_MEMBER_DEFAULT 0x01 // the default member (Attribute Name.VB_UserMemId = 0) #define BAS_MEMBER_PARAMARRAY 0x02 // the last parameter is a ParamArray #define BAS_MEMBER_FRIEND 0x04 // Friend: reached by index (compiled code) only, never by name (late binding) // Separates the names in BasClassMemberT.paramNames (and in the names a // late-bound call with named arguments passes). #define BAS_PARAM_NAME_SEP "," // Bits in BasClassMemberT.defaultParams / byValParams (one per parameter). #define BAS_MEMBER_PARAM_BITS 32 _Static_assert(BAS_VM_MAX_CALL_ARGS <= BAS_MEMBER_PARAM_BITS, "defaultParams needs a bit per parameter"); // BasClassMemberT.enumIdx of a member whose value is no Enum. #define BAS_ENUM_NONE (-1) // One member of an Enum: its name as declared (without the Enum's name) // and its value. typedef struct { char name[BAS_MAX_IDENT]; int32_t value; } BasEnumMemberT; // An Enum a UserControl property is declared As (BasClassMemberT.enumIdx): // its members in declaration order, so a host can show and store the // property by member name. Part of the module format. typedef struct { BasEnumMemberT *members; // malloc'd, at least one int32_t memberCount; } BasEnumInfoT; // A public member: found by its index when the compiler knew the // object's class, by name when a program uses obj.Name late bound. typedef struct { char name[BAS_MAX_IDENT]; int32_t index; // BAS_MEMBER_VAR: member variable slot; else index into procs[] int32_t enumIdx; // a UserControl's property (variable, Get, Let, Set): index into the module's enums of the Enum its value is declared As, else BAS_ENUM_NONE uint8_t kind; // BasMemberKindE uint8_t flags; // BAS_MEMBER_* uint8_t dataType; // BAS_MEMBER_VAR: declared type (stores convert to it) uint8_t requiredParams; // procedures: arguments that must be given (the rest are Optional) uint8_t paramTypes[BAS_VM_MAX_CALL_ARGS]; // procedures: declared parameter types (an omitted Optional argument takes the type's zero) uint32_t defaultParams; // procedures: bit i = parameter i has a default value (an omitted argument is passed as Missing; the procedure stores its default) uint32_t byValParams; // procedures: bit i = parameter i is ByVal (an argument passed by reference is given its value) uint32_t typedParams; // procedures: bit i = parameter i is a scalar declared with a type (an argument is converted to paramTypes[i]; one passed by reference to a variable of another type is given a converted copy, as VB's late binding does) char *paramNames; // procedures: the parameters' names, comma-separated (a late-bound call's arguments given by name), malloc'd; NULL: no parameters } BasClassMemberT; // An interface map (BasImplementsT.map) has BAS_IMPL_WAYS entries per // member of the interface: the implementing class's member a read or call // reaches, and the one a store into an interface variable reaches. #define BAS_IMPL_READ 0 #define BAS_IMPL_STORE 1 #define BAS_IMPL_WAYS 2 // No member (an interface map entry with nothing behind it). #define BAS_MEMBER_NONE (-1) // An interface a class Implements and where its members are: for member // j of class iface, map[j * BAS_IMPL_WAYS + way] is the implementing // class's member index (BAS_MEMBER_NONE: none). Early-bound code using // a variable declared As iface reaches an implementing object through it. typedef struct { int32_t iface; // the interface's class number int32_t *map; // malloc'd, mapLen entries int32_t mapLen; // the interface's member count * BAS_IMPL_WAYS } BasImplementsT; // An Event a class declares; RaiseEvent's operand is its index. Part of // the module format (a UserControl's events reach the widget's host by // these names and parameters). typedef struct { char name[BAS_MAX_IDENT]; int32_t paramCount; uint8_t paramTypes[BAS_VM_MAX_CALL_ARGS]; // BAS_TYPE_* (BAS_TYPE_OBJECT for an object parameter) uint32_t byValParams; // bit i: parameter i is ByVal char *paramNames; // the parameters' names, comma-separated, malloc'd; NULL: none } BasClassEventT; // BasClassInfoT.flags. #define BAS_CLASS_USERCONTROL 0x01 // a UserControl (.ctl): a widget class written in BASIC #define BAS_CLASS_FLAGS_KNOWN BAS_CLASS_USERCONTROL typedef struct { char name[BAS_MAX_IDENT]; int32_t varCount; // member variables per instance int32_t initCodeAddr; // code that sets up a new instance (member variables, then Class_Initialize), ends with OP_RET int32_t terminateProc; // index into procs[] of Class_Terminate, -1 = none BasClassMemberT *members; // public members (malloc'd); a member Interface.Name implements Name of that interface int32_t memberCount; BasImplementsT *implements; // interfaces it Implements (malloc'd) int32_t implementsCount; BasClassEventT *events; // the Events it declares (malloc'd) int32_t eventCount; uint8_t flags; // BAS_CLASS_* int32_t ucSlot; // BAS_CLASS_USERCONTROL: member variable holding the UserControl object, else -1 int32_t ucProcs[BAS_UC_EVT_COUNT]; // BAS_CLASS_USERCONTROL: index into procs[] of UserControl_ (BasUcEventE), -1 = none } BasClassInfoT; // The handler of one event for a WithEvents variable. Part of the module // format. typedef struct { int32_t eventIdx; // event number in the variable's class (RaiseEvent's operand) int32_t procIdx; // index into procs[] of the SUB variable_EventName } BasEventHandlerT; // A module or form variable declared WithEvents: the events the object it // holds raises reach its handlers. Part of the module format. typedef struct { int32_t slot; // variable slot: a global, a form variable or a member variable of ownerClass int32_t classIdx; // the class it is declared as int32_t ownerClass; // SCOPE_MEMBER: the class module it is a member of, else BAS_CLASS_NONE BasEventHandlerT *handlers; // malloc'd int32_t handlerCount; uint8_t scope; // SCOPE_GLOBAL, SCOPE_FORM or SCOPE_MEMBER } BasWithEventsT; // Where a WithEvents variable lives: a module variable (formVars NULL, a // global slot), or one loaded form's variables. RaiseEvent calls the // handlers of the sinks whose variable holds the raising object, running // a form's handlers with that form's context. typedef struct { int32_t binding; // index into the module's withEvents int32_t formVarCount; void *form; // the form (NULL for a module variable) BasValueT *formVars; // its variables (NULL for a module variable) } BasEventSinkT; // ============================================================ // Compiled module (output of the compiler) // ============================================================ // A slot that starts as its declared type's zero (an empty string, a LONG // or DOUBLE 0, False ...) rather than the INTEGER 0 of a cleared slot: a // typed scalar global (set when the module loads) or local (set when its // procedure is entered). Survives stripping. typedef struct { int32_t index; // global slot / local slot uint8_t dataType; // BAS_TYPE_* } BasSlotInitT; // A QBasic line number of the program: the source line it is on (ERL // reports the number of the last one at or before the line of an error). typedef struct { int32_t line; int32_t number; } BasLineNumberT; 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; BasSlotInitT *globalInits; // typed globals' starting values (survives stripping) int32_t globalInitCount; BasSlotInitT *localInits; // typed locals' starting values, per procedure (BasProcEntryT initFirst / initCount) int32_t localInitCount; BasLineNumberT *lineNumbers; // the program's line numbers in source order (none: ERL reports the source line) int32_t lineNumberCount; BasClassInfoT *classes; // class modules (runtime-essential) int32_t classCount; BasEnumInfoT *enums; // the Enums UserControl properties are declared As (survives stripping) int32_t enumCount; BasWithEventsT *withEvents; // WithEvents variables (runtime-essential) int32_t withEventsCount; 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 badOperand; // operand read ran past code[] -- raise BAS_VM_BAD_OPCODE next step 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) int32_t currentOpPc; // PC of the instruction currently executing (set at start of each basVmStep; use this, not vm->pc, for "which SUB are we in" lookups from host callbacks, since vm->pc has already advanced past the opcode + operands) int32_t haltPc; // where the module code's OP_HALT is, once the code ran to it (-1: not yet); basVmFinish runs from there // 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) int32_t stmtSp; // eval-stack depth snapshot at last OP_LINE (statement boundary) int32_t stmtPc; // PC of the OP_LINE/OP_STMT that opened the current statement (-1 = none seen) // 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; // GOSUB return addresses. Each call frame owns the entries above its // savedGosubDepth, so RETURN only returns from a GOSUB made in the // same SUB/FUNCTION (or at module level). int32_t gosubStack[BAS_VM_GOSUB_STACK_SIZE]; int32_t gosubDepth; // 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 // PRINT state: output column (0-based, for comma zones and TAB) and // the scan position inside the active PRINT USING format string. int32_t printColumn; int32_t usingPos; // LPRINT state: the job opened by the first LPRINT (kept until the VM // is destroyed), whether the running statement is an LPRINT, its own // output column, and whether a write of this statement failed. void *lprintJob; bool lprinting; bool lprintFailed; int32_t lprintColumn; int32_t lprintWidth; // WIDTH LPRINT (0: no limit) // PRINT #: the channel the running statement prints to (0: none), and // the WIDTH "device" line widths a printer channel opened later takes // (index 0: PRN, 1.. LPT1..; 0: no limit). int32_t printChannel; int32_t printerWidths[BAS_PRINTER_PORTS + 1]; // Error handling. ON ERROR state lives on the call frames: each frame // records its own handler, whether that handler is running, and its // RESUME targets (BasCallFrameT), so a SUB called from a handler has // its own independent handler. The fields below are the Err state. int32_t errorNumber; // current Err number int32_t errorPc; // PC of the statement that raised the last error (host/debugger display; RESUME uses the frame's resumePc) int32_t errorLine; // source line where the error was raised (frozen; debuggers use this because currentLine keeps advancing while the error dialog pumps events) char errorMsg[BAS_VM_ERROR_MSG_LEN]; // current error description char errorDesc[BAS_VM_ERROR_MSG_LEN]; // Err.Description: the current error's text (kept when ON ERROR RESUME NEXT traps it and errorMsg is cleared) char errorSource[BAS_VM_ERROR_SOURCE_LEN]; // Err.Source: what Err.Raise named ("" otherwise) // RND state: the last value returned (RND(0) repeats it). double rndLast; // I/O callbacks BasPrintFnT printFn; void *printCtx; BasInputFnT inputFn; void *inputCtx; BasDoEventsFnT doEventsFn; void *doEventsCtx; // The slice clock (basVmSetSliceClock): a counter the host's timer // interrupt advances, read after each instruction run out of line // (calls, properties, built-ins); NULL: slices count steps only. const volatile uint32_t *sliceClock; uint32_t sliceSpan; // its counts a slice may last uint32_t sliceStart; // its value when the slice began uint64_t (*microsFn)(void); // basVmSetMicrosClock: TIMER's fine clock (NULL: the wall clock alone) int64_t timerWall; // TIMER: the wall clock's last reading, in microseconds (-1: none yet) uint64_t timerSeen; // microsFn when it came int32_t runLimit; // slices one run or call may last, waits refused (basVmSetRunLimit; 0: no limit) BasBreakpointFnT breakpointFn; void *breakpointCtx; BasEventHostFnT eventHostFn; // UserControl events to the host (NULL: none) void *eventHostCtx; // UI callbacks (set by host for form/control support) BasUiCallbacksT ui; BasSqlCallbacksT sql; BasPrinterCallbacksT printer; BasDeviceCallbacksT device; BasScreenHostT screenHost; // The program screen (NULL until the first console or graphics // statement), and the host's request to stop the program (its screen // window was closed): basVmRun and basVmDoEvents then halt. struct BasScreenT *screen; bool stopRequested; // External library callbacks (set by host) BasExternCallbacksT ext; // Extern function cache (resolved on first call), allocated at load // time with one entry per constant-pool slot. BasExternCacheEntryT *externCache; int32_t externCacheCount; // Objects: every live class instance / Collection this VM made (so a // program's reference cycles can be broken when it ends), and the // instances whose Class_Terminate is waiting to run (linked through // termNext). BasObjectT *liveObjects; BasObjectT *terminateQueue; // Class instances whose class has WithEvents member variables, linked // through BAS_OBJ_LIST_LISTENERS. The list holds no references (an // object leaves it when freed), so a listener is kept alive only by // the program's own references and RaiseEvent never reaches a freed one. BasObjectT *eventListeners; // Cycle collection: objects made since the last collection and how // many may be made before the next; while the Class_Terminate calls a // collection started run (gcPending), the next one waits until the // call depth is back to gcResumeDepth. int32_t gcAllocs; int32_t gcBudget; int32_t gcResumeDepth; bool gcPending; // WithEvents variables' locations (malloc'd, grows) BasEventSinkT *eventSinks; int32_t eventSinkCount; int32_t eventSinkCap; // 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 -- the program's folder char appUser[DVX_MAX_PATH]; // App.User -- its user folder (writable; made on first use) char appData[DVX_MAX_PATH]; // App.Data -- the files it ships (read-only) bool appUserMade; // App.User's folder was made char *commandArgs; // COMMAND$ -- launch arguments (malloc'd copy, NULL = none; see basVmSetCommand) // Dir$ iterator state -- per-VM so a second embedded VM (e.g. the IDE // watch-expression evaluator) cannot clobber an in-progress enumeration. void *dirHandle; // DIR * (opaque here; cast in vm.c) char dirPattern[DVX_MAX_PATH]; // filename glob for Dir$ char dirPath[DVX_MAX_PATH]; // directory being enumerated // IniRead / IniWrite's last file, kept parsed (iniPrefs in vm.c) void *iniPrefs; // PrefsHandleT * (NULL = none kept) char iniPath[DVX_MAX_PATH]; int64_t iniSize; // its size and time when kept (-1: there was no file) int64_t iniTime; } 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. A NULL module, or one that needs // more global slots than BAS_VM_MAX_GLOBALS, is refused: vm->module stays // NULL and basVmRun reports BAS_VM_ERROR with the reason in basVmGetError(). // A successful load clears any error left by an earlier refused load. 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); // Set the launch arguments COMMAND$ returns (copied; NULL or "" = none). // False when the copy cannot be allocated (COMMAND$ is then empty). bool basVmSetCommand(BasVmT *vm, const char *args); // Set App.Path, App.User, and App.Data (copied, cut to DVX_MAX_PATH). // App.User's folder is made when the program first asks for it. void basVmSetAppPaths(BasVmT *vm, const char *path, const char *user, const char *data); // Give the host a turn: show what changed on the program screen, then // the DoEvents callback. False when the program must stop (the host said // so, or basVmRequestStop). bool basVmDoEvents(BasVmT *vm); // Ask the running program to stop at its next turn (basVmRun then // returns BAS_VM_HALTED). void basVmRequestStop(BasVmT *vm); // Set I/O callbacks. void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx); void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx); void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx); // For a VM with no event pump that must never keep the CPU (an installed // control file's, shared by every program): a run, or a call into it (an // event), that lasts more than slices slices is stopped with error 18, // which ON ERROR cannot trap; SLEEP, FRAMEWAIT, and SHELL raise error 5 // instead of blocking, and BEEP, SOUND, and PLAY do not wait for // foreground music. 0: no limit (the default). void basVmSetRunLimit(BasVmT *vm, int32_t slices); // Give the VM a clock for its slices: clock is a counter the host's timer // interrupt advances (reading it costs one load), span how far it may move // in one slice. A slice (a basVmRun with a step limit, or the stretch of // an event handler between two doEvents) then also ends once the clock has // moved span, at the first instruction run out of line after that, so a // loop of long calls (a file copy, a SQL statement) gives the host its turn // as often as a loop of quick instructions does. NULL: steps only. void basVmSetSliceClock(BasVmT *vm, const volatile uint32_t *clock, uint32_t span); // A fine monotonic clock in microseconds (platformMicros) for TIMER: the // wall clock moves in coarse steps on DOS (a BIOS tick, about 55 ms), and // the time since it last moved is added from this, never a whole step, so // TIMER rises smoothly and never passes the wall clock's next reading. // NULL: the wall clock alone. void basVmSetMicrosClock(BasVmT *vm, uint64_t (*micros)(void)); // Set the printer callbacks (LPRINT, OPEN "LPTn:"). void basVmSetPrinterCallbacks(BasVmT *vm, const BasPrinterCallbacksT *printer); // Set the sound, joystick and frame callbacks (BEEP, SOUND, PLAY, STICK, // STRIG, FRAMEWAIT). void basVmSetDeviceCallbacks(BasVmT *vm, const BasDeviceCallbacksT *device); // Set the program screen callbacks. void basVmSetScreenHost(BasVmT *vm, const BasScreenHostT *host); // Set the SQL callbacks (the dvxsql library's functions). void basVmSetSqlCallbacks(BasVmT *vm, const BasSqlCallbacksT *sql); // Set UI callbacks (for form/control system). void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui); // Set external library callbacks (for DECLARE LIBRARY support). void basVmSetExternCallbacks(BasVmT *vm, const BasExternCallbacksT *ext); // The program ended without END, a stop request or an unhandled error: // its module code ran to the end and, with forms, the last one was // unloaded. As VB does then, the module variables are released, so the // objects only they kept get their Class_Terminate. True when that left // BASIC code to run: the host then runs the VM again (basVmRun) until it // halts. bool basVmFinish(BasVmT *vm); // A form's variables (formVarCount of them) are about to be freed: forget // every reference the VM keeps to them (WithEvents locations, event // handler form contexts, and references to them held by running // procedures: ByRef parameters, ParamArray arguments, pending arguments). void basVmForgetForm(BasVmT *vm, void *formRef, BasValueT *formVars, int32_t formVarCount); // A new empty Collection object owned by vm (the host's Form.Controls), // and item appended to one under key (NULL: none; basVmCollectionAdd // returns 0 or the BASIC error number of a key in use). basValInstance // of the new Collection takes its one reference. int32_t basVmCollectionAdd(BasObjectT *coll, BasValueT item, const char *key); BasObjectT *basVmCollectionNew(BasVmT *vm); // 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); // Get the current error message. const char *basVmGetError(const BasVmT *vm); // Runs one pure operation -- op, or built-in fn when op is OP_BUILTIN -- // on the argc values in args (taken over) exactly as a program would, and // gives its result in *out (the caller owns it). The compiler folds // constant expressions with it. On an error *out is Empty and // basVmGetError has the message. The VM must not be running a program. BasVmResultE basVmEvalPure(BasVmT *vm, uint8_t op, uint8_t fn, BasValueT *args, int32_t argc, BasValueT *out); // ---- 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); // Cancel a pending step / run-to-cursor request. void basVmClearStepState(BasVmT *vm); // The source line of the last OP_LINE executed (0: none yet). int32_t basVmGetCurrentLine(const BasVmT *vm); // The source line where the current runtime error was raised (0: none). int32_t basVmGetErrorLine(const BasVmT *vm); // Leave break mode: event handlers may run again and a handler paused // at a breakpoint carries on (its pause loop ends). void basVmResume(BasVmT *vm); // The host's pause notification: fn(ctx, line) when a breakpoint or step // pauses inside a SUB the host called (line -1 once that SUB returns). void basVmSetBreakpointCallback(BasVmT *vm, BasBreakpointFnT fn, void *ctx); // Break mode on or off: while on, basVmCallSub refuses to run handlers. void basVmSetDebugPaused(BasVmT *vm, bool paused); // Stop a program under the debugger: no step request or breakpoint may // pause it again (breakpoints are detached), break mode ends and the // running code unwinds. void basVmStop(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 whose first outCount parameters are passed by reference to // outArgs (each must hold a valid value, released and replaced by its // argument first): what the SUB stores in them is in outArgs when it // returns, also after an error. bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount, BasValueT *outArgs, int32_t outCount); // ---- Class objects from the host (the UserControl bridge) ---- // A new instance of class classIdx, set up as New sets one up (member // variables, then Class_Initialize), run to completion. The caller owns // the one reference returned (basObjectUnref). NULL when the class does // not exist, the VM is paused in the debugger or the set-up code failed // (the VM's error is set then). BasObjectT *basVmNewObject(BasVmT *vm, int32_t classIdx); // obj.name as late-bound code would use it, run to completion: op // BAS_MEMBER_OP_GET reads or calls it (a Sub leaves Empty) with the argc // arguments in args; BAS_MEMBER_OP_LET / _SET assign args[argc - 1] with // the arguments before it. args stay the caller's (they are copied). // *result (may be NULL) receives the value (Empty for a Sub or an // assignment); the caller releases it. False on an error (no such // member, wrong arguments, an untrapped runtime error in the code: the // VM's error is set) or when the VM is paused in the debugger. bool basVmCallMember(BasVmT *vm, BasObjectT *obj, const char *name, int32_t op, const BasValueT *args, int32_t argc, BasValueT *result); // Run procedure procIdx (a SUB) with Me = obj (NULL: none), as // basVmCallSubWithArgsOut runs one: its first outCount parameters are // passed by reference to outArgs. bool basVmCallProc(BasVmT *vm, BasObjectT *obj, int32_t procIdx, const BasValueT *args, int32_t argCount, BasValueT *outArgs, int32_t outCount); // Set the UserControl event host (see BasEventHostFnT). void basVmSetEventHost(BasVmT *vm, BasEventHostFnT fn, void *ctx); #endif // DVXBASIC_VM_H