Immediate window wired to running VM.

This commit is contained in:
Scott Duensing 2026-04-06 20:31:07 -05:00
parent af0ad3091f
commit 1923886d42

View file

@ -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) {