Compiler fixes. Editor fixes. Fixes fixes.

This commit is contained in:
Scott Duensing 2026-04-01 21:54:27 -05:00
parent 4d4aedbc43
commit 17fe1840e3
13 changed files with 1803 additions and 260 deletions

View file

@ -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)

View file

@ -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,18 +1331,16 @@ 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);
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 advance(p); // consume DOT
if (!check(p, TOK_IDENT)) { if (!check(p, TOK_IDENT)) {
errorExpected(p, "field name"); errorExpected(p, "field name");
return; return;
} }
BasSymbolT *typeSym = NULL; BasSymbolT *typeSym = findTypeDefById(p, curTypeId);
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;
}
}
if (typeSym == NULL) { if (typeSym == NULL) {
error(p, "Unknown TYPE definition"); error(p, "Unknown TYPE definition");
return; return;
@ -1275,6 +1355,14 @@ static void parsePrimary(BasParserT *p) {
advance(p); // consume field name advance(p); // consume field name
basEmit8(&p->cg, OP_LOAD_FIELD); basEmit8(&p->cg, OP_LOAD_FIELD);
basEmitU16(&p->cg, (uint16_t)fieldIdx); 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; return;
} }
@ -1383,18 +1471,18 @@ 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);
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 advance(p); // consume DOT
if (!check(p, TOK_IDENT)) { if (!check(p, TOK_IDENT)) {
errorExpected(p, "field name"); errorExpected(p, "field name");
return; return;
} }
BasSymbolT *typeSym = NULL; BasSymbolT *typeSym = findTypeDefById(p, curTypeId);
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;
}
}
if (typeSym == NULL) { if (typeSym == NULL) {
error(p, "Unknown TYPE definition"); error(p, "Unknown TYPE definition");
return; return;
@ -1407,12 +1495,28 @@ static void parseAssignOrCall(BasParserT *p) {
return; return;
} }
advance(p); // consume field name 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); expect(p, TOK_EQ);
parseExpression(p); parseExpression(p);
basEmit8(&p->cg, OP_STORE_FIELD); basEmit8(&p->cg, OP_STORE_FIELD);
basEmitU16(&p->cg, (uint16_t)fieldIdx); basEmitU16(&p->cg, (uint16_t)fieldIdx);
return; return;
} }
}
error(p, "Expected '=' in UDT field assignment");
return;
}
// Control property/method access: CtrlName.Member // Control property/method access: CtrlName.Member
// Emit: push current form ref, push ctrl name, FIND_CTRL // Emit: push current form ref, push ctrl name, FIND_CTRL
@ -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)) {

View file

@ -898,7 +898,10 @@ void basFormRtShowForm(void *ctx, void *formRef, bool modal) {
} }
form->window->visible = true; form->window->visible = true;
if (form->frmAutoSize) {
dvxFitWindow(rt->ctx, form->window); 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

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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,30 +2914,10 @@ 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;
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 (win->onKey) { if (win->onKey) {
WIN_CALLBACK(ctx, win, win->onKey(win, ascii ? ascii : (scancode | 0x100), shiftFlags)); WIN_CALLBACK(ctx, win, win->onKey(win, ascii ? ascii : (scancode | 0x100), shiftFlags));
} }
@ -2950,7 +2925,6 @@ static void pollKeyboard(AppContextT *ctx) {
arrfree(fstack); arrfree(fstack);
continue; continue;
} }
}
WidgetT *next; WidgetT *next;

View file

@ -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

View file

@ -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/"

View file

@ -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

View file

@ -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

View file

@ -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