Form level variable scope. Proper Load/Unload.

This commit is contained in:
Scott Duensing 2026-04-03 18:09:50 -05:00
parent dd68a19b5b
commit bf5bf9bb1d
15 changed files with 611 additions and 50 deletions

View file

@ -38,7 +38,7 @@ APP_TARGET = $(APPDIR)/dvxbasic.app
# Native test programs (host gcc, not cross-compiled) # Native test programs (host gcc, not cross-compiled)
HOSTCC = gcc HOSTCC = gcc
HOSTCFLAGS = -O2 -Wall -Wextra -Wno-type-limits -Wno-sign-compare -I. -I../../core HOSTCFLAGS = -O2 -Wall -Wextra -Wno-type-limits -Wno-sign-compare -I. -I../../core
BINDIR = ../bin BINDIR = ../../bin
TEST_COMPILER = $(BINDIR)/test_compiler TEST_COMPILER = $(BINDIR)/test_compiler
TEST_VM = $(BINDIR)/test_vm TEST_VM = $(BINDIR)/test_vm

View file

@ -100,6 +100,17 @@ BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg) {
mod->dataCount = cg->dataCount; mod->dataCount = cg->dataCount;
// Copy form variable info
if (cg->formVarInfoCount > 0) {
mod->formVarInfo = (BasFormVarInfoT *)malloc(cg->formVarInfoCount * sizeof(BasFormVarInfoT));
if (mod->formVarInfo) {
memcpy(mod->formVarInfo, cg->formVarInfo, cg->formVarInfoCount * sizeof(BasFormVarInfoT));
}
mod->formVarInfoCount = cg->formVarInfoCount;
}
return mod; return mod;
} }
@ -175,12 +186,15 @@ void basCodeGenFree(BasCodeGenT *cg) {
arrfree(cg->code); arrfree(cg->code);
arrfree(cg->constants); arrfree(cg->constants);
arrfree(cg->dataPool); arrfree(cg->dataPool);
arrfree(cg->formVarInfo);
cg->code = NULL; cg->code = NULL;
cg->constants = NULL; cg->constants = NULL;
cg->dataPool = NULL; cg->dataPool = NULL;
cg->formVarInfo = NULL;
cg->constCount = 0; cg->constCount = 0;
cg->dataCount = 0; cg->dataCount = 0;
cg->codeLen = 0; cg->codeLen = 0;
cg->formVarInfoCount = 0;
} }
@ -317,6 +331,7 @@ void basModuleFree(BasModuleT *mod) {
} }
free(mod->procs); free(mod->procs);
free(mod->formVarInfo);
free(mod); free(mod);
} }

View file

@ -27,6 +27,8 @@ typedef struct {
int32_t globalCount; int32_t globalCount;
BasValueT *dataPool; // stb_ds dynamic array BasValueT *dataPool; // stb_ds dynamic array
int32_t dataCount; int32_t dataCount;
BasFormVarInfoT *formVarInfo; // stb_ds dynamic array
int32_t formVarInfoCount;
} BasCodeGenT; } BasCodeGenT;
// ============================================================ // ============================================================

View file

@ -182,7 +182,7 @@ static bool atEnd(const BasLexerT *lex) {
void basLexerInit(BasLexerT *lex, const char *source, int32_t sourceLen) { void basLexerInit(BasLexerT *lex, const char *source, int32_t sourceLen) {
memset(lex, 0, sizeof(*lex)); memset(lex, 0, sizeof(*lex));
lex->source = source; lex->source = source;
lex->sourceLen = sourceLen; lex->sourceLen = (sourceLen < 0) ? (int32_t)strlen(source) : sourceLen;
lex->pos = 0; lex->pos = 0;
lex->line = 1; lex->line = 1;
lex->col = 1; lex->col = 1;

View file

@ -194,6 +194,9 @@
#define OP_FIND_CTRL 0x8C // pop ctrlName, pop formRef, push controlRef #define OP_FIND_CTRL 0x8C // pop ctrlName, pop formRef, push controlRef
#define OP_CTRL_REF 0x8D // [uint16 nameConstIdx] push named control on current form #define OP_CTRL_REF 0x8D // [uint16 nameConstIdx] push named control on current form
#define OP_FIND_CTRL_IDX 0x8E // pop index, pop ctrlName, pop formRef, push ctrlRef #define OP_FIND_CTRL_IDX 0x8E // pop index, pop ctrlName, pop formRef, push ctrlRef
#define OP_LOAD_FORM_VAR 0x8F // [uint16 idx] push currentFormVars[idx]
#define OP_STORE_FORM_VAR 0x9B // [uint16 idx] pop, store to currentFormVars[idx]
#define OP_PUSH_FORM_ADDR 0x9C // [uint16 idx] push &currentFormVars[idx] (ByRef)
// ============================================================ // ============================================================
// Array / misc // Array / misc

View file

@ -140,6 +140,7 @@ static void parsePrimary(BasParserT *p);
// ============================================================ // ============================================================
static void parseAssignOrCall(BasParserT *p); static void parseAssignOrCall(BasParserT *p);
static void parseBeginForm(BasParserT *p);
static void parseClose(BasParserT *p); static void parseClose(BasParserT *p);
static void parseConst(BasParserT *p); static void parseConst(BasParserT *p);
static void parseData(BasParserT *p); static void parseData(BasParserT *p);
@ -150,6 +151,7 @@ static void parseDim(BasParserT *p);
static void parseDimBounds(BasParserT *p, int32_t *outDims); static void parseDimBounds(BasParserT *p, int32_t *outDims);
static void parseDo(BasParserT *p); static void parseDo(BasParserT *p);
static void parseEnd(BasParserT *p); static void parseEnd(BasParserT *p);
static void parseEndForm(BasParserT *p);
static void parseErase(BasParserT *p); static void parseErase(BasParserT *p);
static void parseExit(BasParserT *p); static void parseExit(BasParserT *p);
static void parseFor(BasParserT *p); static void parseFor(BasParserT *p);
@ -734,6 +736,9 @@ static void emitLoad(BasParserT *p, BasSymbolT *sym) {
if (sym->scope == SCOPE_LOCAL) { if (sym->scope == SCOPE_LOCAL) {
basEmit8(&p->cg, OP_LOAD_LOCAL); basEmit8(&p->cg, OP_LOAD_LOCAL);
basEmitU16(&p->cg, (uint16_t)sym->index); basEmitU16(&p->cg, (uint16_t)sym->index);
} else if (sym->scope == SCOPE_FORM) {
basEmit8(&p->cg, OP_LOAD_FORM_VAR);
basEmitU16(&p->cg, (uint16_t)sym->index);
} else { } else {
basEmit8(&p->cg, OP_LOAD_GLOBAL); basEmit8(&p->cg, OP_LOAD_GLOBAL);
basEmitU16(&p->cg, (uint16_t)sym->index); basEmitU16(&p->cg, (uint16_t)sym->index);
@ -750,6 +755,9 @@ static void emitStore(BasParserT *p, BasSymbolT *sym) {
if (sym->scope == SCOPE_LOCAL) { if (sym->scope == SCOPE_LOCAL) {
basEmit8(&p->cg, OP_STORE_LOCAL); basEmit8(&p->cg, OP_STORE_LOCAL);
basEmitU16(&p->cg, (uint16_t)sym->index); basEmitU16(&p->cg, (uint16_t)sym->index);
} else if (sym->scope == SCOPE_FORM) {
basEmit8(&p->cg, OP_STORE_FORM_VAR);
basEmitU16(&p->cg, (uint16_t)sym->index);
} else { } else {
basEmit8(&p->cg, OP_STORE_GLOBAL); basEmit8(&p->cg, OP_STORE_GLOBAL);
basEmitU16(&p->cg, (uint16_t)sym->index); basEmitU16(&p->cg, (uint16_t)sym->index);
@ -807,6 +815,8 @@ static void emitByRefArg(BasParserT *p) {
// It's a bare variable reference -- push its address // It's a bare variable reference -- push its address
if (sym->scope == SCOPE_LOCAL) { if (sym->scope == SCOPE_LOCAL) {
basEmit8(&p->cg, OP_PUSH_LOCAL_ADDR); basEmit8(&p->cg, OP_PUSH_LOCAL_ADDR);
} else if (sym->scope == SCOPE_FORM) {
basEmit8(&p->cg, OP_PUSH_FORM_ADDR);
} else { } else {
basEmit8(&p->cg, OP_PUSH_GLOBAL_ADDR); basEmit8(&p->cg, OP_PUSH_GLOBAL_ADDR);
} }
@ -1951,6 +1961,98 @@ static void parseAssignOrCall(BasParserT *p) {
} }
// ============================================================
// parseBeginForm / parseEndForm -- form scope directives
// ============================================================
//
// BEGINFORM "FormName" enters form scope: DIM at module level
// creates per-form variables. ENDFORM exits form scope.
static void parseBeginForm(BasParserT *p) {
advance(p); // consume BEGINFORM
if (!check(p, TOK_STRING_LIT)) {
errorExpected(p, "form name string");
return;
}
char formName[BAS_MAX_SYMBOL_NAME];
strncpy(formName, p->lex.token.text, BAS_MAX_SYMBOL_NAME - 1);
formName[BAS_MAX_SYMBOL_NAME - 1] = '\0';
advance(p);
if (p->sym.inFormScope) {
error(p, "Nested BEGINFORM is not allowed");
return;
}
if (p->sym.inLocalScope) {
error(p, "BEGINFORM inside SUB/FUNCTION is not allowed");
return;
}
basSymTabEnterFormScope(&p->sym, formName);
// Emit a forward JMP that will skip form init code (patched at ENDFORM).
// If no init code is emitted, the JMP is patched to jump to right here (noop).
basEmit8(&p->cg, OP_JMP);
p->formInitJmpAddr = basCodePos(&p->cg);
basEmit16(&p->cg, 0); // placeholder — patched at ENDFORM
p->formInitCodeStart = basCodePos(&p->cg);
}
static void parseEndForm(BasParserT *p) {
advance(p); // consume ENDFORM
if (!p->sym.inFormScope) {
error(p, "ENDFORM without BEGINFORM");
return;
}
// Capture form name before leaving scope
char formName[BAS_MAX_SYMBOL_NAME];
strncpy(formName, p->sym.formScopeName, sizeof(formName) - 1);
formName[sizeof(formName) - 1] = '\0';
int32_t varCount = basSymTabLeaveFormScope(&p->sym);
// Determine if any form init code was emitted
int32_t initStart = p->formInitCodeStart;
int32_t initAddr = -1;
int32_t initLen = 0;
int32_t curPos = basCodePos(&p->cg);
if (curPos > initStart) {
// Init code was emitted — add OP_RET to end the init block
basEmit8(&p->cg, OP_RET);
initAddr = initStart;
initLen = basCodePos(&p->cg) - initStart;
}
// Patch the JMP to skip over init code (land here)
if (p->formInitJmpAddr >= 0) {
int16_t offset = (int16_t)(basCodePos(&p->cg) - (p->formInitJmpAddr + 2));
basPatch16(&p->cg, p->formInitJmpAddr, offset);
}
p->formInitJmpAddr = -1;
p->formInitCodeStart = -1;
// Record form variable info (even if varCount is 0 but init code exists)
if (varCount > 0 || initAddr >= 0) {
BasFormVarInfoT info;
memset(&info, 0, sizeof(info));
snprintf(info.formName, sizeof(info.formName), "%s", formName);
info.varCount = varCount;
info.initCodeAddr = initAddr;
info.initCodeLen = initLen;
arrput(p->cg.formVarInfo, info);
p->cg.formVarInfoCount = (int32_t)arrlen(p->cg.formVarInfo);
}
}
static void parseClose(BasParserT *p) { static void parseClose(BasParserT *p) {
// CLOSE #channel // CLOSE #channel
advance(p); // consume CLOSE advance(p); // consume CLOSE
@ -2675,6 +2777,8 @@ static void parseDim(BasParserT *p) {
if (p->sym.inLocalScope) { if (p->sym.inLocalScope) {
sym->scope = SCOPE_LOCAL; sym->scope = SCOPE_LOCAL;
} else if (p->sym.inFormScope) {
sym->scope = SCOPE_FORM;
} else { } else {
sym->scope = SCOPE_GLOBAL; sym->scope = SCOPE_GLOBAL;
} }
@ -2896,7 +3000,7 @@ static void parseFor(BasParserT *p) {
// Emit FOR_INIT -- sets up the for-loop state in the VM // Emit FOR_INIT -- sets up the for-loop state in the VM
basEmit8(&p->cg, OP_FOR_INIT); basEmit8(&p->cg, OP_FOR_INIT);
basEmitU16(&p->cg, (uint16_t)loopVar->index); basEmitU16(&p->cg, (uint16_t)loopVar->index);
basEmit8(&p->cg, loopVar->scope == SCOPE_LOCAL ? 1 : 0); basEmit8(&p->cg, loopVar->scope == SCOPE_LOCAL ? 1 : (loopVar->scope == SCOPE_FORM ? 2 : 0));
int32_t loopBody = basCodePos(&p->cg); int32_t loopBody = basCodePos(&p->cg);
@ -2923,7 +3027,7 @@ static void parseFor(BasParserT *p) {
// Emit FOR_NEXT with backward jump to loop body // Emit FOR_NEXT with backward jump to loop body
basEmit8(&p->cg, OP_FOR_NEXT); basEmit8(&p->cg, OP_FOR_NEXT);
basEmitU16(&p->cg, (uint16_t)loopVar->index); basEmitU16(&p->cg, (uint16_t)loopVar->index);
basEmit8(&p->cg, loopVar->scope == SCOPE_LOCAL ? 1 : 0); basEmit8(&p->cg, loopVar->scope == SCOPE_LOCAL ? 1 : (loopVar->scope == SCOPE_FORM ? 2 : 0));
int16_t backOffset = (int16_t)(loopBody - (basCodePos(&p->cg) + 2)); int16_t backOffset = (int16_t)(loopBody - (basCodePos(&p->cg) + 2));
basEmit16(&p->cg, backOffset); basEmit16(&p->cg, backOffset);
@ -4731,6 +4835,17 @@ static void parseStatement(BasParserT *p) {
break; break;
case TOK_IDENT: { case TOK_IDENT: {
// Check for form scope directives (injected by IDE)
if (checkKeyword(p, "BEGINFORM")) {
parseBeginForm(p);
break;
}
if (checkKeyword(p, "ENDFORM")) {
parseEndForm(p);
break;
}
// Check for label: identifier followed by colon // Check for label: identifier followed by colon
BasLexerT savedLex = p->lex; BasLexerT savedLex = p->lex;
char labelName[BAS_MAX_TOKEN_LEN]; char labelName[BAS_MAX_TOKEN_LEN];
@ -5254,6 +5369,9 @@ void basParserInit(BasParserT *p, const char *source, int32_t sourceLen) {
exitListInit(&exitSubList); exitListInit(&exitSubList);
exitListInit(&exitFuncList); exitListInit(&exitFuncList);
p->formInitJmpAddr = -1;
p->formInitCodeStart = -1;
addPredefConsts(p); addPredefConsts(p);
// basLexerInit already primes the first token -- no advance needed // basLexerInit already primes the first token -- no advance needed

View file

@ -34,6 +34,9 @@ typedef struct {
bool optionExplicit; // true = variables must be declared with DIM bool optionExplicit; // true = variables must be declared with DIM
uint8_t defType[26]; // default type per letter (A-Z), set by DEFINT etc. uint8_t defType[26]; // default type per letter (A-Z), set by DEFINT etc.
char currentProc[BAS_MAX_TOKEN_LEN]; // name of current SUB/FUNCTION char currentProc[BAS_MAX_TOKEN_LEN]; // name of current SUB/FUNCTION
// Per-form init block tracking
int32_t formInitJmpAddr; // code position of JMP to patch (-1 = none)
int32_t formInitCodeStart; // code position where init block starts (-1 = none)
} BasParserT; } BasParserT;
// ============================================================ // ============================================================

View file

@ -33,10 +33,24 @@ static bool namesEqual(const char *a, const char *b) {
// ============================================================ // ============================================================
BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType) { BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType) {
// Check for duplicate in current scope // Determine scope: local > form > global.
BasScopeE scope = tab->inLocalScope ? SCOPE_LOCAL : SCOPE_GLOBAL; // Only variables get SCOPE_FORM; SUBs/FUNCTIONs/CONSTs remain global.
BasScopeE scope;
if (tab->inLocalScope) {
scope = SCOPE_LOCAL;
} else if (tab->inFormScope && kind == SYM_VARIABLE) {
scope = SCOPE_FORM;
} else {
scope = SCOPE_GLOBAL;
}
// Check for duplicate in current scope (skip ended form symbols)
for (int32_t i = 0; i < tab->count; i++) { for (int32_t i = 0; i < tab->count; i++) {
if (tab->symbols[i].formScopeEnded) {
continue;
}
if (tab->symbols[i].scope == scope && namesEqual(tab->symbols[i].name, name)) { if (tab->symbols[i].scope == scope && namesEqual(tab->symbols[i].name, name)) {
return NULL; // duplicate return NULL; // duplicate
} }
@ -67,6 +81,10 @@ int32_t basSymTabAllocSlot(BasSymTabT *tab) {
return tab->nextLocalIdx++; return tab->nextLocalIdx++;
} }
if (tab->inFormScope) {
return tab->nextFormVarIdx++;
}
return tab->nextGlobalIdx++; return tab->nextGlobalIdx++;
} }
@ -95,6 +113,17 @@ BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name) {
} }
} }
// Search form scope (active form-scoped symbols shadow globals)
for (int32_t i = tab->count - 1; i >= 0; i--) {
if (tab->symbols[i].formScopeEnded) {
continue;
}
if (tab->symbols[i].scope == SCOPE_FORM && namesEqual(tab->symbols[i].name, name)) {
return &tab->symbols[i];
}
}
// Search global scope // Search global scope
return basSymTabFindGlobal(tab, name); return basSymTabFindGlobal(tab, name);
} }
@ -106,6 +135,10 @@ BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name) {
BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name) { BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name) {
for (int32_t i = 0; i < tab->count; i++) { for (int32_t i = 0; i < tab->count; i++) {
if (tab->symbols[i].formScopeEnded) {
continue;
}
if (tab->symbols[i].scope == SCOPE_GLOBAL && namesEqual(tab->symbols[i].name, name)) { if (tab->symbols[i].scope == SCOPE_GLOBAL && namesEqual(tab->symbols[i].name, name)) {
return &tab->symbols[i]; return &tab->symbols[i];
} }
@ -150,3 +183,39 @@ void basSymTabLeaveLocal(BasSymTabT *tab) {
tab->inLocalScope = false; tab->inLocalScope = false;
tab->nextLocalIdx = 0; tab->nextLocalIdx = 0;
} }
// ============================================================
// basSymTabEnterFormScope
// ============================================================
void basSymTabEnterFormScope(BasSymTabT *tab, const char *formName) {
tab->inFormScope = true;
strncpy(tab->formScopeName, formName, BAS_MAX_SYMBOL_NAME - 1);
tab->formScopeName[BAS_MAX_SYMBOL_NAME - 1] = '\0';
tab->nextFormVarIdx = 0;
tab->formScopeSymStart = tab->count;
}
// ============================================================
// basSymTabLeaveFormScope
// ============================================================
int32_t basSymTabLeaveFormScope(BasSymTabT *tab) {
int32_t varCount = tab->nextFormVarIdx;
// Mark all form-scope symbols added since BEGINFORM as ended
for (int32_t i = tab->formScopeSymStart; i < tab->count; i++) {
if (tab->symbols[i].scope == SCOPE_FORM) {
tab->symbols[i].formScopeEnded = true;
}
}
tab->inFormScope = false;
tab->formScopeName[0] = '\0';
tab->nextFormVarIdx = 0;
tab->formScopeSymStart = 0;
return varCount;
}

View file

@ -33,7 +33,8 @@ typedef enum {
typedef enum { typedef enum {
SCOPE_GLOBAL, SCOPE_GLOBAL,
SCOPE_LOCAL SCOPE_LOCAL,
SCOPE_FORM // per-form variable (persists while form is loaded)
} BasScopeE; } BasScopeE;
// ============================================================ // ============================================================
@ -61,6 +62,7 @@ typedef struct {
bool isArray; bool isArray;
bool isShared; bool isShared;
bool isExtern; // true = external library function (DECLARE LIBRARY) bool isExtern; // true = external library function (DECLARE LIBRARY)
bool formScopeEnded; // true = form scope ended, invisible to lookups
int32_t udtTypeId; // for variables of BAS_TYPE_UDT: index of TYPE_DEF symbol int32_t udtTypeId; // for variables of BAS_TYPE_UDT: index of TYPE_DEF symbol
int32_t fixedLen; // for STRING * n: fixed length (0 = variable-length) int32_t fixedLen; // for STRING * n: fixed length (0 = variable-length)
uint16_t externLibIdx; // constant pool index for library name (if isExtern) uint16_t externLibIdx; // constant pool index for library name (if isExtern)
@ -97,6 +99,10 @@ typedef struct {
int32_t nextGlobalIdx; // next global variable slot int32_t nextGlobalIdx; // next global variable slot
int32_t nextLocalIdx; // next local variable slot (reset per SUB/FUNCTION) int32_t nextLocalIdx; // next local variable slot (reset per SUB/FUNCTION)
bool inLocalScope; // true when inside SUB/FUNCTION bool inLocalScope; // true when inside SUB/FUNCTION
bool inFormScope; // true inside BEGINFORM...ENDFORM
char formScopeName[BAS_MAX_SYMBOL_NAME]; // current form name
int32_t nextFormVarIdx; // next form-level variable slot
int32_t formScopeSymStart; // symbol count at BEGINFORM (for marking ended)
} BasSymTabT; } BasSymTabT;
// ============================================================ // ============================================================
@ -122,7 +128,14 @@ void basSymTabEnterLocal(BasSymTabT *tab);
// Leave local scope (called at END SUB/FUNCTION). Removes local symbols. // Leave local scope (called at END SUB/FUNCTION). Removes local symbols.
void basSymTabLeaveLocal(BasSymTabT *tab); void basSymTabLeaveLocal(BasSymTabT *tab);
// Allocate the next variable slot (global or local depending on scope). // Allocate the next variable slot (global, local, or form depending on scope).
int32_t basSymTabAllocSlot(BasSymTabT *tab); int32_t basSymTabAllocSlot(BasSymTabT *tab);
// Enter form scope (called at BEGINFORM). Form-level DIMs create SCOPE_FORM variables.
void basSymTabEnterFormScope(BasSymTabT *tab, const char *formName);
// Leave form scope (called at ENDFORM). Marks form-scope symbols as ended.
// Returns the number of form variables allocated.
int32_t basSymTabLeaveFormScope(BasSymTabT *tab);
#endif // DVXBASIC_SYMTAB_H #endif // DVXBASIC_SYMTAB_H

View file

@ -308,6 +308,14 @@ void basFormRtDestroy(BasFormRtT *rt) {
for (int32_t i = 0; i < rt->formCount; i++) { for (int32_t i = 0; i < rt->formCount; i++) {
BasFormT *form = &rt->forms[i]; BasFormT *form = &rt->forms[i];
if (form->formVars) {
for (int32_t j = 0; j < form->formVarCount; j++) {
basValRelease(&form->formVars[j]);
}
free(form->formVars);
}
for (int32_t j = 0; j < form->controlCount; j++) { for (int32_t j = 0; j < form->controlCount; j++) {
freeListBoxItems(&form->controls[j]); freeListBoxItems(&form->controls[j]);
} }
@ -321,6 +329,13 @@ void basFormRtDestroy(BasFormRtT *rt) {
} }
arrfree(rt->forms); arrfree(rt->forms);
// Free .frm source cache
for (int32_t i = 0; i < rt->frmCacheCount; i++) {
free(rt->frmCache[i].frmSource);
}
arrfree(rt->frmCache);
free(rt); free(rt);
} }
@ -402,8 +417,12 @@ bool basFormRtFireEventArgs(BasFormRtT *rt, BasFormT *form, const char *ctrlName
} }
BasFormT *prevForm = rt->currentForm; BasFormT *prevForm = rt->currentForm;
BasValueT *prevVars = rt->vm->currentFormVars;
int32_t prevVarCount = rt->vm->currentFormVarCount;
rt->currentForm = form; rt->currentForm = form;
basVmSetCurrentForm(rt->vm, form); basVmSetCurrentForm(rt->vm, form);
basVmSetCurrentFormVars(rt->vm, form->formVars, form->formVarCount);
bool ok; bool ok;
@ -415,6 +434,7 @@ bool basFormRtFireEventArgs(BasFormRtT *rt, BasFormT *form, const char *ctrlName
rt->currentForm = prevForm; rt->currentForm = prevForm;
basVmSetCurrentForm(rt->vm, prevForm); basVmSetCurrentForm(rt->vm, prevForm);
basVmSetCurrentFormVars(rt->vm, prevVars, prevVarCount);
return ok; return ok;
} }
@ -444,8 +464,12 @@ static bool basFormRtFireEventWithCancel(BasFormRtT *rt, BasFormT *form, const c
} }
BasFormT *prevForm = rt->currentForm; BasFormT *prevForm = rt->currentForm;
BasValueT *prevVars = rt->vm->currentFormVars;
int32_t prevVarCount = rt->vm->currentFormVarCount;
rt->currentForm = form; rt->currentForm = form;
basVmSetCurrentForm(rt->vm, form); basVmSetCurrentForm(rt->vm, form);
basVmSetCurrentFormVars(rt->vm, form->formVars, form->formVarCount);
bool cancelled = false; bool cancelled = false;
@ -466,6 +490,7 @@ static bool basFormRtFireEventWithCancel(BasFormRtT *rt, BasFormT *form, const c
rt->currentForm = prevForm; rt->currentForm = prevForm;
basVmSetCurrentForm(rt->vm, prevForm); basVmSetCurrentForm(rt->vm, prevForm);
basVmSetCurrentFormVars(rt->vm, prevVars, prevVarCount);
return cancelled; return cancelled;
} }
@ -569,7 +594,15 @@ void *basFormRtLoadForm(void *ctx, const char *formName) {
} }
} }
// Forms start hidden; code must call Show to make them visible // Check the .frm cache for reload after unload
for (int32_t i = 0; i < rt->frmCacheCount; i++) {
if (strcasecmp(rt->frmCache[i].formName, formName) == 0) {
// Re-parse the cached .frm source (creates window, controls, fires Load)
return basFormRtLoadFrm(rt, rt->frmCache[i].frmSource, rt->frmCache[i].frmSourceLen);
}
}
// No cache entry — create a bare form (first-time load without .frm file)
WindowT *win = dvxCreateWindowCentered(rt->ctx, formName, DEFAULT_FORM_W, DEFAULT_FORM_H, true); WindowT *win = dvxCreateWindowCentered(rt->ctx, formName, DEFAULT_FORM_W, DEFAULT_FORM_H, true);
if (!win) { if (!win) {
@ -597,11 +630,38 @@ void *basFormRtLoadForm(void *ctx, const char *formName) {
win->onBlur = onFormDeactivate; win->onBlur = onFormDeactivate;
form->window = win; form->window = win;
form->root = root; form->root = root;
form->contentBox = NULL; // created lazily after Layout property is known form->contentBox = NULL;
form->ctx = rt->ctx; form->ctx = rt->ctx;
form->vm = rt->vm; form->vm = rt->vm;
form->module = rt->module; form->module = rt->module;
// Allocate per-form variable storage and run init code from module metadata
if (rt->module && rt->module->formVarInfo) {
for (int32_t j = 0; j < rt->module->formVarInfoCount; j++) {
if (strcasecmp(rt->module->formVarInfo[j].formName, formName) == 0) {
int32_t vc = rt->module->formVarInfo[j].varCount;
if (vc > 0) {
form->formVars = (BasValueT *)calloc(vc, sizeof(BasValueT));
form->formVarCount = vc;
}
// Execute per-form init block (DIM arrays, UDT init)
int32_t initAddr = rt->module->formVarInfo[j].initCodeAddr;
if (initAddr >= 0 && rt->vm) {
basVmSetCurrentForm(rt->vm, form);
basVmSetCurrentFormVars(rt->vm, form->formVars, form->formVarCount);
basVmCallSub(rt->vm, initAddr);
basVmSetCurrentForm(rt->vm, NULL);
basVmSetCurrentFormVars(rt->vm, NULL, 0);
}
break;
}
}
}
return form; return form;
} }
@ -919,6 +979,34 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
} }
} }
// Cache the .frm source for reload after unload
if (form) {
bool cached = false;
for (int32_t i = 0; i < rt->frmCacheCount; i++) {
if (strcasecmp(rt->frmCache[i].formName, form->name) == 0) {
cached = true;
break;
}
}
if (!cached) {
BasFrmCacheT entry;
memset(&entry, 0, sizeof(entry));
snprintf(entry.formName, BAS_MAX_CTRL_NAME, "%s", form->name);
entry.frmSource = (char *)malloc(sourceLen + 1);
entry.frmSourceLen = sourceLen;
if (entry.frmSource) {
memcpy(entry.frmSource, source, sourceLen);
entry.frmSource[sourceLen] = '\0';
}
arrput(rt->frmCache, entry);
rt->frmCacheCount = (int32_t)arrlen(rt->frmCache);
}
}
// Fire the Load event now that the form and controls are ready // Fire the Load event now that the form and controls are ready
if (form) { if (form) {
basFormRtFireEvent(rt, form, form->name, "Load"); basFormRtFireEvent(rt, form, form->name, "Load");
@ -1015,6 +1103,17 @@ void basFormRtUnloadForm(void *ctx, void *formRef) {
basFormRtFireEvent(rt, form, form->name, "Unload"); basFormRtFireEvent(rt, form, form->name, "Unload");
// Release per-form variables
if (form->formVars) {
for (int32_t i = 0; i < form->formVarCount; i++) {
basValRelease(&form->formVars[i]);
}
free(form->formVars);
form->formVars = NULL;
form->formVarCount = 0;
}
for (int32_t i = 0; i < form->controlCount; i++) { for (int32_t i = 0; i < form->controlCount; i++) {
freeListBoxItems(&form->controls[i]); freeListBoxItems(&form->controls[i]);
} }
@ -1399,6 +1498,17 @@ static void onFormClose(WindowT *win) {
basFormRtFireEvent(sFormRt, form, form->name, "Unload"); basFormRtFireEvent(sFormRt, form, form->name, "Unload");
// Release per-form variables
if (form->formVars) {
for (int32_t j = 0; j < form->formVarCount; j++) {
basValRelease(&form->formVars[j]);
}
free(form->formVars);
form->formVars = NULL;
form->formVarCount = 0;
}
// Free control resources // Free control resources
for (int32_t j = 0; j < form->controlCount; j++) { for (int32_t j = 0; j < form->controlCount; j++) {
freeListBoxItems(&form->controls[j]); freeListBoxItems(&form->controls[j]);

View file

@ -68,12 +68,22 @@ typedef struct BasFormT {
bool frmCentered; bool frmCentered;
bool frmAutoSize; bool frmAutoSize;
bool frmHBox; // true if Layout = "HBox" bool frmHBox; // true if Layout = "HBox"
// Per-form variable storage (allocated at load, freed at unload)
BasValueT *formVars;
int32_t formVarCount;
} BasFormT; } BasFormT;
// ============================================================ // ============================================================
// Form runtime context // Form runtime context
// ============================================================ // ============================================================
// Cached .frm source for reload after unload
typedef struct {
char formName[BAS_MAX_CTRL_NAME];
char *frmSource; // malloc'd copy of .frm text
int32_t frmSourceLen;
} BasFrmCacheT;
typedef struct { typedef struct {
AppContextT *ctx; // DVX app context AppContextT *ctx; // DVX app context
BasVmT *vm; // shared VM instance BasVmT *vm; // shared VM instance
@ -81,6 +91,8 @@ typedef struct {
BasFormT *forms; // stb_ds dynamic array BasFormT *forms; // stb_ds dynamic array
int32_t formCount; int32_t formCount;
BasFormT *currentForm; // form currently dispatching events BasFormT *currentForm; // form currently dispatching events
BasFrmCacheT *frmCache; // stb_ds array of cached .frm sources
int32_t frmCacheCount;
} BasFormRtT; } BasFormRtT;
// ============================================================ // ============================================================

View file

@ -1027,11 +1027,20 @@ static void compileAndRun(void) {
} }
int32_t startLine = line; int32_t startLine = line;
// Inject BEGINFORM directive for .frm code sections
if (sProject.files[i].isForm && sProject.files[i].formName[0]) {
int32_t dirLen = snprintf(concatBuf + pos, IDE_MAX_SOURCE - pos,
"BEGINFORM \"%s\"\n", sProject.files[i].formName);
pos += dirLen;
line++;
}
int32_t fileLen = (int32_t)strlen(fileSrc); int32_t fileLen = (int32_t)strlen(fileSrc);
int32_t copyLen = fileLen; int32_t copyLen = fileLen;
if (pos + copyLen >= IDE_MAX_SOURCE - 1) { if (pos + copyLen >= IDE_MAX_SOURCE - 64) {
copyLen = IDE_MAX_SOURCE - 1 - pos; copyLen = IDE_MAX_SOURCE - 64 - pos;
} }
memcpy(concatBuf + pos, fileSrc, copyLen); memcpy(concatBuf + pos, fileSrc, copyLen);
@ -1052,6 +1061,13 @@ static void compileAndRun(void) {
line++; line++;
} }
// Inject ENDFORM directive
if (sProject.files[i].isForm && sProject.files[i].formName[0]) {
int32_t dirLen = snprintf(concatBuf + pos, IDE_MAX_SOURCE - pos, "ENDFORM\n");
pos += dirLen;
line++;
}
{ {
PrjSourceMapT mapEntry; PrjSourceMapT mapEntry;
mapEntry.fileIdx = i; mapEntry.fileIdx = i;

View file

@ -399,6 +399,16 @@ void basVmSetCurrentForm(BasVmT *vm, void *formRef) {
} }
// ============================================================
// basVmSetCurrentFormVars
// ============================================================
void basVmSetCurrentFormVars(BasVmT *vm, BasValueT *vars, int32_t count) {
vm->currentFormVars = vars;
vm->currentFormVarCount = count;
}
// ============================================================ // ============================================================
// basVmSetStepLimit // basVmSetStepLimit
// ============================================================ // ============================================================
@ -956,7 +966,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
case OP_FOR_INIT: { case OP_FOR_INIT: {
uint16_t varIdx = readUint16(vm); uint16_t varIdx = readUint16(vm);
uint8_t isLocal = readUint8(vm); uint8_t scopeTag = readUint8(vm);
BasValueT stepVal; BasValueT stepVal;
BasValueT limitVal; BasValueT limitVal;
@ -973,7 +983,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
BasForStateT *fs = &vm->forStack[vm->forDepth++]; BasForStateT *fs = &vm->forStack[vm->forDepth++];
fs->varIdx = varIdx; fs->varIdx = varIdx;
fs->isLocal = (isLocal != 0); fs->scopeTag = scopeTag;
fs->limit = limitVal; fs->limit = limitVal;
fs->step = stepVal; fs->step = stepVal;
fs->loopTop = vm->pc; fs->loopTop = vm->pc;
@ -982,7 +992,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
case OP_FOR_NEXT: { case OP_FOR_NEXT: {
uint16_t varIdx = readUint16(vm); uint16_t varIdx = readUint16(vm);
uint8_t isLocal = readUint8(vm); uint8_t scopeTag = readUint8(vm);
int16_t loopTopOffset = readInt16(vm); int16_t loopTopOffset = readInt16(vm);
if (vm->forDepth <= 0) { if (vm->forDepth <= 0) {
@ -997,10 +1007,10 @@ BasVmResultE basVmStep(BasVmT *vm) {
return BAS_VM_ERROR; return BAS_VM_ERROR;
} }
// Get pointer to the loop variable (global or local) // Get pointer to the loop variable
BasValueT *varSlot; BasValueT *varSlot;
if (isLocal) { if (scopeTag == 1) {
BasCallFrameT *frame = currentFrame(vm); BasCallFrameT *frame = currentFrame(vm);
if (!frame || varIdx >= (uint16_t)frame->localCount) { if (!frame || varIdx >= (uint16_t)frame->localCount) {
@ -1009,6 +1019,13 @@ BasVmResultE basVmStep(BasVmT *vm) {
} }
varSlot = &frame->locals[varIdx]; 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 { } else {
if (varIdx >= BAS_VM_MAX_GLOBALS) { if (varIdx >= BAS_VM_MAX_GLOBALS) {
runtimeError(vm, 9, "Invalid global variable index"); runtimeError(vm, 9, "Invalid global variable index");
@ -2905,6 +2922,56 @@ BasVmResultE basVmStep(BasVmT *vm) {
break; 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 // External library calls
// ============================================================ // ============================================================

View file

@ -195,7 +195,7 @@ typedef struct {
typedef struct { typedef struct {
int32_t varIdx; // loop variable slot index int32_t varIdx; // loop variable slot index
bool isLocal; // true = local, false = global uint8_t scopeTag; // 0 = global, 1 = local, 2 = form
BasValueT limit; // upper bound BasValueT limit; // upper bound
BasValueT step; // step value BasValueT step; // step value
int32_t loopTop; // PC of the loop body start int32_t loopTop; // PC of the loop body start
@ -224,6 +224,17 @@ typedef struct {
bool isFunction; // true = FUNCTION, false = SUB bool isFunction; // true = FUNCTION, false = SUB
} BasProcEntryT; } BasProcEntryT;
// ============================================================
// Per-form variable info (how many form-scoped vars each form needs)
// ============================================================
typedef struct {
char formName[BAS_MAX_PROC_NAME];
int32_t varCount;
int32_t initCodeAddr; // offset in module->code for per-form init (-1 = none)
int32_t initCodeLen; // length of init bytecode
} BasFormVarInfoT;
// ============================================================ // ============================================================
// Compiled module (output of the compiler) // Compiled module (output of the compiler)
// ============================================================ // ============================================================
@ -239,6 +250,8 @@ typedef struct {
int32_t dataCount; // number of values in the data pool int32_t dataCount; // number of values in the data pool
BasProcEntryT *procs; // procedure table (SUBs and FUNCTIONs) BasProcEntryT *procs; // procedure table (SUBs and FUNCTIONs)
int32_t procCount; int32_t procCount;
BasFormVarInfoT *formVarInfo; // per-form variable counts
int32_t formVarInfoCount;
} BasModuleT; } BasModuleT;
// ============================================================ // ============================================================
@ -309,6 +322,8 @@ typedef struct {
// Current form reference (set during event dispatch) // Current form reference (set during event dispatch)
void *currentForm; void *currentForm;
BasValueT *currentFormVars; // points to current form's variable storage
int32_t currentFormVarCount; // number of form variables
} BasVmT; } BasVmT;
// ============================================================ // ============================================================
@ -348,6 +363,7 @@ void basVmSetExternCallbacks(BasVmT *vm, const BasExternCallbacksT *ext);
// Set the current form context (called by host during event dispatch). // Set the current form context (called by host during event dispatch).
void basVmSetCurrentForm(BasVmT *vm, void *formRef); void basVmSetCurrentForm(BasVmT *vm, void *formRef);
void basVmSetCurrentFormVars(BasVmT *vm, BasValueT *vars, int32_t count);
// Set the step limit for basVmRun. 0 = unlimited (default). // Set the step limit for basVmRun. 0 = unlimited (default).
// When the limit is reached, basVmRun returns BAS_VM_STEP_LIMIT. // When the limit is reached, basVmRun returns BAS_VM_STEP_LIMIT.

View file

@ -2630,6 +2630,123 @@ int main(void) {
printf("\n"); printf("\n");
} }
// ============================================================
// Coverage: BEGINFORM/ENDFORM with form-level DIM
// ============================================================
{
printf("=== Form-level variables ===\n");
BasParserT parser;
basParserInit(&parser,
"BEGINFORM \"Form1\"\n"
"Dim counter As Integer\n"
"Dim msg As String\n"
"\n"
"Sub Form1_Load ()\n"
" counter = 0\n"
" msg = \"hello\"\n"
"End Sub\n"
"\n"
"Sub Timer1_Timer ()\n"
" counter = counter + 1\n"
"End Sub\n"
"ENDFORM\n",
-1);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
printf("OK\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: Form-scope arrays compile (init deferred to load)
// ============================================================
{
printf("=== Form-scope array ===\n");
BasParserT parser;
basParserInit(&parser,
"BEGINFORM \"Form1\"\n"
"Dim arr(10) As Integer\n"
"\n"
"Sub Form1_Load ()\n"
" arr(0) = 42\n"
"End Sub\n"
"ENDFORM\n",
-1);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
printf("OK\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: Two forms with same-named form vars (independent)
// ============================================================
{
printf("=== Two forms same var name ===\n");
BasParserT parser;
basParserInit(&parser,
"BEGINFORM \"Form1\"\n"
"Dim counter As Integer\n"
"Sub Form1_Load ()\n"
" counter = 1\n"
"End Sub\n"
"ENDFORM\n"
"\n"
"BEGINFORM \"Form2\"\n"
"Dim counter As Integer\n"
"Sub Form2_Load ()\n"
" counter = 2\n"
"End Sub\n"
"ENDFORM\n",
-1);
if (!basParse(&parser)) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
printf("OK\n");
}
basParserFree(&parser);
printf("\n");
}
// ============================================================
// Coverage: Nested BEGINFORM rejected
// ============================================================
{
printf("=== Nested BEGINFORM rejected ===\n");
BasParserT parser;
basParserInit(&parser,
"BEGINFORM \"Form1\"\n"
"BEGINFORM \"Form2\"\n"
"ENDFORM\n"
"ENDFORM\n",
-1);
if (!basParse(&parser)) {
printf("OK (expected error: %s)\n", parser.error);
} else {
printf("FAIL: should have rejected nested BEGINFORM\n");
}
basParserFree(&parser);
printf("\n");
}
printf("All tests complete.\n"); printf("All tests complete.\n");
return 0; return 0;
} }