Compiler fixes. Editor fixes. Fixes fixes.
This commit is contained in:
parent
4d4aedbc43
commit
17fe1840e3
13 changed files with 1803 additions and 260 deletions
|
|
@ -52,6 +52,7 @@
|
|||
#define OP_STORE_FIELD 0x19 // [uint16 fieldIdx] store UDT field
|
||||
#define OP_PUSH_LOCAL_ADDR 0x1A // [uint16 idx] push address of local (for ByRef)
|
||||
#define OP_PUSH_GLOBAL_ADDR 0x1B // [uint16 idx] push address of global (for ByRef)
|
||||
#define OP_STORE_ARRAY_FIELD 0x1C // [uint8 dims, uint16 fieldIdx] value, indices, array on stack
|
||||
|
||||
// ============================================================
|
||||
// Arithmetic (integer)
|
||||
|
|
|
|||
|
|
@ -104,7 +104,9 @@ static void errorExpected(BasParserT *p, const char *what);
|
|||
static void expect(BasParserT *p, BasTokenTypeE type);
|
||||
static void expectEndOfStatement(BasParserT *p);
|
||||
static const BuiltinFuncT *findBuiltin(const char *name);
|
||||
static void emitUdtInit(BasParserT *p, int32_t udtTypeId);
|
||||
static BasSymbolT *findTypeDef(BasParserT *p, const char *name);
|
||||
static BasSymbolT *findTypeDefById(BasParserT *p, int32_t typeId);
|
||||
static bool match(BasParserT *p, BasTokenTypeE type);
|
||||
static int32_t resolveFieldIndex(BasSymbolT *typeSym, const char *fieldName);
|
||||
static uint8_t resolveTypeName(BasParserT *p);
|
||||
|
|
@ -373,6 +375,59 @@ static BasSymbolT *findTypeDef(BasParserT *p, const char *name) {
|
|||
}
|
||||
|
||||
|
||||
static BasSymbolT *findTypeDefById(BasParserT *p, int32_t typeId) {
|
||||
for (int32_t i = 0; i < p->sym.count; i++) {
|
||||
if (p->sym.symbols[i].kind == SYM_TYPE_DEF && p->sym.symbols[i].index == typeId) {
|
||||
return &p->sym.symbols[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// emitUdtInit -- emit code to initialize nested UDT fields after a UDT
|
||||
// has been created and is on top of the stack. For each field that is
|
||||
// itself a UDT, we DUP the parent, allocate the child UDT, and store it
|
||||
// into the field.
|
||||
|
||||
static void emitUdtInit(BasParserT *p, int32_t udtTypeId) {
|
||||
BasSymbolT *typeSym = findTypeDefById(p, udtTypeId);
|
||||
|
||||
if (!typeSym) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < typeSym->fieldCount; i++) {
|
||||
if (typeSym->fields[i].dataType != BAS_TYPE_UDT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t childTypeId = typeSym->fields[i].udtTypeId;
|
||||
BasSymbolT *childType = findTypeDefById(p, childTypeId);
|
||||
|
||||
if (!childType) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// DUP parent, allocate child UDT, STORE_FIELD
|
||||
basEmit8(&p->cg, OP_DUP);
|
||||
basEmit8(&p->cg, OP_PUSH_INT16);
|
||||
basEmit16(&p->cg, (int16_t)childTypeId);
|
||||
basEmit8(&p->cg, OP_PUSH_INT16);
|
||||
basEmit16(&p->cg, (int16_t)childType->fieldCount);
|
||||
basEmit8(&p->cg, OP_DIM_ARRAY);
|
||||
basEmit8(&p->cg, 0);
|
||||
basEmit8(&p->cg, BAS_TYPE_UDT);
|
||||
|
||||
// Recursively init the child's nested UDT fields
|
||||
emitUdtInit(p, childTypeId);
|
||||
|
||||
basEmit8(&p->cg, OP_STORE_FIELD);
|
||||
basEmitU16(&p->cg, (uint16_t)i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool match(BasParserT *p, BasTokenTypeE type) {
|
||||
if (p->lex.token.type == type) {
|
||||
advance(p);
|
||||
|
|
@ -444,7 +499,7 @@ static uint8_t resolveTypeName(BasParserT *p) {
|
|||
|
||||
|
||||
static void skipNewlines(BasParserT *p) {
|
||||
while (check(p, TOK_NEWLINE)) {
|
||||
while (!p->hasError && check(p, TOK_NEWLINE)) {
|
||||
advance(p);
|
||||
}
|
||||
}
|
||||
|
|
@ -912,24 +967,28 @@ static void parseAddExpr(BasParserT *p) {
|
|||
}
|
||||
|
||||
|
||||
// VB precedence (high to low): ^, unary -, *, /, \, MOD, +, -
|
||||
// So parseMulExpr calls parseUnaryExpr, which calls parsePowExpr,
|
||||
// which calls parsePrimary. This makes -2^2 = -(2^2) = -4.
|
||||
|
||||
static void parseMulExpr(BasParserT *p) {
|
||||
parsePowExpr(p);
|
||||
parseUnaryExpr(p);
|
||||
while (!p->hasError) {
|
||||
if (check(p, TOK_STAR)) {
|
||||
advance(p);
|
||||
parsePowExpr(p);
|
||||
parseUnaryExpr(p);
|
||||
basEmit8(&p->cg, OP_MUL_INT);
|
||||
} else if (check(p, TOK_SLASH)) {
|
||||
advance(p);
|
||||
parsePowExpr(p);
|
||||
parseUnaryExpr(p);
|
||||
basEmit8(&p->cg, OP_DIV_FLT);
|
||||
} else if (check(p, TOK_BACKSLASH)) {
|
||||
advance(p);
|
||||
parsePowExpr(p);
|
||||
parseUnaryExpr(p);
|
||||
basEmit8(&p->cg, OP_IDIV_INT);
|
||||
} else if (check(p, TOK_MOD)) {
|
||||
advance(p);
|
||||
parsePowExpr(p);
|
||||
parseUnaryExpr(p);
|
||||
basEmit8(&p->cg, OP_MOD_INT);
|
||||
} else {
|
||||
break;
|
||||
|
|
@ -938,17 +997,6 @@ static void parseMulExpr(BasParserT *p) {
|
|||
}
|
||||
|
||||
|
||||
static void parsePowExpr(BasParserT *p) {
|
||||
parseUnaryExpr(p);
|
||||
// Right-associative, but iterative is fine for most BASIC uses
|
||||
while (!p->hasError && check(p, TOK_CARET)) {
|
||||
advance(p);
|
||||
parseUnaryExpr(p);
|
||||
basEmit8(&p->cg, OP_POW);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void parseUnaryExpr(BasParserT *p) {
|
||||
if (check(p, TOK_MINUS)) {
|
||||
advance(p);
|
||||
|
|
@ -961,7 +1009,17 @@ static void parseUnaryExpr(BasParserT *p) {
|
|||
parseUnaryExpr(p);
|
||||
return;
|
||||
}
|
||||
parsePowExpr(p);
|
||||
}
|
||||
|
||||
|
||||
static void parsePowExpr(BasParserT *p) {
|
||||
parsePrimary(p);
|
||||
while (!p->hasError && check(p, TOK_CARET)) {
|
||||
advance(p);
|
||||
parsePrimary(p);
|
||||
basEmit8(&p->cg, OP_POW);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1226,6 +1284,30 @@ static void parsePrimary(BasParserT *p) {
|
|||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_LOAD_ARRAY);
|
||||
basEmit8(&p->cg, (uint8_t)dims);
|
||||
|
||||
// Array-of-UDT field access: arr(i).field
|
||||
if (sym->dataType == BAS_TYPE_UDT && sym->udtTypeId >= 0 && check(p, TOK_DOT)) {
|
||||
advance(p); // consume DOT
|
||||
if (!check(p, TOK_IDENT)) {
|
||||
errorExpected(p, "field name");
|
||||
return;
|
||||
}
|
||||
BasSymbolT *typeSym = findTypeDefById(p, sym->udtTypeId);
|
||||
if (typeSym == NULL) {
|
||||
error(p, "Unknown TYPE definition");
|
||||
return;
|
||||
}
|
||||
int32_t fieldIdx = resolveFieldIndex(typeSym, p->lex.token.text);
|
||||
if (fieldIdx < 0) {
|
||||
char buf[512];
|
||||
snprintf(buf, sizeof(buf), "Unknown field '%s' in TYPE '%s'", p->lex.token.text, typeSym->name);
|
||||
error(p, buf);
|
||||
return;
|
||||
}
|
||||
advance(p); // consume field name
|
||||
basEmit8(&p->cg, OP_LOAD_FIELD);
|
||||
basEmitU16(&p->cg, (uint16_t)fieldIdx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Unknown function -- forward reference, assume it's a function
|
||||
|
|
@ -1249,18 +1331,16 @@ static void parsePrimary(BasParserT *p) {
|
|||
sym = basSymTabFind(&p->sym, name);
|
||||
if (sym != NULL && sym->dataType == BAS_TYPE_UDT && sym->udtTypeId >= 0) {
|
||||
emitLoad(p, sym);
|
||||
int32_t curTypeId = sym->udtTypeId;
|
||||
|
||||
// Loop to handle nested UDT field access: a.b.c
|
||||
while (check(p, TOK_DOT) && curTypeId >= 0) {
|
||||
advance(p); // consume DOT
|
||||
if (!check(p, TOK_IDENT)) {
|
||||
errorExpected(p, "field name");
|
||||
return;
|
||||
}
|
||||
BasSymbolT *typeSym = NULL;
|
||||
for (int32_t i = 0; i < p->sym.count; i++) {
|
||||
if (p->sym.symbols[i].kind == SYM_TYPE_DEF && p->sym.symbols[i].index == sym->udtTypeId) {
|
||||
typeSym = &p->sym.symbols[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
BasSymbolT *typeSym = findTypeDefById(p, curTypeId);
|
||||
if (typeSym == NULL) {
|
||||
error(p, "Unknown TYPE definition");
|
||||
return;
|
||||
|
|
@ -1275,6 +1355,14 @@ static void parsePrimary(BasParserT *p) {
|
|||
advance(p); // consume field name
|
||||
basEmit8(&p->cg, OP_LOAD_FIELD);
|
||||
basEmitU16(&p->cg, (uint16_t)fieldIdx);
|
||||
|
||||
// If this field is also a UDT, allow further dot access
|
||||
if (typeSym->fields[fieldIdx].dataType == BAS_TYPE_UDT) {
|
||||
curTypeId = typeSym->fields[fieldIdx].udtTypeId;
|
||||
} else {
|
||||
curTypeId = -1;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1383,18 +1471,18 @@ static void parseAssignOrCall(BasParserT *p) {
|
|||
// Check for UDT field access first
|
||||
if (sym != NULL && sym->dataType == BAS_TYPE_UDT && sym->udtTypeId >= 0) {
|
||||
emitLoad(p, sym);
|
||||
int32_t curTypeId = sym->udtTypeId;
|
||||
|
||||
// Walk the dot chain: a.b.c = expr
|
||||
// For intermediate fields, emit LOAD_FIELD (navigate into nested UDT).
|
||||
// For the final field, emit STORE_FIELD with the assigned value.
|
||||
while (check(p, TOK_DOT) && curTypeId >= 0) {
|
||||
advance(p); // consume DOT
|
||||
if (!check(p, TOK_IDENT)) {
|
||||
errorExpected(p, "field name");
|
||||
return;
|
||||
}
|
||||
BasSymbolT *typeSym = NULL;
|
||||
for (int32_t i = 0; i < p->sym.count; i++) {
|
||||
if (p->sym.symbols[i].kind == SYM_TYPE_DEF && p->sym.symbols[i].index == sym->udtTypeId) {
|
||||
typeSym = &p->sym.symbols[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
BasSymbolT *typeSym = findTypeDefById(p, curTypeId);
|
||||
if (typeSym == NULL) {
|
||||
error(p, "Unknown TYPE definition");
|
||||
return;
|
||||
|
|
@ -1407,12 +1495,28 @@ static void parseAssignOrCall(BasParserT *p) {
|
|||
return;
|
||||
}
|
||||
advance(p); // consume field name
|
||||
|
||||
// Check if this field is a nested UDT with more dots coming
|
||||
bool fieldIsUdt = (typeSym->fields[fieldIdx].dataType == BAS_TYPE_UDT);
|
||||
|
||||
if (fieldIsUdt && check(p, TOK_DOT)) {
|
||||
// Intermediate level: load this field and continue
|
||||
basEmit8(&p->cg, OP_LOAD_FIELD);
|
||||
basEmitU16(&p->cg, (uint16_t)fieldIdx);
|
||||
curTypeId = typeSym->fields[fieldIdx].udtTypeId;
|
||||
} else {
|
||||
// Final field: store value
|
||||
expect(p, TOK_EQ);
|
||||
parseExpression(p);
|
||||
basEmit8(&p->cg, OP_STORE_FIELD);
|
||||
basEmitU16(&p->cg, (uint16_t)fieldIdx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
error(p, "Expected '=' in UDT field assignment");
|
||||
return;
|
||||
}
|
||||
|
||||
// Control property/method access: CtrlName.Member
|
||||
// Emit: push current form ref, push ctrl name, FIND_CTRL
|
||||
|
|
@ -1549,6 +1653,34 @@ static void parseAssignOrCall(BasParserT *p) {
|
|||
}
|
||||
expect(p, TOK_RPAREN);
|
||||
|
||||
// Array-of-UDT field store: arr(i).field = expr
|
||||
if (sym->dataType == BAS_TYPE_UDT && sym->udtTypeId >= 0 && check(p, TOK_DOT)) {
|
||||
advance(p); // consume DOT
|
||||
if (!check(p, TOK_IDENT)) {
|
||||
errorExpected(p, "field name");
|
||||
return;
|
||||
}
|
||||
BasSymbolT *typeSym = findTypeDefById(p, sym->udtTypeId);
|
||||
if (typeSym == NULL) {
|
||||
error(p, "Unknown TYPE definition");
|
||||
return;
|
||||
}
|
||||
int32_t fieldIdx = resolveFieldIndex(typeSym, p->lex.token.text);
|
||||
if (fieldIdx < 0) {
|
||||
char buf[512];
|
||||
snprintf(buf, sizeof(buf), "Unknown field '%s' in TYPE '%s'", p->lex.token.text, typeSym->name);
|
||||
error(p, buf);
|
||||
return;
|
||||
}
|
||||
advance(p); // consume field name
|
||||
expect(p, TOK_EQ);
|
||||
parseExpression(p);
|
||||
basEmit8(&p->cg, OP_STORE_ARRAY_FIELD);
|
||||
basEmit8(&p->cg, (uint8_t)dims);
|
||||
basEmitU16(&p->cg, (uint16_t)fieldIdx);
|
||||
return;
|
||||
}
|
||||
|
||||
expect(p, TOK_EQ);
|
||||
parseExpression(p);
|
||||
|
||||
|
|
@ -1584,7 +1716,19 @@ static void parseAssignOrCall(BasParserT *p) {
|
|||
}
|
||||
|
||||
// Sub call without parens: SUBName arg1, arg2 ...
|
||||
if (sym != NULL && sym->kind == SYM_SUB) {
|
||||
// If the identifier is unknown, treat it as a forward-referenced sub.
|
||||
if (sym == NULL) {
|
||||
sym = basSymTabAdd(&p->sym, name, SYM_SUB, BAS_TYPE_INTEGER);
|
||||
if (sym == NULL) {
|
||||
error(p, "Symbol table full");
|
||||
return;
|
||||
}
|
||||
sym->scope = SCOPE_GLOBAL;
|
||||
sym->isDefined = false;
|
||||
sym->codeAddr = 0;
|
||||
}
|
||||
|
||||
if (sym->kind == SYM_SUB) {
|
||||
int32_t argc = 0;
|
||||
if (!check(p, TOK_NEWLINE) && !check(p, TOK_EOF) && !check(p, TOK_COLON) && !check(p, TOK_ELSE)) {
|
||||
if (argc < sym->paramCount && !sym->paramByVal[argc]) {
|
||||
|
|
@ -2358,20 +2502,24 @@ static void parseDim(BasParserT *p) {
|
|||
}
|
||||
|
||||
if (isArray) {
|
||||
// Emit array dimension instruction
|
||||
if (dt == BAS_TYPE_UDT && udtTypeId >= 0) {
|
||||
// For UDT arrays, push typeId and fieldCount so elements
|
||||
// can be properly initialized
|
||||
BasSymbolT *typeSym = findTypeDefById(p, udtTypeId);
|
||||
if (typeSym != NULL) {
|
||||
basEmit8(&p->cg, OP_PUSH_INT16);
|
||||
basEmit16(&p->cg, (int16_t)udtTypeId);
|
||||
basEmit8(&p->cg, OP_PUSH_INT16);
|
||||
basEmit16(&p->cg, (int16_t)typeSym->fieldCount);
|
||||
}
|
||||
}
|
||||
basEmit8(&p->cg, OP_DIM_ARRAY);
|
||||
basEmit8(&p->cg, (uint8_t)dims);
|
||||
basEmit8(&p->cg, dt);
|
||||
emitStore(p, sym);
|
||||
} else if (dt == BAS_TYPE_UDT && udtTypeId >= 0) {
|
||||
// Allocate a UDT instance
|
||||
BasSymbolT *typeSym = NULL;
|
||||
for (int32_t i = 0; i < p->sym.count; i++) {
|
||||
if (p->sym.symbols[i].kind == SYM_TYPE_DEF && p->sym.symbols[i].index == udtTypeId) {
|
||||
typeSym = &p->sym.symbols[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
BasSymbolT *typeSym = findTypeDefById(p, udtTypeId);
|
||||
if (typeSym != NULL) {
|
||||
basEmit8(&p->cg, OP_PUSH_INT16);
|
||||
basEmit16(&p->cg, (int16_t)udtTypeId);
|
||||
|
|
@ -2381,6 +2529,8 @@ static void parseDim(BasParserT *p) {
|
|||
basEmit8(&p->cg, OP_DIM_ARRAY);
|
||||
basEmit8(&p->cg, 0);
|
||||
basEmit8(&p->cg, BAS_TYPE_UDT);
|
||||
// Initialize nested UDT fields
|
||||
emitUdtInit(p, udtTypeId);
|
||||
emitStore(p, sym);
|
||||
}
|
||||
}
|
||||
|
|
@ -3118,6 +3268,21 @@ static void parseModule(BasParserT *p) {
|
|||
skipNewlines(p);
|
||||
}
|
||||
|
||||
// Check for unresolved forward references (skip externs from DECLARE LIBRARY)
|
||||
if (!p->hasError) {
|
||||
for (int32_t i = 0; i < p->sym.count; i++) {
|
||||
BasSymbolT *sym = &p->sym.symbols[i];
|
||||
|
||||
if ((sym->kind == SYM_SUB || sym->kind == SYM_FUNCTION) && !sym->isDefined && !sym->isExtern) {
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "Undefined %s: %s",
|
||||
sym->kind == SYM_SUB ? "Sub" : "Function", sym->name);
|
||||
error(p, buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// End of module -- emit HALT
|
||||
basEmit8(&p->cg, OP_HALT);
|
||||
}
|
||||
|
|
@ -4252,6 +4417,115 @@ static void parseStatement(BasParserT *p) {
|
|||
basEmit8(&p->cg, OP_POP);
|
||||
break;
|
||||
|
||||
case TOK_ME: {
|
||||
// Me.Show / Me.Hide / Me.CtrlName.Property = expr
|
||||
advance(p); // consume Me
|
||||
if (!check(p, TOK_DOT)) {
|
||||
errorExpected(p, "'.' after Me");
|
||||
break;
|
||||
}
|
||||
advance(p); // consume DOT
|
||||
|
||||
if (!check(p, TOK_IDENT) && !check(p, TOK_SHOW) && !check(p, TOK_HIDE)) {
|
||||
errorExpected(p, "method or member name after Me.");
|
||||
break;
|
||||
}
|
||||
|
||||
char meMember[BAS_MAX_TOKEN_LEN];
|
||||
strncpy(meMember, p->lex.token.text, BAS_MAX_TOKEN_LEN - 1);
|
||||
meMember[BAS_MAX_TOKEN_LEN - 1] = '\0';
|
||||
advance(p);
|
||||
|
||||
if (strcasecmp(meMember, "Show") == 0) {
|
||||
// Me.Show [modal]
|
||||
basEmit8(&p->cg, OP_ME_REF);
|
||||
uint8_t modal = 0;
|
||||
if (check(p, TOK_INT_LIT)) {
|
||||
if (p->lex.token.intVal != 0) {
|
||||
modal = 1;
|
||||
}
|
||||
advance(p);
|
||||
}
|
||||
basEmit8(&p->cg, OP_SHOW_FORM);
|
||||
basEmit8(&p->cg, modal);
|
||||
} else if (strcasecmp(meMember, "Hide") == 0) {
|
||||
// Me.Hide
|
||||
basEmit8(&p->cg, OP_ME_REF);
|
||||
basEmit8(&p->cg, OP_HIDE_FORM);
|
||||
} else if (check(p, TOK_DOT)) {
|
||||
// Me.CtrlName.Property = expr (control access via Me)
|
||||
advance(p); // consume second DOT
|
||||
if (!check(p, TOK_IDENT)) {
|
||||
errorExpected(p, "property name");
|
||||
break;
|
||||
}
|
||||
char propName[BAS_MAX_TOKEN_LEN];
|
||||
strncpy(propName, p->lex.token.text, BAS_MAX_TOKEN_LEN - 1);
|
||||
propName[BAS_MAX_TOKEN_LEN - 1] = '\0';
|
||||
advance(p);
|
||||
|
||||
// Push form ref (Me), ctrl name, FIND_CTRL
|
||||
basEmit8(&p->cg, OP_ME_REF);
|
||||
uint16_t ctrlIdx = basAddConstant(&p->cg, meMember, (int32_t)strlen(meMember));
|
||||
basEmit8(&p->cg, OP_PUSH_STR);
|
||||
basEmitU16(&p->cg, ctrlIdx);
|
||||
basEmit8(&p->cg, OP_FIND_CTRL);
|
||||
|
||||
if (check(p, TOK_EQ)) {
|
||||
// Property assignment
|
||||
advance(p);
|
||||
uint16_t propIdx = basAddConstant(&p->cg, propName, (int32_t)strlen(propName));
|
||||
basEmit8(&p->cg, OP_PUSH_STR);
|
||||
basEmitU16(&p->cg, propIdx);
|
||||
parseExpression(p);
|
||||
basEmit8(&p->cg, OP_STORE_PROP);
|
||||
} else {
|
||||
// Method call
|
||||
uint16_t methodIdx = basAddConstant(&p->cg, propName, (int32_t)strlen(propName));
|
||||
basEmit8(&p->cg, OP_PUSH_STR);
|
||||
basEmitU16(&p->cg, methodIdx);
|
||||
int32_t argc = 0;
|
||||
while (!check(p, TOK_NEWLINE) && !check(p, TOK_COLON) && !check(p, TOK_EOF) && !check(p, TOK_ELSE)) {
|
||||
if (argc > 0 && check(p, TOK_COMMA)) {
|
||||
advance(p);
|
||||
}
|
||||
parseExpression(p);
|
||||
argc++;
|
||||
}
|
||||
basEmit8(&p->cg, OP_CALL_METHOD);
|
||||
basEmit8(&p->cg, (uint8_t)argc);
|
||||
basEmit8(&p->cg, OP_POP);
|
||||
}
|
||||
} else if (check(p, TOK_EQ)) {
|
||||
// Me.Property = expr (form-level property set)
|
||||
advance(p); // consume =
|
||||
basEmit8(&p->cg, OP_ME_REF);
|
||||
uint16_t propIdx = basAddConstant(&p->cg, meMember, (int32_t)strlen(meMember));
|
||||
basEmit8(&p->cg, OP_PUSH_STR);
|
||||
basEmitU16(&p->cg, propIdx);
|
||||
parseExpression(p);
|
||||
basEmit8(&p->cg, OP_STORE_PROP);
|
||||
} else {
|
||||
// Me.Method [args] (form-level method call)
|
||||
basEmit8(&p->cg, OP_ME_REF);
|
||||
uint16_t methodIdx = basAddConstant(&p->cg, meMember, (int32_t)strlen(meMember));
|
||||
basEmit8(&p->cg, OP_PUSH_STR);
|
||||
basEmitU16(&p->cg, methodIdx);
|
||||
int32_t argc = 0;
|
||||
while (!check(p, TOK_NEWLINE) && !check(p, TOK_COLON) && !check(p, TOK_EOF) && !check(p, TOK_ELSE)) {
|
||||
if (argc > 0 && check(p, TOK_COMMA)) {
|
||||
advance(p);
|
||||
}
|
||||
parseExpression(p);
|
||||
argc++;
|
||||
}
|
||||
basEmit8(&p->cg, OP_CALL_METHOD);
|
||||
basEmit8(&p->cg, (uint8_t)argc);
|
||||
basEmit8(&p->cg, OP_POP);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TOK_LET:
|
||||
advance(p); // consume LET, then fall through to assignment
|
||||
if (!check(p, TOK_IDENT)) {
|
||||
|
|
|
|||
|
|
@ -898,7 +898,10 @@ void basFormRtShowForm(void *ctx, void *formRef, bool modal) {
|
|||
}
|
||||
|
||||
form->window->visible = true;
|
||||
|
||||
if (form->frmAutoSize) {
|
||||
dvxFitWindow(rt->ctx, form->window);
|
||||
}
|
||||
|
||||
if (modal) {
|
||||
rt->ctx->modalWindow = form->window;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -469,7 +469,9 @@ void prjRebuildTree(PrjStateT *prj) {
|
|||
|
||||
for (int32_t i = 0; i < prj->fileCount; i++) {
|
||||
if (prj->files[i].isForm) {
|
||||
char *label = strdup(prj->files[i].path);
|
||||
char buf[DVX_MAX_PATH + 4];
|
||||
snprintf(buf, sizeof(buf), "%s%s", prj->files[i].path, prj->files[i].modified ? " *" : "");
|
||||
char *label = strdup(buf);
|
||||
arrput(sLabels, label);
|
||||
WidgetT *item = wgtTreeItem(formsNode, label);
|
||||
item->userData = (void *)(intptr_t)i;
|
||||
|
|
@ -486,7 +488,9 @@ void prjRebuildTree(PrjStateT *prj) {
|
|||
|
||||
for (int32_t i = 0; i < prj->fileCount; i++) {
|
||||
if (!prj->files[i].isForm) {
|
||||
char *label = strdup(prj->files[i].path);
|
||||
char buf[DVX_MAX_PATH + 4];
|
||||
snprintf(buf, sizeof(buf), "%s%s", prj->files[i].path, prj->files[i].modified ? " *" : "");
|
||||
char *label = strdup(buf);
|
||||
arrput(sLabels, label);
|
||||
WidgetT *item = wgtTreeItem(modsNode, label);
|
||||
item->userData = (void *)(intptr_t)i;
|
||||
|
|
|
|||
|
|
@ -1811,6 +1811,24 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
break;
|
||||
}
|
||||
|
||||
// For UDT arrays, parser pushes typeId and fieldCount after bounds
|
||||
int32_t udtTypeId = -1;
|
||||
int32_t udtFieldCnt = 0;
|
||||
|
||||
if (elementType == BAS_TYPE_UDT) {
|
||||
BasValueT fcVal;
|
||||
BasValueT tiVal;
|
||||
|
||||
if (!pop(vm, &fcVal) || !pop(vm, &tiVal)) {
|
||||
return BAS_VM_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
udtFieldCnt = (int32_t)basValToNumber(fcVal);
|
||||
udtTypeId = (int32_t)basValToNumber(tiVal);
|
||||
basValRelease(&fcVal);
|
||||
basValRelease(&tiVal);
|
||||
}
|
||||
|
||||
// Normal array allocation: parser pushes (lbound, ubound) pairs per dim
|
||||
int32_t lbounds[BAS_ARRAY_MAX_DIMS];
|
||||
int32_t ubounds[BAS_ARRAY_MAX_DIMS];
|
||||
|
|
@ -1837,6 +1855,22 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
return BAS_VM_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Initialize UDT array elements with proper UDT instances
|
||||
if (elementType == BAS_TYPE_UDT && udtTypeId >= 0) {
|
||||
for (int32_t i = 0; i < arr->totalElements; i++) {
|
||||
BasUdtT *udt = basUdtNew(udtTypeId, udtFieldCnt);
|
||||
|
||||
if (!udt) {
|
||||
basArrayFree(arr);
|
||||
runtimeError(vm, 7, "Out of memory allocating TYPE array elements");
|
||||
return BAS_VM_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
arr->elements[i].type = BAS_TYPE_UDT;
|
||||
arr->elements[i].udtVal = udt;
|
||||
}
|
||||
}
|
||||
|
||||
BasValueT arrVal;
|
||||
arrVal.type = BAS_TYPE_ARRAY;
|
||||
arrVal.arrVal = arr;
|
||||
|
|
@ -1952,6 +1986,79 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
break;
|
||||
}
|
||||
|
||||
case OP_STORE_ARRAY_FIELD: {
|
||||
uint8_t dims = readUint8(vm);
|
||||
uint16_t fieldIdx = readUint16(vm);
|
||||
|
||||
// Pop value to store
|
||||
BasValueT storeVal;
|
||||
|
||||
if (!pop(vm, &storeVal)) {
|
||||
return BAS_VM_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
// Pop indices in reverse order
|
||||
int32_t indices[BAS_ARRAY_MAX_DIMS];
|
||||
|
||||
for (int32_t d = dims - 1; d >= 0; d--) {
|
||||
BasValueT idxVal;
|
||||
|
||||
if (!pop(vm, &idxVal)) {
|
||||
basValRelease(&storeVal);
|
||||
return BAS_VM_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
indices[d] = (int32_t)basValToNumber(idxVal);
|
||||
basValRelease(&idxVal);
|
||||
}
|
||||
|
||||
// Pop array reference
|
||||
BasValueT arrRef;
|
||||
|
||||
if (!pop(vm, &arrRef)) {
|
||||
basValRelease(&storeVal);
|
||||
return BAS_VM_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
if (arrRef.type != BAS_TYPE_ARRAY || !arrRef.arrVal) {
|
||||
basValRelease(&arrRef);
|
||||
basValRelease(&storeVal);
|
||||
runtimeError(vm, 13, "Not an array");
|
||||
return BAS_VM_TYPE_MISMATCH;
|
||||
}
|
||||
|
||||
int32_t flatIdx = basArrayIndex(arrRef.arrVal, indices, dims);
|
||||
|
||||
if (flatIdx < 0) {
|
||||
basValRelease(&arrRef);
|
||||
basValRelease(&storeVal);
|
||||
runtimeError(vm, 9, "Subscript out of range");
|
||||
return BAS_VM_SUBSCRIPT_RANGE;
|
||||
}
|
||||
|
||||
// Element must be a UDT
|
||||
BasValueT *elem = &arrRef.arrVal->elements[flatIdx];
|
||||
|
||||
if (elem->type != BAS_TYPE_UDT || !elem->udtVal) {
|
||||
basValRelease(&arrRef);
|
||||
basValRelease(&storeVal);
|
||||
runtimeError(vm, 13, "Array element is not a TYPE instance");
|
||||
return BAS_VM_TYPE_MISMATCH;
|
||||
}
|
||||
|
||||
if (fieldIdx >= (uint16_t)elem->udtVal->fieldCount) {
|
||||
basValRelease(&arrRef);
|
||||
basValRelease(&storeVal);
|
||||
runtimeError(vm, 9, "Invalid field index");
|
||||
return BAS_VM_ERROR;
|
||||
}
|
||||
|
||||
basValRelease(&elem->udtVal->fields[fieldIdx]);
|
||||
elem->udtVal->fields[fieldIdx] = storeVal;
|
||||
basValRelease(&arrRef);
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_REDIM: {
|
||||
uint8_t dims = readUint8(vm);
|
||||
uint8_t preserve = readUint8(vm);
|
||||
|
|
|
|||
|
|
@ -1629,14 +1629,649 @@ int main(void) {
|
|||
// Expected: 3 / 42 / 3 / 3 / [ 42]
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Me keyword
|
||||
// Coverage: Me keyword (compilation only -- no form context)
|
||||
// ============================================================
|
||||
|
||||
runProgram("Me keyword",
|
||||
"' Me compiles to OP_ME_REF (returns NULL outside form context)\n"
|
||||
runProgram("Me keyword compiles",
|
||||
"' Me compiles to OP_ME_REF\n"
|
||||
"PRINT \"ok\"\n"
|
||||
);
|
||||
// Expected: ok (just verify Me doesn't crash compilation)
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Me.Show / Me.Hide as statements
|
||||
// ============================================================
|
||||
|
||||
{
|
||||
printf("=== Me.Show / Me.Hide statements ===\n");
|
||||
|
||||
// These compile -- runtime needs a form context to actually show/hide
|
||||
const char *src =
|
||||
"Sub Form1_Load ()\n"
|
||||
" Me.Show\n"
|
||||
" Me.Hide\n"
|
||||
" PRINT \"me ok\"\n"
|
||||
"End Sub\n";
|
||||
|
||||
int32_t len = (int32_t)strlen(src);
|
||||
BasParserT parser;
|
||||
basParserInit(&parser, src, len);
|
||||
|
||||
if (!basParse(&parser)) {
|
||||
printf("COMPILE ERROR: %s\n\n", parser.error);
|
||||
basParserFree(&parser);
|
||||
} else {
|
||||
BasModuleT *mod = basParserBuildModule(&parser);
|
||||
basParserFree(&parser);
|
||||
|
||||
if (mod) {
|
||||
printf("Compiled OK, proc=%s\n", mod->procs[0].name);
|
||||
basModuleFree(mod);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Me keyword with form context
|
||||
// ============================================================
|
||||
{
|
||||
printf("=== Me keyword with form context ===\n");
|
||||
|
||||
const char *src =
|
||||
"Sub Form1_Load ()\n"
|
||||
" PRINT \"load fired\"\n"
|
||||
"End Sub\n"
|
||||
"\n"
|
||||
"Sub Command1_Click ()\n"
|
||||
" PRINT \"click fired\"\n"
|
||||
"End Sub\n"
|
||||
"\n"
|
||||
"Sub Text1_KeyPress (KeyAscii As Integer)\n"
|
||||
" PRINT \"key:\"; KeyAscii\n"
|
||||
"End Sub\n"
|
||||
"\n"
|
||||
"Sub Picture1_MouseDown (Button As Integer, X As Integer, Y As Integer)\n"
|
||||
" PRINT \"mouse:\"; Button; X; Y\n"
|
||||
"End Sub\n";
|
||||
|
||||
int32_t len = (int32_t)strlen(src);
|
||||
BasParserT parser;
|
||||
basParserInit(&parser, src, len);
|
||||
|
||||
if (!basParse(&parser)) {
|
||||
printf("COMPILE ERROR: %s\n\n", parser.error);
|
||||
basParserFree(&parser);
|
||||
} else {
|
||||
BasModuleT *mod = basParserBuildModule(&parser);
|
||||
basParserFree(&parser);
|
||||
|
||||
if (!mod) {
|
||||
printf("MODULE BUILD FAILED\n\n");
|
||||
} else {
|
||||
BasVmT *vm = basVmCreate();
|
||||
basVmLoadModule(vm, mod);
|
||||
vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount;
|
||||
vm->callDepth = 1;
|
||||
|
||||
// Test: find and call Form1_Load (no args)
|
||||
const BasProcEntryT *loadProc = basModuleFindProc(mod, "Form1_Load");
|
||||
|
||||
if (loadProc) {
|
||||
basVmCallSub(vm, loadProc->codeAddr);
|
||||
} else {
|
||||
printf("Form1_Load not found!\n");
|
||||
}
|
||||
|
||||
// Test: find and call Command1_Click (no args)
|
||||
const BasProcEntryT *clickProc = basModuleFindProc(mod, "Command1_Click");
|
||||
|
||||
if (clickProc) {
|
||||
basVmCallSub(vm, clickProc->codeAddr);
|
||||
} else {
|
||||
printf("Command1_Click not found!\n");
|
||||
}
|
||||
|
||||
// Test: find and call Text1_KeyPress with parameter
|
||||
const BasProcEntryT *keyProc = basModuleFindProc(mod, "Text1_KeyPress");
|
||||
|
||||
if (keyProc) {
|
||||
BasValueT args[1];
|
||||
args[0] = basValLong(65); // 'A'
|
||||
basVmCallSubWithArgs(vm, keyProc->codeAddr, args, 1);
|
||||
} else {
|
||||
printf("Text1_KeyPress not found!\n");
|
||||
}
|
||||
|
||||
// Test: find and call Picture1_MouseDown with 3 parameters
|
||||
const BasProcEntryT *mouseProc = basModuleFindProc(mod, "Picture1_MouseDown");
|
||||
|
||||
if (mouseProc) {
|
||||
BasValueT args[3];
|
||||
args[0] = basValLong(1); // left button
|
||||
args[1] = basValLong(50); // X
|
||||
args[2] = basValLong(100); // Y
|
||||
basVmCallSubWithArgs(vm, mouseProc->codeAddr, args, 3);
|
||||
} else {
|
||||
printf("Picture1_MouseDown not found!\n");
|
||||
}
|
||||
|
||||
// Test: verify proc table
|
||||
printf("Proc count: %d\n", (int)mod->procCount);
|
||||
|
||||
for (int32_t i = 0; i < mod->procCount; i++) {
|
||||
printf(" [%d] %s (params=%d)\n", (int)i, mod->procs[i].name, (int)mod->procs[i].paramCount);
|
||||
}
|
||||
|
||||
basVmDestroy(vm);
|
||||
basModuleFree(mod);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Event parameter mismatch
|
||||
// ============================================================
|
||||
|
||||
{
|
||||
printf("=== Event parameter mismatch ===\n");
|
||||
|
||||
const char *src =
|
||||
"Sub NoParams ()\n"
|
||||
" PRINT \"no params ok\"\n"
|
||||
"End Sub\n"
|
||||
"\n"
|
||||
"Sub OneParam (X As Integer)\n"
|
||||
" PRINT \"X =\"; X\n"
|
||||
"End Sub\n"
|
||||
"\n"
|
||||
"Sub TwoParams (A As Integer, B As Integer)\n"
|
||||
" PRINT \"A =\"; A; \"B =\"; B\n"
|
||||
"End Sub\n";
|
||||
|
||||
int32_t len = (int32_t)strlen(src);
|
||||
BasParserT parser;
|
||||
basParserInit(&parser, src, len);
|
||||
|
||||
if (!basParse(&parser)) {
|
||||
printf("COMPILE ERROR: %s\n\n", parser.error);
|
||||
basParserFree(&parser);
|
||||
} else {
|
||||
BasModuleT *mod = basParserBuildModule(&parser);
|
||||
basParserFree(&parser);
|
||||
|
||||
if (mod) {
|
||||
BasVmT *vm = basVmCreate();
|
||||
basVmLoadModule(vm, mod);
|
||||
vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount;
|
||||
vm->callDepth = 1;
|
||||
|
||||
// Call NoParams with no args
|
||||
const BasProcEntryT *p = basModuleFindProc(mod, "NoParams");
|
||||
|
||||
if (p) {
|
||||
basVmCallSub(vm, p->codeAddr);
|
||||
}
|
||||
|
||||
// Call OneParam with 1 arg
|
||||
p = basModuleFindProc(mod, "OneParam");
|
||||
|
||||
if (p) {
|
||||
BasValueT args[1];
|
||||
args[0] = basValLong(42);
|
||||
basVmCallSubWithArgs(vm, p->codeAddr, args, 1);
|
||||
}
|
||||
|
||||
// Call TwoParams with 2 args
|
||||
p = basModuleFindProc(mod, "TwoParams");
|
||||
|
||||
if (p) {
|
||||
BasValueT args[2];
|
||||
args[0] = basValLong(10);
|
||||
args[1] = basValLong(20);
|
||||
basVmCallSubWithArgs(vm, p->codeAddr, args, 2);
|
||||
}
|
||||
|
||||
basVmDestroy(vm);
|
||||
basModuleFree(mod);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Procedure extraction from source
|
||||
// ============================================================
|
||||
|
||||
runProgram("Mixed module-level and procedures",
|
||||
"DIM x As Integer\n"
|
||||
"x = 10\n"
|
||||
"PRINT \"module:\"; x\n"
|
||||
"\n"
|
||||
"Sub Helper ()\n"
|
||||
" PRINT \"helper called\"\n"
|
||||
"End Sub\n"
|
||||
"\n"
|
||||
"Function Add (a As Integer, b As Integer) As Integer\n"
|
||||
" Add = a + b\n"
|
||||
"End Function\n"
|
||||
"\n"
|
||||
"CALL Helper\n"
|
||||
"PRINT \"sum:\"; Add(3, 4)\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: ? shortcut for PRINT
|
||||
// ============================================================
|
||||
|
||||
runProgram("? shortcut for PRINT",
|
||||
"? \"hello\"\n"
|
||||
"? 1 + 2\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Code in .frm format (Sub after form definition)
|
||||
// ============================================================
|
||||
|
||||
{
|
||||
printf("=== FRM code extraction ===\n");
|
||||
|
||||
// Simulate what the IDE does: code section from a .frm
|
||||
const char *frmCode =
|
||||
"Sub Form1_Load ()\n"
|
||||
" PRINT \"form loaded\"\n"
|
||||
"End Sub\n"
|
||||
"\n"
|
||||
"Sub Command1_Click ()\n"
|
||||
" PRINT \"button clicked\"\n"
|
||||
"End Sub\n";
|
||||
|
||||
int32_t len = (int32_t)strlen(frmCode);
|
||||
BasParserT parser;
|
||||
basParserInit(&parser, frmCode, len);
|
||||
|
||||
if (!basParse(&parser)) {
|
||||
printf("COMPILE ERROR: %s\n\n", parser.error);
|
||||
basParserFree(&parser);
|
||||
} else {
|
||||
BasModuleT *mod = basParserBuildModule(&parser);
|
||||
basParserFree(&parser);
|
||||
|
||||
if (mod) {
|
||||
BasVmT *vm = basVmCreate();
|
||||
basVmLoadModule(vm, mod);
|
||||
vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount;
|
||||
vm->callDepth = 1;
|
||||
|
||||
// Fire both events
|
||||
const BasProcEntryT *p1 = basModuleFindProc(mod, "Form1_Load");
|
||||
const BasProcEntryT *p2 = basModuleFindProc(mod, "Command1_Click");
|
||||
|
||||
if (p1) { basVmCallSub(vm, p1->codeAddr); }
|
||||
else { printf("Form1_Load NOT FOUND\n"); }
|
||||
|
||||
if (p2) { basVmCallSub(vm, p2->codeAddr); }
|
||||
else { printf("Command1_Click NOT FOUND\n"); }
|
||||
|
||||
basVmDestroy(vm);
|
||||
basModuleFree(mod);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Coverage: RANDOMIZE TIMER
|
||||
// ============================================================
|
||||
|
||||
runProgram("RANDOMIZE TIMER",
|
||||
"RANDOMIZE TIMER\n"
|
||||
"DIM x AS SINGLE\n"
|
||||
"x = RND\n"
|
||||
"PRINT \"rnd ok\"\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Forward GOSUB
|
||||
// ============================================================
|
||||
|
||||
runProgram("Forward GOSUB",
|
||||
"GOSUB doWork\n"
|
||||
"PRINT \"back\"\n"
|
||||
"END\n"
|
||||
"doWork:\n"
|
||||
"PRINT \"working\"\n"
|
||||
"RETURN\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Forward GOTO
|
||||
// ============================================================
|
||||
|
||||
runProgram("Forward GOTO to label",
|
||||
"GOTO skip\n"
|
||||
"PRINT \"should not print\"\n"
|
||||
"skip:\n"
|
||||
"PRINT \"jumped\"\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Array of UDT
|
||||
// ============================================================
|
||||
|
||||
runProgram("Array of UDT",
|
||||
"TYPE PointT\n"
|
||||
" x AS INTEGER\n"
|
||||
" y AS INTEGER\n"
|
||||
"END TYPE\n"
|
||||
"\n"
|
||||
"DIM pts(3) AS PointT\n"
|
||||
"pts(1).x = 10\n"
|
||||
"pts(1).y = 20\n"
|
||||
"pts(2).x = 30\n"
|
||||
"pts(2).y = 40\n"
|
||||
"PRINT pts(1).x; pts(1).y\n"
|
||||
"PRINT pts(2).x; pts(2).y\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Nested UDTs
|
||||
// ============================================================
|
||||
|
||||
runProgram("Nested UDT",
|
||||
"TYPE AddressT\n"
|
||||
" city AS STRING\n"
|
||||
" zip AS INTEGER\n"
|
||||
"END TYPE\n"
|
||||
"\n"
|
||||
"TYPE PersonT\n"
|
||||
" name AS STRING\n"
|
||||
" addr AS AddressT\n"
|
||||
"END TYPE\n"
|
||||
"\n"
|
||||
"DIM p AS PersonT\n"
|
||||
"p.name = \"Alice\"\n"
|
||||
"p.addr.city = \"NYC\"\n"
|
||||
"p.addr.zip = 10001\n"
|
||||
"PRINT p.name\n"
|
||||
"PRINT p.addr.city\n"
|
||||
"PRINT p.addr.zip\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Operator precedence stress
|
||||
// ============================================================
|
||||
|
||||
runProgram("Operator precedence",
|
||||
"' NOT has highest unary, then AND, OR, XOR, EQV, IMP\n"
|
||||
"PRINT NOT 0 AND -1\n" // NOT 0 = -1, -1 AND -1 = -1
|
||||
"PRINT (1 > 0) AND (2 > 1)\n" // True AND True = -1
|
||||
"PRINT (1 > 0) OR (2 < 1)\n" // True OR False = -1
|
||||
"PRINT 5 AND 3\n" // bitwise: 1
|
||||
"PRINT 5 OR 3\n" // bitwise: 7
|
||||
"PRINT 5 XOR 3\n" // bitwise: 6
|
||||
"PRINT 2 + 3 * 4 - 1\n" // 2 + 12 - 1 = 13
|
||||
"PRINT (2 + 3) * (4 - 1)\n" // 5 * 3 = 15
|
||||
"PRINT -2 ^ 2\n" // -(2^2) = -4 (VB precedence)
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Nested FOR with EXIT
|
||||
// ============================================================
|
||||
|
||||
runProgram("Nested FOR with EXIT FOR",
|
||||
"DIM i AS INTEGER\n"
|
||||
"DIM j AS INTEGER\n"
|
||||
"FOR i = 1 TO 3\n"
|
||||
" FOR j = 1 TO 3\n"
|
||||
" IF j = 2 THEN EXIT FOR\n"
|
||||
" PRINT i; j\n"
|
||||
" NEXT j\n"
|
||||
"NEXT i\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Forward reference CALL (sub defined after use)
|
||||
// ============================================================
|
||||
|
||||
runProgram("Forward CALL reference",
|
||||
"CALL doWork\n"
|
||||
"PRINT \"after call\"\n"
|
||||
"\n"
|
||||
"Sub doWork ()\n"
|
||||
" PRINT \"in sub\"\n"
|
||||
"End Sub\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Multiple CONST declarations
|
||||
// ============================================================
|
||||
|
||||
runProgram("Multiple CONST",
|
||||
"CONST PI = 3.14159\n"
|
||||
"CONST E = 2.71828\n"
|
||||
"CONST NAME = \"DVX\"\n"
|
||||
"PRINT INT(PI * 100)\n"
|
||||
"PRINT INT(E * 100)\n"
|
||||
"PRINT NAME\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: SELECT CASE with strings
|
||||
// ============================================================
|
||||
|
||||
runProgram("SELECT CASE strings",
|
||||
"DIM s AS STRING\n"
|
||||
"s = \"hello\"\n"
|
||||
"SELECT CASE s\n"
|
||||
" CASE \"world\"\n"
|
||||
" PRINT \"wrong\"\n"
|
||||
" CASE \"hello\"\n"
|
||||
" PRINT \"right\"\n"
|
||||
" CASE ELSE\n"
|
||||
" PRINT \"default\"\n"
|
||||
"END SELECT\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Nested IF/ELSEIF chains
|
||||
// ============================================================
|
||||
|
||||
runProgram("Nested IF chains",
|
||||
"DIM x AS INTEGER\n"
|
||||
"x = 50\n"
|
||||
"IF x > 90 THEN\n"
|
||||
" PRINT \"A\"\n"
|
||||
"ELSEIF x > 80 THEN\n"
|
||||
" PRINT \"B\"\n"
|
||||
"ELSEIF x > 70 THEN\n"
|
||||
" PRINT \"C\"\n"
|
||||
"ELSEIF x > 60 THEN\n"
|
||||
" PRINT \"D\"\n"
|
||||
"ELSE\n"
|
||||
" PRINT \"F\"\n"
|
||||
"END IF\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: DO WHILE with EXIT DO
|
||||
// ============================================================
|
||||
|
||||
runProgram("DO WHILE with EXIT DO",
|
||||
"DIM i AS INTEGER\n"
|
||||
"i = 0\n"
|
||||
"DO WHILE i < 100\n"
|
||||
" i = i + 1\n"
|
||||
" IF i = 5 THEN EXIT DO\n"
|
||||
"LOOP\n"
|
||||
"PRINT i\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Recursive Fibonacci
|
||||
// ============================================================
|
||||
|
||||
runProgram("Recursive Fibonacci",
|
||||
"Function Fib (n As Integer) As Integer\n"
|
||||
" IF n <= 1 THEN\n"
|
||||
" Fib = n\n"
|
||||
" ELSE\n"
|
||||
" Fib = Fib(n - 1) + Fib(n - 2)\n"
|
||||
" END IF\n"
|
||||
"End Function\n"
|
||||
"\n"
|
||||
"PRINT Fib(0); Fib(1); Fib(5); Fib(10)\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: String comparison operators
|
||||
// ============================================================
|
||||
|
||||
runProgram("String comparison ops",
|
||||
"IF \"abc\" < \"def\" THEN PRINT \"lt ok\"\n"
|
||||
"IF \"abc\" <= \"abc\" THEN PRINT \"le ok\"\n"
|
||||
"IF \"def\" > \"abc\" THEN PRINT \"gt ok\"\n"
|
||||
"IF \"abc\" >= \"abc\" THEN PRINT \"ge ok\"\n"
|
||||
"IF \"abc\" <> \"def\" THEN PRINT \"ne ok\"\n"
|
||||
"IF \"abc\" = \"abc\" THEN PRINT \"eq ok\"\n"
|
||||
);
|
||||
|
||||
// ============================================================
|
||||
// Coverage: LOAD/UNLOAD/Me statements compile
|
||||
// ============================================================
|
||||
|
||||
{
|
||||
printf("=== Form statements compile ===\n");
|
||||
|
||||
const char *src =
|
||||
"Sub Form1_Load ()\n"
|
||||
" Me.Show\n"
|
||||
" Me.Hide\n"
|
||||
" Load frmOther\n"
|
||||
" Unload frmOther\n"
|
||||
"End Sub\n";
|
||||
|
||||
int32_t len = (int32_t)strlen(src);
|
||||
BasParserT parser;
|
||||
basParserInit(&parser, src, len);
|
||||
|
||||
if (!basParse(&parser)) {
|
||||
printf("COMPILE ERROR: %s\n", parser.error);
|
||||
basParserFree(&parser);
|
||||
} else {
|
||||
BasModuleT *mod = basParserBuildModule(&parser);
|
||||
basParserFree(&parser);
|
||||
|
||||
if (mod) {
|
||||
printf("OK: %d procs, %d bytes\n", (int)mod->procCount, (int)mod->codeLen);
|
||||
basModuleFree(mod);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Me.Property assignment compiles
|
||||
// ============================================================
|
||||
|
||||
{
|
||||
printf("=== Me.Property assignment ===\n");
|
||||
|
||||
const char *src =
|
||||
"Sub Form1_Load ()\n"
|
||||
" Me.Caption = \"Hello\"\n"
|
||||
" Me.Visible = 1\n"
|
||||
"End Sub\n";
|
||||
|
||||
int32_t len = (int32_t)strlen(src);
|
||||
BasParserT parser;
|
||||
basParserInit(&parser, src, len);
|
||||
|
||||
if (!basParse(&parser)) {
|
||||
printf("COMPILE ERROR: %s\n", parser.error);
|
||||
basParserFree(&parser);
|
||||
} else {
|
||||
BasModuleT *mod = basParserBuildModule(&parser);
|
||||
basParserFree(&parser);
|
||||
|
||||
if (mod) {
|
||||
printf("OK\n");
|
||||
basModuleFree(mod);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Coverage: Me.Control.Property compiles
|
||||
// ============================================================
|
||||
|
||||
{
|
||||
printf("=== Me.Control.Property ===\n");
|
||||
|
||||
const char *src =
|
||||
"Sub Form1_Load ()\n"
|
||||
" Me.Text1.Text = \"hello\"\n"
|
||||
" Me.Label1.Caption = \"world\"\n"
|
||||
"End Sub\n";
|
||||
|
||||
int32_t len = (int32_t)strlen(src);
|
||||
BasParserT parser;
|
||||
basParserInit(&parser, src, len);
|
||||
|
||||
if (!basParse(&parser)) {
|
||||
printf("COMPILE ERROR: %s\n", parser.error);
|
||||
basParserFree(&parser);
|
||||
} else {
|
||||
BasModuleT *mod = basParserBuildModule(&parser);
|
||||
basParserFree(&parser);
|
||||
|
||||
if (mod) {
|
||||
printf("OK\n");
|
||||
basModuleFree(mod);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Coverage: DOEVENTS compiles
|
||||
// ============================================================
|
||||
|
||||
{
|
||||
printf("=== DOEVENTS compiles ===\n");
|
||||
|
||||
const char *src =
|
||||
"Sub Form1_Load ()\n"
|
||||
" DoEvents\n"
|
||||
"End Sub\n";
|
||||
|
||||
int32_t len = (int32_t)strlen(src);
|
||||
BasParserT parser;
|
||||
basParserInit(&parser, src, len);
|
||||
|
||||
if (!basParse(&parser)) {
|
||||
printf("COMPILE ERROR: %s\n", parser.error);
|
||||
basParserFree(&parser);
|
||||
} else {
|
||||
BasModuleT *mod = basParserBuildModule(&parser);
|
||||
basParserFree(&parser);
|
||||
|
||||
if (mod) {
|
||||
printf("OK\n");
|
||||
basModuleFree(mod);
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
printf("All tests complete.\n");
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -2893,7 +2893,7 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
|
||||
|
||||
if (win->widgetRoot) {
|
||||
// Find currently focused widget
|
||||
// Find the currently focused widget
|
||||
WidgetT *current = NULL;
|
||||
WidgetT **fstack = NULL;
|
||||
arrput(fstack, win->widgetRoot);
|
||||
|
|
@ -2903,11 +2903,6 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
arrsetlen(fstack, arrlen(fstack) - 1);
|
||||
|
||||
if (w->focused && widgetIsFocusable(w->type)) {
|
||||
if (w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
|
||||
current = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
current = w;
|
||||
break;
|
||||
}
|
||||
|
|
@ -2919,30 +2914,10 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
}
|
||||
}
|
||||
|
||||
// Terminal swallowed Tab -- send to widget system instead
|
||||
if (current == NULL) {
|
||||
arrsetlen(fstack, 0);
|
||||
arrput(fstack, win->widgetRoot);
|
||||
bool termFocused = false;
|
||||
|
||||
while (arrlen(fstack) > 0) {
|
||||
WidgetT *w = fstack[arrlen(fstack) - 1];
|
||||
arrsetlen(fstack, arrlen(fstack) - 1);
|
||||
|
||||
if (w->focused && w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
|
||||
termFocused = true;
|
||||
break;
|
||||
}
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (c->visible) {
|
||||
arrput(fstack, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (termFocused) {
|
||||
// Terminal has focus -- send Tab to it
|
||||
// If the focused widget wants Tab, send it there
|
||||
// instead of cycling focus.
|
||||
if (current && (current->swallowTab ||
|
||||
(current->wclass && (current->wclass->flags & WCLASS_SWALLOWS_TAB)))) {
|
||||
if (win->onKey) {
|
||||
WIN_CALLBACK(ctx, win, win->onKey(win, ascii ? ascii : (scancode | 0x100), shiftFlags));
|
||||
}
|
||||
|
|
@ -2950,7 +2925,6 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
arrfree(fstack);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
WidgetT *next;
|
||||
|
||||
|
|
|
|||
|
|
@ -249,6 +249,7 @@ typedef struct WidgetT {
|
|||
bool enabled;
|
||||
bool readOnly;
|
||||
bool focused;
|
||||
bool swallowTab; // Tab key goes to widget, not focus nav
|
||||
char accelKey; // lowercase accelerator character, 0 if none
|
||||
|
||||
// User data and callbacks. These fire for ALL widget types from the
|
||||
|
|
|
|||
4
mkcd.sh
4
mkcd.sh
|
|
@ -38,13 +38,13 @@ echo "$WGT_COUNT widget modules found in bin/widgets/."
|
|||
# Create the ISO image
|
||||
# -iso-level 1: strict 8.3 filenames (DOS compatibility)
|
||||
# -J: Joliet extensions (long names for Windows/Linux)
|
||||
# -R: Rock Ridge (long names for Linux)
|
||||
# -V: volume label
|
||||
# Note: -R (Rock Ridge) is omitted because DOS surfaces its
|
||||
# attribute sidecar files (._ATR_) as visible junk files.
|
||||
echo "Creating ISO image..."
|
||||
mkisofs \
|
||||
-iso-level 1 \
|
||||
-J \
|
||||
-R \
|
||||
-V "DVX" \
|
||||
-o "$ISO_PATH" \
|
||||
"$SCRIPT_DIR/bin/"
|
||||
|
|
|
|||
|
|
@ -445,23 +445,16 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
|||
app->dxeCtx->appDir[1] = '\0';
|
||||
}
|
||||
|
||||
// Derive config directory: replace the APPS/ prefix with CONFIG/.
|
||||
// e.g. "APPS/GAMES/TETRIS" -> "CONFIG/GAMES/TETRIS"
|
||||
// If the path doesn't start with "apps/" or "APPS/", fall back to
|
||||
// "CONFIG/<appname>" using the descriptor name.
|
||||
// Derive config directory: mirror the app directory under CONFIG/.
|
||||
// e.g. "APPS/DVXBASIC" -> "CONFIG/APPS/DVXBASIC"
|
||||
// If the path doesn't start with a recognized prefix, fall back to
|
||||
// "CONFIG/APPS/<appname>" using the descriptor name.
|
||||
const char *appDirStr = app->dxeCtx->appDir;
|
||||
const char *appsPrefix = NULL;
|
||||
|
||||
if (strncasecmp(appDirStr, "apps/", 5) == 0 || strncasecmp(appDirStr, "apps\\", 5) == 0) {
|
||||
appsPrefix = appDirStr + 5;
|
||||
} else if (strncasecmp(appDirStr, "APPS/", 5) == 0 || strncasecmp(appDirStr, "APPS\\", 5) == 0) {
|
||||
appsPrefix = appDirStr + 5;
|
||||
}
|
||||
|
||||
if (appsPrefix && appsPrefix[0]) {
|
||||
snprintf(app->dxeCtx->configDir, sizeof(app->dxeCtx->configDir), "CONFIG/%s", appsPrefix);
|
||||
if (appDirStr[0]) {
|
||||
snprintf(app->dxeCtx->configDir, sizeof(app->dxeCtx->configDir), "CONFIG/%s", appDirStr);
|
||||
} else {
|
||||
snprintf(app->dxeCtx->configDir, sizeof(app->dxeCtx->configDir), "CONFIG/%s", desc->name);
|
||||
snprintf(app->dxeCtx->configDir, sizeof(app->dxeCtx->configDir), "CONFIG/APPS/%s", desc->name);
|
||||
}
|
||||
|
||||
// Launch. Set currentAppId before any app code runs so that
|
||||
|
|
|
|||
|
|
@ -103,6 +103,9 @@ typedef struct {
|
|||
bool sbDragging;
|
||||
bool showLineNumbers;
|
||||
bool autoIndent;
|
||||
bool captureTabs; // true = Tab key inserts tab/spaces; false = Tab moves focus
|
||||
bool useTabChar; // true = insert '\t'; false = insert spaces
|
||||
int32_t tabWidth; // display width and space count (default 3)
|
||||
|
||||
// Syntax colorizer callback (optional). Called for each visible line.
|
||||
// line: text of the line (NOT null-terminated, use lineLen).
|
||||
|
|
@ -1333,6 +1336,56 @@ navigation:
|
|||
return;
|
||||
}
|
||||
|
||||
// Tab key -- insert tab character or spaces if captureTabs is enabled
|
||||
if (key == 9 && ta->captureTabs && !w->readOnly) {
|
||||
if (*pLen < bufSize - 1) {
|
||||
textEditSaveUndo(buf, *pLen, CUR_OFF(), ta->undoBuf, &ta->undoLen, &ta->undoCursor, bufSize);
|
||||
|
||||
if (HAS_SEL()) {
|
||||
int32_t lo = SEL_LO();
|
||||
int32_t hi = SEL_HI();
|
||||
memmove(buf + lo, buf + hi, *pLen - hi + 1);
|
||||
*pLen -= (hi - lo);
|
||||
textAreaOffToRowCol(buf, lo, pRow, pCol);
|
||||
*pSA = -1;
|
||||
*pSC = -1;
|
||||
}
|
||||
|
||||
int32_t off = CUR_OFF();
|
||||
|
||||
if (ta->useTabChar) {
|
||||
// Insert a single tab character
|
||||
if (*pLen < bufSize - 1) {
|
||||
memmove(buf + off + 1, buf + off, *pLen - off + 1);
|
||||
buf[off] = '\t';
|
||||
(*pLen)++;
|
||||
(*pCol)++;
|
||||
ta->desiredCol = *pCol;
|
||||
}
|
||||
} else {
|
||||
// Insert spaces to next tab stop
|
||||
int32_t tw = ta->tabWidth > 0 ? ta->tabWidth : 3;
|
||||
int32_t spaces = tw - (*pCol % tw);
|
||||
|
||||
for (int32_t s = 0; s < spaces && *pLen < bufSize - 1; s++) {
|
||||
memmove(buf + off + 1, buf + off, *pLen - off + 1);
|
||||
buf[off] = ' ';
|
||||
off++;
|
||||
(*pLen)++;
|
||||
(*pCol)++;
|
||||
}
|
||||
|
||||
ta->desiredCol = *pCol;
|
||||
}
|
||||
|
||||
textAreaDirtyCache(w);
|
||||
textAreaEnsureVisible(w, visRows, visCols);
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Printable character (blocked in read-only mode)
|
||||
if (key >= 32 && key < 127 && !w->readOnly) {
|
||||
if (*pLen < bufSize - 1) {
|
||||
|
|
@ -2449,6 +2502,9 @@ WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen) {
|
|||
ta->desiredCol = 0;
|
||||
ta->cachedLines = -1;
|
||||
ta->cachedMaxLL = -1;
|
||||
ta->captureTabs = false; // default: Tab moves focus
|
||||
ta->useTabChar = true; // default: insert actual tab character
|
||||
ta->tabWidth = 3; // default: 3-space tab stops
|
||||
w->weight = 100;
|
||||
}
|
||||
|
||||
|
|
@ -2587,6 +2643,37 @@ void wgtTextAreaSetShowLineNumbers(WidgetT *w, bool show) {
|
|||
}
|
||||
|
||||
|
||||
void wgtTextAreaSetCaptureTabs(WidgetT *w, bool capture) {
|
||||
if (!w || w->type != sTextAreaTypeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||
ta->captureTabs = capture;
|
||||
w->swallowTab = capture;
|
||||
}
|
||||
|
||||
|
||||
void wgtTextAreaSetTabWidth(WidgetT *w, int32_t width) {
|
||||
if (!w || w->type != sTextAreaTypeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||
ta->tabWidth = width > 0 ? width : 1;
|
||||
}
|
||||
|
||||
|
||||
void wgtTextAreaSetUseTabChar(WidgetT *w, bool useChar) {
|
||||
if (!w || w->type != sTextAreaTypeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||
ta->useTabChar = useChar;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
|
@ -2601,6 +2688,9 @@ static const struct {
|
|||
void (*goToLine)(WidgetT *w, int32_t line);
|
||||
void (*setAutoIndent)(WidgetT *w, bool enable);
|
||||
void (*setShowLineNumbers)(WidgetT *w, bool show);
|
||||
void (*setCaptureTabs)(WidgetT *w, bool capture);
|
||||
void (*setTabWidth)(WidgetT *w, int32_t width);
|
||||
void (*setUseTabChar)(WidgetT *w, bool useChar);
|
||||
} sApi = {
|
||||
.create = wgtTextInput,
|
||||
.password = wgtPasswordInput,
|
||||
|
|
@ -2609,7 +2699,10 @@ static const struct {
|
|||
.setColorize = wgtTextAreaSetColorize,
|
||||
.goToLine = wgtTextAreaGoToLine,
|
||||
.setAutoIndent = wgtTextAreaSetAutoIndent,
|
||||
.setShowLineNumbers = wgtTextAreaSetShowLineNumbers
|
||||
.setShowLineNumbers = wgtTextAreaSetShowLineNumbers,
|
||||
.setCaptureTabs = wgtTextAreaSetCaptureTabs,
|
||||
.setTabWidth = wgtTextAreaSetTabWidth,
|
||||
.setUseTabChar = wgtTextAreaSetUseTabChar
|
||||
};
|
||||
|
||||
// Per-type APIs for the designer
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ typedef struct {
|
|||
void (*goToLine)(WidgetT *w, int32_t line);
|
||||
void (*setAutoIndent)(WidgetT *w, bool enable);
|
||||
void (*setShowLineNumbers)(WidgetT *w, bool show);
|
||||
void (*setCaptureTabs)(WidgetT *w, bool capture);
|
||||
void (*setTabWidth)(WidgetT *w, int32_t width);
|
||||
void (*setUseTabChar)(WidgetT *w, bool useChar);
|
||||
} TextInputApiT;
|
||||
|
||||
static inline const TextInputApiT *dvxTextInputApi(void) {
|
||||
|
|
@ -38,5 +41,8 @@ static inline const TextInputApiT *dvxTextInputApi(void) {
|
|||
#define wgtTextAreaGoToLine(w, line) dvxTextInputApi()->goToLine(w, line)
|
||||
#define wgtTextAreaSetAutoIndent(w, en) dvxTextInputApi()->setAutoIndent(w, en)
|
||||
#define wgtTextAreaSetShowLineNumbers(w, show) dvxTextInputApi()->setShowLineNumbers(w, show)
|
||||
#define wgtTextAreaSetCaptureTabs(w, capture) dvxTextInputApi()->setCaptureTabs(w, capture)
|
||||
#define wgtTextAreaSetTabWidth(w, width) dvxTextInputApi()->setTabWidth(w, width)
|
||||
#define wgtTextAreaSetUseTabChar(w, useChar) dvxTextInputApi()->setUseTabChar(w, useChar)
|
||||
|
||||
#endif // WIDGET_TEXTINPUT_H
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue