diff --git a/apps/dvxbasic/ide/ideMain.c b/apps/dvxbasic/ide/ideMain.c index b6fba7a..a5e0595 100644 --- a/apps/dvxbasic/ide/ideMain.c +++ b/apps/dvxbasic/ide/ideMain.c @@ -199,6 +199,11 @@ static void updateCallStackWindow(void); static void updateLocalsWindow(void); static void updateWatchWindow(void); static void evaluateImmediate(const char *expr); +static const BasDebugVarT *findDebugVar(const char *name); +static void formatValue(const BasValueT *v, char *buf, int32_t bufSize); +static bool readDebugVar(const BasDebugVarT *dv, BasValueT *outVal); +static BasValueT *getDebugVarSlot(const BasDebugVarT *dv); +static bool writeDebugVar(const BasDebugVarT *dv, BasValueT newVal); static void loadFrmFiles(BasFormRtT *rt); static void onEvtDropdownChange(WidgetT *w); static void onImmediateChange(WidgetT *w); @@ -2147,11 +2152,304 @@ static void immPrintCallback(void *ctx, const char *text, bool newline) { } +// immTryAssign -- handle "varName = expr" when paused at a breakpoint. +// Evaluates the RHS, looks up the variable, and writes the value back +// to the running VM. Returns true if handled as an assignment. + +// immParseScalarFromStr -- parse a string into a typed BasValueT based on the +// target slot's current type. Returns true on success. + +static bool immParseScalarFromStr(const char *rhs, const BasValueT *target, BasValueT *outVal) { + memset(outVal, 0, sizeof(*outVal)); + + switch (target->type) { + case BAS_TYPE_INTEGER: + outVal->type = BAS_TYPE_INTEGER; + outVal->intVal = (int16_t)atoi(rhs); + return true; + + case BAS_TYPE_LONG: + outVal->type = BAS_TYPE_LONG; + outVal->longVal = (int32_t)atol(rhs); + return true; + + case BAS_TYPE_SINGLE: + outVal->type = BAS_TYPE_SINGLE; + outVal->sngVal = (float)atof(rhs); + return true; + + case BAS_TYPE_DOUBLE: + outVal->type = BAS_TYPE_DOUBLE; + outVal->dblVal = atof(rhs); + return true; + + case BAS_TYPE_BOOLEAN: + outVal->type = BAS_TYPE_BOOLEAN; + + if (strcasecmp(rhs, "TRUE") == 0 || strcasecmp(rhs, "-1") == 0) { + outVal->intVal = -1; + } else { + outVal->intVal = (atoi(rhs) != 0) ? -1 : 0; + } + + return true; + + case BAS_TYPE_STRING: { + const char *s = rhs; + int32_t sLen = (int32_t)strlen(s); + + if (sLen >= 2 && s[0] == '"' && s[sLen - 1] == '"') { + s++; + sLen -= 2; + } + + outVal->type = BAS_TYPE_STRING; + outVal->strVal = basStringNew(s, sLen); + return true; + } + + default: + return false; + } +} + + +// immResolveLhsSlot -- parse the LHS of an assignment and resolve it to a +// pointer into the running VM's live data. Handles: +// varName -- scalar variable +// varName(i) -- array element +// varName.field -- UDT field +// varName(i).field -- array element UDT field +// Returns NULL if the LHS can't be resolved. *endPtr is set past the LHS. + +static BasValueT *immResolveLhsSlot(const char *lhs, const char **endPtr) { + const char *p = lhs; + + // Extract variable name + const char *nameStart = p; + + while ((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') || + (*p >= '0' && *p <= '9') || *p == '_') { + p++; + } + + if (p == nameStart) { + return NULL; + } + + char varName[128]; + int32_t nameLen = (int32_t)(p - nameStart); + + if (nameLen >= (int32_t)sizeof(varName)) { + return NULL; + } + + memcpy(varName, nameStart, nameLen); + varName[nameLen] = '\0'; + + // Look up the base variable + const BasDebugVarT *dv = findDebugVar(varName); + + if (!dv) { + return NULL; + } + + BasValueT *slot = getDebugVarSlot(dv); + + if (!slot) { + return NULL; + } + + // Parse optional array subscript: (idx1, idx2, ...) + if (*p == '(') { + p++; // skip '(' + + if (slot->type != BAS_TYPE_ARRAY || !slot->arrVal) { + return NULL; + } + + int32_t indices[BAS_ARRAY_MAX_DIMS]; + int32_t numIndices = 0; + + while (*p && *p != ')' && numIndices < BAS_ARRAY_MAX_DIMS) { + while (*p == ' ') { p++; } + indices[numIndices++] = atoi(p); + + // Skip past the number + if (*p == '-') { p++; } + + while (*p >= '0' && *p <= '9') { p++; } + + while (*p == ' ') { p++; } + + if (*p == ',') { p++; } + } + + if (*p == ')') { p++; } + + int32_t flatIdx = basArrayIndex(slot->arrVal, indices, numIndices); + + if (flatIdx < 0 || flatIdx >= slot->arrVal->totalElements) { + return NULL; + } + + slot = &slot->arrVal->elements[flatIdx]; + } + + // Parse optional UDT field: .fieldName + if (*p == '.') { + p++; // skip '.' + + if (slot->type != BAS_TYPE_UDT || !slot->udtVal) { + return NULL; + } + + const char *fieldStart = p; + + while ((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') || + (*p >= '0' && *p <= '9') || *p == '_') { + p++; + } + + char fieldName[128]; + int32_t fieldLen = (int32_t)(p - fieldStart); + + if (fieldLen <= 0 || fieldLen >= (int32_t)sizeof(fieldName)) { + return NULL; + } + + memcpy(fieldName, fieldStart, fieldLen); + fieldName[fieldLen] = '\0'; + + // Find field index from debug UDT definitions + int32_t fieldIdx = -1; + + for (int32_t t = 0; t < sDbgModule->debugUdtDefCount; t++) { + if (sDbgModule->debugUdtDefs[t].typeId == slot->udtVal->typeId) { + for (int32_t f = 0; f < sDbgModule->debugUdtDefs[t].fieldCount; f++) { + if (strcasecmp(sDbgModule->debugUdtDefs[t].fields[f].name, fieldName) == 0) { + fieldIdx = f; + break; + } + } + + break; + } + } + + if (fieldIdx < 0 || fieldIdx >= slot->udtVal->fieldCount) { + return NULL; + } + + slot = &slot->udtVal->fields[fieldIdx]; + } + + if (endPtr) { + *endPtr = p; + } + + return slot; +} + + +static bool immTryAssign(const char *expr) { + if (sDbgState != DBG_PAUSED || !sVm || !sDbgModule) { + return false; + } + + // Skip leading whitespace + const char *p = expr; + + while (*p == ' ' || *p == '\t') { + p++; + } + + // Skip optional LET keyword + if (strncasecmp(p, "LET ", 4) == 0) { + p += 4; + + while (*p == ' ' || *p == '\t') { + p++; + } + } + + // Resolve the LHS to a live slot in the VM + const char *afterLhs = NULL; + BasValueT *slot = immResolveLhsSlot(p, &afterLhs); + + if (!slot || !afterLhs) { + return false; + } + + // Build display name from LHS + char lhsName[256]; + int32_t lhsLen = (int32_t)(afterLhs - p); + + if (lhsLen >= (int32_t)sizeof(lhsName)) { + lhsLen = (int32_t)sizeof(lhsName) - 1; + } + + memcpy(lhsName, p, lhsLen); + lhsName[lhsLen] = '\0'; + + p = afterLhs; + + // Skip whitespace after LHS + while (*p == ' ' || *p == '\t') { + p++; + } + + // Must have '=' (but not '==') + if (*p != '=' || p[1] == '=') { + return false; + } + + p++; // skip '=' + + while (*p == ' ' || *p == '\t') { + p++; + } + + if (*p == '\0') { + return false; + } + + // Parse the RHS into a value matching the target slot's type + BasValueT newVal; + + if (!immParseScalarFromStr(p, slot, &newVal)) { + immPrintCallback(NULL, "Cannot assign to this variable type", true); + return true; + } + + // Write the value directly to the slot + basValRelease(slot); + *slot = newVal; // transfer ownership — don't release newVal + + // Show confirmation + char confirm[256]; + snprintf(confirm, sizeof(confirm), "%s = ", lhsName); + immPrintCallback(NULL, confirm, false); + formatValue(slot, confirm, sizeof(confirm)); + immPrintCallback(NULL, confirm, true); + + // Update debug windows to reflect the change + updateLocalsWindow(); + updateWatchWindow(); + return true; +} + + static void evaluateImmediate(const char *expr) { if (!expr || *expr == '\0') { return; } + // Try assignment first when paused + if (immTryAssign(expr)) { + return; + } + char wrapped[1024]; // If it already starts with a statement keyword, use as-is @@ -6782,6 +7080,45 @@ static bool readDebugVar(const BasDebugVarT *dv, BasValueT *outVal) { } +// getDebugVarSlot -- return a pointer to the actual BasValueT in the running VM +static BasValueT *getDebugVarSlot(const BasDebugVarT *dv) { + if (!sVm) { + return NULL; + } + + if (dv->scope == SCOPE_LOCAL && sVm->callDepth > 0) { + BasCallFrameT *frame = &sVm->callStack[sVm->callDepth - 1]; + + if (dv->index >= 0 && dv->index < BAS_VM_MAX_LOCALS) { + return &frame->locals[dv->index]; + } + } else if (dv->scope == SCOPE_GLOBAL) { + if (dv->index >= 0 && dv->index < BAS_VM_MAX_GLOBALS) { + return &sVm->globals[dv->index]; + } + } else if (dv->scope == SCOPE_FORM && sVm->currentFormVars) { + if (dv->index >= 0 && dv->index < sVm->currentFormVarCount) { + return &sVm->currentFormVars[dv->index]; + } + } + + return NULL; +} + + +static bool writeDebugVar(const BasDebugVarT *dv, BasValueT newVal) { + BasValueT *slot = getDebugVarSlot(dv); + + if (!slot) { + return false; + } + + basValRelease(slot); + *slot = basValCopy(newVal); + return true; +} + + // findDebugVar -- find a debug variable by name, respecting scope static const BasDebugVarT *findDebugVar(const char *name) { if (!sDbgModule || !sDbgModule->debugVars) {