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_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_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_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)
|
// Arithmetic (integer)
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,9 @@ static void errorExpected(BasParserT *p, const char *what);
|
||||||
static void expect(BasParserT *p, BasTokenTypeE type);
|
static void expect(BasParserT *p, BasTokenTypeE type);
|
||||||
static void expectEndOfStatement(BasParserT *p);
|
static void expectEndOfStatement(BasParserT *p);
|
||||||
static const BuiltinFuncT *findBuiltin(const char *name);
|
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 *findTypeDef(BasParserT *p, const char *name);
|
||||||
|
static BasSymbolT *findTypeDefById(BasParserT *p, int32_t typeId);
|
||||||
static bool match(BasParserT *p, BasTokenTypeE type);
|
static bool match(BasParserT *p, BasTokenTypeE type);
|
||||||
static int32_t resolveFieldIndex(BasSymbolT *typeSym, const char *fieldName);
|
static int32_t resolveFieldIndex(BasSymbolT *typeSym, const char *fieldName);
|
||||||
static uint8_t resolveTypeName(BasParserT *p);
|
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) {
|
static bool match(BasParserT *p, BasTokenTypeE type) {
|
||||||
if (p->lex.token.type == type) {
|
if (p->lex.token.type == type) {
|
||||||
advance(p);
|
advance(p);
|
||||||
|
|
@ -444,7 +499,7 @@ static uint8_t resolveTypeName(BasParserT *p) {
|
||||||
|
|
||||||
|
|
||||||
static void skipNewlines(BasParserT *p) {
|
static void skipNewlines(BasParserT *p) {
|
||||||
while (check(p, TOK_NEWLINE)) {
|
while (!p->hasError && check(p, TOK_NEWLINE)) {
|
||||||
advance(p);
|
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) {
|
static void parseMulExpr(BasParserT *p) {
|
||||||
parsePowExpr(p);
|
parseUnaryExpr(p);
|
||||||
while (!p->hasError) {
|
while (!p->hasError) {
|
||||||
if (check(p, TOK_STAR)) {
|
if (check(p, TOK_STAR)) {
|
||||||
advance(p);
|
advance(p);
|
||||||
parsePowExpr(p);
|
parseUnaryExpr(p);
|
||||||
basEmit8(&p->cg, OP_MUL_INT);
|
basEmit8(&p->cg, OP_MUL_INT);
|
||||||
} else if (check(p, TOK_SLASH)) {
|
} else if (check(p, TOK_SLASH)) {
|
||||||
advance(p);
|
advance(p);
|
||||||
parsePowExpr(p);
|
parseUnaryExpr(p);
|
||||||
basEmit8(&p->cg, OP_DIV_FLT);
|
basEmit8(&p->cg, OP_DIV_FLT);
|
||||||
} else if (check(p, TOK_BACKSLASH)) {
|
} else if (check(p, TOK_BACKSLASH)) {
|
||||||
advance(p);
|
advance(p);
|
||||||
parsePowExpr(p);
|
parseUnaryExpr(p);
|
||||||
basEmit8(&p->cg, OP_IDIV_INT);
|
basEmit8(&p->cg, OP_IDIV_INT);
|
||||||
} else if (check(p, TOK_MOD)) {
|
} else if (check(p, TOK_MOD)) {
|
||||||
advance(p);
|
advance(p);
|
||||||
parsePowExpr(p);
|
parseUnaryExpr(p);
|
||||||
basEmit8(&p->cg, OP_MOD_INT);
|
basEmit8(&p->cg, OP_MOD_INT);
|
||||||
} else {
|
} else {
|
||||||
break;
|
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) {
|
static void parseUnaryExpr(BasParserT *p) {
|
||||||
if (check(p, TOK_MINUS)) {
|
if (check(p, TOK_MINUS)) {
|
||||||
advance(p);
|
advance(p);
|
||||||
|
|
@ -961,7 +1009,17 @@ static void parseUnaryExpr(BasParserT *p) {
|
||||||
parseUnaryExpr(p);
|
parseUnaryExpr(p);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
parsePowExpr(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void parsePowExpr(BasParserT *p) {
|
||||||
parsePrimary(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);
|
expect(p, TOK_RPAREN);
|
||||||
basEmit8(&p->cg, OP_LOAD_ARRAY);
|
basEmit8(&p->cg, OP_LOAD_ARRAY);
|
||||||
basEmit8(&p->cg, (uint8_t)dims);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// Unknown function -- forward reference, assume it's a function
|
// Unknown function -- forward reference, assume it's a function
|
||||||
|
|
@ -1249,32 +1331,38 @@ static void parsePrimary(BasParserT *p) {
|
||||||
sym = basSymTabFind(&p->sym, name);
|
sym = basSymTabFind(&p->sym, name);
|
||||||
if (sym != NULL && sym->dataType == BAS_TYPE_UDT && sym->udtTypeId >= 0) {
|
if (sym != NULL && sym->dataType == BAS_TYPE_UDT && sym->udtTypeId >= 0) {
|
||||||
emitLoad(p, sym);
|
emitLoad(p, sym);
|
||||||
advance(p); // consume DOT
|
int32_t curTypeId = sym->udtTypeId;
|
||||||
if (!check(p, TOK_IDENT)) {
|
|
||||||
errorExpected(p, "field name");
|
// Loop to handle nested UDT field access: a.b.c
|
||||||
return;
|
while (check(p, TOK_DOT) && curTypeId >= 0) {
|
||||||
}
|
advance(p); // consume DOT
|
||||||
BasSymbolT *typeSym = NULL;
|
if (!check(p, TOK_IDENT)) {
|
||||||
for (int32_t i = 0; i < p->sym.count; i++) {
|
errorExpected(p, "field name");
|
||||||
if (p->sym.symbols[i].kind == SYM_TYPE_DEF && p->sym.symbols[i].index == sym->udtTypeId) {
|
return;
|
||||||
typeSym = &p->sym.symbols[i];
|
}
|
||||||
break;
|
BasSymbolT *typeSym = findTypeDefById(p, curTypeId);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1383,34 +1471,50 @@ static void parseAssignOrCall(BasParserT *p) {
|
||||||
// Check for UDT field access first
|
// Check for UDT field access first
|
||||||
if (sym != NULL && sym->dataType == BAS_TYPE_UDT && sym->udtTypeId >= 0) {
|
if (sym != NULL && sym->dataType == BAS_TYPE_UDT && sym->udtTypeId >= 0) {
|
||||||
emitLoad(p, sym);
|
emitLoad(p, sym);
|
||||||
advance(p); // consume DOT
|
int32_t curTypeId = sym->udtTypeId;
|
||||||
if (!check(p, TOK_IDENT)) {
|
|
||||||
errorExpected(p, "field name");
|
// Walk the dot chain: a.b.c = expr
|
||||||
return;
|
// For intermediate fields, emit LOAD_FIELD (navigate into nested UDT).
|
||||||
}
|
// For the final field, emit STORE_FIELD with the assigned value.
|
||||||
BasSymbolT *typeSym = NULL;
|
while (check(p, TOK_DOT) && curTypeId >= 0) {
|
||||||
for (int32_t i = 0; i < p->sym.count; i++) {
|
advance(p); // consume DOT
|
||||||
if (p->sym.symbols[i].kind == SYM_TYPE_DEF && p->sym.symbols[i].index == sym->udtTypeId) {
|
if (!check(p, TOK_IDENT)) {
|
||||||
typeSym = &p->sym.symbols[i];
|
errorExpected(p, "field name");
|
||||||
break;
|
return;
|
||||||
|
}
|
||||||
|
BasSymbolT *typeSym = findTypeDefById(p, curTypeId);
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeSym == NULL) {
|
|
||||||
error(p, "Unknown TYPE definition");
|
error(p, "Expected '=' in UDT field assignment");
|
||||||
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_FIELD);
|
|
||||||
basEmitU16(&p->cg, (uint16_t)fieldIdx);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1549,6 +1653,34 @@ static void parseAssignOrCall(BasParserT *p) {
|
||||||
}
|
}
|
||||||
expect(p, TOK_RPAREN);
|
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);
|
expect(p, TOK_EQ);
|
||||||
parseExpression(p);
|
parseExpression(p);
|
||||||
|
|
||||||
|
|
@ -1584,7 +1716,19 @@ static void parseAssignOrCall(BasParserT *p) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sub call without parens: SUBName arg1, arg2 ...
|
// 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;
|
int32_t argc = 0;
|
||||||
if (!check(p, TOK_NEWLINE) && !check(p, TOK_EOF) && !check(p, TOK_COLON) && !check(p, TOK_ELSE)) {
|
if (!check(p, TOK_NEWLINE) && !check(p, TOK_EOF) && !check(p, TOK_COLON) && !check(p, TOK_ELSE)) {
|
||||||
if (argc < sym->paramCount && !sym->paramByVal[argc]) {
|
if (argc < sym->paramCount && !sym->paramByVal[argc]) {
|
||||||
|
|
@ -2358,20 +2502,24 @@ static void parseDim(BasParserT *p) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isArray) {
|
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, OP_DIM_ARRAY);
|
||||||
basEmit8(&p->cg, (uint8_t)dims);
|
basEmit8(&p->cg, (uint8_t)dims);
|
||||||
basEmit8(&p->cg, dt);
|
basEmit8(&p->cg, dt);
|
||||||
emitStore(p, sym);
|
emitStore(p, sym);
|
||||||
} else if (dt == BAS_TYPE_UDT && udtTypeId >= 0) {
|
} else if (dt == BAS_TYPE_UDT && udtTypeId >= 0) {
|
||||||
// Allocate a UDT instance
|
// Allocate a UDT instance
|
||||||
BasSymbolT *typeSym = NULL;
|
BasSymbolT *typeSym = findTypeDefById(p, udtTypeId);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeSym != NULL) {
|
if (typeSym != NULL) {
|
||||||
basEmit8(&p->cg, OP_PUSH_INT16);
|
basEmit8(&p->cg, OP_PUSH_INT16);
|
||||||
basEmit16(&p->cg, (int16_t)udtTypeId);
|
basEmit16(&p->cg, (int16_t)udtTypeId);
|
||||||
|
|
@ -2381,6 +2529,8 @@ static void parseDim(BasParserT *p) {
|
||||||
basEmit8(&p->cg, OP_DIM_ARRAY);
|
basEmit8(&p->cg, OP_DIM_ARRAY);
|
||||||
basEmit8(&p->cg, 0);
|
basEmit8(&p->cg, 0);
|
||||||
basEmit8(&p->cg, BAS_TYPE_UDT);
|
basEmit8(&p->cg, BAS_TYPE_UDT);
|
||||||
|
// Initialize nested UDT fields
|
||||||
|
emitUdtInit(p, udtTypeId);
|
||||||
emitStore(p, sym);
|
emitStore(p, sym);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3118,6 +3268,21 @@ static void parseModule(BasParserT *p) {
|
||||||
skipNewlines(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
|
// End of module -- emit HALT
|
||||||
basEmit8(&p->cg, OP_HALT);
|
basEmit8(&p->cg, OP_HALT);
|
||||||
}
|
}
|
||||||
|
|
@ -4252,6 +4417,115 @@ static void parseStatement(BasParserT *p) {
|
||||||
basEmit8(&p->cg, OP_POP);
|
basEmit8(&p->cg, OP_POP);
|
||||||
break;
|
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:
|
case TOK_LET:
|
||||||
advance(p); // consume LET, then fall through to assignment
|
advance(p); // consume LET, then fall through to assignment
|
||||||
if (!check(p, TOK_IDENT)) {
|
if (!check(p, TOK_IDENT)) {
|
||||||
|
|
|
||||||
|
|
@ -898,7 +898,10 @@ void basFormRtShowForm(void *ctx, void *formRef, bool modal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
form->window->visible = true;
|
form->window->visible = true;
|
||||||
dvxFitWindow(rt->ctx, form->window);
|
|
||||||
|
if (form->frmAutoSize) {
|
||||||
|
dvxFitWindow(rt->ctx, form->window);
|
||||||
|
}
|
||||||
|
|
||||||
if (modal) {
|
if (modal) {
|
||||||
rt->ctx->modalWindow = form->window;
|
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++) {
|
for (int32_t i = 0; i < prj->fileCount; i++) {
|
||||||
if (prj->files[i].isForm) {
|
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);
|
arrput(sLabels, label);
|
||||||
WidgetT *item = wgtTreeItem(formsNode, label);
|
WidgetT *item = wgtTreeItem(formsNode, label);
|
||||||
item->userData = (void *)(intptr_t)i;
|
item->userData = (void *)(intptr_t)i;
|
||||||
|
|
@ -486,7 +488,9 @@ void prjRebuildTree(PrjStateT *prj) {
|
||||||
|
|
||||||
for (int32_t i = 0; i < prj->fileCount; i++) {
|
for (int32_t i = 0; i < prj->fileCount; i++) {
|
||||||
if (!prj->files[i].isForm) {
|
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);
|
arrput(sLabels, label);
|
||||||
WidgetT *item = wgtTreeItem(modsNode, label);
|
WidgetT *item = wgtTreeItem(modsNode, label);
|
||||||
item->userData = (void *)(intptr_t)i;
|
item->userData = (void *)(intptr_t)i;
|
||||||
|
|
|
||||||
|
|
@ -1811,6 +1811,24 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
||||||
break;
|
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
|
// Normal array allocation: parser pushes (lbound, ubound) pairs per dim
|
||||||
int32_t lbounds[BAS_ARRAY_MAX_DIMS];
|
int32_t lbounds[BAS_ARRAY_MAX_DIMS];
|
||||||
int32_t ubounds[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;
|
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;
|
BasValueT arrVal;
|
||||||
arrVal.type = BAS_TYPE_ARRAY;
|
arrVal.type = BAS_TYPE_ARRAY;
|
||||||
arrVal.arrVal = arr;
|
arrVal.arrVal = arr;
|
||||||
|
|
@ -1952,6 +1986,79 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
||||||
break;
|
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: {
|
case OP_REDIM: {
|
||||||
uint8_t dims = readUint8(vm);
|
uint8_t dims = readUint8(vm);
|
||||||
uint8_t preserve = readUint8(vm);
|
uint8_t preserve = readUint8(vm);
|
||||||
|
|
|
||||||
|
|
@ -1629,14 +1629,649 @@ int main(void) {
|
||||||
// Expected: 3 / 42 / 3 / 3 / [ 42]
|
// Expected: 3 / 42 / 3 / 3 / [ 42]
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Coverage: Me keyword
|
// Coverage: Me keyword (compilation only -- no form context)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
runProgram("Me keyword",
|
runProgram("Me keyword compiles",
|
||||||
"' Me compiles to OP_ME_REF (returns NULL outside form context)\n"
|
"' Me compiles to OP_ME_REF\n"
|
||||||
"PRINT \"ok\"\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");
|
printf("All tests complete.\n");
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -2893,7 +2893,7 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
|
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
|
||||||
|
|
||||||
if (win->widgetRoot) {
|
if (win->widgetRoot) {
|
||||||
// Find currently focused widget
|
// Find the currently focused widget
|
||||||
WidgetT *current = NULL;
|
WidgetT *current = NULL;
|
||||||
WidgetT **fstack = NULL;
|
WidgetT **fstack = NULL;
|
||||||
arrput(fstack, win->widgetRoot);
|
arrput(fstack, win->widgetRoot);
|
||||||
|
|
@ -2903,11 +2903,6 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
arrsetlen(fstack, arrlen(fstack) - 1);
|
arrsetlen(fstack, arrlen(fstack) - 1);
|
||||||
|
|
||||||
if (w->focused && widgetIsFocusable(w->type)) {
|
if (w->focused && widgetIsFocusable(w->type)) {
|
||||||
if (w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
|
|
||||||
current = NULL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
current = w;
|
current = w;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -2919,37 +2914,16 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminal swallowed Tab -- send to widget system instead
|
// If the focused widget wants Tab, send it there
|
||||||
if (current == NULL) {
|
// instead of cycling focus.
|
||||||
arrsetlen(fstack, 0);
|
if (current && (current->swallowTab ||
|
||||||
arrput(fstack, win->widgetRoot);
|
(current->wclass && (current->wclass->flags & WCLASS_SWALLOWS_TAB)))) {
|
||||||
bool termFocused = false;
|
if (win->onKey) {
|
||||||
|
WIN_CALLBACK(ctx, win, win->onKey(win, ascii ? ascii : (scancode | 0x100), shiftFlags));
|
||||||
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) {
|
arrfree(fstack);
|
||||||
// Terminal has focus -- send Tab to it
|
continue;
|
||||||
if (win->onKey) {
|
|
||||||
WIN_CALLBACK(ctx, win, win->onKey(win, ascii ? ascii : (scancode | 0x100), shiftFlags));
|
|
||||||
}
|
|
||||||
|
|
||||||
arrfree(fstack);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WidgetT *next;
|
WidgetT *next;
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,7 @@ typedef struct WidgetT {
|
||||||
bool enabled;
|
bool enabled;
|
||||||
bool readOnly;
|
bool readOnly;
|
||||||
bool focused;
|
bool focused;
|
||||||
|
bool swallowTab; // Tab key goes to widget, not focus nav
|
||||||
char accelKey; // lowercase accelerator character, 0 if none
|
char accelKey; // lowercase accelerator character, 0 if none
|
||||||
|
|
||||||
// User data and callbacks. These fire for ALL widget types from the
|
// 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
|
# Create the ISO image
|
||||||
# -iso-level 1: strict 8.3 filenames (DOS compatibility)
|
# -iso-level 1: strict 8.3 filenames (DOS compatibility)
|
||||||
# -J: Joliet extensions (long names for Windows/Linux)
|
# -J: Joliet extensions (long names for Windows/Linux)
|
||||||
# -R: Rock Ridge (long names for Linux)
|
|
||||||
# -V: volume label
|
# -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..."
|
echo "Creating ISO image..."
|
||||||
mkisofs \
|
mkisofs \
|
||||||
-iso-level 1 \
|
-iso-level 1 \
|
||||||
-J \
|
-J \
|
||||||
-R \
|
|
||||||
-V "DVX" \
|
-V "DVX" \
|
||||||
-o "$ISO_PATH" \
|
-o "$ISO_PATH" \
|
||||||
"$SCRIPT_DIR/bin/"
|
"$SCRIPT_DIR/bin/"
|
||||||
|
|
|
||||||
|
|
@ -445,23 +445,16 @@ int32_t shellLoadApp(AppContextT *ctx, const char *path) {
|
||||||
app->dxeCtx->appDir[1] = '\0';
|
app->dxeCtx->appDir[1] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Derive config directory: replace the APPS/ prefix with CONFIG/.
|
// Derive config directory: mirror the app directory under CONFIG/.
|
||||||
// e.g. "APPS/GAMES/TETRIS" -> "CONFIG/GAMES/TETRIS"
|
// e.g. "APPS/DVXBASIC" -> "CONFIG/APPS/DVXBASIC"
|
||||||
// If the path doesn't start with "apps/" or "APPS/", fall back to
|
// If the path doesn't start with a recognized prefix, fall back to
|
||||||
// "CONFIG/<appname>" using the descriptor name.
|
// "CONFIG/APPS/<appname>" using the descriptor name.
|
||||||
const char *appDirStr = app->dxeCtx->appDir;
|
const char *appDirStr = app->dxeCtx->appDir;
|
||||||
const char *appsPrefix = NULL;
|
|
||||||
|
|
||||||
if (strncasecmp(appDirStr, "apps/", 5) == 0 || strncasecmp(appDirStr, "apps\\", 5) == 0) {
|
if (appDirStr[0]) {
|
||||||
appsPrefix = appDirStr + 5;
|
snprintf(app->dxeCtx->configDir, sizeof(app->dxeCtx->configDir), "CONFIG/%s", appDirStr);
|
||||||
} 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);
|
|
||||||
} else {
|
} 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
|
// Launch. Set currentAppId before any app code runs so that
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,9 @@ typedef struct {
|
||||||
bool sbDragging;
|
bool sbDragging;
|
||||||
bool showLineNumbers;
|
bool showLineNumbers;
|
||||||
bool autoIndent;
|
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.
|
// Syntax colorizer callback (optional). Called for each visible line.
|
||||||
// line: text of the line (NOT null-terminated, use lineLen).
|
// line: text of the line (NOT null-terminated, use lineLen).
|
||||||
|
|
@ -1333,6 +1336,56 @@ navigation:
|
||||||
return;
|
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)
|
// Printable character (blocked in read-only mode)
|
||||||
if (key >= 32 && key < 127 && !w->readOnly) {
|
if (key >= 32 && key < 127 && !w->readOnly) {
|
||||||
if (*pLen < bufSize - 1) {
|
if (*pLen < bufSize - 1) {
|
||||||
|
|
@ -2449,6 +2502,9 @@ WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen) {
|
||||||
ta->desiredCol = 0;
|
ta->desiredCol = 0;
|
||||||
ta->cachedLines = -1;
|
ta->cachedLines = -1;
|
||||||
ta->cachedMaxLL = -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;
|
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
|
// DXE registration
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -2601,6 +2688,9 @@ static const struct {
|
||||||
void (*goToLine)(WidgetT *w, int32_t line);
|
void (*goToLine)(WidgetT *w, int32_t line);
|
||||||
void (*setAutoIndent)(WidgetT *w, bool enable);
|
void (*setAutoIndent)(WidgetT *w, bool enable);
|
||||||
void (*setShowLineNumbers)(WidgetT *w, bool show);
|
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 = {
|
} sApi = {
|
||||||
.create = wgtTextInput,
|
.create = wgtTextInput,
|
||||||
.password = wgtPasswordInput,
|
.password = wgtPasswordInput,
|
||||||
|
|
@ -2609,7 +2699,10 @@ static const struct {
|
||||||
.setColorize = wgtTextAreaSetColorize,
|
.setColorize = wgtTextAreaSetColorize,
|
||||||
.goToLine = wgtTextAreaGoToLine,
|
.goToLine = wgtTextAreaGoToLine,
|
||||||
.setAutoIndent = wgtTextAreaSetAutoIndent,
|
.setAutoIndent = wgtTextAreaSetAutoIndent,
|
||||||
.setShowLineNumbers = wgtTextAreaSetShowLineNumbers
|
.setShowLineNumbers = wgtTextAreaSetShowLineNumbers,
|
||||||
|
.setCaptureTabs = wgtTextAreaSetCaptureTabs,
|
||||||
|
.setTabWidth = wgtTextAreaSetTabWidth,
|
||||||
|
.setUseTabChar = wgtTextAreaSetUseTabChar
|
||||||
};
|
};
|
||||||
|
|
||||||
// Per-type APIs for the designer
|
// Per-type APIs for the designer
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ typedef struct {
|
||||||
void (*goToLine)(WidgetT *w, int32_t line);
|
void (*goToLine)(WidgetT *w, int32_t line);
|
||||||
void (*setAutoIndent)(WidgetT *w, bool enable);
|
void (*setAutoIndent)(WidgetT *w, bool enable);
|
||||||
void (*setShowLineNumbers)(WidgetT *w, bool show);
|
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;
|
} TextInputApiT;
|
||||||
|
|
||||||
static inline const TextInputApiT *dvxTextInputApi(void) {
|
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 wgtTextAreaGoToLine(w, line) dvxTextInputApi()->goToLine(w, line)
|
||||||
#define wgtTextAreaSetAutoIndent(w, en) dvxTextInputApi()->setAutoIndent(w, en)
|
#define wgtTextAreaSetAutoIndent(w, en) dvxTextInputApi()->setAutoIndent(w, en)
|
||||||
#define wgtTextAreaSetShowLineNumbers(w, show) dvxTextInputApi()->setShowLineNumbers(w, show)
|
#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
|
#endif // WIDGET_TEXTINPUT_H
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue