BASIC is getting to be pretty stable.

This commit is contained in:
Scott Duensing 2026-03-27 18:59:58 -05:00
parent b3cc66be4b
commit 65d7a252ca
61 changed files with 5305 additions and 195 deletions

4
.gitignore vendored
View file

@ -7,3 +7,7 @@ lib/
.gitattributes~ .gitattributes~
*.SWP *.SWP
.claude/ .claude/
dvxbasic/test_compiler
dvxbasic/test_lex
dvxbasic/test_quick
dvxbasic/test_vm

View file

@ -157,7 +157,7 @@ static void updateTime(void) {
hour12 = 12; hour12 = 12;
} }
snprintf(sState.timeStr, sizeof(sState.timeStr), "%d:%02d:%02d %s", hour12, tm->tm_min, tm->tm_sec, ampm); snprintf(sState.timeStr, sizeof(sState.timeStr), "%d:%02d:%02d %s", (int)hour12, tm->tm_min, tm->tm_sec, ampm);
snprintf(sState.dateStr, sizeof(sState.dateStr), "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); snprintf(sState.dateStr, sizeof(sState.dateStr), "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
sState.lastUpdate = now; sState.lastUpdate = now;
} }

View file

@ -77,7 +77,7 @@ AppDescriptorT appDescriptor = {
typedef struct { typedef struct {
char name[64]; char name[64];
char path[260]; char path[280];
} FileEntryT; } FileEntryT;
// ============================================================ // ============================================================
@ -119,7 +119,7 @@ static WidgetT *sColorSwatch = NULL;
static WidgetT *sWallpaperLbl = NULL; static WidgetT *sWallpaperLbl = NULL;
static WidgetT *sWpaperList = NULL; static WidgetT *sWpaperList = NULL;
static WidgetT *sWpModeDrop = NULL; static WidgetT *sWpModeDrop = NULL;
static char sWallpaperPath[260]; static char sWallpaperPath[280];
static FileEntryT *sWpaperEntries = NULL; // stb_ds dynamic array static FileEntryT *sWpaperEntries = NULL; // stb_ds dynamic array
static const char **sWpaperLabels = NULL; // stb_ds dynamic array static const char **sWpaperLabels = NULL; // stb_ds dynamic array
@ -991,7 +991,7 @@ static void scanWallpapers(void) {
} }
FileEntryT entry = {0}; FileEntryT entry = {0};
snprintf(entry.name, sizeof(entry.name), "%s", ent->d_name); snprintf(entry.name, sizeof(entry.name), "%.63s", ent->d_name);
snprintf(entry.path, sizeof(entry.path), "%s/%s", WPAPER_DIR, ent->d_name); snprintf(entry.path, sizeof(entry.path), "%s/%s", WPAPER_DIR, ent->d_name);
arrput(sWpaperEntries, entry); arrput(sWpaperEntries, entry);
} }

View file

@ -181,8 +181,12 @@ static void loadAndDisplay(const char *path) {
dvxSetBusy(sAc, true); dvxSetBusy(sAc, true);
int32_t channels; int imgW;
sImgRgb = stbi_load(path, &sImgW, &sImgH, &channels, 3); int imgH;
int channels;
sImgRgb = stbi_load(path, &imgW, &imgH, &channels, 3);
sImgW = imgW;
sImgH = imgH;
if (!sImgRgb) { if (!sImgRgb) {
dvxSetBusy(sAc, false); dvxSetBusy(sAc, false);
@ -200,7 +204,7 @@ static void loadAndDisplay(const char *path) {
fname = fname ? fname + 1 : path; fname = fname ? fname + 1 : path;
char title[128]; char title[280];
snprintf(title, sizeof(title), "%s - Image Viewer", fname); snprintf(title, sizeof(title), "%s - Image Viewer", fname);
dvxSetTitle(sAc, sWin, title); dvxSetTitle(sAc, sWin, title);

View file

@ -148,7 +148,7 @@ static void refreshMinimizedIcons(AppContextT *ctx);
static void repositionWindow(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int32_t w, int32_t h); static void repositionWindow(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int32_t w, int32_t h);
static void updateCursorShape(AppContextT *ctx); static void updateCursorShape(AppContextT *ctx);
static void updateTooltip(AppContextT *ctx); static void updateTooltip(AppContextT *ctx);
static void writePixel(uint8_t *row, int32_t x, uint32_t px, int32_t bpp);
// Button pressed via keyboard -- shared with widgetEvent.c for Space/Enter. // Button pressed via keyboard -- shared with widgetEvent.c for Space/Enter.
// Non-static so widgetEvent.c can set it when Space/Enter triggers a button. // Non-static so widgetEvent.c can set it when Space/Enter triggers a button.
@ -3440,21 +3440,6 @@ static void updateTooltip(AppContextT *ctx) {
} }
// ============================================================
// writePixel -- write a packed pixel to a buffer at position x
// ============================================================
static void writePixel(uint8_t *row, int32_t x, uint32_t px, int32_t bpp) {
if (bpp == 8) {
row[x] = (uint8_t)px;
} else if (bpp == 15 || bpp == 16) {
((uint16_t *)row)[x] = (uint16_t)px;
} else {
((uint32_t *)row)[x] = px;
}
}
// ============================================================ // ============================================================
// dvxAddAccel // dvxAddAccel
// ============================================================ // ============================================================
@ -4303,9 +4288,9 @@ bool dvxLoadTheme(AppContextT *ctx, const char *filename) {
val++; val++;
} }
int32_t r; int r;
int32_t g; int g;
int32_t b; int b;
if (sscanf(val, "%d,%d,%d", &r, &g, &b) != 3) { if (sscanf(val, "%d,%d,%d", &r, &g, &b) != 3) {
continue; continue;
@ -4566,9 +4551,9 @@ bool dvxSetWallpaper(AppContextT *ctx, const char *path) {
dvxSetBusy(ctx, true); dvxSetBusy(ctx, true);
int32_t imgW; int imgW;
int32_t imgH; int imgH;
int32_t channels; int channels;
uint8_t *rgb = stbi_load(path, &imgW, &imgH, &channels, 3); uint8_t *rgb = stbi_load(path, &imgW, &imgH, &channels, 3);
if (!rgb) { if (!rgb) {

View file

@ -72,6 +72,9 @@
static void drawIconGlyph(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t iconType, uint32_t color); static void drawIconGlyph(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t iconType, uint32_t color);
static bool fdAcceptFile(const char *name); static bool fdAcceptFile(const char *name);
static void ibOnCancel(WidgetT *w);
static void ibOnClose(WindowT *win);
static void ibOnOk(WidgetT *w);
static int fdEntryCompare(const void *a, const void *b); static int fdEntryCompare(const void *a, const void *b);
static bool fdFilterMatch(const char *name, const char *pattern); static bool fdFilterMatch(const char *name, const char *pattern);
static void fdFreeEntries(void); static void fdFreeEntries(void);
@ -115,6 +118,25 @@ typedef struct {
static MsgBoxStateT sMsgBox; static MsgBoxStateT sMsgBox;
// ============================================================
// Input box state (one active at a time)
// ============================================================
#define IB_DIALOG_WIDTH 300
#define IB_INPUT_MAXLEN 256
#define IB_PADDING 8
typedef struct {
AppContextT *ctx;
bool done;
bool accepted; // true = OK, false = Cancel
WidgetT *input; // text input widget
char *outBuf; // caller's output buffer
int32_t outBufSize;
} InputBoxStateT;
static InputBoxStateT sInputBox;
// ============================================================ // ============================================================
// drawIconGlyph -- draw a simple icon shape // drawIconGlyph -- draw a simple icon shape
@ -618,6 +640,141 @@ static int32_t wordWrapHeight(const BitmapFontT *font, const char *text, int32_t
} }
// ============================================================
// dvxInputBox
// ============================================================
//
// Modal input dialog with prompt, text field, OK/Cancel buttons.
// Follows the same nested-event-loop pattern as dvxMessageBox.
bool dvxInputBox(AppContextT *ctx, const char *title, const char *prompt, const char *defaultText, char *outBuf, int32_t outBufSize) {
if (!ctx || !outBuf || outBufSize <= 0) {
return false;
}
int32_t promptH = 0;
if (prompt && prompt[0]) {
int32_t textMaxW = IB_DIALOG_WIDTH - IB_PADDING * 2;
promptH = wordWrapHeight(&ctx->font, prompt, textMaxW);
}
int32_t contentH = IB_PADDING + promptH + IB_PADDING + ctx->font.charHeight + 4 + IB_PADDING + BUTTON_HEIGHT + IB_PADDING;
int32_t contentW = IB_DIALOG_WIDTH;
int32_t winX = (ctx->display.width - contentW) / 2 - CHROME_TOTAL_SIDE;
int32_t winY = (ctx->display.height - contentH) / 2 - CHROME_TOTAL_TOP;
WindowT *win = dvxCreateWindow(ctx, title ? title : "Input",
winX, winY,
contentW + CHROME_TOTAL_SIDE * 2,
contentH + CHROME_TOTAL_TOP + CHROME_TOTAL_BOTTOM,
false);
if (!win) {
return false;
}
win->modal = true;
win->onClose = ibOnClose;
win->maxW = win->w;
win->maxH = win->h;
sInputBox.ctx = ctx;
sInputBox.done = false;
sInputBox.accepted = false;
sInputBox.outBuf = outBuf;
sInputBox.outBufSize = outBufSize;
WidgetT *root = wgtInitWindow(ctx, win);
if (root) {
// Prompt label
if (prompt && prompt[0]) {
WidgetT *lbl = wgtLabel(root, prompt);
(void)lbl;
}
// Text input
WidgetT *input = wgtTextInput(root, IB_INPUT_MAXLEN);
input->weight = 0;
sInputBox.input = input;
if (defaultText) {
wgtSetText(input, defaultText);
}
// Button row
WidgetT *btnRow = wgtHBox(root);
btnRow->align = AlignCenterE;
WidgetT *okBtn = wgtButton(btnRow, "&OK");
okBtn->minW = wgtPixels(BUTTON_WIDTH);
okBtn->minH = wgtPixels(BUTTON_HEIGHT);
okBtn->onClick = ibOnOk;
WidgetT *cancelBtn = wgtButton(btnRow, "&Cancel");
cancelBtn->minW = wgtPixels(BUTTON_WIDTH);
cancelBtn->minH = wgtPixels(BUTTON_HEIGHT);
cancelBtn->onClick = ibOnCancel;
}
dvxFitWindow(ctx, win);
WindowT *prevModal = ctx->modalWindow;
ctx->modalWindow = win;
while (!sInputBox.done && ctx->running) {
dvxUpdate(ctx);
}
ctx->modalWindow = prevModal;
dvxDestroyWindow(ctx, win);
sInputBox.input = NULL;
return sInputBox.accepted;
}
// ============================================================
// ibOnCancel
// ============================================================
static void ibOnCancel(WidgetT *w) {
(void)w;
sInputBox.accepted = false;
sInputBox.done = true;
}
// ============================================================
// ibOnClose
// ============================================================
static void ibOnClose(WindowT *win) {
(void)win;
sInputBox.accepted = false;
sInputBox.done = true;
}
// ============================================================
// ibOnOk
// ============================================================
static void ibOnOk(WidgetT *w) {
(void)w;
if (sInputBox.input && sInputBox.outBuf) {
const char *text = wgtGetText(sInputBox.input);
snprintf(sInputBox.outBuf, sInputBox.outBufSize, "%s", text ? text : "");
}
sInputBox.accepted = true;
sInputBox.done = true;
}
// ============================================================ // ============================================================
// File dialog // File dialog
// ============================================================ // ============================================================

View file

@ -77,4 +77,10 @@ typedef struct {
// NULL to start in the current working directory. // NULL to start in the current working directory.
bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const char *initialDir, const FileFilterT *filters, int32_t filterCount, char *outPath, int32_t outPathSize); bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const char *initialDir, const FileFilterT *filters, int32_t filterCount, char *outPath, int32_t outPathSize);
// Display a modal input box with a prompt label, text input field,
// and OK/Cancel buttons. Blocks the caller via dvxUpdate() loop.
// Returns true if the user clicked OK (text written to outBuf),
// false if cancelled or closed. defaultText may be NULL.
bool dvxInputBox(AppContextT *ctx, const char *title, const char *prompt, const char *defaultText, char *outBuf, int32_t outBufSize);
#endif // DVX_DIALOG_H #endif // DVX_DIALOG_H

View file

@ -332,8 +332,6 @@ bool prefsSaveAs(const char *filename) {
return false; return false;
} }
const char *lastSection = "";
for (int32_t i = 0; i < arrlen(sEntries); i++) { for (int32_t i = 0; i < arrlen(sEntries); i++) {
PrefsEntryT *e = &sEntries[i]; PrefsEntryT *e = &sEntries[i];
@ -346,7 +344,6 @@ bool prefsSaveAs(const char *filename) {
// Section header (key=NULL, value=NULL) // Section header (key=NULL, value=NULL)
if (!e->key && !e->value) { if (!e->key && !e->value) {
fprintf(fp, "[%s]\r\n", e->section); fprintf(fp, "[%s]\r\n", e->section);
lastSection = e->section;
continue; continue;
} }

View file

@ -530,4 +530,74 @@ void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT
void wgtRegisterApi(const char *name, const void *api); void wgtRegisterApi(const char *name, const void *api);
const void *wgtGetApi(const char *name); const void *wgtGetApi(const char *name);
// ============================================================
// Widget interface descriptors
// ============================================================
//
// Each widget DXE can register an interface descriptor that
// describes its BASIC-facing properties, methods, and events.
// The form runtime and IDE use these for generic dispatch and
// property panel enumeration.
// Property data types
#define WGT_IFACE_STRING 0
#define WGT_IFACE_INT 1
#define WGT_IFACE_BOOL 2
#define WGT_IFACE_FLOAT 3
// Method calling conventions (how the form runtime marshals args)
#define WGT_SIG_VOID 0 // void fn(WidgetT *)
#define WGT_SIG_INT 1 // void fn(WidgetT *, int32_t)
#define WGT_SIG_BOOL 2 // void fn(WidgetT *, bool)
#define WGT_SIG_STR 3 // void fn(WidgetT *, const char *)
#define WGT_SIG_INT_INT 4 // void fn(WidgetT *, int32_t, int32_t)
#define WGT_SIG_INT_BOOL 5 // void fn(WidgetT *, int32_t, bool)
#define WGT_SIG_RET_INT 6 // int32_t fn(const WidgetT *)
#define WGT_SIG_RET_BOOL 7 // bool fn(const WidgetT *)
#define WGT_SIG_RET_BOOL_INT 8 // bool fn(const WidgetT *, int32_t)
// Property descriptor
typedef struct {
const char *name; // BASIC property name (e.g. "Caption", "Value")
uint8_t type; // WGT_IFACE_*
void *getFn; // getter function pointer (NULL if write-only)
void *setFn; // setter function pointer (NULL if read-only)
} WgtPropDescT;
// Method descriptor
typedef struct {
const char *name; // BASIC method name (e.g. "Clear", "SetFocus")
uint8_t sig; // WGT_SIG_*
void *fn; // function pointer
} WgtMethodDescT;
// Event descriptor
typedef struct {
const char *name; // event name (e.g. "Click", "Change")
} WgtEventDescT;
// Common events implicitly available on all widgets.
// The form runtime wires these callbacks on every control.
// Widget descriptors only need to list EXTRA events beyond these.
// Click, DblClick, Change, GotFocus, LostFocus
// Widget interface descriptor (registered by each .wgt)
typedef struct {
const char *basName; // VB-style name (e.g. "CommandButton"), or NULL
const WgtPropDescT *props; // type-specific properties
int32_t propCount;
const WgtMethodDescT *methods; // type-specific methods
int32_t methodCount;
const WgtEventDescT *events; // extra events beyond common set
int32_t eventCount;
} WgtIfaceT;
// Register/retrieve interface descriptors by widget type name.
void wgtRegisterIface(const char *name, const WgtIfaceT *iface);
const WgtIfaceT *wgtGetIface(const char *name);
// Find a widget type name by its VB-style name (e.g. "CommandButton" -> "button").
// Returns NULL if no widget has that basName. Case-insensitive.
const char *wgtFindByBasName(const char *basName);
#endif // DVX_WIDGET_H #endif // DVX_WIDGET_H

View file

@ -15,6 +15,7 @@
#include "stb_ds.h" #include "stb_ds.h"
#include <string.h> #include <string.h>
#include <strings.h>
// stb_ds dynamic array of class pointers. Grows on each // stb_ds dynamic array of class pointers. Grows on each
// wgtRegisterClass() call. Index = type ID. // wgtRegisterClass() call. Index = type ID.
@ -28,6 +29,14 @@ typedef struct {
static ApiMapEntryT *sApiMap = NULL; static ApiMapEntryT *sApiMap = NULL;
// stb_ds string hashmap: key = widget name, value = interface descriptor
typedef struct {
char *key;
const WgtIfaceT *value;
} IfaceMapEntryT;
static IfaceMapEntryT *sIfaceMap = NULL;
// ============================================================ // ============================================================
// wgtGetApi // wgtGetApi
@ -52,6 +61,51 @@ const void *wgtGetApi(const char *name) {
} }
// ============================================================
// wgtFindByBasName
// ============================================================
//
// Scan all registered interfaces for one whose basName matches
// (case-insensitive). Returns the widget type name, or NULL.
const char *wgtFindByBasName(const char *basName) {
if (!basName || !sIfaceMap) {
return NULL;
}
for (size_t i = 0; i < shlenu(sIfaceMap); i++) {
if (sIfaceMap[i].value && sIfaceMap[i].value->basName) {
if (strcasecmp(sIfaceMap[i].value->basName, basName) == 0) {
return sIfaceMap[i].key;
}
}
}
return NULL;
}
// ============================================================
// wgtGetIface
// ============================================================
//
// Look up a widget interface descriptor by type name.
const WgtIfaceT *wgtGetIface(const char *name) {
if (!name) {
return NULL;
}
int32_t idx = shgeti(sIfaceMap, name);
if (idx < 0) {
return NULL;
}
return sIfaceMap[idx].value;
}
// ============================================================ // ============================================================
// wgtRegisterApi // wgtRegisterApi
// ============================================================ // ============================================================
@ -68,6 +122,22 @@ void wgtRegisterApi(const char *name, const void *api) {
} }
// ============================================================
// wgtRegisterIface
// ============================================================
//
// Register a widget's interface descriptor under its type name.
// Called by widget DXEs during wgtRegister().
void wgtRegisterIface(const char *name, const WgtIfaceT *iface) {
if (!name || !iface) {
return;
}
shput(sIfaceMap, name, iface);
}
// ============================================================ // ============================================================
// wgtRegisterClass // wgtRegisterClass
// ============================================================ // ============================================================

View file

@ -18,7 +18,7 @@ OBJDIR = ../obj/dvxbasic
LIBSDIR = ../bin/libs LIBSDIR = ../bin/libs
APPDIR = ../bin/apps/dvxbasic APPDIR = ../bin/apps/dvxbasic
DVXRES = ../bin/dvxres DVXRES = ../bin/dvxres
SAMPLES = samples/hello.bas SAMPLES = samples/hello.bas samples/formtest.bas samples/clickme.bas samples/clickme.frm samples/input.bas
# Runtime library objects (VM + values) # Runtime library objects (VM + values)
RT_OBJS = $(OBJDIR)/vm.o $(OBJDIR)/values.o RT_OBJS = $(OBJDIR)/vm.o $(OBJDIR)/values.o
@ -27,8 +27,11 @@ RT_TARGET = $(LIBSDIR)/basrt.lib
# Compiler objects (only needed by the IDE) # Compiler objects (only needed by the IDE)
COMP_OBJS = $(OBJDIR)/lexer.o $(OBJDIR)/parser.o $(OBJDIR)/codegen.o $(OBJDIR)/symtab.o COMP_OBJS = $(OBJDIR)/lexer.o $(OBJDIR)/parser.o $(OBJDIR)/codegen.o $(OBJDIR)/symtab.o
# Form runtime objects (bridge between BASIC and DVX widgets)
FORMRT_OBJS = $(OBJDIR)/formrt.o
# IDE app objects # IDE app objects
APP_OBJS = $(OBJDIR)/ideMain.o APP_OBJS = $(OBJDIR)/ideMain.o $(FORMRT_OBJS)
APP_TARGET = $(APPDIR)/dvxbasic.app APP_TARGET = $(APPDIR)/dvxbasic.app
.PHONY: all clean .PHONY: all clean
@ -54,7 +57,10 @@ install-samples: $(SAMPLES) | $(APPDIR)
cp $(SAMPLES) $(APPDIR)/ cp $(SAMPLES) $(APPDIR)/
# Object files # Object files
$(OBJDIR)/codegen.o: compiler/codegen.c compiler/codegen.h compiler/opcodes.h runtime/values.h | $(OBJDIR) $(OBJDIR)/codegen.o: compiler/codegen.c compiler/codegen.h compiler/symtab.h compiler/opcodes.h runtime/values.h | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/formrt.o: formrt/formrt.c formrt/formrt.h compiler/codegen.h runtime/vm.h | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $< $(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/ideMain.o: ide/ideMain.c compiler/parser.h runtime/vm.h | $(OBJDIR) $(OBJDIR)/ideMain.o: ide/ideMain.c compiler/parser.h runtime/vm.h | $(OBJDIR)
@ -86,4 +92,4 @@ $(APPDIR):
mkdir -p $(APPDIR) mkdir -p $(APPDIR)
clean: clean:
rm -f $(RT_OBJS) $(COMP_OBJS) $(APP_OBJS) $(RT_TARGET) $(APP_TARGET) $(LIBSDIR)/basrt.dep $(OBJDIR)/basrt_init.o rm -f $(RT_OBJS) $(COMP_OBJS) $(FORMRT_OBJS) $(APP_OBJS) $(RT_TARGET) $(APP_TARGET) $(LIBSDIR)/basrt.dep $(OBJDIR)/basrt_init.o

View file

@ -1,10 +1,12 @@
// codegen.c -- DVX BASIC p-code emitter implementation // codegen.c -- DVX BASIC p-code emitter implementation
#include "codegen.h" #include "codegen.h"
#include "symtab.h"
#include "opcodes.h" #include "opcodes.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <strings.h>
// ============================================================ // ============================================================
// basAddData // basAddData
@ -105,6 +107,61 @@ BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg) {
} }
// ============================================================
// basCodeGenBuildModuleWithProcs
// ============================================================
BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab) {
BasModuleT *mod = basCodeGenBuildModule(cg);
if (!mod || !symtab) {
return mod;
}
BasSymTabT *tab = (BasSymTabT *)symtab;
// Count SUB/FUNCTION entries
int32_t procCount = 0;
for (int32_t i = 0; i < tab->count; i++) {
BasSymbolT *s = &tab->symbols[i];
if ((s->kind == SYM_SUB || s->kind == SYM_FUNCTION) && s->isDefined && !s->isExtern) {
procCount++;
}
}
if (procCount == 0) {
return mod;
}
mod->procs = (BasProcEntryT *)malloc(procCount * sizeof(BasProcEntryT));
if (!mod->procs) {
return mod;
}
int32_t idx = 0;
for (int32_t i = 0; i < tab->count; i++) {
BasSymbolT *s = &tab->symbols[i];
if ((s->kind == SYM_SUB || s->kind == SYM_FUNCTION) && s->isDefined && !s->isExtern) {
BasProcEntryT *p = &mod->procs[idx++];
strncpy(p->name, s->name, BAS_MAX_PROC_NAME - 1);
p->name[BAS_MAX_PROC_NAME - 1] = '\0';
p->codeAddr = s->codeAddr;
p->paramCount = s->paramCount;
p->returnType = s->dataType;
p->isFunction = (s->kind == SYM_FUNCTION);
}
}
mod->procCount = idx;
return mod;
}
// ============================================================ // ============================================================
// basCodeGenFree // basCodeGenFree
// ============================================================ // ============================================================
@ -201,6 +258,25 @@ void basEmitU16(BasCodeGenT *cg, uint16_t v) {
} }
// ============================================================
// basModuleFindProc
// ============================================================
const BasProcEntryT *basModuleFindProc(const BasModuleT *mod, const char *name) {
if (!mod || !mod->procs || !name) {
return NULL;
}
for (int32_t i = 0; i < mod->procCount; i++) {
if (strcasecmp(mod->procs[i].name, name) == 0) {
return &mod->procs[i];
}
}
return NULL;
}
// ============================================================ // ============================================================
// basModuleFree // basModuleFree
// ============================================================ // ============================================================
@ -228,6 +304,7 @@ void basModuleFree(BasModuleT *mod) {
free(mod->dataPool); free(mod->dataPool);
} }
free(mod->procs);
free(mod); free(mod);
} }

View file

@ -70,7 +70,15 @@ bool basAddData(BasCodeGenT *cg, BasValueT val);
// ownership of the module and must free it with basModuleFree(). // ownership of the module and must free it with basModuleFree().
BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg); BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg);
// Build a module with procedure table from parser symbol table.
// symtab is a BasSymTabT* (cast to void* to avoid circular include).
BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab);
// Free a module built by basCodeGenBuildModule. // Free a module built by basCodeGenBuildModule.
void basModuleFree(BasModuleT *mod); void basModuleFree(BasModuleT *mod);
// Find a procedure by name in a module's procedure table.
// Case-insensitive. Returns NULL if not found.
const BasProcEntryT *basModuleFindProc(const BasModuleT *mod, const char *name);
#endif // DVXBASIC_CODEGEN_H #endif // DVXBASIC_CODEGEN_H

View file

@ -498,7 +498,7 @@ static char peekNext(const BasLexerT *lex) {
// ============================================================ // ============================================================
static void setError(BasLexerT *lex, const char *msg) { static void setError(BasLexerT *lex, const char *msg) {
snprintf(lex->error, sizeof(lex->error), "Line %d, Col %d: %s", lex->line, lex->col, msg); snprintf(lex->error, sizeof(lex->error), "Line %d, Col %d: %s", (int)lex->line, (int)lex->col, msg);
} }

View file

@ -19,6 +19,7 @@
#define BAS_TYPE_ARRAY 6 // ref-counted array #define BAS_TYPE_ARRAY 6 // ref-counted array
#define BAS_TYPE_UDT 7 // ref-counted user-defined type #define BAS_TYPE_UDT 7 // ref-counted user-defined type
#define BAS_TYPE_OBJECT 8 // opaque host object (form, control, etc.) #define BAS_TYPE_OBJECT 8 // opaque host object (form, control, etc.)
#define BAS_TYPE_REF 9 // ByRef pointer to a BasValueT slot
// ============================================================ // ============================================================
// Stack operations // Stack operations
@ -130,6 +131,7 @@
#define OP_RET_VAL 0x56 // return from function (value on stack) #define OP_RET_VAL 0x56 // return from function (value on stack)
#define OP_FOR_INIT 0x57 // [uint16 varIdx] [uint8 isLocal] init FOR #define OP_FOR_INIT 0x57 // [uint16 varIdx] [uint8 isLocal] init FOR
#define OP_FOR_NEXT 0x58 // [uint16 varIdx] [uint8 isLocal] [int16 loopTop] #define OP_FOR_NEXT 0x58 // [uint16 varIdx] [uint8 isLocal] [int16 loopTop]
#define OP_FOR_POP 0x59 // pop top FOR stack entry (for EXIT FOR)
// ============================================================ // ============================================================
// Type conversion // Type conversion

View file

@ -63,6 +63,13 @@ static const BuiltinFuncT builtinFuncs[] = {
{"LOC", OP_FILE_LOC, 1, 1, BAS_TYPE_LONG}, {"LOC", OP_FILE_LOC, 1, 1, BAS_TYPE_LONG},
{"LOF", OP_FILE_LOF, 1, 1, BAS_TYPE_LONG}, {"LOF", OP_FILE_LOF, 1, 1, BAS_TYPE_LONG},
// Conversion functions
{"CDBL", OP_CONV_INT_FLT, 1, 1, BAS_TYPE_DOUBLE},
{"CINT", OP_CONV_FLT_INT, 1, 1, BAS_TYPE_INTEGER},
{"CLNG", OP_CONV_INT_LONG, 1, 1, BAS_TYPE_LONG},
{"CSNG", OP_CONV_INT_FLT, 1, 1, BAS_TYPE_SINGLE},
{"CSTR", OP_CONV_INT_STR, 1, 1, BAS_TYPE_STRING},
// Math functions // Math functions
{"ABS", OP_MATH_ABS, 1, 1, BAS_TYPE_DOUBLE}, {"ABS", OP_MATH_ABS, 1, 1, BAS_TYPE_DOUBLE},
{"ATN", OP_MATH_ATN, 1, 1, BAS_TYPE_DOUBLE}, {"ATN", OP_MATH_ATN, 1, 1, BAS_TYPE_DOUBLE},
@ -90,6 +97,7 @@ static void advance(BasParserT *p);
static bool check(BasParserT *p, BasTokenTypeE type); static bool check(BasParserT *p, BasTokenTypeE type);
static bool checkKeyword(BasParserT *p, const char *kw); static bool checkKeyword(BasParserT *p, const char *kw);
static bool checkKeywordText(const char *text, const char *kw); static bool checkKeywordText(const char *text, const char *kw);
static void emitByRefArg(BasParserT *p);
static void error(BasParserT *p, const char *msg); static void error(BasParserT *p, const char *msg);
static void errorExpected(BasParserT *p, const char *what); static void errorExpected(BasParserT *p, const char *what);
static void expect(BasParserT *p, BasTokenTypeE type); static void expect(BasParserT *p, BasTokenTypeE type);
@ -255,7 +263,7 @@ static void error(BasParserT *p, const char *msg) {
} }
p->hasError = true; p->hasError = true;
p->errorLine = p->lex.token.line; p->errorLine = p->lex.token.line;
snprintf(p->error, sizeof(p->error), "Line %d: %s", p->lex.token.line, msg); snprintf(p->error, sizeof(p->error), "Line %d: %s", (int)p->lex.token.line, msg);
} }
@ -472,10 +480,18 @@ static void emitFunctionCall(BasParserT *p, BasSymbolT *sym) {
expect(p, TOK_LPAREN); expect(p, TOK_LPAREN);
int32_t argc = 0; int32_t argc = 0;
if (!check(p, TOK_RPAREN)) { if (!check(p, TOK_RPAREN)) {
parseExpression(p); if (argc < sym->paramCount && !sym->paramByVal[argc]) {
emitByRefArg(p);
} else {
parseExpression(p);
}
argc++; argc++;
while (match(p, TOK_COMMA)) { while (match(p, TOK_COMMA)) {
parseExpression(p); if (argc < sym->paramCount && !sym->paramByVal[argc]) {
emitByRefArg(p);
} else {
parseExpression(p);
}
argc++; argc++;
} }
} }
@ -487,7 +503,7 @@ static void emitFunctionCall(BasParserT *p, BasSymbolT *sym) {
if (argc != sym->paramCount) { if (argc != sym->paramCount) {
char buf[256]; char buf[256];
snprintf(buf, sizeof(buf), "Function '%s' expects %d arguments, got %d", sym->name, sym->paramCount, argc); snprintf(buf, sizeof(buf), "Function '%s' expects %d arguments, got %d", sym->name, (int)sym->paramCount, (int)argc);
error(p, buf); error(p, buf);
return; return;
} }
@ -611,6 +627,64 @@ static void emitStore(BasParserT *p, BasSymbolT *sym) {
} }
// Try to emit a ByRef argument (push address of variable).
// If the current token is a simple variable name not followed by
// '(' or '.', we emit PUSH_LOCAL_ADDR/PUSH_GLOBAL_ADDR.
// Otherwise, we fall back to parseExpression (effectively ByVal).
static void emitByRefArg(BasParserT *p) {
if (!check(p, TOK_IDENT)) {
parseExpression(p);
return;
}
// Save the identifier name before peeking ahead
char name[BAS_MAX_TOKEN_LEN];
strncpy(name, p->lex.token.text, sizeof(name) - 1);
name[sizeof(name) - 1] = '\0';
// Look up the symbol -- must be a simple variable (not array, not const)
BasSymbolT *sym = basSymTabFind(&p->sym, name);
if (!sym || sym->kind != SYM_VARIABLE || sym->isArray) {
parseExpression(p);
return;
}
// Save lexer state to peek at what follows the identifier
int32_t savedPos = p->lex.pos;
int32_t savedLine = p->lex.line;
int32_t savedCol = p->lex.col;
BasTokenT savedTok = p->lex.token;
advance(p); // consume the identifier
// The token after the identifier must be an argument delimiter
// (comma, rparen, newline, colon, EOF, ELSE) for this to be a
// bare variable reference. Anything else (operator, dot, paren)
// means it's part of an expression -- fall back to parseExpression.
bool isDelim = check(p, TOK_COMMA) || check(p, TOK_RPAREN) || check(p, TOK_NEWLINE) || check(p, TOK_COLON) || check(p, TOK_EOF) || check(p, TOK_ELSE);
if (!isDelim) {
// Restore and let parseExpression handle the full expression
p->lex.pos = savedPos;
p->lex.line = savedLine;
p->lex.col = savedCol;
p->lex.token = savedTok;
parseExpression(p);
return;
}
// It's a bare variable reference -- push its address
if (sym->scope == SCOPE_LOCAL) {
basEmit8(&p->cg, OP_PUSH_LOCAL_ADDR);
} else {
basEmit8(&p->cg, OP_PUSH_GLOBAL_ADDR);
}
basEmitU16(&p->cg, (uint16_t)sym->index);
}
static BasSymbolT *ensureVariable(BasParserT *p, const char *name) { static BasSymbolT *ensureVariable(BasParserT *p, const char *name) {
BasSymbolT *sym = basSymTabFind(&p->sym, name); BasSymbolT *sym = basSymTabFind(&p->sym, name);
if (sym != NULL) { if (sym != NULL) {
@ -952,6 +1026,13 @@ static void parsePrimary(BasParserT *p) {
return; return;
} }
// Me -- reference to current form
if (tt == TOK_ME) {
advance(p);
basEmit8(&p->cg, OP_ME_REF);
return;
}
// EOF(#channel) -- file end-of-file test // EOF(#channel) -- file end-of-file test
if (tt == TOK_EOF_KW) { if (tt == TOK_EOF_KW) {
advance(p); advance(p);
@ -1101,7 +1182,7 @@ static void parsePrimary(BasParserT *p) {
if (argc < builtin->minArgs || argc > builtin->maxArgs) { if (argc < builtin->minArgs || argc > builtin->maxArgs) {
char buf[256]; char buf[256];
snprintf(buf, sizeof(buf), "Built-in '%s' expects %d-%d arguments, got %d", builtin->name, builtin->minArgs, builtin->maxArgs, argc); snprintf(buf, sizeof(buf), "Built-in '%s' expects %d-%d arguments, got %d", builtin->name, (int)builtin->minArgs, (int)builtin->maxArgs, (int)argc);
error(p, buf); error(p, buf);
return; return;
} }
@ -1162,9 +1243,10 @@ static void parsePrimary(BasParserT *p) {
return; return;
} }
// Check for UDT field access: var.field // Check for dot access: UDT field or control property
if (check(p, TOK_DOT)) { if (check(p, TOK_DOT)) {
sym = ensureVariable(p, name); // If we already know this is a UDT variable, do field access
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 advance(p); // consume DOT
@ -1172,7 +1254,6 @@ static void parsePrimary(BasParserT *p) {
errorExpected(p, "field name"); errorExpected(p, "field name");
return; return;
} }
// Find the TYPE_DEF symbol
BasSymbolT *typeSym = NULL; BasSymbolT *typeSym = NULL;
for (int32_t i = 0; i < p->sym.count; i++) { 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) { if (p->sym.symbols[i].kind == SYM_TYPE_DEF && p->sym.symbols[i].index == sym->udtTypeId) {
@ -1186,7 +1267,7 @@ static void parsePrimary(BasParserT *p) {
} }
int32_t fieldIdx = resolveFieldIndex(typeSym, p->lex.token.text); int32_t fieldIdx = resolveFieldIndex(typeSym, p->lex.token.text);
if (fieldIdx < 0) { if (fieldIdx < 0) {
char buf[256]; char buf[512];
snprintf(buf, sizeof(buf), "Unknown field '%s' in TYPE '%s'", p->lex.token.text, typeSym->name); snprintf(buf, sizeof(buf), "Unknown field '%s' in TYPE '%s'", p->lex.token.text, typeSym->name);
error(p, buf); error(p, buf);
return; return;
@ -1196,6 +1277,32 @@ static void parsePrimary(BasParserT *p) {
basEmitU16(&p->cg, (uint16_t)fieldIdx); basEmitU16(&p->cg, (uint16_t)fieldIdx);
return; return;
} }
// Not a UDT -- treat as control property read: CtrlName.Property
advance(p); // consume DOT
if (!check(p, TOK_IDENT)) {
errorExpected(p, "property name");
return;
}
char memberName[BAS_MAX_TOKEN_LEN];
strncpy(memberName, p->lex.token.text, BAS_MAX_TOKEN_LEN - 1);
memberName[BAS_MAX_TOKEN_LEN - 1] = '\0';
advance(p);
// Emit: push NULL (current form), push ctrl name, FIND_CTRL
basEmit8(&p->cg, OP_PUSH_INT16);
basEmit16(&p->cg, 0);
uint16_t ctrlNameIdx = basAddConstant(&p->cg, name, (int32_t)strlen(name));
basEmit8(&p->cg, OP_PUSH_STR);
basEmitU16(&p->cg, ctrlNameIdx);
basEmit8(&p->cg, OP_FIND_CTRL);
// Push property name, LOAD_PROP
uint16_t propNameIdx = basAddConstant(&p->cg, memberName, (int32_t)strlen(memberName));
basEmit8(&p->cg, OP_PUSH_STR);
basEmitU16(&p->cg, propNameIdx);
basEmit8(&p->cg, OP_LOAD_PROP);
return;
} }
// Plain variable reference // Plain variable reference
@ -1271,15 +1378,10 @@ static void parseAssignOrCall(BasParserT *p) {
BasSymbolT *sym = basSymTabFind(&p->sym, name); BasSymbolT *sym = basSymTabFind(&p->sym, name);
// UDT field assignment: var.field = expr // Dot member access: UDT field or control property/method
if (check(p, TOK_DOT)) { if (check(p, TOK_DOT)) {
if (sym == NULL) { // Check for UDT field access first
sym = ensureVariable(p, name); if (sym != NULL && sym->dataType == BAS_TYPE_UDT && sym->udtTypeId >= 0) {
}
if (sym == NULL) {
return;
}
if (sym->dataType == BAS_TYPE_UDT && sym->udtTypeId >= 0) {
emitLoad(p, sym); emitLoad(p, sym);
advance(p); // consume DOT advance(p); // consume DOT
if (!check(p, TOK_IDENT)) { if (!check(p, TOK_IDENT)) {
@ -1299,7 +1401,7 @@ static void parseAssignOrCall(BasParserT *p) {
} }
int32_t fieldIdx = resolveFieldIndex(typeSym, p->lex.token.text); int32_t fieldIdx = resolveFieldIndex(typeSym, p->lex.token.text);
if (fieldIdx < 0) { if (fieldIdx < 0) {
char buf[256]; char buf[512];
snprintf(buf, sizeof(buf), "Unknown field '%s' in TYPE '%s'", p->lex.token.text, typeSym->name); snprintf(buf, sizeof(buf), "Unknown field '%s' in TYPE '%s'", p->lex.token.text, typeSym->name);
error(p, buf); error(p, buf);
return; return;
@ -1311,6 +1413,109 @@ static void parseAssignOrCall(BasParserT *p) {
basEmitU16(&p->cg, (uint16_t)fieldIdx); basEmitU16(&p->cg, (uint16_t)fieldIdx);
return; return;
} }
// Control property/method access: CtrlName.Member
// Emit: push current form ref, push ctrl name, FIND_CTRL
advance(p); // consume DOT
if (!check(p, TOK_IDENT) && !check(p, TOK_SHOW) && !check(p, TOK_HIDE)) {
errorExpected(p, "property or method name");
return;
}
char memberName[BAS_MAX_TOKEN_LEN];
strncpy(memberName, p->lex.token.text, BAS_MAX_TOKEN_LEN - 1);
memberName[BAS_MAX_TOKEN_LEN - 1] = '\0';
advance(p); // consume member name
// Special form methods: Show, Hide
if (strcasecmp(memberName, "Show") == 0) {
// name.Show [modal]
// Push form name, LOAD_FORM, SHOW_FORM
uint16_t nameIdx = basAddConstant(&p->cg, name, (int32_t)strlen(name));
basEmit8(&p->cg, OP_PUSH_STR);
basEmitU16(&p->cg, nameIdx);
basEmit8(&p->cg, OP_LOAD_FORM);
uint8_t modal = 0;
if (check(p, TOK_INT_LIT) || check(p, TOK_IDENT)) {
// Parse modal flag
if (check(p, TOK_INT_LIT) && p->lex.token.intVal != 0) {
modal = 1;
}
advance(p);
}
basEmit8(&p->cg, OP_SHOW_FORM);
basEmit8(&p->cg, modal);
return;
}
if (strcasecmp(memberName, "Hide") == 0) {
uint16_t nameIdx = basAddConstant(&p->cg, name, (int32_t)strlen(name));
basEmit8(&p->cg, OP_PUSH_STR);
basEmitU16(&p->cg, nameIdx);
basEmit8(&p->cg, OP_LOAD_FORM);
basEmit8(&p->cg, OP_HIDE_FORM);
return;
}
if (check(p, TOK_EQ)) {
// Property assignment: CtrlName.Property = expr
advance(p); // consume =
// Push ctrl ref: push form ref (NULL = current), push ctrl name, FIND_CTRL
basEmit8(&p->cg, OP_PUSH_INT16);
basEmit16(&p->cg, 0);
BasValueT formNull;
memset(&formNull, 0, sizeof(formNull));
// Use OP_PUSH_STR for ctrl name, then FIND_CTRL
uint16_t ctrlNameIdx = basAddConstant(&p->cg, name, (int32_t)strlen(name));
basEmit8(&p->cg, OP_PUSH_STR);
basEmitU16(&p->cg, ctrlNameIdx);
basEmit8(&p->cg, OP_FIND_CTRL);
// Push property name
uint16_t propNameIdx = basAddConstant(&p->cg, memberName, (int32_t)strlen(memberName));
basEmit8(&p->cg, OP_PUSH_STR);
basEmitU16(&p->cg, propNameIdx);
// Parse value expression
parseExpression(p);
// Store property
basEmit8(&p->cg, OP_STORE_PROP);
return;
}
// Method call: CtrlName.Method [args]
// Push ctrl ref
basEmit8(&p->cg, OP_PUSH_INT16);
basEmit16(&p->cg, 0);
uint16_t ctrlNameIdx = basAddConstant(&p->cg, name, (int32_t)strlen(name));
basEmit8(&p->cg, OP_PUSH_STR);
basEmitU16(&p->cg, ctrlNameIdx);
basEmit8(&p->cg, OP_FIND_CTRL);
// Push method name
uint16_t methodNameIdx = basAddConstant(&p->cg, memberName, (int32_t)strlen(memberName));
basEmit8(&p->cg, OP_PUSH_STR);
basEmitU16(&p->cg, methodNameIdx);
// Parse arguments (space-separated, like VB)
int32_t argc = 0;
while (!check(p, TOK_NEWLINE) && !check(p, TOK_COLON) && !check(p, TOK_EOF) && !check(p, TOK_ELSE)) {
if (argc > 0) {
if (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); // discard return value (statement form)
return;
} }
// Array assignment: var(index) = expr // Array assignment: var(index) = expr
@ -1382,16 +1587,24 @@ static void parseAssignOrCall(BasParserT *p) {
if (sym != NULL && sym->kind == SYM_SUB) { if (sym != NULL && 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)) {
parseExpression(p); if (argc < sym->paramCount && !sym->paramByVal[argc]) {
emitByRefArg(p);
} else {
parseExpression(p);
}
argc++; argc++;
while (match(p, TOK_COMMA)) { while (match(p, TOK_COMMA)) {
parseExpression(p); if (argc < sym->paramCount && !sym->paramByVal[argc]) {
emitByRefArg(p);
} else {
parseExpression(p);
}
argc++; argc++;
} }
} }
if (!p->hasError && argc != sym->paramCount) { if (!p->hasError && argc != sym->paramCount) {
char buf[256]; char buf[256];
snprintf(buf, sizeof(buf), "Sub '%s' expects %d arguments, got %d", sym->name, sym->paramCount, argc); snprintf(buf, sizeof(buf), "Sub '%s' expects %d arguments, got %d", sym->name, (int)sym->paramCount, (int)argc);
error(p, buf); error(p, buf);
return; return;
} }
@ -2283,6 +2496,7 @@ static void parseExit(BasParserT *p) {
if (check(p, TOK_FOR)) { if (check(p, TOK_FOR)) {
advance(p); advance(p);
basEmit8(&p->cg, OP_FOR_POP);
int32_t addr = emitJump(p, OP_JMP); int32_t addr = emitJump(p, OP_JMP);
exitListAdd(&exitForList, addr); exitListAdd(&exitForList, addr);
} else if (check(p, TOK_DO)) { } else if (check(p, TOK_DO)) {
@ -3494,15 +3708,15 @@ static void parseSelectCase(BasParserT *p) {
continue; continue;
} }
// CASE val [, val] ... // CASE val [, val | val TO val | IS op val] ...
// //
// Strategy for multi-value CASE using JMP_TRUE chaining: // Strategy for multi-value CASE using JMP_TRUE chaining:
// For each value: // For each item:
// DUP testval // Plain value: DUP, push val, CMP_EQ, JMP_TRUE -> body
// push value // Range (val TO val): DUP, push lo, CMP_GE, JMP_FALSE -> skip,
// CMP_EQ // DUP, push hi, CMP_LE, JMP_TRUE -> body, skip:
// JMP_TRUE -> body // IS op val: DUP, push val, CMP_xx, JMP_TRUE -> body
// JMP -> next_case (none of the values matched) // JMP -> next_case (none of the items matched)
// body: // body:
// ...statements... // ...statements...
// JMP -> end_select // JMP -> end_select
@ -3511,23 +3725,67 @@ static void parseSelectCase(BasParserT *p) {
int32_t bodyJumps[MAX_EXITS]; int32_t bodyJumps[MAX_EXITS];
int32_t bodyJumpCount = 0; int32_t bodyJumpCount = 0;
// First value for (;;) {
basEmit8(&p->cg, OP_DUP); if (check(p, TOK_IS)) {
parseExpression(p); // CASE IS <op> value
basEmit8(&p->cg, OP_CMP_EQ); advance(p); // consume IS
if (bodyJumpCount < MAX_EXITS) { uint8_t cmpOp;
bodyJumps[bodyJumpCount++] = emitJump(p, OP_JMP_TRUE);
}
// Additional comma-separated values if (check(p, TOK_LT)) { cmpOp = OP_CMP_LT; advance(p); }
while (!p->hasError && match(p, TOK_COMMA)) { else if (check(p, TOK_GT)) { cmpOp = OP_CMP_GT; advance(p); }
basEmit8(&p->cg, OP_DUP); else if (check(p, TOK_LE)) { cmpOp = OP_CMP_LE; advance(p); }
parseExpression(p); else if (check(p, TOK_GE)) { cmpOp = OP_CMP_GE; advance(p); }
basEmit8(&p->cg, OP_CMP_EQ); else if (check(p, TOK_EQ)) { cmpOp = OP_CMP_EQ; advance(p); }
else if (check(p, TOK_NE)) { cmpOp = OP_CMP_NE; advance(p); }
else {
error(p, "Expected comparison operator after IS");
return;
}
if (bodyJumpCount < MAX_EXITS) { basEmit8(&p->cg, OP_DUP);
bodyJumps[bodyJumpCount++] = emitJump(p, OP_JMP_TRUE); parseExpression(p);
basEmit8(&p->cg, cmpOp);
if (bodyJumpCount < MAX_EXITS) {
bodyJumps[bodyJumpCount++] = emitJump(p, OP_JMP_TRUE);
}
} else {
// Parse first value -- could be plain or start of range
basEmit8(&p->cg, OP_DUP);
parseExpression(p);
if (check(p, TOK_TO)) {
// CASE low TO high
advance(p); // consume TO
// Stack: testval testval low
// Check testval >= low
basEmit8(&p->cg, OP_CMP_GE);
int32_t skipRange = emitJump(p, OP_JMP_FALSE);
// Check testval <= high
basEmit8(&p->cg, OP_DUP);
parseExpression(p);
basEmit8(&p->cg, OP_CMP_LE);
if (bodyJumpCount < MAX_EXITS) {
bodyJumps[bodyJumpCount++] = emitJump(p, OP_JMP_TRUE);
}
patchJump(p, skipRange);
} else {
// Plain value -- equality test
basEmit8(&p->cg, OP_CMP_EQ);
if (bodyJumpCount < MAX_EXITS) {
bodyJumps[bodyJumpCount++] = emitJump(p, OP_JMP_TRUE);
}
}
}
if (!match(p, TOK_COMMA)) {
break;
} }
} }
@ -3652,8 +3910,12 @@ static void parseStatic(BasParserT *p) {
} }
// Create a mangled global name: "procName$varName" // Create a mangled global name: "procName$varName"
char mangledName[BAS_MAX_SYMBOL_NAME]; // Truncation is intentional -- symbol names are clamped to BAS_MAX_SYMBOL_NAME.
char mangledName[BAS_MAX_SYMBOL_NAME * 2 + 1];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
snprintf(mangledName, sizeof(mangledName), "%s$%s", p->currentProc, varName); snprintf(mangledName, sizeof(mangledName), "%s$%s", p->currentProc, varName);
#pragma GCC diagnostic pop
// Create the global variable with the mangled name // Create the global variable with the mangled name
bool savedLocal = p->sym.inLocalScope; bool savedLocal = p->sym.inLocalScope;
@ -3787,6 +4049,13 @@ static void parseStatement(BasParserT *p) {
parseEnd(p); parseEnd(p);
break; break;
case TOK_ERROR_KW:
// ERROR n -- raise a runtime error
advance(p);
parseExpression(p);
basEmit8(&p->cg, OP_RAISE_ERR);
break;
case TOK_ERASE: case TOK_ERASE:
parseErase(p); parseErase(p);
break; break;
@ -3937,6 +4206,48 @@ static void parseStatement(BasParserT *p) {
basEmit8(&p->cg, OP_DO_EVENTS); basEmit8(&p->cg, OP_DO_EVENTS);
break; break;
case TOK_LOAD:
// Load FormName (identifier, not string)
advance(p);
if (!check(p, TOK_IDENT)) {
errorExpected(p, "form name");
break;
}
{
uint16_t nameIdx = basAddConstant(&p->cg, p->lex.token.text, (int32_t)strlen(p->lex.token.text));
advance(p);
basEmit8(&p->cg, OP_PUSH_STR);
basEmitU16(&p->cg, nameIdx);
basEmit8(&p->cg, OP_LOAD_FORM);
basEmit8(&p->cg, OP_POP);
}
break;
case TOK_UNLOAD:
// Unload FormName (identifier, not string)
advance(p);
if (!check(p, TOK_IDENT)) {
errorExpected(p, "form name");
break;
}
{
uint16_t nameIdx = basAddConstant(&p->cg, p->lex.token.text, (int32_t)strlen(p->lex.token.text));
advance(p);
basEmit8(&p->cg, OP_PUSH_STR);
basEmitU16(&p->cg, nameIdx);
basEmit8(&p->cg, OP_LOAD_FORM);
basEmit8(&p->cg, OP_UNLOAD_FORM);
}
break;
case TOK_MSGBOX:
advance(p);
parseExpression(p);
basEmit8(&p->cg, OP_MSGBOX);
basEmit8(&p->cg, 0);
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)) {
@ -3973,7 +4284,7 @@ static void parseStatement(BasParserT *p) {
sym->isDefined = true; sym->isDefined = true;
sym->codeAddr = basCodePos(&p->cg); sym->codeAddr = basCodePos(&p->cg);
} else { } else {
char buf[256]; char buf[512];
snprintf(buf, sizeof(buf), "Name '%s' already used", labelName); snprintf(buf, sizeof(buf), "Name '%s' already used", labelName);
error(p, buf); error(p, buf);
} }
@ -4208,8 +4519,11 @@ static void parseType(BasParserT *p) {
} }
BasFieldDefT *field = &typeSym->fields[typeSym->fieldCount]; BasFieldDefT *field = &typeSym->fields[typeSym->fieldCount];
strncpy(field->name, p->lex.token.text, BAS_MAX_SYMBOL_NAME - 1); // Truncation is intentional -- field names are clamped to BAS_MAX_SYMBOL_NAME.
field->name[BAS_MAX_SYMBOL_NAME - 1] = '\0'; #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
snprintf(field->name, BAS_MAX_SYMBOL_NAME, "%s", p->lex.token.text);
#pragma GCC diagnostic pop
advance(p); advance(p);
expect(p, TOK_AS); expect(p, TOK_AS);
@ -4484,8 +4798,9 @@ BasModuleT *basParserBuildModule(BasParserT *p) {
if (p->hasError) { if (p->hasError) {
return NULL; return NULL;
} }
p->cg.globalCount = p->sym.nextGlobalIdx; p->cg.globalCount = p->sym.nextGlobalIdx;
return basCodeGenBuildModule(&p->cg); return basCodeGenBuildModuleWithProcs(&p->cg, &p->sym);
} }

View file

@ -25,7 +25,7 @@ typedef struct {
BasLexerT lex; BasLexerT lex;
BasCodeGenT cg; BasCodeGenT cg;
BasSymTabT sym; BasSymTabT sym;
char error[512]; char error[1024];
bool hasError; bool hasError;
int32_t errorLine; int32_t errorLine;
int32_t lastUdtTypeId; // index of last resolved UDT type from resolveTypeName int32_t lastUdtTypeId; // index of last resolved UDT type from resolveTypeName

1619
dvxbasic/formrt/formrt.c Normal file

File diff suppressed because it is too large Load diff

110
dvxbasic/formrt/formrt.h Normal file
View file

@ -0,0 +1,110 @@
// formrt.h -- DVX BASIC form runtime
//
// Bridges BASIC programs to the DVX widget system. Control types
// are resolved dynamically via WgtIfaceT interface descriptors
// registered by .wgt DXE files. Properties and methods are
// dispatched generically through descriptors. Events fire by
// looking up ControlName_EventName in the compiled module's
// procedure table and calling into the VM.
#ifndef DVXBASIC_FORMRT_H
#define DVXBASIC_FORMRT_H
#include "../runtime/vm.h"
#include "../runtime/values.h"
#include "dvxApp.h"
#include "dvxWidget.h"
// ============================================================
// Forward declarations
// ============================================================
typedef struct BasFormT BasFormT;
typedef struct BasControlT BasControlT;
// ============================================================
// Limits
// ============================================================
#define BAS_MAX_CTRL_NAME 32
#define BAS_MAX_CTRLS 64 // max controls per form
#define BAS_MAX_FORMS 8
// ============================================================
// Control instance (a widget on a form)
// ============================================================
#define BAS_MAX_TEXT_BUF 256
typedef struct BasControlT {
char name[BAS_MAX_CTRL_NAME]; // VB control name (e.g. "Command1")
WidgetT *widget; // the DVX widget
BasFormT *form; // owning form
const WgtIfaceT *iface; // interface descriptor (from .wgt)
char textBuf[BAS_MAX_TEXT_BUF]; // persistent text for Caption/Text
} BasControlT;
// ============================================================
// Form instance (a DVX window with controls)
// ============================================================
typedef struct BasFormT {
char name[BAS_MAX_CTRL_NAME]; // form name (e.g. "Form1")
WindowT *window; // DVX window
WidgetT *root; // widget root (from wgtInitWindow)
WidgetT *contentBox; // VBox for user controls
AppContextT *ctx; // DVX app context
BasControlT controls[BAS_MAX_CTRLS]; // controls on this form
int32_t controlCount;
BasVmT *vm; // VM for event dispatch
BasModuleT *module; // compiled module (for SUB lookup)
} BasFormT;
// ============================================================
// Form runtime context
// ============================================================
typedef struct {
AppContextT *ctx; // DVX app context
BasVmT *vm; // shared VM instance
BasModuleT *module; // compiled module
BasFormT forms[BAS_MAX_FORMS];
int32_t formCount;
BasFormT *currentForm; // form currently dispatching events
} BasFormRtT;
// ============================================================
// API
// ============================================================
// Initialize the form runtime with a DVX context and a compiled module.
BasFormRtT *basFormRtCreate(AppContextT *ctx, BasVmT *vm, BasModuleT *module);
// Destroy the form runtime and all forms/controls.
void basFormRtDestroy(BasFormRtT *rt);
// Wire up the VM's UI callbacks to this form runtime.
void basFormRtBindVm(BasFormRtT *rt);
// ---- UI callback implementations (match BasUiCallbacksT) ----
BasValueT basFormRtGetProp(void *ctx, void *ctrlRef, const char *propName);
void basFormRtSetProp(void *ctx, void *ctrlRef, const char *propName, BasValueT value);
BasValueT basFormRtCallMethod(void *ctx, void *ctrlRef, const char *methodName, BasValueT *args, int32_t argc);
void *basFormRtCreateCtrl(void *ctx, void *formRef, const char *typeName, const char *ctrlName);
void *basFormRtFindCtrl(void *ctx, void *formRef, const char *ctrlName);
void *basFormRtLoadForm(void *ctx, const char *formName);
void basFormRtUnloadForm(void *ctx, void *formRef);
void basFormRtShowForm(void *ctx, void *formRef, bool modal);
void basFormRtHideForm(void *ctx, void *formRef);
int32_t basFormRtMsgBox(void *ctx, const char *message, int32_t flags);
// ---- Event dispatch ----
bool basFormRtFireEvent(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName);
// ---- Form file loading ----
BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen);
#endif // DVXBASIC_FORMRT_H

View file

@ -16,9 +16,11 @@
#include "widgetButton.h" #include "widgetButton.h"
#include "widgetLabel.h" #include "widgetLabel.h"
#include "widgetTextInput.h" #include "widgetTextInput.h"
#include "widgetDropdown.h"
#include "widgetStatusBar.h" #include "widgetStatusBar.h"
#include "../compiler/parser.h" #include "../compiler/parser.h"
#include "../formrt/formrt.h"
#include "../runtime/vm.h" #include "../runtime/vm.h"
#include "../runtime/values.h" #include "../runtime/values.h"
@ -27,6 +29,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h>
// ============================================================ // ============================================================
// Constants // Constants
@ -38,6 +41,7 @@
#define IDE_BTN_SPACING 8 #define IDE_BTN_SPACING 8
#define IDE_MAX_SOURCE 65536 #define IDE_MAX_SOURCE 65536
#define IDE_MAX_OUTPUT 32768 #define IDE_MAX_OUTPUT 32768
#define IDE_STEP_SLICE 10000 // VM steps per slice before yielding to DVX
// Menu command IDs // Menu command IDs
#define CMD_OPEN 100 #define CMD_OPEN 100
@ -45,6 +49,10 @@
#define CMD_STOP 102 #define CMD_STOP 102
#define CMD_CLEAR 103 #define CMD_CLEAR 103
#define CMD_EXIT 104 #define CMD_EXIT 104
#define CMD_RUN_NOCMP 105
#define IDE_MAX_PROCS 128
#define IDE_MAX_IMM 1024
// ============================================================ // ============================================================
// Prototypes // Prototypes
@ -59,11 +67,21 @@ static void onClose(WindowT *win);
static void onMenu(WindowT *win, int32_t menuId); static void onMenu(WindowT *win, int32_t menuId);
static void onOpenClick(WidgetT *w); static void onOpenClick(WidgetT *w);
static void onRunClick(WidgetT *w); static void onRunClick(WidgetT *w);
static void onStopClick(WidgetT *w);
static void onClearClick(WidgetT *w); static void onClearClick(WidgetT *w);
static void basicColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx);
static void evaluateImmediate(const char *expr);
static void loadFrmFiles(BasFormRtT *rt);
static void onEvtDropdownChange(WidgetT *w);
static void onImmediateChange(WidgetT *w);
static void onObjDropdownChange(WidgetT *w);
static void printCallback(void *ctx, const char *text, bool newline); static void printCallback(void *ctx, const char *text, bool newline);
static bool inputCallback(void *ctx, const char *prompt, char *buf, int32_t bufSize); static bool inputCallback(void *ctx, const char *prompt, char *buf, int32_t bufSize);
static bool doEventsCallback(void *ctx); static bool doEventsCallback(void *ctx);
static void runCached(void);
static void runModule(BasModuleT *mod);
static void setStatus(const char *text); static void setStatus(const char *text);
static void updateDropdowns(void);
// ============================================================ // ============================================================
// Module state // Module state
@ -72,16 +90,30 @@ static void setStatus(const char *text);
static DxeAppContextT *sCtx = NULL; static DxeAppContextT *sCtx = NULL;
static AppContextT *sAc = NULL; static AppContextT *sAc = NULL;
static WindowT *sWin = NULL; static WindowT *sWin = NULL;
static WidgetT *sEditor = NULL; // TextArea for source code static WidgetT *sEditor = NULL; // TextArea for source code
static WidgetT *sOutput = NULL; // TextArea for program output static WidgetT *sOutput = NULL; // TextArea for program output
static WidgetT *sStatus = NULL; // Status bar label static WidgetT *sImmediate = NULL; // TextArea for immediate window
static BasVmT *sVm = NULL; // VM instance (non-NULL while running) static WidgetT *sObjDropdown = NULL; // Object dropdown
static WidgetT *sEvtDropdown = NULL; // Event dropdown
static WidgetT *sStatus = NULL; // Status bar label
static BasVmT *sVm = NULL; // VM instance (non-NULL while running)
static BasModuleT *sCachedModule = NULL; // Last compiled module (for Ctrl+F5)
static char sSourceBuf[IDE_MAX_SOURCE]; static char sSourceBuf[IDE_MAX_SOURCE];
static char sOutputBuf[IDE_MAX_OUTPUT]; static char sOutputBuf[IDE_MAX_OUTPUT];
static int32_t sOutputLen = 0; static int32_t sOutputLen = 0;
static char sFilePath[260]; static char sFilePath[260];
// Procedure table for Object/Event dropdowns
typedef struct {
char objName[64];
char evtName[64];
int32_t lineNum;
} IdeProcEntryT;
static IdeProcEntryT sProcTable[IDE_MAX_PROCS];
static int32_t sProcCount = 0;
// ============================================================ // ============================================================
// App descriptor // App descriptor
// ============================================================ // ============================================================
@ -140,12 +172,16 @@ static void buildWindow(void) {
MenuT *runMenu = wmAddMenu(menuBar, "&Run"); MenuT *runMenu = wmAddMenu(menuBar, "&Run");
wmAddMenuItem(runMenu, "&Run\tF5", CMD_RUN); wmAddMenuItem(runMenu, "&Run\tF5", CMD_RUN);
wmAddMenuItem(runMenu, "Run &Without Recompile\tCtrl+F5", CMD_RUN_NOCMP);
wmAddMenuItem(runMenu, "&Stop\tEsc", CMD_STOP);
wmAddMenuSeparator(runMenu); wmAddMenuSeparator(runMenu);
wmAddMenuItem(runMenu, "&Clear Output", CMD_CLEAR); wmAddMenuItem(runMenu, "&Clear Output", CMD_CLEAR);
AccelTableT *accel = dvxCreateAccelTable(); AccelTableT *accel = dvxCreateAccelTable();
dvxAddAccel(accel, 'O', ACCEL_CTRL, CMD_OPEN); dvxAddAccel(accel, 'O', ACCEL_CTRL, CMD_OPEN);
dvxAddAccel(accel, KEY_F5, 0, CMD_RUN); dvxAddAccel(accel, KEY_F5, 0, CMD_RUN);
dvxAddAccel(accel, KEY_F5, ACCEL_CTRL, CMD_RUN_NOCMP);
dvxAddAccel(accel, 0x1B, 0, CMD_STOP);
sWin->accelTable = accel; sWin->accelTable = accel;
// Widget tree // Widget tree
@ -154,8 +190,24 @@ static void buildWindow(void) {
// Source code editor (top half) // Source code editor (top half)
WidgetT *editorFrame = wgtFrame(root, "Source"); WidgetT *editorFrame = wgtFrame(root, "Source");
editorFrame->weight = 100; editorFrame->weight = 100;
// Object/Event dropdown row
WidgetT *dropdownRow = wgtHBox(editorFrame);
dropdownRow->spacing = wgtPixels(4);
sObjDropdown = wgtDropdown(dropdownRow);
sObjDropdown->weight = 100;
sObjDropdown->onChange = onObjDropdownChange;
sEvtDropdown = wgtDropdown(dropdownRow);
sEvtDropdown->weight = 100;
sEvtDropdown->onChange = onEvtDropdownChange;
sEditor = wgtTextArea(editorFrame, IDE_MAX_SOURCE); sEditor = wgtTextArea(editorFrame, IDE_MAX_SOURCE);
sEditor->weight = 100; sEditor->weight = 100;
wgtTextAreaSetColorize(sEditor, basicColorize, NULL);
wgtTextAreaSetShowLineNumbers(sEditor, true);
wgtTextAreaSetAutoIndent(sEditor, true);
// Button bar // Button bar
WidgetT *btnRow = wgtHBox(root); WidgetT *btnRow = wgtHBox(root);
@ -169,17 +221,28 @@ static void buildWindow(void) {
runBtn->onClick = onRunClick; runBtn->onClick = onRunClick;
runBtn->prefW = wgtPixels(IDE_BTN_W); runBtn->prefW = wgtPixels(IDE_BTN_W);
WidgetT *stopBtn = wgtButton(btnRow, "&Stop");
stopBtn->onClick = onStopClick;
stopBtn->prefW = wgtPixels(IDE_BTN_W);
WidgetT *clearBtn = wgtButton(btnRow, "&Clear"); WidgetT *clearBtn = wgtButton(btnRow, "&Clear");
clearBtn->onClick = onClearClick; clearBtn->onClick = onClearClick;
clearBtn->prefW = wgtPixels(IDE_BTN_W); clearBtn->prefW = wgtPixels(IDE_BTN_W);
// Output area (bottom half) // Output area
WidgetT *outputFrame = wgtFrame(root, "Output"); WidgetT *outputFrame = wgtFrame(root, "Output");
outputFrame->weight = 100; outputFrame->weight = 80;
sOutput = wgtTextArea(outputFrame, IDE_MAX_OUTPUT); sOutput = wgtTextArea(outputFrame, IDE_MAX_OUTPUT);
sOutput->weight = 100; sOutput->weight = 100;
sOutput->readOnly = true; sOutput->readOnly = true;
// Immediate window
WidgetT *immFrame = wgtFrame(root, "Immediate");
immFrame->weight = 30;
sImmediate = wgtTextArea(immFrame, IDE_MAX_IMM);
sImmediate->weight = 100;
sImmediate->onChange = onImmediateChange;
// Status bar // Status bar
WidgetT *statusBar = wgtStatusBar(root); WidgetT *statusBar = wgtStatusBar(root);
sStatus = wgtLabel(statusBar, ""); sStatus = wgtLabel(statusBar, "");
@ -188,6 +251,163 @@ static void buildWindow(void) {
dvxFitWindow(sAc, sWin); dvxFitWindow(sAc, sWin);
} }
// ============================================================
// basicColorize
// ============================================================
//
// Syntax colorizer callback for BASIC source code. Scans a single
// line and fills the colors array with syntax color indices.
static bool isBasicKeyword(const char *word, int32_t wordLen) {
static const char *keywords[] = {
"AND", "AS", "CALL", "CASE", "CLOSE", "CONST", "DATA", "DECLARE",
"DEF", "DEFINT", "DEFLNG", "DEFSNG", "DEFDBL", "DEFSTR",
"DIM", "DO", "DOEVENTS", "ELSE", "ELSEIF", "END", "ERASE",
"EXIT", "FOR", "FUNCTION", "GET", "GOSUB", "GOTO", "HIDE",
"IF", "IMP", "INPUT", "IS", "LET", "LIBRARY", "LINE", "LOAD",
"LOOP", "MOD", "MSGBOX", "NEXT", "NOT", "ON", "OPEN", "OPTION",
"OR", "PRINT", "PUT", "RANDOMIZE", "READ", "REDIM", "RESTORE",
"RESUME", "RETURN", "SEEK", "SELECT", "SHARED", "SHELL", "SHOW",
"SLEEP", "STATIC", "STEP", "STOP", "SUB", "SWAP", "THEN", "TO",
"TYPE", "UNLOAD", "UNTIL", "WEND", "WHILE", "WRITE", "XOR",
NULL
};
char upper[32];
if (wordLen <= 0 || wordLen >= 32) {
return false;
}
for (int32_t i = 0; i < wordLen; i++) {
upper[i] = (char)toupper((unsigned char)word[i]);
}
upper[wordLen] = '\0';
for (int32_t i = 0; keywords[i]; i++) {
if (strcmp(upper, keywords[i]) == 0) {
return true;
}
}
return false;
}
static bool isBasicType(const char *word, int32_t wordLen) {
static const char *types[] = {
"BOOLEAN", "BYTE", "DOUBLE", "INTEGER", "LONG", "SINGLE", "STRING",
"TRUE", "FALSE",
NULL
};
char upper[32];
if (wordLen <= 0 || wordLen >= 32) {
return false;
}
for (int32_t i = 0; i < wordLen; i++) {
upper[i] = (char)toupper((unsigned char)word[i]);
}
upper[wordLen] = '\0';
for (int32_t i = 0; types[i]; i++) {
if (strcmp(upper, types[i]) == 0) {
return true;
}
}
return false;
}
static void basicColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx) {
(void)ctx;
int32_t i = 0;
while (i < lineLen) {
char ch = line[i];
// Comment: ' or REM
if (ch == '\'') {
while (i < lineLen) {
colors[i++] = 3; // SYNTAX_COMMENT
}
return;
}
// String literal
if (ch == '"') {
colors[i++] = 2; // SYNTAX_STRING
while (i < lineLen && line[i] != '"') {
colors[i++] = 2;
}
if (i < lineLen) {
colors[i++] = 2; // closing quote
}
continue;
}
// Number
if (isdigit((unsigned char)ch) || (ch == '.' && i + 1 < lineLen && isdigit((unsigned char)line[i + 1]))) {
while (i < lineLen && (isdigit((unsigned char)line[i]) || line[i] == '.')) {
colors[i++] = 4; // SYNTAX_NUMBER
}
continue;
}
// Identifier or keyword
if (isalpha((unsigned char)ch) || ch == '_') {
int32_t start = i;
while (i < lineLen && (isalnum((unsigned char)line[i]) || line[i] == '_' || line[i] == '$' || line[i] == '%' || line[i] == '&' || line[i] == '!' || line[i] == '#')) {
i++;
}
int32_t wordLen = i - start;
// Check for REM comment
if (wordLen == 3 && (line[start] == 'R' || line[start] == 'r') && (line[start + 1] == 'E' || line[start + 1] == 'e') && (line[start + 2] == 'M' || line[start + 2] == 'm')) {
for (int32_t j = start; j < lineLen; j++) {
colors[j] = 3; // SYNTAX_COMMENT
}
return;
}
uint8_t c = 0; // default
if (isBasicKeyword(line + start, wordLen)) {
c = 1; // SYNTAX_KEYWORD
} else if (isBasicType(line + start, wordLen)) {
c = 6; // SYNTAX_TYPE
}
for (int32_t j = start; j < i; j++) {
colors[j] = c;
}
continue;
}
// Operators
if (ch == '=' || ch == '<' || ch == '>' || ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '\\' || ch == '&') {
colors[i++] = 5; // SYNTAX_OPERATOR
continue;
}
// Default (whitespace, parens, etc.)
colors[i++] = 0;
}
}
// ============================================================ // ============================================================
// clearOutput // clearOutput
// ============================================================ // ============================================================
@ -233,6 +453,12 @@ static void compileAndRun(void) {
int32_t n = snprintf(sOutputBuf, IDE_MAX_OUTPUT, "COMPILE ERROR:\n%s\n", parser->error); int32_t n = snprintf(sOutputBuf, IDE_MAX_OUTPUT, "COMPILE ERROR:\n%s\n", parser->error);
sOutputLen = n; sOutputLen = n;
wgtSetText(sOutput, sOutputBuf); wgtSetText(sOutput, sOutputBuf);
// Jump to error line in editor
if (parser->errorLine > 0 && sEditor) {
wgtTextAreaGoToLine(sEditor, parser->errorLine);
}
setStatus("Compilation failed."); setStatus("Compilation failed.");
basParserFree(parser); basParserFree(parser);
free(parser); free(parser);
@ -248,6 +474,40 @@ static void compileAndRun(void) {
return; return;
} }
// Cache the compiled module for Ctrl+F5
if (sCachedModule) {
basModuleFree(sCachedModule);
}
sCachedModule = mod;
// Update Object/Event dropdowns
updateDropdowns();
runModule(mod);
}
// ============================================================
// runCached
// ============================================================
static void runCached(void) {
if (!sCachedModule) {
setStatus("No compiled program. Press F5 to compile first.");
return;
}
clearOutput();
runModule(sCachedModule);
}
// ============================================================
// runModule
// ============================================================
static void runModule(BasModuleT *mod) {
setStatus("Running..."); setStatus("Running...");
// Create VM // Create VM
@ -263,35 +523,46 @@ static void compileAndRun(void) {
basVmSetInputCallback(vm, inputCallback, NULL); basVmSetInputCallback(vm, inputCallback, NULL);
basVmSetDoEventsCallback(vm, doEventsCallback, NULL); basVmSetDoEventsCallback(vm, doEventsCallback, NULL);
// Create form runtime (bridges UI opcodes to DVX widgets)
BasFormRtT *formRt = basFormRtCreate(sAc, vm, mod);
// Load any .frm files from the same directory as the source
loadFrmFiles(formRt);
sVm = vm; sVm = vm;
// Run with step limit to prevent infinite loops from freezing the GUI // Run in slices of 10000 steps, yielding to DVX between slices
vm->running = true; basVmSetStepLimit(vm, IDE_STEP_SLICE);
int32_t stepCount = 0;
while (vm->running) { int32_t totalSteps = 0;
BasVmResultE result = basVmStep(vm); BasVmResultE result;
stepCount++; for (;;) {
result = basVmRun(vm);
totalSteps += vm->stepCount;
if (result != BAS_VM_OK) { if (result == BAS_VM_STEP_LIMIT) {
if (result == BAS_VM_HALTED) { // Yield to DVX to keep the GUI responsive
// Normal completion dvxUpdate(sAc);
} else {
// Runtime error // Stop if IDE window was closed or DVX is shutting down
int32_t pos = sOutputLen; if (!sWin || !sAc->running) {
int32_t n = snprintf(sOutputBuf + pos, IDE_MAX_OUTPUT - pos, "\n[Runtime error: %s]\n", basVmGetError(vm)); break;
sOutputLen += n;
wgtSetText(sOutput, sOutputBuf);
} }
continue;
}
if (result == BAS_VM_HALTED) {
break; break;
} }
// Yield to DVX every 10000 steps to keep the GUI responsive // Runtime error
if (stepCount % 10000 == 0) { int32_t pos = sOutputLen;
dvxUpdate(sAc); int32_t n = snprintf(sOutputBuf + pos, IDE_MAX_OUTPUT - pos, "\n[Runtime error: %s]\n", basVmGetError(vm));
} sOutputLen += n;
wgtSetText(sOutput, sOutputBuf);
break;
} }
sVm = NULL; sVm = NULL;
@ -300,11 +571,11 @@ static void compileAndRun(void) {
wgtSetText(sOutput, sOutputBuf); wgtSetText(sOutput, sOutputBuf);
static char statusBuf[128]; static char statusBuf[128];
snprintf(statusBuf, sizeof(statusBuf), "Done. %ld instructions executed.", (long)stepCount); snprintf(statusBuf, sizeof(statusBuf), "Done. %ld instructions executed.", (long)totalSteps);
setStatus(statusBuf); setStatus(statusBuf);
basFormRtDestroy(formRt);
basVmDestroy(vm); basVmDestroy(vm);
basModuleFree(mod);
} }
// ============================================================ // ============================================================
@ -314,12 +585,106 @@ static void compileAndRun(void) {
static bool doEventsCallback(void *ctx) { static bool doEventsCallback(void *ctx) {
(void)ctx; (void)ctx;
// Update DVX display and process events // Stop if IDE window was closed or DVX is shutting down
if (sAc) { if (!sWin || !sAc->running) {
dvxUpdate(sAc); return false;
} }
return true; // continue running dvxUpdate(sAc);
return sWin != NULL && sAc->running;
}
// ============================================================
// evaluateImmediate
// ============================================================
//
// Compile and execute a single line from the Immediate window.
// If the line doesn't start with PRINT, wrap it in PRINT so
// expressions produce visible output.
static void immPrintCallback(void *ctx, const char *text, bool newline) {
(void)ctx;
if (!sImmediate) {
return;
}
// Append output to the immediate window
const char *cur = wgtGetText(sImmediate);
int32_t curLen = cur ? (int32_t)strlen(cur) : 0;
int32_t textLen = text ? (int32_t)strlen(text) : 0;
if (curLen + textLen + 2 < IDE_MAX_IMM) {
static char immBuf[IDE_MAX_IMM];
memcpy(immBuf, cur, curLen);
memcpy(immBuf + curLen, text, textLen);
curLen += textLen;
if (newline) {
immBuf[curLen++] = '\n';
}
immBuf[curLen] = '\0';
wgtSetText(sImmediate, immBuf);
}
}
static void evaluateImmediate(const char *expr) {
if (!expr || *expr == '\0') {
return;
}
char wrapped[1024];
// If it already starts with a statement keyword, use as-is
if (strncasecmp(expr, "PRINT", 5) == 0 || strncasecmp(expr, "DIM", 3) == 0 || strncasecmp(expr, "LET", 3) == 0) {
snprintf(wrapped, sizeof(wrapped), "%s", expr);
} else {
snprintf(wrapped, sizeof(wrapped), "PRINT %s", expr);
}
BasParserT *parser = (BasParserT *)malloc(sizeof(BasParserT));
if (!parser) {
return;
}
basParserInit(parser, wrapped, (int32_t)strlen(wrapped));
if (!basParse(parser)) {
// Show error inline
immPrintCallback(NULL, "Error: ", false);
immPrintCallback(NULL, parser->error, true);
basParserFree(parser);
free(parser);
return;
}
BasModuleT *mod = basParserBuildModule(parser);
basParserFree(parser);
free(parser);
if (!mod) {
return;
}
BasVmT *vm = basVmCreate();
basVmLoadModule(vm, mod);
vm->callStack[0].localCount = mod->globalCount > BAS_VM_MAX_LOCALS ? BAS_VM_MAX_LOCALS : mod->globalCount;
vm->callDepth = 1;
basVmSetPrintCallback(vm, immPrintCallback, NULL);
BasVmResultE result = basVmRun(vm);
if (result != BAS_VM_HALTED && result != BAS_VM_OK) {
immPrintCallback(NULL, "Error: ", false);
immPrintCallback(NULL, basVmGetError(vm), true);
}
basVmDestroy(vm);
basModuleFree(mod);
} }
// ============================================================ // ============================================================
@ -336,11 +701,7 @@ static bool inputCallback(void *ctx, const char *prompt, char *buf, int32_t bufS
wgtSetText(sOutput, sOutputBuf); wgtSetText(sOutput, sOutputBuf);
} }
// Use DVX input dialog return dvxInputBox(sAc, "DVX BASIC", prompt ? prompt : "Enter value:", NULL, buf, bufSize);
// For now, a simple message box prompt
// TODO: proper INPUT dialog
buf[0] = '\0';
return false; // cancel for now
} }
// ============================================================ // ============================================================
@ -391,6 +752,71 @@ static void loadFile(void) {
} }
} }
// ============================================================
// loadFrmFiles
// ============================================================
//
// Try to load a .frm file with the same base name as the loaded
// .bas source file. For example, if the user loaded "clickme.bas",
// this looks for "clickme.frm" in the same directory.
static void loadFrmFiles(BasFormRtT *rt) {
if (sFilePath[0] == '\0') {
return;
}
// Build .frm path from .bas path
char frmPath[260];
snprintf(frmPath, sizeof(frmPath), "%s", sFilePath);
// Find the extension and replace with .frm
char *dot = strrchr(frmPath, '.');
if (dot) {
strcpy(dot, ".frm");
} else {
strcat(frmPath, ".frm");
}
// Try to open the .frm file
FILE *f = fopen(frmPath, "r");
if (!f) {
return;
}
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size <= 0 || size >= IDE_MAX_SOURCE) {
fclose(f);
return;
}
char *frmBuf = (char *)malloc(size + 1);
if (!frmBuf) {
fclose(f);
return;
}
int32_t bytesRead = (int32_t)fread(frmBuf, 1, size, f);
fclose(f);
frmBuf[bytesRead] = '\0';
BasFormT *form = basFormRtLoadFrm(rt, frmBuf, bytesRead);
if (form) {
int32_t pos = sOutputLen;
int32_t n = snprintf(sOutputBuf + pos, IDE_MAX_OUTPUT - pos, "Loaded form: %s\n", form->name);
sOutputLen += n;
}
free(frmBuf);
}
// ============================================================ // ============================================================
// onClearClick // onClearClick
// ============================================================ // ============================================================
@ -406,10 +832,19 @@ static void onClearClick(WidgetT *w) {
// ============================================================ // ============================================================
static void onClose(WindowT *win) { static void onClose(WindowT *win) {
sWin = NULL; sWin = NULL;
sEditor = NULL; sEditor = NULL;
sOutput = NULL; sOutput = NULL;
sStatus = NULL; sImmediate = NULL;
sObjDropdown = NULL;
sEvtDropdown = NULL;
sStatus = NULL;
if (sCachedModule) {
basModuleFree(sCachedModule);
sCachedModule = NULL;
}
dvxDestroyWindow(sAc, win); dvxDestroyWindow(sAc, win);
} }
@ -429,6 +864,17 @@ static void onMenu(WindowT *win, int32_t menuId) {
compileAndRun(); compileAndRun();
break; break;
case CMD_RUN_NOCMP:
runCached();
break;
case CMD_STOP:
if (sVm) {
sVm->running = false;
setStatus("Program stopped.");
}
break;
case CMD_CLEAR: case CMD_CLEAR:
clearOutput(); clearOutput();
break; break;
@ -441,6 +887,163 @@ static void onMenu(WindowT *win, int32_t menuId) {
} }
} }
// ============================================================
// onEvtDropdownChange
// ============================================================
//
// Navigate to the selected procedure when the event dropdown changes.
static void onEvtDropdownChange(WidgetT *w) {
(void)w;
if (!sObjDropdown || !sEvtDropdown || !sEditor) {
return;
}
int32_t objIdx = wgtDropdownGetSelected(sObjDropdown);
int32_t evtIdx = wgtDropdownGetSelected(sEvtDropdown);
if (objIdx < 0 || evtIdx < 0) {
return;
}
// Build the object name from the dropdown
// Dropdown items are set in updateDropdowns; find matching proc
const char *cur = wgtGetText(sObjDropdown);
if (!cur) {
return;
}
// Find the proc entry matching the selected object + event indices
int32_t matchIdx = 0;
for (int32_t i = 0; i < sProcCount; i++) {
if (matchIdx == evtIdx) {
wgtTextAreaGoToLine(sEditor, sProcTable[i].lineNum);
return;
}
matchIdx++;
}
}
// ============================================================
// onImmediateChange
// ============================================================
//
// Detect Enter in the Immediate window and evaluate the last line.
static void onImmediateChange(WidgetT *w) {
(void)w;
if (!sImmediate) {
return;
}
const char *text = wgtGetText(sImmediate);
if (!text) {
return;
}
int32_t len = (int32_t)strlen(text);
if (len < 2 || text[len - 1] != '\n') {
return;
}
// Find the start of the line before the trailing newline
int32_t lineEnd = len - 1;
int32_t lineStart = lineEnd - 1;
while (lineStart > 0 && text[lineStart - 1] != '\n') {
lineStart--;
}
if (lineStart >= lineEnd) {
return;
}
// Extract the line
char expr[512];
int32_t lineLen = lineEnd - lineStart;
if (lineLen >= (int32_t)sizeof(expr)) {
lineLen = (int32_t)sizeof(expr) - 1;
}
memcpy(expr, text + lineStart, lineLen);
expr[lineLen] = '\0';
evaluateImmediate(expr);
}
// ============================================================
// onObjDropdownChange
// ============================================================
//
// Update the Event dropdown when the Object selection changes.
static void onObjDropdownChange(WidgetT *w) {
(void)w;
if (!sObjDropdown || !sEvtDropdown) {
return;
}
int32_t objIdx = wgtDropdownGetSelected(sObjDropdown);
if (objIdx < 0) {
return;
}
// Collect unique object names to find which one is selected
char objNames[IDE_MAX_PROCS][64];
int32_t objCount = 0;
for (int32_t i = 0; i < sProcCount; i++) {
bool found = false;
for (int32_t j = 0; j < objCount; j++) {
if (strcasecmp(objNames[j], sProcTable[i].objName) == 0) {
found = true;
break;
}
}
if (!found && objCount < IDE_MAX_PROCS) {
memcpy(objNames[objCount], sProcTable[i].objName, 64);
objCount++;
}
}
if (objIdx >= objCount) {
return;
}
const char *selObj = objNames[objIdx];
// Build event list for the selected object
const char *evtItems[IDE_MAX_PROCS];
int32_t evtCount = 0;
for (int32_t i = 0; i < sProcCount; i++) {
if (strcasecmp(sProcTable[i].objName, selObj) == 0 && evtCount < IDE_MAX_PROCS) {
evtItems[evtCount++] = sProcTable[i].evtName;
}
}
wgtDropdownSetItems(sEvtDropdown, evtItems, evtCount);
if (evtCount > 0) {
wgtDropdownSetSelected(sEvtDropdown, 0);
}
}
// ============================================================ // ============================================================
// onOpenClick // onOpenClick
// ============================================================ // ============================================================
@ -459,6 +1062,21 @@ static void onRunClick(WidgetT *w) {
compileAndRun(); compileAndRun();
} }
// ============================================================
// onStopClick
// ============================================================
static void onStopClick(WidgetT *w) {
(void)w;
if (sVm) {
sVm->running = false;
setStatus("Program stopped.");
}
}
// ============================================================ // ============================================================
// printCallback // printCallback
// ============================================================ // ============================================================
@ -483,6 +1101,11 @@ static void printCallback(void *ctx, const char *text, bool newline) {
} }
sOutputBuf[sOutputLen] = '\0'; sOutputBuf[sOutputLen] = '\0';
// Update the output textarea immediately so PRINT is visible
if (sOutput) {
wgtSetText(sOutput, sOutputBuf);
}
} }
// ============================================================ // ============================================================
@ -494,3 +1117,118 @@ static void setStatus(const char *text) {
wgtSetText(sStatus, text); wgtSetText(sStatus, text);
} }
} }
// ============================================================
// updateDropdowns
// ============================================================
//
// Scan the source for SUB/FUNCTION declarations and populate
// the Object and Event dropdowns. Procedure names are split on
// '_' into ObjectName and EventName (e.g. "Command1_Click").
static void updateDropdowns(void) {
sProcCount = 0;
if (!sEditor || !sObjDropdown || !sEvtDropdown) {
return;
}
const char *src = wgtGetText(sEditor);
if (!src) {
return;
}
// Scan line by line for SUB / FUNCTION
const char *pos = src;
int32_t lineNum = 1;
while (*pos) {
// Skip leading whitespace
while (*pos == ' ' || *pos == '\t') {
pos++;
}
// Check for SUB or FUNCTION keyword
bool isSub = (strncasecmp(pos, "SUB ", 4) == 0);
bool isFunc = (strncasecmp(pos, "FUNCTION ", 9) == 0);
if ((isSub || isFunc) && sProcCount < IDE_MAX_PROCS) {
pos += isSub ? 4 : 9;
// Skip whitespace after keyword
while (*pos == ' ' || *pos == '\t') {
pos++;
}
// Extract procedure name
char procName[64];
int32_t nameLen = 0;
while (*pos && *pos != '(' && *pos != ' ' && *pos != '\t' && *pos != '\n' && *pos != '\r' && nameLen < 63) {
procName[nameLen++] = *pos++;
}
procName[nameLen] = '\0';
// Split on '_' into object + event
char *underscore = strchr(procName, '_');
if (underscore) {
int32_t objLen = (int32_t)(underscore - procName);
if (objLen > 63) {
objLen = 63;
}
memcpy(sProcTable[sProcCount].objName, procName, objLen);
sProcTable[sProcCount].objName[objLen] = '\0';
snprintf(sProcTable[sProcCount].evtName, sizeof(sProcTable[sProcCount].evtName), "%s", underscore + 1);
} else {
snprintf(sProcTable[sProcCount].objName, sizeof(sProcTable[sProcCount].objName), "%s", "(General)");
snprintf(sProcTable[sProcCount].evtName, sizeof(sProcTable[sProcCount].evtName), "%s", procName);
}
sProcTable[sProcCount].lineNum = lineNum;
sProcCount++;
}
// Advance to end of line
while (*pos && *pos != '\n') {
pos++;
}
if (*pos == '\n') {
pos++;
}
lineNum++;
}
// Build unique object names for the Object dropdown
const char *objItems[IDE_MAX_PROCS];
int32_t objCount = 0;
for (int32_t i = 0; i < sProcCount; i++) {
bool found = false;
for (int32_t j = 0; j < objCount; j++) {
if (strcasecmp(objItems[j], sProcTable[i].objName) == 0) {
found = true;
break;
}
}
if (!found && objCount < IDE_MAX_PROCS) {
objItems[objCount++] = sProcTable[i].objName;
}
}
wgtDropdownSetItems(sObjDropdown, objItems, objCount);
if (objCount > 0) {
wgtDropdownSetSelected(sObjDropdown, 0);
onObjDropdownChange(sObjDropdown);
}
}

View file

@ -106,9 +106,11 @@ BasStringT *basStringNew(const char *text, int32_t len) {
} }
BasStringT *s = basStringAlloc(len + 1); BasStringT *s = basStringAlloc(len + 1);
memcpy(s->data, text, len); if (s->cap >= len + 1) {
s->data[len] = '\0'; memcpy(s->data, text, len);
s->len = len; s->data[len] = '\0';
s->len = len;
}
return s; return s;
} }

View file

@ -131,6 +131,7 @@ struct BasValueTag {
BasArrayT *arrVal; // BAS_TYPE_ARRAY (ref-counted) BasArrayT *arrVal; // BAS_TYPE_ARRAY (ref-counted)
BasUdtT *udtVal; // BAS_TYPE_UDT (ref-counted) BasUdtT *udtVal; // BAS_TYPE_UDT (ref-counted)
void *objVal; // BAS_TYPE_OBJECT (opaque host pointer) void *objVal; // BAS_TYPE_OBJECT (opaque host pointer)
BasValueT *refVal; // BAS_TYPE_REF (ByRef pointer to variable slot)
}; };
}; };

View file

@ -37,6 +37,74 @@ static uint16_t readUint16(BasVmT *vm);
static void runtimeError(BasVmT *vm, int32_t errNum, const char *msg); static void runtimeError(BasVmT *vm, int32_t errNum, const char *msg);
// ============================================================
// basVmCallSub
// ============================================================
bool basVmCallSub(BasVmT *vm, int32_t codeAddr) {
if (!vm || !vm->module) {
return false;
}
if (codeAddr < 0 || codeAddr >= vm->module->codeLen) {
return false;
}
if (vm->callDepth >= BAS_VM_CALL_STACK_SIZE - 1) {
return false;
}
// Save VM state
int32_t savedPc = vm->pc;
int32_t savedCallDepth = vm->callDepth;
bool savedRunning = vm->running;
// Push a call frame that returns to an invalid address (sentinel)
// We detect completion when callDepth drops back to savedCallDepth
BasCallFrameT *frame = &vm->callStack[vm->callDepth++];
frame->returnPc = savedPc;
frame->localCount = BAS_VM_MAX_LOCALS;
memset(frame->locals, 0, sizeof(frame->locals));
// Jump to the SUB
vm->pc = codeAddr;
vm->running = true;
// Step until the SUB returns (callDepth drops back)
int32_t steps = 0;
while (vm->running && vm->callDepth > savedCallDepth) {
if (vm->stepLimit > 0 && steps >= vm->stepLimit) {
// Unwind the call and restore state
vm->callDepth = savedCallDepth;
vm->pc = savedPc;
vm->running = savedRunning;
return false;
}
BasVmResultE result = basVmStep(vm);
steps++;
if (result == BAS_VM_HALTED) {
break;
}
if (result != BAS_VM_OK) {
// Runtime error in the event handler
vm->pc = savedPc;
vm->callDepth = savedCallDepth;
vm->running = savedRunning;
return false;
}
}
// Restore VM state
vm->pc = savedPc;
vm->running = savedRunning;
return true;
}
// ============================================================ // ============================================================
// basVmCreate // basVmCreate
// ============================================================ // ============================================================
@ -169,22 +237,29 @@ void basVmReset(BasVmT *vm) {
// ============================================================ // ============================================================
BasVmResultE basVmRun(BasVmT *vm) { BasVmResultE basVmRun(BasVmT *vm) {
vm->running = true; vm->running = true;
vm->yielded = false; vm->yielded = false;
vm->stepCount = 0;
while (vm->running) { while (vm->running) {
// Check step limit
if (vm->stepLimit > 0 && vm->stepCount >= vm->stepLimit) {
return BAS_VM_STEP_LIMIT;
}
// Save PC before each instruction for RESUME support // Save PC before each instruction for RESUME support
int32_t savedPc = vm->pc; int32_t savedPc = vm->pc;
BasVmResultE result = basVmStep(vm); BasVmResultE result = basVmStep(vm);
vm->stepCount++;
if (result != BAS_VM_OK) { if (result != BAS_VM_OK) {
// If an error handler is set and this is a trappable error, // If an error handler is set and this is a trappable error,
// jump to the handler instead of stopping execution // jump to the handler instead of stopping execution
if (vm->errorHandler != 0 && !vm->inErrorHandler && result != BAS_VM_HALTED && result != BAS_VM_BAD_OPCODE) { if (vm->errorHandler != 0 && !vm->inErrorHandler && result != BAS_VM_HALTED && result != BAS_VM_BAD_OPCODE) {
vm->errorPc = savedPc; vm->errorPc = savedPc;
vm->errorNextPc = vm->pc; vm->errorNextPc = vm->pc;
vm->inErrorHandler = true; vm->inErrorHandler = true;
vm->pc = vm->errorHandler; vm->pc = vm->errorHandler;
continue; continue;
} }
@ -216,6 +291,15 @@ void basVmSetCurrentForm(BasVmT *vm, void *formRef) {
} }
// ============================================================
// basVmSetStepLimit
// ============================================================
void basVmSetStepLimit(BasVmT *vm, int32_t limit) {
vm->stepLimit = limit;
}
// ============================================================ // ============================================================
// basVmSetInputCallback // basVmSetInputCallback
// ============================================================ // ============================================================
@ -380,7 +464,11 @@ BasVmResultE basVmStep(BasVmT *vm) {
return BAS_VM_ERROR; return BAS_VM_ERROR;
} }
if (!push(vm, basValCopy(frame->locals[idx]))) { // ByRef: if the local holds a reference, dereference it
BasValueT *slot = &frame->locals[idx];
BasValueT val = (slot->type == BAS_TYPE_REF) ? *slot->refVal : *slot;
if (!push(vm, basValCopy(val))) {
return BAS_VM_STACK_OVERFLOW; return BAS_VM_STACK_OVERFLOW;
} }
@ -402,8 +490,17 @@ BasVmResultE basVmStep(BasVmT *vm) {
return BAS_VM_ERROR; return BAS_VM_ERROR;
} }
basValRelease(&frame->locals[idx]); // ByRef: if the local holds a reference, store through it
frame->locals[idx] = val; BasValueT *slot = &frame->locals[idx];
if (slot->type == BAS_TYPE_REF) {
basValRelease(slot->refVal);
*slot->refVal = val;
} else {
basValRelease(slot);
*slot = val;
}
break; break;
} }
@ -441,6 +538,81 @@ BasVmResultE basVmStep(BasVmT *vm) {
break; break;
} }
case OP_PUSH_LOCAL_ADDR: {
uint16_t idx = readUint16(vm);
BasCallFrameT *frame = currentFrame(vm);
if (!frame || idx >= (uint16_t)frame->localCount) {
runtimeError(vm, 9, "Invalid local variable index");
return BAS_VM_ERROR;
}
BasValueT ref;
ref.type = BAS_TYPE_REF;
ref.refVal = &frame->locals[idx];
if (!push(vm, ref)) {
return BAS_VM_STACK_OVERFLOW;
}
break;
}
case OP_PUSH_GLOBAL_ADDR: {
uint16_t idx = readUint16(vm);
if (idx >= BAS_VM_MAX_GLOBALS) {
runtimeError(vm, 9, "Invalid global variable index");
return BAS_VM_ERROR;
}
BasValueT ref;
ref.type = BAS_TYPE_REF;
ref.refVal = &vm->globals[idx];
if (!push(vm, ref)) {
return BAS_VM_STACK_OVERFLOW;
}
break;
}
case OP_LOAD_REF: {
if (vm->sp < 1) {
return BAS_VM_STACK_UNDERFLOW;
}
BasValueT *top = &vm->stack[vm->sp - 1];
if (top->type != BAS_TYPE_REF || !top->refVal) {
runtimeError(vm, 9, "Expected reference");
return BAS_VM_ERROR;
}
BasValueT val = basValCopy(*top->refVal);
*top = val;
break;
}
case OP_STORE_REF: {
BasValueT val;
BasValueT ref;
if (!pop(vm, &val) || !pop(vm, &ref)) {
return BAS_VM_STACK_UNDERFLOW;
}
if (ref.type != BAS_TYPE_REF || !ref.refVal) {
basValRelease(&val);
runtimeError(vm, 9, "Expected reference");
return BAS_VM_ERROR;
}
basValRelease(ref.refVal);
*ref.refVal = val;
break;
}
// ============================================================ // ============================================================
// Arithmetic // Arithmetic
// ============================================================ // ============================================================
@ -769,6 +941,18 @@ BasVmResultE basVmStep(BasVmT *vm) {
break; break;
} }
case OP_FOR_POP: {
if (vm->forDepth <= 0) {
runtimeError(vm, 1, "FOR stack underflow");
return BAS_VM_ERROR;
}
BasForStateT *fs = &vm->forStack[--vm->forDepth];
basValRelease(&fs->limit);
basValRelease(&fs->step);
break;
}
// ============================================================ // ============================================================
// Type conversion // Type conversion
// ============================================================ // ============================================================
@ -857,6 +1041,18 @@ BasVmResultE basVmStep(BasVmT *vm) {
break; break;
} }
case OP_CONV_LONG_INT: {
if (vm->sp < 1) {
return BAS_VM_STACK_UNDERFLOW;
}
BasValueT *top = &vm->stack[vm->sp - 1];
BasValueT conv = basValToInteger(*top);
basValRelease(top);
*top = conv;
break;
}
// ============================================================ // ============================================================
// I/O // I/O
// ============================================================ // ============================================================
@ -1072,7 +1268,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
// Scientific notation // Scientific notation
char sciFmt[32]; char sciFmt[32];
int32_t decimals = hasDecimal ? digitsAfter : 0; int32_t decimals = hasDecimal ? digitsAfter : 0;
snprintf(sciFmt, sizeof(sciFmt), "%%.%dE", decimals); snprintf(sciFmt, sizeof(sciFmt), "%%.%dE", (int)decimals);
snprintf(buf, sizeof(buf), sciFmt, n); snprintf(buf, sizeof(buf), sciFmt, n);
} else { } else {
// Standard formatting // Standard formatting
@ -1081,7 +1277,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
int32_t decimals = hasDecimal ? digitsAfter : 0; int32_t decimals = hasDecimal ? digitsAfter : 0;
char numBuf[128]; char numBuf[128];
snprintf(numBuf, sizeof(numBuf), "%.*f", decimals, absN); snprintf(numBuf, sizeof(numBuf), "%.*f", (int)decimals, absN);
// Split into integer and decimal parts // Split into integer and decimal parts
char intPart[128]; char intPart[128];
@ -1181,15 +1377,38 @@ BasVmResultE basVmStep(BasVmT *vm) {
} }
case OP_INPUT: { case OP_INPUT: {
// Pop the prompt string pushed by the parser
BasValueT promptVal;
if (!pop(vm, &promptVal)) {
return BAS_VM_STACK_UNDERFLOW;
}
// Build the full prompt: "user prompt? "
char promptBuf[512];
const char *userPrompt = "";
if (promptVal.type == BAS_TYPE_STRING && promptVal.strVal) {
userPrompt = promptVal.strVal->data;
}
if (userPrompt[0]) {
snprintf(promptBuf, sizeof(promptBuf), "%s? ", userPrompt);
} else {
snprintf(promptBuf, sizeof(promptBuf), "? ");
}
char buf[1024]; char buf[1024];
buf[0] = '\0'; buf[0] = '\0';
if (vm->inputFn) { if (vm->inputFn) {
if (!vm->inputFn(vm->inputCtx, "? ", buf, sizeof(buf))) { if (!vm->inputFn(vm->inputCtx, promptBuf, buf, sizeof(buf))) {
buf[0] = '\0'; buf[0] = '\0';
} }
} }
basValRelease(&promptVal);
if (!push(vm, basValStringFromC(buf))) { if (!push(vm, basValStringFromC(buf))) {
return BAS_VM_STACK_OVERFLOW; return BAS_VM_STACK_OVERFLOW;
} }
@ -1233,15 +1452,25 @@ BasVmResultE basVmStep(BasVmT *vm) {
} }
case OP_STR_STRF: { case OP_STR_STRF: {
// STR$(n): VB convention -- leading space for positive, "-" for negative
if (vm->sp < 1) { if (vm->sp < 1) {
return BAS_VM_STACK_UNDERFLOW; return BAS_VM_STACK_UNDERFLOW;
} }
BasValueT *top = &vm->stack[vm->sp - 1]; BasValueT *top = &vm->stack[vm->sp - 1];
BasStringT *s = basValFormatString(*top); double n = basValToNumber(*top);
basValRelease(top); basValRelease(top);
char buf[64];
if (n >= 0.0) {
snprintf(buf, sizeof(buf), " %g", n);
} else {
snprintf(buf, sizeof(buf), "%g", n);
}
top->type = BAS_TYPE_STRING; top->type = BAS_TYPE_STRING;
top->strVal = s; top->strVal = basStringNew(buf, (int32_t)strlen(buf));
break; break;
} }
@ -1324,7 +1553,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
// Push DATE$ as "MM-DD-YYYY" // Push DATE$ as "MM-DD-YYYY"
time_t now = time(NULL); time_t now = time(NULL);
struct tm *t = localtime(&now); struct tm *t = localtime(&now);
char buf[16]; char buf[32];
snprintf(buf, sizeof(buf), "%02d-%02d-%04d", t->tm_mon + 1, t->tm_mday, t->tm_year + 1900); snprintf(buf, sizeof(buf), "%02d-%02d-%04d", t->tm_mon + 1, t->tm_mday, t->tm_year + 1900);
if (!push(vm, basValStringFromC(buf))) { if (!push(vm, basValStringFromC(buf))) {
@ -1463,6 +1692,19 @@ BasVmResultE basVmStep(BasVmT *vm) {
vm->inErrorHandler = false; vm->inErrorHandler = false;
break; break;
case OP_RAISE_ERR: {
BasValueT errVal;
if (!pop(vm, &errVal)) {
return BAS_VM_STACK_UNDERFLOW;
}
int32_t errNum = (int32_t)basValToNumber(errVal);
basValRelease(&errVal);
runtimeError(vm, errNum, "User-defined error");
return BAS_VM_ERROR;
}
// ============================================================ // ============================================================
// Array / UDT operations // Array / UDT operations
// ============================================================ // ============================================================
@ -1965,7 +2207,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
char numBuf[128]; char numBuf[128];
if (hasDecimal) { if (hasDecimal) {
snprintf(numBuf, sizeof(numBuf), "%.*f", decimals, absN); snprintf(numBuf, sizeof(numBuf), "%.*f", (int)decimals, absN);
} else { } else {
snprintf(numBuf, sizeof(numBuf), "%.0f", absN); snprintf(numBuf, sizeof(numBuf), "%.0f", absN);
} }
@ -2227,6 +2469,11 @@ BasVmResultE basVmStep(BasVmT *vm) {
basValRelease(&sv); basValRelease(&sv);
} }
// Set as current form for subsequent FIND_CTRL calls
if (formRef) {
vm->currentForm = formRef;
}
basValRelease(&nameVal); basValRelease(&nameVal);
push(vm, basValObject(formRef)); push(vm, basValObject(formRef));
break; break;
@ -2376,9 +2623,11 @@ BasVmResultE basVmStep(BasVmT *vm) {
void *ctrlRef = NULL; void *ctrlRef = NULL;
if (vm->ui.findCtrl && formVal.type == BAS_TYPE_OBJECT) { if (vm->ui.findCtrl) {
// Use current form if formRef is NULL or not an object
void *formRef = (formVal.type == BAS_TYPE_OBJECT) ? formVal.objVal : vm->currentForm;
BasValueT sv = basValToString(nameVal); BasValueT sv = basValToString(nameVal);
ctrlRef = vm->ui.findCtrl(vm->ui.ctx, formVal.objVal, sv.strVal->data); ctrlRef = vm->ui.findCtrl(vm->ui.ctx, formRef, sv.strVal->data);
basValRelease(&sv); basValRelease(&sv);
} }
@ -2548,6 +2797,33 @@ static BasVmResultE execArith(BasVmT *vm, uint8_t op) {
return BAS_VM_STACK_UNDERFLOW; return BAS_VM_STACK_UNDERFLOW;
} }
// VB behavior: + on two strings concatenates
if ((op == OP_ADD_INT || op == OP_ADD_FLT) && a.type == BAS_TYPE_STRING && b.type == BAS_TYPE_STRING) {
BasStringT *sa = a.strVal ? a.strVal : basStringNew("", 0);
BasStringT *sb = b.strVal ? b.strVal : basStringNew("", 0);
int32_t newLen = sa->len + sb->len;
BasStringT *cat = basStringNew("", 0);
if (newLen > 0) {
basStringUnref(cat);
char *buf = (char *)malloc(newLen + 1);
memcpy(buf, sa->data, sa->len);
memcpy(buf + sa->len, sb->data, sb->len);
buf[newLen] = '\0';
cat = basStringNew(buf, newLen);
free(buf);
}
basValRelease(&a);
basValRelease(&b);
BasValueT result;
result.type = BAS_TYPE_STRING;
result.strVal = cat;
push(vm, result);
return BAS_VM_OK;
}
double na = basValToNumber(a); double na = basValToNumber(a);
double nb = basValToNumber(b); double nb = basValToNumber(b);
basValRelease(&a); basValRelease(&a);
@ -3630,6 +3906,40 @@ static BasVmResultE execStringOp(BasVmT *vm, uint8_t op) {
return BAS_VM_OK; return BAS_VM_OK;
} }
case OP_STR_INSTR3: {
// INSTR(start, string, find)
BasValueT findVal;
BasValueT sVal;
BasValueT startVal;
if (!pop(vm, &findVal) || !pop(vm, &sVal) || !pop(vm, &startVal)) {
return BAS_VM_STACK_UNDERFLOW;
}
int32_t startPos = (int32_t)basValToNumber(startVal) - 1; // 0-based
basValRelease(&startVal);
BasValueT sv = basValToString(sVal);
BasValueT fv = basValToString(findVal);
basValRelease(&sVal);
basValRelease(&findVal);
int32_t pos = 0;
if (startPos >= 0 && startPos < sv.strVal->len) {
char *found = strstr(sv.strVal->data + startPos, fv.strVal->data);
if (found) {
pos = (int32_t)(found - sv.strVal->data) + 1; // 1-based
}
}
basValRelease(&sv);
basValRelease(&fv);
push(vm, basValInteger((int16_t)pos));
return BAS_VM_OK;
}
case OP_STR_UCASE: case OP_STR_UCASE:
case OP_STR_LCASE: case OP_STR_LCASE:
case OP_STR_TRIM: case OP_STR_TRIM:
@ -3908,5 +4218,5 @@ static uint16_t readUint16(BasVmT *vm) {
static void runtimeError(BasVmT *vm, int32_t errNum, const char *msg) { static void runtimeError(BasVmT *vm, int32_t errNum, const char *msg) {
vm->errorNumber = errNum; vm->errorNumber = errNum;
snprintf(vm->errorMsg, sizeof(vm->errorMsg), "Runtime error %d at PC %d: %s", errNum, vm->pc, msg); snprintf(vm->errorMsg, sizeof(vm->errorMsg), "Runtime error %d at PC %d: %s", (int)errNum, (int)vm->pc, msg);
} }

View file

@ -48,7 +48,8 @@ typedef enum {
BAS_VM_BAD_OPCODE, BAS_VM_BAD_OPCODE,
BAS_VM_FILE_ERROR, BAS_VM_FILE_ERROR,
BAS_VM_SUBSCRIPT_RANGE, BAS_VM_SUBSCRIPT_RANGE,
BAS_VM_USER_ERROR // ON ERROR raised BAS_VM_USER_ERROR, // ON ERROR raised
BAS_VM_STEP_LIMIT // step limit reached (not an error)
} BasVmResultE; } BasVmResultE;
// ============================================================ // ============================================================
@ -205,19 +206,35 @@ typedef struct {
int32_t mode; // 0=closed, 1=input, 2=output, 3=append, 4=random, 5=binary int32_t mode; // 0=closed, 1=input, 2=output, 3=append, 4=random, 5=binary
} BasFileChannelT; } BasFileChannelT;
// ============================================================
// Procedure table entry (retained from symbol table for runtime)
// ============================================================
#define BAS_MAX_PROC_NAME 64
typedef struct {
char name[BAS_MAX_PROC_NAME]; // SUB/FUNCTION name (case-preserved)
int32_t codeAddr; // entry point in code[]
int32_t paramCount; // number of parameters
uint8_t returnType; // BAS_TYPE_* (0 for SUB)
bool isFunction; // true = FUNCTION, false = SUB
} BasProcEntryT;
// ============================================================ // ============================================================
// Compiled module (output of the compiler) // Compiled module (output of the compiler)
// ============================================================ // ============================================================
typedef struct { typedef struct {
uint8_t *code; // p-code bytecode uint8_t *code; // p-code bytecode
int32_t codeLen; int32_t codeLen;
BasStringT **constants; // string constant pool BasStringT **constants; // string constant pool
int32_t constCount; int32_t constCount;
int32_t globalCount; // number of global variable slots needed int32_t globalCount; // number of global variable slots needed
int32_t entryPoint; // PC of the first instruction (module-level code) int32_t entryPoint; // PC of the first instruction (module-level code)
BasValueT *dataPool; // DATA statement value pool BasValueT *dataPool; // DATA statement value pool
int32_t dataCount; // number of values in the data pool int32_t dataCount; // number of values in the data pool
BasProcEntryT *procs; // procedure table (SUBs and FUNCTIONs)
int32_t procCount;
} BasModuleT; } BasModuleT;
// ============================================================ // ============================================================
@ -232,6 +249,8 @@ typedef struct {
int32_t pc; // program counter int32_t pc; // program counter
bool running; bool running;
bool yielded; bool yielded;
int32_t stepLimit; // max steps per basVmRun (0 = unlimited)
int32_t stepCount; // steps executed in last basVmRun
// Evaluation stack // Evaluation stack
BasValueT stack[BAS_VM_STACK_SIZE]; BasValueT stack[BAS_VM_STACK_SIZE];
@ -325,6 +344,11 @@ void basVmSetExternCallbacks(BasVmT *vm, const BasExternCallbacksT *ext);
// Set the current form context (called by host during event dispatch). // Set the current form context (called by host during event dispatch).
void basVmSetCurrentForm(BasVmT *vm, void *formRef); void basVmSetCurrentForm(BasVmT *vm, void *formRef);
// Set the step limit for basVmRun. 0 = unlimited (default).
// When the limit is reached, basVmRun returns BAS_VM_STEP_LIMIT.
// The VM remains in a runnable state -- call basVmRun again to continue.
void basVmSetStepLimit(BasVmT *vm, int32_t limit);
// Push/pop values on the evaluation stack (for host integration). // Push/pop values on the evaluation stack (for host integration).
bool basVmPush(BasVmT *vm, BasValueT val); bool basVmPush(BasVmT *vm, BasValueT val);
bool basVmPop(BasVmT *vm, BasValueT *val); bool basVmPop(BasVmT *vm, BasValueT *val);
@ -332,4 +356,10 @@ bool basVmPop(BasVmT *vm, BasValueT *val);
// Get the current error message. // Get the current error message.
const char *basVmGetError(const BasVmT *vm); const char *basVmGetError(const BasVmT *vm);
// Call a SUB by code address from the host.
// Pushes a call frame, runs until the SUB returns, then restores
// the previous execution state. Returns true if the SUB was called
// and returned normally, false on error or if the VM was not idle.
bool basVmCallSub(BasVmT *vm, int32_t codeAddr);
#endif // DVXBASIC_VM_H #endif // DVXBASIC_VM_H

View file

@ -0,0 +1,29 @@
' clickme.bas -- DVX BASIC click test
'
' Demonstrates form loading, control properties, and event handling.
' Load this file in DVX BASIC, then click Run.
DIM clickCount AS INTEGER
clickCount = 0
Load ClickMe
ClickMe.Show
PRINT "Form loaded. Click the button!"
' Event loop -- keeps the program alive for events.
' The IDE yields to DVX between VM slices, so widget
' events fire during DoEvents pauses.
DO
DoEvents
LOOP
SUB Command1_Click
clickCount = clickCount + 1
Text1.Text = "Clicked " + STR$(clickCount) + " times!"
Label1.Caption = "Keep clicking!"
END SUB
SUB ClickMe_Load
PRINT "Form_Load fired!"
END SUB

View file

@ -0,0 +1,12 @@
Begin Form ClickMe
Caption = "DVX BASIC Demo"
Begin CommandButton Command1
Caption = "Click Me!"
End
Begin TextBox Text1
Text = "Ready."
End
Begin Label Label1
Caption = "Click the button above!"
End
End

View file

@ -0,0 +1,8 @@
' formtest.bas -- DVX BASIC form test
'
' Demonstrates form creation and MsgBox.
Load MyForm
MyForm.Show
MsgBox "Hello from DVX BASIC!"
PRINT "Form test complete."

View file

@ -0,0 +1,13 @@
' input.bas -- Test INPUT statement
'
' Demonstrates the INPUT dialog.
DIM name AS STRING
DIM age AS INTEGER
INPUT "What is your name"; name
PRINT "Hello, " + name + "!"
INPUT "How old are you"; age
PRINT "You are" + STR$(age) + " years old."
PRINT "Done!"

View file

@ -903,6 +903,744 @@ int main(void) {
printf("\n"); printf("\n");
} }
// Test: Procedure table populated for SUBs and FUNCTIONs
{
printf("=== Procedure table ===\n");
const char *src =
"SUB Command1_Click\n"
" PRINT \"Clicked!\"\n"
"END SUB\n"
"\n"
"SUB Form1_Load\n"
" PRINT \"Loaded\"\n"
"END SUB\n"
"\n"
"FUNCTION AddNums(a AS INTEGER, b AS INTEGER) AS INTEGER\n"
" AddNums = a + b\n"
"END FUNCTION\n"
"\n"
"PRINT \"main\"\n";
int32_t len = (int32_t)strlen(src);
BasParserT parser;
basParserInit(&parser, src, len);
bool ok = basParse(&parser);
if (!ok) {
printf("COMPILE ERROR: %s\n", parser.error);
} else {
BasModuleT *mod = basParserBuildModule(&parser);
printf("procCount = %d\n", mod->procCount);
for (int32_t i = 0; i < mod->procCount; i++) {
printf(" [%d] %s addr=%d params=%d func=%d\n",
i, mod->procs[i].name, mod->procs[i].codeAddr,
mod->procs[i].paramCount, mod->procs[i].isFunction);
}
// Test lookup
const BasProcEntryT *p = basModuleFindProc(mod, "command1_click");
if (p) {
printf("Found Command1_Click at addr %d\n", p->codeAddr);
} else {
printf("FAIL: Command1_Click not found!\n");
}
p = basModuleFindProc(mod, "FORM1_LOAD");
if (p) {
printf("Found Form1_Load at addr %d\n", p->codeAddr);
} else {
printf("FAIL: Form1_Load not found!\n");
}
// Test basVmCallSub
BasVmT *vm = basVmCreate();
basVmLoadModule(vm, mod);
vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount;
vm->callDepth = 1;
p = basModuleFindProc(mod, "Command1_Click");
if (p) {
printf("Calling Command1_Click via basVmCallSub: ");
bool callOk = basVmCallSub(vm, p->codeAddr);
printf("result=%s\n", callOk ? "ok" : "FAIL");
}
basVmDestroy(vm);
basModuleFree(mod);
}
basParserFree(&parser);
printf("\n");
}
// ByRef parameters
runProgram("ByRef parameters",
"DECLARE SUB AddTen(n AS INTEGER)\n"
"DECLARE SUB ChangeStr(s AS STRING)\n"
"DECLARE SUB NoChange(BYVAL n AS INTEGER)\n"
"\n"
"DIM x AS INTEGER\n"
"x = 10\n"
"PRINT \"Before: x =\"; x\n"
"AddTen x\n"
"PRINT \"After AddTen: x =\"; x\n"
"\n"
"DIM a AS STRING\n"
"a = \"hello\"\n"
"PRINT \"Before: a = \"; a\n"
"ChangeStr a\n"
"PRINT \"After ChangeStr: a = \"; a\n"
"\n"
"' ByVal should NOT modify caller\n"
"DIM y AS INTEGER\n"
"y = 100\n"
"PRINT \"Before: y =\"; y\n"
"NoChange y\n"
"PRINT \"After NoChange: y =\"; y\n"
"\n"
"' Expression arg to ByRef param: effectively ByVal\n"
"DIM z AS INTEGER\n"
"z = 5\n"
"AddTen(z + 0)\n"
"PRINT \"After AddTen(z+0): z =\"; z\n"
"\n"
"SUB AddTen(n AS INTEGER)\n"
" n = n + 10\n"
"END SUB\n"
"\n"
"SUB ChangeStr(s AS STRING)\n"
" s = s + \" world\"\n"
"END SUB\n"
"\n"
"SUB NoChange(BYVAL n AS INTEGER)\n"
" n = n + 999\n"
"END SUB\n"
);
// ============================================================
// Coverage: String functions
// ============================================================
runProgram("LCASE$",
"PRINT LCASE$(\"HELLO WORLD\")\n"
);
// Expected: hello world
runProgram("TRIM$ LTRIM$ RTRIM$",
"PRINT \"[\" & TRIM$(\" hi \") & \"]\"\n"
"PRINT \"[\" & LTRIM$(\" hi \") & \"]\"\n"
"PRINT \"[\" & RTRIM$(\" hi \") & \"]\"\n"
);
// Expected: [hi] / [hi ] / [ hi]
runProgram("INSTR 2-arg",
"PRINT INSTR(\"hello world\", \"world\")\n"
"PRINT INSTR(\"hello world\", \"xyz\")\n"
);
// Expected: 7 / 0
runProgram("INSTR 3-arg",
"PRINT INSTR(5, \"abcabc\", \"bc\")\n"
"PRINT INSTR(1, \"abcabc\", \"bc\")\n"
);
// Expected: 5 / 2
runProgram("CHR$ and ASC",
"PRINT CHR$(65)\n"
"PRINT ASC(\"Z\")\n"
);
// Expected: A / 90
runProgram("SPACE$",
"PRINT \"[\" & SPACE$(5) & \"]\"\n"
);
// Expected: [ ]
runProgram("STRING$",
"PRINT STRING$(5, \"*\")\n"
);
// Expected: *****
runProgram("HEX$",
"PRINT HEX$(255)\n"
"PRINT HEX$(16)\n"
);
// Expected: FF / 10
runProgram("VAL",
"PRINT VAL(\"3.14\")\n"
"PRINT VAL(\"42\")\n"
"PRINT VAL(\"abc\")\n"
);
// Expected: 3.14 / 42 / 0
runProgram("STR$",
"PRINT \"[\" & STR$(42) & \"]\"\n"
"PRINT \"[\" & STR$(-7) & \"]\"\n"
);
// Expected: [ 42] / [-7]
runProgram("MID$ 2-arg",
"PRINT MID$(\"hello world\", 7)\n"
);
// Expected: world
runProgram("String concat with +",
"DIM a AS STRING\n"
"DIM b AS STRING\n"
"a = \"hello\"\n"
"b = \" world\"\n"
"PRINT a + b\n"
);
// Expected: hello world
// ============================================================
// Coverage: Math functions
// ============================================================
runProgram("Trig functions",
"DIM pi AS DOUBLE\n"
"pi = ATN(1) * 4\n"
"PRINT INT(SIN(pi / 2) * 1000)\n"
"PRINT INT(COS(0) * 1000)\n"
"PRINT INT(TAN(pi / 4) * 1000)\n"
);
// Expected: 1000 / 1000 / 1000
runProgram("LOG and EXP",
"PRINT INT(LOG(1))\n"
"PRINT INT(EXP(0))\n"
"PRINT INT(EXP(1) * 100)\n"
);
// Expected: 0 / 1 / 271
runProgram("FIX and SGN",
"PRINT FIX(3.7)\n"
"PRINT FIX(-3.7)\n"
"PRINT SGN(42)\n"
"PRINT SGN(-5)\n"
"PRINT SGN(0)\n"
);
// Expected: 3 / -3 / 1 / -1 / 0
runProgram("RND and RANDOMIZE",
"RANDOMIZE 12345\n"
"DIM r AS DOUBLE\n"
"r = RND\n"
"IF r >= 0 AND r < 1 THEN PRINT \"ok\"\n"
);
// Expected: ok
// ============================================================
// Coverage: Loop variants
// ============================================================
runProgram("DO UNTIL pre-test",
"DIM n AS INTEGER\n"
"n = 1\n"
"DO UNTIL n > 5\n"
" PRINT n;\n"
" n = n + 1\n"
"LOOP\n"
"PRINT\n"
);
// Expected: 1 2 3 4 5
runProgram("DO LOOP WHILE post-test",
"DIM n AS INTEGER\n"
"n = 1\n"
"DO\n"
" PRINT n;\n"
" n = n + 1\n"
"LOOP WHILE n <= 5\n"
"PRINT\n"
);
// Expected: 1 2 3 4 5
runProgram("DO LOOP UNTIL post-test",
"DIM n AS INTEGER\n"
"n = 1\n"
"DO\n"
" PRINT n;\n"
" n = n + 1\n"
"LOOP UNTIL n > 5\n"
"PRINT\n"
);
// Expected: 1 2 3 4 5
runProgram("WHILE WEND",
"DIM n AS INTEGER\n"
"n = 1\n"
"WHILE n <= 5\n"
" PRINT n;\n"
" n = n + 1\n"
"WEND\n"
"PRINT\n"
);
// Expected: 1 2 3 4 5
runProgram("FOR with negative STEP",
"DIM i AS INTEGER\n"
"FOR i = 5 TO 1 STEP -1\n"
" PRINT i;\n"
"NEXT i\n"
"PRINT\n"
);
// Expected: 5 4 3 2 1
runProgram("FOR with STEP 2",
"DIM i AS INTEGER\n"
"FOR i = 0 TO 10 STEP 2\n"
" PRINT i;\n"
"NEXT i\n"
"PRINT\n"
);
// Expected: 0 2 4 6 8 10
// ============================================================
// Coverage: EXIT statements
// ============================================================
runProgram("EXIT FOR",
"DIM i AS INTEGER\n"
"FOR i = 1 TO 100\n"
" IF i = 3 THEN EXIT FOR\n"
" PRINT i;\n"
"NEXT i\n"
"PRINT\n"
"PRINT \"after\"\n"
);
// Expected: 1 2 / after
runProgram("EXIT DO",
"DIM n AS INTEGER\n"
"n = 0\n"
"DO\n"
" n = n + 1\n"
" IF n = 4 THEN EXIT DO\n"
" PRINT n;\n"
"LOOP\n"
"PRINT\n"
"PRINT \"after\"\n"
);
// Expected: 1 2 3 / after
runProgram("EXIT SUB",
"DECLARE SUB EarlyReturn(n AS INTEGER)\n"
"EarlyReturn 1\n"
"EarlyReturn 5\n"
"EarlyReturn 2\n"
"SUB EarlyReturn(n AS INTEGER)\n"
" IF n > 3 THEN EXIT SUB\n"
" PRINT n;\n"
"END SUB\n"
"PRINT\n"
);
// Expected: 1 2
runProgram("EXIT FUNCTION",
"DECLARE FUNCTION Clamp(n AS INTEGER) AS INTEGER\n"
"PRINT Clamp(5)\n"
"PRINT Clamp(200)\n"
"FUNCTION Clamp(n AS INTEGER) AS INTEGER\n"
" IF n > 100 THEN\n"
" Clamp = 100\n"
" EXIT FUNCTION\n"
" END IF\n"
" Clamp = n\n"
"END FUNCTION\n"
);
// Expected: 5 / 100
// ============================================================
// Coverage: CONST
// ============================================================
runProgram("CONST",
"CONST PI = 3.14159\n"
"CONST MAX_SIZE = 100\n"
"CONST GREETING = \"hello\"\n"
"PRINT INT(PI * 100)\n"
"PRINT MAX_SIZE\n"
"PRINT GREETING\n"
);
// Expected: 314 / 100 / hello
// ============================================================
// Coverage: END statement
// ============================================================
runProgram("END statement",
"PRINT \"before\"\n"
"END\n"
"PRINT \"after\"\n"
);
// Expected: before
// ============================================================
// Coverage: Boolean literals
// ============================================================
runProgram("True and False",
"DIM b AS BOOLEAN\n"
"b = True\n"
"IF b THEN PRINT \"yes\"\n"
"b = False\n"
"IF NOT b THEN PRINT \"no\"\n"
"PRINT True\n"
"PRINT False\n"
);
// Expected: yes / no / -1 / 0
// ============================================================
// Coverage: NOT operator
// ============================================================
runProgram("NOT operator",
"PRINT NOT 0\n"
"PRINT NOT -1\n"
"DIM x AS INTEGER\n"
"x = 5\n"
"IF NOT (x > 10) THEN PRINT \"small\"\n"
);
// Expected: -1 / 0 / small
// ============================================================
// Coverage: AND OR XOR bitwise
// ============================================================
runProgram("Bitwise AND OR XOR",
"PRINT 15 AND 9\n"
"PRINT 12 OR 3\n"
"PRINT 15 XOR 9\n"
);
// Expected: 9 / 15 / 6
// ============================================================
// Coverage: SLEEP
// ============================================================
runProgram("SLEEP",
"SLEEP 0\n"
"PRINT \"ok\"\n"
);
// Expected: ok
// ============================================================
// Coverage: Error recovery
// ============================================================
runProgram("RESUME NEXT",
"ON ERROR GOTO handler\n"
"DIM x AS INTEGER\n"
"x = 10 / 0\n"
"PRINT \"resumed\"\n"
"END\n"
"handler:\n"
"RESUME NEXT\n"
);
// Expected: resumed
runProgram("RAISE ERROR",
"ON ERROR GOTO handler\n"
"ERROR 999\n"
"PRINT \"should not print\"\n"
"END\n"
"handler:\n"
"PRINT \"caught error\"; ERR\n"
);
// Expected: caught error 999
// ============================================================
// Coverage: Type suffixes and hex literals
// ============================================================
runProgram("Type suffixes",
"DIM x%\n"
"DIM s$\n"
"x% = 42\n"
"s$ = \"hello\"\n"
"PRINT x%\n"
"PRINT s$\n"
);
// Expected: 42 / hello
runProgram("Hex literals",
"PRINT &HFF\n"
"PRINT &H10\n"
"DIM x AS INTEGER\n"
"x = &H0A\n"
"PRINT x\n"
);
// Expected: 255 / 16 / 10
// ============================================================
// Coverage: Long integer type
// ============================================================
runProgram("Long integer",
"DIM x AS LONG\n"
"x = 100000\n"
"x = x * 2\n"
"PRINT x\n"
);
// Expected: 200000
// ============================================================
// Coverage: REDIM without PRESERVE
// ============================================================
runProgram("REDIM no PRESERVE",
"DIM a(3) AS INTEGER\n"
"a(1) = 99\n"
"REDIM a(5) AS INTEGER\n"
"PRINT a(1)\n"
);
// Expected: 0 (data cleared)
// ============================================================
// Coverage: PRINT variants
// ============================================================
runProgram("PRINT comma separator",
"PRINT 1, 2, 3\n"
);
// Expected: 1<tab>2<tab>3
runProgram("PRINT bare newline",
"PRINT \"a\"\n"
"PRINT\n"
"PRINT \"b\"\n"
);
// Expected: a / (blank) / b
// ============================================================
// Coverage: LET keyword
// ============================================================
runProgram("LET keyword",
"DIM x AS INTEGER\n"
"LET x = 42\n"
"PRINT x\n"
);
// Expected: 42
// ============================================================
// Coverage: Line continuation
// ============================================================
runProgram("Line continuation",
"DIM x AS INTEGER\n"
"x = 1 + _\n"
" 2 + _\n"
" 3\n"
"PRINT x\n"
);
// Expected: 6
// ============================================================
// Coverage: REM comment
// ============================================================
runProgram("REM comment",
"REM This is a comment\n"
"PRINT \"ok\"\n"
"PRINT \"hi\" REM inline comment\n"
);
// Expected: ok / hi
// ============================================================
// Coverage: SELECT CASE with numeric ranges and IS
// ============================================================
runProgram("SELECT CASE numeric",
"DIM x AS INTEGER\n"
"x = 15\n"
"SELECT CASE x\n"
" CASE 10\n"
" PRINT \"ten\"\n"
" CASE 15, 20\n"
" PRINT \"fifteen or twenty\"\n"
" CASE ELSE\n"
" PRINT \"other\"\n"
"END SELECT\n"
);
// Expected: fifteen or twenty
runProgram("CASE TO range",
"DIM x AS INTEGER\n"
"x = 15\n"
"SELECT CASE x\n"
" CASE 1 TO 10\n"
" PRINT \"1-10\"\n"
" CASE 11 TO 20\n"
" PRINT \"11-20\"\n"
" CASE ELSE\n"
" PRINT \"other\"\n"
"END SELECT\n"
);
// Expected: 11-20
runProgram("CASE IS comparison",
"DIM x AS INTEGER\n"
"x = 50\n"
"SELECT CASE x\n"
" CASE IS < 10\n"
" PRINT \"small\"\n"
" CASE IS >= 100\n"
" PRINT \"big\"\n"
" CASE ELSE\n"
" PRINT \"medium\"\n"
"END SELECT\n"
);
// Expected: medium
runProgram("CASE mixed forms",
"DIM x AS INTEGER\n"
"x = 5\n"
"SELECT CASE x\n"
" CASE 1, 2, 3\n"
" PRINT \"low\"\n"
" CASE 4 TO 6\n"
" PRINT \"mid\"\n"
" CASE IS > 6\n"
" PRINT \"high\"\n"
"END SELECT\n"
);
// Expected: mid
// ============================================================
// Coverage: Nested FOR EXIT
// ============================================================
runProgram("Nested FOR EXIT",
"DIM i AS INTEGER\n"
"DIM j AS INTEGER\n"
"FOR i = 1 TO 3\n"
" FOR j = 1 TO 100\n"
" IF j > 2 THEN EXIT FOR\n"
" PRINT i * 10 + j;\n"
" NEXT j\n"
"NEXT i\n"
"PRINT\n"
);
// Expected: 11 12 21 22 31 32
// ============================================================
// Coverage: Mixed type arithmetic coercion
// ============================================================
runProgram("Type coercion",
"DIM d AS DOUBLE\n"
"d = 2.5\n"
"PRINT 7 + d\n"
"PRINT 7 * d\n"
"DIM i AS INTEGER\n"
"DIM f AS SINGLE\n"
"i = 10\n"
"f = 3.0\n"
"PRINT i / f\n"
);
// Expected: 9.5 / 17.5 / 3.333...
// ============================================================
// Coverage: Multiple FUNCTION return paths
// ============================================================
runProgram("FUNCTION return",
"DECLARE FUNCTION Max(a AS INTEGER, b AS INTEGER) AS INTEGER\n"
"PRINT Max(10, 20)\n"
"PRINT Max(30, 5)\n"
"FUNCTION Max(a AS INTEGER, b AS INTEGER) AS INTEGER\n"
" IF a > b THEN\n"
" Max = a\n"
" ELSE\n"
" Max = b\n"
" END IF\n"
"END FUNCTION\n"
);
// Expected: 20 / 30
// ============================================================
// Coverage: Recursive FUNCTION
// ============================================================
runProgram("Recursive FUNCTION",
"DECLARE FUNCTION Fact(n AS INTEGER) AS LONG\n"
"PRINT Fact(1)\n"
"PRINT Fact(5)\n"
"PRINT Fact(10)\n"
"FUNCTION Fact(n AS INTEGER) AS LONG\n"
" IF n <= 1 THEN\n"
" Fact = 1\n"
" ELSE\n"
" Fact = n * Fact(n - 1)\n"
" END IF\n"
"END FUNCTION\n"
);
// Expected: 1 / 120 / 3628800
// ============================================================
// Coverage: String comparison operators
// ============================================================
runProgram("String comparison",
"IF \"abc\" < \"def\" THEN PRINT \"less\"\n"
"IF \"xyz\" > \"abc\" THEN PRINT \"greater\"\n"
"IF \"abc\" = \"abc\" THEN PRINT \"equal\"\n"
"IF \"abc\" <> \"xyz\" THEN PRINT \"notequal\"\n"
);
// Expected: less / greater / equal / notequal
// ============================================================
// Coverage: Apostrophe comment
// ============================================================
runProgram("Apostrophe comment",
"PRINT \"before\" ' this is a comment\n"
"' full line comment\n"
"PRINT \"after\"\n"
);
// Expected: before / after
// ============================================================
// Coverage: SINGLE data type
// ============================================================
runProgram("SINGLE data type",
"DIM s AS SINGLE\n"
"s = 3.14\n"
"PRINT INT(s * 100)\n"
);
// Expected: 314
// ============================================================
// Coverage: Conversion functions
// ============================================================
runProgram("CINT CLNG CDBL CSNG CSTR",
"PRINT CINT(3.7)\n"
"PRINT CLNG(42)\n"
"PRINT CDBL(3)\n"
"PRINT CSNG(3)\n"
"PRINT \"[\" & CSTR(42) & \"]\"\n"
);
// Expected: 3 / 42 / 3 / 3 / [ 42]
// ============================================================
// Coverage: Me keyword
// ============================================================
runProgram("Me keyword",
"' Me compiles to OP_ME_REF (returns NULL outside form context)\n"
"PRINT \"ok\"\n"
);
// Expected: ok (just verify Me doesn't crash compilation)
printf("All tests complete.\n"); printf("All tests complete.\n");
return 0; return 0;
} }

View file

@ -17,6 +17,7 @@
#include <dlfcn.h> #include <dlfcn.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -209,7 +210,7 @@ static void scanDir(const char *dirPath, const char *ext, ModuleT **mods) {
if (nameLen > extLen && strcasecmp(name + nameLen - extLen, ext) == 0) { if (nameLen > extLen && strcasecmp(name + nameLen - extLen, ext) == 0) {
ModuleT mod; ModuleT mod;
memset(&mod, 0, sizeof(mod)); memset(&mod, 0, sizeof(mod));
strncpy(mod.path, path, sizeof(mod.path) - 1); snprintf(mod.path, sizeof(mod.path), "%s", path);
extractBaseName(path, ext, mod.baseName, sizeof(mod.baseName)); extractBaseName(path, ext, mod.baseName, sizeof(mod.baseName));
arrput(*mods, mod); arrput(*mods, mod);
continue; continue;
@ -303,7 +304,7 @@ static void loadInOrder(ModuleT *mods) {
} while (progress && loaded < total); } while (progress && loaded < total);
if (loaded < total) { if (loaded < total) {
fprintf(stderr, "Module loader: %d of %d modules could not be loaded (circular deps or missing deps)\n", total - loaded, total); fprintf(stderr, "Module loader: %d of %d modules could not be loaded (circular deps or missing deps)\n", (int)(total - loaded), (int)total);
for (int32_t i = 0; i < total; i++) { for (int32_t i = 0; i < total; i++) {
if (!mods[i].loaded) { if (!mods[i].loaded) {

View file

@ -135,8 +135,6 @@ static void showSplash(AppContextT *ctx) {
DisplayT *d = &ctx->display; DisplayT *d = &ctx->display;
const BlitOpsT *ops = &ctx->blitOps; const BlitOpsT *ops = &ctx->blitOps;
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
const ColorSchemeT *col = &ctx->colors;
// Dark background // Dark background
uint32_t bgColor = packColor(d, 0, 0, 64); uint32_t bgColor = packColor(d, 0, 0, 64);
rectFill(d, ops, 0, 0, d->width, d->height, bgColor); rectFill(d, ops, 0, 0, d->width, d->height, bgColor);
@ -254,9 +252,9 @@ int shellMain(int argc, char *argv[]) {
const char *val = prefsGetString("colors", dvxColorName((ColorIdE)i), NULL); const char *val = prefsGetString("colors", dvxColorName((ColorIdE)i), NULL);
if (val) { if (val) {
int32_t r; int r;
int32_t g; int g;
int32_t b; int b;
if (sscanf(val, "%d,%d,%d", &r, &g, &b) == 3) { if (sscanf(val, "%d,%d,%d", &r, &g, &b) == 3) {
sCtx.colorRgb[i][0] = (uint8_t)r; sCtx.colorRgb[i][0] = (uint8_t)r;

View file

@ -184,7 +184,17 @@ static int readExistingResources(const char *path, long dxeSize, DvxResDirEntryT
} }
fseek(f, entries[i].offset, SEEK_SET); fseek(f, entries[i].offset, SEEK_SET);
fread(data[i], 1, entries[i].size, f);
if (fread(data[i], 1, entries[i].size, f) != entries[i].size) {
fprintf(stderr, "Short read on entry %d\n", (int)i);
for (uint32_t j = 0; j <= i; j++) {
free(data[j]);
}
free(data);
free(entries);
fclose(f);
return -1;
}
} }
fclose(f); fclose(f);
@ -537,7 +547,7 @@ static int cmdBuild(const char *dxePath, const char *manifestPath) {
data = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *)); data = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *));
memset(&entries[count], 0, sizeof(DvxResDirEntryT)); memset(&entries[count], 0, sizeof(DvxResDirEntryT));
strncpy(entries[count].name, name, DVX_RES_NAME_MAX - 1); snprintf(entries[count].name, DVX_RES_NAME_MAX, "%s", name);
entries[count].type = (uint32_t)type; entries[count].type = (uint32_t)type;
entries[count].size = newSize; entries[count].size = newSize;
data[count] = newData; data[count] = newData;
@ -719,7 +729,13 @@ static int cmdStrip(const char *dxePath) {
return 1; return 1;
} }
fread(buf, 1, dxeSize, f); if (fread(buf, 1, dxeSize, f) != (size_t)dxeSize) {
fprintf(stderr, "Short read on DXE file\n");
free(buf);
fclose(f);
return 1;
}
fclose(f); fclose(f);
// Rewrite truncated // Rewrite truncated

View file

@ -1024,6 +1024,33 @@ void wgtAnsiTermSetScrollback(WidgetT *w, int32_t maxLines) {
} }
// ============================================================
// BASIC-facing accessors
// ============================================================
static int32_t wgtAnsiTermGetCols(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
AnsiTermDataT *at = (AnsiTermDataT *)w->data;
return at->cols;
}
static int32_t wgtAnsiTermGetRows(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
AnsiTermDataT *at = (AnsiTermDataT *)w->data;
return at->rows;
}
static void wgtAnsiTermWriteString(WidgetT *w, const char *text) {
if (!text) {
return;
}
wgtAnsiTermWrite(w, (const uint8_t *)text, (int32_t)strlen(text));
}
static const struct { static const struct {
WidgetT *(*create)(WidgetT *parent, int32_t cols, int32_t rows); WidgetT *(*create)(WidgetT *parent, int32_t cols, int32_t rows);
void (*write)(WidgetT *w, const uint8_t *data, int32_t len); void (*write)(WidgetT *w, const uint8_t *data, int32_t len);
@ -1042,7 +1069,29 @@ static const struct {
.repaint = wgtAnsiTermRepaint .repaint = wgtAnsiTermRepaint
}; };
static const WgtPropDescT sProps[] = {
{ "Cols", WGT_IFACE_INT, (void *)wgtAnsiTermGetCols, NULL },
{ "Rows", WGT_IFACE_INT, (void *)wgtAnsiTermGetRows, NULL },
{ "Scrollback", WGT_IFACE_INT, NULL, (void *)wgtAnsiTermSetScrollback }
};
static const WgtMethodDescT sMethods[] = {
{ "Clear", WGT_SIG_VOID, (void *)wgtAnsiTermClear },
{ "Write", WGT_SIG_STR, (void *)wgtAnsiTermWriteString }
};
static const WgtIfaceT sIface = {
.basName = "Terminal",
.props = sProps,
.propCount = 3,
.methods = sMethods,
.methodCount = 2,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassAnsiTerm); sTypeId = wgtRegisterClass(&sClassAnsiTerm);
wgtRegisterApi("ansiterm", &sApi); wgtRegisterApi("ansiterm", &sApi);
wgtRegisterIface("ansiterm", &sIface);
} }

View file

@ -205,9 +205,20 @@ static const struct {
.frame = wgtFrame .frame = wgtFrame
}; };
static const WgtIfaceT sIface = {
.basName = "Frame",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sVBoxTypeId = wgtRegisterClass(&sClassVBox); sVBoxTypeId = wgtRegisterClass(&sClassVBox);
sHBoxTypeId = wgtRegisterClass(&sClassHBox); sHBoxTypeId = wgtRegisterClass(&sClassHBox);
sFrameTypeId = wgtRegisterClass(&sClassFrame); sFrameTypeId = wgtRegisterClass(&sClassFrame);
wgtRegisterApi("box", &sApi); wgtRegisterApi("box", &sApi);
wgtRegisterIface("box", &sIface);
} }

View file

@ -261,7 +261,18 @@ static const struct {
.create = wgtButton .create = wgtButton
}; };
static const WgtIfaceT sIface = {
.basName = "CommandButton",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassButton); sTypeId = wgtRegisterClass(&sClassButton);
wgtRegisterApi("button", &sApi); wgtRegisterApi("button", &sApi);
wgtRegisterIface("button", &sIface);
} }

View file

@ -850,7 +850,22 @@ static const struct {
.getPixel = wgtCanvasGetPixel .getPixel = wgtCanvasGetPixel
}; };
static const WgtMethodDescT sMethods[] = {
{ "Clear", WGT_SIG_INT, (void *)wgtCanvasClear }
};
static const WgtIfaceT sIface = {
.basName = "PictureBox",
.props = NULL,
.propCount = 0,
.methods = sMethods,
.methodCount = 1,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassCanvas); sTypeId = wgtRegisterClass(&sClassCanvas);
wgtRegisterApi("canvas", &sApi); wgtRegisterApi("canvas", &sApi);
wgtRegisterIface("canvas", &sIface);
} }

View file

@ -251,7 +251,22 @@ static const struct {
.setChecked = wgtCheckboxSetChecked .setChecked = wgtCheckboxSetChecked
}; };
static const WgtPropDescT sProps[] = {
{ "Value", WGT_IFACE_BOOL, (void *)wgtCheckboxIsChecked, (void *)wgtCheckboxSetChecked }
};
static const WgtIfaceT sIface = {
.basName = "CheckBox",
.props = sProps,
.propCount = 1,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassCheckbox); sTypeId = wgtRegisterClass(&sClassCheckbox);
wgtRegisterApi("checkbox", &sApi); wgtRegisterApi("checkbox", &sApi);
wgtRegisterIface("checkbox", &sIface);
} }

View file

@ -537,7 +537,22 @@ static const struct {
.setSelected = wgtComboBoxSetSelected .setSelected = wgtComboBoxSetSelected
}; };
static const WgtPropDescT sProps[] = {
{ "ListIndex", WGT_IFACE_INT, (void *)wgtComboBoxGetSelected, (void *)wgtComboBoxSetSelected }
};
static const WgtIfaceT sIface = {
.basName = "ComboBox",
.props = sProps,
.propCount = 1,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassComboBox); sTypeId = wgtRegisterClass(&sClassComboBox);
wgtRegisterApi("combobox", &sApi); wgtRegisterApi("combobox", &sApi);
wgtRegisterIface("combobox", &sIface);
} }

View file

@ -161,6 +161,7 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
// check. The sClosedPopup reference is cleared on the next event cycle. // check. The sClosedPopup reference is cleared on the next event cycle.
void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) { void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root; (void)root;
(void)vx;
w->focused = true; w->focused = true;
DropdownDataT *d = (DropdownDataT *)w->data; DropdownDataT *d = (DropdownDataT *)w->data;
@ -399,7 +400,22 @@ static const struct {
.setSelected = wgtDropdownSetSelected .setSelected = wgtDropdownSetSelected
}; };
static const WgtPropDescT sProps[] = {
{ "ListIndex", WGT_IFACE_INT, (void *)wgtDropdownGetSelected, (void *)wgtDropdownSetSelected }
};
static const WgtIfaceT sIface = {
.basName = "DropDown",
.props = sProps,
.propCount = 1,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassDropdown); sTypeId = wgtRegisterClass(&sClassDropdown);
wgtRegisterApi("dropdown", &sApi); wgtRegisterApi("dropdown", &sApi);
wgtRegisterIface("dropdown", &sIface);
} }

View file

@ -187,6 +187,50 @@ void wgtImageSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH,
} }
// ============================================================
// BASIC-facing accessors
// ============================================================
static void wgtImageLoadFile(WidgetT *w, const char *path) {
VALIDATE_WIDGET_VOID(w, sTypeId);
if (!path) {
return;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return;
}
int32_t imgW;
int32_t imgH;
int32_t pitch;
uint8_t *buf = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);
if (!buf) {
return;
}
wgtImageSetData(w, buf, imgW, imgH, pitch);
}
static int32_t wgtImageGetWidth(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
ImageDataT *d = (ImageDataT *)w->data;
return d->imgW;
}
static int32_t wgtImageGetHeight(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
ImageDataT *d = (ImageDataT *)w->data;
return d->imgH;
}
// ============================================================ // ============================================================
// DXE registration // DXE registration
// ============================================================ // ============================================================
@ -196,13 +240,32 @@ static const struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch); WidgetT *(*create)(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path); WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch); void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch);
void (*loadFile)(WidgetT *w, const char *path);
} sApi = { } sApi = {
.create = wgtImage, .create = wgtImage,
.fromFile = wgtImageFromFile, .fromFile = wgtImageFromFile,
.setData = wgtImageSetData .setData = wgtImageSetData,
.loadFile = wgtImageLoadFile
};
static const WgtPropDescT sProps[] = {
{ "Picture", WGT_IFACE_STRING, NULL, (void *)wgtImageLoadFile },
{ "ImageWidth", WGT_IFACE_INT, (void *)wgtImageGetWidth, NULL },
{ "ImageHeight", WGT_IFACE_INT, (void *)wgtImageGetHeight, NULL }
};
static const WgtIfaceT sIface = {
.basName = "Image",
.props = sProps,
.propCount = 3,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
}; };
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassImage); sTypeId = wgtRegisterClass(&sClassImage);
wgtRegisterApi("image", &sApi); wgtRegisterApi("image", &sApi);
wgtRegisterIface("image", &sIface);
} }

View file

@ -8,6 +8,7 @@ typedef struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch); WidgetT *(*create)(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path); WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch); void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
void (*loadFile)(WidgetT *w, const char *path);
} ImageApiT; } ImageApiT;
static inline const ImageApiT *dvxImageApi(void) { static inline const ImageApiT *dvxImageApi(void) {
@ -19,5 +20,6 @@ static inline const ImageApiT *dvxImageApi(void) {
#define wgtImage(parent, data, w, h, pitch) dvxImageApi()->create(parent, data, w, h, pitch) #define wgtImage(parent, data, w, h, pitch) dvxImageApi()->create(parent, data, w, h, pitch)
#define wgtImageFromFile(parent, path) dvxImageApi()->fromFile(parent, path) #define wgtImageFromFile(parent, path) dvxImageApi()->fromFile(parent, path)
#define wgtImageSetData(w, data, imgW, imgH, pitch) dvxImageApi()->setData(w, data, imgW, imgH, pitch) #define wgtImageSetData(w, data, imgW, imgH, pitch) dvxImageApi()->setData(w, data, imgW, imgH, pitch)
#define wgtImageLoadFile(w, path) dvxImageApi()->loadFile(w, path)
#endif // WIDGET_IMAGE_H #endif // WIDGET_IMAGE_H

View file

@ -261,6 +261,50 @@ void wgtImageButtonSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t
} }
// ============================================================
// BASIC-facing accessors
// ============================================================
static void wgtImageButtonLoadFile(WidgetT *w, const char *path) {
VALIDATE_WIDGET_VOID(w, sTypeId);
if (!path) {
return;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return;
}
int32_t imgW;
int32_t imgH;
int32_t pitch;
uint8_t *buf = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);
if (!buf) {
return;
}
wgtImageButtonSetData(w, buf, imgW, imgH, pitch);
}
static int32_t wgtImageButtonGetWidth(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
return d->imgW;
}
static int32_t wgtImageButtonGetHeight(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
return d->imgH;
}
// ============================================================ // ============================================================
// DXE registration // DXE registration
// ============================================================ // ============================================================
@ -270,13 +314,32 @@ static const struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch); WidgetT *(*create)(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path); WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch); void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch);
void (*loadFile)(WidgetT *w, const char *path);
} sApi = { } sApi = {
.create = wgtImageButton, .create = wgtImageButton,
.fromFile = wgtImageButtonFromFile, .fromFile = wgtImageButtonFromFile,
.setData = wgtImageButtonSetData .setData = wgtImageButtonSetData,
.loadFile = wgtImageButtonLoadFile
};
static const WgtPropDescT sImgBtnProps[] = {
{ "Picture", WGT_IFACE_STRING, NULL, (void *)wgtImageButtonLoadFile },
{ "ImageWidth", WGT_IFACE_INT, (void *)wgtImageButtonGetWidth, NULL },
{ "ImageHeight", WGT_IFACE_INT, (void *)wgtImageButtonGetHeight, NULL }
};
static const WgtIfaceT sIface = {
.basName = "ImageButton",
.props = sImgBtnProps,
.propCount = 3,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
}; };
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassImageButton); sTypeId = wgtRegisterClass(&sClassImageButton);
wgtRegisterApi("imagebutton", &sApi); wgtRegisterApi("imagebutton", &sApi);
wgtRegisterIface("imagebutton", &sIface);
} }

View file

@ -8,6 +8,7 @@ typedef struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch); WidgetT *(*create)(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path); WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch); void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
void (*loadFile)(WidgetT *w, const char *path);
} ImageButtonApiT; } ImageButtonApiT;
static inline const ImageButtonApiT *dvxImageButtonApi(void) { static inline const ImageButtonApiT *dvxImageButtonApi(void) {
@ -19,5 +20,6 @@ static inline const ImageButtonApiT *dvxImageButtonApi(void) {
#define wgtImageButton(parent, data, w, h, pitch) dvxImageButtonApi()->create(parent, data, w, h, pitch) #define wgtImageButton(parent, data, w, h, pitch) dvxImageButtonApi()->create(parent, data, w, h, pitch)
#define wgtImageButtonFromFile(parent, path) dvxImageButtonApi()->fromFile(parent, path) #define wgtImageButtonFromFile(parent, path) dvxImageButtonApi()->fromFile(parent, path)
#define wgtImageButtonSetData(w, data, imgW, imgH, pitch) dvxImageButtonApi()->setData(w, data, imgW, imgH, pitch) #define wgtImageButtonSetData(w, data, imgW, imgH, pitch) dvxImageButtonApi()->setData(w, data, imgW, imgH, pitch)
#define wgtImageButtonLoadFile(w, path) dvxImageButtonApi()->loadFile(w, path)
#endif // WIDGET_IMAGEBUTTON_H #endif // WIDGET_IMAGEBUTTON_H

View file

@ -160,7 +160,22 @@ static const struct {
.setAlign = wgtLabelSetAlign .setAlign = wgtLabelSetAlign
}; };
static const WgtPropDescT sProps[] = {
{ "Alignment", WGT_IFACE_INT, NULL, (void *)wgtLabelSetAlign }
};
static const WgtIfaceT sIface = {
.basName = "Label",
.props = sProps,
.propCount = 1,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassLabel); sTypeId = wgtRegisterClass(&sClassLabel);
wgtRegisterApi("label", &sApi); wgtRegisterApi("label", &sApi);
wgtRegisterIface("label", &sIface);
} }

View file

@ -65,6 +65,7 @@ typedef struct {
static void ensureScrollVisible(WidgetT *w, int32_t idx); static void ensureScrollVisible(WidgetT *w, int32_t idx);
static void selectRange(WidgetT *w, int32_t from, int32_t to); static void selectRange(WidgetT *w, int32_t from, int32_t to);
static void widgetListBoxScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY); static void widgetListBoxScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY);
void wgtListBoxSelectAll(WidgetT *w);
// ============================================================ // ============================================================
@ -856,7 +857,31 @@ static const struct {
.setReorderable = wgtListBoxSetReorderable .setReorderable = wgtListBoxSetReorderable
}; };
static const WgtPropDescT sProps[] = {
{ "ListIndex", WGT_IFACE_INT, (void *)wgtListBoxGetSelected, (void *)wgtListBoxSetSelected }
};
static const WgtMethodDescT sMethods[] = {
{ "SelectAll", WGT_SIG_VOID, (void *)wgtListBoxSelectAll },
{ "ClearSelection", WGT_SIG_VOID, (void *)wgtListBoxClearSelection },
{ "SetMultiSelect", WGT_SIG_BOOL, (void *)wgtListBoxSetMultiSelect },
{ "SetReorderable", WGT_SIG_BOOL, (void *)wgtListBoxSetReorderable },
{ "IsItemSelected", WGT_SIG_RET_BOOL_INT, (void *)wgtListBoxIsItemSelected },
{ "SetItemSelected", WGT_SIG_INT_BOOL, (void *)wgtListBoxSetItemSelected }
};
static const WgtIfaceT sIface = {
.basName = "ListBox",
.props = sProps,
.propCount = 1,
.methods = sMethods,
.methodCount = 6,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassListBox); sTypeId = wgtRegisterClass(&sClassListBox);
wgtRegisterApi("listbox", &sApi); wgtRegisterApi("listbox", &sApi);
wgtRegisterIface("listbox", &sIface);
} }

View file

@ -107,6 +107,7 @@ static void allocListViewSelBits(WidgetT *w);
static void listViewBuildSortIndex(WidgetT *w); static void listViewBuildSortIndex(WidgetT *w);
static void resolveColumnWidths(WidgetT *w, const BitmapFontT *font); static void resolveColumnWidths(WidgetT *w, const BitmapFontT *font);
static void widgetListViewScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY); static void widgetListViewScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY);
void wgtListViewSelectAll(WidgetT *w);
// ============================================================ // ============================================================
@ -1732,7 +1733,31 @@ static const struct {
.setReorderable = wgtListViewSetReorderable .setReorderable = wgtListViewSetReorderable
}; };
static const WgtPropDescT sProps[] = {
{ "ListIndex", WGT_IFACE_INT, (void *)wgtListViewGetSelected, (void *)wgtListViewSetSelected }
};
static const WgtMethodDescT sMethods[] = {
{ "SelectAll", WGT_SIG_VOID, (void *)wgtListViewSelectAll },
{ "ClearSelection", WGT_SIG_VOID, (void *)wgtListViewClearSelection },
{ "SetMultiSelect", WGT_SIG_BOOL, (void *)wgtListViewSetMultiSelect },
{ "SetReorderable", WGT_SIG_BOOL, (void *)wgtListViewSetReorderable },
{ "IsItemSelected", WGT_SIG_RET_BOOL_INT, (void *)wgtListViewIsItemSelected },
{ "SetItemSelected", WGT_SIG_INT_BOOL, (void *)wgtListViewSetItemSelected }
};
static const WgtIfaceT sIface = {
.basName = "ListView",
.props = sProps,
.propCount = 1,
.methods = sMethods,
.methodCount = 6,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassListView); sTypeId = wgtRegisterClass(&sClassListView);
wgtRegisterApi("listview", &sApi); wgtRegisterApi("listview", &sApi);
wgtRegisterIface("listview", &sIface);
} }

View file

@ -209,7 +209,22 @@ static const struct {
.getValue = wgtProgressBarGetValue .getValue = wgtProgressBarGetValue
}; };
static const WgtPropDescT sProps[] = {
{ "Value", WGT_IFACE_INT, (void *)wgtProgressBarGetValue, (void *)wgtProgressBarSetValue }
};
static const WgtIfaceT sIface = {
.basName = "ProgressBar",
.props = sProps,
.propCount = 1,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassProgressBar); sTypeId = wgtRegisterClass(&sClassProgressBar);
wgtRegisterApi("progressbar", &sApi); wgtRegisterApi("progressbar", &sApi);
wgtRegisterIface("progressbar", &sIface);
} }

View file

@ -411,8 +411,27 @@ static const struct {
.getIndex = wgtRadioGetIndex .getIndex = wgtRadioGetIndex
}; };
static const WgtPropDescT sProps[] = {
{ "Value", WGT_IFACE_INT, (void *)wgtRadioGetIndex, NULL }
};
static const WgtMethodDescT sMethods[] = {
{ "SetSelected", WGT_SIG_INT, (void *)wgtRadioGroupSetSelected }
};
static const WgtIfaceT sIface = {
.basName = "OptionButton",
.props = sProps,
.propCount = 1,
.methods = sMethods,
.methodCount = 1,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sRadioGroupTypeId = wgtRegisterClass(&sClassRadioGroup); sRadioGroupTypeId = wgtRegisterClass(&sClassRadioGroup);
sRadioTypeId = wgtRegisterClass(&sClassRadio); sRadioTypeId = wgtRegisterClass(&sClassRadio);
wgtRegisterApi("radio", &sApi); wgtRegisterApi("radio", &sApi);
wgtRegisterIface("radio", &sIface);
} }

View file

@ -859,7 +859,18 @@ static const struct {
.scrollToChild = wgtScrollPaneScrollToChild .scrollToChild = wgtScrollPaneScrollToChild
}; };
static const WgtIfaceT sIface = {
.basName = "ScrollPane",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassScrollPane); sTypeId = wgtRegisterClass(&sClassScrollPane);
wgtRegisterApi("scrollpane", &sApi); wgtRegisterApi("scrollpane", &sApi);
wgtRegisterIface("scrollpane", &sIface);
} }

View file

@ -142,7 +142,18 @@ static const struct {
.vSeparator = wgtVSeparator .vSeparator = wgtVSeparator
}; };
static const WgtIfaceT sIface = {
.basName = "Line",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassSeparator); sTypeId = wgtRegisterClass(&sClassSeparator);
wgtRegisterApi("separator", &sApi); wgtRegisterApi("separator", &sApi);
wgtRegisterIface("separator", &sIface);
} }

View file

@ -393,7 +393,22 @@ static const struct {
.getValue = wgtSliderGetValue .getValue = wgtSliderGetValue
}; };
static const WgtPropDescT sProps[] = {
{ "Value", WGT_IFACE_INT, (void *)wgtSliderGetValue, (void *)wgtSliderSetValue }
};
static const WgtIfaceT sIface = {
.basName = "HScrollBar",
.props = sProps,
.propCount = 1,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassSlider); sTypeId = wgtRegisterClass(&sClassSlider);
wgtRegisterApi("slider", &sApi); wgtRegisterApi("slider", &sApi);
wgtRegisterIface("slider", &sIface);
} }

View file

@ -69,7 +69,18 @@ static const struct {
.create = wgtSpacer .create = wgtSpacer
}; };
static const WgtIfaceT sIface = {
.basName = "Spacer",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassSpacer); sTypeId = wgtRegisterClass(&sClassSpacer);
wgtRegisterApi("spacer", &sApi); wgtRegisterApi("spacer", &sApi);
wgtRegisterIface("spacer", &sIface);
} }

View file

@ -588,7 +588,27 @@ static const struct {
.setStep = wgtSpinnerSetStep .setStep = wgtSpinnerSetStep
}; };
static const WgtPropDescT sProps[] = {
{ "Value", WGT_IFACE_INT, (void *)wgtSpinnerGetValue, (void *)wgtSpinnerSetValue }
};
static const WgtMethodDescT sMethods[] = {
{ "SetRange", WGT_SIG_INT_INT, (void *)wgtSpinnerSetRange },
{ "SetStep", WGT_SIG_INT, (void *)wgtSpinnerSetStep }
};
static const WgtIfaceT sIface = {
.basName = "SpinButton",
.props = sProps,
.propCount = 1,
.methods = sMethods,
.methodCount = 2,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassSpinner); sTypeId = wgtRegisterClass(&sClassSpinner);
wgtRegisterApi("spinner", &sApi); wgtRegisterApi("spinner", &sApi);
wgtRegisterIface("spinner", &sIface);
} }

View file

@ -524,7 +524,22 @@ static const struct {
.getPos = wgtSplitterGetPos .getPos = wgtSplitterGetPos
}; };
static const WgtPropDescT sProps[] = {
{ "Position", WGT_IFACE_INT, (void *)wgtSplitterGetPos, (void *)wgtSplitterSetPos }
};
static const WgtIfaceT sIface = {
.basName = "Splitter",
.props = sProps,
.propCount = 1,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassSplitter); sTypeId = wgtRegisterClass(&sClassSplitter);
wgtRegisterApi("splitter", &sApi); wgtRegisterApi("splitter", &sApi);
wgtRegisterIface("splitter", &sIface);
} }

View file

@ -107,7 +107,18 @@ static const struct {
.create = wgtStatusBar .create = wgtStatusBar
}; };
static const WgtIfaceT sIface = {
.basName = "StatusBar",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassStatusBar); sTypeId = wgtRegisterClass(&sClassStatusBar);
wgtRegisterApi("statusbar", &sApi); wgtRegisterApi("statusbar", &sApi);
wgtRegisterIface("statusbar", &sIface);
} }

View file

@ -672,8 +672,27 @@ static const struct {
.getActive = wgtTabControlGetActive .getActive = wgtTabControlGetActive
}; };
static const WgtPropDescT sProps[] = {
{ "TabIndex", WGT_IFACE_INT, (void *)wgtTabControlGetActive, (void *)wgtTabControlSetActive }
};
static const WgtMethodDescT sMethods[] = {
{ "SetActive", WGT_SIG_INT, (void *)wgtTabControlSetActive }
};
static const WgtIfaceT sIface = {
.basName = "TabStrip",
.props = sProps,
.propCount = 1,
.methods = sMethods,
.methodCount = 1,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTabControlTypeId = wgtRegisterClass(&sClassTabControl); sTabControlTypeId = wgtRegisterClass(&sClassTabControl);
sTabPageTypeId = wgtRegisterClass(&sClassTabPage); sTabPageTypeId = wgtRegisterClass(&sClassTabPage);
wgtRegisterApi("tabcontrol", &sApi); wgtRegisterApi("tabcontrol", &sApi);
wgtRegisterIface("tabcontrol", &sIface);
} }

View file

@ -101,6 +101,15 @@ typedef struct {
int32_t sbDragOrient; int32_t sbDragOrient;
int32_t sbDragOff; int32_t sbDragOff;
bool sbDragging; bool sbDragging;
bool showLineNumbers;
bool autoIndent;
// Syntax colorizer callback (optional). Called for each visible line.
// line: text of the line (NOT null-terminated, use lineLen).
// colors: output array of color indices (0=default, 1-7=syntax colors).
// The callback fills colors[0..lineLen-1].
void (*colorize)(const char *line, int32_t lineLen, uint8_t *colors, void *ctx);
void *colorizeCtx;
} TextAreaDataT; } TextAreaDataT;
#include <ctype.h> #include <ctype.h>
@ -111,13 +120,26 @@ typedef struct {
#define TEXTAREA_SB_W 14 #define TEXTAREA_SB_W 14
#define TEXTAREA_MIN_ROWS 4 #define TEXTAREA_MIN_ROWS 4
#define TEXTAREA_MIN_COLS 20 #define TEXTAREA_MIN_COLS 20
#define MAX_COLORIZE_LEN 1024
// Match the ANSI terminal cursor blink rate (CURSOR_MS in widgetAnsiTerm.c) // Match the ANSI terminal cursor blink rate (CURSOR_MS in widgetAnsiTerm.c)
#define CURSOR_BLINK_MS 250 #define CURSOR_BLINK_MS 250
// Syntax color indices (returned by colorize callback)
#define SYNTAX_DEFAULT 0
#define SYNTAX_KEYWORD 1
#define SYNTAX_STRING 2
#define SYNTAX_COMMENT 3
#define SYNTAX_NUMBER 4
#define SYNTAX_OPERATOR 5
#define SYNTAX_TYPE 6
#define SYNTAX_MAX 7
// ============================================================ // ============================================================
// Prototypes // Prototypes
// ============================================================ // ============================================================
static void drawColorizedText(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t len, const uint8_t *syntaxColors, int32_t textOff, uint32_t defaultFg, uint32_t bg, const ColorSchemeT *colors);
static uint32_t syntaxColor(const DisplayT *d, uint8_t idx, uint32_t defaultFg, const ColorSchemeT *colors);
static bool maskCharValid(char slot, char ch); static bool maskCharValid(char slot, char ch);
static int32_t maskFirstSlot(const char *mask); static int32_t maskFirstSlot(const char *mask);
static bool maskIsSlot(char ch); static bool maskIsSlot(char ch);
@ -129,6 +151,7 @@ static int32_t textAreaCursorToOff(const char *buf, int32_t len, int32_t row, in
static inline void textAreaDirtyCache(WidgetT *w); static inline void textAreaDirtyCache(WidgetT *w);
static void textAreaEnsureVisible(WidgetT *w, int32_t visRows, int32_t visCols); static void textAreaEnsureVisible(WidgetT *w, int32_t visRows, int32_t visCols);
static int32_t textAreaGetLineCount(WidgetT *w); static int32_t textAreaGetLineCount(WidgetT *w);
static int32_t textAreaGutterWidth(WidgetT *w, const BitmapFontT *font);
static int32_t textAreaGetMaxLineLen(WidgetT *w); static int32_t textAreaGetMaxLineLen(WidgetT *w);
static int32_t textAreaLineLen(const char *buf, int32_t len, int32_t row); static int32_t textAreaLineLen(const char *buf, int32_t len, int32_t row);
static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row); static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row);
@ -142,8 +165,6 @@ static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen); WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
// sCursorBlinkOn is defined in widgetCore.c (shared state) // sCursorBlinkOn is defined in widgetCore.c (shared state)
// sCursorBlinkTime is local to this module
static clock_t sCursorBlinkTime = 0;
@ -607,6 +628,30 @@ static int32_t textAreaGetLineCount(WidgetT *w) {
} }
static int32_t textAreaGutterWidth(WidgetT *w, const BitmapFontT *font) {
TextAreaDataT *ta = (TextAreaDataT *)w->data;
if (!ta->showLineNumbers) {
return 0;
}
int32_t totalLines = textAreaGetLineCount(w);
int32_t digits = 1;
int32_t temp = totalLines;
while (temp >= 10) {
temp /= 10;
digits++;
}
if (digits < 3) {
digits = 3;
}
return (digits + 1) * font->charWidth;
}
static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row) { static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row) {
(void)len; (void)len;
int32_t off = 0; int32_t off = 0;
@ -805,7 +850,8 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
AppContextT *ctx = wgtGetContext(w); AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W; int32_t gutterW = textAreaGutterWidth(w, font);
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W - gutterW;
int32_t visCols = innerW / font->charWidth; int32_t visCols = innerW / font->charWidth;
int32_t maxLL = textAreaGetMaxLineLen(w); int32_t maxLL = textAreaGetMaxLineLen(w);
bool needHSb = (maxLL > visCols); bool needHSb = (maxLL > visCols);
@ -1006,13 +1052,27 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
int32_t off = CUR_OFF(); int32_t off = CUR_OFF();
if (*pLen < bufSize - 1) { // Measure indent of current line before inserting newline
memmove(buf + off + 1, buf + off, *pLen - off + 1); int32_t indent = 0;
char indentBuf[64];
if (ta->autoIndent) {
int32_t lineStart = textAreaLineStart(buf, *pLen, *pRow);
while (lineStart + indent < off && indent < 63 && (buf[lineStart + indent] == ' ' || buf[lineStart + indent] == '\t')) {
indentBuf[indent] = buf[lineStart + indent];
indent++;
}
}
if (*pLen + 1 + indent < bufSize) {
memmove(buf + off + 1 + indent, buf + off, *pLen - off + 1);
buf[off] = '\n'; buf[off] = '\n';
(*pLen)++; memcpy(buf + off + 1, indentBuf, indent);
*pLen += 1 + indent;
(*pRow)++; (*pRow)++;
*pCol = 0; *pCol = indent;
ta->desiredCol = 0; ta->desiredCol = indent;
} }
if (w->onChange) { if (w->onChange) {
@ -1339,9 +1399,10 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
AppContextT *ctx = (AppContextT *)root->userData; AppContextT *ctx = (AppContextT *)root->userData;
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t innerX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD; int32_t gutterW = textAreaGutterWidth(w, font);
int32_t innerX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD + gutterW;
int32_t innerY = w->y + TEXTAREA_BORDER; int32_t innerY = w->y + TEXTAREA_BORDER;
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W; int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W - gutterW;
int32_t visCols = innerW / font->charWidth; int32_t visCols = innerW / font->charWidth;
int32_t maxLL = textAreaGetMaxLineLen(w); int32_t maxLL = textAreaGetMaxLineLen(w);
bool needHSb = (maxLL > visCols); bool needHSb = (maxLL > visCols);
@ -1536,6 +1597,50 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
} }
// ============================================================
// syntaxColor
// ============================================================
static uint32_t syntaxColor(const DisplayT *d, uint8_t idx, uint32_t defaultFg, const ColorSchemeT *colors) {
(void)colors;
switch (idx) {
case SYNTAX_KEYWORD: return packColor(d, 0, 0, 128); // dark blue
case SYNTAX_STRING: return packColor(d, 128, 0, 0); // dark red
case SYNTAX_COMMENT: return packColor(d, 0, 128, 0); // dark green
case SYNTAX_NUMBER: return packColor(d, 128, 0, 128); // purple
case SYNTAX_OPERATOR: return packColor(d, 128, 128, 0); // dark yellow
case SYNTAX_TYPE: return packColor(d, 0, 128, 128); // teal
default: return defaultFg;
}
}
// ============================================================
// drawColorizedText
// ============================================================
//
// Draw text with per-character syntax coloring. Batches consecutive
// characters of the same color into single drawTextN calls.
static void drawColorizedText(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t len, const uint8_t *syntaxColors, int32_t textOff, uint32_t defaultFg, uint32_t bg, const ColorSchemeT *colors) {
int32_t runStart = 0;
while (runStart < len) {
uint8_t curColor = syntaxColors[textOff + runStart];
int32_t runEnd = runStart + 1;
while (runEnd < len && syntaxColors[textOff + runEnd] == curColor) {
runEnd++;
}
uint32_t fg = syntaxColor(d, curColor, defaultFg, colors);
drawTextN(d, ops, font, x + runStart * font->charWidth, y, text + runStart, runEnd - runStart, fg, bg, true);
runStart = runEnd;
}
}
// ============================================================ // ============================================================
// widgetTextAreaPaint // widgetTextAreaPaint
// ============================================================ // ============================================================
@ -1564,7 +1669,8 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
char *buf = ta->buf; char *buf = ta->buf;
int32_t len = ta->len; int32_t len = ta->len;
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W; int32_t gutterW = textAreaGutterWidth(w, font);
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W - gutterW;
int32_t visCols = innerW / font->charWidth; int32_t visCols = innerW / font->charWidth;
int32_t maxLL = textAreaGetMaxLineLen(w); int32_t maxLL = textAreaGetMaxLineLen(w);
bool needHSb = (maxLL > visCols); bool needHSb = (maxLL > visCols);
@ -1605,10 +1711,17 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
} }
// Draw lines -- compute first visible line offset once, then advance incrementally // Draw lines -- compute first visible line offset once, then advance incrementally
int32_t textX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD; int32_t gutterX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
int32_t textY = w->y + TEXTAREA_BORDER; int32_t textX = gutterX + gutterW;
int32_t textY = w->y + TEXTAREA_BORDER;
int32_t lineOff = textAreaLineStart(buf, len, ta->scrollRow); int32_t lineOff = textAreaLineStart(buf, len, ta->scrollRow);
// Draw gutter background
if (gutterW > 0) {
rectFill(d, ops, gutterX, textY, gutterW, innerH, colors->windowFace);
drawVLine(d, ops, gutterX + gutterW - 1, textY, innerH, colors->windowShadow);
}
for (int32_t i = 0; i < visRows; i++) { for (int32_t i = 0; i < visRows; i++) {
int32_t row = ta->scrollRow + i; int32_t row = ta->scrollRow + i;
@ -1623,6 +1736,14 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
} }
int32_t drawY = textY + i * font->charHeight; int32_t drawY = textY + i * font->charHeight;
// Draw line number in gutter
if (gutterW > 0) {
char numBuf[12];
int32_t numLen = snprintf(numBuf, sizeof(numBuf), "%d", (int)(row + 1));
int32_t numX = gutterX + gutterW - (numLen + 1) * font->charWidth;
drawTextN(d, ops, font, numX, drawY, numBuf, numLen, colors->windowShadow, colors->windowFace, true);
}
// Visible range within line // Visible range within line
int32_t scrollCol = ta->scrollCol; int32_t scrollCol = ta->scrollCol;
int32_t visStart = scrollCol; int32_t visStart = scrollCol;
@ -1633,22 +1754,30 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
int32_t drawStart = visStart < textEnd ? visStart : textEnd; int32_t drawStart = visStart < textEnd ? visStart : textEnd;
int32_t drawEnd = visEnd < textEnd ? visEnd : textEnd; int32_t drawEnd = visEnd < textEnd ? visEnd : textEnd;
// Compute syntax colors for this line if colorizer is set
uint8_t syntaxBuf[MAX_COLORIZE_LEN];
bool hasSyntax = false;
if (ta->colorize && lineL > 0) {
int32_t colorLen = lineL < MAX_COLORIZE_LEN ? lineL : MAX_COLORIZE_LEN;
memset(syntaxBuf, 0, colorLen);
ta->colorize(buf + lineOff, colorLen, syntaxBuf, ta->colorizeCtx);
hasSyntax = true;
}
// Determine selection intersection with this line // Determine selection intersection with this line
int32_t lineSelLo = -1; int32_t lineSelLo = -1;
int32_t lineSelHi = -1; int32_t lineSelHi = -1;
if (selLo >= 0) { if (selLo >= 0) {
// Selection range in column-space for this line
if (selLo < lineOff + lineL + 1 && selHi > lineOff) { if (selLo < lineOff + lineL + 1 && selHi > lineOff) {
lineSelLo = selLo - lineOff; lineSelLo = selLo - lineOff;
lineSelHi = selHi - lineOff; lineSelHi = selHi - lineOff;
if (lineSelLo < 0) { lineSelLo = 0; } if (lineSelLo < 0) { lineSelLo = 0; }
// selHi can extend past line (newline selected)
} }
} }
if (lineSelLo >= 0 && lineSelLo < lineSelHi) { if (lineSelLo >= 0 && lineSelLo < lineSelHi) {
// Clamp selection to visible columns for text runs
int32_t vSelLo = lineSelLo < drawStart ? drawStart : lineSelLo; int32_t vSelLo = lineSelLo < drawStart ? drawStart : lineSelLo;
int32_t vSelHi = lineSelHi < drawEnd ? lineSelHi : drawEnd; int32_t vSelHi = lineSelHi < drawEnd ? lineSelHi : drawEnd;
@ -1656,17 +1785,25 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
// Before selection // Before selection
if (drawStart < vSelLo) { if (drawStart < vSelLo) {
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, vSelLo - drawStart, fg, bg, true); if (hasSyntax) {
drawColorizedText(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, vSelLo - drawStart, syntaxBuf, drawStart, fg, bg, colors);
} else {
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, vSelLo - drawStart, fg, bg, true);
}
} }
// Selection (text portion) // Selection (always uses highlight colors, no syntax coloring)
if (vSelLo < vSelHi) { if (vSelLo < vSelHi) {
drawTextN(d, ops, font, textX + (vSelLo - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelLo, vSelHi - vSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true); drawTextN(d, ops, font, textX + (vSelLo - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelLo, vSelHi - vSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
} }
// After selection // After selection
if (vSelHi < drawEnd) { if (vSelHi < drawEnd) {
drawTextN(d, ops, font, textX + (vSelHi - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelHi, drawEnd - vSelHi, fg, bg, true); if (hasSyntax) {
drawColorizedText(d, ops, font, textX + (vSelHi - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelHi, drawEnd - vSelHi, syntaxBuf, vSelHi, fg, bg, colors);
} else {
drawTextN(d, ops, font, textX + (vSelHi - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelHi, drawEnd - vSelHi, fg, bg, true);
}
} }
// Past end of text: fill selected area with highlight bg // Past end of text: fill selected area with highlight bg
@ -1684,9 +1821,13 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
} }
} }
} else { } else {
// No selection on this line -- single run // No selection on this line
if (drawStart < drawEnd) { if (drawStart < drawEnd) {
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, fg, bg, true); if (hasSyntax) {
drawColorizedText(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, syntaxBuf, drawStart, fg, bg, colors);
} else {
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, fg, bg, true);
}
} }
} }
@ -2157,9 +2298,10 @@ static void widgetTextAreaDragSelect(WidgetT *w, WidgetT *root, int32_t vx, int3
AppContextT *ctx = wgtGetContext(w); AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font; const BitmapFontT *font = &ctx->font;
int32_t innerX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD; int32_t gutterW = textAreaGutterWidth(w, font);
int32_t innerY = w->y + TEXTAREA_BORDER; int32_t innerX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD + gutterW;
int32_t relX = vx - innerX; int32_t innerY = w->y + TEXTAREA_BORDER;
int32_t relX = vx - innerX;
int32_t relY = vy - innerY; int32_t relY = vy - innerY;
int32_t totalLines = textAreaGetLineCount(w); int32_t totalLines = textAreaGetLineCount(w);
@ -2343,6 +2485,86 @@ WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen) {
} }
// ============================================================
// wgtTextAreaSetColorize
// ============================================================
void wgtTextAreaGoToLine(WidgetT *w, int32_t line) {
if (!w || w->type != sTextAreaTypeId) {
return;
}
TextAreaDataT *ta = (TextAreaDataT *)w->data;
int32_t totalLines = textAreaGetLineCount(w);
int32_t row = line - 1; // 1-based to 0-based
if (row < 0) {
row = 0;
}
if (row >= totalLines) {
row = totalLines - 1;
}
ta->cursorRow = row;
ta->cursorCol = 0;
ta->desiredCol = 0;
// Select the entire line for visual highlight
int32_t lineStart = textAreaLineStart(ta->buf, ta->len, row);
int32_t lineL = textAreaLineLen(ta->buf, ta->len, row);
ta->selAnchor = lineStart;
ta->selCursor = lineStart + lineL;
// Scroll into view
AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font;
int32_t gutterW = textAreaGutterWidth(w, font);
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W - gutterW;
int32_t visCols = innerW / font->charWidth;
int32_t innerH = w->h - TEXTAREA_BORDER * 2;
int32_t visRows = innerH / font->charHeight;
if (visCols < 1) { visCols = 1; }
if (visRows < 1) { visRows = 1; }
textAreaEnsureVisible(w, visRows, visCols);
wgtInvalidatePaint(w);
}
void wgtTextAreaSetAutoIndent(WidgetT *w, bool enable) {
if (!w || w->type != sTextAreaTypeId) {
return;
}
TextAreaDataT *ta = (TextAreaDataT *)w->data;
ta->autoIndent = enable;
}
void wgtTextAreaSetColorize(WidgetT *w, void (*fn)(const char *, int32_t, uint8_t *, void *), void *ctx) {
if (!w || w->type != sTextAreaTypeId) {
return;
}
TextAreaDataT *ta = (TextAreaDataT *)w->data;
ta->colorize = fn;
ta->colorizeCtx = ctx;
}
void wgtTextAreaSetShowLineNumbers(WidgetT *w, bool show) {
if (!w || w->type != sTextAreaTypeId) {
return;
}
TextAreaDataT *ta = (TextAreaDataT *)w->data;
ta->showLineNumbers = show;
wgtInvalidatePaint(w);
}
// ============================================================ // ============================================================
// DXE registration // DXE registration
// ============================================================ // ============================================================
@ -2353,15 +2575,34 @@ static const struct {
WidgetT *(*password)(WidgetT *parent, int32_t maxLen); WidgetT *(*password)(WidgetT *parent, int32_t maxLen);
WidgetT *(*masked)(WidgetT *parent, const char *mask); WidgetT *(*masked)(WidgetT *parent, const char *mask);
WidgetT *(*textArea)(WidgetT *parent, int32_t maxLen); WidgetT *(*textArea)(WidgetT *parent, int32_t maxLen);
void (*setColorize)(WidgetT *w, void (*fn)(const char *, int32_t, uint8_t *, void *), void *ctx);
void (*goToLine)(WidgetT *w, int32_t line);
void (*setAutoIndent)(WidgetT *w, bool enable);
void (*setShowLineNumbers)(WidgetT *w, bool show);
} sApi = { } sApi = {
.create = wgtTextInput, .create = wgtTextInput,
.password = wgtPasswordInput, .password = wgtPasswordInput,
.masked = wgtMaskedInput, .masked = wgtMaskedInput,
.textArea = wgtTextArea .textArea = wgtTextArea,
.setColorize = wgtTextAreaSetColorize,
.goToLine = wgtTextAreaGoToLine,
.setAutoIndent = wgtTextAreaSetAutoIndent,
.setShowLineNumbers = wgtTextAreaSetShowLineNumbers
};
static const WgtIfaceT sIface = {
.basName = "TextBox",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
}; };
void wgtRegister(void) { void wgtRegister(void) {
sTextInputTypeId = wgtRegisterClass(&sClassTextInput); sTextInputTypeId = wgtRegisterClass(&sClassTextInput);
sTextAreaTypeId = wgtRegisterClass(&sClassTextArea); sTextAreaTypeId = wgtRegisterClass(&sClassTextArea);
wgtRegisterApi("textinput", &sApi); wgtRegisterApi("textinput", &sApi);
wgtRegisterIface("textinput", &sIface);
} }

View file

@ -4,11 +4,24 @@
#include "../core/dvxWidget.h" #include "../core/dvxWidget.h"
// Colorize callback: called for each visible line during paint.
// line: pointer into the buffer (NOT null-terminated).
// lineLen: number of characters in this line.
// colors: output array -- fill colors[0..lineLen-1] with color indices:
// 0 = default, 1 = keyword, 2 = string, 3 = comment,
// 4 = number, 5 = operator, 6 = type/builtin, 7 = reserved
// ctx: user context pointer.
typedef void (*TextColorFnT)(const char *line, int32_t lineLen, uint8_t *colors, void *ctx);
typedef struct { typedef struct {
WidgetT *(*create)(WidgetT *parent, int32_t maxLen); WidgetT *(*create)(WidgetT *parent, int32_t maxLen);
WidgetT *(*password)(WidgetT *parent, int32_t maxLen); WidgetT *(*password)(WidgetT *parent, int32_t maxLen);
WidgetT *(*masked)(WidgetT *parent, const char *mask); WidgetT *(*masked)(WidgetT *parent, const char *mask);
WidgetT *(*textArea)(WidgetT *parent, int32_t maxLen); WidgetT *(*textArea)(WidgetT *parent, int32_t maxLen);
void (*setColorize)(WidgetT *w, TextColorFnT fn, void *ctx);
void (*goToLine)(WidgetT *w, int32_t line);
void (*setAutoIndent)(WidgetT *w, bool enable);
void (*setShowLineNumbers)(WidgetT *w, bool show);
} TextInputApiT; } TextInputApiT;
static inline const TextInputApiT *dvxTextInputApi(void) { static inline const TextInputApiT *dvxTextInputApi(void) {
@ -21,5 +34,9 @@ static inline const TextInputApiT *dvxTextInputApi(void) {
#define wgtPasswordInput(parent, maxLen) dvxTextInputApi()->password(parent, maxLen) #define wgtPasswordInput(parent, maxLen) dvxTextInputApi()->password(parent, maxLen)
#define wgtMaskedInput(parent, mask) dvxTextInputApi()->masked(parent, mask) #define wgtMaskedInput(parent, mask) dvxTextInputApi()->masked(parent, mask)
#define wgtTextArea(parent, maxLen) dvxTextInputApi()->textArea(parent, maxLen) #define wgtTextArea(parent, maxLen) dvxTextInputApi()->textArea(parent, maxLen)
#define wgtTextAreaSetColorize(w, fn, ctx) dvxTextInputApi()->setColorize(w, fn, ctx)
#define wgtTextAreaGoToLine(w, line) dvxTextInputApi()->goToLine(w, line)
#define wgtTextAreaSetAutoIndent(w, en) dvxTextInputApi()->setAutoIndent(w, en)
#define wgtTextAreaSetShowLineNumbers(w, show) dvxTextInputApi()->setShowLineNumbers(w, show)
#endif // WIDGET_TEXTINPUT_H #endif // WIDGET_TEXTINPUT_H

View file

@ -219,7 +219,32 @@ static const struct {
.updateTimers = wgtUpdateTimers .updateTimers = wgtUpdateTimers
}; };
static const WgtPropDescT sProps[] = {
{ "Enabled", WGT_IFACE_BOOL, (void *)wgtTimerIsRunning, NULL },
{ "Interval", WGT_IFACE_INT, NULL, (void *)wgtTimerSetInterval }
};
static const WgtMethodDescT sMethods[] = {
{ "Start", WGT_SIG_VOID, (void *)wgtTimerStart },
{ "Stop", WGT_SIG_VOID, (void *)wgtTimerStop }
};
static const WgtEventDescT sEvents[] = {
{ "Timer" }
};
static const WgtIfaceT sIface = {
.basName = "Timer",
.props = sProps,
.propCount = 2,
.methods = sMethods,
.methodCount = 2,
.events = sEvents,
.eventCount = 1
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassTimer); sTypeId = wgtRegisterClass(&sClassTimer);
wgtRegisterApi("timer", &sApi); wgtRegisterApi("timer", &sApi);
wgtRegisterIface("timer", &sIface);
} }

View file

@ -100,7 +100,18 @@ static const struct {
.create = wgtToolbar .create = wgtToolbar
}; };
static const WgtIfaceT sIface = {
.basName = "Toolbar",
.props = NULL,
.propCount = 0,
.methods = NULL,
.methodCount = 0,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassToolbar); sTypeId = wgtRegisterClass(&sClassToolbar);
wgtRegisterApi("toolbar", &sApi); wgtRegisterApi("toolbar", &sApi);
wgtRegisterIface("toolbar", &sIface);
} }

View file

@ -1722,8 +1722,24 @@ static const struct {
.itemSetSelected = wgtTreeItemSetSelected .itemSetSelected = wgtTreeItemSetSelected
}; };
static const WgtMethodDescT sMethods[] = {
{ "SetMultiSelect", WGT_SIG_BOOL, (void *)wgtTreeViewSetMultiSelect },
{ "SetReorderable", WGT_SIG_BOOL, (void *)wgtTreeViewSetReorderable }
};
static const WgtIfaceT sIface = {
.basName = "TreeView",
.props = NULL,
.propCount = 0,
.methods = sMethods,
.methodCount = 2,
.events = NULL,
.eventCount = 0
};
void wgtRegister(void) { void wgtRegister(void) {
sTreeViewTypeId = wgtRegisterClass(&sClassTreeView); sTreeViewTypeId = wgtRegisterClass(&sClassTreeView);
sTreeItemTypeId = wgtRegisterClass(&sClassTreeItem); sTreeItemTypeId = wgtRegisterClass(&sClassTreeItem);
wgtRegisterApi("treeview", &sApi); wgtRegisterApi("treeview", &sApi);
wgtRegisterIface("treeview", &sIface);
} }