DVX_GUI/apps/dvxbasic/runtime/vm.c

4287 lines
126 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 <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
// ============================================================
// Prototypes
// ============================================================
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 BasVmResultE execFileOp(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);
// ============================================================
// basVmCallSub
// ============================================================
bool basVmCallSub(BasVmT *vm, int32_t codeAddr) {
if (!vm || !vm->module) {
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;
// Step until the SUB returns (callDepth drops back)
int32_t steps = 0;
while (vm->running && vm->callDepth > savedCallDepth) {
if (vm->stepLimit > 0 && steps >= vm->stepLimit) {
// Unwind the call and restore state
vm->callDepth = savedCallDepth;
vm->pc = savedPc;
vm->running = savedRunning;
return false;
}
BasVmResultE result = basVmStep(vm);
steps++;
if (result == BAS_VM_HALTED) {
break;
}
if (result != BAS_VM_OK) {
// Runtime error in the event handler
vm->pc = savedPc;
vm->callDepth = savedCallDepth;
vm->running = savedRunning;
return false;
}
}
// Restore VM state
vm->pc = savedPc;
vm->running = savedRunning;
return true;
}
// ============================================================
// basVmCallSubWithArgs
// ============================================================
bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount) {
if (!vm || !vm->module) {
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;
int32_t steps = 0;
while (vm->running && vm->callDepth > savedCallDepth) {
if (vm->stepLimit > 0 && steps >= vm->stepLimit) {
vm->callDepth = savedCallDepth;
vm->pc = savedPc;
vm->running = savedRunning;
return false;
}
BasVmResultE result = basVmStep(vm);
steps++;
if (result == BAS_VM_HALTED) {
break;
}
if (result != BAS_VM_OK) {
vm->pc = savedPc;
vm->callDepth = savedCallDepth;
vm->running = savedRunning;
return false;
}
}
vm->pc = savedPc;
vm->running = savedRunning;
return true;
}
// ============================================================
// basVmCreate
// ============================================================
BasVmT *basVmCreate(void) {
BasVmT *vm = (BasVmT *)calloc(1, sizeof(BasVmT));
if (!vm) {
return NULL;
}
vm->printFn = defaultPrint;
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);
}
}
basStringSystemShutdown();
free(vm);
}
// ============================================================
// basVmGetError
// ============================================================
const char *basVmGetError(const BasVmT *vm) {
return vm->errorMsg;
}
// ============================================================
// 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;
}
// ============================================================
// 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;
}
// ============================================================
// 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 isLocal = 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->isLocal = (isLocal != 0);
fs->limit = limitVal;
fs->step = stepVal;
fs->loopTop = vm->pc;
break;
}
case OP_FOR_NEXT: {
uint16_t varIdx = readUint16(vm);
uint8_t isLocal = 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 (global or local)
BasValueT *varSlot;
if (isLocal) {
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 (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);
// ============================================================
// 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;
}
// 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;
}
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_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_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: {
uint8_t flags = readUint8(vm);
BasValueT msgVal;
if (!pop(vm, &msgVal)) {
return BAS_VM_STACK_UNDERFLOW;
}
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(&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;
}
// ============================================================
// 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 *funcName = funcNameIdx < (uint16_t)vm->module->constCount
? vm->module->constants[funcNameIdx]->data : "";
result = vm->ext.callExtern(vm->ext.ctx, funcPtr, 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;
}
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 = basStringNew("", 0);
if (newLen > 0) {
basStringUnref(cat);
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);
}
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;
}
// ============================================================
// 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;
snprintf(vm->errorMsg, sizeof(vm->errorMsg), "Runtime error %d at PC %d: %s", (int)errNum, (int)vm->pc, msg);
}