1157 lines
56 KiB
C
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
|