Form level variable scope. Proper Load/Unload.
This commit is contained in:
parent
dd68a19b5b
commit
bf5bf9bb1d
15 changed files with 611 additions and 50 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
cg->code = NULL;
|
arrfree(cg->formVarInfo);
|
||||||
cg->constants = NULL;
|
cg->code = NULL;
|
||||||
cg->dataPool = NULL;
|
cg->constants = NULL;
|
||||||
cg->constCount = 0;
|
cg->dataPool = NULL;
|
||||||
cg->dataCount = 0;
|
cg->formVarInfo = NULL;
|
||||||
cg->codeLen = 0;
|
cg->constCount = 0;
|
||||||
|
cg->dataCount = 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,15 @@
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t *code; // stb_ds dynamic array
|
uint8_t *code; // stb_ds dynamic array
|
||||||
int32_t codeLen;
|
int32_t codeLen;
|
||||||
BasStringT **constants; // stb_ds dynamic array
|
BasStringT **constants; // stb_ds dynamic array
|
||||||
int32_t constCount;
|
int32_t constCount;
|
||||||
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;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 ¤tFormVars[idx] (ByRef)
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Array / misc
|
// Array / misc
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -94,9 +96,13 @@ typedef struct {
|
||||||
typedef struct {
|
typedef struct {
|
||||||
BasSymbolT *symbols; // stb_ds dynamic array
|
BasSymbolT *symbols; // stb_ds dynamic array
|
||||||
int32_t count;
|
int32_t count;
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -401,9 +416,13 @@ bool basFormRtFireEventArgs(BasFormRtT *rt, BasFormT *form, const char *ctrlName
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -443,9 +463,13 @@ static bool basFormRtFireEventWithCancel(BasFormRtT *rt, BasFormT *form, const c
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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]);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -1027,11 +1027,20 @@ static void compileAndRun(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t startLine = line;
|
int32_t startLine = line;
|
||||||
int32_t fileLen = (int32_t)strlen(fileSrc);
|
|
||||||
int32_t copyLen = fileLen;
|
|
||||||
|
|
||||||
if (pos + copyLen >= IDE_MAX_SOURCE - 1) {
|
// Inject BEGINFORM directive for .frm code sections
|
||||||
copyLen = IDE_MAX_SOURCE - 1 - pos;
|
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 copyLen = fileLen;
|
||||||
|
|
||||||
|
if (pos + copyLen >= IDE_MAX_SOURCE - 64) {
|
||||||
|
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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -955,8 +965,8 @@ 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
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -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,21 +224,34 @@ 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)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t *code; // p-code bytecode
|
uint8_t *code; // p-code bytecode
|
||||||
int32_t codeLen;
|
int32_t codeLen;
|
||||||
BasStringT **constants; // string constant pool
|
BasStringT **constants; // string constant pool
|
||||||
int32_t constCount;
|
int32_t constCount;
|
||||||
int32_t globalCount; // number of global variable slots needed
|
int32_t globalCount; // number of global variable slots needed
|
||||||
int32_t entryPoint; // PC of the first instruction (module-level code)
|
int32_t entryPoint; // PC of the first instruction (module-level code)
|
||||||
BasValueT *dataPool; // DATA statement value pool
|
BasValueT *dataPool; // DATA statement value pool
|
||||||
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;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -308,7 +321,9 @@ typedef struct {
|
||||||
int32_t externCacheCount;
|
int32_t externCacheCount;
|
||||||
|
|
||||||
// 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.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue