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 updateLocalsWindow(void);
|
||||||
static void updateWatchWindow(void);
|
static void updateWatchWindow(void);
|
||||||
static void evaluateImmediate(const char *expr);
|
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 loadFrmFiles(BasFormRtT *rt);
|
||||||
static void onEvtDropdownChange(WidgetT *w);
|
static void onEvtDropdownChange(WidgetT *w);
|
||||||
static void onImmediateChange(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) {
|
static void evaluateImmediate(const char *expr) {
|
||||||
if (!expr || *expr == '\0') {
|
if (!expr || *expr == '\0') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try assignment first when paused
|
||||||
|
if (immTryAssign(expr)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
char wrapped[1024];
|
char wrapped[1024];
|
||||||
|
|
||||||
// If it already starts with a statement keyword, use as-is
|
// 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
|
// findDebugVar -- find a debug variable by name, respecting scope
|
||||||
static const BasDebugVarT *findDebugVar(const char *name) {
|
static const BasDebugVarT *findDebugVar(const char *name) {
|
||||||
if (!sDbgModule || !sDbgModule->debugVars) {
|
if (!sDbgModule || !sDbgModule->debugVars) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue