BASIC is getting to be pretty stable.
This commit is contained in:
parent
b3cc66be4b
commit
65d7a252ca
61 changed files with 5305 additions and 195 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -7,3 +7,7 @@ lib/
|
||||||
.gitattributes~
|
.gitattributes~
|
||||||
*.SWP
|
*.SWP
|
||||||
.claude/
|
.claude/
|
||||||
|
dvxbasic/test_compiler
|
||||||
|
dvxbasic/test_lex
|
||||||
|
dvxbasic/test_quick
|
||||||
|
dvxbasic/test_vm
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
157
core/dvxDialog.c
157
core/dvxDialog.c
|
|
@ -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
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)) {
|
||||||
|
if (argc < sym->paramCount && !sym->paramByVal[argc]) {
|
||||||
|
emitByRefArg(p);
|
||||||
|
} else {
|
||||||
parseExpression(p);
|
parseExpression(p);
|
||||||
|
}
|
||||||
argc++;
|
argc++;
|
||||||
while (match(p, TOK_COMMA)) {
|
while (match(p, TOK_COMMA)) {
|
||||||
|
if (argc < sym->paramCount && !sym->paramByVal[argc]) {
|
||||||
|
emitByRefArg(p);
|
||||||
|
} else {
|
||||||
parseExpression(p);
|
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)) {
|
||||||
|
if (argc < sym->paramCount && !sym->paramByVal[argc]) {
|
||||||
|
emitByRefArg(p);
|
||||||
|
} else {
|
||||||
parseExpression(p);
|
parseExpression(p);
|
||||||
|
}
|
||||||
argc++;
|
argc++;
|
||||||
while (match(p, TOK_COMMA)) {
|
while (match(p, TOK_COMMA)) {
|
||||||
|
if (argc < sym->paramCount && !sym->paramByVal[argc]) {
|
||||||
|
emitByRefArg(p);
|
||||||
|
} else {
|
||||||
parseExpression(p);
|
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,25 +3725,69 @@ 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 (;;) {
|
||||||
|
if (check(p, TOK_IS)) {
|
||||||
|
// CASE IS <op> value
|
||||||
|
advance(p); // consume IS
|
||||||
|
|
||||||
|
uint8_t cmpOp;
|
||||||
|
|
||||||
|
if (check(p, TOK_LT)) { cmpOp = OP_CMP_LT; advance(p); }
|
||||||
|
else if (check(p, TOK_GT)) { cmpOp = OP_CMP_GT; advance(p); }
|
||||||
|
else if (check(p, TOK_LE)) { cmpOp = OP_CMP_LE; advance(p); }
|
||||||
|
else if (check(p, TOK_GE)) { cmpOp = OP_CMP_GE; advance(p); }
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
basEmit8(&p->cg, OP_DUP);
|
basEmit8(&p->cg, OP_DUP);
|
||||||
parseExpression(p);
|
parseExpression(p);
|
||||||
basEmit8(&p->cg, OP_CMP_EQ);
|
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) {
|
if (bodyJumpCount < MAX_EXITS) {
|
||||||
bodyJumps[bodyJumpCount++] = emitJump(p, OP_JMP_TRUE);
|
bodyJumps[bodyJumpCount++] = emitJump(p, OP_JMP_TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional comma-separated values
|
patchJump(p, skipRange);
|
||||||
while (!p->hasError && match(p, TOK_COMMA)) {
|
} else {
|
||||||
basEmit8(&p->cg, OP_DUP);
|
// Plain value -- equality test
|
||||||
parseExpression(p);
|
|
||||||
basEmit8(&p->cg, OP_CMP_EQ);
|
basEmit8(&p->cg, OP_CMP_EQ);
|
||||||
|
|
||||||
if (bodyJumpCount < MAX_EXITS) {
|
if (bodyJumpCount < MAX_EXITS) {
|
||||||
bodyJumps[bodyJumpCount++] = emitJump(p, OP_JMP_TRUE);
|
bodyJumps[bodyJumpCount++] = emitJump(p, OP_JMP_TRUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!match(p, TOK_COMMA)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// None matched -- jump to next case
|
// None matched -- jump to next case
|
||||||
int32_t nextCaseJump = emitJump(p, OP_JMP);
|
int32_t nextCaseJump = emitJump(p, OP_JMP);
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
1619
dvxbasic/formrt/formrt.c
Normal file
File diff suppressed because it is too large
Load diff
110
dvxbasic/formrt/formrt.h
Normal file
110
dvxbasic/formrt/formrt.h
Normal 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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -74,14 +92,28 @@ 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 *sImmediate = NULL; // TextArea for immediate window
|
||||||
|
static WidgetT *sObjDropdown = NULL; // Object dropdown
|
||||||
|
static WidgetT *sEvtDropdown = NULL; // Event dropdown
|
||||||
static WidgetT *sStatus = NULL; // Status bar label
|
static WidgetT *sStatus = NULL; // Status bar label
|
||||||
static BasVmT *sVm = NULL; // VM instance (non-NULL while running)
|
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,48 +523,59 @@ 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_STEP_LIMIT) {
|
||||||
|
// Yield to DVX to keep the GUI responsive
|
||||||
|
dvxUpdate(sAc);
|
||||||
|
|
||||||
|
// Stop if IDE window was closed or DVX is shutting down
|
||||||
|
if (!sWin || !sAc->running) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (result != BAS_VM_OK) {
|
|
||||||
if (result == BAS_VM_HALTED) {
|
if (result == BAS_VM_HALTED) {
|
||||||
// Normal completion
|
break;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
// Runtime error
|
// Runtime error
|
||||||
int32_t pos = sOutputLen;
|
int32_t pos = sOutputLen;
|
||||||
int32_t n = snprintf(sOutputBuf + pos, IDE_MAX_OUTPUT - pos, "\n[Runtime error: %s]\n", basVmGetError(vm));
|
int32_t n = snprintf(sOutputBuf + pos, IDE_MAX_OUTPUT - pos, "\n[Runtime error: %s]\n", basVmGetError(vm));
|
||||||
sOutputLen += n;
|
sOutputLen += n;
|
||||||
wgtSetText(sOutput, sOutputBuf);
|
wgtSetText(sOutput, sOutputBuf);
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yield to DVX every 10000 steps to keep the GUI responsive
|
|
||||||
if (stepCount % 10000 == 0) {
|
|
||||||
dvxUpdate(sAc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sVm = NULL;
|
sVm = NULL;
|
||||||
|
|
||||||
// Update output display
|
// Update output display
|
||||||
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
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -409,7 +835,16 @@ static void onClose(WindowT *win) {
|
||||||
sWin = NULL;
|
sWin = NULL;
|
||||||
sEditor = NULL;
|
sEditor = NULL;
|
||||||
sOutput = NULL;
|
sOutput = NULL;
|
||||||
|
sImmediate = NULL;
|
||||||
|
sObjDropdown = NULL;
|
||||||
|
sEvtDropdown = NULL;
|
||||||
sStatus = 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,9 +106,11 @@ BasStringT *basStringNew(const char *text, int32_t len) {
|
||||||
}
|
}
|
||||||
|
|
||||||
BasStringT *s = basStringAlloc(len + 1);
|
BasStringT *s = basStringAlloc(len + 1);
|
||||||
|
if (s->cap >= len + 1) {
|
||||||
memcpy(s->data, text, len);
|
memcpy(s->data, text, len);
|
||||||
s->data[len] = '\0';
|
s->data[len] = '\0';
|
||||||
s->len = len;
|
s->len = len;
|
||||||
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -171,11 +239,18 @@ 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,
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,6 +206,20 @@ 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)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -218,6 +233,8 @@ typedef struct {
|
||||||
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
|
||||||
|
|
|
||||||
29
dvxbasic/samples/clickme.bas
Normal file
29
dvxbasic/samples/clickme.bas
Normal 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
|
||||||
12
dvxbasic/samples/clickme.frm
Normal file
12
dvxbasic/samples/clickme.frm
Normal 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
|
||||||
8
dvxbasic/samples/formtest.bas
Normal file
8
dvxbasic/samples/formtest.bas
Normal 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."
|
||||||
13
dvxbasic/samples/input.bas
Normal file
13
dvxbasic/samples/input.bas
Normal 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!"
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 textX = gutterX + gutterW;
|
||||||
int32_t textY = w->y + TEXTAREA_BORDER;
|
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,18 +1785,26 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
|
|
||||||
// Before selection
|
// Before selection
|
||||||
if (drawStart < vSelLo) {
|
if (drawStart < vSelLo) {
|
||||||
|
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);
|
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) {
|
||||||
|
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);
|
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
|
||||||
int32_t nlOff = lineOff + lineL;
|
int32_t nlOff = lineOff + lineL;
|
||||||
|
|
@ -1684,11 +1821,15 @@ 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) {
|
||||||
|
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);
|
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, fg, bg, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Advance lineOff to the next line
|
// Advance lineOff to the next line
|
||||||
lineOff += lineL;
|
lineOff += lineL;
|
||||||
|
|
@ -2157,7 +2298,8 @@ 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 innerX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD + gutterW;
|
||||||
int32_t innerY = w->y + TEXTAREA_BORDER;
|
int32_t innerY = w->y + TEXTAREA_BORDER;
|
||||||
int32_t relX = vx - innerX;
|
int32_t relX = vx - innerX;
|
||||||
int32_t relY = vy - innerY;
|
int32_t relY = vy - innerY;
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue