// 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 I/O callbacks. No DVX dependencies.
//
// 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 "dvxTypes.h"
#include "dvxPlat.h"

#include <stdint.h>
#include <stdbool.h>

// ============================================================
// Limits
// ============================================================

#define BAS_VM_STACK_SIZE         256    // evaluation stack depth
#define BAS_VM_CALL_STACK_SIZE    64     // max call nesting
#define BAS_VM_MAX_GLOBALS        512    // global variable slots
#define BAS_VM_MAX_LOCALS         64     // locals per stack frame
#define BAS_VM_MAX_FOR_DEPTH      32     // nested FOR loops
#define BAS_VM_MAX_FILES          16     // 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 10000  // bytecode steps per yield
#define BAS_VM_MAX_CALL_ARGS      16     // max args to OP_CALL_METHOD / OP_CALL_EXTERN

#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_VM_ERROR_MSG_LEN      256    // runtime error description buffer

// ============================================================
// 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);

// ============================================================
// 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);

// 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);

// 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;
    BasUiCreateCtrlExFnT createCtrlEx;
    BasUiSetEventFnT     setEvent;
    BasUiRemoveCtrlFnT   removeCtrl;
    void                *ctx;        // passed as first arg to all callbacks
} BasUiCallbacksT;

// ============================================================
// SQL callbacks (host-provided)
// ============================================================

typedef struct {
    int32_t     (*sqlOpen)(const char *path);
    void        (*sqlClose)(int32_t db);
    bool        (*sqlExec)(int32_t db, const char *sql);
    const char *(*sqlError)(int32_t db);
    int32_t     (*sqlQuery)(int32_t db, const char *sql);
    bool        (*sqlNext)(int32_t rs);
    bool        (*sqlEof)(int32_t rs);
    int32_t     (*sqlFieldCount)(int32_t rs);
    const char *(*sqlFieldName)(int32_t rs, int32_t col);
    const char *(*sqlFieldText)(int32_t rs, int32_t col);
    const char *(*sqlFieldByName)(int32_t rs, const char *name);
    int32_t     (*sqlFieldInt)(int32_t rs, int32_t col);
    double      (*sqlFieldDbl)(int32_t rs, int32_t col);
    void        (*sqlFreeResult)(int32_t rs);
    int32_t     (*sqlAffectedRows)(int32_t db);
} BasSqlCallbacksT;

// ============================================================
// External library callbacks (host-provided)
// ============================================================
//
// The VM resolves external functions by library name + function
// name at runtime. The host uses dlsym() or equivalent to find
// the native function pointer. A second callback marshals the
// call -- converting BasValueT arguments to native C types and
// the return value back to BasValueT.

// Resolve a function by library and symbol name.
// Returns an opaque function pointer, or NULL if not found.
// The VM caches the result so this is called only once per
// unique (library, function) pair.
typedef void *(*BasResolveExternFnT)(void *ctx, const char *libName, const char *funcName);

// Call a resolved native function. funcPtr is the pointer returned
// by resolveExtern. args[0..argc-1] are the BASIC arguments.
// retType is the expected return type (BAS_TYPE_*).
// libName is the DECLARE LIBRARY name (e.g. "dvx"); the host may use
// it to inject hidden parameters (e.g. AppContextT for "dvx" library).
// Returns the native function's return value as a BasValueT.
typedef BasValueT (*BasCallExternFnT)(void *ctx, void *funcPtr, const char *libName, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType);

typedef struct {
    BasResolveExternFnT resolveExtern;
    BasCallExternFnT    callExtern;
    void               *ctx;
} BasExternCallbacksT;

// ============================================================
// Extern function cache (resolved on first call)
// ============================================================

#define BAS_EXTERN_CACHE_SIZE 128

typedef struct {
    uint16_t libNameIdx;    // constant pool index for library name
    uint16_t funcNameIdx;   // constant pool index for function name
    void    *funcPtr;       // resolved native function pointer (NULL = not yet resolved)
} BasExternCacheEntryT;

// ============================================================
// Call stack frame
// ============================================================

typedef struct {
    int32_t   returnPc;               // instruction to return to
    int32_t   localCount;             // number of locals in this frame
    int32_t   errorHandler;           // ON ERROR GOTO target in this SUB (0 = none)
    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)
    BasValueT locals[BAS_VM_MAX_LOCALS];
} BasCallFrameT;

// ============================================================
// FOR loop state
// ============================================================

typedef struct {
    int32_t   varIdx;     // loop variable slot index
    uint8_t   scopeTag;   // BasScopeE value
    BasValueT limit;      // upper bound
    BasValueT step;       // step value
    int32_t   loopTop;    // PC of the loop body start
} BasForStateT;

// ============================================================
// File channel
// ============================================================

typedef struct {
    void   *handle;       // FILE* or platform-specific
    int32_t mode;         // BasFileModeE value
    int32_t recLen;       // RANDOM-mode record length (OPEN ... LEN=)
} 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;                  // number of local variables (for debugger)
    uint8_t returnType;                  // BAS_TYPE_* (0 for SUB)
    bool    isFunction;                  // true = FUNCTION, false = SUB
} BasProcEntryT;

// Debug UDT field definition (preserved for debugger watch)
typedef struct {
    char    name[BAS_MAX_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_*
    int32_t  index;     // variable slot index
    int32_t  procIndex; // -1 for globals, else index into procs[]
} BasDebugVarT;

// ============================================================
// Per-form variable info (how many form-scoped vars each form needs)
// ============================================================

typedef struct {
    char    formName[BAS_MAX_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;

// ============================================================
// Compiled module (output of the compiler)
// ============================================================

// Runtime-required global init entry.  STRING globals need to start
// with an empty string in the slot even when debug info has been
// stripped, or operators that switch on slot type (STRING concat)
// break on first use.  Only BAS_TYPE_STRING entries are emitted and
// acted on; other types keep the zero default.
typedef struct {
    int32_t  index;     // global slot index
    uint8_t  dataType;  // BAS_TYPE_*
} BasGlobalInitT;

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;
    BasGlobalInitT   *globalInits;      // runtime global type init (survives stripping)
    int32_t           globalInitCount;
    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)

    // 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;

    // Out-argument copy-back for basVmCallSubWithArgsOut.  OP_RET/OP_RET_VAL
    // copy the returning frame's locals here before releasing them, so the
    // caller reads valid values instead of already-released refs.  Not
    // reentrant: only one out-arg call may be in flight at a time.
    BasValueT *outArgs;                              // caller-provided out-arg buffer (NULL = none)
    int32_t    outArgCount;                          // number of out-args to copy back
    int32_t    outArgFrame;                          // callDepth of the frame to read back (-1 = none)

    // FOR loop stack
    BasForStateT forStack[BAS_VM_MAX_FOR_DEPTH];
    int32_t      forDepth;

    // Global variables
    BasValueT globals[BAS_VM_MAX_GLOBALS];

    // File channels (1-based, index 0 unused)
    BasFileChannelT files[BAS_VM_MAX_FILES];

    // DATA/READ pointer
    int32_t  dataPtr;                                    // current READ position in data pool

    // String comparison mode
    bool     compareTextMode;                        // true = case-insensitive comparisons

    // Error handling.  The active ON ERROR handler lives on the call frame
    // that installed it (BasCallFrameT.errorHandler); the dispatcher walks
    // frames to find it.
    int32_t  errorNumber;    // current Err number
    int32_t  errorPc;        // PC of the instruction that caused the error (for RESUME)
    int32_t  errorNextPc;    // PC of the next instruction after error (for RESUME NEXT)
    int32_t  errorLine;      // source line where the error was raised (frozen; debuggers use this because currentLine keeps advancing while the error dialog pumps events)
    bool     inErrorHandler; // true when executing error handler code
    char     errorMsg[BAS_VM_ERROR_MSG_LEN];  // current error description

    // I/O callbacks
    BasPrintFnT    printFn;
    void          *printCtx;
    BasInputFnT    inputFn;
    void          *inputCtx;
    BasDoEventsFnT    doEventsFn;
    void             *doEventsCtx;
    BasBreakpointFnT  breakpointFn;
    void             *breakpointCtx;

    // UI callbacks (set by host for form/control support)
    BasUiCallbacksT  ui;
    BasSqlCallbacksT sql;

    // External library callbacks (set by host)
    BasExternCallbacksT ext;

    // Extern function cache (resolved on first call)
    BasExternCacheEntryT externCache[BAS_EXTERN_CACHE_SIZE];
    int32_t              externCacheCount;

    // Current form reference (set during event dispatch)
    void      *currentForm;
    BasValueT *currentFormVars;     // points to current form's variable storage
    int32_t    currentFormVarCount; // number of form variables

    // App object paths (set by host)
    char        appPath[DVX_MAX_PATH];    // App.Path   -- project/app directory
    char        appConfig[DVX_MAX_PATH];  // App.Config -- writable config directory
    char        appData[DVX_MAX_PATH];    // App.Data   -- writable data directory

    // 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
} 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 module 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().
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 I/O callbacks.
void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx);
void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx);
void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx);

// Set UI callbacks (for form/control system).
void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui);
void basVmSetSqlCallbacks(BasVmT *vm, const BasSqlCallbacksT *sql);

// Set external library callbacks (for DECLARE LIBRARY support).
void basVmSetExternCallbacks(BasVmT *vm, const BasExternCallbacksT *ext);

// Set the current form context (called by host during event dispatch).
void basVmSetCurrentForm(BasVmT *vm, void *formRef);
void basVmSetCurrentFormVars(BasVmT *vm, BasValueT *vars, int32_t count);

// Set the step limit for basVmRun. 0 = unlimited (default).
// When the limit is reached, basVmRun returns BAS_VM_STEP_LIMIT.
// The VM remains in a runnable state -- call basVmRun again to continue.
void basVmSetStepLimit(BasVmT *vm, int32_t limit);

// Get the current error message.
const char *basVmGetError(const BasVmT *vm);

// ---- Debugger API ----

// Set the breakpoint list (sorted array of source line numbers, host-owned).
void basVmSetBreakpoints(BasVmT *vm, int32_t *lines, int32_t count);

// Step into: break at the next OP_LINE instruction.
void basVmStepInto(BasVmT *vm);

// Step over: break when call depth returns to current level.
void basVmStepOver(BasVmT *vm);

// Step out: break when call depth drops below current level.
void basVmStepOut(BasVmT *vm);

// Run to cursor: break when reaching the specified source line.
void basVmRunToCursor(BasVmT *vm, int32_t line);

// Call a SUB by code address from the host.
// Pushes a call frame, runs until the SUB returns, then restores
// the previous execution state. Returns true if the SUB was called
// and returned normally, false on error or if the VM was not idle.
bool basVmCallSub(BasVmT *vm, int32_t codeAddr);
bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount);

// Call a SUB and read back modified argument values.
// outArgs receives copies of the locals after the SUB returns.
// outCount specifies how many args to read back.
bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount, BasValueT *outArgs, int32_t outCount);

#endif // DVXBASIC_VM_H
