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