Immediate window wired to running VM.
This commit is contained in:
parent
af0ad3091f
commit
1923886d42
1 changed files with 337 additions and 0 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue