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)
|
||||
HOSTCC = gcc
|
||||
HOSTCFLAGS = -O2 -Wall -Wextra -Wno-type-limits -Wno-sign-compare -I. -I../../core
|
||||
BINDIR = ../bin
|
||||
BINDIR = ../../bin
|
||||
|
||||
TEST_COMPILER = $(BINDIR)/test_compiler
|
||||
TEST_VM = $(BINDIR)/test_vm
|
||||
|
|
|
|||
|
|
@ -100,6 +100,17 @@ BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg) {
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -175,12 +186,15 @@ void basCodeGenFree(BasCodeGenT *cg) {
|
|||
arrfree(cg->code);
|
||||
arrfree(cg->constants);
|
||||
arrfree(cg->dataPool);
|
||||
cg->code = NULL;
|
||||
cg->constants = NULL;
|
||||
cg->dataPool = NULL;
|
||||
cg->constCount = 0;
|
||||
cg->dataCount = 0;
|
||||
cg->codeLen = 0;
|
||||
arrfree(cg->formVarInfo);
|
||||
cg->code = NULL;
|
||||
cg->constants = NULL;
|
||||
cg->dataPool = NULL;
|
||||
cg->formVarInfo = NULL;
|
||||
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->formVarInfo);
|
||||
free(mod);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,13 +20,15 @@
|
|||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
uint8_t *code; // stb_ds dynamic array
|
||||
int32_t codeLen;
|
||||
BasStringT **constants; // stb_ds dynamic array
|
||||
int32_t constCount;
|
||||
int32_t globalCount;
|
||||
BasValueT *dataPool; // stb_ds dynamic array
|
||||
int32_t dataCount;
|
||||
uint8_t *code; // stb_ds dynamic array
|
||||
int32_t codeLen;
|
||||
BasStringT **constants; // stb_ds dynamic array
|
||||
int32_t constCount;
|
||||
int32_t globalCount;
|
||||
BasValueT *dataPool; // stb_ds dynamic array
|
||||
int32_t dataCount;
|
||||
BasFormVarInfoT *formVarInfo; // stb_ds dynamic array
|
||||
int32_t formVarInfoCount;
|
||||
} BasCodeGenT;
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ static bool atEnd(const BasLexerT *lex) {
|
|||
void basLexerInit(BasLexerT *lex, const char *source, int32_t sourceLen) {
|
||||
memset(lex, 0, sizeof(*lex));
|
||||
lex->source = source;
|
||||
lex->sourceLen = sourceLen;
|
||||
lex->sourceLen = (sourceLen < 0) ? (int32_t)strlen(source) : sourceLen;
|
||||
lex->pos = 0;
|
||||
lex->line = 1;
|
||||
lex->col = 1;
|
||||
|
|
|
|||
|
|
@ -194,6 +194,9 @@
|
|||
#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_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
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ static void parsePrimary(BasParserT *p);
|
|||
// ============================================================
|
||||
|
||||
static void parseAssignOrCall(BasParserT *p);
|
||||
static void parseBeginForm(BasParserT *p);
|
||||
static void parseClose(BasParserT *p);
|
||||
static void parseConst(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 parseDo(BasParserT *p);
|
||||
static void parseEnd(BasParserT *p);
|
||||
static void parseEndForm(BasParserT *p);
|
||||
static void parseErase(BasParserT *p);
|
||||
static void parseExit(BasParserT *p);
|
||||
static void parseFor(BasParserT *p);
|
||||
|
|
@ -734,6 +736,9 @@ static void emitLoad(BasParserT *p, BasSymbolT *sym) {
|
|||
if (sym->scope == SCOPE_LOCAL) {
|
||||
basEmit8(&p->cg, OP_LOAD_LOCAL);
|
||||
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 {
|
||||
basEmit8(&p->cg, OP_LOAD_GLOBAL);
|
||||
basEmitU16(&p->cg, (uint16_t)sym->index);
|
||||
|
|
@ -750,6 +755,9 @@ static void emitStore(BasParserT *p, BasSymbolT *sym) {
|
|||
if (sym->scope == SCOPE_LOCAL) {
|
||||
basEmit8(&p->cg, OP_STORE_LOCAL);
|
||||
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 {
|
||||
basEmit8(&p->cg, OP_STORE_GLOBAL);
|
||||
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
|
||||
if (sym->scope == SCOPE_LOCAL) {
|
||||
basEmit8(&p->cg, OP_PUSH_LOCAL_ADDR);
|
||||
} else if (sym->scope == SCOPE_FORM) {
|
||||
basEmit8(&p->cg, OP_PUSH_FORM_ADDR);
|
||||
} else {
|
||||
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) {
|
||||
// CLOSE #channel
|
||||
advance(p); // consume CLOSE
|
||||
|
|
@ -2675,6 +2777,8 @@ static void parseDim(BasParserT *p) {
|
|||
|
||||
if (p->sym.inLocalScope) {
|
||||
sym->scope = SCOPE_LOCAL;
|
||||
} else if (p->sym.inFormScope) {
|
||||
sym->scope = SCOPE_FORM;
|
||||
} else {
|
||||
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
|
||||
basEmit8(&p->cg, OP_FOR_INIT);
|
||||
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);
|
||||
|
||||
|
|
@ -2923,7 +3027,7 @@ static void parseFor(BasParserT *p) {
|
|||
// Emit FOR_NEXT with backward jump to loop body
|
||||
basEmit8(&p->cg, OP_FOR_NEXT);
|
||||
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));
|
||||
basEmit16(&p->cg, backOffset);
|
||||
|
||||
|
|
@ -4731,6 +4835,17 @@ static void parseStatement(BasParserT *p) {
|
|||
break;
|
||||
|
||||
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
|
||||
BasLexerT savedLex = p->lex;
|
||||
char labelName[BAS_MAX_TOKEN_LEN];
|
||||
|
|
@ -5254,6 +5369,9 @@ void basParserInit(BasParserT *p, const char *source, int32_t sourceLen) {
|
|||
exitListInit(&exitSubList);
|
||||
exitListInit(&exitFuncList);
|
||||
|
||||
p->formInitJmpAddr = -1;
|
||||
p->formInitCodeStart = -1;
|
||||
|
||||
addPredefConsts(p);
|
||||
|
||||
// basLexerInit already primes the first token -- no advance needed
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ typedef struct {
|
|||
bool optionExplicit; // true = variables must be declared with DIM
|
||||
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
|
||||
// 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;
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
// Check for duplicate in current scope
|
||||
BasScopeE scope = tab->inLocalScope ? SCOPE_LOCAL : SCOPE_GLOBAL;
|
||||
// Determine scope: local > form > 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++) {
|
||||
if (tab->symbols[i].formScopeEnded) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tab->symbols[i].scope == scope && namesEqual(tab->symbols[i].name, name)) {
|
||||
return NULL; // duplicate
|
||||
}
|
||||
|
|
@ -67,6 +81,10 @@ int32_t basSymTabAllocSlot(BasSymTabT *tab) {
|
|||
return tab->nextLocalIdx++;
|
||||
}
|
||||
|
||||
if (tab->inFormScope) {
|
||||
return tab->nextFormVarIdx++;
|
||||
}
|
||||
|
||||
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
|
||||
return basSymTabFindGlobal(tab, name);
|
||||
}
|
||||
|
|
@ -106,6 +135,10 @@ BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name) {
|
|||
|
||||
BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name) {
|
||||
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)) {
|
||||
return &tab->symbols[i];
|
||||
}
|
||||
|
|
@ -150,3 +183,39 @@ void basSymTabLeaveLocal(BasSymTabT *tab) {
|
|||
tab->inLocalScope = false;
|
||||
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 {
|
||||
SCOPE_GLOBAL,
|
||||
SCOPE_LOCAL
|
||||
SCOPE_LOCAL,
|
||||
SCOPE_FORM // per-form variable (persists while form is loaded)
|
||||
} BasScopeE;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -61,6 +62,7 @@ typedef struct {
|
|||
bool isArray;
|
||||
bool isShared;
|
||||
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 fixedLen; // for STRING * n: fixed length (0 = variable-length)
|
||||
uint16_t externLibIdx; // constant pool index for library name (if isExtern)
|
||||
|
|
@ -94,9 +96,13 @@ typedef struct {
|
|||
typedef struct {
|
||||
BasSymbolT *symbols; // stb_ds dynamic array
|
||||
int32_t count;
|
||||
int32_t nextGlobalIdx; // next global variable slot
|
||||
int32_t nextLocalIdx; // next local variable slot (reset per SUB/FUNCTION)
|
||||
bool inLocalScope; // true when inside SUB/FUNCTION
|
||||
int32_t nextGlobalIdx; // next global variable slot
|
||||
int32_t nextLocalIdx; // next local variable slot (reset per 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;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -122,7 +128,14 @@ void basSymTabEnterLocal(BasSymTabT *tab);
|
|||
// Leave local scope (called at END SUB/FUNCTION). Removes local symbols.
|
||||
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);
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -308,6 +308,14 @@ void basFormRtDestroy(BasFormRtT *rt) {
|
|||
for (int32_t i = 0; i < rt->formCount; 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++) {
|
||||
freeListBoxItems(&form->controls[j]);
|
||||
}
|
||||
|
|
@ -321,6 +329,13 @@ void basFormRtDestroy(BasFormRtT *rt) {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -401,9 +416,13 @@ bool basFormRtFireEventArgs(BasFormRtT *rt, BasFormT *form, const char *ctrlName
|
|||
return false;
|
||||
}
|
||||
|
||||
BasFormT *prevForm = rt->currentForm;
|
||||
BasFormT *prevForm = rt->currentForm;
|
||||
BasValueT *prevVars = rt->vm->currentFormVars;
|
||||
int32_t prevVarCount = rt->vm->currentFormVarCount;
|
||||
|
||||
rt->currentForm = form;
|
||||
basVmSetCurrentForm(rt->vm, form);
|
||||
basVmSetCurrentFormVars(rt->vm, form->formVars, form->formVarCount);
|
||||
|
||||
bool ok;
|
||||
|
||||
|
|
@ -415,6 +434,7 @@ bool basFormRtFireEventArgs(BasFormRtT *rt, BasFormT *form, const char *ctrlName
|
|||
|
||||
rt->currentForm = prevForm;
|
||||
basVmSetCurrentForm(rt->vm, prevForm);
|
||||
basVmSetCurrentFormVars(rt->vm, prevVars, prevVarCount);
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
|
@ -443,9 +463,13 @@ static bool basFormRtFireEventWithCancel(BasFormRtT *rt, BasFormT *form, const c
|
|||
return false;
|
||||
}
|
||||
|
||||
BasFormT *prevForm = rt->currentForm;
|
||||
BasFormT *prevForm = rt->currentForm;
|
||||
BasValueT *prevVars = rt->vm->currentFormVars;
|
||||
int32_t prevVarCount = rt->vm->currentFormVarCount;
|
||||
|
||||
rt->currentForm = form;
|
||||
basVmSetCurrentForm(rt->vm, form);
|
||||
basVmSetCurrentFormVars(rt->vm, form->formVars, form->formVarCount);
|
||||
|
||||
bool cancelled = false;
|
||||
|
||||
|
|
@ -466,6 +490,7 @@ static bool basFormRtFireEventWithCancel(BasFormRtT *rt, BasFormT *form, const c
|
|||
|
||||
rt->currentForm = prevForm;
|
||||
basVmSetCurrentForm(rt->vm, prevForm);
|
||||
basVmSetCurrentFormVars(rt->vm, prevVars, prevVarCount);
|
||||
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);
|
||||
|
||||
if (!win) {
|
||||
|
|
@ -597,11 +630,38 @@ void *basFormRtLoadForm(void *ctx, const char *formName) {
|
|||
win->onBlur = onFormDeactivate;
|
||||
form->window = win;
|
||||
form->root = root;
|
||||
form->contentBox = NULL; // created lazily after Layout property is known
|
||||
form->contentBox = NULL;
|
||||
form->ctx = rt->ctx;
|
||||
form->vm = rt->vm;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
if (form) {
|
||||
basFormRtFireEvent(rt, form, form->name, "Load");
|
||||
|
|
@ -1015,6 +1103,17 @@ void basFormRtUnloadForm(void *ctx, void *formRef) {
|
|||
|
||||
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++) {
|
||||
freeListBoxItems(&form->controls[i]);
|
||||
}
|
||||
|
|
@ -1399,6 +1498,17 @@ static void onFormClose(WindowT *win) {
|
|||
|
||||
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
|
||||
for (int32_t j = 0; j < form->controlCount; j++) {
|
||||
freeListBoxItems(&form->controls[j]);
|
||||
|
|
|
|||
|
|
@ -68,12 +68,22 @@ typedef struct BasFormT {
|
|||
bool frmCentered;
|
||||
bool frmAutoSize;
|
||||
bool frmHBox; // true if Layout = "HBox"
|
||||
// Per-form variable storage (allocated at load, freed at unload)
|
||||
BasValueT *formVars;
|
||||
int32_t formVarCount;
|
||||
} BasFormT;
|
||||
|
||||
// ============================================================
|
||||
// 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 {
|
||||
AppContextT *ctx; // DVX app context
|
||||
BasVmT *vm; // shared VM instance
|
||||
|
|
@ -81,6 +91,8 @@ typedef struct {
|
|||
BasFormT *forms; // stb_ds dynamic array
|
||||
int32_t formCount;
|
||||
BasFormT *currentForm; // form currently dispatching events
|
||||
BasFrmCacheT *frmCache; // stb_ds array of cached .frm sources
|
||||
int32_t frmCacheCount;
|
||||
} BasFormRtT;
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -1027,11 +1027,20 @@ static void compileAndRun(void) {
|
|||
}
|
||||
|
||||
int32_t startLine = line;
|
||||
int32_t fileLen = (int32_t)strlen(fileSrc);
|
||||
int32_t copyLen = fileLen;
|
||||
|
||||
if (pos + copyLen >= IDE_MAX_SOURCE - 1) {
|
||||
copyLen = IDE_MAX_SOURCE - 1 - pos;
|
||||
// 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 copyLen = fileLen;
|
||||
|
||||
if (pos + copyLen >= IDE_MAX_SOURCE - 64) {
|
||||
copyLen = IDE_MAX_SOURCE - 64 - pos;
|
||||
}
|
||||
|
||||
memcpy(concatBuf + pos, fileSrc, copyLen);
|
||||
|
|
@ -1052,6 +1061,13 @@ static void compileAndRun(void) {
|
|||
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;
|
||||
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
|
||||
// ============================================================
|
||||
|
|
@ -955,8 +965,8 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
}
|
||||
|
||||
case OP_FOR_INIT: {
|
||||
uint16_t varIdx = readUint16(vm);
|
||||
uint8_t isLocal = readUint8(vm);
|
||||
uint16_t varIdx = readUint16(vm);
|
||||
uint8_t scopeTag = readUint8(vm);
|
||||
BasValueT stepVal;
|
||||
BasValueT limitVal;
|
||||
|
||||
|
|
@ -973,7 +983,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
|
||||
BasForStateT *fs = &vm->forStack[vm->forDepth++];
|
||||
fs->varIdx = varIdx;
|
||||
fs->isLocal = (isLocal != 0);
|
||||
fs->scopeTag = scopeTag;
|
||||
fs->limit = limitVal;
|
||||
fs->step = stepVal;
|
||||
fs->loopTop = vm->pc;
|
||||
|
|
@ -982,7 +992,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
|
||||
case OP_FOR_NEXT: {
|
||||
uint16_t varIdx = readUint16(vm);
|
||||
uint8_t isLocal = readUint8(vm);
|
||||
uint8_t scopeTag = readUint8(vm);
|
||||
int16_t loopTopOffset = readInt16(vm);
|
||||
|
||||
if (vm->forDepth <= 0) {
|
||||
|
|
@ -997,10 +1007,10 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
return BAS_VM_ERROR;
|
||||
}
|
||||
|
||||
// Get pointer to the loop variable (global or local)
|
||||
// Get pointer to the loop variable
|
||||
BasValueT *varSlot;
|
||||
|
||||
if (isLocal) {
|
||||
if (scopeTag == 1) {
|
||||
BasCallFrameT *frame = currentFrame(vm);
|
||||
|
||||
if (!frame || varIdx >= (uint16_t)frame->localCount) {
|
||||
|
|
@ -1009,6 +1019,13 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
}
|
||||
|
||||
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 {
|
||||
if (varIdx >= BAS_VM_MAX_GLOBALS) {
|
||||
runtimeError(vm, 9, "Invalid global variable index");
|
||||
|
|
@ -2905,6 +2922,56 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
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
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
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 step; // step value
|
||||
int32_t loopTop; // PC of the loop body start
|
||||
|
|
@ -224,21 +224,34 @@ typedef struct {
|
|||
bool isFunction; // true = FUNCTION, false = SUB
|
||||
} 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)
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
uint8_t *code; // p-code bytecode
|
||||
int32_t codeLen;
|
||||
BasStringT **constants; // string constant pool
|
||||
int32_t constCount;
|
||||
int32_t globalCount; // number of global variable slots needed
|
||||
int32_t entryPoint; // PC of the first instruction (module-level code)
|
||||
BasValueT *dataPool; // DATA statement value pool
|
||||
int32_t dataCount; // number of values in the data pool
|
||||
BasProcEntryT *procs; // procedure table (SUBs and FUNCTIONs)
|
||||
int32_t procCount;
|
||||
uint8_t *code; // p-code bytecode
|
||||
int32_t codeLen;
|
||||
BasStringT **constants; // string constant pool
|
||||
int32_t constCount;
|
||||
int32_t globalCount; // number of global variable slots needed
|
||||
int32_t entryPoint; // PC of the first instruction (module-level code)
|
||||
BasValueT *dataPool; // DATA statement value pool
|
||||
int32_t dataCount; // number of values in the data pool
|
||||
BasProcEntryT *procs; // procedure table (SUBs and FUNCTIONs)
|
||||
int32_t procCount;
|
||||
BasFormVarInfoT *formVarInfo; // per-form variable counts
|
||||
int32_t formVarInfoCount;
|
||||
} BasModuleT;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -308,7 +321,9 @@ typedef struct {
|
|||
int32_t externCacheCount;
|
||||
|
||||
// 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;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -348,6 +363,7 @@ void basVmSetExternCallbacks(BasVmT *vm, const BasExternCallbacksT *ext);
|
|||
|
||||
// Set the current form context (called by host during event dispatch).
|
||||
void basVmSetCurrentForm(BasVmT *vm, void *formRef);
|
||||
void basVmSetCurrentFormVars(BasVmT *vm, BasValueT *vars, int32_t count);
|
||||
|
||||
// Set the step limit for basVmRun. 0 = unlimited (default).
|
||||
// When the limit is reached, basVmRun returns BAS_VM_STEP_LIMIT.
|
||||
|
|
|
|||
|
|
@ -2630,6 +2630,123 @@ int main(void) {
|
|||
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");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue