5532 lines
165 KiB
C
5532 lines
165 KiB
C
// vm.c -- DVX BASIC virtual machine implementation
|
|
//
|
|
// Stack-based p-code interpreter. Executes one instruction per
|
|
// basVmStep() call, or runs until completion via basVmRun().
|
|
// All I/O is through host-provided callbacks -- the VM itself
|
|
// has no platform dependencies.
|
|
|
|
#include "vm.h"
|
|
#include "../compiler/opcodes.h"
|
|
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <fnmatch.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
// ============================================================
|
|
// Prototypes
|
|
// ============================================================
|
|
|
|
// Steps to execute between doEvents yields during subroutine calls.
|
|
// This keeps the GUI responsive without excessive overhead from
|
|
// calling doEvents on every instruction.
|
|
#define SUB_YIELD_INTERVAL 500
|
|
|
|
static BasCallFrameT *currentFrame(BasVmT *vm);
|
|
static void defaultPrint(void *ctx, const char *text, bool newline);
|
|
static BasVmResultE execArith(BasVmT *vm, uint8_t op);
|
|
static BasVmResultE execCompare(BasVmT *vm, uint8_t op);
|
|
static void dirClose(void);
|
|
static BasVmResultE execFileOp(BasVmT *vm, uint8_t op);
|
|
static BasVmResultE execFsOp(BasVmT *vm, uint8_t op);
|
|
static BasVmResultE execLogical(BasVmT *vm, uint8_t op);
|
|
static BasVmResultE execMath(BasVmT *vm, uint8_t op);
|
|
static BasVmResultE execPrint(BasVmT *vm);
|
|
static BasVmResultE execStringOp(BasVmT *vm, uint8_t op);
|
|
static bool pop(BasVmT *vm, BasValueT *val);
|
|
static bool push(BasVmT *vm, BasValueT val);
|
|
static int16_t readInt16(BasVmT *vm);
|
|
static uint8_t readUint8(BasVmT *vm);
|
|
static uint16_t readUint16(BasVmT *vm);
|
|
static void runtimeError(BasVmT *vm, int32_t errNum, const char *msg);
|
|
|
|
|
|
// ============================================================
|
|
// runSubLoop -- shared inner loop for basVmCallSub variants
|
|
// ============================================================
|
|
//
|
|
// Executes instructions until callDepth drops back to savedCallDepth
|
|
// (meaning the subroutine returned). Periodically yields via the
|
|
// doEvents callback to keep the GUI responsive during long-running
|
|
// event handlers.
|
|
|
|
static bool runSubLoop(BasVmT *vm, int32_t savedPc, int32_t savedCallDepth, bool savedRunning) {
|
|
int32_t stepsSinceYield = 0;
|
|
bool hadBreakpoint = false;
|
|
|
|
while (vm->running && vm->callDepth > savedCallDepth) {
|
|
BasVmResultE result = basVmStep(vm);
|
|
|
|
if (result == BAS_VM_HALTED) {
|
|
break;
|
|
}
|
|
|
|
if (result == BAS_VM_BREAKPOINT) {
|
|
// Pause in place: yield to the GUI until the host resumes.
|
|
// This ensures the sub runs to completion before returning
|
|
// to the caller (critical for form init code, event handlers).
|
|
vm->running = false;
|
|
hadBreakpoint = true;
|
|
|
|
// Notify host to update debug UI (highlight line, locals, etc.)
|
|
if (vm->breakpointFn) {
|
|
vm->breakpointFn(vm->breakpointCtx, vm->currentLine);
|
|
}
|
|
|
|
while (!vm->running && vm->doEventsFn) {
|
|
if (!vm->doEventsFn(vm->doEventsCtx)) {
|
|
// Host is shutting down
|
|
vm->pc = savedPc;
|
|
vm->running = savedRunning;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// User resumed — continue executing the sub
|
|
stepsSinceYield = 0;
|
|
continue;
|
|
}
|
|
|
|
if (result != BAS_VM_OK) {
|
|
vm->pc = savedPc;
|
|
vm->callDepth = savedCallDepth;
|
|
vm->running = savedRunning;
|
|
return false;
|
|
}
|
|
|
|
// Yield periodically to keep the GUI responsive
|
|
if (++stepsSinceYield >= SUB_YIELD_INTERVAL) {
|
|
stepsSinceYield = 0;
|
|
|
|
if (vm->doEventsFn) {
|
|
if (!vm->doEventsFn(vm->doEventsCtx)) {
|
|
vm->running = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
vm->pc = savedPc;
|
|
vm->running = savedRunning;
|
|
|
|
// If we paused at a breakpoint during this sub, notify the host
|
|
// so it can pause the event loop before any new event fires.
|
|
if (hadBreakpoint && vm->breakpointFn) {
|
|
vm->breakpointFn(vm->breakpointCtx, -1);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmCallSub
|
|
// ============================================================
|
|
|
|
bool basVmCallSub(BasVmT *vm, int32_t codeAddr) {
|
|
if (!vm || !vm->module) {
|
|
return false;
|
|
}
|
|
|
|
// Reject calls while debugger is paused (break mode between events)
|
|
if (vm->debugPaused) {
|
|
return false;
|
|
}
|
|
|
|
if (codeAddr < 0 || codeAddr >= vm->module->codeLen) {
|
|
return false;
|
|
}
|
|
|
|
if (vm->callDepth >= BAS_VM_CALL_STACK_SIZE - 1) {
|
|
return false;
|
|
}
|
|
|
|
// Save VM state
|
|
int32_t savedPc = vm->pc;
|
|
int32_t savedCallDepth = vm->callDepth;
|
|
bool savedRunning = vm->running;
|
|
|
|
// Push a call frame that returns to an invalid address (sentinel)
|
|
// We detect completion when callDepth drops back to savedCallDepth
|
|
BasCallFrameT *frame = &vm->callStack[vm->callDepth++];
|
|
frame->returnPc = savedPc;
|
|
frame->localCount = BAS_VM_MAX_LOCALS;
|
|
memset(frame->locals, 0, sizeof(frame->locals));
|
|
|
|
// Jump to the SUB
|
|
vm->pc = codeAddr;
|
|
vm->running = true;
|
|
|
|
return runSubLoop(vm, savedPc, savedCallDepth, savedRunning);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmCallSubWithArgs
|
|
// ============================================================
|
|
|
|
bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount) {
|
|
if (!vm || !vm->module) {
|
|
return false;
|
|
}
|
|
|
|
if (vm->debugPaused) {
|
|
return false;
|
|
}
|
|
|
|
if (codeAddr < 0 || codeAddr >= vm->module->codeLen) {
|
|
return false;
|
|
}
|
|
|
|
if (vm->callDepth >= BAS_VM_CALL_STACK_SIZE - 1) {
|
|
return false;
|
|
}
|
|
|
|
int32_t savedPc = vm->pc;
|
|
int32_t savedCallDepth = vm->callDepth;
|
|
bool savedRunning = vm->running;
|
|
|
|
BasCallFrameT *frame = &vm->callStack[vm->callDepth++];
|
|
frame->returnPc = savedPc;
|
|
frame->localCount = BAS_VM_MAX_LOCALS;
|
|
memset(frame->locals, 0, sizeof(frame->locals));
|
|
|
|
// Set arguments as locals (parameter 0 = local 0, etc.)
|
|
for (int32_t i = 0; i < argCount && i < BAS_VM_MAX_LOCALS; i++) {
|
|
frame->locals[i] = basValCopy(args[i]);
|
|
}
|
|
|
|
vm->pc = codeAddr;
|
|
vm->running = true;
|
|
|
|
return runSubLoop(vm, savedPc, savedCallDepth, savedRunning);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmCallSubWithArgsOut
|
|
// ============================================================
|
|
|
|
bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount, BasValueT *outArgs, int32_t outCount) {
|
|
if (!vm || !vm->module) {
|
|
return false;
|
|
}
|
|
|
|
if (vm->debugPaused) {
|
|
return false;
|
|
}
|
|
|
|
if (codeAddr < 0 || codeAddr >= vm->module->codeLen) {
|
|
return false;
|
|
}
|
|
|
|
if (vm->callDepth >= BAS_VM_CALL_STACK_SIZE - 1) {
|
|
return false;
|
|
}
|
|
|
|
int32_t savedPc = vm->pc;
|
|
int32_t savedCallDepth = vm->callDepth;
|
|
bool savedRunning = vm->running;
|
|
|
|
BasCallFrameT *frame = &vm->callStack[vm->callDepth++];
|
|
frame->returnPc = savedPc;
|
|
frame->localCount = BAS_VM_MAX_LOCALS;
|
|
memset(frame->locals, 0, sizeof(frame->locals));
|
|
|
|
for (int32_t i = 0; i < argCount && i < BAS_VM_MAX_LOCALS; i++) {
|
|
frame->locals[i] = basValCopy(args[i]);
|
|
}
|
|
|
|
vm->pc = codeAddr;
|
|
vm->running = true;
|
|
|
|
bool ok = runSubLoop(vm, savedPc, savedCallDepth, savedRunning);
|
|
|
|
// Read back modified locals before the frame is reused.
|
|
// The frame at savedCallDepth still has the data even
|
|
// though callDepth was decremented by RET.
|
|
if (ok && outArgs && outCount > 0) {
|
|
BasCallFrameT *doneFrame = &vm->callStack[savedCallDepth];
|
|
|
|
for (int32_t i = 0; i < outCount && i < BAS_VM_MAX_LOCALS; i++) {
|
|
outArgs[i] = basValCopy(doneFrame->locals[i]);
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmCreate
|
|
// ============================================================
|
|
|
|
BasVmT *basVmCreate(void) {
|
|
BasVmT *vm = (BasVmT *)calloc(1, sizeof(BasVmT));
|
|
|
|
if (!vm) {
|
|
return NULL;
|
|
}
|
|
|
|
vm->printFn = defaultPrint;
|
|
vm->stepOverDepth = -1;
|
|
vm->stepOutDepth = -1;
|
|
vm->runToCursorLine = -1;
|
|
basStringSystemInit();
|
|
return vm;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmDestroy
|
|
// ============================================================
|
|
|
|
void basVmDestroy(BasVmT *vm) {
|
|
if (!vm) {
|
|
return;
|
|
}
|
|
|
|
// Release stack values
|
|
for (int32_t i = 0; i < vm->sp; i++) {
|
|
basValRelease(&vm->stack[i]);
|
|
}
|
|
|
|
// Release global variables
|
|
for (int32_t i = 0; i < BAS_VM_MAX_GLOBALS; i++) {
|
|
basValRelease(&vm->globals[i]);
|
|
}
|
|
|
|
// Release call frame locals
|
|
for (int32_t d = 0; d < vm->callDepth; d++) {
|
|
for (int32_t i = 0; i < vm->callStack[d].localCount; i++) {
|
|
basValRelease(&vm->callStack[d].locals[i]);
|
|
}
|
|
}
|
|
|
|
// Release FOR stack
|
|
for (int32_t i = 0; i < vm->forDepth; i++) {
|
|
basValRelease(&vm->forStack[i].limit);
|
|
basValRelease(&vm->forStack[i].step);
|
|
}
|
|
|
|
// Close files
|
|
for (int32_t i = 0; i < BAS_VM_MAX_FILES; i++) {
|
|
if (vm->files[i].handle) {
|
|
fclose((FILE *)vm->files[i].handle);
|
|
}
|
|
}
|
|
|
|
// Close Dir$ iterator
|
|
dirClose();
|
|
|
|
basStringSystemShutdown();
|
|
free(vm);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmGetError
|
|
// ============================================================
|
|
|
|
const char *basVmGetError(const BasVmT *vm) {
|
|
return vm->errorMsg;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Debugger API
|
|
// ============================================================
|
|
|
|
void basVmSetBreakpoints(BasVmT *vm, int32_t *lines, int32_t count) {
|
|
if (!vm) {
|
|
return;
|
|
}
|
|
|
|
vm->breakpoints = lines;
|
|
vm->breakpointCount = count;
|
|
}
|
|
|
|
|
|
void basVmStepInto(BasVmT *vm) {
|
|
if (vm) {
|
|
vm->debugBreak = true;
|
|
}
|
|
}
|
|
|
|
|
|
void basVmStepOver(BasVmT *vm) {
|
|
if (vm) {
|
|
vm->stepOverDepth = vm->callDepth;
|
|
}
|
|
}
|
|
|
|
|
|
void basVmStepOut(BasVmT *vm) {
|
|
if (vm) {
|
|
vm->stepOutDepth = vm->callDepth;
|
|
}
|
|
}
|
|
|
|
|
|
void basVmRunToCursor(BasVmT *vm, int32_t line) {
|
|
if (vm) {
|
|
vm->runToCursorLine = line;
|
|
}
|
|
}
|
|
|
|
|
|
int32_t basVmGetCurrentLine(const BasVmT *vm) {
|
|
return vm ? vm->currentLine : 0;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmLoadModule
|
|
// ============================================================
|
|
|
|
void basVmLoadModule(BasVmT *vm, BasModuleT *module) {
|
|
vm->module = module;
|
|
vm->pc = module->entryPoint;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmPop
|
|
// ============================================================
|
|
|
|
bool basVmPop(BasVmT *vm, BasValueT *val) {
|
|
return pop(vm, val);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmPush
|
|
// ============================================================
|
|
|
|
bool basVmPush(BasVmT *vm, BasValueT val) {
|
|
return push(vm, val);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmReset
|
|
// ============================================================
|
|
|
|
void basVmReset(BasVmT *vm) {
|
|
for (int32_t i = 0; i < vm->sp; i++) {
|
|
basValRelease(&vm->stack[i]);
|
|
}
|
|
|
|
for (int32_t i = 0; i < BAS_VM_MAX_GLOBALS; i++) {
|
|
basValRelease(&vm->globals[i]);
|
|
}
|
|
|
|
vm->sp = 0;
|
|
vm->callDepth = 0;
|
|
vm->forDepth = 0;
|
|
vm->pc = vm->module ? vm->module->entryPoint : 0;
|
|
vm->running = false;
|
|
vm->yielded = false;
|
|
vm->dataPtr = 0;
|
|
vm->errorHandler = 0;
|
|
vm->errorNumber = 0;
|
|
vm->errorPc = 0;
|
|
vm->errorNextPc = 0;
|
|
vm->inErrorHandler = false;
|
|
vm->errorMsg[0] = '\0';
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmRun
|
|
// ============================================================
|
|
|
|
BasVmResultE basVmRun(BasVmT *vm) {
|
|
vm->running = true;
|
|
vm->yielded = false;
|
|
vm->stepCount = 0;
|
|
|
|
while (vm->running) {
|
|
// Check step limit
|
|
if (vm->stepLimit > 0 && vm->stepCount >= vm->stepLimit) {
|
|
return BAS_VM_STEP_LIMIT;
|
|
}
|
|
|
|
// Save PC before each instruction for RESUME support
|
|
int32_t savedPc = vm->pc;
|
|
BasVmResultE result = basVmStep(vm);
|
|
vm->stepCount++;
|
|
|
|
if (result != BAS_VM_OK) {
|
|
// If an error handler is set and this is a trappable error,
|
|
// jump to the handler instead of stopping execution
|
|
if (vm->errorHandler != 0 && !vm->inErrorHandler && result != BAS_VM_HALTED && result != BAS_VM_BAD_OPCODE) {
|
|
vm->errorPc = savedPc;
|
|
vm->errorNextPc = vm->pc;
|
|
vm->inErrorHandler = true;
|
|
vm->pc = vm->errorHandler;
|
|
continue;
|
|
}
|
|
|
|
vm->running = false;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return BAS_VM_HALTED;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmSetDoEventsCallback
|
|
// ============================================================
|
|
|
|
void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx) {
|
|
vm->doEventsFn = fn;
|
|
vm->doEventsCtx = ctx;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmSetCurrentForm
|
|
// ============================================================
|
|
|
|
void basVmSetCurrentForm(BasVmT *vm, void *formRef) {
|
|
vm->currentForm = formRef;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmSetCurrentFormVars
|
|
// ============================================================
|
|
|
|
void basVmSetCurrentFormVars(BasVmT *vm, BasValueT *vars, int32_t count) {
|
|
vm->currentFormVars = vars;
|
|
vm->currentFormVarCount = count;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmSetStepLimit
|
|
// ============================================================
|
|
|
|
void basVmSetStepLimit(BasVmT *vm, int32_t limit) {
|
|
vm->stepLimit = limit;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmSetInputCallback
|
|
// ============================================================
|
|
|
|
void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx) {
|
|
vm->inputFn = fn;
|
|
vm->inputCtx = ctx;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmSetPrintCallback
|
|
// ============================================================
|
|
|
|
void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx) {
|
|
vm->printFn = fn;
|
|
vm->printCtx = ctx;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmSetUiCallbacks
|
|
// ============================================================
|
|
|
|
void basVmSetExternCallbacks(BasVmT *vm, const BasExternCallbacksT *ext) {
|
|
vm->ext = *ext;
|
|
}
|
|
|
|
|
|
void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui) {
|
|
vm->ui = *ui;
|
|
}
|
|
|
|
|
|
void basVmSetSqlCallbacks(BasVmT *vm, const BasSqlCallbacksT *sql) {
|
|
vm->sql = *sql;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basVmStep -- execute one instruction
|
|
// ============================================================
|
|
|
|
BasVmResultE basVmStep(BasVmT *vm) {
|
|
if (!vm->module || vm->pc < 0 || vm->pc >= vm->module->codeLen) {
|
|
vm->running = false;
|
|
return BAS_VM_HALTED;
|
|
}
|
|
|
|
uint8_t op = vm->module->code[vm->pc++];
|
|
|
|
switch (op) {
|
|
case OP_NOP:
|
|
break;
|
|
|
|
// ============================================================
|
|
// Stack operations
|
|
// ============================================================
|
|
|
|
case OP_PUSH_INT16: {
|
|
int16_t val = readInt16(vm);
|
|
|
|
if (!push(vm, basValInteger(val))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_PUSH_INT32: {
|
|
int16_t lo = readInt16(vm);
|
|
int16_t hi = readInt16(vm);
|
|
int32_t val = ((int32_t)hi << 16) | (uint16_t)lo;
|
|
|
|
if (!push(vm, basValLong(val))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_PUSH_FLT32: {
|
|
float val;
|
|
memcpy(&val, &vm->module->code[vm->pc], sizeof(float));
|
|
vm->pc += sizeof(float);
|
|
|
|
if (!push(vm, basValSingle(val))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_PUSH_FLT64: {
|
|
double val;
|
|
memcpy(&val, &vm->module->code[vm->pc], sizeof(double));
|
|
vm->pc += sizeof(double);
|
|
|
|
if (!push(vm, basValDouble(val))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_PUSH_STR: {
|
|
uint16_t idx = readUint16(vm);
|
|
|
|
if (idx < (uint16_t)vm->module->constCount) {
|
|
if (!push(vm, basValString(vm->module->constants[idx]))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
} else {
|
|
if (!push(vm, basValStringFromC(""))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_PUSH_TRUE:
|
|
if (!push(vm, basValBool(true))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
break;
|
|
|
|
case OP_PUSH_FALSE:
|
|
if (!push(vm, basValBool(false))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
break;
|
|
|
|
case OP_POP: {
|
|
BasValueT val;
|
|
|
|
if (!pop(vm, &val)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
basValRelease(&val);
|
|
break;
|
|
}
|
|
|
|
case OP_DUP: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (!push(vm, basValCopy(vm->stack[vm->sp - 1]))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// Variable access
|
|
// ============================================================
|
|
|
|
case OP_LOAD_LOCAL: {
|
|
uint16_t idx = readUint16(vm);
|
|
BasCallFrameT *frame = currentFrame(vm);
|
|
|
|
if (!frame || idx >= (uint16_t)frame->localCount) {
|
|
runtimeError(vm, 9, "Invalid local variable index");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
// ByRef: if the local holds a reference, dereference it
|
|
BasValueT *slot = &frame->locals[idx];
|
|
BasValueT val = (slot->type == BAS_TYPE_REF) ? *slot->refVal : *slot;
|
|
|
|
if (!push(vm, basValCopy(val))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_STORE_LOCAL: {
|
|
uint16_t idx = readUint16(vm);
|
|
BasCallFrameT *frame = currentFrame(vm);
|
|
BasValueT val;
|
|
|
|
if (!pop(vm, &val)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (!frame || idx >= (uint16_t)frame->localCount) {
|
|
basValRelease(&val);
|
|
runtimeError(vm, 9, "Invalid local variable index");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
// ByRef: if the local holds a reference, store through it
|
|
BasValueT *slot = &frame->locals[idx];
|
|
|
|
if (slot->type == BAS_TYPE_REF) {
|
|
basValRelease(slot->refVal);
|
|
*slot->refVal = val;
|
|
} else {
|
|
basValRelease(slot);
|
|
*slot = val;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_LOAD_GLOBAL: {
|
|
uint16_t idx = readUint16(vm);
|
|
|
|
if (idx >= BAS_VM_MAX_GLOBALS) {
|
|
runtimeError(vm, 9, "Invalid global variable index");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
if (!push(vm, basValCopy(vm->globals[idx]))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_STORE_GLOBAL: {
|
|
uint16_t idx = readUint16(vm);
|
|
BasValueT val;
|
|
|
|
if (!pop(vm, &val)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (idx >= BAS_VM_MAX_GLOBALS) {
|
|
basValRelease(&val);
|
|
runtimeError(vm, 9, "Invalid global variable index");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
basValRelease(&vm->globals[idx]);
|
|
vm->globals[idx] = val;
|
|
break;
|
|
}
|
|
|
|
case OP_PUSH_LOCAL_ADDR: {
|
|
uint16_t idx = readUint16(vm);
|
|
BasCallFrameT *frame = currentFrame(vm);
|
|
|
|
if (!frame || idx >= (uint16_t)frame->localCount) {
|
|
runtimeError(vm, 9, "Invalid local variable index");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
BasValueT ref;
|
|
ref.type = BAS_TYPE_REF;
|
|
ref.refVal = &frame->locals[idx];
|
|
|
|
if (!push(vm, ref)) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_PUSH_GLOBAL_ADDR: {
|
|
uint16_t idx = readUint16(vm);
|
|
|
|
if (idx >= BAS_VM_MAX_GLOBALS) {
|
|
runtimeError(vm, 9, "Invalid global variable index");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
BasValueT ref;
|
|
ref.type = BAS_TYPE_REF;
|
|
ref.refVal = &vm->globals[idx];
|
|
|
|
if (!push(vm, ref)) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_LOAD_REF: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
|
|
if (top->type != BAS_TYPE_REF || !top->refVal) {
|
|
runtimeError(vm, 9, "Expected reference");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
BasValueT val = basValCopy(*top->refVal);
|
|
*top = val;
|
|
break;
|
|
}
|
|
|
|
case OP_STORE_REF: {
|
|
BasValueT val;
|
|
BasValueT ref;
|
|
|
|
if (!pop(vm, &val) || !pop(vm, &ref)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (ref.type != BAS_TYPE_REF || !ref.refVal) {
|
|
basValRelease(&val);
|
|
runtimeError(vm, 9, "Expected reference");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
basValRelease(ref.refVal);
|
|
*ref.refVal = val;
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// Arithmetic
|
|
// ============================================================
|
|
|
|
case OP_ADD_INT:
|
|
case OP_SUB_INT:
|
|
case OP_MUL_INT:
|
|
case OP_IDIV_INT:
|
|
case OP_MOD_INT:
|
|
case OP_ADD_FLT:
|
|
case OP_SUB_FLT:
|
|
case OP_MUL_FLT:
|
|
case OP_DIV_FLT:
|
|
case OP_POW:
|
|
return execArith(vm, op);
|
|
|
|
case OP_NEG_INT: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
|
|
if (top->type == BAS_TYPE_INTEGER) {
|
|
top->intVal = -top->intVal;
|
|
} else if (top->type == BAS_TYPE_LONG) {
|
|
top->longVal = -top->longVal;
|
|
} else {
|
|
double n = basValToNumber(*top);
|
|
basValRelease(top);
|
|
*top = basValDouble(-n);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_NEG_FLT: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
|
|
if (top->type == BAS_TYPE_SINGLE) {
|
|
top->sngVal = -top->sngVal;
|
|
} else if (top->type == BAS_TYPE_DOUBLE) {
|
|
top->dblVal = -top->dblVal;
|
|
} else {
|
|
double n = basValToNumber(*top);
|
|
basValRelease(top);
|
|
*top = basValDouble(-n);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// String operations
|
|
// ============================================================
|
|
|
|
case OP_STR_CONCAT:
|
|
case OP_STR_LEFT:
|
|
case OP_STR_RIGHT:
|
|
case OP_STR_MID:
|
|
case OP_STR_MID2:
|
|
case OP_STR_LEN:
|
|
case OP_STR_INSTR:
|
|
case OP_STR_INSTR3:
|
|
case OP_STR_UCASE:
|
|
case OP_STR_LCASE:
|
|
case OP_STR_TRIM:
|
|
case OP_STR_LTRIM:
|
|
case OP_STR_RTRIM:
|
|
case OP_STR_CHR:
|
|
case OP_STR_ASC:
|
|
case OP_STR_SPACE:
|
|
case OP_STR_FIXLEN:
|
|
case OP_STR_MID_ASGN:
|
|
return execStringOp(vm, op);
|
|
|
|
// ============================================================
|
|
// Comparison
|
|
// ============================================================
|
|
|
|
case OP_CMP_EQ:
|
|
case OP_CMP_NE:
|
|
case OP_CMP_LT:
|
|
case OP_CMP_GT:
|
|
case OP_CMP_LE:
|
|
case OP_CMP_GE:
|
|
return execCompare(vm, op);
|
|
|
|
// ============================================================
|
|
// Logical / bitwise
|
|
// ============================================================
|
|
|
|
case OP_AND:
|
|
case OP_OR:
|
|
case OP_NOT:
|
|
case OP_XOR:
|
|
case OP_EQV:
|
|
case OP_IMP:
|
|
return execLogical(vm, op);
|
|
|
|
// ============================================================
|
|
// Control flow
|
|
// ============================================================
|
|
|
|
case OP_JMP: {
|
|
int16_t offset = readInt16(vm);
|
|
vm->pc += offset;
|
|
break;
|
|
}
|
|
|
|
case OP_JMP_TRUE: {
|
|
int16_t offset = readInt16(vm);
|
|
BasValueT val;
|
|
|
|
if (!pop(vm, &val)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (basValIsTruthy(val)) {
|
|
vm->pc += offset;
|
|
}
|
|
|
|
basValRelease(&val);
|
|
break;
|
|
}
|
|
|
|
case OP_JMP_FALSE: {
|
|
int16_t offset = readInt16(vm);
|
|
BasValueT val;
|
|
|
|
if (!pop(vm, &val)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (!basValIsTruthy(val)) {
|
|
vm->pc += offset;
|
|
}
|
|
|
|
basValRelease(&val);
|
|
break;
|
|
}
|
|
|
|
case OP_CALL: {
|
|
uint16_t addr = readUint16(vm);
|
|
uint8_t argc = readUint8(vm);
|
|
uint8_t baseSlot = readUint8(vm);
|
|
|
|
if (vm->callDepth >= BAS_VM_CALL_STACK_SIZE) {
|
|
return BAS_VM_CALL_OVERFLOW;
|
|
}
|
|
|
|
BasCallFrameT *frame = &vm->callStack[vm->callDepth++];
|
|
frame->returnPc = vm->pc;
|
|
frame->localCount = BAS_VM_MAX_LOCALS;
|
|
|
|
// Zero all local slots
|
|
memset(frame->locals, 0, sizeof(frame->locals));
|
|
|
|
// Pop arguments into locals starting at baseSlot (in reverse order)
|
|
for (int32_t i = baseSlot + argc - 1; i >= baseSlot; i--) {
|
|
if (!pop(vm, &frame->locals[i])) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
}
|
|
|
|
vm->pc = addr;
|
|
break;
|
|
}
|
|
|
|
case OP_RET: {
|
|
if (vm->callDepth <= 0) {
|
|
vm->running = false;
|
|
return BAS_VM_HALTED;
|
|
}
|
|
|
|
BasCallFrameT *frame = &vm->callStack[--vm->callDepth];
|
|
|
|
// Release locals
|
|
for (int32_t i = 0; i < frame->localCount; i++) {
|
|
basValRelease(&frame->locals[i]);
|
|
}
|
|
|
|
vm->pc = frame->returnPc;
|
|
break;
|
|
}
|
|
|
|
case OP_RET_VAL: {
|
|
// Like RET but leaves TOS as the return value
|
|
BasValueT retVal;
|
|
|
|
if (!pop(vm, &retVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (vm->callDepth <= 0) {
|
|
basValRelease(&retVal);
|
|
vm->running = false;
|
|
return BAS_VM_HALTED;
|
|
}
|
|
|
|
BasCallFrameT *frame = &vm->callStack[--vm->callDepth];
|
|
|
|
for (int32_t i = 0; i < frame->localCount; i++) {
|
|
basValRelease(&frame->locals[i]);
|
|
}
|
|
|
|
vm->pc = frame->returnPc;
|
|
|
|
if (!push(vm, retVal)) {
|
|
basValRelease(&retVal);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_GOSUB_RET: {
|
|
// GOSUB return: pop integer from eval stack, set PC to that value
|
|
BasValueT retAddr;
|
|
|
|
if (!pop(vm, &retAddr)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
vm->pc = (int32_t)basValToNumber(retAddr);
|
|
basValRelease(&retAddr);
|
|
break;
|
|
}
|
|
|
|
case OP_FOR_INIT: {
|
|
uint16_t varIdx = readUint16(vm);
|
|
uint8_t scopeTag = readUint8(vm);
|
|
BasValueT stepVal;
|
|
BasValueT limitVal;
|
|
|
|
if (!pop(vm, &stepVal) || !pop(vm, &limitVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (vm->forDepth >= BAS_VM_MAX_FOR_DEPTH) {
|
|
basValRelease(&stepVal);
|
|
basValRelease(&limitVal);
|
|
runtimeError(vm, 26, "FOR loop nesting too deep");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
BasForStateT *fs = &vm->forStack[vm->forDepth++];
|
|
fs->varIdx = varIdx;
|
|
fs->scopeTag = scopeTag;
|
|
fs->limit = limitVal;
|
|
fs->step = stepVal;
|
|
fs->loopTop = vm->pc;
|
|
break;
|
|
}
|
|
|
|
case OP_FOR_NEXT: {
|
|
uint16_t varIdx = readUint16(vm);
|
|
uint8_t scopeTag = readUint8(vm);
|
|
int16_t loopTopOffset = readInt16(vm);
|
|
|
|
if (vm->forDepth <= 0) {
|
|
runtimeError(vm, 1, "NEXT without FOR");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
BasForStateT *fs = &vm->forStack[vm->forDepth - 1];
|
|
|
|
if (fs->varIdx != (int32_t)varIdx) {
|
|
runtimeError(vm, 1, "NEXT variable mismatch");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
// Get pointer to the loop variable
|
|
BasValueT *varSlot;
|
|
|
|
if (scopeTag == 1) {
|
|
BasCallFrameT *frame = currentFrame(vm);
|
|
|
|
if (!frame || varIdx >= (uint16_t)frame->localCount) {
|
|
runtimeError(vm, 9, "Invalid local variable index");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
varSlot = &frame->locals[varIdx];
|
|
} else if (scopeTag == 2) {
|
|
if (!vm->currentFormVars || varIdx >= (uint16_t)vm->currentFormVarCount) {
|
|
runtimeError(vm, 9, "Invalid form variable index");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
varSlot = &vm->currentFormVars[varIdx];
|
|
} else {
|
|
if (varIdx >= BAS_VM_MAX_GLOBALS) {
|
|
runtimeError(vm, 9, "Invalid global variable index");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
varSlot = &vm->globals[varIdx];
|
|
}
|
|
|
|
// Increment: var = var + step
|
|
double varVal = basValToNumber(*varSlot);
|
|
double stepVal = basValToNumber(fs->step);
|
|
double limVal = basValToNumber(fs->limit);
|
|
varVal += stepVal;
|
|
|
|
basValRelease(varSlot);
|
|
*varSlot = basValDouble(varVal);
|
|
|
|
// Test: if step > 0 then continue while var <= limit
|
|
// if step < 0 then continue while var >= limit
|
|
bool cont;
|
|
|
|
if (stepVal >= 0) {
|
|
cont = (varVal <= limVal);
|
|
} else {
|
|
cont = (varVal >= limVal);
|
|
}
|
|
|
|
if (cont) {
|
|
vm->pc += loopTopOffset;
|
|
} else {
|
|
// Loop done -- pop FOR state
|
|
basValRelease(&fs->limit);
|
|
basValRelease(&fs->step);
|
|
vm->forDepth--;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FOR_POP: {
|
|
if (vm->forDepth <= 0) {
|
|
runtimeError(vm, 1, "FOR stack underflow");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
BasForStateT *fs = &vm->forStack[--vm->forDepth];
|
|
basValRelease(&fs->limit);
|
|
basValRelease(&fs->step);
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// Type conversion
|
|
// ============================================================
|
|
|
|
case OP_CONV_INT_FLT: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
double n = basValToNumber(*top);
|
|
basValRelease(top);
|
|
*top = basValSingle((float)n);
|
|
break;
|
|
}
|
|
|
|
case OP_CONV_FLT_INT: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
BasValueT conv = basValToInteger(*top);
|
|
basValRelease(top);
|
|
*top = conv;
|
|
break;
|
|
}
|
|
|
|
case OP_CONV_INT_STR: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
BasValueT conv = basValToString(*top);
|
|
basValRelease(top);
|
|
*top = conv;
|
|
break;
|
|
}
|
|
|
|
case OP_CONV_STR_INT: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
BasValueT conv = basValToInteger(*top);
|
|
basValRelease(top);
|
|
*top = conv;
|
|
break;
|
|
}
|
|
|
|
case OP_CONV_FLT_STR: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
BasValueT conv = basValToString(*top);
|
|
basValRelease(top);
|
|
*top = conv;
|
|
break;
|
|
}
|
|
|
|
case OP_CONV_STR_FLT: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
BasValueT conv = basValToDouble(*top);
|
|
basValRelease(top);
|
|
*top = conv;
|
|
break;
|
|
}
|
|
|
|
case OP_CONV_INT_LONG: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
BasValueT conv = basValToLong(*top);
|
|
basValRelease(top);
|
|
*top = conv;
|
|
break;
|
|
}
|
|
|
|
case OP_CONV_LONG_INT: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
BasValueT conv = basValToInteger(*top);
|
|
basValRelease(top);
|
|
*top = conv;
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// I/O
|
|
// ============================================================
|
|
|
|
case OP_PRINT:
|
|
return execPrint(vm);
|
|
|
|
case OP_PRINT_NL:
|
|
if (vm->printFn) {
|
|
vm->printFn(vm->printCtx, "", true);
|
|
}
|
|
break;
|
|
|
|
case OP_PRINT_TAB:
|
|
if (vm->printFn) {
|
|
vm->printFn(vm->printCtx, "\t", false);
|
|
}
|
|
break;
|
|
|
|
case OP_PRINT_SPC: {
|
|
uint8_t n = readUint8(vm);
|
|
char spaces[256];
|
|
int32_t count = n < 255 ? n : 255;
|
|
memset(spaces, ' ', count);
|
|
spaces[count] = '\0';
|
|
|
|
if (vm->printFn) {
|
|
vm->printFn(vm->printCtx, spaces, false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_PRINT_SPC_N: {
|
|
BasValueT nVal;
|
|
|
|
if (!pop(vm, &nVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t count = (int32_t)basValToNumber(nVal);
|
|
basValRelease(&nVal);
|
|
|
|
if (count < 0) {
|
|
count = 0;
|
|
}
|
|
|
|
if (count > 255) {
|
|
count = 255;
|
|
}
|
|
|
|
char spaces[256];
|
|
memset(spaces, ' ', count);
|
|
spaces[count] = '\0';
|
|
|
|
if (vm->printFn) {
|
|
vm->printFn(vm->printCtx, spaces, false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_PRINT_TAB_N: {
|
|
BasValueT nVal;
|
|
|
|
if (!pop(vm, &nVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t col = (int32_t)basValToNumber(nVal);
|
|
basValRelease(&nVal);
|
|
|
|
if (col < 1) {
|
|
col = 1;
|
|
}
|
|
|
|
if (col > 255) {
|
|
col = 255;
|
|
}
|
|
|
|
// TAB outputs spaces to reach the specified column
|
|
// For simplicity, just output (col-1) spaces
|
|
char spaces[256];
|
|
int32_t count = col - 1;
|
|
memset(spaces, ' ', count);
|
|
spaces[count] = '\0';
|
|
|
|
if (vm->printFn) {
|
|
vm->printFn(vm->printCtx, spaces, false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_PRINT_USING: {
|
|
// Pop value and format string (format is below value on stack)
|
|
BasValueT val;
|
|
BasValueT fmtVal;
|
|
|
|
if (!pop(vm, &val) || !pop(vm, &fmtVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT fmtStr = basValToString(fmtVal);
|
|
basValRelease(&fmtVal);
|
|
|
|
const char *fmt = fmtStr.strVal->data;
|
|
int32_t fmtLen = fmtStr.strVal->len;
|
|
char buf[256];
|
|
|
|
if (val.type == BAS_TYPE_STRING) {
|
|
// String formatting
|
|
const char *src = val.strVal ? val.strVal->data : "";
|
|
|
|
if (fmtLen > 0 && fmt[0] == '!') {
|
|
// First character only
|
|
buf[0] = src[0] ? src[0] : ' ';
|
|
buf[1] = '\0';
|
|
} else if (fmtLen > 0 && fmt[0] == '&') {
|
|
// Entire string
|
|
snprintf(buf, sizeof(buf), "%s", src);
|
|
} else if (fmtLen >= 2 && fmt[0] == '\\') {
|
|
// Fixed-width: count characters between backslashes
|
|
int32_t width = 2;
|
|
for (int32_t i = 1; i < fmtLen; i++) {
|
|
if (fmt[i] == '\\') {
|
|
width = i + 1;
|
|
break;
|
|
}
|
|
width = i + 2;
|
|
}
|
|
int32_t srcLen = (int32_t)strlen(src);
|
|
int32_t copyLen = srcLen < width ? srcLen : width;
|
|
memcpy(buf, src, copyLen);
|
|
for (int32_t i = copyLen; i < width; i++) {
|
|
buf[i] = ' ';
|
|
}
|
|
buf[width] = '\0';
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "%s", src);
|
|
}
|
|
} else {
|
|
// Numeric formatting
|
|
double n = basValToNumber(val);
|
|
|
|
// Parse format flags
|
|
bool asteriskFill = false; // ** fill leading spaces with *
|
|
bool dollarFloat = false; // $$ floating dollar sign
|
|
bool plusAtStart = false; // + at start: always show sign
|
|
bool plusAtEnd = false; // + at end: always show sign
|
|
bool minusAtEnd = false; // - at end: show minus for negative
|
|
bool sciNotation = false; // ^^^^ scientific notation
|
|
bool hasDecimal = false;
|
|
bool hasComma = false;
|
|
int32_t digitsBefore = 0;
|
|
int32_t digitsAfter = 0;
|
|
|
|
// Check for ** at start
|
|
if (fmtLen >= 2 && fmt[0] == '*' && fmt[1] == '*') {
|
|
asteriskFill = true;
|
|
}
|
|
|
|
// Check for $$ (may follow **)
|
|
int32_t scanStart = 0;
|
|
if (asteriskFill) {
|
|
scanStart = 2;
|
|
}
|
|
if (fmtLen >= scanStart + 2 && fmt[scanStart] == '$' && fmt[scanStart + 1] == '$') {
|
|
dollarFloat = true;
|
|
}
|
|
|
|
// Check for + at start or end
|
|
if (fmtLen > 0 && fmt[0] == '+') {
|
|
plusAtStart = true;
|
|
}
|
|
if (fmtLen > 0 && fmt[fmtLen - 1] == '+') {
|
|
plusAtEnd = true;
|
|
}
|
|
|
|
// Check for - at end
|
|
if (fmtLen > 0 && fmt[fmtLen - 1] == '-') {
|
|
minusAtEnd = true;
|
|
}
|
|
|
|
// Check for ^^^^ (scientific notation)
|
|
for (int32_t i = 0; i <= fmtLen - 4; i++) {
|
|
if (fmt[i] == '^' && fmt[i+1] == '^' && fmt[i+2] == '^' && fmt[i+3] == '^') {
|
|
sciNotation = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Count # and 0 digits before and after decimal
|
|
for (int32_t i = 0; i < fmtLen; i++) {
|
|
if (fmt[i] == '.') {
|
|
hasDecimal = true;
|
|
} else if (fmt[i] == ',') {
|
|
hasComma = true;
|
|
} else if (fmt[i] == '#' || fmt[i] == '0') {
|
|
if (hasDecimal) {
|
|
digitsAfter++;
|
|
} else {
|
|
digitsBefore++;
|
|
}
|
|
} else if (fmt[i] == '*') {
|
|
if (!hasDecimal) {
|
|
digitsBefore++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sciNotation) {
|
|
// Scientific notation
|
|
char sciFmt[32];
|
|
int32_t decimals = hasDecimal ? digitsAfter : 0;
|
|
snprintf(sciFmt, sizeof(sciFmt), "%%.%dE", (int)decimals);
|
|
snprintf(buf, sizeof(buf), sciFmt, n);
|
|
} else {
|
|
// Standard formatting
|
|
bool isNeg = (n < 0);
|
|
double absN = isNeg ? -n : n;
|
|
int32_t decimals = hasDecimal ? digitsAfter : 0;
|
|
|
|
char numBuf[128];
|
|
snprintf(numBuf, sizeof(numBuf), "%.*f", (int)decimals, absN);
|
|
|
|
// Split into integer and decimal parts
|
|
char intPart[128];
|
|
char decPart[128];
|
|
intPart[0] = '\0';
|
|
decPart[0] = '\0';
|
|
char *dot = strchr(numBuf, '.');
|
|
if (dot) {
|
|
int32_t intLen = (int32_t)(dot - numBuf);
|
|
memcpy(intPart, numBuf, intLen);
|
|
intPart[intLen] = '\0';
|
|
strncpy(decPart, dot + 1, sizeof(decPart) - 1);
|
|
decPart[sizeof(decPart) - 1] = '\0';
|
|
} else {
|
|
strncpy(intPart, numBuf, sizeof(intPart) - 1);
|
|
intPart[sizeof(intPart) - 1] = '\0';
|
|
}
|
|
|
|
// Apply thousands separator
|
|
char fmtIntPart[128];
|
|
if (hasComma) {
|
|
int32_t srcLen = (int32_t)strlen(intPart);
|
|
int32_t dstIdx = 0;
|
|
for (int32_t i = 0; i < srcLen; i++) {
|
|
if (i > 0 && (srcLen - i) % 3 == 0) {
|
|
fmtIntPart[dstIdx++] = ',';
|
|
}
|
|
fmtIntPart[dstIdx++] = intPart[i];
|
|
}
|
|
fmtIntPart[dstIdx] = '\0';
|
|
} else {
|
|
strncpy(fmtIntPart, intPart, sizeof(fmtIntPart) - 1);
|
|
fmtIntPart[sizeof(fmtIntPart) - 1] = '\0';
|
|
}
|
|
|
|
// Build result
|
|
int32_t idx = 0;
|
|
|
|
// Sign prefix
|
|
if (plusAtStart) {
|
|
buf[idx++] = isNeg ? '-' : '+';
|
|
} else if (isNeg && !minusAtEnd) {
|
|
buf[idx++] = '-';
|
|
}
|
|
|
|
// Dollar sign
|
|
if (dollarFloat) {
|
|
buf[idx++] = '$';
|
|
}
|
|
|
|
// Pad leading
|
|
int32_t intLen = (int32_t)strlen(fmtIntPart);
|
|
int32_t padNeeded = digitsBefore - intLen;
|
|
char fillChar = asteriskFill ? '*' : ' ';
|
|
|
|
for (int32_t i = 0; i < padNeeded; i++) {
|
|
buf[idx++] = fillChar;
|
|
}
|
|
|
|
// Integer part
|
|
for (int32_t i = 0; fmtIntPart[i]; i++) {
|
|
buf[idx++] = fmtIntPart[i];
|
|
}
|
|
|
|
// Decimal part
|
|
if (hasDecimal) {
|
|
buf[idx++] = '.';
|
|
for (int32_t i = 0; i < decimals; i++) {
|
|
buf[idx++] = decPart[i] ? decPart[i] : '0';
|
|
}
|
|
}
|
|
|
|
// Trailing sign
|
|
if (plusAtEnd) {
|
|
buf[idx++] = isNeg ? '-' : '+';
|
|
} else if (minusAtEnd) {
|
|
buf[idx++] = isNeg ? '-' : ' ';
|
|
}
|
|
|
|
buf[idx] = '\0';
|
|
}
|
|
}
|
|
|
|
basValRelease(&val);
|
|
|
|
// Push format string back (PRINT USING reuses it for multiple values)
|
|
if (!push(vm, fmtStr)) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
// Push formatted result
|
|
if (!push(vm, basValStringFromC(buf))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_INPUT: {
|
|
// Pop the prompt string pushed by the parser
|
|
BasValueT promptVal;
|
|
|
|
if (!pop(vm, &promptVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
// Build the full prompt: "user prompt? "
|
|
char promptBuf[512];
|
|
const char *userPrompt = "";
|
|
|
|
if (promptVal.type == BAS_TYPE_STRING && promptVal.strVal) {
|
|
userPrompt = promptVal.strVal->data;
|
|
}
|
|
|
|
if (userPrompt[0]) {
|
|
snprintf(promptBuf, sizeof(promptBuf), "%s? ", userPrompt);
|
|
} else {
|
|
snprintf(promptBuf, sizeof(promptBuf), "? ");
|
|
}
|
|
|
|
char buf[1024];
|
|
buf[0] = '\0';
|
|
|
|
if (vm->inputFn) {
|
|
if (!vm->inputFn(vm->inputCtx, promptBuf, buf, sizeof(buf))) {
|
|
buf[0] = '\0';
|
|
}
|
|
}
|
|
|
|
basValRelease(&promptVal);
|
|
|
|
if (!push(vm, basValStringFromC(buf))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// Math built-ins
|
|
// ============================================================
|
|
|
|
case OP_MATH_ABS:
|
|
case OP_MATH_INT:
|
|
case OP_MATH_FIX:
|
|
case OP_MATH_SGN:
|
|
case OP_MATH_SQR:
|
|
case OP_MATH_SIN:
|
|
case OP_MATH_COS:
|
|
case OP_MATH_TAN:
|
|
case OP_MATH_ATN:
|
|
case OP_MATH_LOG:
|
|
case OP_MATH_EXP:
|
|
case OP_MATH_RND:
|
|
case OP_MATH_RANDOMIZE:
|
|
return execMath(vm, op);
|
|
|
|
// ============================================================
|
|
// Conversion built-ins
|
|
// ============================================================
|
|
|
|
case OP_STR_VAL: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
double n = basValToNumber(*top);
|
|
basValRelease(top);
|
|
*top = basValDouble(n);
|
|
break;
|
|
}
|
|
|
|
case OP_STR_STRF: {
|
|
// STR$(n): VB convention -- leading space for positive, "-" for negative
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
double n = basValToNumber(*top);
|
|
basValRelease(top);
|
|
|
|
char buf[64];
|
|
|
|
if (n >= 0.0) {
|
|
snprintf(buf, sizeof(buf), " %g", n);
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "%g", n);
|
|
}
|
|
|
|
top->type = BAS_TYPE_STRING;
|
|
top->strVal = basStringNew(buf, (int32_t)strlen(buf));
|
|
break;
|
|
}
|
|
|
|
case OP_STR_HEX: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
int32_t n = (int32_t)basValToNumber(*top);
|
|
char buf[16];
|
|
snprintf(buf, sizeof(buf), "%X", (unsigned int)n);
|
|
basValRelease(top);
|
|
*top = basValStringFromC(buf);
|
|
break;
|
|
}
|
|
|
|
case OP_STR_STRING: {
|
|
// STRING$(n, char)
|
|
BasValueT charVal;
|
|
BasValueT countVal;
|
|
|
|
if (!pop(vm, &charVal) || !pop(vm, &countVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t count = (int32_t)basValToNumber(countVal);
|
|
basValRelease(&countVal);
|
|
|
|
char ch;
|
|
|
|
if (charVal.type == BAS_TYPE_STRING && charVal.strVal && charVal.strVal->len > 0) {
|
|
ch = charVal.strVal->data[0];
|
|
} else {
|
|
ch = (char)(int32_t)basValToNumber(charVal);
|
|
}
|
|
|
|
basValRelease(&charVal);
|
|
|
|
if (count < 0) {
|
|
count = 0;
|
|
}
|
|
|
|
if (count > 32767) {
|
|
count = 32767;
|
|
}
|
|
|
|
BasStringT *s = basStringAlloc(count + 1);
|
|
memset(s->data, ch, count);
|
|
s->data[count] = '\0';
|
|
s->len = count;
|
|
|
|
if (!push(vm, basValString(s))) {
|
|
basStringUnref(s);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
basStringUnref(s);
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// Extended built-ins
|
|
// ============================================================
|
|
|
|
case OP_MATH_TIMER: {
|
|
// Push seconds since midnight as a double
|
|
time_t now = time(NULL);
|
|
struct tm *t = localtime(&now);
|
|
double secs = (double)t->tm_hour * 3600.0 + (double)t->tm_min * 60.0 + (double)t->tm_sec;
|
|
|
|
if (!push(vm, basValDouble(secs))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_DATE_STR: {
|
|
// Push DATE$ as "MM-DD-YYYY"
|
|
time_t now = time(NULL);
|
|
struct tm *t = localtime(&now);
|
|
char buf[32];
|
|
snprintf(buf, sizeof(buf), "%02d-%02d-%04d", t->tm_mon + 1, t->tm_mday, t->tm_year + 1900);
|
|
|
|
if (!push(vm, basValStringFromC(buf))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_TIME_STR: {
|
|
// Push TIME$ as "HH:MM:SS"
|
|
time_t now = time(NULL);
|
|
struct tm *t = localtime(&now);
|
|
char buf[16];
|
|
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", t->tm_hour, t->tm_min, t->tm_sec);
|
|
|
|
if (!push(vm, basValStringFromC(buf))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_SLEEP: {
|
|
// Pop seconds, sleep
|
|
BasValueT val;
|
|
|
|
if (!pop(vm, &val)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t secs = (int32_t)basValToNumber(val);
|
|
basValRelease(&val);
|
|
|
|
if (secs > 0) {
|
|
sleep((unsigned int)secs);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_ENVIRON: {
|
|
// Pop env var name, push value string
|
|
BasValueT nameVal;
|
|
|
|
if (!pop(vm, &nameVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT nameStr = basValToString(nameVal);
|
|
basValRelease(&nameVal);
|
|
|
|
const char *envVal = getenv(nameStr.strVal->data);
|
|
basValRelease(&nameStr);
|
|
|
|
if (envVal == NULL) {
|
|
envVal = "";
|
|
}
|
|
|
|
if (!push(vm, basValStringFromC(envVal))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// File I/O
|
|
// ============================================================
|
|
|
|
case OP_FILE_OPEN:
|
|
case OP_FILE_CLOSE:
|
|
case OP_FILE_PRINT:
|
|
case OP_FILE_INPUT:
|
|
case OP_FILE_EOF:
|
|
case OP_FILE_LINE_INPUT:
|
|
case OP_FILE_WRITE:
|
|
case OP_FILE_WRITE_SEP:
|
|
case OP_FILE_WRITE_NL:
|
|
case OP_FILE_GET:
|
|
case OP_FILE_PUT:
|
|
case OP_FILE_SEEK:
|
|
case OP_FILE_LOF:
|
|
case OP_FILE_LOC:
|
|
case OP_FILE_FREEFILE:
|
|
case OP_FILE_INPUT_N:
|
|
return execFileOp(vm, op);
|
|
|
|
case OP_FS_KILL:
|
|
case OP_FS_NAME:
|
|
case OP_FS_FILECOPY:
|
|
case OP_FS_MKDIR:
|
|
case OP_FS_RMDIR:
|
|
case OP_FS_CHDIR:
|
|
case OP_FS_CHDRIVE:
|
|
case OP_FS_CURDIR:
|
|
case OP_FS_DIR:
|
|
case OP_FS_DIR_NEXT:
|
|
case OP_FS_FILELEN:
|
|
case OP_FS_GETATTR:
|
|
case OP_FS_SETATTR:
|
|
return execFsOp(vm, op);
|
|
|
|
// ============================================================
|
|
// DoEvents
|
|
// ============================================================
|
|
|
|
case OP_DO_EVENTS:
|
|
if (vm->doEventsFn) {
|
|
if (!vm->doEventsFn(vm->doEventsCtx)) {
|
|
vm->running = false;
|
|
return BAS_VM_HALTED;
|
|
}
|
|
}
|
|
break;
|
|
|
|
// ============================================================
|
|
// Error handling
|
|
// ============================================================
|
|
|
|
case OP_ON_ERROR: {
|
|
int16_t handler = readInt16(vm);
|
|
vm->errorHandler = (handler == 0) ? 0 : vm->pc + handler;
|
|
break;
|
|
}
|
|
|
|
case OP_ERR_NUM:
|
|
if (!push(vm, basValInteger((int16_t)vm->errorNumber))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
break;
|
|
|
|
case OP_ERR_CLEAR:
|
|
vm->errorNumber = 0;
|
|
vm->errorMsg[0] = '\0';
|
|
break;
|
|
|
|
case OP_RESUME:
|
|
// RESUME -- re-execute the statement that caused the error
|
|
vm->pc = vm->errorPc;
|
|
vm->errorNumber = 0;
|
|
vm->errorMsg[0] = '\0';
|
|
vm->inErrorHandler = false;
|
|
break;
|
|
|
|
case OP_RESUME_NEXT:
|
|
// RESUME NEXT -- continue at next statement after the error
|
|
vm->pc = vm->errorNextPc;
|
|
vm->errorNumber = 0;
|
|
vm->errorMsg[0] = '\0';
|
|
vm->inErrorHandler = false;
|
|
break;
|
|
|
|
case OP_RAISE_ERR: {
|
|
BasValueT errVal;
|
|
|
|
if (!pop(vm, &errVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t errNum = (int32_t)basValToNumber(errVal);
|
|
basValRelease(&errVal);
|
|
runtimeError(vm, errNum, "User-defined error");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
// ============================================================
|
|
// Array / UDT operations
|
|
// ============================================================
|
|
|
|
case OP_DIM_ARRAY: {
|
|
uint8_t dims = readUint8(vm);
|
|
uint8_t elementType = readUint8(vm);
|
|
|
|
// dims=0 with elementType=BAS_TYPE_UDT means allocate a UDT instance
|
|
if (dims == 0 && elementType == BAS_TYPE_UDT) {
|
|
BasValueT fieldCountVal;
|
|
BasValueT typeIdVal;
|
|
|
|
if (!pop(vm, &fieldCountVal) || !pop(vm, &typeIdVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t fieldCount = (int32_t)basValToNumber(fieldCountVal);
|
|
int32_t typeId = (int32_t)basValToNumber(typeIdVal);
|
|
basValRelease(&fieldCountVal);
|
|
basValRelease(&typeIdVal);
|
|
|
|
BasUdtT *udt = basUdtNew(typeId, fieldCount);
|
|
|
|
if (!udt) {
|
|
runtimeError(vm, 7, "Out of memory allocating TYPE");
|
|
return BAS_VM_OUT_OF_MEMORY;
|
|
}
|
|
|
|
BasValueT udtVal;
|
|
udtVal.type = BAS_TYPE_UDT;
|
|
udtVal.udtVal = udt;
|
|
|
|
if (!push(vm, udtVal)) {
|
|
basUdtFree(udt);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// For UDT arrays, parser pushes typeId and fieldCount after bounds
|
|
int32_t udtTypeId = -1;
|
|
int32_t udtFieldCnt = 0;
|
|
|
|
if (elementType == BAS_TYPE_UDT) {
|
|
BasValueT fcVal;
|
|
BasValueT tiVal;
|
|
|
|
if (!pop(vm, &fcVal) || !pop(vm, &tiVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
udtFieldCnt = (int32_t)basValToNumber(fcVal);
|
|
udtTypeId = (int32_t)basValToNumber(tiVal);
|
|
basValRelease(&fcVal);
|
|
basValRelease(&tiVal);
|
|
}
|
|
|
|
// Normal array allocation: parser pushes (lbound, ubound) pairs per dim
|
|
int32_t lbounds[BAS_ARRAY_MAX_DIMS];
|
|
int32_t ubounds[BAS_ARRAY_MAX_DIMS];
|
|
|
|
// Pop bounds in reverse order (last dim first)
|
|
for (int32_t d = dims - 1; d >= 0; d--) {
|
|
BasValueT ubVal;
|
|
BasValueT lbVal;
|
|
|
|
if (!pop(vm, &ubVal) || !pop(vm, &lbVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
ubounds[d] = (int32_t)basValToNumber(ubVal);
|
|
lbounds[d] = (int32_t)basValToNumber(lbVal);
|
|
basValRelease(&ubVal);
|
|
basValRelease(&lbVal);
|
|
}
|
|
|
|
BasArrayT *arr = basArrayNew(dims, lbounds, ubounds, elementType);
|
|
|
|
if (!arr) {
|
|
runtimeError(vm, 7, "Out of memory allocating array");
|
|
return BAS_VM_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// Initialize UDT array elements with proper UDT instances
|
|
if (elementType == BAS_TYPE_UDT && udtTypeId >= 0) {
|
|
for (int32_t i = 0; i < arr->totalElements; i++) {
|
|
BasUdtT *udt = basUdtNew(udtTypeId, udtFieldCnt);
|
|
|
|
if (!udt) {
|
|
basArrayFree(arr);
|
|
runtimeError(vm, 7, "Out of memory allocating TYPE array elements");
|
|
return BAS_VM_OUT_OF_MEMORY;
|
|
}
|
|
|
|
arr->elements[i].type = BAS_TYPE_UDT;
|
|
arr->elements[i].udtVal = udt;
|
|
}
|
|
}
|
|
|
|
BasValueT arrVal;
|
|
arrVal.type = BAS_TYPE_ARRAY;
|
|
arrVal.arrVal = arr;
|
|
|
|
if (!push(vm, arrVal)) {
|
|
basArrayFree(arr);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_LOAD_ARRAY: {
|
|
uint8_t dims = readUint8(vm);
|
|
int32_t indices[BAS_ARRAY_MAX_DIMS];
|
|
|
|
// Pop indices in reverse order
|
|
for (int32_t d = dims - 1; d >= 0; d--) {
|
|
BasValueT idxVal;
|
|
|
|
if (!pop(vm, &idxVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
indices[d] = (int32_t)basValToNumber(idxVal);
|
|
basValRelease(&idxVal);
|
|
}
|
|
|
|
// Pop array reference
|
|
BasValueT arrRef;
|
|
|
|
if (!pop(vm, &arrRef)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (arrRef.type != BAS_TYPE_ARRAY || !arrRef.arrVal) {
|
|
basValRelease(&arrRef);
|
|
runtimeError(vm, 13, "Not an array");
|
|
return BAS_VM_TYPE_MISMATCH;
|
|
}
|
|
|
|
int32_t flatIdx = basArrayIndex(arrRef.arrVal, indices, dims);
|
|
|
|
if (flatIdx < 0) {
|
|
basValRelease(&arrRef);
|
|
runtimeError(vm, 9, "Subscript out of range");
|
|
return BAS_VM_SUBSCRIPT_RANGE;
|
|
}
|
|
|
|
BasValueT elem = basValCopy(arrRef.arrVal->elements[flatIdx]);
|
|
basValRelease(&arrRef);
|
|
|
|
if (!push(vm, elem)) {
|
|
basValRelease(&elem);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_STORE_ARRAY: {
|
|
uint8_t dims = readUint8(vm);
|
|
|
|
// Pop value to store
|
|
BasValueT storeVal;
|
|
|
|
if (!pop(vm, &storeVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
// Pop indices in reverse order
|
|
int32_t indices[BAS_ARRAY_MAX_DIMS];
|
|
|
|
for (int32_t d = dims - 1; d >= 0; d--) {
|
|
BasValueT idxVal;
|
|
|
|
if (!pop(vm, &idxVal)) {
|
|
basValRelease(&storeVal);
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
indices[d] = (int32_t)basValToNumber(idxVal);
|
|
basValRelease(&idxVal);
|
|
}
|
|
|
|
// Pop array reference
|
|
BasValueT arrRef;
|
|
|
|
if (!pop(vm, &arrRef)) {
|
|
basValRelease(&storeVal);
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (arrRef.type != BAS_TYPE_ARRAY || !arrRef.arrVal) {
|
|
basValRelease(&arrRef);
|
|
basValRelease(&storeVal);
|
|
runtimeError(vm, 13, "Not an array");
|
|
return BAS_VM_TYPE_MISMATCH;
|
|
}
|
|
|
|
int32_t flatIdx = basArrayIndex(arrRef.arrVal, indices, dims);
|
|
|
|
if (flatIdx < 0) {
|
|
basValRelease(&arrRef);
|
|
basValRelease(&storeVal);
|
|
runtimeError(vm, 9, "Subscript out of range");
|
|
return BAS_VM_SUBSCRIPT_RANGE;
|
|
}
|
|
|
|
basValRelease(&arrRef.arrVal->elements[flatIdx]);
|
|
arrRef.arrVal->elements[flatIdx] = storeVal;
|
|
basValRelease(&arrRef);
|
|
break;
|
|
}
|
|
|
|
case OP_STORE_ARRAY_FIELD: {
|
|
uint8_t dims = readUint8(vm);
|
|
uint16_t fieldIdx = readUint16(vm);
|
|
|
|
// Pop value to store
|
|
BasValueT storeVal;
|
|
|
|
if (!pop(vm, &storeVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
// Pop indices in reverse order
|
|
int32_t indices[BAS_ARRAY_MAX_DIMS];
|
|
|
|
for (int32_t d = dims - 1; d >= 0; d--) {
|
|
BasValueT idxVal;
|
|
|
|
if (!pop(vm, &idxVal)) {
|
|
basValRelease(&storeVal);
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
indices[d] = (int32_t)basValToNumber(idxVal);
|
|
basValRelease(&idxVal);
|
|
}
|
|
|
|
// Pop array reference
|
|
BasValueT arrRef;
|
|
|
|
if (!pop(vm, &arrRef)) {
|
|
basValRelease(&storeVal);
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (arrRef.type != BAS_TYPE_ARRAY || !arrRef.arrVal) {
|
|
basValRelease(&arrRef);
|
|
basValRelease(&storeVal);
|
|
runtimeError(vm, 13, "Not an array");
|
|
return BAS_VM_TYPE_MISMATCH;
|
|
}
|
|
|
|
int32_t flatIdx = basArrayIndex(arrRef.arrVal, indices, dims);
|
|
|
|
if (flatIdx < 0) {
|
|
basValRelease(&arrRef);
|
|
basValRelease(&storeVal);
|
|
runtimeError(vm, 9, "Subscript out of range");
|
|
return BAS_VM_SUBSCRIPT_RANGE;
|
|
}
|
|
|
|
// Element must be a UDT
|
|
BasValueT *elem = &arrRef.arrVal->elements[flatIdx];
|
|
|
|
if (elem->type != BAS_TYPE_UDT || !elem->udtVal) {
|
|
basValRelease(&arrRef);
|
|
basValRelease(&storeVal);
|
|
runtimeError(vm, 13, "Array element is not a TYPE instance");
|
|
return BAS_VM_TYPE_MISMATCH;
|
|
}
|
|
|
|
if (fieldIdx >= (uint16_t)elem->udtVal->fieldCount) {
|
|
basValRelease(&arrRef);
|
|
basValRelease(&storeVal);
|
|
runtimeError(vm, 9, "Invalid field index");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
basValRelease(&elem->udtVal->fields[fieldIdx]);
|
|
elem->udtVal->fields[fieldIdx] = storeVal;
|
|
basValRelease(&arrRef);
|
|
break;
|
|
}
|
|
|
|
case OP_REDIM: {
|
|
uint8_t dims = readUint8(vm);
|
|
uint8_t preserve = readUint8(vm);
|
|
|
|
int32_t lbounds[BAS_ARRAY_MAX_DIMS];
|
|
int32_t ubounds[BAS_ARRAY_MAX_DIMS];
|
|
|
|
for (int32_t d = dims - 1; d >= 0; d--) {
|
|
BasValueT ubVal;
|
|
BasValueT lbVal;
|
|
|
|
if (!pop(vm, &ubVal) || !pop(vm, &lbVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
ubounds[d] = (int32_t)basValToNumber(ubVal);
|
|
lbounds[d] = (int32_t)basValToNumber(lbVal);
|
|
basValRelease(&ubVal);
|
|
basValRelease(&lbVal);
|
|
}
|
|
|
|
// Pop old array reference
|
|
BasValueT oldRef;
|
|
|
|
if (!pop(vm, &oldRef)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
uint8_t elementType = BAS_TYPE_INTEGER;
|
|
|
|
if (oldRef.type == BAS_TYPE_ARRAY && oldRef.arrVal) {
|
|
elementType = oldRef.arrVal->elementType;
|
|
}
|
|
|
|
BasArrayT *newArr = basArrayNew(dims, lbounds, ubounds, elementType);
|
|
|
|
if (!newArr) {
|
|
basValRelease(&oldRef);
|
|
runtimeError(vm, 7, "Out of memory in REDIM");
|
|
return BAS_VM_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// Copy old elements if PRESERVE
|
|
if (preserve && oldRef.type == BAS_TYPE_ARRAY && oldRef.arrVal) {
|
|
int32_t copyCount = oldRef.arrVal->totalElements;
|
|
|
|
if (copyCount > newArr->totalElements) {
|
|
copyCount = newArr->totalElements;
|
|
}
|
|
|
|
for (int32_t i = 0; i < copyCount; i++) {
|
|
newArr->elements[i] = basValCopy(oldRef.arrVal->elements[i]);
|
|
}
|
|
}
|
|
|
|
basValRelease(&oldRef);
|
|
|
|
BasValueT arrVal;
|
|
arrVal.type = BAS_TYPE_ARRAY;
|
|
arrVal.arrVal = newArr;
|
|
|
|
if (!push(vm, arrVal)) {
|
|
basArrayFree(newArr);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_ERASE: {
|
|
BasValueT arrRef;
|
|
|
|
if (!pop(vm, &arrRef)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
basValRelease(&arrRef);
|
|
|
|
// Push an empty/zero value to store back
|
|
BasValueT empty;
|
|
memset(&empty, 0, sizeof(empty));
|
|
|
|
if (!push(vm, empty)) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_LBOUND: {
|
|
uint8_t dim = readUint8(vm);
|
|
BasValueT arrRef;
|
|
|
|
if (!pop(vm, &arrRef)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (arrRef.type != BAS_TYPE_ARRAY || !arrRef.arrVal) {
|
|
basValRelease(&arrRef);
|
|
runtimeError(vm, 13, "Not an array");
|
|
return BAS_VM_TYPE_MISMATCH;
|
|
}
|
|
|
|
if (dim < 1 || dim > (uint8_t)arrRef.arrVal->dims) {
|
|
basValRelease(&arrRef);
|
|
runtimeError(vm, 9, "Invalid dimension for LBOUND");
|
|
return BAS_VM_SUBSCRIPT_RANGE;
|
|
}
|
|
|
|
int32_t lb = arrRef.arrVal->lbound[dim - 1];
|
|
basValRelease(&arrRef);
|
|
|
|
if (!push(vm, basValLong(lb))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_UBOUND: {
|
|
uint8_t dim = readUint8(vm);
|
|
BasValueT arrRef;
|
|
|
|
if (!pop(vm, &arrRef)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (arrRef.type != BAS_TYPE_ARRAY || !arrRef.arrVal) {
|
|
basValRelease(&arrRef);
|
|
runtimeError(vm, 13, "Not an array");
|
|
return BAS_VM_TYPE_MISMATCH;
|
|
}
|
|
|
|
if (dim < 1 || dim > (uint8_t)arrRef.arrVal->dims) {
|
|
basValRelease(&arrRef);
|
|
runtimeError(vm, 9, "Invalid dimension for UBOUND");
|
|
return BAS_VM_SUBSCRIPT_RANGE;
|
|
}
|
|
|
|
int32_t ub = arrRef.arrVal->ubound[dim - 1];
|
|
basValRelease(&arrRef);
|
|
|
|
if (!push(vm, basValLong(ub))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_LOAD_FIELD: {
|
|
uint16_t fieldIdx = readUint16(vm);
|
|
BasValueT udtRef;
|
|
|
|
if (!pop(vm, &udtRef)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (udtRef.type != BAS_TYPE_UDT || !udtRef.udtVal) {
|
|
basValRelease(&udtRef);
|
|
runtimeError(vm, 13, "Not a TYPE instance");
|
|
return BAS_VM_TYPE_MISMATCH;
|
|
}
|
|
|
|
if (fieldIdx >= (uint16_t)udtRef.udtVal->fieldCount) {
|
|
basValRelease(&udtRef);
|
|
runtimeError(vm, 9, "Invalid field index");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
BasValueT fieldVal = basValCopy(udtRef.udtVal->fields[fieldIdx]);
|
|
basValRelease(&udtRef);
|
|
|
|
if (!push(vm, fieldVal)) {
|
|
basValRelease(&fieldVal);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_STORE_FIELD: {
|
|
uint16_t fieldIdx = readUint16(vm);
|
|
BasValueT storeVal;
|
|
|
|
if (!pop(vm, &storeVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT udtRef;
|
|
|
|
if (!pop(vm, &udtRef)) {
|
|
basValRelease(&storeVal);
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (udtRef.type != BAS_TYPE_UDT || !udtRef.udtVal) {
|
|
basValRelease(&udtRef);
|
|
basValRelease(&storeVal);
|
|
runtimeError(vm, 13, "Not a TYPE instance");
|
|
return BAS_VM_TYPE_MISMATCH;
|
|
}
|
|
|
|
if (fieldIdx >= (uint16_t)udtRef.udtVal->fieldCount) {
|
|
basValRelease(&udtRef);
|
|
basValRelease(&storeVal);
|
|
runtimeError(vm, 9, "Invalid field index");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
basValRelease(&udtRef.udtVal->fields[fieldIdx]);
|
|
udtRef.udtVal->fields[fieldIdx] = storeVal;
|
|
basValRelease(&udtRef);
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// DATA/READ/RESTORE
|
|
// ============================================================
|
|
|
|
case OP_READ_DATA: {
|
|
if (!vm->module || vm->dataPtr >= vm->module->dataCount) {
|
|
runtimeError(vm, 4, "Out of DATA");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
if (!push(vm, basValCopy(vm->module->dataPool[vm->dataPtr++]))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_RESTORE:
|
|
vm->dataPtr = 0;
|
|
break;
|
|
|
|
// ============================================================
|
|
// FORMAT$
|
|
// ============================================================
|
|
|
|
case OP_FORMAT: {
|
|
// Pop format string, then value
|
|
BasValueT fmtVal;
|
|
BasValueT val;
|
|
|
|
if (!pop(vm, &fmtVal) || !pop(vm, &val)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT fmtStr = basValToString(fmtVal);
|
|
basValRelease(&fmtVal);
|
|
|
|
const char *fmt = fmtStr.strVal->data;
|
|
int32_t fmtLen = fmtStr.strVal->len;
|
|
double n = basValToNumber(val);
|
|
basValRelease(&val);
|
|
|
|
char buf[256];
|
|
buf[0] = '\0';
|
|
|
|
// Check for "percent" format
|
|
bool isPercent = false;
|
|
if (fmtLen == 7) {
|
|
isPercent = true;
|
|
const char *pct = "PERCENT";
|
|
for (int32_t i = 0; i < 7; i++) {
|
|
if (toupper((unsigned char)fmt[i]) != pct[i]) {
|
|
isPercent = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (isPercent) {
|
|
snprintf(buf, sizeof(buf), "%.0f%%", n * 100.0);
|
|
} else {
|
|
// Count format characters
|
|
int32_t hashBefore = 0;
|
|
int32_t zeroBefore = 0;
|
|
int32_t hashAfter = 0;
|
|
int32_t zeroAfter = 0;
|
|
bool hasDecimal = false;
|
|
bool hasComma = false;
|
|
bool plusStart = false;
|
|
bool plusEnd = false;
|
|
bool minusEnd = false;
|
|
|
|
for (int32_t i = 0; i < fmtLen; i++) {
|
|
if (fmt[i] == '+' && i == 0) {
|
|
plusStart = true;
|
|
} else if (fmt[i] == '+' && i == fmtLen - 1) {
|
|
plusEnd = true;
|
|
} else if (fmt[i] == '-' && i == fmtLen - 1) {
|
|
minusEnd = true;
|
|
} else if (fmt[i] == '.') {
|
|
hasDecimal = true;
|
|
} else if (fmt[i] == ',') {
|
|
hasComma = true;
|
|
} else if (fmt[i] == '#') {
|
|
if (hasDecimal) {
|
|
hashAfter++;
|
|
} else {
|
|
hashBefore++;
|
|
}
|
|
} else if (fmt[i] == '0') {
|
|
if (hasDecimal) {
|
|
zeroAfter++;
|
|
} else {
|
|
zeroBefore++;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32_t decimals = hashAfter + zeroAfter;
|
|
bool isNeg = (n < 0);
|
|
double absN = isNeg ? -n : n;
|
|
|
|
// Format the number
|
|
char numBuf[128];
|
|
|
|
if (hasDecimal) {
|
|
snprintf(numBuf, sizeof(numBuf), "%.*f", (int)decimals, absN);
|
|
} else {
|
|
snprintf(numBuf, sizeof(numBuf), "%.0f", absN);
|
|
}
|
|
|
|
// Split into integer and decimal parts
|
|
char intPart[128];
|
|
char decPart[128];
|
|
intPart[0] = '\0';
|
|
decPart[0] = '\0';
|
|
|
|
char *dot = strchr(numBuf, '.');
|
|
|
|
if (dot) {
|
|
int32_t intLen = (int32_t)(dot - numBuf);
|
|
memcpy(intPart, numBuf, intLen);
|
|
intPart[intLen] = '\0';
|
|
strncpy(decPart, dot + 1, sizeof(decPart) - 1);
|
|
decPart[sizeof(decPart) - 1] = '\0';
|
|
} else {
|
|
strncpy(intPart, numBuf, sizeof(intPart) - 1);
|
|
intPart[sizeof(intPart) - 1] = '\0';
|
|
}
|
|
|
|
// Apply thousands separator
|
|
char fmtIntPart[128];
|
|
|
|
if (hasComma) {
|
|
int32_t srcLen = (int32_t)strlen(intPart);
|
|
int32_t dstIdx = 0;
|
|
|
|
for (int32_t i = 0; i < srcLen; i++) {
|
|
if (i > 0 && (srcLen - i) % 3 == 0) {
|
|
fmtIntPart[dstIdx++] = ',';
|
|
}
|
|
fmtIntPart[dstIdx++] = intPart[i];
|
|
}
|
|
|
|
fmtIntPart[dstIdx] = '\0';
|
|
} else {
|
|
strncpy(fmtIntPart, intPart, sizeof(fmtIntPart) - 1);
|
|
fmtIntPart[sizeof(fmtIntPart) - 1] = '\0';
|
|
}
|
|
|
|
// Pad integer part with leading zeros if format has 0's
|
|
int32_t totalIntDigits = hashBefore + zeroBefore;
|
|
int32_t curIntLen = (int32_t)strlen(fmtIntPart);
|
|
|
|
// Build result
|
|
int32_t idx = 0;
|
|
|
|
// Sign prefix
|
|
if (plusStart || plusEnd) {
|
|
if (isNeg) {
|
|
buf[idx++] = '-';
|
|
} else if (plusStart) {
|
|
buf[idx++] = '+';
|
|
}
|
|
} else if (isNeg) {
|
|
buf[idx++] = '-';
|
|
}
|
|
|
|
// Pad with leading spaces or zeros
|
|
int32_t padNeeded = totalIntDigits - curIntLen;
|
|
|
|
for (int32_t i = 0; i < padNeeded; i++) {
|
|
if (i < padNeeded - (int32_t)strlen(intPart)) {
|
|
// Positions before the number
|
|
if (zeroBefore > 0) {
|
|
buf[idx++] = '0';
|
|
} else {
|
|
buf[idx++] = ' ';
|
|
}
|
|
} else {
|
|
buf[idx++] = '0';
|
|
}
|
|
}
|
|
|
|
// Integer part
|
|
for (int32_t i = 0; fmtIntPart[i]; i++) {
|
|
buf[idx++] = fmtIntPart[i];
|
|
}
|
|
|
|
// Decimal part
|
|
if (hasDecimal) {
|
|
buf[idx++] = '.';
|
|
for (int32_t i = 0; decPart[i] && i < decimals; i++) {
|
|
buf[idx++] = decPart[i];
|
|
}
|
|
}
|
|
|
|
// Trailing sign
|
|
if (plusEnd && !isNeg) {
|
|
buf[idx++] = '+';
|
|
} else if (minusEnd && isNeg) {
|
|
buf[idx++] = '-';
|
|
} else if (minusEnd && !isNeg) {
|
|
buf[idx++] = ' ';
|
|
}
|
|
|
|
buf[idx] = '\0';
|
|
}
|
|
|
|
basValRelease(&fmtStr);
|
|
|
|
if (!push(vm, basValStringFromC(buf))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// SHELL
|
|
// ============================================================
|
|
|
|
case OP_SHELL: {
|
|
BasValueT cmdVal;
|
|
|
|
if (!pop(vm, &cmdVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT cmdStr = basValToString(cmdVal);
|
|
basValRelease(&cmdVal);
|
|
|
|
int32_t result = 0;
|
|
|
|
if (cmdStr.strVal && cmdStr.strVal->len > 0) {
|
|
result = system(cmdStr.strVal->data);
|
|
}
|
|
|
|
basValRelease(&cmdStr);
|
|
|
|
if (!push(vm, basValInteger((int16_t)result))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// COMPARE_MODE
|
|
// ============================================================
|
|
|
|
case OP_COMPARE_MODE: {
|
|
uint8_t mode = readUint8(vm);
|
|
vm->compareTextMode = (mode != 0);
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// Halt
|
|
// ============================================================
|
|
|
|
case OP_END:
|
|
vm->running = false;
|
|
vm->ended = true;
|
|
return BAS_VM_HALTED;
|
|
|
|
case OP_HALT:
|
|
vm->running = false;
|
|
return BAS_VM_HALTED;
|
|
|
|
// ============================================================
|
|
// UI / Event opcodes (name-based resolution)
|
|
// ============================================================
|
|
|
|
case OP_LOAD_PROP: {
|
|
BasValueT propNameVal;
|
|
BasValueT ctrlRefVal;
|
|
|
|
if (!pop(vm, &propNameVal) || !pop(vm, &ctrlRefVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (vm->ui.getProp && ctrlRefVal.type == BAS_TYPE_OBJECT) {
|
|
BasValueT sv = basValToString(propNameVal);
|
|
BasValueT result = vm->ui.getProp(vm->ui.ctx, ctrlRefVal.objVal, sv.strVal->data);
|
|
basValRelease(&sv);
|
|
push(vm, result);
|
|
} else {
|
|
push(vm, basValInteger(0));
|
|
}
|
|
|
|
basValRelease(&propNameVal);
|
|
basValRelease(&ctrlRefVal);
|
|
break;
|
|
}
|
|
|
|
case OP_STORE_PROP: {
|
|
BasValueT value;
|
|
BasValueT propNameVal;
|
|
BasValueT ctrlRefVal;
|
|
|
|
if (!pop(vm, &value) || !pop(vm, &propNameVal) || !pop(vm, &ctrlRefVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (vm->ui.setProp && ctrlRefVal.type == BAS_TYPE_OBJECT) {
|
|
BasValueT sv = basValToString(propNameVal);
|
|
vm->ui.setProp(vm->ui.ctx, ctrlRefVal.objVal, sv.strVal->data, value);
|
|
basValRelease(&sv);
|
|
}
|
|
|
|
basValRelease(&value);
|
|
basValRelease(&propNameVal);
|
|
basValRelease(&ctrlRefVal);
|
|
break;
|
|
}
|
|
|
|
case OP_CALL_METHOD: {
|
|
uint8_t argc = readUint8(vm);
|
|
BasValueT methodNameVal;
|
|
BasValueT ctrlRefVal;
|
|
|
|
// Pop args in reverse
|
|
BasValueT args[16];
|
|
int32_t argCount = argc < 16 ? argc : 16;
|
|
|
|
for (int32_t i = argCount - 1; i >= 0; i--) {
|
|
if (!pop(vm, &args[i])) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
}
|
|
|
|
if (!pop(vm, &methodNameVal) || !pop(vm, &ctrlRefVal)) {
|
|
for (int32_t i = 0; i < argCount; i++) {
|
|
basValRelease(&args[i]);
|
|
}
|
|
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (vm->ui.callMethod && ctrlRefVal.type == BAS_TYPE_OBJECT) {
|
|
BasValueT sv = basValToString(methodNameVal);
|
|
BasValueT result = vm->ui.callMethod(vm->ui.ctx, ctrlRefVal.objVal, sv.strVal->data, args, argCount);
|
|
basValRelease(&sv);
|
|
push(vm, result);
|
|
} else {
|
|
push(vm, basValInteger(0));
|
|
}
|
|
|
|
for (int32_t i = 0; i < argCount; i++) {
|
|
basValRelease(&args[i]);
|
|
}
|
|
|
|
basValRelease(&methodNameVal);
|
|
basValRelease(&ctrlRefVal);
|
|
break;
|
|
}
|
|
|
|
case OP_LOAD_FORM: {
|
|
BasValueT nameVal;
|
|
|
|
if (!pop(vm, &nameVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
void *formRef = NULL;
|
|
|
|
if (vm->ui.loadForm) {
|
|
BasValueT sv = basValToString(nameVal);
|
|
formRef = vm->ui.loadForm(vm->ui.ctx, sv.strVal->data);
|
|
basValRelease(&sv);
|
|
}
|
|
|
|
// Set as current form for subsequent FIND_CTRL calls
|
|
if (formRef) {
|
|
vm->currentForm = formRef;
|
|
}
|
|
|
|
basValRelease(&nameVal);
|
|
push(vm, basValObject(formRef));
|
|
break;
|
|
}
|
|
|
|
case OP_UNLOAD_FORM: {
|
|
BasValueT formVal;
|
|
|
|
if (!pop(vm, &formVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (vm->ui.unloadForm && formVal.type == BAS_TYPE_OBJECT) {
|
|
vm->ui.unloadForm(vm->ui.ctx, formVal.objVal);
|
|
}
|
|
|
|
basValRelease(&formVal);
|
|
break;
|
|
}
|
|
|
|
case OP_SHOW_FORM: {
|
|
uint8_t modal = readUint8(vm);
|
|
BasValueT formVal;
|
|
|
|
if (!pop(vm, &formVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (vm->ui.showForm && formVal.type == BAS_TYPE_OBJECT) {
|
|
vm->ui.showForm(vm->ui.ctx, formVal.objVal, modal != 0);
|
|
}
|
|
|
|
basValRelease(&formVal);
|
|
break;
|
|
}
|
|
|
|
case OP_HIDE_FORM: {
|
|
BasValueT formVal;
|
|
|
|
if (!pop(vm, &formVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (vm->ui.hideForm && formVal.type == BAS_TYPE_OBJECT) {
|
|
vm->ui.hideForm(vm->ui.ctx, formVal.objVal);
|
|
}
|
|
|
|
basValRelease(&formVal);
|
|
break;
|
|
}
|
|
|
|
case OP_MSGBOX: {
|
|
// Stack: [message, flags] — flags on top
|
|
BasValueT flagsVal;
|
|
BasValueT msgVal;
|
|
|
|
if (!pop(vm, &flagsVal) || !pop(vm, &msgVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t flags = (int32_t)basValToNumber(flagsVal);
|
|
int32_t result = 1; // default OK
|
|
|
|
if (vm->ui.msgBox) {
|
|
BasValueT sv = basValToString(msgVal);
|
|
result = vm->ui.msgBox(vm->ui.ctx, sv.strVal->data, flags);
|
|
basValRelease(&sv);
|
|
}
|
|
|
|
basValRelease(&flagsVal);
|
|
basValRelease(&msgVal);
|
|
push(vm, basValInteger((int16_t)result));
|
|
break;
|
|
}
|
|
|
|
case OP_INPUTBOX: {
|
|
BasValueT defaultVal;
|
|
BasValueT titleVal;
|
|
BasValueT promptVal;
|
|
|
|
if (!pop(vm, &defaultVal) || !pop(vm, &titleVal) || !pop(vm, &promptVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasStringT *result = NULL;
|
|
|
|
if (vm->ui.inputBox) {
|
|
BasValueT sp = basValToString(promptVal);
|
|
BasValueT st = basValToString(titleVal);
|
|
BasValueT sd = basValToString(defaultVal);
|
|
result = vm->ui.inputBox(vm->ui.ctx, sp.strVal->data, st.strVal->data, sd.strVal->data);
|
|
basValRelease(&sp);
|
|
basValRelease(&st);
|
|
basValRelease(&sd);
|
|
}
|
|
|
|
basValRelease(&defaultVal);
|
|
basValRelease(&titleVal);
|
|
basValRelease(&promptVal);
|
|
|
|
if (result) {
|
|
BasValueT rv;
|
|
rv.type = BAS_TYPE_STRING;
|
|
rv.strVal = result;
|
|
push(vm, rv);
|
|
} else {
|
|
push(vm, basValStringFromC(""));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_ME_REF:
|
|
push(vm, basValObject(vm->currentForm));
|
|
break;
|
|
|
|
case OP_CREATE_CTRL: {
|
|
BasValueT nameVal;
|
|
BasValueT typeVal;
|
|
BasValueT formVal;
|
|
|
|
if (!pop(vm, &nameVal) || !pop(vm, &typeVal) || !pop(vm, &formVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
void *ctrlRef = NULL;
|
|
|
|
if (vm->ui.createCtrl && formVal.type == BAS_TYPE_OBJECT) {
|
|
BasValueT sv1 = basValToString(typeVal);
|
|
BasValueT sv2 = basValToString(nameVal);
|
|
ctrlRef = vm->ui.createCtrl(vm->ui.ctx, formVal.objVal, sv1.strVal->data, sv2.strVal->data);
|
|
basValRelease(&sv1);
|
|
basValRelease(&sv2);
|
|
}
|
|
|
|
basValRelease(&nameVal);
|
|
basValRelease(&typeVal);
|
|
basValRelease(&formVal);
|
|
push(vm, basValObject(ctrlRef));
|
|
break;
|
|
}
|
|
|
|
case OP_FIND_CTRL: {
|
|
BasValueT nameVal;
|
|
BasValueT formVal;
|
|
|
|
if (!pop(vm, &nameVal) || !pop(vm, &formVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
void *ctrlRef = NULL;
|
|
|
|
if (vm->ui.findCtrl) {
|
|
// Use current form if formRef is NULL or not an object
|
|
void *formRef = (formVal.type == BAS_TYPE_OBJECT) ? formVal.objVal : vm->currentForm;
|
|
BasValueT sv = basValToString(nameVal);
|
|
ctrlRef = vm->ui.findCtrl(vm->ui.ctx, formRef, sv.strVal->data);
|
|
basValRelease(&sv);
|
|
}
|
|
|
|
basValRelease(&nameVal);
|
|
basValRelease(&formVal);
|
|
push(vm, basValObject(ctrlRef));
|
|
break;
|
|
}
|
|
|
|
case OP_CTRL_REF: {
|
|
uint16_t nameIdx = readUint16(vm);
|
|
const char *ctrlName = "";
|
|
|
|
if (nameIdx < (uint16_t)vm->module->constCount) {
|
|
ctrlName = vm->module->constants[nameIdx]->data;
|
|
}
|
|
|
|
void *ctrlRef = NULL;
|
|
|
|
if (vm->ui.findCtrl && vm->currentForm) {
|
|
ctrlRef = vm->ui.findCtrl(vm->ui.ctx, vm->currentForm, ctrlName);
|
|
}
|
|
|
|
push(vm, basValObject(ctrlRef));
|
|
break;
|
|
}
|
|
|
|
case OP_FIND_CTRL_IDX: {
|
|
// Stack: [formRef, ctrlName, index] -- index on top
|
|
BasValueT idxVal;
|
|
BasValueT nameVal;
|
|
BasValueT formVal;
|
|
|
|
if (!pop(vm, &idxVal) || !pop(vm, &nameVal) || !pop(vm, &formVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
void *ctrlRef = NULL;
|
|
|
|
if (vm->ui.findCtrlIdx) {
|
|
void *formRef = (formVal.type == BAS_TYPE_OBJECT) ? formVal.objVal : vm->currentForm;
|
|
int32_t index = (int32_t)basValToNumber(idxVal);
|
|
BasValueT sv = basValToString(nameVal);
|
|
ctrlRef = vm->ui.findCtrlIdx(vm->ui.ctx, formRef, sv.strVal->data, index);
|
|
basValRelease(&sv);
|
|
}
|
|
|
|
basValRelease(&idxVal);
|
|
basValRelease(&nameVal);
|
|
basValRelease(&formVal);
|
|
push(vm, basValObject(ctrlRef));
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// Form-level variables
|
|
// ============================================================
|
|
|
|
case OP_LOAD_FORM_VAR: {
|
|
uint16_t idx = readUint16(vm);
|
|
|
|
if (!vm->currentFormVars || idx >= (uint16_t)vm->currentFormVarCount) {
|
|
runtimeError(vm, 9, "Form variable access outside form context");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
push(vm, basValCopy(vm->currentFormVars[idx]));
|
|
break;
|
|
}
|
|
|
|
case OP_STORE_FORM_VAR: {
|
|
uint16_t idx = readUint16(vm);
|
|
BasValueT val;
|
|
|
|
if (!pop(vm, &val)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
if (!vm->currentFormVars || idx >= (uint16_t)vm->currentFormVarCount) {
|
|
basValRelease(&val);
|
|
runtimeError(vm, 9, "Form variable access outside form context");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
basValRelease(&vm->currentFormVars[idx]);
|
|
vm->currentFormVars[idx] = val;
|
|
break;
|
|
}
|
|
|
|
case OP_PUSH_FORM_ADDR: {
|
|
uint16_t idx = readUint16(vm);
|
|
|
|
if (!vm->currentFormVars || idx >= (uint16_t)vm->currentFormVarCount) {
|
|
runtimeError(vm, 9, "Form variable address outside form context");
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
BasValueT ref;
|
|
ref.type = BAS_TYPE_REF;
|
|
ref.refVal = &vm->currentFormVars[idx];
|
|
push(vm, ref);
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// External library calls
|
|
// ============================================================
|
|
|
|
case OP_CALL_EXTERN: {
|
|
uint16_t libNameIdx = readUint16(vm);
|
|
uint16_t funcNameIdx = readUint16(vm);
|
|
uint8_t argc = readUint8(vm);
|
|
uint8_t retType = readUint8(vm);
|
|
|
|
// Look up in cache
|
|
void *funcPtr = NULL;
|
|
int32_t cacheIdx = -1;
|
|
|
|
for (int32_t i = 0; i < vm->externCacheCount; i++) {
|
|
if (vm->externCache[i].libNameIdx == libNameIdx &&
|
|
vm->externCache[i].funcNameIdx == funcNameIdx) {
|
|
funcPtr = vm->externCache[i].funcPtr;
|
|
cacheIdx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Resolve on first call
|
|
if (cacheIdx < 0) {
|
|
const char *libName = "";
|
|
const char *funcName = "";
|
|
|
|
if (libNameIdx < (uint16_t)vm->module->constCount) {
|
|
libName = vm->module->constants[libNameIdx]->data;
|
|
}
|
|
|
|
if (funcNameIdx < (uint16_t)vm->module->constCount) {
|
|
funcName = vm->module->constants[funcNameIdx]->data;
|
|
}
|
|
|
|
if (vm->ext.resolveExtern) {
|
|
funcPtr = vm->ext.resolveExtern(vm->ext.ctx, libName, funcName);
|
|
}
|
|
|
|
if (!funcPtr) {
|
|
// Pop args to keep stack balanced
|
|
for (int32_t i = 0; i < argc; i++) {
|
|
BasValueT tmp;
|
|
pop(vm, &tmp);
|
|
basValRelease(&tmp);
|
|
}
|
|
|
|
char msg[256];
|
|
const char *fn = funcNameIdx < (uint16_t)vm->module->constCount
|
|
? vm->module->constants[funcNameIdx]->data : "?";
|
|
snprintf(msg, sizeof(msg), "External function not found: %s", fn);
|
|
runtimeError(vm, 453, msg);
|
|
return BAS_VM_ERROR;
|
|
}
|
|
|
|
// Cache it
|
|
if (vm->externCacheCount < BAS_EXTERN_CACHE_SIZE) {
|
|
vm->externCache[vm->externCacheCount].libNameIdx = libNameIdx;
|
|
vm->externCache[vm->externCacheCount].funcNameIdx = funcNameIdx;
|
|
vm->externCache[vm->externCacheCount].funcPtr = funcPtr;
|
|
vm->externCacheCount++;
|
|
}
|
|
}
|
|
|
|
// Pop arguments (reverse order)
|
|
BasValueT args[16];
|
|
int32_t argCount = argc < 16 ? argc : 16;
|
|
|
|
for (int32_t i = argCount - 1; i >= 0; i--) {
|
|
if (!pop(vm, &args[i])) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
}
|
|
|
|
// Call through host callback
|
|
BasValueT result = basValInteger(0);
|
|
|
|
if (vm->ext.callExtern) {
|
|
const char *libName = libNameIdx < (uint16_t)vm->module->constCount
|
|
? vm->module->constants[libNameIdx]->data : "";
|
|
const char *funcName = funcNameIdx < (uint16_t)vm->module->constCount
|
|
? vm->module->constants[funcNameIdx]->data : "";
|
|
result = vm->ext.callExtern(vm->ext.ctx, funcPtr, libName, funcName, args, argCount, retType);
|
|
}
|
|
|
|
for (int32_t i = 0; i < argCount; i++) {
|
|
basValRelease(&args[i]);
|
|
}
|
|
|
|
// Push return value (void functions still push a dummy 0)
|
|
push(vm, result);
|
|
break;
|
|
}
|
|
|
|
// ============================================================
|
|
// SQL database operations
|
|
// ============================================================
|
|
|
|
case OP_SQL_OPEN: {
|
|
BasValueT pathVal;
|
|
if (!pop(vm, &pathVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
int32_t handle = 0;
|
|
if (vm->sql.sqlOpen) {
|
|
BasValueT sv = basValToString(pathVal);
|
|
handle = vm->sql.sqlOpen(sv.strVal->data);
|
|
basValRelease(&sv);
|
|
}
|
|
basValRelease(&pathVal);
|
|
push(vm, basValLong(handle));
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_CLOSE: {
|
|
BasValueT dbVal;
|
|
if (!pop(vm, &dbVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
if (vm->sql.sqlClose) {
|
|
vm->sql.sqlClose((int32_t)basValToNumber(dbVal));
|
|
}
|
|
basValRelease(&dbVal);
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_EXEC: {
|
|
BasValueT sqlVal;
|
|
BasValueT dbVal;
|
|
if (!pop(vm, &sqlVal) || !pop(vm, &dbVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
bool ok = false;
|
|
if (vm->sql.sqlExec) {
|
|
BasValueT sv = basValToString(sqlVal);
|
|
ok = vm->sql.sqlExec((int32_t)basValToNumber(dbVal), sv.strVal->data);
|
|
basValRelease(&sv);
|
|
}
|
|
basValRelease(&sqlVal);
|
|
basValRelease(&dbVal);
|
|
push(vm, ok ? basValInteger(-1) : basValInteger(0));
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_ERROR: {
|
|
BasValueT dbVal;
|
|
if (!pop(vm, &dbVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
const char *err = "";
|
|
if (vm->sql.sqlError) {
|
|
err = vm->sql.sqlError((int32_t)basValToNumber(dbVal));
|
|
}
|
|
basValRelease(&dbVal);
|
|
push(vm, basValStringFromC(err ? err : ""));
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_QUERY: {
|
|
BasValueT sqlVal;
|
|
BasValueT dbVal;
|
|
if (!pop(vm, &sqlVal) || !pop(vm, &dbVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
int32_t handle = 0;
|
|
if (vm->sql.sqlQuery) {
|
|
BasValueT sv = basValToString(sqlVal);
|
|
handle = vm->sql.sqlQuery((int32_t)basValToNumber(dbVal), sv.strVal->data);
|
|
basValRelease(&sv);
|
|
}
|
|
basValRelease(&sqlVal);
|
|
basValRelease(&dbVal);
|
|
push(vm, basValLong(handle));
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_NEXT: {
|
|
BasValueT rsVal;
|
|
if (!pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
bool ok = false;
|
|
if (vm->sql.sqlNext) {
|
|
ok = vm->sql.sqlNext((int32_t)basValToNumber(rsVal));
|
|
}
|
|
basValRelease(&rsVal);
|
|
push(vm, ok ? basValInteger(-1) : basValInteger(0));
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_EOF: {
|
|
BasValueT rsVal;
|
|
if (!pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
bool eof = true;
|
|
if (vm->sql.sqlEof) {
|
|
eof = vm->sql.sqlEof((int32_t)basValToNumber(rsVal));
|
|
}
|
|
basValRelease(&rsVal);
|
|
push(vm, eof ? basValInteger(-1) : basValInteger(0));
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_FIELD_COUNT: {
|
|
BasValueT rsVal;
|
|
if (!pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
int32_t count = 0;
|
|
if (vm->sql.sqlFieldCount) {
|
|
count = vm->sql.sqlFieldCount((int32_t)basValToNumber(rsVal));
|
|
}
|
|
basValRelease(&rsVal);
|
|
push(vm, basValLong(count));
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_FIELD_NAME: {
|
|
BasValueT colVal;
|
|
BasValueT rsVal;
|
|
if (!pop(vm, &colVal) || !pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
const char *name = "";
|
|
if (vm->sql.sqlFieldName) {
|
|
name = vm->sql.sqlFieldName((int32_t)basValToNumber(rsVal), (int32_t)basValToNumber(colVal));
|
|
}
|
|
basValRelease(&colVal);
|
|
basValRelease(&rsVal);
|
|
push(vm, basValStringFromC(name ? name : ""));
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_FIELD_TEXT: {
|
|
BasValueT colVal;
|
|
BasValueT rsVal;
|
|
if (!pop(vm, &colVal) || !pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
const char *text = "";
|
|
if (vm->sql.sqlFieldText) {
|
|
text = vm->sql.sqlFieldText((int32_t)basValToNumber(rsVal), (int32_t)basValToNumber(colVal));
|
|
}
|
|
basValRelease(&colVal);
|
|
basValRelease(&rsVal);
|
|
push(vm, basValStringFromC(text ? text : ""));
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_FIELD_BYNAME: {
|
|
BasValueT nameVal;
|
|
BasValueT rsVal;
|
|
if (!pop(vm, &nameVal) || !pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
const char *text = "";
|
|
if (vm->sql.sqlFieldByName) {
|
|
BasValueT sv = basValToString(nameVal);
|
|
text = vm->sql.sqlFieldByName((int32_t)basValToNumber(rsVal), sv.strVal->data);
|
|
basValRelease(&sv);
|
|
}
|
|
basValRelease(&nameVal);
|
|
basValRelease(&rsVal);
|
|
push(vm, basValStringFromC(text ? text : ""));
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_FIELD_INT: {
|
|
BasValueT colVal;
|
|
BasValueT rsVal;
|
|
if (!pop(vm, &colVal) || !pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
int32_t val = 0;
|
|
if (vm->sql.sqlFieldInt) {
|
|
val = vm->sql.sqlFieldInt((int32_t)basValToNumber(rsVal), (int32_t)basValToNumber(colVal));
|
|
}
|
|
basValRelease(&colVal);
|
|
basValRelease(&rsVal);
|
|
push(vm, basValLong(val));
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_FIELD_DBL: {
|
|
BasValueT colVal;
|
|
BasValueT rsVal;
|
|
if (!pop(vm, &colVal) || !pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
double val = 0.0;
|
|
if (vm->sql.sqlFieldDbl) {
|
|
val = vm->sql.sqlFieldDbl((int32_t)basValToNumber(rsVal), (int32_t)basValToNumber(colVal));
|
|
}
|
|
basValRelease(&colVal);
|
|
basValRelease(&rsVal);
|
|
push(vm, basValDouble(val));
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_FREE_RESULT: {
|
|
BasValueT rsVal;
|
|
if (!pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
if (vm->sql.sqlFreeResult) {
|
|
vm->sql.sqlFreeResult((int32_t)basValToNumber(rsVal));
|
|
}
|
|
basValRelease(&rsVal);
|
|
break;
|
|
}
|
|
|
|
case OP_SQL_AFFECTED: {
|
|
BasValueT dbVal;
|
|
if (!pop(vm, &dbVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
|
int32_t rows = 0;
|
|
if (vm->sql.sqlAffectedRows) {
|
|
rows = vm->sql.sqlAffectedRows((int32_t)basValToNumber(dbVal));
|
|
}
|
|
basValRelease(&dbVal);
|
|
push(vm, basValLong(rows));
|
|
break;
|
|
}
|
|
|
|
case OP_LINE: {
|
|
uint16_t lineNum = readUint16(vm);
|
|
vm->currentLine = lineNum;
|
|
|
|
// Step into: break at any OP_LINE
|
|
if (vm->debugBreak) {
|
|
vm->debugBreak = false;
|
|
return BAS_VM_BREAKPOINT;
|
|
}
|
|
|
|
// Step over: break when call depth returns to target level
|
|
if (vm->stepOverDepth >= 0 && vm->callDepth <= vm->stepOverDepth) {
|
|
vm->stepOverDepth = -1;
|
|
return BAS_VM_BREAKPOINT;
|
|
}
|
|
|
|
// Step out: break when call depth drops below target
|
|
if (vm->stepOutDepth >= 0 && vm->callDepth < vm->stepOutDepth) {
|
|
vm->stepOutDepth = -1;
|
|
return BAS_VM_BREAKPOINT;
|
|
}
|
|
|
|
// Run to cursor
|
|
if (vm->runToCursorLine >= 0 && (int32_t)lineNum == vm->runToCursorLine) {
|
|
vm->runToCursorLine = -1;
|
|
return BAS_VM_BREAKPOINT;
|
|
}
|
|
|
|
// Breakpoint check (linear scan, typically < 20 entries)
|
|
for (int32_t i = 0; i < vm->breakpointCount; i++) {
|
|
if (vm->breakpoints[i] == (int32_t)lineNum) {
|
|
return BAS_VM_BREAKPOINT;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_APP_PATH: {
|
|
push(vm, basValStringFromC(vm->appPath));
|
|
break;
|
|
}
|
|
|
|
case OP_APP_CONFIG: {
|
|
push(vm, basValStringFromC(vm->appConfig));
|
|
break;
|
|
}
|
|
|
|
case OP_APP_DATA: {
|
|
push(vm, basValStringFromC(vm->appData));
|
|
break;
|
|
}
|
|
|
|
case OP_INI_READ: {
|
|
// Stack: file, section, key, default -> result string
|
|
BasValueT defVal, keyVal, secVal, fileVal;
|
|
if (!pop(vm, &defVal) || !pop(vm, &keyVal) || !pop(vm, &secVal) || !pop(vm, &fileVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
BasStringT *fileStr = basValFormatString(fileVal);
|
|
BasStringT *secStr = basValFormatString(secVal);
|
|
BasStringT *keyStr = basValFormatString(keyVal);
|
|
BasStringT *defStr = basValFormatString(defVal);
|
|
const char *result = defStr->data;
|
|
|
|
FILE *fp = fopen(fileStr->data, "r");
|
|
if (fp) {
|
|
char line[512];
|
|
bool inSection = false;
|
|
while (fgets(line, sizeof(line), fp)) {
|
|
// Strip trailing whitespace
|
|
char *end = line + strlen(line) - 1;
|
|
while (end >= line && (*end == '\r' || *end == '\n' || *end == ' ')) {
|
|
*end-- = '\0';
|
|
}
|
|
char *p = line;
|
|
while (*p == ' ' || *p == '\t') { p++; }
|
|
if (*p == '\0' || *p == ';' || *p == '#') { continue; }
|
|
if (*p == '[') {
|
|
char *rb = strchr(p, ']');
|
|
if (rb) {
|
|
*rb = '\0';
|
|
inSection = (strcasecmp(p + 1, secStr->data) == 0);
|
|
}
|
|
continue;
|
|
}
|
|
if (inSection) {
|
|
char *eq = strchr(p, '=');
|
|
if (eq) {
|
|
*eq = '\0';
|
|
char *k = p;
|
|
char *ke = eq - 1;
|
|
while (ke >= k && (*ke == ' ' || *ke == '\t')) { *ke-- = '\0'; }
|
|
if (strcasecmp(k, keyStr->data) == 0) {
|
|
char *v = eq + 1;
|
|
while (*v == ' ' || *v == '\t') { v++; }
|
|
result = v;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
push(vm, basValStringFromC(result));
|
|
basStringUnref(fileStr);
|
|
basStringUnref(secStr);
|
|
basStringUnref(keyStr);
|
|
basStringUnref(defStr);
|
|
basValRelease(&fileVal);
|
|
basValRelease(&secVal);
|
|
basValRelease(&keyVal);
|
|
basValRelease(&defVal);
|
|
break;
|
|
}
|
|
|
|
case OP_INI_WRITE: {
|
|
// Stack: file, section, key, value
|
|
BasValueT valVal, keyVal, secVal, fileVal;
|
|
if (!pop(vm, &valVal) || !pop(vm, &keyVal) || !pop(vm, &secVal) || !pop(vm, &fileVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
BasStringT *fileStr = basValFormatString(fileVal);
|
|
BasStringT *secStr = basValFormatString(secVal);
|
|
BasStringT *keyStr = basValFormatString(keyVal);
|
|
BasStringT *valStr = basValFormatString(valVal);
|
|
|
|
// Read existing file into memory
|
|
char *content = NULL;
|
|
int32_t contentLen = 0;
|
|
FILE *fp = fopen(fileStr->data, "r");
|
|
if (fp) {
|
|
fseek(fp, 0, SEEK_END);
|
|
contentLen = (int32_t)ftell(fp);
|
|
fseek(fp, 0, SEEK_SET);
|
|
content = (char *)malloc(contentLen + 1);
|
|
if (content) {
|
|
contentLen = (int32_t)fread(content, 1, contentLen, fp);
|
|
content[contentLen] = '\0';
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
// Write updated file
|
|
fp = fopen(fileStr->data, "w");
|
|
if (fp) {
|
|
bool keyWritten = false;
|
|
bool inSection = false;
|
|
bool sectionFound = false;
|
|
|
|
if (content) {
|
|
char *line = content;
|
|
while (line && *line) {
|
|
char *eol = strchr(line, '\n');
|
|
char lineBuf[512];
|
|
int32_t ll;
|
|
if (eol) {
|
|
ll = (int32_t)(eol - line);
|
|
if (ll > 0 && line[ll - 1] == '\r') { ll--; }
|
|
if (ll >= (int32_t)sizeof(lineBuf)) { ll = (int32_t)sizeof(lineBuf) - 1; }
|
|
memcpy(lineBuf, line, ll);
|
|
lineBuf[ll] = '\0';
|
|
line = eol + 1;
|
|
} else {
|
|
snprintf(lineBuf, sizeof(lineBuf), "%s", line);
|
|
ll = (int32_t)strlen(lineBuf);
|
|
line = NULL;
|
|
}
|
|
|
|
char *p = lineBuf;
|
|
while (*p == ' ' || *p == '\t') { p++; }
|
|
|
|
if (*p == '[') {
|
|
// Write pending key before leaving section
|
|
if (inSection && !keyWritten) {
|
|
fprintf(fp, "%s = %s\n", keyStr->data, valStr->data);
|
|
keyWritten = true;
|
|
}
|
|
char *rb = strchr(p, ']');
|
|
if (rb) {
|
|
char secName[256];
|
|
int32_t sn = (int32_t)(rb - p - 1);
|
|
if (sn >= (int32_t)sizeof(secName)) { sn = (int32_t)sizeof(secName) - 1; }
|
|
memcpy(secName, p + 1, sn);
|
|
secName[sn] = '\0';
|
|
inSection = (strcasecmp(secName, secStr->data) == 0);
|
|
if (inSection) { sectionFound = true; }
|
|
}
|
|
fprintf(fp, "%s\n", lineBuf);
|
|
continue;
|
|
}
|
|
|
|
if (inSection && !keyWritten) {
|
|
char *eq = strchr(p, '=');
|
|
if (eq) {
|
|
char k[256];
|
|
int32_t kl = (int32_t)(eq - p);
|
|
while (kl > 0 && (p[kl - 1] == ' ' || p[kl - 1] == '\t')) { kl--; }
|
|
if (kl >= (int32_t)sizeof(k)) { kl = (int32_t)sizeof(k) - 1; }
|
|
memcpy(k, p, kl);
|
|
k[kl] = '\0';
|
|
if (strcasecmp(k, keyStr->data) == 0) {
|
|
fprintf(fp, "%s = %s\n", keyStr->data, valStr->data);
|
|
keyWritten = true;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
fprintf(fp, "%s\n", lineBuf);
|
|
}
|
|
}
|
|
|
|
// Key not found in existing section — append
|
|
if (!keyWritten) {
|
|
if (!sectionFound) {
|
|
fprintf(fp, "[%s]\n", secStr->data);
|
|
} else if (inSection) {
|
|
// Still in the right section at EOF — just append
|
|
}
|
|
fprintf(fp, "%s = %s\n", keyStr->data, valStr->data);
|
|
}
|
|
|
|
fclose(fp);
|
|
}
|
|
|
|
free(content);
|
|
basStringUnref(fileStr);
|
|
basStringUnref(secStr);
|
|
basStringUnref(keyStr);
|
|
basStringUnref(valStr);
|
|
basValRelease(&fileVal);
|
|
basValRelease(&secVal);
|
|
basValRelease(&keyVal);
|
|
basValRelease(&valVal);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
runtimeError(vm, 51, "Bad opcode");
|
|
return BAS_VM_BAD_OPCODE;
|
|
}
|
|
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// currentFrame
|
|
// ============================================================
|
|
|
|
static BasCallFrameT *currentFrame(BasVmT *vm) {
|
|
if (vm->callDepth <= 0) {
|
|
// Module-level: use callStack[0] as implicit main frame
|
|
return &vm->callStack[0];
|
|
}
|
|
|
|
return &vm->callStack[vm->callDepth - 1];
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// defaultPrint
|
|
// ============================================================
|
|
|
|
static void defaultPrint(void *ctx, const char *text, bool newline) {
|
|
(void)ctx;
|
|
fputs(text, stdout);
|
|
|
|
if (newline) {
|
|
fputc('\n', stdout);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// execArith
|
|
// ============================================================
|
|
|
|
static BasVmResultE execArith(BasVmT *vm, uint8_t op) {
|
|
BasValueT b;
|
|
BasValueT a;
|
|
|
|
if (!pop(vm, &b) || !pop(vm, &a)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
// VB behavior: + on two strings concatenates
|
|
if ((op == OP_ADD_INT || op == OP_ADD_FLT) && a.type == BAS_TYPE_STRING && b.type == BAS_TYPE_STRING) {
|
|
BasStringT *sa = a.strVal ? a.strVal : basStringNew("", 0);
|
|
BasStringT *sb = b.strVal ? b.strVal : basStringNew("", 0);
|
|
int32_t newLen = sa->len + sb->len;
|
|
BasStringT *cat;
|
|
|
|
if (newLen > 0) {
|
|
char *buf = (char *)malloc(newLen + 1);
|
|
memcpy(buf, sa->data, sa->len);
|
|
memcpy(buf + sa->len, sb->data, sb->len);
|
|
buf[newLen] = '\0';
|
|
cat = basStringNew(buf, newLen);
|
|
free(buf);
|
|
} else {
|
|
cat = basStringNew("", 0);
|
|
}
|
|
|
|
basValRelease(&a);
|
|
basValRelease(&b);
|
|
|
|
BasValueT result;
|
|
result.type = BAS_TYPE_STRING;
|
|
result.strVal = cat;
|
|
push(vm, result);
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
double na = basValToNumber(a);
|
|
double nb = basValToNumber(b);
|
|
basValRelease(&a);
|
|
basValRelease(&b);
|
|
|
|
double result;
|
|
|
|
switch (op) {
|
|
case OP_ADD_INT:
|
|
case OP_ADD_FLT:
|
|
result = na + nb;
|
|
break;
|
|
|
|
case OP_SUB_INT:
|
|
case OP_SUB_FLT:
|
|
result = na - nb;
|
|
break;
|
|
|
|
case OP_MUL_INT:
|
|
case OP_MUL_FLT:
|
|
result = na * nb;
|
|
break;
|
|
|
|
case OP_IDIV_INT:
|
|
if ((int32_t)nb == 0) {
|
|
runtimeError(vm, 11, "Division by zero");
|
|
return BAS_VM_DIV_BY_ZERO;
|
|
}
|
|
|
|
result = (double)((int32_t)na / (int32_t)nb);
|
|
break;
|
|
|
|
case OP_DIV_FLT:
|
|
if (nb == 0.0) {
|
|
runtimeError(vm, 11, "Division by zero");
|
|
return BAS_VM_DIV_BY_ZERO;
|
|
}
|
|
|
|
result = na / nb;
|
|
break;
|
|
|
|
case OP_MOD_INT:
|
|
if ((int32_t)nb == 0) {
|
|
runtimeError(vm, 11, "Division by zero");
|
|
return BAS_VM_DIV_BY_ZERO;
|
|
}
|
|
|
|
result = (double)((int32_t)na % (int32_t)nb);
|
|
break;
|
|
|
|
case OP_POW:
|
|
result = pow(na, nb);
|
|
break;
|
|
|
|
default:
|
|
result = 0.0;
|
|
break;
|
|
}
|
|
|
|
// Return appropriate type
|
|
if (op == OP_ADD_INT || op == OP_SUB_INT || op == OP_MUL_INT || op == OP_IDIV_INT || op == OP_MOD_INT) {
|
|
if (result >= -32768.0 && result <= 32767.0) {
|
|
push(vm, basValInteger((int16_t)result));
|
|
} else if (result >= -2147483648.0 && result <= 2147483647.0) {
|
|
push(vm, basValLong((int32_t)result));
|
|
} else {
|
|
push(vm, basValDouble(result));
|
|
}
|
|
} else {
|
|
push(vm, basValDouble(result));
|
|
}
|
|
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// execCompare
|
|
// ============================================================
|
|
|
|
static BasVmResultE execCompare(BasVmT *vm, uint8_t op) {
|
|
BasValueT b;
|
|
BasValueT a;
|
|
|
|
if (!pop(vm, &b) || !pop(vm, &a)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t cmp = vm->compareTextMode ? basValCompareCI(a, b) : basValCompare(a, b);
|
|
basValRelease(&a);
|
|
basValRelease(&b);
|
|
|
|
bool result;
|
|
|
|
switch (op) {
|
|
case OP_CMP_EQ: result = (cmp == 0); break;
|
|
case OP_CMP_NE: result = (cmp != 0); break;
|
|
case OP_CMP_LT: result = (cmp < 0); break;
|
|
case OP_CMP_GT: result = (cmp > 0); break;
|
|
case OP_CMP_LE: result = (cmp <= 0); break;
|
|
case OP_CMP_GE: result = (cmp >= 0); break;
|
|
default: result = false; break;
|
|
}
|
|
|
|
push(vm, basValBool(result));
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// execFileOp
|
|
// ============================================================
|
|
|
|
// File mode constants (matches compiler/parser.c emission)
|
|
#define FILE_MODE_INPUT 1
|
|
#define FILE_MODE_OUTPUT 2
|
|
#define FILE_MODE_APPEND 3
|
|
#define FILE_MODE_RANDOM 4
|
|
#define FILE_MODE_BINARY 5
|
|
|
|
static BasVmResultE execFileOp(BasVmT *vm, uint8_t op) {
|
|
switch (op) {
|
|
case OP_FILE_OPEN: {
|
|
uint8_t mode = readUint8(vm);
|
|
BasValueT channelVal;
|
|
BasValueT filenameVal;
|
|
|
|
if (!pop(vm, &channelVal) || !pop(vm, &filenameVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
basValRelease(&channelVal);
|
|
|
|
BasValueT fnStr = basValToString(filenameVal);
|
|
basValRelease(&filenameVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES) {
|
|
basValRelease(&fnStr);
|
|
runtimeError(vm, 52, "Bad file channel number");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
// Close existing file on this channel
|
|
if (vm->files[channel].handle) {
|
|
fclose((FILE *)vm->files[channel].handle);
|
|
vm->files[channel].handle = NULL;
|
|
vm->files[channel].mode = 0;
|
|
}
|
|
|
|
const char *modeStr;
|
|
|
|
switch (mode) {
|
|
case FILE_MODE_INPUT:
|
|
modeStr = "r";
|
|
break;
|
|
case FILE_MODE_OUTPUT:
|
|
modeStr = "w";
|
|
break;
|
|
case FILE_MODE_APPEND:
|
|
modeStr = "a";
|
|
break;
|
|
case FILE_MODE_RANDOM:
|
|
case FILE_MODE_BINARY:
|
|
modeStr = "r+b";
|
|
break;
|
|
default:
|
|
basValRelease(&fnStr);
|
|
runtimeError(vm, 54, "Bad file mode");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
// For RANDOM/BINARY: create file if it doesn't exist, then reopen r+b
|
|
if (mode == FILE_MODE_RANDOM || mode == FILE_MODE_BINARY) {
|
|
FILE *test = fopen(fnStr.strVal->data, "r");
|
|
if (!test) {
|
|
// Create the file
|
|
test = fopen(fnStr.strVal->data, "w+b");
|
|
if (test) {
|
|
fclose(test);
|
|
}
|
|
} else {
|
|
fclose(test);
|
|
}
|
|
}
|
|
|
|
FILE *fp = fopen(fnStr.strVal->data, modeStr);
|
|
basValRelease(&fnStr);
|
|
|
|
if (!fp) {
|
|
runtimeError(vm, 53, "File not found or cannot open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
vm->files[channel].handle = fp;
|
|
vm->files[channel].mode = mode;
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_CLOSE: {
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
basValRelease(&channelVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES) {
|
|
runtimeError(vm, 52, "Bad file channel number");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
if (vm->files[channel].handle) {
|
|
fclose((FILE *)vm->files[channel].handle);
|
|
vm->files[channel].handle = NULL;
|
|
vm->files[channel].mode = 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_PRINT: {
|
|
BasValueT val;
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &val) || !pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
basValRelease(&channelVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
basValRelease(&val);
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
BasStringT *s = basValFormatString(val);
|
|
basValRelease(&val);
|
|
|
|
if (s) {
|
|
fputs(s->data, (FILE *)vm->files[channel].handle);
|
|
fputc('\n', (FILE *)vm->files[channel].handle);
|
|
basStringUnref(s);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_INPUT: {
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
basValRelease(&channelVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
char buf[1024];
|
|
buf[0] = '\0';
|
|
|
|
if (fgets(buf, sizeof(buf), (FILE *)vm->files[channel].handle)) {
|
|
// Strip trailing newline
|
|
int32_t len = (int32_t)strlen(buf);
|
|
|
|
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) {
|
|
buf[--len] = '\0';
|
|
}
|
|
}
|
|
|
|
if (!push(vm, basValStringFromC(buf))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_EOF: {
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
basValRelease(&channelVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
// Peek ahead to detect EOF before the next read
|
|
FILE *fp = (FILE *)vm->files[channel].handle;
|
|
int ch = fgetc(fp);
|
|
bool isEof;
|
|
|
|
if (ch == EOF) {
|
|
isEof = true;
|
|
} else {
|
|
ungetc(ch, fp);
|
|
isEof = false;
|
|
}
|
|
|
|
if (!push(vm, basValBool(isEof))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_LINE_INPUT: {
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
basValRelease(&channelVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
char buf[1024];
|
|
buf[0] = '\0';
|
|
|
|
if (fgets(buf, sizeof(buf), (FILE *)vm->files[channel].handle)) {
|
|
int32_t len = (int32_t)strlen(buf);
|
|
|
|
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) {
|
|
buf[--len] = '\0';
|
|
}
|
|
}
|
|
|
|
if (!push(vm, basValStringFromC(buf))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_WRITE: {
|
|
// Pop value and channel, write value in WRITE format
|
|
BasValueT val;
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &val) || !pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
basValRelease(&channelVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
basValRelease(&val);
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
FILE *fp = (FILE *)vm->files[channel].handle;
|
|
|
|
if (val.type == BAS_TYPE_STRING) {
|
|
// Strings: enclosed in quotes
|
|
fputc('"', fp);
|
|
if (val.strVal) {
|
|
fputs(val.strVal->data, fp);
|
|
}
|
|
fputc('"', fp);
|
|
} else {
|
|
// Numbers: no leading space (unlike PRINT)
|
|
BasStringT *s = basValFormatString(val);
|
|
if (s) {
|
|
// Skip leading space that basValFormatString adds for positive numbers
|
|
const char *text = s->data;
|
|
if (*text == ' ') {
|
|
text++;
|
|
}
|
|
fputs(text, fp);
|
|
basStringUnref(s);
|
|
}
|
|
}
|
|
|
|
basValRelease(&val);
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_WRITE_SEP: {
|
|
// Pop channel, write comma separator
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
basValRelease(&channelVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
fputc(',', (FILE *)vm->files[channel].handle);
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_WRITE_NL: {
|
|
// Pop channel, write newline
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
basValRelease(&channelVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
fputc('\n', (FILE *)vm->files[channel].handle);
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_GET: {
|
|
// Pop type, recno, channel; read data; push value
|
|
BasValueT typeVal;
|
|
BasValueT recnoVal;
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &typeVal) || !pop(vm, &recnoVal) || !pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
int32_t recno = (int32_t)basValToNumber(recnoVal);
|
|
int32_t dataType = (int32_t)basValToNumber(typeVal);
|
|
basValRelease(&channelVal);
|
|
basValRelease(&recnoVal);
|
|
basValRelease(&typeVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
FILE *fp = (FILE *)vm->files[channel].handle;
|
|
|
|
// Seek to record position if recno > 0
|
|
// (recno is 1-based in QB; 0 means current position)
|
|
if (recno > 0) {
|
|
// For simplicity, use fixed 128-byte records for RANDOM
|
|
fseek(fp, (long)(recno - 1) * 128, SEEK_SET);
|
|
}
|
|
|
|
BasValueT result;
|
|
memset(&result, 0, sizeof(result));
|
|
|
|
switch (dataType) {
|
|
case BAS_TYPE_INTEGER: {
|
|
int16_t val = 0;
|
|
if (fread(&val, sizeof(val), 1, fp) < 1) { /* EOF ok */ }
|
|
result = basValInteger(val);
|
|
break;
|
|
}
|
|
case BAS_TYPE_LONG: {
|
|
int32_t val = 0;
|
|
if (fread(&val, sizeof(val), 1, fp) < 1) { /* EOF ok */ }
|
|
result = basValLong(val);
|
|
break;
|
|
}
|
|
case BAS_TYPE_SINGLE: {
|
|
float val = 0.0f;
|
|
if (fread(&val, sizeof(val), 1, fp) < 1) { /* EOF ok */ }
|
|
result = basValSingle(val);
|
|
break;
|
|
}
|
|
case BAS_TYPE_DOUBLE: {
|
|
double val = 0.0;
|
|
if (fread(&val, sizeof(val), 1, fp) < 1) { /* EOF ok */ }
|
|
result = basValDouble(val);
|
|
break;
|
|
}
|
|
case BAS_TYPE_STRING: {
|
|
// Read a length-prefixed string (int16 len + data)
|
|
int16_t len = 0;
|
|
if (fread(&len, sizeof(len), 1, fp) < 1) { /* EOF ok */ }
|
|
if (len < 0) {
|
|
len = 0;
|
|
}
|
|
char *buf = (char *)malloc(len + 1);
|
|
if (buf) {
|
|
if (fread(buf, 1, len, fp) < 1) { /* EOF ok */ }
|
|
buf[len] = '\0';
|
|
result = basValStringFromC(buf);
|
|
free(buf);
|
|
} else {
|
|
result = basValStringFromC("");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
result = basValInteger(0);
|
|
break;
|
|
}
|
|
|
|
if (!push(vm, result)) {
|
|
basValRelease(&result);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_PUT: {
|
|
// Pop value, recno, channel; write data
|
|
BasValueT val;
|
|
BasValueT recnoVal;
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &val) || !pop(vm, &recnoVal) || !pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
int32_t recno = (int32_t)basValToNumber(recnoVal);
|
|
basValRelease(&channelVal);
|
|
basValRelease(&recnoVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
basValRelease(&val);
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
FILE *fp = (FILE *)vm->files[channel].handle;
|
|
|
|
if (recno > 0) {
|
|
fseek(fp, (long)(recno - 1) * 128, SEEK_SET);
|
|
}
|
|
|
|
switch (val.type) {
|
|
case BAS_TYPE_INTEGER: {
|
|
int16_t v = val.intVal;
|
|
fwrite(&v, sizeof(v), 1, fp);
|
|
break;
|
|
}
|
|
case BAS_TYPE_LONG: {
|
|
int32_t v = val.longVal;
|
|
fwrite(&v, sizeof(v), 1, fp);
|
|
break;
|
|
}
|
|
case BAS_TYPE_SINGLE: {
|
|
float v = val.sngVal;
|
|
fwrite(&v, sizeof(v), 1, fp);
|
|
break;
|
|
}
|
|
case BAS_TYPE_DOUBLE: {
|
|
double v = val.dblVal;
|
|
fwrite(&v, sizeof(v), 1, fp);
|
|
break;
|
|
}
|
|
case BAS_TYPE_STRING: {
|
|
// Write length-prefixed string
|
|
int16_t len = val.strVal ? (int16_t)val.strVal->len : 0;
|
|
fwrite(&len, sizeof(len), 1, fp);
|
|
if (len > 0 && val.strVal) {
|
|
fwrite(val.strVal->data, 1, len, fp);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
int16_t zero = 0;
|
|
fwrite(&zero, sizeof(zero), 1, fp);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fflush(fp);
|
|
basValRelease(&val);
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_SEEK: {
|
|
// Pop position and channel, seek
|
|
BasValueT posVal;
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &posVal) || !pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
int32_t pos = (int32_t)basValToNumber(posVal);
|
|
basValRelease(&channelVal);
|
|
basValRelease(&posVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
// QB SEEK is 1-based
|
|
fseek((FILE *)vm->files[channel].handle, (long)(pos - 1), SEEK_SET);
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_LOF: {
|
|
// Pop channel, push file length
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
basValRelease(&channelVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
FILE *fp = (FILE *)vm->files[channel].handle;
|
|
long savedPos = ftell(fp);
|
|
fseek(fp, 0, SEEK_END);
|
|
long length = ftell(fp);
|
|
fseek(fp, savedPos, SEEK_SET);
|
|
|
|
if (!push(vm, basValLong((int32_t)length))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_LOC: {
|
|
// Pop channel, push current position
|
|
BasValueT channelVal;
|
|
|
|
if (!pop(vm, &channelVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
basValRelease(&channelVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
long pos = ftell((FILE *)vm->files[channel].handle);
|
|
|
|
// QB returns 1-based position
|
|
if (!push(vm, basValLong((int32_t)(pos + 1)))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_FREEFILE: {
|
|
// Push the next available file channel number
|
|
int32_t freeNum = 0;
|
|
|
|
for (int32_t i = 1; i < BAS_VM_MAX_FILES; i++) {
|
|
if (!vm->files[i].handle) {
|
|
freeNum = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (freeNum == 0) {
|
|
runtimeError(vm, 67, "Too many files open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
if (!push(vm, basValInteger((int16_t)freeNum))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FILE_INPUT_N: {
|
|
// Pop channel and n, read n chars, push string
|
|
BasValueT channelVal;
|
|
BasValueT nVal;
|
|
|
|
if (!pop(vm, &channelVal) || !pop(vm, &nVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t channel = (int32_t)basValToNumber(channelVal);
|
|
int32_t n = (int32_t)basValToNumber(nVal);
|
|
basValRelease(&channelVal);
|
|
basValRelease(&nVal);
|
|
|
|
if (channel < 1 || channel >= BAS_VM_MAX_FILES || !vm->files[channel].handle) {
|
|
runtimeError(vm, 52, "Bad file number or file not open");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
if (n < 0) {
|
|
n = 0;
|
|
}
|
|
|
|
if (n > 32767) {
|
|
n = 32767;
|
|
}
|
|
|
|
char *buf = (char *)malloc(n + 1);
|
|
|
|
if (!buf) {
|
|
runtimeError(vm, 7, "Out of memory");
|
|
return BAS_VM_OUT_OF_MEMORY;
|
|
}
|
|
|
|
int32_t bytesRead = (int32_t)fread(buf, 1, n, (FILE *)vm->files[channel].handle);
|
|
buf[bytesRead] = '\0';
|
|
|
|
BasStringT *s = basStringNew(buf, bytesRead);
|
|
free(buf);
|
|
|
|
BasValueT result;
|
|
result.type = BAS_TYPE_STRING;
|
|
result.strVal = s;
|
|
|
|
if (!push(vm, result)) {
|
|
basStringUnref(s);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return BAS_VM_BAD_OPCODE;
|
|
}
|
|
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// execFsOp -- filesystem operations (Kill, Name, MkDir, etc.)
|
|
// ============================================================
|
|
|
|
// Dir$ state -- persists across Dir$() calls within a VM session
|
|
static DIR *sDirHandle = NULL;
|
|
static char sDirPattern[260];
|
|
static char sDirPath[260];
|
|
|
|
static void dirClose(void) {
|
|
if (sDirHandle) {
|
|
closedir(sDirHandle);
|
|
sDirHandle = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
static const char *dirNext(void) {
|
|
if (!sDirHandle) {
|
|
return NULL;
|
|
}
|
|
|
|
struct dirent *ent;
|
|
|
|
while ((ent = readdir(sDirHandle)) != NULL) {
|
|
if (fnmatch(sDirPattern, ent->d_name, FNM_CASEFOLD) == 0) {
|
|
return ent->d_name;
|
|
}
|
|
}
|
|
|
|
dirClose();
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static BasVmResultE execFsOp(BasVmT *vm, uint8_t op) {
|
|
switch (op) {
|
|
case OP_FS_KILL: {
|
|
BasValueT fnVal;
|
|
|
|
if (!pop(vm, &fnVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT fnStr = basValToString(fnVal);
|
|
basValRelease(&fnVal);
|
|
|
|
if (remove(fnStr.strVal->data) != 0) {
|
|
basValRelease(&fnStr);
|
|
runtimeError(vm, 53, "File not found");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
basValRelease(&fnStr);
|
|
break;
|
|
}
|
|
|
|
case OP_FS_NAME: {
|
|
BasValueT newVal;
|
|
BasValueT oldVal;
|
|
|
|
if (!pop(vm, &newVal) || !pop(vm, &oldVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT newStr = basValToString(newVal);
|
|
BasValueT oldStr = basValToString(oldVal);
|
|
basValRelease(&newVal);
|
|
basValRelease(&oldVal);
|
|
|
|
if (rename(oldStr.strVal->data, newStr.strVal->data) != 0) {
|
|
basValRelease(&oldStr);
|
|
basValRelease(&newStr);
|
|
runtimeError(vm, 58, "File already exists or rename failed");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
basValRelease(&oldStr);
|
|
basValRelease(&newStr);
|
|
break;
|
|
}
|
|
|
|
case OP_FS_FILECOPY: {
|
|
BasValueT dstVal;
|
|
BasValueT srcVal;
|
|
|
|
if (!pop(vm, &dstVal) || !pop(vm, &srcVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT dstStr = basValToString(dstVal);
|
|
BasValueT srcStr = basValToString(srcVal);
|
|
basValRelease(&dstVal);
|
|
basValRelease(&srcVal);
|
|
|
|
FILE *fin = fopen(srcStr.strVal->data, "rb");
|
|
FILE *fout = NULL;
|
|
|
|
if (fin) {
|
|
fout = fopen(dstStr.strVal->data, "wb");
|
|
}
|
|
|
|
basValRelease(&srcStr);
|
|
basValRelease(&dstStr);
|
|
|
|
if (!fin || !fout) {
|
|
if (fin) {
|
|
fclose(fin);
|
|
}
|
|
|
|
if (fout) {
|
|
fclose(fout);
|
|
}
|
|
|
|
runtimeError(vm, 53, "File not found or cannot create destination");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
char buf[4096];
|
|
size_t n;
|
|
|
|
while ((n = fread(buf, 1, sizeof(buf), fin)) > 0) {
|
|
fwrite(buf, 1, n, fout);
|
|
}
|
|
|
|
fclose(fin);
|
|
fclose(fout);
|
|
break;
|
|
}
|
|
|
|
case OP_FS_MKDIR: {
|
|
BasValueT pathVal;
|
|
|
|
if (!pop(vm, &pathVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT pathStr = basValToString(pathVal);
|
|
basValRelease(&pathVal);
|
|
|
|
#ifdef _WIN32
|
|
int rc = mkdir(pathStr.strVal->data);
|
|
#else
|
|
int rc = mkdir(pathStr.strVal->data, 0755);
|
|
#endif
|
|
|
|
if (rc != 0) {
|
|
basValRelease(&pathStr);
|
|
runtimeError(vm, 75, "Path/File access error");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
basValRelease(&pathStr);
|
|
break;
|
|
}
|
|
|
|
case OP_FS_RMDIR: {
|
|
BasValueT pathVal;
|
|
|
|
if (!pop(vm, &pathVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT pathStr = basValToString(pathVal);
|
|
basValRelease(&pathVal);
|
|
|
|
if (rmdir(pathStr.strVal->data) != 0) {
|
|
basValRelease(&pathStr);
|
|
runtimeError(vm, 75, "Path/File access error");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
basValRelease(&pathStr);
|
|
break;
|
|
}
|
|
|
|
case OP_FS_CHDIR: {
|
|
BasValueT pathVal;
|
|
|
|
if (!pop(vm, &pathVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT pathStr = basValToString(pathVal);
|
|
basValRelease(&pathVal);
|
|
|
|
if (chdir(pathStr.strVal->data) != 0) {
|
|
basValRelease(&pathStr);
|
|
runtimeError(vm, 76, "Path not found");
|
|
return BAS_VM_FILE_ERROR;
|
|
}
|
|
|
|
basValRelease(&pathStr);
|
|
break;
|
|
}
|
|
|
|
case OP_FS_CHDRIVE: {
|
|
BasValueT driveVal;
|
|
|
|
if (!pop(vm, &driveVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT driveStr = basValToString(driveVal);
|
|
basValRelease(&driveVal);
|
|
|
|
// On DOS, change to the drive's root directory
|
|
if (driveStr.strVal->data[0]) {
|
|
char drivePath[4];
|
|
drivePath[0] = driveStr.strVal->data[0];
|
|
drivePath[1] = ':';
|
|
drivePath[2] = '\0';
|
|
chdir(drivePath);
|
|
}
|
|
|
|
basValRelease(&driveStr);
|
|
break;
|
|
}
|
|
|
|
case OP_FS_CURDIR: {
|
|
char cwd[260];
|
|
|
|
if (!getcwd(cwd, sizeof(cwd))) {
|
|
cwd[0] = '\0';
|
|
}
|
|
|
|
BasStringT *s = basStringNew(cwd, (int32_t)strlen(cwd));
|
|
BasValueT result;
|
|
result.type = BAS_TYPE_STRING;
|
|
result.strVal = s;
|
|
|
|
if (!push(vm, result)) {
|
|
basStringUnref(s);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FS_DIR: {
|
|
BasValueT patVal;
|
|
|
|
if (!pop(vm, &patVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT patStr = basValToString(patVal);
|
|
basValRelease(&patVal);
|
|
|
|
dirClose();
|
|
|
|
// Split pattern into directory + filename pattern
|
|
const char *pat = patStr.strVal->data;
|
|
const char *lastSep = NULL;
|
|
|
|
for (const char *c = pat; *c; c++) {
|
|
if (*c == '/' || *c == '\\') {
|
|
lastSep = c;
|
|
}
|
|
}
|
|
|
|
if (lastSep) {
|
|
int32_t dirLen = (int32_t)(lastSep - pat);
|
|
|
|
if (dirLen >= (int32_t)sizeof(sDirPath)) {
|
|
dirLen = (int32_t)sizeof(sDirPath) - 1;
|
|
}
|
|
|
|
memcpy(sDirPath, pat, dirLen);
|
|
sDirPath[dirLen] = '\0';
|
|
snprintf(sDirPattern, sizeof(sDirPattern), "%s", lastSep + 1);
|
|
} else {
|
|
snprintf(sDirPath, sizeof(sDirPath), ".");
|
|
snprintf(sDirPattern, sizeof(sDirPattern), "%s", pat);
|
|
}
|
|
|
|
basValRelease(&patStr);
|
|
|
|
if (sDirPattern[0] == '\0') {
|
|
snprintf(sDirPattern, sizeof(sDirPattern), "*");
|
|
}
|
|
|
|
sDirHandle = opendir(sDirPath);
|
|
|
|
const char *match = dirNext();
|
|
const char *text = match ? match : "";
|
|
BasStringT *s = basStringNew(text, (int32_t)strlen(text));
|
|
BasValueT result;
|
|
result.type = BAS_TYPE_STRING;
|
|
result.strVal = s;
|
|
|
|
if (!push(vm, result)) {
|
|
basStringUnref(s);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FS_DIR_NEXT: {
|
|
const char *match = dirNext();
|
|
const char *text = match ? match : "";
|
|
BasStringT *s = basStringNew(text, (int32_t)strlen(text));
|
|
BasValueT result;
|
|
result.type = BAS_TYPE_STRING;
|
|
result.strVal = s;
|
|
|
|
if (!push(vm, result)) {
|
|
basStringUnref(s);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FS_FILELEN: {
|
|
BasValueT fnVal;
|
|
|
|
if (!pop(vm, &fnVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT fnStr = basValToString(fnVal);
|
|
basValRelease(&fnVal);
|
|
|
|
struct stat st;
|
|
int32_t size = 0;
|
|
|
|
if (stat(fnStr.strVal->data, &st) == 0) {
|
|
size = (int32_t)st.st_size;
|
|
}
|
|
|
|
basValRelease(&fnStr);
|
|
|
|
BasValueT result;
|
|
result.type = BAS_TYPE_LONG;
|
|
result.longVal = size;
|
|
|
|
if (!push(vm, result)) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FS_GETATTR: {
|
|
BasValueT fnVal;
|
|
|
|
if (!pop(vm, &fnVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT fnStr = basValToString(fnVal);
|
|
basValRelease(&fnVal);
|
|
|
|
struct stat st;
|
|
int32_t attrs = 0;
|
|
|
|
if (stat(fnStr.strVal->data, &st) == 0) {
|
|
if (S_ISDIR(st.st_mode)) {
|
|
attrs |= 16; // vbDirectory
|
|
}
|
|
|
|
if (!(st.st_mode & S_IWUSR)) {
|
|
attrs |= 1; // vbReadOnly
|
|
}
|
|
}
|
|
|
|
basValRelease(&fnStr);
|
|
|
|
BasValueT result;
|
|
result.type = BAS_TYPE_INTEGER;
|
|
result.intVal = (int16_t)attrs;
|
|
|
|
if (!push(vm, result)) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case OP_FS_SETATTR: {
|
|
BasValueT attrVal;
|
|
BasValueT fnVal;
|
|
|
|
if (!pop(vm, &attrVal) || !pop(vm, &fnVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT fnStr = basValToString(fnVal);
|
|
int32_t attrs = (int32_t)basValToNumber(attrVal);
|
|
basValRelease(&fnVal);
|
|
basValRelease(&attrVal);
|
|
|
|
struct stat st;
|
|
|
|
if (stat(fnStr.strVal->data, &st) == 0) {
|
|
mode_t mode = st.st_mode;
|
|
|
|
if (attrs & 1) {
|
|
mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
|
|
} else {
|
|
mode |= S_IWUSR;
|
|
}
|
|
|
|
chmod(fnStr.strVal->data, mode);
|
|
}
|
|
|
|
basValRelease(&fnStr);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return BAS_VM_BAD_OPCODE;
|
|
}
|
|
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// execLogical
|
|
// ============================================================
|
|
|
|
static BasVmResultE execLogical(BasVmT *vm, uint8_t op) {
|
|
if (op == OP_NOT) {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
int32_t n = (int32_t)basValToNumber(*top);
|
|
basValRelease(top);
|
|
*top = basValInteger((int16_t)(~n));
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
BasValueT b;
|
|
BasValueT a;
|
|
|
|
if (!pop(vm, &b) || !pop(vm, &a)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t na = (int32_t)basValToNumber(a);
|
|
int32_t nb = (int32_t)basValToNumber(b);
|
|
basValRelease(&a);
|
|
basValRelease(&b);
|
|
|
|
int32_t result;
|
|
|
|
switch (op) {
|
|
case OP_AND: result = na & nb; break;
|
|
case OP_OR: result = na | nb; break;
|
|
case OP_XOR: result = na ^ nb; break;
|
|
case OP_EQV: result = ~(na ^ nb); break;
|
|
case OP_IMP: result = (~na) | nb; break;
|
|
default: result = 0; break;
|
|
}
|
|
|
|
push(vm, basValInteger((int16_t)result));
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// execMath
|
|
// ============================================================
|
|
|
|
static BasVmResultE execMath(BasVmT *vm, uint8_t op) {
|
|
if (op == OP_MATH_RND) {
|
|
// Pop the dummy arg (parser pushes -1 for RND())
|
|
BasValueT dummy;
|
|
|
|
if (!pop(vm, &dummy)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
basValRelease(&dummy);
|
|
|
|
double r = (double)rand() / (double)RAND_MAX;
|
|
|
|
if (!push(vm, basValSingle((float)r))) {
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
if (op == OP_MATH_RANDOMIZE) {
|
|
BasValueT val;
|
|
|
|
if (!pop(vm, &val)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
double n = basValToNumber(val);
|
|
basValRelease(&val);
|
|
|
|
if (n < 0) {
|
|
srand((unsigned int)time(NULL));
|
|
} else {
|
|
srand((unsigned int)n);
|
|
}
|
|
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
// All other math ops take one argument
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
double n = basValToNumber(*top);
|
|
double result;
|
|
|
|
switch (op) {
|
|
case OP_MATH_ABS: result = fabs(n); break;
|
|
case OP_MATH_INT: result = floor(n); break;
|
|
case OP_MATH_FIX: result = (n >= 0) ? floor(n) : ceil(n); break;
|
|
case OP_MATH_SGN: result = (n > 0) ? 1.0 : (n < 0) ? -1.0 : 0.0; break;
|
|
case OP_MATH_SQR: result = sqrt(n); break;
|
|
case OP_MATH_SIN: result = sin(n); break;
|
|
case OP_MATH_COS: result = cos(n); break;
|
|
case OP_MATH_TAN: result = tan(n); break;
|
|
case OP_MATH_ATN: result = atan(n); break;
|
|
case OP_MATH_LOG: result = log(n); break;
|
|
case OP_MATH_EXP: result = exp(n); break;
|
|
default: result = 0.0; break;
|
|
}
|
|
|
|
basValRelease(top);
|
|
*top = basValDouble(result);
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// execPrint
|
|
// ============================================================
|
|
|
|
static BasVmResultE execPrint(BasVmT *vm) {
|
|
BasValueT val;
|
|
|
|
if (!pop(vm, &val)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
// QB prints numeric values with a trailing space
|
|
bool isNumeric = (val.type != BAS_TYPE_STRING);
|
|
|
|
BasStringT *s = basValFormatString(val);
|
|
basValRelease(&val);
|
|
|
|
if (vm->printFn && s) {
|
|
vm->printFn(vm->printCtx, s->data, false);
|
|
|
|
if (isNumeric) {
|
|
vm->printFn(vm->printCtx, " ", false);
|
|
}
|
|
}
|
|
|
|
basStringUnref(s);
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// execStringOp
|
|
// ============================================================
|
|
|
|
static BasVmResultE execStringOp(BasVmT *vm, uint8_t op) {
|
|
switch (op) {
|
|
case OP_STR_CONCAT: {
|
|
BasValueT b;
|
|
BasValueT a;
|
|
|
|
if (!pop(vm, &b) || !pop(vm, &a)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT sa = basValToString(a);
|
|
BasValueT sb = basValToString(b);
|
|
basValRelease(&a);
|
|
basValRelease(&b);
|
|
|
|
BasStringT *result = basStringConcat(sa.strVal, sb.strVal);
|
|
basValRelease(&sa);
|
|
basValRelease(&sb);
|
|
|
|
BasValueT rv;
|
|
rv.type = BAS_TYPE_STRING;
|
|
rv.strVal = result;
|
|
push(vm, rv);
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_LEFT: {
|
|
BasValueT nVal;
|
|
BasValueT sVal;
|
|
|
|
if (!pop(vm, &nVal) || !pop(vm, &sVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t n = (int32_t)basValToNumber(nVal);
|
|
basValRelease(&nVal);
|
|
|
|
BasValueT sv = basValToString(sVal);
|
|
basValRelease(&sVal);
|
|
|
|
BasStringT *result = basStringSub(sv.strVal, 0, n);
|
|
basValRelease(&sv);
|
|
|
|
BasValueT rv;
|
|
rv.type = BAS_TYPE_STRING;
|
|
rv.strVal = result;
|
|
push(vm, rv);
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_RIGHT: {
|
|
BasValueT nVal;
|
|
BasValueT sVal;
|
|
|
|
if (!pop(vm, &nVal) || !pop(vm, &sVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t n = (int32_t)basValToNumber(nVal);
|
|
basValRelease(&nVal);
|
|
|
|
BasValueT sv = basValToString(sVal);
|
|
basValRelease(&sVal);
|
|
|
|
int32_t start = sv.strVal->len - n;
|
|
|
|
if (start < 0) {
|
|
start = 0;
|
|
}
|
|
|
|
BasStringT *result = basStringSub(sv.strVal, start, n);
|
|
basValRelease(&sv);
|
|
|
|
BasValueT rv;
|
|
rv.type = BAS_TYPE_STRING;
|
|
rv.strVal = result;
|
|
push(vm, rv);
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_MID: {
|
|
BasValueT lenVal;
|
|
BasValueT startVal;
|
|
BasValueT sVal;
|
|
|
|
if (!pop(vm, &lenVal) || !pop(vm, &startVal) || !pop(vm, &sVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t start = (int32_t)basValToNumber(startVal) - 1; // 1-based to 0-based
|
|
int32_t len = (int32_t)basValToNumber(lenVal);
|
|
basValRelease(&startVal);
|
|
basValRelease(&lenVal);
|
|
|
|
BasValueT sv = basValToString(sVal);
|
|
basValRelease(&sVal);
|
|
|
|
BasStringT *result = basStringSub(sv.strVal, start, len);
|
|
basValRelease(&sv);
|
|
|
|
BasValueT rv;
|
|
rv.type = BAS_TYPE_STRING;
|
|
rv.strVal = result;
|
|
push(vm, rv);
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_MID2: {
|
|
BasValueT startVal;
|
|
BasValueT sVal;
|
|
|
|
if (!pop(vm, &startVal) || !pop(vm, &sVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t start = (int32_t)basValToNumber(startVal) - 1;
|
|
basValRelease(&startVal);
|
|
|
|
BasValueT sv = basValToString(sVal);
|
|
basValRelease(&sVal);
|
|
|
|
BasStringT *result = basStringSub(sv.strVal, start, sv.strVal->len - start);
|
|
basValRelease(&sv);
|
|
|
|
BasValueT rv;
|
|
rv.type = BAS_TYPE_STRING;
|
|
rv.strVal = result;
|
|
push(vm, rv);
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_LEN: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
BasValueT sv = basValToString(*top);
|
|
int32_t len = sv.strVal ? sv.strVal->len : 0;
|
|
basValRelease(&sv);
|
|
basValRelease(top);
|
|
*top = basValInteger((int16_t)len);
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_INSTR: {
|
|
BasValueT findVal;
|
|
BasValueT sVal;
|
|
|
|
if (!pop(vm, &findVal) || !pop(vm, &sVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT sv = basValToString(sVal);
|
|
BasValueT fv = basValToString(findVal);
|
|
basValRelease(&sVal);
|
|
basValRelease(&findVal);
|
|
|
|
int32_t pos = 0;
|
|
char *found = strstr(sv.strVal->data, fv.strVal->data);
|
|
|
|
if (found) {
|
|
pos = (int32_t)(found - sv.strVal->data) + 1; // 1-based
|
|
}
|
|
|
|
basValRelease(&sv);
|
|
basValRelease(&fv);
|
|
push(vm, basValInteger((int16_t)pos));
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_INSTR3: {
|
|
// INSTR(start, string, find)
|
|
BasValueT findVal;
|
|
BasValueT sVal;
|
|
BasValueT startVal;
|
|
|
|
if (!pop(vm, &findVal) || !pop(vm, &sVal) || !pop(vm, &startVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
int32_t startPos = (int32_t)basValToNumber(startVal) - 1; // 0-based
|
|
basValRelease(&startVal);
|
|
|
|
BasValueT sv = basValToString(sVal);
|
|
BasValueT fv = basValToString(findVal);
|
|
basValRelease(&sVal);
|
|
basValRelease(&findVal);
|
|
|
|
int32_t pos = 0;
|
|
|
|
if (startPos >= 0 && startPos < sv.strVal->len) {
|
|
char *found = strstr(sv.strVal->data + startPos, fv.strVal->data);
|
|
|
|
if (found) {
|
|
pos = (int32_t)(found - sv.strVal->data) + 1; // 1-based
|
|
}
|
|
}
|
|
|
|
basValRelease(&sv);
|
|
basValRelease(&fv);
|
|
push(vm, basValInteger((int16_t)pos));
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_UCASE:
|
|
case OP_STR_LCASE:
|
|
case OP_STR_TRIM:
|
|
case OP_STR_LTRIM:
|
|
case OP_STR_RTRIM: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
BasValueT sv = basValToString(*top);
|
|
basValRelease(top);
|
|
|
|
BasStringT *src = sv.strVal;
|
|
BasStringT *result;
|
|
|
|
if (op == OP_STR_UCASE || op == OP_STR_LCASE) {
|
|
result = basStringNew(src->data, src->len);
|
|
|
|
for (int32_t i = 0; i < result->len; i++) {
|
|
if (op == OP_STR_UCASE && result->data[i] >= 'a' && result->data[i] <= 'z') {
|
|
result->data[i] -= 32;
|
|
} else if (op == OP_STR_LCASE && result->data[i] >= 'A' && result->data[i] <= 'Z') {
|
|
result->data[i] += 32;
|
|
}
|
|
}
|
|
} else {
|
|
int32_t start = 0;
|
|
int32_t end = src->len;
|
|
|
|
if (op == OP_STR_LTRIM || op == OP_STR_TRIM) {
|
|
while (start < end && src->data[start] == ' ') {
|
|
start++;
|
|
}
|
|
}
|
|
|
|
if (op == OP_STR_RTRIM || op == OP_STR_TRIM) {
|
|
while (end > start && src->data[end - 1] == ' ') {
|
|
end--;
|
|
}
|
|
}
|
|
|
|
result = basStringSub(src, start, end - start);
|
|
}
|
|
|
|
basValRelease(&sv);
|
|
top->type = BAS_TYPE_STRING;
|
|
top->strVal = result;
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_CHR: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
int32_t code = (int32_t)basValToNumber(*top);
|
|
char buf[2] = { (char)(code & 0xFF), '\0' };
|
|
basValRelease(top);
|
|
*top = basValStringFromC(buf);
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_ASC: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
BasValueT sv = basValToString(*top);
|
|
int32_t code = (sv.strVal && sv.strVal->len > 0) ? (unsigned char)sv.strVal->data[0] : 0;
|
|
basValRelease(&sv);
|
|
basValRelease(top);
|
|
*top = basValInteger((int16_t)code);
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_SPACE: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
int32_t n = (int32_t)basValToNumber(*top);
|
|
basValRelease(top);
|
|
|
|
if (n < 0) {
|
|
n = 0;
|
|
}
|
|
|
|
if (n > 32767) {
|
|
n = 32767;
|
|
}
|
|
|
|
BasStringT *s = basStringAlloc(n + 1);
|
|
memset(s->data, ' ', n);
|
|
s->data[n] = '\0';
|
|
s->len = n;
|
|
|
|
top->type = BAS_TYPE_STRING;
|
|
top->strVal = s;
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_FIXLEN: {
|
|
// [uint16 len] pop string, pad/truncate to fixed length, push result
|
|
uint16_t fixLen = readUint16(vm);
|
|
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
BasValueT sv = basValToString(*top);
|
|
basValRelease(top);
|
|
|
|
BasStringT *src = sv.strVal;
|
|
BasStringT *result = basStringAlloc(fixLen + 1);
|
|
result->len = fixLen;
|
|
|
|
int32_t srcLen = src ? src->len : 0;
|
|
int32_t copyLen = srcLen < (int32_t)fixLen ? srcLen : (int32_t)fixLen;
|
|
|
|
if (copyLen > 0 && src) {
|
|
memcpy(result->data, src->data, copyLen);
|
|
}
|
|
|
|
// Pad with spaces
|
|
for (int32_t i = copyLen; i < (int32_t)fixLen; i++) {
|
|
result->data[i] = ' ';
|
|
}
|
|
|
|
result->data[fixLen] = '\0';
|
|
basValRelease(&sv);
|
|
|
|
top->type = BAS_TYPE_STRING;
|
|
top->strVal = result;
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
case OP_STR_MID_ASGN: {
|
|
// Pop replacement, len, start, str; push modified string
|
|
BasValueT replVal;
|
|
BasValueT lenVal;
|
|
BasValueT startVal;
|
|
BasValueT strVal;
|
|
|
|
if (!pop(vm, &replVal) || !pop(vm, &lenVal) || !pop(vm, &startVal) || !pop(vm, &strVal)) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT sv = basValToString(strVal);
|
|
BasValueT rv = basValToString(replVal);
|
|
int32_t start = (int32_t)basValToNumber(startVal) - 1; // 1-based to 0-based
|
|
int32_t len = (int32_t)basValToNumber(lenVal);
|
|
basValRelease(&strVal);
|
|
basValRelease(&startVal);
|
|
basValRelease(&lenVal);
|
|
basValRelease(&replVal);
|
|
|
|
BasStringT *src = sv.strVal;
|
|
BasStringT *repl = rv.strVal;
|
|
int32_t srcLen = src ? src->len : 0;
|
|
int32_t replLen = repl ? repl->len : 0;
|
|
|
|
// If len is 0, use replacement length
|
|
if (len <= 0) {
|
|
len = replLen;
|
|
}
|
|
|
|
// Clamp to available replacement length
|
|
if (len > replLen) {
|
|
len = replLen;
|
|
}
|
|
|
|
// Create a copy of the original string
|
|
BasStringT *result = basStringNew(src ? src->data : "", srcLen);
|
|
|
|
// Replace characters
|
|
if (start >= 0 && start < srcLen && len > 0) {
|
|
int32_t maxReplace = srcLen - start;
|
|
if (len > maxReplace) {
|
|
len = maxReplace;
|
|
}
|
|
if (repl) {
|
|
memcpy(result->data + start, repl->data, len);
|
|
}
|
|
}
|
|
|
|
basValRelease(&sv);
|
|
basValRelease(&rv);
|
|
|
|
BasValueT resultVal;
|
|
resultVal.type = BAS_TYPE_STRING;
|
|
resultVal.strVal = result;
|
|
|
|
if (!push(vm, resultVal)) {
|
|
basStringUnref(result);
|
|
return BAS_VM_STACK_OVERFLOW;
|
|
}
|
|
|
|
return BAS_VM_OK;
|
|
}
|
|
|
|
default:
|
|
return BAS_VM_BAD_OPCODE;
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// pop
|
|
// ============================================================
|
|
|
|
static bool pop(BasVmT *vm, BasValueT *val) {
|
|
if (vm->sp <= 0) {
|
|
return false;
|
|
}
|
|
|
|
*val = vm->stack[--vm->sp];
|
|
return true;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// push
|
|
// ============================================================
|
|
|
|
static bool push(BasVmT *vm, BasValueT val) {
|
|
if (vm->sp >= BAS_VM_STACK_SIZE) {
|
|
return false;
|
|
}
|
|
|
|
vm->stack[vm->sp++] = val;
|
|
return true;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// readInt16
|
|
// ============================================================
|
|
|
|
static int16_t readInt16(BasVmT *vm) {
|
|
int16_t val;
|
|
memcpy(&val, &vm->module->code[vm->pc], sizeof(int16_t));
|
|
vm->pc += sizeof(int16_t);
|
|
return val;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// readUint8
|
|
// ============================================================
|
|
|
|
static uint8_t readUint8(BasVmT *vm) {
|
|
return vm->module->code[vm->pc++];
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// readUint16
|
|
// ============================================================
|
|
|
|
static uint16_t readUint16(BasVmT *vm) {
|
|
uint16_t val;
|
|
memcpy(&val, &vm->module->code[vm->pc], sizeof(uint16_t));
|
|
vm->pc += sizeof(uint16_t);
|
|
return val;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// runtimeError
|
|
// ============================================================
|
|
|
|
static void runtimeError(BasVmT *vm, int32_t errNum, const char *msg) {
|
|
vm->errorNumber = errNum;
|
|
|
|
if (vm->currentLine > 0) {
|
|
snprintf(vm->errorMsg, sizeof(vm->errorMsg), "Runtime error %d on line %d: %s", (int)errNum, (int)vm->currentLine, msg);
|
|
} else {
|
|
snprintf(vm->errorMsg, sizeof(vm->errorMsg), "Runtime error %d at PC %d: %s", (int)errNum, (int)vm->pc, msg);
|
|
}
|
|
}
|