Compare commits
5 commits
0043e06c82
...
932f44e784
| Author | SHA1 | Date | |
|---|---|---|---|
| 932f44e784 | |||
| 9b43582f87 | |||
| 6050c32c7a | |||
| 1b9e0155c8 | |||
| da84c2a599 |
74 changed files with 18717 additions and 75 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
|
||||||
|
|
|
||||||
8
Makefile
8
Makefile
|
|
@ -3,9 +3,9 @@
|
||||||
# Builds the full DVX stack: core library, task switcher,
|
# Builds the full DVX stack: core library, task switcher,
|
||||||
# bootstrap loader, text help library, widgets, shell, and apps.
|
# bootstrap loader, text help library, widgets, shell, and apps.
|
||||||
|
|
||||||
.PHONY: all clean core tasks loader texthelp listhelp widgets shell taskmgr serial apps tools
|
.PHONY: all clean core tasks loader texthelp listhelp widgets shell taskmgr serial dvxbasic apps tools
|
||||||
|
|
||||||
all: core tasks loader texthelp listhelp widgets shell taskmgr serial apps tools
|
all: core tasks loader texthelp listhelp widgets shell taskmgr serial dvxbasic apps tools
|
||||||
|
|
||||||
core:
|
core:
|
||||||
$(MAKE) -C core
|
$(MAKE) -C core
|
||||||
|
|
@ -34,6 +34,9 @@ taskmgr: shell
|
||||||
serial: core tasks
|
serial: core tasks
|
||||||
$(MAKE) -C serial
|
$(MAKE) -C serial
|
||||||
|
|
||||||
|
dvxbasic: core tasks shell tools
|
||||||
|
$(MAKE) -C dvxbasic
|
||||||
|
|
||||||
tools:
|
tools:
|
||||||
$(MAKE) -C tools
|
$(MAKE) -C tools
|
||||||
|
|
||||||
|
|
@ -50,6 +53,7 @@ clean:
|
||||||
$(MAKE) -C shell clean
|
$(MAKE) -C shell clean
|
||||||
$(MAKE) -C taskmgr clean
|
$(MAKE) -C taskmgr clean
|
||||||
$(MAKE) -C serial clean
|
$(MAKE) -C serial clean
|
||||||
|
$(MAKE) -C dvxbasic clean
|
||||||
$(MAKE) -C apps clean
|
$(MAKE) -C apps clean
|
||||||
$(MAKE) -C tools clean
|
$(MAKE) -C tools clean
|
||||||
-rmdir obj 2>/dev/null
|
-rmdir obj 2>/dev/null
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
2
config/basrt.dep
Normal file
2
config/basrt.dep
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
libtasks
|
||||||
|
libdvx
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -2315,6 +2315,7 @@ extern unsigned long long __umoddi3(unsigned long long, unsigned long long);
|
||||||
extern void __dj_assert(const char *, const char *, int);
|
extern void __dj_assert(const char *, const char *, int);
|
||||||
extern unsigned short __dj_ctype_flags[];
|
extern unsigned short __dj_ctype_flags[];
|
||||||
extern unsigned char __dj_ctype_tolower[];
|
extern unsigned char __dj_ctype_tolower[];
|
||||||
|
extern unsigned char __dj_ctype_toupper[];
|
||||||
|
|
||||||
// GCC emulated thread-local storage
|
// GCC emulated thread-local storage
|
||||||
extern void *__emutls_get_address(void *);
|
extern void *__emutls_get_address(void *);
|
||||||
|
|
@ -2424,6 +2425,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
||||||
DXE_EXPORT(strcspn)
|
DXE_EXPORT(strcspn)
|
||||||
{ "_strdup", (void *)dvxStrdup },
|
{ "_strdup", (void *)dvxStrdup },
|
||||||
DXE_EXPORT(strerror)
|
DXE_EXPORT(strerror)
|
||||||
|
DXE_EXPORT(strnicmp)
|
||||||
DXE_EXPORT(stricmp)
|
DXE_EXPORT(stricmp)
|
||||||
DXE_EXPORT(strlen)
|
DXE_EXPORT(strlen)
|
||||||
DXE_EXPORT(strncasecmp)
|
DXE_EXPORT(strncasecmp)
|
||||||
|
|
@ -2439,6 +2441,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
||||||
// --- ctype ---
|
// --- ctype ---
|
||||||
DXE_EXPORT(isalnum)
|
DXE_EXPORT(isalnum)
|
||||||
DXE_EXPORT(isalpha)
|
DXE_EXPORT(isalpha)
|
||||||
|
DXE_EXPORT(isascii)
|
||||||
DXE_EXPORT(isdigit)
|
DXE_EXPORT(isdigit)
|
||||||
DXE_EXPORT(islower)
|
DXE_EXPORT(islower)
|
||||||
DXE_EXPORT(isprint)
|
DXE_EXPORT(isprint)
|
||||||
|
|
@ -2451,6 +2454,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
||||||
|
|
||||||
// --- conversion ---
|
// --- conversion ---
|
||||||
DXE_EXPORT(abs)
|
DXE_EXPORT(abs)
|
||||||
|
DXE_EXPORT(ldiv)
|
||||||
DXE_EXPORT(atof)
|
DXE_EXPORT(atof)
|
||||||
DXE_EXPORT(atoi)
|
DXE_EXPORT(atoi)
|
||||||
DXE_EXPORT(atol)
|
DXE_EXPORT(atol)
|
||||||
|
|
@ -2461,6 +2465,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
||||||
|
|
||||||
// --- formatted I/O ---
|
// --- formatted I/O ---
|
||||||
DXE_EXPORT(fprintf)
|
DXE_EXPORT(fprintf)
|
||||||
|
DXE_EXPORT(perror)
|
||||||
DXE_EXPORT(fputs)
|
DXE_EXPORT(fputs)
|
||||||
DXE_EXPORT(fscanf)
|
DXE_EXPORT(fscanf)
|
||||||
DXE_EXPORT(printf)
|
DXE_EXPORT(printf)
|
||||||
|
|
@ -2521,13 +2526,19 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
||||||
DXE_EXPORT(localtime)
|
DXE_EXPORT(localtime)
|
||||||
DXE_EXPORT(mktime)
|
DXE_EXPORT(mktime)
|
||||||
DXE_EXPORT(strftime)
|
DXE_EXPORT(strftime)
|
||||||
|
DXE_EXPORT(asctime)
|
||||||
|
DXE_EXPORT(ctime)
|
||||||
|
DXE_EXPORT(sleep)
|
||||||
DXE_EXPORT(time)
|
DXE_EXPORT(time)
|
||||||
|
DXE_EXPORT(usleep)
|
||||||
|
|
||||||
// --- process / environment ---
|
// --- process / environment ---
|
||||||
DXE_EXPORT(abort)
|
DXE_EXPORT(abort)
|
||||||
DXE_EXPORT(atexit)
|
DXE_EXPORT(atexit)
|
||||||
DXE_EXPORT(exit)
|
DXE_EXPORT(exit)
|
||||||
DXE_EXPORT(getenv)
|
DXE_EXPORT(getenv)
|
||||||
|
DXE_EXPORT(setenv)
|
||||||
|
DXE_EXPORT(putenv)
|
||||||
DXE_EXPORT(system)
|
DXE_EXPORT(system)
|
||||||
|
|
||||||
// --- sorting / searching ---
|
// --- sorting / searching ---
|
||||||
|
|
@ -2541,6 +2552,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
||||||
// --- setjmp / signal ---
|
// --- setjmp / signal ---
|
||||||
DXE_EXPORT(longjmp)
|
DXE_EXPORT(longjmp)
|
||||||
DXE_EXPORT(setjmp)
|
DXE_EXPORT(setjmp)
|
||||||
|
DXE_EXPORT(raise)
|
||||||
DXE_EXPORT(signal)
|
DXE_EXPORT(signal)
|
||||||
|
|
||||||
// --- libm ---
|
// --- libm ---
|
||||||
|
|
@ -2561,7 +2573,11 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
||||||
DXE_EXPORT(modf)
|
DXE_EXPORT(modf)
|
||||||
DXE_EXPORT(pow)
|
DXE_EXPORT(pow)
|
||||||
DXE_EXPORT(sin)
|
DXE_EXPORT(sin)
|
||||||
|
DXE_EXPORT(sinh)
|
||||||
|
DXE_EXPORT(cosh)
|
||||||
|
DXE_EXPORT(tanh)
|
||||||
DXE_EXPORT(sqrt)
|
DXE_EXPORT(sqrt)
|
||||||
|
DXE_EXPORT(strtof)
|
||||||
DXE_EXPORT(tan)
|
DXE_EXPORT(tan)
|
||||||
|
|
||||||
// --- errno ---
|
// --- errno ---
|
||||||
|
|
@ -2582,6 +2598,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
||||||
DXE_EXPORT(__dj_assert)
|
DXE_EXPORT(__dj_assert)
|
||||||
DXE_EXPORT(__dj_ctype_flags)
|
DXE_EXPORT(__dj_ctype_flags)
|
||||||
DXE_EXPORT(__dj_ctype_tolower)
|
DXE_EXPORT(__dj_ctype_tolower)
|
||||||
|
DXE_EXPORT(__dj_ctype_toupper)
|
||||||
DXE_EXPORT(__djgpp_exception_state_ptr)
|
DXE_EXPORT(__djgpp_exception_state_ptr)
|
||||||
|
|
||||||
// --- GCC internals ---
|
// --- GCC internals ---
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
128
dvxbasic/Makefile
Normal file
128
dvxbasic/Makefile
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# DVX BASIC Makefile for DJGPP cross-compilation
|
||||||
|
#
|
||||||
|
# Builds:
|
||||||
|
# basrt.lib -- BASIC runtime library (VM + values + dlregsym init)
|
||||||
|
# dvxbasic.app -- BASIC IDE (compiler + UI, links against basrt.lib)
|
||||||
|
#
|
||||||
|
# The runtime is a separate library so compiled BASIC apps can use
|
||||||
|
# it without including the compiler. The library's constructor calls
|
||||||
|
# dlregsym() to register its exports, making them available to DXEs
|
||||||
|
# loaded later (apps, widgets, etc.).
|
||||||
|
|
||||||
|
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||||
|
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||||
|
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
|
||||||
|
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../widgets -I../shell -I../tasks -I../core/thirdparty -I.
|
||||||
|
|
||||||
|
OBJDIR = ../obj/dvxbasic
|
||||||
|
LIBSDIR = ../bin/libs
|
||||||
|
APPDIR = ../bin/apps/dvxbasic
|
||||||
|
DVXRES = ../bin/dvxres
|
||||||
|
SAMPLES = samples/hello.bas samples/formtest.bas samples/clickme.bas samples/clickme.frm samples/input.bas
|
||||||
|
|
||||||
|
# Runtime library objects (VM + values)
|
||||||
|
RT_OBJS = $(OBJDIR)/vm.o $(OBJDIR)/values.o
|
||||||
|
RT_TARGET = $(LIBSDIR)/basrt.lib
|
||||||
|
|
||||||
|
# Compiler objects (only needed by the IDE)
|
||||||
|
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
|
||||||
|
APP_OBJS = $(OBJDIR)/ideMain.o $(FORMRT_OBJS)
|
||||||
|
APP_TARGET = $(APPDIR)/dvxbasic.app
|
||||||
|
|
||||||
|
# Native test programs (host gcc, not cross-compiled)
|
||||||
|
HOSTCC = gcc
|
||||||
|
HOSTCFLAGS = -O2 -Wall -Wextra -I.
|
||||||
|
BINDIR = ../bin
|
||||||
|
|
||||||
|
TEST_COMPILER = $(BINDIR)/test_compiler
|
||||||
|
TEST_VM = $(BINDIR)/test_vm
|
||||||
|
TEST_LEX = $(BINDIR)/test_lex
|
||||||
|
TEST_QUICK = $(BINDIR)/test_quick
|
||||||
|
|
||||||
|
TEST_COMPILER_SRCS = test_compiler.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c
|
||||||
|
TEST_VM_SRCS = test_vm.c runtime/vm.c runtime/values.c
|
||||||
|
TEST_LEX_SRCS = test_lex.c compiler/lexer.c
|
||||||
|
TEST_QUICK_SRCS = test_quick.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c
|
||||||
|
|
||||||
|
.PHONY: all clean tests
|
||||||
|
|
||||||
|
all: $(RT_TARGET) $(LIBSDIR)/basrt.dep $(APP_TARGET) install-samples
|
||||||
|
|
||||||
|
tests: $(TEST_COMPILER) $(TEST_VM) $(TEST_LEX) $(TEST_QUICK)
|
||||||
|
|
||||||
|
$(TEST_COMPILER): $(TEST_COMPILER_SRCS) | $(BINDIR)
|
||||||
|
$(HOSTCC) $(HOSTCFLAGS) -o $@ $(TEST_COMPILER_SRCS) -lm
|
||||||
|
|
||||||
|
$(TEST_VM): $(TEST_VM_SRCS) | $(BINDIR)
|
||||||
|
$(HOSTCC) $(HOSTCFLAGS) -o $@ $(TEST_VM_SRCS) -lm
|
||||||
|
|
||||||
|
$(TEST_LEX): $(TEST_LEX_SRCS) | $(BINDIR)
|
||||||
|
$(HOSTCC) $(HOSTCFLAGS) -w -o $@ $(TEST_LEX_SRCS) -lm
|
||||||
|
|
||||||
|
$(TEST_QUICK): $(TEST_QUICK_SRCS) | $(BINDIR)
|
||||||
|
$(HOSTCC) $(HOSTCFLAGS) -o $@ $(TEST_QUICK_SRCS) -lm
|
||||||
|
|
||||||
|
# Runtime library DXE (exports symbols via dlregsym constructor)
|
||||||
|
$(RT_TARGET): $(RT_OBJS) | $(LIBSDIR)
|
||||||
|
$(DXE3GEN) -o $(LIBSDIR)/basrt.dxe \
|
||||||
|
-E _bas -E _BAS \
|
||||||
|
-U $(RT_OBJS)
|
||||||
|
mv $(LIBSDIR)/basrt.dxe $@
|
||||||
|
|
||||||
|
$(LIBSDIR)/basrt.dep: ../config/basrt.dep | $(LIBSDIR)
|
||||||
|
sed 's/$$/\r/' $< > $@
|
||||||
|
|
||||||
|
# IDE app DXE (compiler linked in, runtime from basrt.lib)
|
||||||
|
$(APP_TARGET): $(COMP_OBJS) $(APP_OBJS) dvxbasic.res | $(APPDIR)
|
||||||
|
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $(COMP_OBJS) $(APP_OBJS)
|
||||||
|
$(DVXRES) build $@ dvxbasic.res
|
||||||
|
|
||||||
|
install-samples: $(SAMPLES) | $(APPDIR)
|
||||||
|
cp $(SAMPLES) $(APPDIR)/
|
||||||
|
|
||||||
|
# Object files
|
||||||
|
$(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 $@ $<
|
||||||
|
|
||||||
|
$(OBJDIR)/ideMain.o: ide/ideMain.c compiler/parser.h runtime/vm.h | $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
$(OBJDIR)/lexer.o: compiler/lexer.c compiler/lexer.h | $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
$(OBJDIR)/parser.o: compiler/parser.c compiler/parser.h compiler/lexer.h compiler/codegen.h compiler/symtab.h compiler/opcodes.h | $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
$(OBJDIR)/symtab.o: compiler/symtab.c compiler/symtab.h compiler/opcodes.h | $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
$(OBJDIR)/values.o: runtime/values.c runtime/values.h compiler/opcodes.h | $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
$(OBJDIR)/vm.o: runtime/vm.c runtime/vm.h runtime/values.h compiler/opcodes.h | $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
# Directories
|
||||||
|
$(OBJDIR):
|
||||||
|
mkdir -p $(OBJDIR)
|
||||||
|
|
||||||
|
$(LIBSDIR):
|
||||||
|
mkdir -p $(LIBSDIR)
|
||||||
|
|
||||||
|
$(APPDIR):
|
||||||
|
mkdir -p $(APPDIR)
|
||||||
|
|
||||||
|
$(BINDIR):
|
||||||
|
mkdir -p $(BINDIR)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(RT_OBJS) $(COMP_OBJS) $(FORMRT_OBJS) $(APP_OBJS) $(RT_TARGET) $(APP_TARGET) $(LIBSDIR)/basrt.dep $(OBJDIR)/basrt_init.o
|
||||||
|
rm -f $(TEST_COMPILER) $(TEST_VM) $(TEST_LEX) $(TEST_QUICK)
|
||||||
320
dvxbasic/compiler/codegen.c
Normal file
320
dvxbasic/compiler/codegen.c
Normal file
|
|
@ -0,0 +1,320 @@
|
||||||
|
// codegen.c -- DVX BASIC p-code emitter implementation
|
||||||
|
|
||||||
|
#include "codegen.h"
|
||||||
|
#include "symtab.h"
|
||||||
|
#include "opcodes.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basAddData
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool basAddData(BasCodeGenT *cg, BasValueT val) {
|
||||||
|
if (cg->dataCount >= BAS_MAX_CONSTANTS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cg->dataPool[cg->dataCount++] = basValCopy(val);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basAddConstant
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len) {
|
||||||
|
// Check if this string is already in the pool
|
||||||
|
for (int32_t i = 0; i < cg->constCount; i++) {
|
||||||
|
if (cg->constants[i]->len == len && memcmp(cg->constants[i]->data, text, len) == 0) {
|
||||||
|
return (uint16_t)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cg->constCount >= BAS_MAX_CONSTANTS) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t idx = (uint16_t)cg->constCount;
|
||||||
|
cg->constants[cg->constCount++] = basStringNew(text, len);
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basCodeGenBuildModule
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg) {
|
||||||
|
BasModuleT *mod = (BasModuleT *)calloc(1, sizeof(BasModuleT));
|
||||||
|
|
||||||
|
if (!mod) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy code
|
||||||
|
mod->code = (uint8_t *)malloc(cg->codeLen);
|
||||||
|
|
||||||
|
if (!mod->code) {
|
||||||
|
free(mod);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(mod->code, cg->code, cg->codeLen);
|
||||||
|
mod->codeLen = cg->codeLen;
|
||||||
|
|
||||||
|
// Copy constant pool (share string refs)
|
||||||
|
if (cg->constCount > 0) {
|
||||||
|
mod->constants = (BasStringT **)malloc(cg->constCount * sizeof(BasStringT *));
|
||||||
|
|
||||||
|
if (!mod->constants) {
|
||||||
|
free(mod->code);
|
||||||
|
free(mod);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < cg->constCount; i++) {
|
||||||
|
mod->constants[i] = basStringRef(cg->constants[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod->constCount = cg->constCount;
|
||||||
|
mod->globalCount = cg->globalCount;
|
||||||
|
mod->entryPoint = 0;
|
||||||
|
|
||||||
|
// Copy data pool
|
||||||
|
if (cg->dataCount > 0) {
|
||||||
|
mod->dataPool = (BasValueT *)malloc(cg->dataCount * sizeof(BasValueT));
|
||||||
|
|
||||||
|
if (!mod->dataPool) {
|
||||||
|
free(mod->constants);
|
||||||
|
free(mod->code);
|
||||||
|
free(mod);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < cg->dataCount; i++) {
|
||||||
|
mod->dataPool[i] = basValCopy(cg->dataPool[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod->dataCount = cg->dataCount;
|
||||||
|
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 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
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basCodeGenFree(BasCodeGenT *cg) {
|
||||||
|
for (int32_t i = 0; i < cg->constCount; i++) {
|
||||||
|
basStringUnref(cg->constants[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < cg->dataCount; i++) {
|
||||||
|
basValRelease(&cg->dataPool[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
cg->constCount = 0;
|
||||||
|
cg->dataCount = 0;
|
||||||
|
cg->codeLen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basCodeGenInit
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basCodeGenInit(BasCodeGenT *cg) {
|
||||||
|
memset(cg, 0, sizeof(*cg));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basCodePos
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
int32_t basCodePos(const BasCodeGenT *cg) {
|
||||||
|
return cg->codeLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basEmit8
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basEmit8(BasCodeGenT *cg, uint8_t b) {
|
||||||
|
if (cg->codeLen < BAS_MAX_CODE) {
|
||||||
|
cg->code[cg->codeLen++] = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basEmit16
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basEmit16(BasCodeGenT *cg, int16_t v) {
|
||||||
|
if (cg->codeLen + 2 <= BAS_MAX_CODE) {
|
||||||
|
memcpy(&cg->code[cg->codeLen], &v, 2);
|
||||||
|
cg->codeLen += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basEmitDouble
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basEmitDouble(BasCodeGenT *cg, double v) {
|
||||||
|
if (cg->codeLen + (int32_t)sizeof(double) <= BAS_MAX_CODE) {
|
||||||
|
memcpy(&cg->code[cg->codeLen], &v, sizeof(double));
|
||||||
|
cg->codeLen += (int32_t)sizeof(double);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basEmitFloat
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basEmitFloat(BasCodeGenT *cg, float v) {
|
||||||
|
if (cg->codeLen + (int32_t)sizeof(float) <= BAS_MAX_CODE) {
|
||||||
|
memcpy(&cg->code[cg->codeLen], &v, sizeof(float));
|
||||||
|
cg->codeLen += (int32_t)sizeof(float);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basEmitU16
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basEmitU16(BasCodeGenT *cg, uint16_t v) {
|
||||||
|
if (cg->codeLen + 2 <= BAS_MAX_CODE) {
|
||||||
|
memcpy(&cg->code[cg->codeLen], &v, 2);
|
||||||
|
cg->codeLen += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 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
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basModuleFree(BasModuleT *mod) {
|
||||||
|
if (!mod) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(mod->code);
|
||||||
|
|
||||||
|
if (mod->constants) {
|
||||||
|
for (int32_t i = 0; i < mod->constCount; i++) {
|
||||||
|
basStringUnref(mod->constants[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(mod->constants);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mod->dataPool) {
|
||||||
|
for (int32_t i = 0; i < mod->dataCount; i++) {
|
||||||
|
basValRelease(&mod->dataPool[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(mod->dataPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(mod->procs);
|
||||||
|
free(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basPatch16
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basPatch16(BasCodeGenT *cg, int32_t pos, int16_t val) {
|
||||||
|
if (pos >= 0 && pos + 2 <= cg->codeLen) {
|
||||||
|
memcpy(&cg->code[pos], &val, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
84
dvxbasic/compiler/codegen.h
Normal file
84
dvxbasic/compiler/codegen.h
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
// codegen.h -- DVX BASIC p-code emitter
|
||||||
|
//
|
||||||
|
// Builds a p-code byte stream and string constant pool from
|
||||||
|
// calls made by the parser. Provides helpers for backpatching
|
||||||
|
// forward jumps.
|
||||||
|
//
|
||||||
|
// Embeddable: no DVX dependencies, pure C.
|
||||||
|
|
||||||
|
#ifndef DVXBASIC_CODEGEN_H
|
||||||
|
#define DVXBASIC_CODEGEN_H
|
||||||
|
|
||||||
|
#include "../runtime/vm.h"
|
||||||
|
#include "../runtime/values.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Code generator state
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define BAS_MAX_CODE 65536
|
||||||
|
#define BAS_MAX_CONSTANTS 1024
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t code[BAS_MAX_CODE];
|
||||||
|
int32_t codeLen;
|
||||||
|
BasStringT *constants[BAS_MAX_CONSTANTS];
|
||||||
|
int32_t constCount;
|
||||||
|
int32_t globalCount;
|
||||||
|
BasValueT dataPool[BAS_MAX_CONSTANTS];
|
||||||
|
int32_t dataCount;
|
||||||
|
} BasCodeGenT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// API
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basCodeGenInit(BasCodeGenT *cg);
|
||||||
|
void basCodeGenFree(BasCodeGenT *cg);
|
||||||
|
|
||||||
|
// Emit single byte
|
||||||
|
void basEmit8(BasCodeGenT *cg, uint8_t b);
|
||||||
|
|
||||||
|
// Emit 16-bit signed value
|
||||||
|
void basEmit16(BasCodeGenT *cg, int16_t v);
|
||||||
|
|
||||||
|
// Emit 16-bit unsigned value
|
||||||
|
void basEmitU16(BasCodeGenT *cg, uint16_t v);
|
||||||
|
|
||||||
|
// Emit 32-bit float
|
||||||
|
void basEmitFloat(BasCodeGenT *cg, float v);
|
||||||
|
|
||||||
|
// Emit 64-bit double
|
||||||
|
void basEmitDouble(BasCodeGenT *cg, double v);
|
||||||
|
|
||||||
|
// Get current code position (for jump targets)
|
||||||
|
int32_t basCodePos(const BasCodeGenT *cg);
|
||||||
|
|
||||||
|
// Patch a 16-bit value at a previous position (for backpatching jumps)
|
||||||
|
void basPatch16(BasCodeGenT *cg, int32_t pos, int16_t val);
|
||||||
|
|
||||||
|
// Add a string to the constant pool. Returns the pool index.
|
||||||
|
uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len);
|
||||||
|
|
||||||
|
// Add a value to the data pool (for DATA statements). Returns true on success.
|
||||||
|
bool basAddData(BasCodeGenT *cg, BasValueT val);
|
||||||
|
|
||||||
|
// Build a BasModuleT from the generated code. The caller takes
|
||||||
|
// ownership of the module and must free it with basModuleFree().
|
||||||
|
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.
|
||||||
|
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
|
||||||
820
dvxbasic/compiler/lexer.c
Normal file
820
dvxbasic/compiler/lexer.c
Normal file
|
|
@ -0,0 +1,820 @@
|
||||||
|
// lexer.c -- DVX BASIC lexer implementation
|
||||||
|
//
|
||||||
|
// Single-pass tokenizer. Keywords are case-insensitive. Identifiers
|
||||||
|
// preserve their original case for display but comparisons are
|
||||||
|
// case-insensitive. Line continuations (underscore at end of line)
|
||||||
|
// are handled transparently.
|
||||||
|
|
||||||
|
#include "lexer.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Keyword table
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *text;
|
||||||
|
BasTokenTypeE type;
|
||||||
|
} KeywordEntryT;
|
||||||
|
|
||||||
|
static const KeywordEntryT sKeywords[] = {
|
||||||
|
{ "AND", TOK_AND },
|
||||||
|
{ "APPEND", TOK_APPEND },
|
||||||
|
{ "AS", TOK_AS },
|
||||||
|
{ "BASE", TOK_BASE },
|
||||||
|
{ "BINARY", TOK_BINARY },
|
||||||
|
{ "BOOLEAN", TOK_BOOLEAN },
|
||||||
|
{ "BYVAL", TOK_BYVAL },
|
||||||
|
{ "CALL", TOK_CALL },
|
||||||
|
{ "CASE", TOK_CASE },
|
||||||
|
{ "CLOSE", TOK_CLOSE },
|
||||||
|
{ "CONST", TOK_CONST },
|
||||||
|
{ "DATA", TOK_DATA },
|
||||||
|
{ "DECLARE", TOK_DECLARE },
|
||||||
|
{ "DEF", TOK_DEF },
|
||||||
|
{ "DEFDBL", TOK_DEFDBL },
|
||||||
|
{ "DEFINT", TOK_DEFINT },
|
||||||
|
{ "DEFLNG", TOK_DEFLNG },
|
||||||
|
{ "DEFSNG", TOK_DEFSNG },
|
||||||
|
{ "DEFSTR", TOK_DEFSTR },
|
||||||
|
{ "DIM", TOK_DIM },
|
||||||
|
{ "DO", TOK_DO },
|
||||||
|
{ "DOEVENTS", TOK_DOEVENTS },
|
||||||
|
{ "DOUBLE", TOK_DOUBLE },
|
||||||
|
{ "ELSE", TOK_ELSE },
|
||||||
|
{ "ELSEIF", TOK_ELSEIF },
|
||||||
|
{ "END", TOK_END },
|
||||||
|
{ "EOF", TOK_EOF_KW },
|
||||||
|
{ "EQV", TOK_EQV },
|
||||||
|
{ "ERASE", TOK_ERASE },
|
||||||
|
{ "ERR", TOK_ERR },
|
||||||
|
{ "ERROR", TOK_ERROR_KW },
|
||||||
|
{ "EXPLICIT", TOK_EXPLICIT },
|
||||||
|
{ "EXIT", TOK_EXIT },
|
||||||
|
{ "FALSE", TOK_FALSE_KW },
|
||||||
|
{ "FOR", TOK_FOR },
|
||||||
|
{ "FUNCTION", TOK_FUNCTION },
|
||||||
|
{ "GET", TOK_GET },
|
||||||
|
{ "GOSUB", TOK_GOSUB },
|
||||||
|
{ "GOTO", TOK_GOTO },
|
||||||
|
{ "HIDE", TOK_HIDE },
|
||||||
|
{ "IF", TOK_IF },
|
||||||
|
{ "IMP", TOK_IMP },
|
||||||
|
{ "INPUT", TOK_INPUT },
|
||||||
|
{ "INTEGER", TOK_INTEGER },
|
||||||
|
{ "IS", TOK_IS },
|
||||||
|
{ "LBOUND", TOK_LBOUND },
|
||||||
|
{ "LET", TOK_LET },
|
||||||
|
{ "LINE", TOK_LINE },
|
||||||
|
{ "LOAD", TOK_LOAD },
|
||||||
|
{ "LONG", TOK_LONG },
|
||||||
|
{ "LOOP", TOK_LOOP },
|
||||||
|
{ "ME", TOK_ME },
|
||||||
|
{ "MOD", TOK_MOD },
|
||||||
|
{ "MSGBOX", TOK_MSGBOX },
|
||||||
|
{ "NEXT", TOK_NEXT },
|
||||||
|
{ "NOT", TOK_NOT },
|
||||||
|
{ "ON", TOK_ON },
|
||||||
|
{ "OPEN", TOK_OPEN },
|
||||||
|
{ "OPTION", TOK_OPTION },
|
||||||
|
{ "OR", TOK_OR },
|
||||||
|
{ "OUTPUT", TOK_OUTPUT },
|
||||||
|
{ "PRESERVE", TOK_PRESERVE },
|
||||||
|
{ "PRINT", TOK_PRINT },
|
||||||
|
{ "PUT", TOK_PUT },
|
||||||
|
{ "RANDOM", TOK_RANDOM },
|
||||||
|
{ "RANDOMIZE", TOK_RANDOMIZE },
|
||||||
|
{ "READ", TOK_READ },
|
||||||
|
{ "REDIM", TOK_REDIM },
|
||||||
|
{ "REM", TOK_REM },
|
||||||
|
{ "RESTORE", TOK_RESTORE },
|
||||||
|
{ "RESUME", TOK_RESUME },
|
||||||
|
{ "RETURN", TOK_RETURN },
|
||||||
|
{ "SEEK", TOK_SEEK },
|
||||||
|
{ "SELECT", TOK_SELECT },
|
||||||
|
{ "SET", TOK_SET },
|
||||||
|
{ "SHARED", TOK_SHARED },
|
||||||
|
{ "SHELL", TOK_SHELL },
|
||||||
|
{ "SHOW", TOK_SHOW },
|
||||||
|
{ "SINGLE", TOK_SINGLE },
|
||||||
|
{ "SLEEP", TOK_SLEEP },
|
||||||
|
{ "STATIC", TOK_STATIC },
|
||||||
|
{ "STEP", TOK_STEP },
|
||||||
|
{ "STRING", TOK_STRING_KW },
|
||||||
|
{ "SUB", TOK_SUB },
|
||||||
|
{ "SWAP", TOK_SWAP },
|
||||||
|
{ "THEN", TOK_THEN },
|
||||||
|
{ "TIMER", TOK_TIMER },
|
||||||
|
{ "TO", TOK_TO },
|
||||||
|
{ "TRUE", TOK_TRUE_KW },
|
||||||
|
{ "TYPE", TOK_TYPE },
|
||||||
|
{ "UBOUND", TOK_UBOUND },
|
||||||
|
{ "UNLOAD", TOK_UNLOAD },
|
||||||
|
{ "UNTIL", TOK_UNTIL },
|
||||||
|
{ "WEND", TOK_WEND },
|
||||||
|
{ "WHILE", TOK_WHILE },
|
||||||
|
{ "WITH", TOK_WITH },
|
||||||
|
{ "WRITE", TOK_WRITE },
|
||||||
|
{ "XOR", TOK_XOR },
|
||||||
|
{ NULL, TOK_ERROR }
|
||||||
|
};
|
||||||
|
|
||||||
|
#define KEYWORD_COUNT (sizeof(sKeywords) / sizeof(sKeywords[0]) - 1)
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Prototypes
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static char advance(BasLexerT *lex);
|
||||||
|
static bool atEnd(const BasLexerT *lex);
|
||||||
|
static BasTokenTypeE lookupKeyword(const char *text, int32_t len);
|
||||||
|
static char peek(const BasLexerT *lex);
|
||||||
|
static char peekNext(const BasLexerT *lex);
|
||||||
|
static void setError(BasLexerT *lex, const char *msg);
|
||||||
|
static void skipLineComment(BasLexerT *lex);
|
||||||
|
static void skipWhitespace(BasLexerT *lex);
|
||||||
|
static BasTokenTypeE tokenizeHexLiteral(BasLexerT *lex);
|
||||||
|
static BasTokenTypeE tokenizeIdentOrKeyword(BasLexerT *lex);
|
||||||
|
static BasTokenTypeE tokenizeNumber(BasLexerT *lex);
|
||||||
|
static BasTokenTypeE tokenizeString(BasLexerT *lex);
|
||||||
|
static char upperChar(char c);
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// advance
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static char advance(BasLexerT *lex) {
|
||||||
|
if (atEnd(lex)) {
|
||||||
|
return '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char c = lex->source[lex->pos++];
|
||||||
|
|
||||||
|
if (c == '\n') {
|
||||||
|
lex->line++;
|
||||||
|
lex->col = 1;
|
||||||
|
} else {
|
||||||
|
lex->col++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// atEnd
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static bool atEnd(const BasLexerT *lex) {
|
||||||
|
return lex->pos >= lex->sourceLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basLexerInit
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basLexerInit(BasLexerT *lex, const char *source, int32_t sourceLen) {
|
||||||
|
memset(lex, 0, sizeof(*lex));
|
||||||
|
lex->source = source;
|
||||||
|
lex->sourceLen = sourceLen;
|
||||||
|
lex->pos = 0;
|
||||||
|
lex->line = 1;
|
||||||
|
lex->col = 1;
|
||||||
|
|
||||||
|
// Prime the first token
|
||||||
|
basLexerNext(lex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basLexerNext
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
BasTokenTypeE basLexerNext(BasLexerT *lex) {
|
||||||
|
skipWhitespace(lex);
|
||||||
|
|
||||||
|
lex->token.line = lex->line;
|
||||||
|
lex->token.col = lex->col;
|
||||||
|
lex->token.textLen = 0;
|
||||||
|
lex->token.text[0] = '\0';
|
||||||
|
|
||||||
|
if (atEnd(lex)) {
|
||||||
|
lex->token.type = TOK_EOF;
|
||||||
|
return TOK_EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
char c = peek(lex);
|
||||||
|
|
||||||
|
// Newline
|
||||||
|
if (c == '\n') {
|
||||||
|
advance(lex);
|
||||||
|
lex->token.type = TOK_NEWLINE;
|
||||||
|
lex->token.text[0] = '\n';
|
||||||
|
lex->token.text[1] = '\0';
|
||||||
|
lex->token.textLen = 1;
|
||||||
|
return TOK_NEWLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carriage return (handle CR, CRLF)
|
||||||
|
if (c == '\r') {
|
||||||
|
advance(lex);
|
||||||
|
|
||||||
|
if (!atEnd(lex) && peek(lex) == '\n') {
|
||||||
|
advance(lex);
|
||||||
|
}
|
||||||
|
|
||||||
|
lex->token.type = TOK_NEWLINE;
|
||||||
|
lex->token.text[0] = '\n';
|
||||||
|
lex->token.text[1] = '\0';
|
||||||
|
lex->token.textLen = 1;
|
||||||
|
return TOK_NEWLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comment (apostrophe)
|
||||||
|
if (c == '\'') {
|
||||||
|
skipLineComment(lex);
|
||||||
|
lex->token.type = TOK_NEWLINE;
|
||||||
|
lex->token.text[0] = '\n';
|
||||||
|
lex->token.text[1] = '\0';
|
||||||
|
lex->token.textLen = 1;
|
||||||
|
return TOK_NEWLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// String literal
|
||||||
|
if (c == '"') {
|
||||||
|
lex->token.type = tokenizeString(lex);
|
||||||
|
return lex->token.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number
|
||||||
|
if (isdigit((unsigned char)c) || (c == '.' && isdigit((unsigned char)peekNext(lex)))) {
|
||||||
|
lex->token.type = tokenizeNumber(lex);
|
||||||
|
return lex->token.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hex literal (&H...)
|
||||||
|
if (c == '&' && upperChar(peekNext(lex)) == 'H') {
|
||||||
|
lex->token.type = tokenizeHexLiteral(lex);
|
||||||
|
return lex->token.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identifier or keyword
|
||||||
|
if (isalpha((unsigned char)c) || c == '_') {
|
||||||
|
lex->token.type = tokenizeIdentOrKeyword(lex);
|
||||||
|
return lex->token.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single and multi-character operators/punctuation
|
||||||
|
advance(lex);
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case '+':
|
||||||
|
lex->token.type = TOK_PLUS;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
lex->token.type = TOK_MINUS;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '*':
|
||||||
|
lex->token.type = TOK_STAR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '/':
|
||||||
|
lex->token.type = TOK_SLASH;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\\':
|
||||||
|
lex->token.type = TOK_BACKSLASH;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '^':
|
||||||
|
lex->token.type = TOK_CARET;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '&':
|
||||||
|
lex->token.type = TOK_AMPERSAND;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '(':
|
||||||
|
lex->token.type = TOK_LPAREN;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ')':
|
||||||
|
lex->token.type = TOK_RPAREN;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
lex->token.type = TOK_COMMA;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ';':
|
||||||
|
lex->token.type = TOK_SEMICOLON;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
lex->token.type = TOK_COLON;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
lex->token.type = TOK_DOT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '#':
|
||||||
|
lex->token.type = TOK_HASH;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '=':
|
||||||
|
lex->token.type = TOK_EQ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '<':
|
||||||
|
if (!atEnd(lex) && peek(lex) == '>') {
|
||||||
|
advance(lex);
|
||||||
|
lex->token.type = TOK_NE;
|
||||||
|
} else if (!atEnd(lex) && peek(lex) == '=') {
|
||||||
|
advance(lex);
|
||||||
|
lex->token.type = TOK_LE;
|
||||||
|
} else {
|
||||||
|
lex->token.type = TOK_LT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '>':
|
||||||
|
if (!atEnd(lex) && peek(lex) == '=') {
|
||||||
|
advance(lex);
|
||||||
|
lex->token.type = TOK_GE;
|
||||||
|
} else {
|
||||||
|
lex->token.type = TOK_GT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
setError(lex, "Unexpected character");
|
||||||
|
lex->token.type = TOK_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the operator text
|
||||||
|
if (lex->token.type != TOK_ERROR) {
|
||||||
|
lex->token.text[0] = c;
|
||||||
|
lex->token.textLen = 1;
|
||||||
|
|
||||||
|
if (lex->token.type == TOK_NE || lex->token.type == TOK_LE || lex->token.type == TOK_GE) {
|
||||||
|
lex->token.text[1] = lex->source[lex->pos - 1];
|
||||||
|
lex->token.textLen = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
lex->token.text[lex->token.textLen] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return lex->token.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basLexerPeek
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
BasTokenTypeE basLexerPeek(const BasLexerT *lex) {
|
||||||
|
return lex->token.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basTokenName
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
const char *basTokenName(BasTokenTypeE type) {
|
||||||
|
switch (type) {
|
||||||
|
case TOK_INT_LIT: return "integer";
|
||||||
|
case TOK_LONG_LIT: return "long";
|
||||||
|
case TOK_FLOAT_LIT: return "float";
|
||||||
|
case TOK_STRING_LIT: return "string";
|
||||||
|
case TOK_IDENT: return "identifier";
|
||||||
|
case TOK_DOT: return "'.'";
|
||||||
|
case TOK_COMMA: return "','";
|
||||||
|
case TOK_SEMICOLON: return "';'";
|
||||||
|
case TOK_COLON: return "':'";
|
||||||
|
case TOK_LPAREN: return "'('";
|
||||||
|
case TOK_RPAREN: return "')'";
|
||||||
|
case TOK_HASH: return "'#'";
|
||||||
|
case TOK_PLUS: return "'+'";
|
||||||
|
case TOK_MINUS: return "'-'";
|
||||||
|
case TOK_STAR: return "'*'";
|
||||||
|
case TOK_SLASH: return "'/'";
|
||||||
|
case TOK_BACKSLASH: return "'\\'";
|
||||||
|
case TOK_CARET: return "'^'";
|
||||||
|
case TOK_AMPERSAND: return "'&'";
|
||||||
|
case TOK_EQ: return "'='";
|
||||||
|
case TOK_NE: return "'<>'";
|
||||||
|
case TOK_LT: return "'<'";
|
||||||
|
case TOK_GT: return "'>'";
|
||||||
|
case TOK_LE: return "'<='";
|
||||||
|
case TOK_GE: return "'>='";
|
||||||
|
case TOK_NEWLINE: return "newline";
|
||||||
|
case TOK_EOF: return "end of file";
|
||||||
|
case TOK_ERROR: return "error";
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keywords
|
||||||
|
for (int32_t i = 0; i < (int32_t)KEYWORD_COUNT; i++) {
|
||||||
|
if (sKeywords[i].type == type) {
|
||||||
|
return sKeywords[i].text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// lookupKeyword
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static BasTokenTypeE lookupKeyword(const char *text, int32_t len) {
|
||||||
|
// Case-insensitive keyword lookup
|
||||||
|
for (int32_t i = 0; i < (int32_t)KEYWORD_COUNT; i++) {
|
||||||
|
const char *kw = sKeywords[i].text;
|
||||||
|
int32_t kwLen = (int32_t)strlen(kw);
|
||||||
|
|
||||||
|
if (kwLen != len) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool match = true;
|
||||||
|
|
||||||
|
for (int32_t j = 0; j < len; j++) {
|
||||||
|
if (upperChar(text[j]) != kw[j]) {
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
return sKeywords[i].type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TOK_IDENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// peek
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static char peek(const BasLexerT *lex) {
|
||||||
|
if (atEnd(lex)) {
|
||||||
|
return '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return lex->source[lex->pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// peekNext
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static char peekNext(const BasLexerT *lex) {
|
||||||
|
if (lex->pos + 1 >= lex->sourceLen) {
|
||||||
|
return '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return lex->source[lex->pos + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// setError
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void setError(BasLexerT *lex, const char *msg) {
|
||||||
|
snprintf(lex->error, sizeof(lex->error), "Line %d, Col %d: %s", (int)lex->line, (int)lex->col, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// skipLineComment
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void skipLineComment(BasLexerT *lex) {
|
||||||
|
while (!atEnd(lex) && peek(lex) != '\n' && peek(lex) != '\r') {
|
||||||
|
advance(lex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// skipWhitespace
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Skips spaces and tabs. Does NOT skip newlines (they are tokens).
|
||||||
|
// Handles line continuation: underscore followed by newline joins
|
||||||
|
// the next line to the current logical line.
|
||||||
|
|
||||||
|
static void skipWhitespace(BasLexerT *lex) {
|
||||||
|
while (!atEnd(lex)) {
|
||||||
|
char c = peek(lex);
|
||||||
|
|
||||||
|
if (c == ' ' || c == '\t') {
|
||||||
|
advance(lex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line continuation: _ at end of line
|
||||||
|
if (c == '_') {
|
||||||
|
int32_t savedPos = lex->pos;
|
||||||
|
int32_t savedLine = lex->line;
|
||||||
|
int32_t savedCol = lex->col;
|
||||||
|
advance(lex);
|
||||||
|
|
||||||
|
// Skip spaces/tabs after underscore
|
||||||
|
while (!atEnd(lex) && (peek(lex) == ' ' || peek(lex) == '\t')) {
|
||||||
|
advance(lex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be followed by newline
|
||||||
|
if (!atEnd(lex) && (peek(lex) == '\n' || peek(lex) == '\r')) {
|
||||||
|
advance(lex);
|
||||||
|
|
||||||
|
if (!atEnd(lex) && peek(lex) == '\n' && lex->source[lex->pos - 1] == '\r') {
|
||||||
|
advance(lex);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue; // Continue skipping whitespace on next line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not a continuation -- put back
|
||||||
|
lex->pos = savedPos;
|
||||||
|
lex->line = savedLine;
|
||||||
|
lex->col = savedCol;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// tokenizeHexLiteral
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static BasTokenTypeE tokenizeHexLiteral(BasLexerT *lex) {
|
||||||
|
advance(lex); // skip &
|
||||||
|
advance(lex); // skip H
|
||||||
|
|
||||||
|
int32_t idx = 0;
|
||||||
|
int32_t value = 0;
|
||||||
|
|
||||||
|
while (!atEnd(lex) && isxdigit((unsigned char)peek(lex))) {
|
||||||
|
char c = advance(lex);
|
||||||
|
|
||||||
|
if (idx < BAS_MAX_TOKEN_LEN - 1) {
|
||||||
|
lex->token.text[idx++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t digit;
|
||||||
|
|
||||||
|
if (c >= '0' && c <= '9') {
|
||||||
|
digit = c - '0';
|
||||||
|
} else if (c >= 'A' && c <= 'F') {
|
||||||
|
digit = c - 'A' + 10;
|
||||||
|
} else {
|
||||||
|
digit = c - 'a' + 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = (value << 4) | digit;
|
||||||
|
}
|
||||||
|
|
||||||
|
lex->token.text[idx] = '\0';
|
||||||
|
lex->token.textLen = idx;
|
||||||
|
|
||||||
|
// Check for trailing & (long suffix)
|
||||||
|
if (!atEnd(lex) && peek(lex) == '&') {
|
||||||
|
advance(lex);
|
||||||
|
lex->token.longVal = (int64_t)value;
|
||||||
|
return TOK_LONG_LIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
lex->token.intVal = value;
|
||||||
|
return TOK_INT_LIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// tokenizeIdentOrKeyword
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static BasTokenTypeE tokenizeIdentOrKeyword(BasLexerT *lex) {
|
||||||
|
int32_t idx = 0;
|
||||||
|
|
||||||
|
while (!atEnd(lex) && (isalnum((unsigned char)peek(lex)) || peek(lex) == '_')) {
|
||||||
|
char c = advance(lex);
|
||||||
|
|
||||||
|
if (idx < BAS_MAX_TOKEN_LEN - 1) {
|
||||||
|
lex->token.text[idx++] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lex->token.text[idx] = '\0';
|
||||||
|
lex->token.textLen = idx;
|
||||||
|
|
||||||
|
// Check for type suffix
|
||||||
|
if (!atEnd(lex)) {
|
||||||
|
char c = peek(lex);
|
||||||
|
|
||||||
|
if (c == '%' || c == '&' || c == '!' || c == '#' || c == '$') {
|
||||||
|
advance(lex);
|
||||||
|
lex->token.text[idx++] = c;
|
||||||
|
lex->token.text[idx] = '\0';
|
||||||
|
lex->token.textLen = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is a keyword
|
||||||
|
// For suffix-bearing identifiers, only check the base (without suffix)
|
||||||
|
int32_t baseLen = idx;
|
||||||
|
|
||||||
|
if (baseLen > 0) {
|
||||||
|
char last = lex->token.text[baseLen - 1];
|
||||||
|
|
||||||
|
if (last == '%' || last == '&' || last == '!' || last == '#' || last == '$') {
|
||||||
|
baseLen--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasTokenTypeE kwType = lookupKeyword(lex->token.text, baseLen);
|
||||||
|
|
||||||
|
// REM is a comment -- skip to end of line
|
||||||
|
if (kwType == TOK_REM) {
|
||||||
|
skipLineComment(lex);
|
||||||
|
lex->token.type = TOK_NEWLINE;
|
||||||
|
lex->token.text[0] = '\n';
|
||||||
|
lex->token.text[1] = '\0';
|
||||||
|
lex->token.textLen = 1;
|
||||||
|
return TOK_NEWLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a keyword and has no suffix, return the keyword token
|
||||||
|
if (kwType != TOK_IDENT && baseLen == idx) {
|
||||||
|
return kwType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TOK_IDENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// tokenizeNumber
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static BasTokenTypeE tokenizeNumber(BasLexerT *lex) {
|
||||||
|
int32_t idx = 0;
|
||||||
|
bool hasDecimal = false;
|
||||||
|
bool hasExp = false;
|
||||||
|
|
||||||
|
// Integer part
|
||||||
|
while (!atEnd(lex) && isdigit((unsigned char)peek(lex))) {
|
||||||
|
if (idx < BAS_MAX_TOKEN_LEN - 1) {
|
||||||
|
lex->token.text[idx++] = advance(lex);
|
||||||
|
} else {
|
||||||
|
advance(lex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decimal part
|
||||||
|
if (!atEnd(lex) && peek(lex) == '.' && isdigit((unsigned char)peekNext(lex))) {
|
||||||
|
hasDecimal = true;
|
||||||
|
lex->token.text[idx++] = advance(lex); // .
|
||||||
|
|
||||||
|
while (!atEnd(lex) && isdigit((unsigned char)peek(lex))) {
|
||||||
|
if (idx < BAS_MAX_TOKEN_LEN - 1) {
|
||||||
|
lex->token.text[idx++] = advance(lex);
|
||||||
|
} else {
|
||||||
|
advance(lex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exponent
|
||||||
|
if (!atEnd(lex) && (upperChar(peek(lex)) == 'E' || upperChar(peek(lex)) == 'D')) {
|
||||||
|
hasExp = true;
|
||||||
|
lex->token.text[idx++] = advance(lex);
|
||||||
|
|
||||||
|
if (!atEnd(lex) && (peek(lex) == '+' || peek(lex) == '-')) {
|
||||||
|
lex->token.text[idx++] = advance(lex);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!atEnd(lex) && isdigit((unsigned char)peek(lex))) {
|
||||||
|
if (idx < BAS_MAX_TOKEN_LEN - 1) {
|
||||||
|
lex->token.text[idx++] = advance(lex);
|
||||||
|
} else {
|
||||||
|
advance(lex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lex->token.text[idx] = '\0';
|
||||||
|
lex->token.textLen = idx;
|
||||||
|
|
||||||
|
// Check for type suffix
|
||||||
|
if (!atEnd(lex)) {
|
||||||
|
char c = peek(lex);
|
||||||
|
|
||||||
|
if (c == '%') {
|
||||||
|
advance(lex);
|
||||||
|
lex->token.intVal = (int32_t)atoi(lex->token.text);
|
||||||
|
return TOK_INT_LIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '&') {
|
||||||
|
advance(lex);
|
||||||
|
lex->token.longVal = (int64_t)atol(lex->token.text);
|
||||||
|
return TOK_LONG_LIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '!') {
|
||||||
|
advance(lex);
|
||||||
|
lex->token.dblVal = atof(lex->token.text);
|
||||||
|
return TOK_FLOAT_LIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '#') {
|
||||||
|
advance(lex);
|
||||||
|
lex->token.dblVal = atof(lex->token.text);
|
||||||
|
return TOK_FLOAT_LIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No suffix: determine type from content
|
||||||
|
if (hasDecimal || hasExp) {
|
||||||
|
lex->token.dblVal = atof(lex->token.text);
|
||||||
|
return TOK_FLOAT_LIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
long val = atol(lex->token.text);
|
||||||
|
|
||||||
|
if (val >= -32768 && val <= 32767) {
|
||||||
|
lex->token.intVal = (int32_t)val;
|
||||||
|
return TOK_INT_LIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
lex->token.longVal = (int64_t)val;
|
||||||
|
return TOK_LONG_LIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// tokenizeString
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static BasTokenTypeE tokenizeString(BasLexerT *lex) {
|
||||||
|
advance(lex); // skip opening quote
|
||||||
|
|
||||||
|
int32_t idx = 0;
|
||||||
|
|
||||||
|
while (!atEnd(lex) && peek(lex) != '"' && peek(lex) != '\n' && peek(lex) != '\r') {
|
||||||
|
if (idx < BAS_MAX_TOKEN_LEN - 1) {
|
||||||
|
lex->token.text[idx++] = advance(lex);
|
||||||
|
} else {
|
||||||
|
advance(lex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atEnd(lex) || peek(lex) != '"') {
|
||||||
|
setError(lex, "Unterminated string literal");
|
||||||
|
lex->token.text[idx] = '\0';
|
||||||
|
lex->token.textLen = idx;
|
||||||
|
return TOK_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance(lex); // skip closing quote
|
||||||
|
|
||||||
|
lex->token.text[idx] = '\0';
|
||||||
|
lex->token.textLen = idx;
|
||||||
|
|
||||||
|
return TOK_STRING_LIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// upperChar
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static char upperChar(char c) {
|
||||||
|
if (c >= 'a' && c <= 'z') {
|
||||||
|
return c - 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
221
dvxbasic/compiler/lexer.h
Normal file
221
dvxbasic/compiler/lexer.h
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
// lexer.h -- DVX BASIC lexer (tokenizer)
|
||||||
|
//
|
||||||
|
// Converts BASIC source text into a stream of tokens. Case-insensitive
|
||||||
|
// for keywords. Handles line continuations (_), comments (' and REM),
|
||||||
|
// type suffixes (%, &, !, #, $), and string literals.
|
||||||
|
//
|
||||||
|
// Embeddable: no DVX dependencies, pure C.
|
||||||
|
|
||||||
|
#ifndef DVXBASIC_LEXER_H
|
||||||
|
#define DVXBASIC_LEXER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Token types
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
// Literals
|
||||||
|
TOK_INT_LIT, // integer literal (123, &HFF)
|
||||||
|
TOK_LONG_LIT, // long literal (123&)
|
||||||
|
TOK_FLOAT_LIT, // float literal (3.14, 1.5E10)
|
||||||
|
TOK_STRING_LIT, // "string literal"
|
||||||
|
|
||||||
|
// Identifiers and symbols
|
||||||
|
TOK_IDENT, // variable/function name
|
||||||
|
TOK_DOT, // .
|
||||||
|
TOK_COMMA, // ,
|
||||||
|
TOK_SEMICOLON, // ;
|
||||||
|
TOK_COLON, // :
|
||||||
|
TOK_LPAREN, // (
|
||||||
|
TOK_RPAREN, // )
|
||||||
|
TOK_HASH, // # (file channel)
|
||||||
|
|
||||||
|
// Operators
|
||||||
|
TOK_PLUS, // +
|
||||||
|
TOK_MINUS, // -
|
||||||
|
TOK_STAR, // *
|
||||||
|
TOK_SLASH, // /
|
||||||
|
TOK_BACKSLASH, // \ (integer divide)
|
||||||
|
TOK_CARET, // ^
|
||||||
|
TOK_AMPERSAND, // & (string concat or hex prefix)
|
||||||
|
TOK_EQ, // =
|
||||||
|
TOK_NE, // <>
|
||||||
|
TOK_LT, // <
|
||||||
|
TOK_GT, // >
|
||||||
|
TOK_LE, // <=
|
||||||
|
TOK_GE, // >=
|
||||||
|
|
||||||
|
// Type suffixes (attached to identifier)
|
||||||
|
TOK_SUFFIX_INT, // %
|
||||||
|
TOK_SUFFIX_LONG, // &
|
||||||
|
TOK_SUFFIX_SINGLE, // !
|
||||||
|
TOK_SUFFIX_DOUBLE, // #
|
||||||
|
TOK_SUFFIX_STRING, // $
|
||||||
|
|
||||||
|
// Keywords
|
||||||
|
TOK_AND,
|
||||||
|
TOK_AS,
|
||||||
|
TOK_BASE,
|
||||||
|
TOK_BOOLEAN,
|
||||||
|
TOK_BYVAL,
|
||||||
|
TOK_CALL,
|
||||||
|
TOK_CASE,
|
||||||
|
TOK_CLOSE,
|
||||||
|
TOK_CONST,
|
||||||
|
TOK_DATA,
|
||||||
|
TOK_DECLARE,
|
||||||
|
TOK_DEF,
|
||||||
|
TOK_DEFDBL,
|
||||||
|
TOK_DEFINT,
|
||||||
|
TOK_DEFLNG,
|
||||||
|
TOK_DEFSNG,
|
||||||
|
TOK_DEFSTR,
|
||||||
|
TOK_DIM,
|
||||||
|
TOK_DO,
|
||||||
|
TOK_DOEVENTS,
|
||||||
|
TOK_DOUBLE,
|
||||||
|
TOK_ELSE,
|
||||||
|
TOK_ELSEIF,
|
||||||
|
TOK_END,
|
||||||
|
TOK_EOF_KW, // EOF (keyword, not end-of-file)
|
||||||
|
TOK_EQV,
|
||||||
|
TOK_ERASE,
|
||||||
|
TOK_ERR,
|
||||||
|
TOK_ERROR_KW,
|
||||||
|
TOK_EXPLICIT,
|
||||||
|
TOK_EXIT,
|
||||||
|
TOK_FALSE_KW,
|
||||||
|
TOK_FOR,
|
||||||
|
TOK_FUNCTION,
|
||||||
|
TOK_GET,
|
||||||
|
TOK_GOSUB,
|
||||||
|
TOK_GOTO,
|
||||||
|
TOK_HIDE,
|
||||||
|
TOK_IF,
|
||||||
|
TOK_IMP,
|
||||||
|
TOK_INPUT,
|
||||||
|
TOK_INTEGER,
|
||||||
|
TOK_IS,
|
||||||
|
TOK_LBOUND,
|
||||||
|
TOK_LET,
|
||||||
|
TOK_LINE,
|
||||||
|
TOK_LOAD,
|
||||||
|
TOK_LONG,
|
||||||
|
TOK_LOOP,
|
||||||
|
TOK_ME,
|
||||||
|
TOK_MOD,
|
||||||
|
TOK_MSGBOX,
|
||||||
|
TOK_NEXT,
|
||||||
|
TOK_NOT,
|
||||||
|
TOK_ON,
|
||||||
|
TOK_OPEN,
|
||||||
|
TOK_OPTION,
|
||||||
|
TOK_OR,
|
||||||
|
TOK_OUTPUT,
|
||||||
|
TOK_PRESERVE,
|
||||||
|
TOK_PRINT,
|
||||||
|
TOK_PUT,
|
||||||
|
TOK_RANDOMIZE,
|
||||||
|
TOK_READ,
|
||||||
|
TOK_REDIM,
|
||||||
|
TOK_REM,
|
||||||
|
TOK_RESTORE,
|
||||||
|
TOK_RESUME,
|
||||||
|
TOK_RETURN,
|
||||||
|
TOK_SEEK,
|
||||||
|
TOK_SELECT,
|
||||||
|
TOK_SET,
|
||||||
|
TOK_SHARED,
|
||||||
|
TOK_SHELL,
|
||||||
|
TOK_SHOW,
|
||||||
|
TOK_SINGLE,
|
||||||
|
TOK_SLEEP,
|
||||||
|
TOK_STATIC,
|
||||||
|
TOK_STEP,
|
||||||
|
TOK_STRING_KW,
|
||||||
|
TOK_SUB,
|
||||||
|
TOK_SWAP,
|
||||||
|
TOK_THEN,
|
||||||
|
TOK_TIMER,
|
||||||
|
TOK_TO,
|
||||||
|
TOK_TRUE_KW,
|
||||||
|
TOK_TYPE,
|
||||||
|
TOK_UBOUND,
|
||||||
|
TOK_UNLOAD,
|
||||||
|
TOK_UNTIL,
|
||||||
|
TOK_WEND,
|
||||||
|
TOK_WHILE,
|
||||||
|
TOK_WITH,
|
||||||
|
TOK_WRITE,
|
||||||
|
TOK_XOR,
|
||||||
|
|
||||||
|
// File modes
|
||||||
|
TOK_APPEND,
|
||||||
|
TOK_BINARY,
|
||||||
|
TOK_RANDOM,
|
||||||
|
|
||||||
|
// Special
|
||||||
|
TOK_NEWLINE, // end of logical line
|
||||||
|
TOK_EOF, // end of source
|
||||||
|
TOK_ERROR // lexer error
|
||||||
|
} BasTokenTypeE;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Token
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define BAS_MAX_TOKEN_LEN 256
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
BasTokenTypeE type;
|
||||||
|
int32_t line; // 1-based source line number
|
||||||
|
int32_t col; // 1-based column number
|
||||||
|
|
||||||
|
// Value (depends on type)
|
||||||
|
union {
|
||||||
|
int32_t intVal;
|
||||||
|
int64_t longVal;
|
||||||
|
float fltVal;
|
||||||
|
double dblVal;
|
||||||
|
};
|
||||||
|
|
||||||
|
char text[BAS_MAX_TOKEN_LEN]; // raw text of the token
|
||||||
|
int32_t textLen;
|
||||||
|
} BasTokenT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Lexer state
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *source; // source text (not owned)
|
||||||
|
int32_t sourceLen;
|
||||||
|
int32_t pos; // current position in source
|
||||||
|
int32_t line; // current line (1-based)
|
||||||
|
int32_t col; // current column (1-based)
|
||||||
|
BasTokenT token; // current token
|
||||||
|
char error[256];
|
||||||
|
} BasLexerT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// API
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Initialize lexer with source text. The source must remain valid
|
||||||
|
// for the lifetime of the lexer.
|
||||||
|
void basLexerInit(BasLexerT *lex, const char *source, int32_t sourceLen);
|
||||||
|
|
||||||
|
// Advance to the next token. Returns the token type.
|
||||||
|
// The token is available in lex->token.
|
||||||
|
BasTokenTypeE basLexerNext(BasLexerT *lex);
|
||||||
|
|
||||||
|
// Peek at the current token type without advancing.
|
||||||
|
BasTokenTypeE basLexerPeek(const BasLexerT *lex);
|
||||||
|
|
||||||
|
// Return human-readable name for a token type.
|
||||||
|
const char *basTokenName(BasTokenTypeE type);
|
||||||
|
|
||||||
|
#endif // DVXBASIC_LEXER_H
|
||||||
317
dvxbasic/compiler/opcodes.h
Normal file
317
dvxbasic/compiler/opcodes.h
Normal file
|
|
@ -0,0 +1,317 @@
|
||||||
|
// opcodes.h -- DVX BASIC bytecode instruction definitions
|
||||||
|
//
|
||||||
|
// Stack-based p-code for the DVX BASIC virtual machine.
|
||||||
|
// Embeddable: no DVX dependencies, pure C.
|
||||||
|
|
||||||
|
#ifndef DVXBASIC_OPCODES_H
|
||||||
|
#define DVXBASIC_OPCODES_H
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Data type tags (used in Value representation)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define BAS_TYPE_INTEGER 0 // 16-bit signed
|
||||||
|
#define BAS_TYPE_LONG 1 // 32-bit signed
|
||||||
|
#define BAS_TYPE_SINGLE 2 // 32-bit float
|
||||||
|
#define BAS_TYPE_DOUBLE 3 // 64-bit float
|
||||||
|
#define BAS_TYPE_STRING 4 // ref-counted dynamic string
|
||||||
|
#define BAS_TYPE_BOOLEAN 5 // True (-1) or False (0)
|
||||||
|
#define BAS_TYPE_ARRAY 6 // ref-counted array
|
||||||
|
#define BAS_TYPE_UDT 7 // ref-counted user-defined type
|
||||||
|
#define BAS_TYPE_OBJECT 8 // opaque host object (form, control, etc.)
|
||||||
|
#define BAS_TYPE_REF 9 // ByRef pointer to a BasValueT slot
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Stack operations
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_NOP 0x00
|
||||||
|
#define OP_PUSH_INT16 0x01 // [int16] push 16-bit integer
|
||||||
|
#define OP_PUSH_INT32 0x02 // [int32] push 32-bit integer
|
||||||
|
#define OP_PUSH_FLT32 0x03 // [float32] push 32-bit float
|
||||||
|
#define OP_PUSH_FLT64 0x04 // [float64] push 64-bit float
|
||||||
|
#define OP_PUSH_STR 0x05 // [uint16 idx] push string from constant pool
|
||||||
|
#define OP_PUSH_TRUE 0x06 // push boolean True (-1)
|
||||||
|
#define OP_PUSH_FALSE 0x07 // push boolean False (0)
|
||||||
|
#define OP_POP 0x08 // discard top of stack
|
||||||
|
#define OP_DUP 0x09 // duplicate top of stack
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Variable access
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_LOAD_LOCAL 0x10 // [uint16 idx] push local variable
|
||||||
|
#define OP_STORE_LOCAL 0x11 // [uint16 idx] pop to local variable
|
||||||
|
#define OP_LOAD_GLOBAL 0x12 // [uint16 idx] push global variable
|
||||||
|
#define OP_STORE_GLOBAL 0x13 // [uint16 idx] pop to global variable
|
||||||
|
#define OP_LOAD_REF 0x14 // dereference top of stack (ByRef)
|
||||||
|
#define OP_STORE_REF 0x15 // store through reference on stack
|
||||||
|
#define OP_LOAD_ARRAY 0x16 // [uint8 dims] indices on stack, array ref below
|
||||||
|
#define OP_STORE_ARRAY 0x17 // [uint8 dims] value, indices, array ref on stack
|
||||||
|
#define OP_LOAD_FIELD 0x18 // [uint16 fieldIdx] load UDT field
|
||||||
|
#define OP_STORE_FIELD 0x19 // [uint16 fieldIdx] store UDT field
|
||||||
|
#define OP_PUSH_LOCAL_ADDR 0x1A // [uint16 idx] push address of local (for ByRef)
|
||||||
|
#define OP_PUSH_GLOBAL_ADDR 0x1B // [uint16 idx] push address of global (for ByRef)
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Arithmetic (integer)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_ADD_INT 0x20
|
||||||
|
#define OP_SUB_INT 0x21
|
||||||
|
#define OP_MUL_INT 0x22
|
||||||
|
#define OP_IDIV_INT 0x23 // integer divide (\)
|
||||||
|
#define OP_MOD_INT 0x24
|
||||||
|
#define OP_NEG_INT 0x25
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Arithmetic (float)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_ADD_FLT 0x26
|
||||||
|
#define OP_SUB_FLT 0x27
|
||||||
|
#define OP_MUL_FLT 0x28
|
||||||
|
#define OP_DIV_FLT 0x29 // float divide (/)
|
||||||
|
#define OP_NEG_FLT 0x2A
|
||||||
|
#define OP_POW 0x2B // exponentiation (^)
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// String operations
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_STR_CONCAT 0x30
|
||||||
|
#define OP_STR_LEFT 0x31
|
||||||
|
#define OP_STR_RIGHT 0x32
|
||||||
|
#define OP_STR_MID 0x33 // 3 args: str, start, len
|
||||||
|
#define OP_STR_MID2 0x34 // 2 args: str, start (to end)
|
||||||
|
#define OP_STR_LEN 0x35
|
||||||
|
#define OP_STR_INSTR 0x36 // 2 args: str, find
|
||||||
|
#define OP_STR_INSTR3 0x37 // 3 args: start, str, find
|
||||||
|
#define OP_STR_UCASE 0x38
|
||||||
|
#define OP_STR_LCASE 0x39
|
||||||
|
#define OP_STR_TRIM 0x3A
|
||||||
|
#define OP_STR_LTRIM 0x3B
|
||||||
|
#define OP_STR_RTRIM 0x3C
|
||||||
|
#define OP_STR_CHR 0x3D
|
||||||
|
#define OP_STR_ASC 0x3E
|
||||||
|
#define OP_STR_SPACE 0x3F
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Comparison (push boolean result)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_CMP_EQ 0x40
|
||||||
|
#define OP_CMP_NE 0x41
|
||||||
|
#define OP_CMP_LT 0x42
|
||||||
|
#define OP_CMP_GT 0x43
|
||||||
|
#define OP_CMP_LE 0x44
|
||||||
|
#define OP_CMP_GE 0x45
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Logical / bitwise
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_AND 0x48
|
||||||
|
#define OP_OR 0x49
|
||||||
|
#define OP_NOT 0x4A
|
||||||
|
#define OP_XOR 0x4B
|
||||||
|
#define OP_EQV 0x4C
|
||||||
|
#define OP_IMP 0x4D
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Control flow
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_JMP 0x50 // [int16 offset] unconditional jump
|
||||||
|
#define OP_JMP_TRUE 0x51 // [int16 offset] jump if TOS is true
|
||||||
|
#define OP_JMP_FALSE 0x52 // [int16 offset] jump if TOS is false
|
||||||
|
#define OP_CALL 0x53 // [uint16 addr] [uint8 argc] [uint8 baseSlot]
|
||||||
|
#define OP_GOSUB_RET 0x54 // pop PC from eval stack, jump (GOSUB return)
|
||||||
|
#define OP_RET 0x55 // return from subroutine
|
||||||
|
#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_NEXT 0x58 // [uint16 varIdx] [uint8 isLocal] [int16 loopTop]
|
||||||
|
#define OP_FOR_POP 0x59 // pop top FOR stack entry (for EXIT FOR)
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Type conversion
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_CONV_INT_FLT 0x60 // int -> float
|
||||||
|
#define OP_CONV_FLT_INT 0x61 // float -> int (banker's rounding)
|
||||||
|
#define OP_CONV_INT_STR 0x62 // int -> string
|
||||||
|
#define OP_CONV_STR_INT 0x63 // string -> int (VAL)
|
||||||
|
#define OP_CONV_FLT_STR 0x64 // float -> string
|
||||||
|
#define OP_CONV_STR_FLT 0x65 // string -> float (VAL)
|
||||||
|
#define OP_CONV_INT_LONG 0x66 // int16 -> int32
|
||||||
|
#define OP_CONV_LONG_INT 0x67 // int32 -> int16
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// I/O
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_PRINT 0x70 // print TOS to current output
|
||||||
|
#define OP_PRINT_NL 0x71 // print newline
|
||||||
|
#define OP_PRINT_TAB 0x72 // print tab (14-column zones)
|
||||||
|
#define OP_PRINT_SPC 0x73 // [uint8 n] print n spaces
|
||||||
|
#define OP_INPUT 0x74 // read line into string on stack
|
||||||
|
#define OP_FILE_OPEN 0x75 // [uint8 mode] filename, channel# on stack
|
||||||
|
#define OP_FILE_CLOSE 0x76 // channel# on stack
|
||||||
|
#define OP_FILE_PRINT 0x77 // channel#, value on stack
|
||||||
|
#define OP_FILE_INPUT 0x78 // channel# on stack, push string
|
||||||
|
#define OP_FILE_EOF 0x79 // channel# on stack, push boolean
|
||||||
|
#define OP_FILE_LINE_INPUT 0x7A // channel# on stack, push string
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// UI / Event (used when form system is active)
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// All UI opcodes are name-based: control references, property names,
|
||||||
|
// method names, and form names are strings resolved at runtime.
|
||||||
|
// This allows third-party widget DXEs and new properties to work
|
||||||
|
// without recompiling the BASIC runtime.
|
||||||
|
//
|
||||||
|
// Stack convention:
|
||||||
|
// LOAD_PROP: ... controlRef propNameStr -> ... value
|
||||||
|
// STORE_PROP: ... controlRef propNameStr value -> ...
|
||||||
|
// CALL_METHOD: ... controlRef methodNameStr [args] -> ... [result]
|
||||||
|
// LOAD_FORM: ... formNameStr -> ... formRef
|
||||||
|
// CREATE_CTRL: ... formRef typeNameStr nameStr -> ... controlRef
|
||||||
|
|
||||||
|
#define OP_LOAD_PROP 0x80 // pop propName, pop ctrlRef, push property value
|
||||||
|
#define OP_STORE_PROP 0x81 // pop value, pop propName, pop ctrlRef, set property
|
||||||
|
#define OP_CALL_METHOD 0x82 // [uint8 argc] pop methodName, pop ctrlRef, pop args, push result
|
||||||
|
#define OP_LOAD_FORM 0x83 // pop formName string, push form reference
|
||||||
|
#define OP_UNLOAD_FORM 0x84 // pop formRef, unload it
|
||||||
|
#define OP_SHOW_FORM 0x85 // [uint8 modal] pop formRef, show it
|
||||||
|
#define OP_HIDE_FORM 0x86 // pop formRef, hide it
|
||||||
|
#define OP_DO_EVENTS 0x87
|
||||||
|
#define OP_MSGBOX 0x88 // [uint8 flags] pop message string, push result
|
||||||
|
#define OP_INPUTBOX 0x89 // pop default, pop title, pop prompt, push result string
|
||||||
|
#define OP_ME_REF 0x8A // push current form reference
|
||||||
|
#define OP_CREATE_CTRL 0x8B // pop name, pop typeName, pop formRef, push controlRef
|
||||||
|
#define OP_FIND_CTRL 0x8C // pop ctrlName, pop formRef, push controlRef
|
||||||
|
#define OP_CTRL_REF 0x8D // [uint16 nameConstIdx] push named control on current form
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Array / misc
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_DIM_ARRAY 0x90 // [uint8 dims] [uint8 type] bounds on stack
|
||||||
|
#define OP_REDIM 0x91 // [uint8 dims] [uint8 preserve] bounds on stack
|
||||||
|
#define OP_ERASE 0x92 // array ref on stack
|
||||||
|
#define OP_LBOUND 0x93 // [uint8 dim] array ref on stack
|
||||||
|
#define OP_UBOUND 0x94 // [uint8 dim] array ref on stack
|
||||||
|
#define OP_ON_ERROR 0x95 // [int16 handler] set error handler (0 = disable)
|
||||||
|
#define OP_RESUME 0x96 // resume after error
|
||||||
|
#define OP_RESUME_NEXT 0x97 // resume at next statement
|
||||||
|
#define OP_RAISE_ERR 0x98 // error number on stack
|
||||||
|
#define OP_ERR_NUM 0x99 // push current error number
|
||||||
|
#define OP_ERR_CLEAR 0x9A // clear error state
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Math built-ins (single opcode each for common functions)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_MATH_ABS 0xA0
|
||||||
|
#define OP_MATH_INT 0xA1 // floor
|
||||||
|
#define OP_MATH_FIX 0xA2 // truncate toward zero
|
||||||
|
#define OP_MATH_SGN 0xA3
|
||||||
|
#define OP_MATH_SQR 0xA4
|
||||||
|
#define OP_MATH_SIN 0xA5
|
||||||
|
#define OP_MATH_COS 0xA6
|
||||||
|
#define OP_MATH_TAN 0xA7
|
||||||
|
#define OP_MATH_ATN 0xA8
|
||||||
|
#define OP_MATH_LOG 0xA9
|
||||||
|
#define OP_MATH_EXP 0xAA
|
||||||
|
#define OP_MATH_RND 0xAB
|
||||||
|
#define OP_MATH_RANDOMIZE 0xAC // seed on stack (or TIMER if -1)
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Conversion built-ins
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_STR_VAL 0xB0 // VAL(s$) -> number
|
||||||
|
#define OP_STR_STRF 0xB1 // STR$(n) -> string
|
||||||
|
#define OP_STR_HEX 0xB2 // HEX$(n) -> string
|
||||||
|
#define OP_STR_STRING 0xB3 // STRING$(n, char) -> string
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Extended built-ins
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_MATH_TIMER 0xB4 // push seconds since midnight as DOUBLE
|
||||||
|
#define OP_DATE_STR 0xB5 // push DATE$ string "MM-DD-YYYY"
|
||||||
|
#define OP_TIME_STR 0xB6 // push TIME$ string "HH:MM:SS"
|
||||||
|
#define OP_SLEEP 0xB7 // pop seconds, sleep
|
||||||
|
#define OP_ENVIRON 0xB8 // pop env var name, push value string
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// DATA/READ/RESTORE
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_READ_DATA 0xB9 // push next value from data pool
|
||||||
|
#define OP_RESTORE 0xBA // reset data pointer to 0
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// WRITE # (comma-delimited with quoted strings)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_FILE_WRITE 0xBB // pop channel + value, write in WRITE format
|
||||||
|
#define OP_FILE_WRITE_SEP 0xBC // pop channel, write comma separator
|
||||||
|
#define OP_FILE_WRITE_NL 0xBD // pop channel, write newline
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Random/Binary file I/O
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_FILE_GET 0xBE // pop channel + recno, read record, push value
|
||||||
|
#define OP_FILE_PUT 0xBF // pop channel + recno + value, write record
|
||||||
|
#define OP_FILE_SEEK 0xC0 // pop channel + position, seek
|
||||||
|
#define OP_FILE_LOF 0xC1 // pop channel, push file length
|
||||||
|
#define OP_FILE_LOC 0xC2 // pop channel, push current position
|
||||||
|
#define OP_FILE_FREEFILE 0xC3 // push next free channel number
|
||||||
|
#define OP_FILE_INPUT_N 0xC4 // pop channel + n, read n chars, push string
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Fixed-length strings and MID$ assignment
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_STR_FIXLEN 0xC5 // [uint16 len] pop string, pad/truncate, push
|
||||||
|
#define OP_STR_MID_ASGN 0xC6 // pop replacement, len, start, str; push modified
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// PRINT USING
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_PRINT_USING 0xC7 // pop format + value, push formatted string
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SPC(n) and TAB(n) with stack-based argument
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_PRINT_TAB_N 0xC8 // pop column count, print spaces to reach column
|
||||||
|
#define OP_PRINT_SPC_N 0xC9 // pop count, print that many spaces
|
||||||
|
#define OP_FORMAT 0xCA // pop format string + value, push formatted string
|
||||||
|
#define OP_SHELL 0xCB // pop command string, call system(), push return value
|
||||||
|
#define OP_COMPARE_MODE 0xCC // [uint8 mode] set string compare mode (0=binary, 1=text)
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// External library calls (DECLARE LIBRARY)
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Calls native functions exported by dynamically loaded libraries.
|
||||||
|
// The VM resolves library + function name on first call via a host
|
||||||
|
// callback, caches the result, and marshals arguments through a
|
||||||
|
// second callback. This allows BASIC programs to use any library
|
||||||
|
// (serial, security, third-party) without recompiling the runtime.
|
||||||
|
|
||||||
|
#define OP_CALL_EXTERN 0xCD // [uint16 libNameIdx] [uint16 funcNameIdx] [uint8 argc] [uint8 retType]
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Halt
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define OP_HALT 0xFF
|
||||||
|
|
||||||
|
#endif // DVXBASIC_OPCODES_H
|
||||||
4809
dvxbasic/compiler/parser.c
Normal file
4809
dvxbasic/compiler/parser.c
Normal file
File diff suppressed because it is too large
Load diff
57
dvxbasic/compiler/parser.h
Normal file
57
dvxbasic/compiler/parser.h
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
// parser.h -- DVX BASIC parser (recursive descent)
|
||||||
|
//
|
||||||
|
// Single-pass compiler: reads tokens from the lexer and emits
|
||||||
|
// p-code directly via the code generator. No AST. Forward
|
||||||
|
// references to SUBs/FUNCTIONs are resolved via backpatching.
|
||||||
|
//
|
||||||
|
// Embeddable: no DVX dependencies, pure C.
|
||||||
|
|
||||||
|
#ifndef DVXBASIC_PARSER_H
|
||||||
|
#define DVXBASIC_PARSER_H
|
||||||
|
|
||||||
|
#include "lexer.h"
|
||||||
|
#include "codegen.h"
|
||||||
|
#include "symtab.h"
|
||||||
|
#include "../runtime/vm.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Parser state
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
BasLexerT lex;
|
||||||
|
BasCodeGenT cg;
|
||||||
|
BasSymTabT sym;
|
||||||
|
char error[1024];
|
||||||
|
bool hasError;
|
||||||
|
int32_t errorLine;
|
||||||
|
int32_t lastUdtTypeId; // index of last resolved UDT type from resolveTypeName
|
||||||
|
int32_t optionBase; // default array lower bound (0 or 1)
|
||||||
|
bool optionCompareText; // true = case-insensitive string comparison
|
||||||
|
bool optionExplicit; // true = variables must be declared with DIM
|
||||||
|
uint8_t defType[26]; // default type per letter (A-Z), set by DEFINT etc.
|
||||||
|
char currentProc[BAS_MAX_TOKEN_LEN]; // name of current SUB/FUNCTION
|
||||||
|
} BasParserT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// API
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Initialize parser with source text.
|
||||||
|
void basParserInit(BasParserT *p, const char *source, int32_t sourceLen);
|
||||||
|
|
||||||
|
// Parse the entire source and generate p-code.
|
||||||
|
// Returns true on success, false on error (check p->error).
|
||||||
|
bool basParse(BasParserT *p);
|
||||||
|
|
||||||
|
// Build a module from the parsed code. Returns NULL on error.
|
||||||
|
// Caller owns the module and must free with basModuleFree().
|
||||||
|
BasModuleT *basParserBuildModule(BasParserT *p);
|
||||||
|
|
||||||
|
// Free parser resources.
|
||||||
|
void basParserFree(BasParserT *p);
|
||||||
|
|
||||||
|
#endif // DVXBASIC_PARSER_H
|
||||||
147
dvxbasic/compiler/symtab.c
Normal file
147
dvxbasic/compiler/symtab.c
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
// symtab.c -- DVX BASIC symbol table implementation
|
||||||
|
|
||||||
|
#include "symtab.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Case-insensitive name comparison
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static bool namesEqual(const char *a, const char *b) {
|
||||||
|
while (*a && *b) {
|
||||||
|
char ca = *a >= 'a' && *a <= 'z' ? *a - 32 : *a;
|
||||||
|
char cb = *b >= 'a' && *b <= 'z' ? *b - 32 : *b;
|
||||||
|
|
||||||
|
if (ca != cb) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
a++;
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *a == *b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basSymTabAdd
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType) {
|
||||||
|
if (tab->count >= BAS_MAX_SYMBOLS) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for duplicate in current scope
|
||||||
|
BasScopeE scope = tab->inLocalScope ? SCOPE_LOCAL : SCOPE_GLOBAL;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < tab->count; i++) {
|
||||||
|
if (tab->symbols[i].scope == scope && namesEqual(tab->symbols[i].name, name)) {
|
||||||
|
return NULL; // duplicate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasSymbolT *sym = &tab->symbols[tab->count++];
|
||||||
|
memset(sym, 0, sizeof(*sym));
|
||||||
|
strncpy(sym->name, name, BAS_MAX_SYMBOL_NAME - 1);
|
||||||
|
sym->name[BAS_MAX_SYMBOL_NAME - 1] = '\0';
|
||||||
|
sym->kind = kind;
|
||||||
|
sym->scope = scope;
|
||||||
|
sym->dataType = dataType;
|
||||||
|
sym->isDefined = true;
|
||||||
|
|
||||||
|
return sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basSymTabAllocSlot
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
int32_t basSymTabAllocSlot(BasSymTabT *tab) {
|
||||||
|
if (tab->inLocalScope) {
|
||||||
|
return tab->nextLocalIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tab->nextGlobalIdx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basSymTabEnterLocal
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basSymTabEnterLocal(BasSymTabT *tab) {
|
||||||
|
tab->inLocalScope = true;
|
||||||
|
tab->nextLocalIdx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basSymTabFind
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name) {
|
||||||
|
// Search local scope first
|
||||||
|
if (tab->inLocalScope) {
|
||||||
|
for (int32_t i = tab->count - 1; i >= 0; i--) {
|
||||||
|
if (tab->symbols[i].scope == SCOPE_LOCAL && namesEqual(tab->symbols[i].name, name)) {
|
||||||
|
return &tab->symbols[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search global scope
|
||||||
|
return basSymTabFindGlobal(tab, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basSymTabFindGlobal
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name) {
|
||||||
|
for (int32_t i = 0; i < tab->count; i++) {
|
||||||
|
if (tab->symbols[i].scope == SCOPE_GLOBAL && namesEqual(tab->symbols[i].name, name)) {
|
||||||
|
return &tab->symbols[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basSymTabInit
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basSymTabInit(BasSymTabT *tab) {
|
||||||
|
memset(tab, 0, sizeof(*tab));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basSymTabLeaveLocal
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basSymTabLeaveLocal(BasSymTabT *tab) {
|
||||||
|
// Remove all local symbols
|
||||||
|
int32_t newCount = 0;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < tab->count; i++) {
|
||||||
|
if (tab->symbols[i].scope != SCOPE_LOCAL) {
|
||||||
|
if (i != newCount) {
|
||||||
|
tab->symbols[newCount] = tab->symbols[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
newCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tab->count = newCount;
|
||||||
|
tab->inLocalScope = false;
|
||||||
|
tab->nextLocalIdx = 0;
|
||||||
|
}
|
||||||
132
dvxbasic/compiler/symtab.h
Normal file
132
dvxbasic/compiler/symtab.h
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
// symtab.h -- DVX BASIC symbol table
|
||||||
|
//
|
||||||
|
// Tracks variables, constants, subroutines, functions, and labels
|
||||||
|
// during compilation. Supports nested scopes (global + one local
|
||||||
|
// scope per SUB/FUNCTION).
|
||||||
|
//
|
||||||
|
// Embeddable: no DVX dependencies, pure C.
|
||||||
|
|
||||||
|
#ifndef DVXBASIC_SYMTAB_H
|
||||||
|
#define DVXBASIC_SYMTAB_H
|
||||||
|
|
||||||
|
#include "../compiler/opcodes.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Symbol kinds
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SYM_VARIABLE,
|
||||||
|
SYM_CONST,
|
||||||
|
SYM_SUB,
|
||||||
|
SYM_FUNCTION,
|
||||||
|
SYM_LABEL,
|
||||||
|
SYM_TYPE_DEF // user-defined TYPE
|
||||||
|
} BasSymKindE;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Symbol scope
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SCOPE_GLOBAL,
|
||||||
|
SCOPE_LOCAL
|
||||||
|
} BasScopeE;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Symbol entry
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define BAS_MAX_SYMBOL_NAME 64
|
||||||
|
#define BAS_MAX_PARAMS 16
|
||||||
|
#define BAS_MAX_CALL_PATCHES 32
|
||||||
|
#define BAS_MAX_UDT_FIELDS 32
|
||||||
|
|
||||||
|
// UDT field definition
|
||||||
|
typedef struct {
|
||||||
|
char name[BAS_MAX_SYMBOL_NAME];
|
||||||
|
uint8_t dataType; // BAS_TYPE_*
|
||||||
|
int32_t udtTypeId; // if dataType == BAS_TYPE_UDT, index of the TYPE_DEF symbol
|
||||||
|
} BasFieldDefT;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char name[BAS_MAX_SYMBOL_NAME];
|
||||||
|
BasSymKindE kind;
|
||||||
|
BasScopeE scope;
|
||||||
|
uint8_t dataType; // BAS_TYPE_* for variables/functions
|
||||||
|
int32_t index; // slot index (local or global)
|
||||||
|
int32_t codeAddr; // PC address for SUB/FUNCTION/LABEL
|
||||||
|
bool isDefined; // false = forward-declared
|
||||||
|
bool isArray;
|
||||||
|
bool isShared;
|
||||||
|
bool isExtern; // true = external library function (DECLARE LIBRARY)
|
||||||
|
int32_t udtTypeId; // for variables of BAS_TYPE_UDT: index of TYPE_DEF symbol
|
||||||
|
int32_t fixedLen; // for STRING * n: fixed length (0 = variable-length)
|
||||||
|
uint16_t externLibIdx; // constant pool index for library name (if isExtern)
|
||||||
|
uint16_t externFuncIdx; // constant pool index for function name (if isExtern)
|
||||||
|
|
||||||
|
// For SUB/FUNCTION: parameter info
|
||||||
|
int32_t paramCount;
|
||||||
|
uint8_t paramTypes[BAS_MAX_PARAMS];
|
||||||
|
bool paramByVal[BAS_MAX_PARAMS];
|
||||||
|
|
||||||
|
// Forward-reference backpatch list (code addresses to patch when defined)
|
||||||
|
int32_t patchAddrs[BAS_MAX_CALL_PATCHES];
|
||||||
|
int32_t patchCount;
|
||||||
|
|
||||||
|
// For CONST: the constant value
|
||||||
|
union {
|
||||||
|
int32_t constInt;
|
||||||
|
double constDbl;
|
||||||
|
};
|
||||||
|
char constStr[256];
|
||||||
|
|
||||||
|
// For TYPE_DEF: field definitions
|
||||||
|
BasFieldDefT fields[BAS_MAX_UDT_FIELDS];
|
||||||
|
int32_t fieldCount;
|
||||||
|
} BasSymbolT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Symbol table
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define BAS_MAX_SYMBOLS 512
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
BasSymbolT symbols[BAS_MAX_SYMBOLS];
|
||||||
|
int32_t count;
|
||||||
|
int32_t nextGlobalIdx; // next global variable slot
|
||||||
|
int32_t nextLocalIdx; // next local variable slot (reset per SUB/FUNCTION)
|
||||||
|
bool inLocalScope; // true when inside SUB/FUNCTION
|
||||||
|
} BasSymTabT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// API
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basSymTabInit(BasSymTabT *tab);
|
||||||
|
|
||||||
|
// Add a symbol. Returns the symbol pointer, or NULL if the table is full
|
||||||
|
// or the name already exists in the current scope.
|
||||||
|
BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType);
|
||||||
|
|
||||||
|
// Look up a symbol by name. Searches local scope first, then global.
|
||||||
|
// Case-insensitive.
|
||||||
|
BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name);
|
||||||
|
|
||||||
|
// Look up a symbol in the global scope only.
|
||||||
|
BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name);
|
||||||
|
|
||||||
|
// Enter local scope (called at SUB/FUNCTION start).
|
||||||
|
void basSymTabEnterLocal(BasSymTabT *tab);
|
||||||
|
|
||||||
|
// Leave local scope (called at END SUB/FUNCTION). Removes local symbols.
|
||||||
|
void basSymTabLeaveLocal(BasSymTabT *tab);
|
||||||
|
|
||||||
|
// Allocate the next variable slot (global or local depending on scope).
|
||||||
|
int32_t basSymTabAllocSlot(BasSymTabT *tab);
|
||||||
|
|
||||||
|
#endif // DVXBASIC_SYMTAB_H
|
||||||
5
dvxbasic/dvxbasic.res
Normal file
5
dvxbasic/dvxbasic.res
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# dvxbasic.res -- Resource manifest for DVX BASIC
|
||||||
|
icon32 icon icon32.bmp
|
||||||
|
name text "DVX BASIC"
|
||||||
|
author text "DVX Project"
|
||||||
|
description text "BASIC language IDE and runtime"
|
||||||
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
|
||||||
BIN
dvxbasic/icon32.bmp
(Stored with Git LFS)
Normal file
BIN
dvxbasic/icon32.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
1234
dvxbasic/ide/ideMain.c
Normal file
1234
dvxbasic/ide/ideMain.c
Normal file
File diff suppressed because it is too large
Load diff
643
dvxbasic/runtime/values.c
Normal file
643
dvxbasic/runtime/values.c
Normal file
|
|
@ -0,0 +1,643 @@
|
||||||
|
// values.c -- DVX BASIC value system implementation
|
||||||
|
//
|
||||||
|
// Tagged union values with reference-counted strings. The string
|
||||||
|
// heap uses simple refcounting: assignment increments, scope exit
|
||||||
|
// decrements, zero frees. No garbage collector needed.
|
||||||
|
|
||||||
|
#include "values.h"
|
||||||
|
#include "../compiler/opcodes.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// String system
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Singleton empty string -- never freed, always available.
|
||||||
|
// Extra byte for the null terminator via the struct hack.
|
||||||
|
static struct {
|
||||||
|
BasStringT hdr;
|
||||||
|
char nul;
|
||||||
|
} sEmptyStringStorage = { { .refCount = 999999, .len = 0, .cap = 1 }, '\0' };
|
||||||
|
BasStringT *basEmptyString = &sEmptyStringStorage.hdr;
|
||||||
|
|
||||||
|
|
||||||
|
BasStringT *basStringAlloc(int32_t cap) {
|
||||||
|
if (cap < 1) {
|
||||||
|
cap = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasStringT *s = (BasStringT *)malloc(sizeof(BasStringT) + cap);
|
||||||
|
|
||||||
|
if (!s) {
|
||||||
|
return basStringRef(basEmptyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
s->refCount = 1;
|
||||||
|
s->len = 0;
|
||||||
|
s->cap = cap;
|
||||||
|
s->data[0] = '\0';
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasStringT *basStringConcat(const BasStringT *a, const BasStringT *b) {
|
||||||
|
int32_t newLen = a->len + b->len;
|
||||||
|
BasStringT *s = basStringAlloc(newLen + 1);
|
||||||
|
memcpy(s->data, a->data, a->len);
|
||||||
|
memcpy(s->data + a->len, b->data, b->len);
|
||||||
|
s->data[newLen] = '\0';
|
||||||
|
s->len = newLen;
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t basStringCompare(const BasStringT *a, const BasStringT *b) {
|
||||||
|
int32_t minLen = a->len < b->len ? a->len : b->len;
|
||||||
|
int32_t cmp = memcmp(a->data, b->data, minLen);
|
||||||
|
|
||||||
|
if (cmp != 0) {
|
||||||
|
return cmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a->len < b->len) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a->len > b->len) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t basStringCompareCI(const BasStringT *a, const BasStringT *b) {
|
||||||
|
int32_t minLen = a->len < b->len ? a->len : b->len;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < minLen; i++) {
|
||||||
|
int32_t ca = toupper((unsigned char)a->data[i]);
|
||||||
|
int32_t cb = toupper((unsigned char)b->data[i]);
|
||||||
|
|
||||||
|
if (ca != cb) {
|
||||||
|
return ca - cb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a->len < b->len) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a->len > b->len) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasStringT *basStringNew(const char *text, int32_t len) {
|
||||||
|
if (!text || len <= 0) {
|
||||||
|
return basStringRef(basEmptyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
BasStringT *s = basStringAlloc(len + 1);
|
||||||
|
if (s->cap >= len + 1) {
|
||||||
|
memcpy(s->data, text, len);
|
||||||
|
s->data[len] = '\0';
|
||||||
|
s->len = len;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasStringT *basStringRef(BasStringT *s) {
|
||||||
|
if (s) {
|
||||||
|
s->refCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasStringT *basStringSub(const BasStringT *s, int32_t start, int32_t len) {
|
||||||
|
if (start < 0) {
|
||||||
|
start = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start >= s->len) {
|
||||||
|
return basStringRef(basEmptyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < 0 || start + len > s->len) {
|
||||||
|
len = s->len - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
return basStringNew(s->data + start, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void basStringSystemInit(void) {
|
||||||
|
sEmptyStringStorage.nul = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void basStringSystemShutdown(void) {
|
||||||
|
// Nothing to do -- empty string is static
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void basStringUnref(BasStringT *s) {
|
||||||
|
if (!s || s == basEmptyString) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->refCount--;
|
||||||
|
|
||||||
|
if (s->refCount <= 0) {
|
||||||
|
free(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Array system
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basArrayFree(BasArrayT *arr) {
|
||||||
|
if (!arr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arr->elements) {
|
||||||
|
for (int32_t i = 0; i < arr->totalElements; i++) {
|
||||||
|
basValRelease(&arr->elements[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(arr->elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t basArrayIndex(BasArrayT *arr, int32_t *indices, int32_t ndims) {
|
||||||
|
if (!arr || ndims != arr->dims) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t flatIdx = 0;
|
||||||
|
int32_t multiplier = 1;
|
||||||
|
|
||||||
|
// Row-major order: last dimension varies fastest
|
||||||
|
for (int32_t d = ndims - 1; d >= 0; d--) {
|
||||||
|
int32_t idx = indices[d] - arr->lbound[d];
|
||||||
|
int32_t dimSize = arr->ubound[d] - arr->lbound[d] + 1;
|
||||||
|
|
||||||
|
if (idx < 0 || idx >= dimSize) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
flatIdx += idx * multiplier;
|
||||||
|
multiplier *= dimSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return flatIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasArrayT *basArrayNew(int32_t dims, int32_t *lbounds, int32_t *ubounds, uint8_t elementType) {
|
||||||
|
if (dims < 1 || dims > BAS_ARRAY_MAX_DIMS) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasArrayT *arr = (BasArrayT *)calloc(1, sizeof(BasArrayT));
|
||||||
|
|
||||||
|
if (!arr) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
arr->refCount = 1;
|
||||||
|
arr->elementType = elementType;
|
||||||
|
arr->dims = dims;
|
||||||
|
|
||||||
|
int32_t total = 1;
|
||||||
|
|
||||||
|
for (int32_t d = 0; d < dims; d++) {
|
||||||
|
arr->lbound[d] = lbounds[d];
|
||||||
|
arr->ubound[d] = ubounds[d];
|
||||||
|
int32_t dimSize = ubounds[d] - lbounds[d] + 1;
|
||||||
|
|
||||||
|
if (dimSize < 1) {
|
||||||
|
free(arr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
total *= dimSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
arr->totalElements = total;
|
||||||
|
arr->elements = (BasValueT *)calloc(total, sizeof(BasValueT));
|
||||||
|
|
||||||
|
if (!arr->elements) {
|
||||||
|
free(arr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize all elements to the default for the element type
|
||||||
|
for (int32_t i = 0; i < total; i++) {
|
||||||
|
arr->elements[i].type = elementType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasArrayT *basArrayRef(BasArrayT *arr) {
|
||||||
|
if (arr) {
|
||||||
|
arr->refCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void basArrayUnref(BasArrayT *arr) {
|
||||||
|
if (!arr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
arr->refCount--;
|
||||||
|
|
||||||
|
if (arr->refCount <= 0) {
|
||||||
|
basArrayFree(arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// UDT system
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void basUdtFree(BasUdtT *udt) {
|
||||||
|
if (!udt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (udt->fields) {
|
||||||
|
for (int32_t i = 0; i < udt->fieldCount; i++) {
|
||||||
|
basValRelease(&udt->fields[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(udt->fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(udt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasUdtT *basUdtNew(int32_t typeId, int32_t fieldCount) {
|
||||||
|
BasUdtT *udt = (BasUdtT *)calloc(1, sizeof(BasUdtT));
|
||||||
|
|
||||||
|
if (!udt) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
udt->refCount = 1;
|
||||||
|
udt->typeId = typeId;
|
||||||
|
udt->fieldCount = fieldCount;
|
||||||
|
udt->fields = (BasValueT *)calloc(fieldCount, sizeof(BasValueT));
|
||||||
|
|
||||||
|
if (!udt->fields) {
|
||||||
|
free(udt);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return udt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasUdtT *basUdtRef(BasUdtT *udt) {
|
||||||
|
if (udt) {
|
||||||
|
udt->refCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return udt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void basUdtUnref(BasUdtT *udt) {
|
||||||
|
if (!udt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
udt->refCount--;
|
||||||
|
|
||||||
|
if (udt->refCount <= 0) {
|
||||||
|
basUdtFree(udt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Value constructors
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
BasValueT basValBool(bool v) {
|
||||||
|
BasValueT val;
|
||||||
|
val.type = BAS_TYPE_BOOLEAN;
|
||||||
|
val.boolVal = v ? -1 : 0;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValObject(void *obj) {
|
||||||
|
BasValueT val;
|
||||||
|
val.type = BAS_TYPE_OBJECT;
|
||||||
|
val.objVal = obj;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValCopy(BasValueT v) {
|
||||||
|
if (v.type == BAS_TYPE_STRING && v.strVal) {
|
||||||
|
basStringRef(v.strVal);
|
||||||
|
} else if (v.type == BAS_TYPE_ARRAY && v.arrVal) {
|
||||||
|
basArrayRef(v.arrVal);
|
||||||
|
} else if (v.type == BAS_TYPE_UDT && v.udtVal) {
|
||||||
|
basUdtRef(v.udtVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValDouble(double v) {
|
||||||
|
BasValueT val;
|
||||||
|
val.type = BAS_TYPE_DOUBLE;
|
||||||
|
val.dblVal = v;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValInteger(int16_t v) {
|
||||||
|
BasValueT val;
|
||||||
|
val.type = BAS_TYPE_INTEGER;
|
||||||
|
val.intVal = v;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValLong(int32_t v) {
|
||||||
|
BasValueT val;
|
||||||
|
val.type = BAS_TYPE_LONG;
|
||||||
|
val.longVal = v;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValSingle(float v) {
|
||||||
|
BasValueT val;
|
||||||
|
val.type = BAS_TYPE_SINGLE;
|
||||||
|
val.sngVal = v;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValString(BasStringT *s) {
|
||||||
|
BasValueT val;
|
||||||
|
val.type = BAS_TYPE_STRING;
|
||||||
|
val.strVal = s ? basStringRef(s) : basStringRef(basEmptyString);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValStringFromC(const char *text) {
|
||||||
|
BasValueT val;
|
||||||
|
val.type = BAS_TYPE_STRING;
|
||||||
|
val.strVal = basStringNew(text, text ? (int32_t)strlen(text) : 0);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void basValRelease(BasValueT *v) {
|
||||||
|
if (v->type == BAS_TYPE_STRING) {
|
||||||
|
basStringUnref(v->strVal);
|
||||||
|
v->strVal = NULL;
|
||||||
|
} else if (v->type == BAS_TYPE_ARRAY) {
|
||||||
|
basArrayUnref(v->arrVal);
|
||||||
|
v->arrVal = NULL;
|
||||||
|
} else if (v->type == BAS_TYPE_UDT) {
|
||||||
|
basUdtUnref(v->udtVal);
|
||||||
|
v->udtVal = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Type conversion
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
BasValueT basValToBool(BasValueT v) {
|
||||||
|
return basValBool(basValIsTruthy(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValToDouble(BasValueT v) {
|
||||||
|
return basValDouble(basValToNumber(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValToInteger(BasValueT v) {
|
||||||
|
double n = basValToNumber(v);
|
||||||
|
// Banker's rounding (round half to even)
|
||||||
|
int32_t rounded = (int32_t)(n + (n > 0 ? 0.5 : -0.5));
|
||||||
|
return basValInteger((int16_t)rounded);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValToLong(BasValueT v) {
|
||||||
|
double n = basValToNumber(v);
|
||||||
|
int32_t rounded = (int32_t)(n + (n > 0 ? 0.5 : -0.5));
|
||||||
|
return basValLong(rounded);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double basValToNumber(BasValueT v) {
|
||||||
|
switch (v.type) {
|
||||||
|
case BAS_TYPE_INTEGER:
|
||||||
|
return (double)v.intVal;
|
||||||
|
|
||||||
|
case BAS_TYPE_LONG:
|
||||||
|
return (double)v.longVal;
|
||||||
|
|
||||||
|
case BAS_TYPE_SINGLE:
|
||||||
|
return (double)v.sngVal;
|
||||||
|
|
||||||
|
case BAS_TYPE_DOUBLE:
|
||||||
|
return v.dblVal;
|
||||||
|
|
||||||
|
case BAS_TYPE_BOOLEAN:
|
||||||
|
return (double)v.boolVal;
|
||||||
|
|
||||||
|
case BAS_TYPE_STRING:
|
||||||
|
if (v.strVal && v.strVal->len > 0) {
|
||||||
|
return atof(v.strVal->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValToSingle(BasValueT v) {
|
||||||
|
return basValSingle((float)basValToNumber(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasValueT basValToString(BasValueT v) {
|
||||||
|
if (v.type == BAS_TYPE_STRING) {
|
||||||
|
return basValCopy(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
BasStringT *s = basValFormatString(v);
|
||||||
|
BasValueT result;
|
||||||
|
result.type = BAS_TYPE_STRING;
|
||||||
|
result.strVal = s;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BasStringT *basValFormatString(BasValueT v) {
|
||||||
|
char buf[64];
|
||||||
|
|
||||||
|
switch (v.type) {
|
||||||
|
case BAS_TYPE_INTEGER:
|
||||||
|
snprintf(buf, sizeof(buf), "%d", (int)v.intVal);
|
||||||
|
return basStringNew(buf, (int32_t)strlen(buf));
|
||||||
|
|
||||||
|
case BAS_TYPE_LONG:
|
||||||
|
snprintf(buf, sizeof(buf), "%ld", (long)v.longVal);
|
||||||
|
return basStringNew(buf, (int32_t)strlen(buf));
|
||||||
|
|
||||||
|
case BAS_TYPE_SINGLE: {
|
||||||
|
snprintf(buf, sizeof(buf), "%g", (double)v.sngVal);
|
||||||
|
return basStringNew(buf, (int32_t)strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
case BAS_TYPE_DOUBLE:
|
||||||
|
snprintf(buf, sizeof(buf), "%g", v.dblVal);
|
||||||
|
return basStringNew(buf, (int32_t)strlen(buf));
|
||||||
|
|
||||||
|
case BAS_TYPE_BOOLEAN:
|
||||||
|
return basStringNew(v.boolVal ? "True" : "False", v.boolVal ? 4 : 5);
|
||||||
|
|
||||||
|
case BAS_TYPE_STRING:
|
||||||
|
return v.strVal ? basStringRef(v.strVal) : basStringRef(basEmptyString);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return basStringRef(basEmptyString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool basValIsTruthy(BasValueT v) {
|
||||||
|
switch (v.type) {
|
||||||
|
case BAS_TYPE_INTEGER:
|
||||||
|
return v.intVal != 0;
|
||||||
|
|
||||||
|
case BAS_TYPE_LONG:
|
||||||
|
return v.longVal != 0;
|
||||||
|
|
||||||
|
case BAS_TYPE_SINGLE:
|
||||||
|
return v.sngVal != 0.0f;
|
||||||
|
|
||||||
|
case BAS_TYPE_DOUBLE:
|
||||||
|
return v.dblVal != 0.0;
|
||||||
|
|
||||||
|
case BAS_TYPE_BOOLEAN:
|
||||||
|
return v.boolVal != 0;
|
||||||
|
|
||||||
|
case BAS_TYPE_STRING:
|
||||||
|
return v.strVal && v.strVal->len > 0;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t basValCompare(BasValueT a, BasValueT b) {
|
||||||
|
// String comparison
|
||||||
|
if (a.type == BAS_TYPE_STRING && b.type == BAS_TYPE_STRING) {
|
||||||
|
return basStringCompare(a.strVal ? a.strVal : basEmptyString, b.strVal ? b.strVal : basEmptyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric comparison
|
||||||
|
double na = basValToNumber(a);
|
||||||
|
double nb = basValToNumber(b);
|
||||||
|
|
||||||
|
if (na < nb) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (na > nb) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t basValCompareCI(BasValueT a, BasValueT b) {
|
||||||
|
// String comparison (case-insensitive)
|
||||||
|
if (a.type == BAS_TYPE_STRING && b.type == BAS_TYPE_STRING) {
|
||||||
|
return basStringCompareCI(a.strVal ? a.strVal : basEmptyString, b.strVal ? b.strVal : basEmptyString);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric comparison (same as basValCompare)
|
||||||
|
double na = basValToNumber(a);
|
||||||
|
double nb = basValToNumber(b);
|
||||||
|
|
||||||
|
if (na < nb) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (na > nb) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t basValPromoteType(uint8_t a, uint8_t b) {
|
||||||
|
// String stays string (concat, not arithmetic)
|
||||||
|
if (a == BAS_TYPE_STRING || b == BAS_TYPE_STRING) {
|
||||||
|
return BAS_TYPE_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double wins over everything
|
||||||
|
if (a == BAS_TYPE_DOUBLE || b == BAS_TYPE_DOUBLE) {
|
||||||
|
return BAS_TYPE_DOUBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single wins over integer/long
|
||||||
|
if (a == BAS_TYPE_SINGLE || b == BAS_TYPE_SINGLE) {
|
||||||
|
return BAS_TYPE_SINGLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Long wins over integer
|
||||||
|
if (a == BAS_TYPE_LONG || b == BAS_TYPE_LONG) {
|
||||||
|
return BAS_TYPE_LONG;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BAS_TYPE_INTEGER;
|
||||||
|
}
|
||||||
183
dvxbasic/runtime/values.h
Normal file
183
dvxbasic/runtime/values.h
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
// values.h -- DVX BASIC value representation and string heap
|
||||||
|
//
|
||||||
|
// Tagged union value type for the VM's evaluation stack, variables,
|
||||||
|
// and array elements. Strings are reference-counted for automatic
|
||||||
|
// memory management without a garbage collector.
|
||||||
|
//
|
||||||
|
// Embeddable: no DVX dependencies, pure C.
|
||||||
|
|
||||||
|
#ifndef DVXBASIC_VALUES_H
|
||||||
|
#define DVXBASIC_VALUES_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Reference-counted string
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t refCount;
|
||||||
|
int32_t len;
|
||||||
|
int32_t cap; // allocated capacity (>= len + 1)
|
||||||
|
char data[]; // flexible array member, null-terminated
|
||||||
|
} BasStringT;
|
||||||
|
|
||||||
|
// Allocate a new string from a C string. refCount starts at 1.
|
||||||
|
BasStringT *basStringNew(const char *text, int32_t len);
|
||||||
|
|
||||||
|
// Allocate an empty string with a given capacity.
|
||||||
|
BasStringT *basStringAlloc(int32_t cap);
|
||||||
|
|
||||||
|
// Increment reference count.
|
||||||
|
BasStringT *basStringRef(BasStringT *s);
|
||||||
|
|
||||||
|
// Decrement reference count. Frees if count reaches zero.
|
||||||
|
void basStringUnref(BasStringT *s);
|
||||||
|
|
||||||
|
// Concatenate two strings. Returns a new string (refCount 1).
|
||||||
|
BasStringT *basStringConcat(const BasStringT *a, const BasStringT *b);
|
||||||
|
|
||||||
|
// Substring. Returns a new string (refCount 1).
|
||||||
|
BasStringT *basStringSub(const BasStringT *s, int32_t start, int32_t len);
|
||||||
|
|
||||||
|
// Compare two strings. Returns <0, 0, >0 like strcmp.
|
||||||
|
int32_t basStringCompare(const BasStringT *a, const BasStringT *b);
|
||||||
|
|
||||||
|
// Compare two strings case-insensitively. Returns <0, 0, >0.
|
||||||
|
int32_t basStringCompareCI(const BasStringT *a, const BasStringT *b);
|
||||||
|
|
||||||
|
// The empty string singleton (never freed).
|
||||||
|
extern BasStringT *basEmptyString;
|
||||||
|
|
||||||
|
// Initialize/shutdown the string system.
|
||||||
|
void basStringSystemInit(void);
|
||||||
|
void basStringSystemShutdown(void);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Forward declarations
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct BasValueTag BasValueT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Reference-counted array
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define BAS_ARRAY_MAX_DIMS 8
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t refCount;
|
||||||
|
uint8_t elementType; // BAS_TYPE_*
|
||||||
|
int32_t dims; // number of dimensions
|
||||||
|
int32_t lbound[BAS_ARRAY_MAX_DIMS]; // lower bound per dimension
|
||||||
|
int32_t ubound[BAS_ARRAY_MAX_DIMS]; // upper bound per dimension
|
||||||
|
int32_t totalElements;
|
||||||
|
BasValueT *elements; // flat array of values
|
||||||
|
} BasArrayT;
|
||||||
|
|
||||||
|
// Allocate a new array. refCount starts at 1.
|
||||||
|
BasArrayT *basArrayNew(int32_t dims, int32_t *lbounds, int32_t *ubounds, uint8_t elementType);
|
||||||
|
|
||||||
|
// Free all elements and release the array.
|
||||||
|
void basArrayFree(BasArrayT *arr);
|
||||||
|
|
||||||
|
// Increment reference count.
|
||||||
|
BasArrayT *basArrayRef(BasArrayT *arr);
|
||||||
|
|
||||||
|
// Decrement reference count. Frees if count reaches zero.
|
||||||
|
void basArrayUnref(BasArrayT *arr);
|
||||||
|
|
||||||
|
// Compute flat index from multi-dimensional indices. Returns -1 if out of bounds.
|
||||||
|
int32_t basArrayIndex(BasArrayT *arr, int32_t *indices, int32_t ndims);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Reference-counted user-defined type instance
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t refCount;
|
||||||
|
int32_t typeId; // index into type definition table
|
||||||
|
int32_t fieldCount;
|
||||||
|
BasValueT *fields; // array of field values
|
||||||
|
} BasUdtT;
|
||||||
|
|
||||||
|
// Allocate a new UDT instance. refCount starts at 1.
|
||||||
|
BasUdtT *basUdtNew(int32_t typeId, int32_t fieldCount);
|
||||||
|
|
||||||
|
// Free all fields and release the UDT.
|
||||||
|
void basUdtFree(BasUdtT *udt);
|
||||||
|
|
||||||
|
// Increment reference count.
|
||||||
|
BasUdtT *basUdtRef(BasUdtT *udt);
|
||||||
|
|
||||||
|
// Decrement reference count. Frees if count reaches zero.
|
||||||
|
void basUdtUnref(BasUdtT *udt);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Tagged value
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
struct BasValueTag {
|
||||||
|
uint8_t type; // BAS_TYPE_*
|
||||||
|
union {
|
||||||
|
int16_t intVal; // BAS_TYPE_INTEGER
|
||||||
|
int32_t longVal; // BAS_TYPE_LONG
|
||||||
|
float sngVal; // BAS_TYPE_SINGLE
|
||||||
|
double dblVal; // BAS_TYPE_DOUBLE
|
||||||
|
BasStringT *strVal; // BAS_TYPE_STRING (ref-counted)
|
||||||
|
int16_t boolVal; // BAS_TYPE_BOOLEAN (True=-1, False=0)
|
||||||
|
BasArrayT *arrVal; // BAS_TYPE_ARRAY (ref-counted)
|
||||||
|
BasUdtT *udtVal; // BAS_TYPE_UDT (ref-counted)
|
||||||
|
void *objVal; // BAS_TYPE_OBJECT (opaque host pointer)
|
||||||
|
BasValueT *refVal; // BAS_TYPE_REF (ByRef pointer to variable slot)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create values
|
||||||
|
BasValueT basValInteger(int16_t v);
|
||||||
|
BasValueT basValLong(int32_t v);
|
||||||
|
BasValueT basValSingle(float v);
|
||||||
|
BasValueT basValDouble(double v);
|
||||||
|
BasValueT basValString(BasStringT *s);
|
||||||
|
BasValueT basValStringFromC(const char *text);
|
||||||
|
BasValueT basValBool(bool v);
|
||||||
|
BasValueT basValObject(void *obj);
|
||||||
|
|
||||||
|
// Copy a value (increments string refcount if applicable).
|
||||||
|
BasValueT basValCopy(BasValueT v);
|
||||||
|
|
||||||
|
// Release a value (decrements string refcount if applicable).
|
||||||
|
void basValRelease(BasValueT *v);
|
||||||
|
|
||||||
|
// Convert a value to a specific type. Returns the converted value.
|
||||||
|
// The original is NOT released -- caller manages lifetime.
|
||||||
|
BasValueT basValToInteger(BasValueT v);
|
||||||
|
BasValueT basValToLong(BasValueT v);
|
||||||
|
BasValueT basValToSingle(BasValueT v);
|
||||||
|
BasValueT basValToDouble(BasValueT v);
|
||||||
|
BasValueT basValToString(BasValueT v);
|
||||||
|
BasValueT basValToBool(BasValueT v);
|
||||||
|
|
||||||
|
// Get the numeric value as a double (for mixed-type arithmetic).
|
||||||
|
double basValToNumber(BasValueT v);
|
||||||
|
|
||||||
|
// Get the string representation. Returns a new ref-counted string.
|
||||||
|
BasStringT *basValFormatString(BasValueT v);
|
||||||
|
|
||||||
|
// Check if a value is truthy (non-zero number, non-empty string).
|
||||||
|
bool basValIsTruthy(BasValueT v);
|
||||||
|
|
||||||
|
// Compare two values. Returns -1, 0, or 1.
|
||||||
|
// Numeric types are compared numerically. Strings lexicographically.
|
||||||
|
int32_t basValCompare(BasValueT a, BasValueT b);
|
||||||
|
|
||||||
|
// Compare two values case-insensitively (for OPTION COMPARE TEXT).
|
||||||
|
int32_t basValCompareCI(BasValueT a, BasValueT b);
|
||||||
|
|
||||||
|
// Determine the common type for a binary operation (type promotion).
|
||||||
|
// Integer + Single -> Single, etc.
|
||||||
|
uint8_t basValPromoteType(uint8_t a, uint8_t b);
|
||||||
|
|
||||||
|
#endif // DVXBASIC_VALUES_H
|
||||||
4222
dvxbasic/runtime/vm.c
Normal file
4222
dvxbasic/runtime/vm.c
Normal file
File diff suppressed because it is too large
Load diff
365
dvxbasic/runtime/vm.h
Normal file
365
dvxbasic/runtime/vm.h
Normal file
|
|
@ -0,0 +1,365 @@
|
||||||
|
// vm.h -- DVX BASIC virtual machine
|
||||||
|
//
|
||||||
|
// Stack-based p-code interpreter. Executes compiled BASIC bytecode.
|
||||||
|
// Embeddable: the host provides I/O callbacks. No DVX dependencies.
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// BasVmT *vm = basVmCreate();
|
||||||
|
// basVmSetPrintCallback(vm, myPrintFn, myCtx);
|
||||||
|
// basVmSetInputCallback(vm, myInputFn, myCtx);
|
||||||
|
// basVmLoadModule(vm, compiledCode, codeLen, constants, numConsts);
|
||||||
|
// BasVmResultE result = basVmRun(vm);
|
||||||
|
// basVmDestroy(vm);
|
||||||
|
|
||||||
|
#ifndef DVXBASIC_VM_H
|
||||||
|
#define DVXBASIC_VM_H
|
||||||
|
|
||||||
|
#include "values.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Limits
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define BAS_VM_STACK_SIZE 256 // evaluation stack depth
|
||||||
|
#define BAS_VM_CALL_STACK_SIZE 64 // max call nesting
|
||||||
|
#define BAS_VM_MAX_GLOBALS 512 // global variable slots
|
||||||
|
#define BAS_VM_MAX_LOCALS 64 // locals per stack frame
|
||||||
|
#define BAS_VM_MAX_FOR_DEPTH 32 // nested FOR loops
|
||||||
|
#define BAS_VM_MAX_FILES 16 // open file channels
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Result codes
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BAS_VM_OK, // program completed normally
|
||||||
|
BAS_VM_HALTED, // HALT instruction reached
|
||||||
|
BAS_VM_YIELDED, // DoEvents yielded control
|
||||||
|
BAS_VM_ERROR, // runtime error
|
||||||
|
BAS_VM_STACK_OVERFLOW,
|
||||||
|
BAS_VM_STACK_UNDERFLOW,
|
||||||
|
BAS_VM_CALL_OVERFLOW,
|
||||||
|
BAS_VM_DIV_BY_ZERO,
|
||||||
|
BAS_VM_TYPE_MISMATCH,
|
||||||
|
BAS_VM_OUT_OF_MEMORY,
|
||||||
|
BAS_VM_BAD_OPCODE,
|
||||||
|
BAS_VM_FILE_ERROR,
|
||||||
|
BAS_VM_SUBSCRIPT_RANGE,
|
||||||
|
BAS_VM_USER_ERROR, // ON ERROR raised
|
||||||
|
BAS_VM_STEP_LIMIT // step limit reached (not an error)
|
||||||
|
} BasVmResultE;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// I/O callbacks (host-provided)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Print callback: called for PRINT output.
|
||||||
|
// text is a null-terminated string. newline indicates whether
|
||||||
|
// to advance to the next line after printing.
|
||||||
|
typedef void (*BasPrintFnT)(void *ctx, const char *text, bool newline);
|
||||||
|
|
||||||
|
// Input callback: called for INPUT statement.
|
||||||
|
// prompt is the text to display. The callback must fill buf
|
||||||
|
// (up to bufSize-1 chars, null-terminated). Returns true on
|
||||||
|
// success, false on cancel/error.
|
||||||
|
typedef bool (*BasInputFnT)(void *ctx, const char *prompt, char *buf, int32_t bufSize);
|
||||||
|
|
||||||
|
// DoEvents callback: called for DoEvents statement.
|
||||||
|
// The host should process pending events and return. Returns
|
||||||
|
// true to continue execution, false to stop the program.
|
||||||
|
typedef bool (*BasDoEventsFnT)(void *ctx);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// UI callbacks (host-provided, for form/control system)
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// The VM resolves all UI operations through these callbacks.
|
||||||
|
// Control types, property names, and method names are passed
|
||||||
|
// as strings -- the host maps them to its native widget system.
|
||||||
|
// This keeps the VM independent of any specific GUI toolkit.
|
||||||
|
|
||||||
|
// Get a property from a control. Returns the value.
|
||||||
|
// ctrlRef is an opaque pointer previously returned by createControl/findControl.
|
||||||
|
typedef BasValueT (*BasUiGetPropFnT)(void *ctx, void *ctrlRef, const char *propName);
|
||||||
|
|
||||||
|
// Set a property on a control.
|
||||||
|
typedef void (*BasUiSetPropFnT)(void *ctx, void *ctrlRef, const char *propName, BasValueT value);
|
||||||
|
|
||||||
|
// Call a method on a control. args[0..argc-1] are the arguments.
|
||||||
|
// Returns the method's return value (or a zero-value for void methods).
|
||||||
|
typedef BasValueT (*BasUiCallMethodFnT)(void *ctx, void *ctrlRef, const char *methodName, BasValueT *args, int32_t argc);
|
||||||
|
|
||||||
|
// Create a control on a form. typeName is the widget type ("Button",
|
||||||
|
// "TextBox", etc.). ctrlName is the VB control name ("Command1").
|
||||||
|
// Returns an opaque control reference.
|
||||||
|
typedef void *(*BasUiCreateCtrlFnT)(void *ctx, void *formRef, const char *typeName, const char *ctrlName);
|
||||||
|
|
||||||
|
// Find an existing control by name on a form. Returns NULL if not found.
|
||||||
|
typedef void *(*BasUiFindCtrlFnT)(void *ctx, void *formRef, const char *ctrlName);
|
||||||
|
|
||||||
|
// Load a form by name. Returns an opaque form reference.
|
||||||
|
typedef void *(*BasUiLoadFormFnT)(void *ctx, const char *formName);
|
||||||
|
|
||||||
|
// Unload a form.
|
||||||
|
typedef void (*BasUiUnloadFormFnT)(void *ctx, void *formRef);
|
||||||
|
|
||||||
|
// Show a form. modal=true for modal display.
|
||||||
|
typedef void (*BasUiShowFormFnT)(void *ctx, void *formRef, bool modal);
|
||||||
|
|
||||||
|
// Hide a form (keep in memory).
|
||||||
|
typedef void (*BasUiHideFormFnT)(void *ctx, void *formRef);
|
||||||
|
|
||||||
|
// Display a message box. Returns the button clicked (1=OK, 6=Yes, 7=No, 2=Cancel).
|
||||||
|
typedef int32_t (*BasUiMsgBoxFnT)(void *ctx, const char *message, int32_t flags);
|
||||||
|
|
||||||
|
// Display an input box. Returns the entered string (empty on cancel).
|
||||||
|
typedef BasStringT *(*BasUiInputBoxFnT)(void *ctx, const char *prompt, const char *title, const char *defaultText);
|
||||||
|
|
||||||
|
// Collected UI callbacks
|
||||||
|
typedef struct {
|
||||||
|
BasUiGetPropFnT getProp;
|
||||||
|
BasUiSetPropFnT setProp;
|
||||||
|
BasUiCallMethodFnT callMethod;
|
||||||
|
BasUiCreateCtrlFnT createCtrl;
|
||||||
|
BasUiFindCtrlFnT findCtrl;
|
||||||
|
BasUiLoadFormFnT loadForm;
|
||||||
|
BasUiUnloadFormFnT unloadForm;
|
||||||
|
BasUiShowFormFnT showForm;
|
||||||
|
BasUiHideFormFnT hideForm;
|
||||||
|
BasUiMsgBoxFnT msgBox;
|
||||||
|
BasUiInputBoxFnT inputBox;
|
||||||
|
void *ctx; // passed as first arg to all callbacks
|
||||||
|
} BasUiCallbacksT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// External library callbacks (host-provided)
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// The VM resolves external functions by library name + function
|
||||||
|
// name at runtime. The host uses dlsym() or equivalent to find
|
||||||
|
// the native function pointer. A second callback marshals the
|
||||||
|
// call -- converting BasValueT arguments to native C types and
|
||||||
|
// the return value back to BasValueT.
|
||||||
|
|
||||||
|
// Resolve a function by library and symbol name.
|
||||||
|
// Returns an opaque function pointer, or NULL if not found.
|
||||||
|
// The VM caches the result so this is called only once per
|
||||||
|
// unique (library, function) pair.
|
||||||
|
typedef void *(*BasResolveExternFnT)(void *ctx, const char *libName, const char *funcName);
|
||||||
|
|
||||||
|
// Call a resolved native function. funcPtr is the pointer returned
|
||||||
|
// by resolveExtern. args[0..argc-1] are the BASIC arguments.
|
||||||
|
// retType is the expected return type (BAS_TYPE_*).
|
||||||
|
// Returns the native function's return value as a BasValueT.
|
||||||
|
typedef BasValueT (*BasCallExternFnT)(void *ctx, void *funcPtr, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
BasResolveExternFnT resolveExtern;
|
||||||
|
BasCallExternFnT callExtern;
|
||||||
|
void *ctx;
|
||||||
|
} BasExternCallbacksT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Extern function cache (resolved on first call)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define BAS_EXTERN_CACHE_SIZE 128
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t libNameIdx; // constant pool index for library name
|
||||||
|
uint16_t funcNameIdx; // constant pool index for function name
|
||||||
|
void *funcPtr; // resolved native function pointer (NULL = not yet resolved)
|
||||||
|
} BasExternCacheEntryT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Call stack frame
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t returnPc; // instruction to return to
|
||||||
|
int32_t baseSlot; // base index in locals array
|
||||||
|
int32_t localCount; // number of locals in this frame
|
||||||
|
BasValueT locals[BAS_VM_MAX_LOCALS];
|
||||||
|
} BasCallFrameT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// FOR loop state
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t varIdx; // loop variable slot index
|
||||||
|
bool isLocal; // true = local, false = global
|
||||||
|
BasValueT limit; // upper bound
|
||||||
|
BasValueT step; // step value
|
||||||
|
int32_t loopTop; // PC of the loop body start
|
||||||
|
} BasForStateT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// File channel
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *handle; // FILE* or platform-specific
|
||||||
|
int32_t mode; // 0=closed, 1=input, 2=output, 3=append, 4=random, 5=binary
|
||||||
|
} 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)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t *code; // p-code bytecode
|
||||||
|
int32_t codeLen;
|
||||||
|
BasStringT **constants; // string constant pool
|
||||||
|
int32_t constCount;
|
||||||
|
int32_t globalCount; // number of global variable slots needed
|
||||||
|
int32_t entryPoint; // PC of the first instruction (module-level code)
|
||||||
|
BasValueT *dataPool; // DATA statement value pool
|
||||||
|
int32_t dataCount; // number of values in the data pool
|
||||||
|
BasProcEntryT *procs; // procedure table (SUBs and FUNCTIONs)
|
||||||
|
int32_t procCount;
|
||||||
|
} BasModuleT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// VM state
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// Program
|
||||||
|
BasModuleT *module;
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
int32_t pc; // program counter
|
||||||
|
bool running;
|
||||||
|
bool yielded;
|
||||||
|
int32_t stepLimit; // max steps per basVmRun (0 = unlimited)
|
||||||
|
int32_t stepCount; // steps executed in last basVmRun
|
||||||
|
|
||||||
|
// Evaluation stack
|
||||||
|
BasValueT stack[BAS_VM_STACK_SIZE];
|
||||||
|
int32_t sp; // stack pointer (index of next free slot)
|
||||||
|
|
||||||
|
// Call stack
|
||||||
|
BasCallFrameT callStack[BAS_VM_CALL_STACK_SIZE];
|
||||||
|
int32_t callDepth;
|
||||||
|
|
||||||
|
// FOR loop stack
|
||||||
|
BasForStateT forStack[BAS_VM_MAX_FOR_DEPTH];
|
||||||
|
int32_t forDepth;
|
||||||
|
|
||||||
|
// Global variables
|
||||||
|
BasValueT globals[BAS_VM_MAX_GLOBALS];
|
||||||
|
|
||||||
|
// File channels (1-based, index 0 unused)
|
||||||
|
BasFileChannelT files[BAS_VM_MAX_FILES];
|
||||||
|
|
||||||
|
// DATA/READ pointer
|
||||||
|
int32_t dataPtr; // current READ position in data pool
|
||||||
|
|
||||||
|
// String comparison mode
|
||||||
|
bool compareTextMode; // true = case-insensitive comparisons
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
int32_t errorHandler; // PC of ON ERROR GOTO handler (0 = none)
|
||||||
|
int32_t errorNumber; // current Err number
|
||||||
|
int32_t errorPc; // PC of the instruction that caused the error (for RESUME)
|
||||||
|
int32_t errorNextPc; // PC of the next instruction after error (for RESUME NEXT)
|
||||||
|
bool inErrorHandler; // true when executing error handler code
|
||||||
|
char errorMsg[256]; // current error description
|
||||||
|
|
||||||
|
// I/O callbacks
|
||||||
|
BasPrintFnT printFn;
|
||||||
|
void *printCtx;
|
||||||
|
BasInputFnT inputFn;
|
||||||
|
void *inputCtx;
|
||||||
|
BasDoEventsFnT doEventsFn;
|
||||||
|
void *doEventsCtx;
|
||||||
|
|
||||||
|
// UI callbacks (set by host for form/control support)
|
||||||
|
BasUiCallbacksT ui;
|
||||||
|
|
||||||
|
// External library callbacks (set by host)
|
||||||
|
BasExternCallbacksT ext;
|
||||||
|
|
||||||
|
// Extern function cache (resolved on first call)
|
||||||
|
BasExternCacheEntryT externCache[BAS_EXTERN_CACHE_SIZE];
|
||||||
|
int32_t externCacheCount;
|
||||||
|
|
||||||
|
// Current form reference (set during event dispatch)
|
||||||
|
void *currentForm;
|
||||||
|
} BasVmT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// API
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Create a new VM instance.
|
||||||
|
BasVmT *basVmCreate(void);
|
||||||
|
|
||||||
|
// Destroy a VM instance and free all resources.
|
||||||
|
void basVmDestroy(BasVmT *vm);
|
||||||
|
|
||||||
|
// Load a compiled module into the VM.
|
||||||
|
void basVmLoadModule(BasVmT *vm, BasModuleT *module);
|
||||||
|
|
||||||
|
// Execute the loaded module. Returns when the program ends,
|
||||||
|
// halts, yields, or hits an error.
|
||||||
|
BasVmResultE basVmRun(BasVmT *vm);
|
||||||
|
|
||||||
|
// Execute a single instruction. Returns the result.
|
||||||
|
// Useful for stepping/debugging.
|
||||||
|
BasVmResultE basVmStep(BasVmT *vm);
|
||||||
|
|
||||||
|
// Reset the VM to initial state (clear stack, globals, PC).
|
||||||
|
void basVmReset(BasVmT *vm);
|
||||||
|
|
||||||
|
// Set I/O callbacks.
|
||||||
|
void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx);
|
||||||
|
void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx);
|
||||||
|
void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx);
|
||||||
|
|
||||||
|
// Set UI callbacks (for form/control system).
|
||||||
|
void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui);
|
||||||
|
|
||||||
|
// Set external library callbacks (for DECLARE LIBRARY support).
|
||||||
|
void basVmSetExternCallbacks(BasVmT *vm, const BasExternCallbacksT *ext);
|
||||||
|
|
||||||
|
// Set the current form context (called by host during event dispatch).
|
||||||
|
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).
|
||||||
|
bool basVmPush(BasVmT *vm, BasValueT val);
|
||||||
|
bool basVmPop(BasVmT *vm, BasValueT *val);
|
||||||
|
|
||||||
|
// Get the current error message.
|
||||||
|
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
|
||||||
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."
|
||||||
27
dvxbasic/samples/hello.bas
Normal file
27
dvxbasic/samples/hello.bas
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
' hello.bas -- DVX BASIC Hello World
|
||||||
|
'
|
||||||
|
' A simple program to test the DVX BASIC runner.
|
||||||
|
|
||||||
|
PRINT "Hello from DVX BASIC!"
|
||||||
|
PRINT
|
||||||
|
PRINT "Testing arithmetic: 2 + 3 * 4 ="; 2 + 3 * 4
|
||||||
|
PRINT "Testing strings: "; "Hello" & " " & "World"
|
||||||
|
PRINT
|
||||||
|
|
||||||
|
DIM i AS INTEGER
|
||||||
|
PRINT "Fibonacci sequence:"
|
||||||
|
DIM a AS INTEGER
|
||||||
|
DIM b AS INTEGER
|
||||||
|
DIM temp AS INTEGER
|
||||||
|
a = 0
|
||||||
|
b = 1
|
||||||
|
FOR i = 1 TO 15
|
||||||
|
PRINT a;
|
||||||
|
temp = a + b
|
||||||
|
a = b
|
||||||
|
b = temp
|
||||||
|
NEXT i
|
||||||
|
PRINT
|
||||||
|
|
||||||
|
PRINT
|
||||||
|
PRINT "Done!"
|
||||||
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!"
|
||||||
1643
dvxbasic/test_compiler.c
Normal file
1643
dvxbasic/test_compiler.c
Normal file
File diff suppressed because it is too large
Load diff
25
dvxbasic/test_lex.c
Normal file
25
dvxbasic/test_lex.c
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// test_lex.c -- Dump lexer tokens
|
||||||
|
//
|
||||||
|
// Build: make -C dvxbasic tests
|
||||||
|
|
||||||
|
#include "compiler/lexer.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
const char *src = "PRINT \"Hello, World!\"\n";
|
||||||
|
BasLexerT lex;
|
||||||
|
basLexerInit(&lex, src, (int32_t)strlen(src));
|
||||||
|
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
printf("Token %d: type=%d (%s) text='%s'\n", i, lex.token.type, basTokenName(lex.token.type), lex.token.text);
|
||||||
|
|
||||||
|
if (lex.token.type == TOK_EOF) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
basLexerNext(&lex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
65
dvxbasic/test_quick.c
Normal file
65
dvxbasic/test_quick.c
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
// test_quick.c -- Quick single-program test
|
||||||
|
//
|
||||||
|
// Build: make -C dvxbasic tests
|
||||||
|
|
||||||
|
#include "compiler/parser.h"
|
||||||
|
#include "runtime/vm.h"
|
||||||
|
#include "runtime/values.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
basStringSystemInit();
|
||||||
|
|
||||||
|
const char *source = "PRINT \"Hello, World!\"\n";
|
||||||
|
printf("Source: [%s]\n", source);
|
||||||
|
printf("Source len: %d\n", (int)strlen(source));
|
||||||
|
|
||||||
|
int32_t len = (int32_t)strlen(source);
|
||||||
|
BasParserT parser;
|
||||||
|
basParserInit(&parser, source, len);
|
||||||
|
|
||||||
|
if (!basParse(&parser)) {
|
||||||
|
printf("COMPILE ERROR: %s\n", parser.error);
|
||||||
|
basParserFree(&parser);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Compiled OK (%d bytes of p-code)\n", parser.cg.codeLen);
|
||||||
|
|
||||||
|
// Dump p-code
|
||||||
|
for (int i = 0; i < parser.cg.codeLen; i++) {
|
||||||
|
printf("%02X ", parser.cg.code[i]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
BasModuleT *mod = basParserBuildModule(&parser);
|
||||||
|
basParserFree(&parser);
|
||||||
|
|
||||||
|
BasVmT *vm = basVmCreate();
|
||||||
|
basVmLoadModule(vm, mod);
|
||||||
|
vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount;
|
||||||
|
vm->callDepth = 1;
|
||||||
|
|
||||||
|
// Step limit
|
||||||
|
int steps = 0;
|
||||||
|
vm->running = true;
|
||||||
|
|
||||||
|
while (vm->running && steps < 1000) {
|
||||||
|
BasVmResultE r = basVmStep(vm);
|
||||||
|
steps++;
|
||||||
|
|
||||||
|
if (r != BAS_VM_OK) {
|
||||||
|
printf("[Result: %d after %d steps: %s]\n", r, steps, basVmGetError(vm));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (steps >= 1000) {
|
||||||
|
printf("[TIMEOUT after %d steps, PC=%d]\n", steps, vm->pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
basVmDestroy(vm);
|
||||||
|
basModuleFree(mod);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
233
dvxbasic/test_vm.c
Normal file
233
dvxbasic/test_vm.c
Normal file
|
|
@ -0,0 +1,233 @@
|
||||||
|
// test_vm.c -- Quick test for the DVX BASIC VM
|
||||||
|
//
|
||||||
|
// Hand-assembles a small p-code program and executes it.
|
||||||
|
// Tests: PRINT "Hello, World!", arithmetic, FOR loop, string ops.
|
||||||
|
//
|
||||||
|
// Build: make -C dvxbasic tests
|
||||||
|
|
||||||
|
#include "compiler/opcodes.h"
|
||||||
|
#include "runtime/vm.h"
|
||||||
|
#include "runtime/values.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Helper: emit bytes into a code buffer
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static uint8_t sCode[4096];
|
||||||
|
static int32_t sCodeLen = 0;
|
||||||
|
|
||||||
|
static void emit8(uint8_t b) {
|
||||||
|
sCode[sCodeLen++] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void emit16(int16_t v) {
|
||||||
|
memcpy(&sCode[sCodeLen], &v, 2);
|
||||||
|
sCodeLen += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void emitU16(uint16_t v) {
|
||||||
|
memcpy(&sCode[sCodeLen], &v, 2);
|
||||||
|
sCodeLen += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Test 1: PRINT "Hello, World!"
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void test1(void) {
|
||||||
|
printf("--- Test 1: PRINT \"Hello, World!\" ---\n");
|
||||||
|
|
||||||
|
sCodeLen = 0;
|
||||||
|
|
||||||
|
// String constant pool
|
||||||
|
BasStringT *consts[1];
|
||||||
|
consts[0] = basStringNew("Hello, World!", 13);
|
||||||
|
|
||||||
|
// Code: PUSH_STR 0; PRINT; PRINT_NL; HALT
|
||||||
|
emit8(OP_PUSH_STR);
|
||||||
|
emitU16(0);
|
||||||
|
emit8(OP_PRINT);
|
||||||
|
emit8(OP_PRINT_NL);
|
||||||
|
emit8(OP_HALT);
|
||||||
|
|
||||||
|
BasModuleT module;
|
||||||
|
memset(&module, 0, sizeof(module));
|
||||||
|
module.code = sCode;
|
||||||
|
module.codeLen = sCodeLen;
|
||||||
|
module.constants = consts;
|
||||||
|
module.constCount = 1;
|
||||||
|
module.entryPoint = 0;
|
||||||
|
|
||||||
|
BasVmT *vm = basVmCreate();
|
||||||
|
basVmLoadModule(vm, &module);
|
||||||
|
BasVmResultE result = basVmRun(vm);
|
||||||
|
printf("Result: %d (expected %d = HALTED)\n\n", result, BAS_VM_HALTED);
|
||||||
|
basVmDestroy(vm);
|
||||||
|
basStringUnref(consts[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Test 2: Arithmetic: PRINT 2 + 3 * 4
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void test2(void) {
|
||||||
|
printf("--- Test 2: PRINT 2 + 3 * 4 (expect 14) ---\n");
|
||||||
|
|
||||||
|
sCodeLen = 0;
|
||||||
|
|
||||||
|
// Code: PUSH 3; PUSH 4; MUL; PUSH 2; ADD; PRINT; PRINT_NL; HALT
|
||||||
|
emit8(OP_PUSH_INT16);
|
||||||
|
emit16(3);
|
||||||
|
emit8(OP_PUSH_INT16);
|
||||||
|
emit16(4);
|
||||||
|
emit8(OP_MUL_INT);
|
||||||
|
emit8(OP_PUSH_INT16);
|
||||||
|
emit16(2);
|
||||||
|
emit8(OP_ADD_INT);
|
||||||
|
emit8(OP_PRINT);
|
||||||
|
emit8(OP_PRINT_NL);
|
||||||
|
emit8(OP_HALT);
|
||||||
|
|
||||||
|
BasModuleT module;
|
||||||
|
memset(&module, 0, sizeof(module));
|
||||||
|
module.code = sCode;
|
||||||
|
module.codeLen = sCodeLen;
|
||||||
|
module.entryPoint = 0;
|
||||||
|
|
||||||
|
BasVmT *vm = basVmCreate();
|
||||||
|
basVmLoadModule(vm, &module);
|
||||||
|
basVmRun(vm);
|
||||||
|
basVmDestroy(vm);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Test 3: String concatenation
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void test3(void) {
|
||||||
|
printf("--- Test 3: PRINT \"Hello\" & \" \" & \"BASIC\" ---\n");
|
||||||
|
|
||||||
|
sCodeLen = 0;
|
||||||
|
|
||||||
|
BasStringT *consts[3];
|
||||||
|
consts[0] = basStringNew("Hello", 5);
|
||||||
|
consts[1] = basStringNew(" ", 1);
|
||||||
|
consts[2] = basStringNew("BASIC", 5);
|
||||||
|
|
||||||
|
// Code: PUSH consts[0]; PUSH consts[1]; CONCAT; PUSH consts[2]; CONCAT; PRINT; PRINT_NL; HALT
|
||||||
|
emit8(OP_PUSH_STR); emitU16(0);
|
||||||
|
emit8(OP_PUSH_STR); emitU16(1);
|
||||||
|
emit8(OP_STR_CONCAT);
|
||||||
|
emit8(OP_PUSH_STR); emitU16(2);
|
||||||
|
emit8(OP_STR_CONCAT);
|
||||||
|
emit8(OP_PRINT);
|
||||||
|
emit8(OP_PRINT_NL);
|
||||||
|
emit8(OP_HALT);
|
||||||
|
|
||||||
|
BasModuleT module;
|
||||||
|
memset(&module, 0, sizeof(module));
|
||||||
|
module.code = sCode;
|
||||||
|
module.codeLen = sCodeLen;
|
||||||
|
module.constants = consts;
|
||||||
|
module.constCount = 3;
|
||||||
|
module.entryPoint = 0;
|
||||||
|
|
||||||
|
BasVmT *vm = basVmCreate();
|
||||||
|
basVmLoadModule(vm, &module);
|
||||||
|
basVmRun(vm);
|
||||||
|
basVmDestroy(vm);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
basStringUnref(consts[0]);
|
||||||
|
basStringUnref(consts[1]);
|
||||||
|
basStringUnref(consts[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Test 4: FOR loop -- PRINT 1 to 5
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void test4(void) {
|
||||||
|
printf("--- Test 4: FOR i = 1 TO 5: PRINT i: NEXT ---\n");
|
||||||
|
|
||||||
|
sCodeLen = 0;
|
||||||
|
|
||||||
|
// We need a call frame with at least 1 local (the loop variable)
|
||||||
|
// For module-level code, we use callStack[0] as implicit frame
|
||||||
|
|
||||||
|
// Setup: store initial value in local 0
|
||||||
|
// PUSH 1; STORE_LOCAL 0 -- i = 1
|
||||||
|
emit8(OP_PUSH_INT16); emit16(1);
|
||||||
|
emit8(OP_STORE_LOCAL); emitU16(0);
|
||||||
|
|
||||||
|
// Push limit and step for FOR_INIT
|
||||||
|
// PUSH 5 (limit); PUSH 1 (step)
|
||||||
|
emit8(OP_PUSH_INT16); emit16(5);
|
||||||
|
emit8(OP_PUSH_INT16); emit16(1);
|
||||||
|
emit8(OP_FOR_INIT); emitU16(0); emit8(1); // isLocal=1
|
||||||
|
|
||||||
|
// Loop body start (record PC for FOR_NEXT offset)
|
||||||
|
int32_t loopBody = sCodeLen;
|
||||||
|
|
||||||
|
// LOAD_LOCAL 0; PRINT; PRINT " "
|
||||||
|
emit8(OP_LOAD_LOCAL); emitU16(0);
|
||||||
|
emit8(OP_PRINT);
|
||||||
|
|
||||||
|
// FOR_NEXT: increment i, test, jump back
|
||||||
|
emit8(OP_FOR_NEXT);
|
||||||
|
emitU16(0); // local index
|
||||||
|
emit8(1); // isLocal=1
|
||||||
|
int16_t offset = (int16_t)(loopBody - (sCodeLen + 2));
|
||||||
|
emit16(offset);
|
||||||
|
|
||||||
|
// After loop
|
||||||
|
emit8(OP_PRINT_NL);
|
||||||
|
emit8(OP_HALT);
|
||||||
|
|
||||||
|
BasModuleT module;
|
||||||
|
memset(&module, 0, sizeof(module));
|
||||||
|
module.code = sCode;
|
||||||
|
module.codeLen = sCodeLen;
|
||||||
|
module.entryPoint = 0;
|
||||||
|
|
||||||
|
BasVmT *vm = basVmCreate();
|
||||||
|
|
||||||
|
// Initialize the implicit main frame with 1 local
|
||||||
|
vm->callStack[0].localCount = 1;
|
||||||
|
vm->callDepth = 1;
|
||||||
|
|
||||||
|
basVmLoadModule(vm, &module);
|
||||||
|
basVmRun(vm);
|
||||||
|
basVmDestroy(vm);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// main
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
printf("DVX BASIC VM Tests\n");
|
||||||
|
printf("==================\n\n");
|
||||||
|
|
||||||
|
basStringSystemInit();
|
||||||
|
|
||||||
|
test1();
|
||||||
|
test2();
|
||||||
|
test3();
|
||||||
|
test4();
|
||||||
|
|
||||||
|
printf("All tests complete.\n");
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,57 @@ static void iconImgview(void) {
|
||||||
rect(5, 22, 22, 3, 60, 120, 60);
|
rect(5, 22, 22, 3, 60, 120, 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DVX BASIC icon: code brackets with "B" letterform
|
||||||
|
static void iconBasic(void) {
|
||||||
|
clear(192, 192, 192);
|
||||||
|
|
||||||
|
// Blue background rectangle (code editor feel)
|
||||||
|
rect(2, 2, 28, 28, 0, 0, 128);
|
||||||
|
|
||||||
|
// Left bracket {
|
||||||
|
vline(6, 6, 20, 255, 255, 0);
|
||||||
|
hline(7, 6, 3, 255, 255, 0);
|
||||||
|
hline(7, 25, 3, 255, 255, 0);
|
||||||
|
hline(4, 15, 2, 255, 255, 0);
|
||||||
|
pixel(5, 14, 255, 255, 0);
|
||||||
|
pixel(5, 16, 255, 255, 0);
|
||||||
|
|
||||||
|
// "B" letter in white (bold, centered)
|
||||||
|
// Vertical stroke
|
||||||
|
vline(14, 7, 18, 255, 255, 255);
|
||||||
|
|
||||||
|
// Top horizontal
|
||||||
|
hline(14, 7, 7, 255, 255, 255);
|
||||||
|
hline(14, 8, 7, 255, 255, 255);
|
||||||
|
|
||||||
|
// Middle horizontal
|
||||||
|
hline(14, 15, 7, 255, 255, 255);
|
||||||
|
hline(14, 16, 7, 255, 255, 255);
|
||||||
|
|
||||||
|
// Bottom horizontal
|
||||||
|
hline(14, 23, 7, 255, 255, 255);
|
||||||
|
hline(14, 24, 7, 255, 255, 255);
|
||||||
|
|
||||||
|
// Right curves of B (top bump)
|
||||||
|
vline(21, 8, 7, 255, 255, 255);
|
||||||
|
pixel(20, 8, 255, 255, 255);
|
||||||
|
pixel(20, 14, 255, 255, 255);
|
||||||
|
|
||||||
|
// Right curves of B (bottom bump)
|
||||||
|
vline(21, 17, 6, 255, 255, 255);
|
||||||
|
pixel(20, 17, 255, 255, 255);
|
||||||
|
pixel(20, 23, 255, 255, 255);
|
||||||
|
|
||||||
|
// Right bracket }
|
||||||
|
vline(25, 6, 20, 255, 255, 0);
|
||||||
|
hline(22, 6, 3, 255, 255, 0);
|
||||||
|
hline(22, 25, 3, 255, 255, 0);
|
||||||
|
hline(26, 15, 2, 255, 255, 0);
|
||||||
|
pixel(26, 14, 255, 255, 0);
|
||||||
|
pixel(26, 16, 255, 255, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void writeBmp(const char *path) {
|
static void writeBmp(const char *path) {
|
||||||
int32_t rowPad = (4 - (W * 3) % 4) % 4;
|
int32_t rowPad = (4 - (W * 3) % 4) % 4;
|
||||||
int32_t rowSize = W * 3 + rowPad;
|
int32_t rowSize = W * 3 + rowPad;
|
||||||
|
|
@ -243,7 +294,7 @@ static void writeBmp(const char *path) {
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
fprintf(stderr, "Usage: mkicon <output.bmp> <type>\n");
|
fprintf(stderr, "Usage: mkicon <output.bmp> <type>\n");
|
||||||
fprintf(stderr, "Types: clock, notepad, cpanel, dvxdemo, imgview\n");
|
fprintf(stderr, "Types: clock, notepad, cpanel, dvxdemo, imgview, basic\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -258,6 +309,8 @@ int main(int argc, char **argv) {
|
||||||
iconCpanel();
|
iconCpanel();
|
||||||
} else if (strcmp(type, "dvxdemo") == 0) {
|
} else if (strcmp(type, "dvxdemo") == 0) {
|
||||||
iconDvxdemo();
|
iconDvxdemo();
|
||||||
|
} else if (strcmp(type, "basic") == 0) {
|
||||||
|
iconBasic();
|
||||||
} else if (strcmp(type, "imgview") == 0) {
|
} else if (strcmp(type, "imgview") == 0) {
|
||||||
iconImgview();
|
iconImgview();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -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 textY = w->y + TEXTAREA_BORDER;
|
int32_t textX = gutterX + gutterW;
|
||||||
|
int32_t textY = w->y + TEXTAREA_BORDER;
|
||||||
int32_t lineOff = textAreaLineStart(buf, len, ta->scrollRow);
|
int32_t lineOff = textAreaLineStart(buf, len, ta->scrollRow);
|
||||||
|
|
||||||
|
// Draw gutter background
|
||||||
|
if (gutterW > 0) {
|
||||||
|
rectFill(d, ops, gutterX, textY, gutterW, innerH, colors->windowFace);
|
||||||
|
drawVLine(d, ops, gutterX + gutterW - 1, textY, innerH, colors->windowShadow);
|
||||||
|
}
|
||||||
|
|
||||||
for (int32_t i = 0; i < visRows; i++) {
|
for (int32_t i = 0; i < visRows; i++) {
|
||||||
int32_t row = ta->scrollRow + i;
|
int32_t row = ta->scrollRow + i;
|
||||||
|
|
||||||
|
|
@ -1623,6 +1736,14 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
}
|
}
|
||||||
int32_t drawY = textY + i * font->charHeight;
|
int32_t drawY = textY + i * font->charHeight;
|
||||||
|
|
||||||
|
// Draw line number in gutter
|
||||||
|
if (gutterW > 0) {
|
||||||
|
char numBuf[12];
|
||||||
|
int32_t numLen = snprintf(numBuf, sizeof(numBuf), "%d", (int)(row + 1));
|
||||||
|
int32_t numX = gutterX + gutterW - (numLen + 1) * font->charWidth;
|
||||||
|
drawTextN(d, ops, font, numX, drawY, numBuf, numLen, colors->windowShadow, colors->windowFace, true);
|
||||||
|
}
|
||||||
|
|
||||||
// Visible range within line
|
// Visible range within line
|
||||||
int32_t scrollCol = ta->scrollCol;
|
int32_t scrollCol = ta->scrollCol;
|
||||||
int32_t visStart = scrollCol;
|
int32_t visStart = scrollCol;
|
||||||
|
|
@ -1633,22 +1754,30 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
int32_t drawStart = visStart < textEnd ? visStart : textEnd;
|
int32_t drawStart = visStart < textEnd ? visStart : textEnd;
|
||||||
int32_t drawEnd = visEnd < textEnd ? visEnd : textEnd;
|
int32_t drawEnd = visEnd < textEnd ? visEnd : textEnd;
|
||||||
|
|
||||||
|
// Compute syntax colors for this line if colorizer is set
|
||||||
|
uint8_t syntaxBuf[MAX_COLORIZE_LEN];
|
||||||
|
bool hasSyntax = false;
|
||||||
|
|
||||||
|
if (ta->colorize && lineL > 0) {
|
||||||
|
int32_t colorLen = lineL < MAX_COLORIZE_LEN ? lineL : MAX_COLORIZE_LEN;
|
||||||
|
memset(syntaxBuf, 0, colorLen);
|
||||||
|
ta->colorize(buf + lineOff, colorLen, syntaxBuf, ta->colorizeCtx);
|
||||||
|
hasSyntax = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Determine selection intersection with this line
|
// Determine selection intersection with this line
|
||||||
int32_t lineSelLo = -1;
|
int32_t lineSelLo = -1;
|
||||||
int32_t lineSelHi = -1;
|
int32_t lineSelHi = -1;
|
||||||
|
|
||||||
if (selLo >= 0) {
|
if (selLo >= 0) {
|
||||||
// Selection range in column-space for this line
|
|
||||||
if (selLo < lineOff + lineL + 1 && selHi > lineOff) {
|
if (selLo < lineOff + lineL + 1 && selHi > lineOff) {
|
||||||
lineSelLo = selLo - lineOff;
|
lineSelLo = selLo - lineOff;
|
||||||
lineSelHi = selHi - lineOff;
|
lineSelHi = selHi - lineOff;
|
||||||
if (lineSelLo < 0) { lineSelLo = 0; }
|
if (lineSelLo < 0) { lineSelLo = 0; }
|
||||||
// selHi can extend past line (newline selected)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lineSelLo >= 0 && lineSelLo < lineSelHi) {
|
if (lineSelLo >= 0 && lineSelLo < lineSelHi) {
|
||||||
// Clamp selection to visible columns for text runs
|
|
||||||
int32_t vSelLo = lineSelLo < drawStart ? drawStart : lineSelLo;
|
int32_t vSelLo = lineSelLo < drawStart ? drawStart : lineSelLo;
|
||||||
int32_t vSelHi = lineSelHi < drawEnd ? lineSelHi : drawEnd;
|
int32_t vSelHi = lineSelHi < drawEnd ? lineSelHi : drawEnd;
|
||||||
|
|
||||||
|
|
@ -1656,17 +1785,25 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
|
|
||||||
// Before selection
|
// Before selection
|
||||||
if (drawStart < vSelLo) {
|
if (drawStart < vSelLo) {
|
||||||
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, vSelLo - drawStart, fg, bg, true);
|
if (hasSyntax) {
|
||||||
|
drawColorizedText(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, vSelLo - drawStart, syntaxBuf, drawStart, fg, bg, colors);
|
||||||
|
} else {
|
||||||
|
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, vSelLo - drawStart, fg, bg, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selection (text portion)
|
// Selection (always uses highlight colors, no syntax coloring)
|
||||||
if (vSelLo < vSelHi) {
|
if (vSelLo < vSelHi) {
|
||||||
drawTextN(d, ops, font, textX + (vSelLo - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelLo, vSelHi - vSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
|
drawTextN(d, ops, font, textX + (vSelLo - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelLo, vSelHi - vSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// After selection
|
// After selection
|
||||||
if (vSelHi < drawEnd) {
|
if (vSelHi < drawEnd) {
|
||||||
drawTextN(d, ops, font, textX + (vSelHi - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelHi, drawEnd - vSelHi, fg, bg, true);
|
if (hasSyntax) {
|
||||||
|
drawColorizedText(d, ops, font, textX + (vSelHi - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelHi, drawEnd - vSelHi, syntaxBuf, vSelHi, fg, bg, colors);
|
||||||
|
} else {
|
||||||
|
drawTextN(d, ops, font, textX + (vSelHi - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelHi, drawEnd - vSelHi, fg, bg, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Past end of text: fill selected area with highlight bg
|
// Past end of text: fill selected area with highlight bg
|
||||||
|
|
@ -1684,9 +1821,13 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No selection on this line -- single run
|
// No selection on this line
|
||||||
if (drawStart < drawEnd) {
|
if (drawStart < drawEnd) {
|
||||||
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, fg, bg, true);
|
if (hasSyntax) {
|
||||||
|
drawColorizedText(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, syntaxBuf, drawStart, fg, bg, colors);
|
||||||
|
} else {
|
||||||
|
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, fg, bg, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2157,9 +2298,10 @@ static void widgetTextAreaDragSelect(WidgetT *w, WidgetT *root, int32_t vx, int3
|
||||||
AppContextT *ctx = wgtGetContext(w);
|
AppContextT *ctx = wgtGetContext(w);
|
||||||
const BitmapFontT *font = &ctx->font;
|
const BitmapFontT *font = &ctx->font;
|
||||||
|
|
||||||
int32_t innerX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
|
int32_t gutterW = textAreaGutterWidth(w, font);
|
||||||
int32_t innerY = w->y + TEXTAREA_BORDER;
|
int32_t innerX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD + gutterW;
|
||||||
int32_t relX = vx - innerX;
|
int32_t innerY = w->y + TEXTAREA_BORDER;
|
||||||
|
int32_t relX = vx - innerX;
|
||||||
int32_t relY = vy - innerY;
|
int32_t relY = vy - innerY;
|
||||||
|
|
||||||
int32_t totalLines = textAreaGetLineCount(w);
|
int32_t totalLines = textAreaGetLineCount(w);
|
||||||
|
|
@ -2343,6 +2485,86 @@ WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtTextAreaSetColorize
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtTextAreaGoToLine(WidgetT *w, int32_t line) {
|
||||||
|
if (!w || w->type != sTextAreaTypeId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||||
|
int32_t totalLines = textAreaGetLineCount(w);
|
||||||
|
int32_t row = line - 1; // 1-based to 0-based
|
||||||
|
|
||||||
|
if (row < 0) {
|
||||||
|
row = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row >= totalLines) {
|
||||||
|
row = totalLines - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ta->cursorRow = row;
|
||||||
|
ta->cursorCol = 0;
|
||||||
|
ta->desiredCol = 0;
|
||||||
|
|
||||||
|
// Select the entire line for visual highlight
|
||||||
|
int32_t lineStart = textAreaLineStart(ta->buf, ta->len, row);
|
||||||
|
int32_t lineL = textAreaLineLen(ta->buf, ta->len, row);
|
||||||
|
ta->selAnchor = lineStart;
|
||||||
|
ta->selCursor = lineStart + lineL;
|
||||||
|
|
||||||
|
// Scroll into view
|
||||||
|
AppContextT *ctx = wgtGetContext(w);
|
||||||
|
const BitmapFontT *font = &ctx->font;
|
||||||
|
int32_t gutterW = textAreaGutterWidth(w, font);
|
||||||
|
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W - gutterW;
|
||||||
|
int32_t visCols = innerW / font->charWidth;
|
||||||
|
int32_t innerH = w->h - TEXTAREA_BORDER * 2;
|
||||||
|
int32_t visRows = innerH / font->charHeight;
|
||||||
|
|
||||||
|
if (visCols < 1) { visCols = 1; }
|
||||||
|
if (visRows < 1) { visRows = 1; }
|
||||||
|
|
||||||
|
textAreaEnsureVisible(w, visRows, visCols);
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void wgtTextAreaSetAutoIndent(WidgetT *w, bool enable) {
|
||||||
|
if (!w || w->type != sTextAreaTypeId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||||
|
ta->autoIndent = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void wgtTextAreaSetColorize(WidgetT *w, void (*fn)(const char *, int32_t, uint8_t *, void *), void *ctx) {
|
||||||
|
if (!w || w->type != sTextAreaTypeId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||||
|
ta->colorize = fn;
|
||||||
|
ta->colorizeCtx = ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void wgtTextAreaSetShowLineNumbers(WidgetT *w, bool show) {
|
||||||
|
if (!w || w->type != sTextAreaTypeId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||||
|
ta->showLineNumbers = show;
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// DXE registration
|
// DXE registration
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -2353,15 +2575,34 @@ static const struct {
|
||||||
WidgetT *(*password)(WidgetT *parent, int32_t maxLen);
|
WidgetT *(*password)(WidgetT *parent, int32_t maxLen);
|
||||||
WidgetT *(*masked)(WidgetT *parent, const char *mask);
|
WidgetT *(*masked)(WidgetT *parent, const char *mask);
|
||||||
WidgetT *(*textArea)(WidgetT *parent, int32_t maxLen);
|
WidgetT *(*textArea)(WidgetT *parent, int32_t maxLen);
|
||||||
|
void (*setColorize)(WidgetT *w, void (*fn)(const char *, int32_t, uint8_t *, void *), void *ctx);
|
||||||
|
void (*goToLine)(WidgetT *w, int32_t line);
|
||||||
|
void (*setAutoIndent)(WidgetT *w, bool enable);
|
||||||
|
void (*setShowLineNumbers)(WidgetT *w, bool show);
|
||||||
} sApi = {
|
} sApi = {
|
||||||
.create = wgtTextInput,
|
.create = wgtTextInput,
|
||||||
.password = wgtPasswordInput,
|
.password = wgtPasswordInput,
|
||||||
.masked = wgtMaskedInput,
|
.masked = wgtMaskedInput,
|
||||||
.textArea = wgtTextArea
|
.textArea = wgtTextArea,
|
||||||
|
.setColorize = wgtTextAreaSetColorize,
|
||||||
|
.goToLine = wgtTextAreaGoToLine,
|
||||||
|
.setAutoIndent = wgtTextAreaSetAutoIndent,
|
||||||
|
.setShowLineNumbers = wgtTextAreaSetShowLineNumbers
|
||||||
|
};
|
||||||
|
|
||||||
|
static const WgtIfaceT sIface = {
|
||||||
|
.basName = "TextBox",
|
||||||
|
.props = NULL,
|
||||||
|
.propCount = 0,
|
||||||
|
.methods = NULL,
|
||||||
|
.methodCount = 0,
|
||||||
|
.events = NULL,
|
||||||
|
.eventCount = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
void wgtRegister(void) {
|
void wgtRegister(void) {
|
||||||
sTextInputTypeId = wgtRegisterClass(&sClassTextInput);
|
sTextInputTypeId = wgtRegisterClass(&sClassTextInput);
|
||||||
sTextAreaTypeId = wgtRegisterClass(&sClassTextArea);
|
sTextAreaTypeId = wgtRegisterClass(&sClassTextArea);
|
||||||
wgtRegisterApi("textinput", &sApi);
|
wgtRegisterApi("textinput", &sApi);
|
||||||
|
wgtRegisterIface("textinput", &sIface);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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