3514 lines
103 KiB
C
3514 lines
103 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);
|
|
|
|
|
|
// ============================================================
|
|
// 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;
|
|
|
|
while (vm->running) {
|
|
// Save PC before each instruction for RESUME support
|
|
int32_t savedPc = vm->pc;
|
|
BasVmResultE result = basVmStep(vm);
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// 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;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// 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;
|
|
}
|
|
|
|
if (!push(vm, basValCopy(frame->locals[idx]))) {
|
|
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;
|
|
}
|
|
|
|
basValRelease(&frame->locals[idx]);
|
|
frame->locals[idx] = 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;
|
|
}
|
|
|
|
// ============================================================
|
|
// 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;
|
|
}
|
|
|
|
// ============================================================
|
|
// 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;
|
|
}
|
|
|
|
// ============================================================
|
|
// 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", 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", 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: {
|
|
char buf[1024];
|
|
buf[0] = '\0';
|
|
|
|
if (vm->inputFn) {
|
|
if (!vm->inputFn(vm->inputCtx, "? ", buf, sizeof(buf))) {
|
|
buf[0] = '\0';
|
|
}
|
|
}
|
|
|
|
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: {
|
|
if (vm->sp < 1) {
|
|
return BAS_VM_STACK_UNDERFLOW;
|
|
}
|
|
|
|
BasValueT *top = &vm->stack[vm->sp - 1];
|
|
BasStringT *s = basValFormatString(*top);
|
|
basValRelease(top);
|
|
top->type = BAS_TYPE_STRING;
|
|
top->strVal = s;
|
|
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[16];
|
|
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;
|
|
|
|
// ============================================================
|
|
// 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", 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;
|
|
|
|
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;
|
|
}
|
|
|
|
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_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", errNum, vm->pc, msg);
|
|
}
|