DVX_GUI/src/apps/kpunch/dvxbasic/runtime/vm.h
2026-10-20 17:24:15 -05:00

1157 lines
56 KiB
C

// 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 <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_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_<event> (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