Compare commits

...

5 commits

74 changed files with 18717 additions and 75 deletions

4
.gitignore vendored
View file

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

View file

@ -3,9 +3,9 @@
# Builds the full DVX stack: core library, task switcher,
# 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:
$(MAKE) -C core
@ -34,6 +34,9 @@ taskmgr: shell
serial: core tasks
$(MAKE) -C serial
dvxbasic: core tasks shell tools
$(MAKE) -C dvxbasic
tools:
$(MAKE) -C tools
@ -50,6 +53,7 @@ clean:
$(MAKE) -C shell clean
$(MAKE) -C taskmgr clean
$(MAKE) -C serial clean
$(MAKE) -C dvxbasic clean
$(MAKE) -C apps clean
$(MAKE) -C tools clean
-rmdir obj 2>/dev/null

View file

@ -157,7 +157,7 @@ static void updateTime(void) {
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);
sState.lastUpdate = now;
}

View file

@ -77,7 +77,7 @@ AppDescriptorT appDescriptor = {
typedef struct {
char name[64];
char path[260];
char path[280];
} FileEntryT;
// ============================================================
@ -119,7 +119,7 @@ static WidgetT *sColorSwatch = NULL;
static WidgetT *sWallpaperLbl = NULL;
static WidgetT *sWpaperList = NULL;
static WidgetT *sWpModeDrop = NULL;
static char sWallpaperPath[260];
static char sWallpaperPath[280];
static FileEntryT *sWpaperEntries = 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};
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);
arrput(sWpaperEntries, entry);
}

View file

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

2
config/basrt.dep Normal file
View file

@ -0,0 +1,2 @@
libtasks
libdvx

View file

@ -148,7 +148,7 @@ static void refreshMinimizedIcons(AppContextT *ctx);
static void repositionWindow(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int32_t w, int32_t h);
static void updateCursorShape(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.
// 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
// ============================================================
@ -4303,9 +4288,9 @@ bool dvxLoadTheme(AppContextT *ctx, const char *filename) {
val++;
}
int32_t r;
int32_t g;
int32_t b;
int r;
int g;
int b;
if (sscanf(val, "%d,%d,%d", &r, &g, &b) != 3) {
continue;
@ -4566,9 +4551,9 @@ bool dvxSetWallpaper(AppContextT *ctx, const char *path) {
dvxSetBusy(ctx, true);
int32_t imgW;
int32_t imgH;
int32_t channels;
int imgW;
int imgH;
int channels;
uint8_t *rgb = stbi_load(path, &imgW, &imgH, &channels, 3);
if (!rgb) {

View file

@ -72,6 +72,9 @@
static void drawIconGlyph(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t iconType, uint32_t color);
static 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 bool fdFilterMatch(const char *name, const char *pattern);
static void fdFreeEntries(void);
@ -115,6 +118,25 @@ typedef struct {
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
@ -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
// ============================================================

View file

@ -77,4 +77,10 @@ typedef struct {
// 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);
// 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

View file

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

View file

@ -530,4 +530,74 @@ void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT
void wgtRegisterApi(const char *name, const void *api);
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

View file

@ -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 unsigned short __dj_ctype_flags[];
extern unsigned char __dj_ctype_tolower[];
extern unsigned char __dj_ctype_toupper[];
// GCC emulated thread-local storage
extern void *__emutls_get_address(void *);
@ -2424,6 +2425,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
DXE_EXPORT(strcspn)
{ "_strdup", (void *)dvxStrdup },
DXE_EXPORT(strerror)
DXE_EXPORT(strnicmp)
DXE_EXPORT(stricmp)
DXE_EXPORT(strlen)
DXE_EXPORT(strncasecmp)
@ -2439,6 +2441,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
// --- ctype ---
DXE_EXPORT(isalnum)
DXE_EXPORT(isalpha)
DXE_EXPORT(isascii)
DXE_EXPORT(isdigit)
DXE_EXPORT(islower)
DXE_EXPORT(isprint)
@ -2451,6 +2454,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
// --- conversion ---
DXE_EXPORT(abs)
DXE_EXPORT(ldiv)
DXE_EXPORT(atof)
DXE_EXPORT(atoi)
DXE_EXPORT(atol)
@ -2461,6 +2465,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
// --- formatted I/O ---
DXE_EXPORT(fprintf)
DXE_EXPORT(perror)
DXE_EXPORT(fputs)
DXE_EXPORT(fscanf)
DXE_EXPORT(printf)
@ -2521,13 +2526,19 @@ DXE_EXPORT_TABLE(sDxeExportTable)
DXE_EXPORT(localtime)
DXE_EXPORT(mktime)
DXE_EXPORT(strftime)
DXE_EXPORT(asctime)
DXE_EXPORT(ctime)
DXE_EXPORT(sleep)
DXE_EXPORT(time)
DXE_EXPORT(usleep)
// --- process / environment ---
DXE_EXPORT(abort)
DXE_EXPORT(atexit)
DXE_EXPORT(exit)
DXE_EXPORT(getenv)
DXE_EXPORT(setenv)
DXE_EXPORT(putenv)
DXE_EXPORT(system)
// --- sorting / searching ---
@ -2541,6 +2552,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
// --- setjmp / signal ---
DXE_EXPORT(longjmp)
DXE_EXPORT(setjmp)
DXE_EXPORT(raise)
DXE_EXPORT(signal)
// --- libm ---
@ -2561,7 +2573,11 @@ DXE_EXPORT_TABLE(sDxeExportTable)
DXE_EXPORT(modf)
DXE_EXPORT(pow)
DXE_EXPORT(sin)
DXE_EXPORT(sinh)
DXE_EXPORT(cosh)
DXE_EXPORT(tanh)
DXE_EXPORT(sqrt)
DXE_EXPORT(strtof)
DXE_EXPORT(tan)
// --- errno ---
@ -2582,6 +2598,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
DXE_EXPORT(__dj_assert)
DXE_EXPORT(__dj_ctype_flags)
DXE_EXPORT(__dj_ctype_tolower)
DXE_EXPORT(__dj_ctype_toupper)
DXE_EXPORT(__djgpp_exception_state_ptr)
// --- GCC internals ---

View file

@ -15,6 +15,7 @@
#include "stb_ds.h"
#include <string.h>
#include <strings.h>
// stb_ds dynamic array of class pointers. Grows on each
// wgtRegisterClass() call. Index = type ID.
@ -28,6 +29,14 @@ typedef struct {
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
@ -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
// ============================================================
@ -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
// ============================================================

128
dvxbasic/Makefile Normal file
View 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
View 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);
}
}

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

File diff suppressed because it is too large Load diff

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

File diff suppressed because it is too large Load diff

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

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

BIN
dvxbasic/icon32.bmp (Stored with Git LFS) Normal file

Binary file not shown.

1234
dvxbasic/ide/ideMain.c Normal file

File diff suppressed because it is too large Load diff

643
dvxbasic/runtime/values.c Normal file
View 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
View 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

File diff suppressed because it is too large Load diff

365
dvxbasic/runtime/vm.h Normal file
View 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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,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!"

View file

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

1643
dvxbasic/test_compiler.c Normal file

File diff suppressed because it is too large Load diff

25
dvxbasic/test_lex.c Normal file
View 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
View 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
View 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;
}

View file

@ -17,6 +17,7 @@
#include <dlfcn.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.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) {
ModuleT 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));
arrput(*mods, mod);
continue;
@ -303,7 +304,7 @@ static void loadInOrder(ModuleT *mods) {
} while (progress && 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++) {
if (!mods[i].loaded) {

View file

@ -135,8 +135,6 @@ static void showSplash(AppContextT *ctx) {
DisplayT *d = &ctx->display;
const BlitOpsT *ops = &ctx->blitOps;
const BitmapFontT *font = &ctx->font;
const ColorSchemeT *col = &ctx->colors;
// Dark background
uint32_t bgColor = packColor(d, 0, 0, 64);
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);
if (val) {
int32_t r;
int32_t g;
int32_t b;
int r;
int g;
int b;
if (sscanf(val, "%d,%d,%d", &r, &g, &b) == 3) {
sCtx.colorRgb[i][0] = (uint8_t)r;

View file

@ -184,7 +184,17 @@ static int readExistingResources(const char *path, long dxeSize, DvxResDirEntryT
}
fseek(f, entries[i].offset, SEEK_SET);
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);
@ -537,7 +547,7 @@ static int cmdBuild(const char *dxePath, const char *manifestPath) {
data = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *));
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].size = newSize;
data[count] = newData;
@ -719,7 +729,13 @@ static int cmdStrip(const char *dxePath) {
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);
// Rewrite truncated

View file

@ -194,6 +194,57 @@ static void iconImgview(void) {
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) {
int32_t rowPad = (4 - (W * 3) % 4) % 4;
int32_t rowSize = W * 3 + rowPad;
@ -243,7 +294,7 @@ static void writeBmp(const char *path) {
int main(int argc, char **argv) {
if (argc < 3) {
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;
}
@ -258,6 +309,8 @@ int main(int argc, char **argv) {
iconCpanel();
} else if (strcmp(type, "dvxdemo") == 0) {
iconDvxdemo();
} else if (strcmp(type, "basic") == 0) {
iconBasic();
} else if (strcmp(type, "imgview") == 0) {
iconImgview();
} else {

View file

@ -1024,6 +1024,33 @@ void wgtAnsiTermSetScrollback(WidgetT *w, int32_t maxLines) {
}
// ============================================================
// BASIC-facing accessors
// ============================================================
static int32_t wgtAnsiTermGetCols(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
AnsiTermDataT *at = (AnsiTermDataT *)w->data;
return at->cols;
}
static int32_t wgtAnsiTermGetRows(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
AnsiTermDataT *at = (AnsiTermDataT *)w->data;
return at->rows;
}
static void wgtAnsiTermWriteString(WidgetT *w, const char *text) {
if (!text) {
return;
}
wgtAnsiTermWrite(w, (const uint8_t *)text, (int32_t)strlen(text));
}
static const struct {
WidgetT *(*create)(WidgetT *parent, int32_t cols, int32_t rows);
void (*write)(WidgetT *w, const uint8_t *data, int32_t len);
@ -1042,7 +1069,29 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassAnsiTerm);
wgtRegisterApi("ansiterm", &sApi);
wgtRegisterIface("ansiterm", &sIface);
}

View file

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

View file

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

View file

@ -850,7 +850,22 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassCanvas);
wgtRegisterApi("canvas", &sApi);
wgtRegisterIface("canvas", &sIface);
}

View file

@ -251,7 +251,22 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassCheckbox);
wgtRegisterApi("checkbox", &sApi);
wgtRegisterIface("checkbox", &sIface);
}

View file

@ -537,7 +537,22 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassComboBox);
wgtRegisterApi("combobox", &sApi);
wgtRegisterIface("combobox", &sIface);
}

View file

@ -161,6 +161,7 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
// check. The sClosedPopup reference is cleared on the next event cycle.
void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
(void)vx;
w->focused = true;
DropdownDataT *d = (DropdownDataT *)w->data;
@ -399,7 +400,22 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassDropdown);
wgtRegisterApi("dropdown", &sApi);
wgtRegisterIface("dropdown", &sIface);
}

View file

@ -187,6 +187,50 @@ void wgtImageSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH,
}
// ============================================================
// BASIC-facing accessors
// ============================================================
static void wgtImageLoadFile(WidgetT *w, const char *path) {
VALIDATE_WIDGET_VOID(w, sTypeId);
if (!path) {
return;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return;
}
int32_t imgW;
int32_t imgH;
int32_t pitch;
uint8_t *buf = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);
if (!buf) {
return;
}
wgtImageSetData(w, buf, imgW, imgH, pitch);
}
static int32_t wgtImageGetWidth(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
ImageDataT *d = (ImageDataT *)w->data;
return d->imgW;
}
static int32_t wgtImageGetHeight(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
ImageDataT *d = (ImageDataT *)w->data;
return d->imgH;
}
// ============================================================
// DXE registration
// ============================================================
@ -196,13 +240,32 @@ static const struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch);
void (*loadFile)(WidgetT *w, const char *path);
} sApi = {
.create = wgtImage,
.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) {
sTypeId = wgtRegisterClass(&sClassImage);
wgtRegisterApi("image", &sApi);
wgtRegisterIface("image", &sIface);
}

View file

@ -8,6 +8,7 @@ typedef struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
void (*loadFile)(WidgetT *w, const char *path);
} ImageApiT;
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 wgtImageFromFile(parent, path) dvxImageApi()->fromFile(parent, path)
#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

View file

@ -261,6 +261,50 @@ void wgtImageButtonSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t
}
// ============================================================
// BASIC-facing accessors
// ============================================================
static void wgtImageButtonLoadFile(WidgetT *w, const char *path) {
VALIDATE_WIDGET_VOID(w, sTypeId);
if (!path) {
return;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return;
}
int32_t imgW;
int32_t imgH;
int32_t pitch;
uint8_t *buf = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);
if (!buf) {
return;
}
wgtImageButtonSetData(w, buf, imgW, imgH, pitch);
}
static int32_t wgtImageButtonGetWidth(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
return d->imgW;
}
static int32_t wgtImageButtonGetHeight(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
return d->imgH;
}
// ============================================================
// DXE registration
// ============================================================
@ -270,13 +314,32 @@ static const struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch);
void (*loadFile)(WidgetT *w, const char *path);
} sApi = {
.create = wgtImageButton,
.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) {
sTypeId = wgtRegisterClass(&sClassImageButton);
wgtRegisterApi("imagebutton", &sApi);
wgtRegisterIface("imagebutton", &sIface);
}

View file

@ -8,6 +8,7 @@ typedef struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
void (*loadFile)(WidgetT *w, const char *path);
} ImageButtonApiT;
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 wgtImageButtonFromFile(parent, path) dvxImageButtonApi()->fromFile(parent, path)
#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

View file

@ -160,7 +160,22 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassLabel);
wgtRegisterApi("label", &sApi);
wgtRegisterIface("label", &sIface);
}

View file

@ -65,6 +65,7 @@ typedef struct {
static void ensureScrollVisible(WidgetT *w, int32_t idx);
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);
void wgtListBoxSelectAll(WidgetT *w);
// ============================================================
@ -856,7 +857,31 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassListBox);
wgtRegisterApi("listbox", &sApi);
wgtRegisterIface("listbox", &sIface);
}

View file

@ -107,6 +107,7 @@ static void allocListViewSelBits(WidgetT *w);
static void listViewBuildSortIndex(WidgetT *w);
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);
void wgtListViewSelectAll(WidgetT *w);
// ============================================================
@ -1732,7 +1733,31 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassListView);
wgtRegisterApi("listview", &sApi);
wgtRegisterIface("listview", &sIface);
}

View file

@ -209,7 +209,22 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassProgressBar);
wgtRegisterApi("progressbar", &sApi);
wgtRegisterIface("progressbar", &sIface);
}

View file

@ -411,8 +411,27 @@ static const struct {
.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) {
sRadioGroupTypeId = wgtRegisterClass(&sClassRadioGroup);
sRadioTypeId = wgtRegisterClass(&sClassRadio);
wgtRegisterApi("radio", &sApi);
wgtRegisterIface("radio", &sIface);
}

View file

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

View file

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

View file

@ -393,7 +393,22 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassSlider);
wgtRegisterApi("slider", &sApi);
wgtRegisterIface("slider", &sIface);
}

View file

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

View file

@ -588,7 +588,27 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassSpinner);
wgtRegisterApi("spinner", &sApi);
wgtRegisterIface("spinner", &sIface);
}

View file

@ -524,7 +524,22 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassSplitter);
wgtRegisterApi("splitter", &sApi);
wgtRegisterIface("splitter", &sIface);
}

View file

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

View file

@ -672,8 +672,27 @@ static const struct {
.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) {
sTabControlTypeId = wgtRegisterClass(&sClassTabControl);
sTabPageTypeId = wgtRegisterClass(&sClassTabPage);
wgtRegisterApi("tabcontrol", &sApi);
wgtRegisterIface("tabcontrol", &sIface);
}

View file

@ -101,6 +101,15 @@ typedef struct {
int32_t sbDragOrient;
int32_t sbDragOff;
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;
#include <ctype.h>
@ -111,13 +120,26 @@ typedef struct {
#define TEXTAREA_SB_W 14
#define TEXTAREA_MIN_ROWS 4
#define TEXTAREA_MIN_COLS 20
#define MAX_COLORIZE_LEN 1024
// Match the ANSI terminal cursor blink rate (CURSOR_MS in widgetAnsiTerm.c)
#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
// ============================================================
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 int32_t maskFirstSlot(const char *mask);
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 void textAreaEnsureVisible(WidgetT *w, int32_t visRows, int32_t visCols);
static int32_t textAreaGetLineCount(WidgetT *w);
static int32_t textAreaGutterWidth(WidgetT *w, const BitmapFontT *font);
static int32_t textAreaGetMaxLineLen(WidgetT *w);
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);
@ -142,8 +165,6 @@ static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
// 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) {
(void)len;
int32_t off = 0;
@ -805,7 +850,8 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
AppContextT *ctx = wgtGetContext(w);
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 maxLL = textAreaGetMaxLineLen(w);
bool needHSb = (maxLL > visCols);
@ -1006,13 +1052,27 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
int32_t off = CUR_OFF();
if (*pLen < bufSize - 1) {
memmove(buf + off + 1, buf + off, *pLen - off + 1);
// Measure indent of current line before inserting newline
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';
(*pLen)++;
memcpy(buf + off + 1, indentBuf, indent);
*pLen += 1 + indent;
(*pRow)++;
*pCol = 0;
ta->desiredCol = 0;
*pCol = indent;
ta->desiredCol = indent;
}
if (w->onChange) {
@ -1339,9 +1399,10 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
AppContextT *ctx = (AppContextT *)root->userData;
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 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 maxLL = textAreaGetMaxLineLen(w);
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
// ============================================================
@ -1564,7 +1669,8 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
char *buf = ta->buf;
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 maxLL = textAreaGetMaxLineLen(w);
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
int32_t textX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
int32_t textY = w->y + TEXTAREA_BORDER;
int32_t gutterX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
int32_t textX = gutterX + gutterW;
int32_t textY = w->y + TEXTAREA_BORDER;
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++) {
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;
// 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
int32_t scrollCol = ta->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 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
int32_t lineSelLo = -1;
int32_t lineSelHi = -1;
if (selLo >= 0) {
// Selection range in column-space for this line
if (selLo < lineOff + lineL + 1 && selHi > lineOff) {
lineSelLo = selLo - lineOff;
lineSelHi = selHi - lineOff;
if (lineSelLo < 0) { lineSelLo = 0; }
// selHi can extend past line (newline selected)
}
}
if (lineSelLo >= 0 && lineSelLo < lineSelHi) {
// Clamp selection to visible columns for text runs
int32_t vSelLo = lineSelLo < drawStart ? drawStart : lineSelLo;
int32_t vSelHi = lineSelHi < drawEnd ? lineSelHi : drawEnd;
@ -1656,17 +1785,25 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
// Before selection
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) {
drawTextN(d, ops, font, textX + (vSelLo - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelLo, vSelHi - vSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
}
// After selection
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
@ -1684,9 +1821,13 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
}
}
} else {
// No selection on this line -- single run
// No selection on this line
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);
const BitmapFontT *font = &ctx->font;
int32_t innerX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
int32_t innerY = w->y + TEXTAREA_BORDER;
int32_t relX = vx - innerX;
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 relX = vx - innerX;
int32_t relY = vy - innerY;
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
// ============================================================
@ -2353,15 +2575,34 @@ static const struct {
WidgetT *(*password)(WidgetT *parent, int32_t maxLen);
WidgetT *(*masked)(WidgetT *parent, const char *mask);
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 = {
.create = wgtTextInput,
.password = wgtPasswordInput,
.masked = wgtMaskedInput,
.textArea = wgtTextArea
.create = wgtTextInput,
.password = wgtPasswordInput,
.masked = wgtMaskedInput,
.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) {
sTextInputTypeId = wgtRegisterClass(&sClassTextInput);
sTextAreaTypeId = wgtRegisterClass(&sClassTextArea);
wgtRegisterApi("textinput", &sApi);
wgtRegisterIface("textinput", &sIface);
}

View file

@ -4,11 +4,24 @@
#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 {
WidgetT *(*create)(WidgetT *parent, int32_t maxLen);
WidgetT *(*password)(WidgetT *parent, int32_t maxLen);
WidgetT *(*masked)(WidgetT *parent, const char *mask);
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;
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 wgtMaskedInput(parent, mask) dvxTextInputApi()->masked(parent, mask)
#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

View file

@ -219,7 +219,32 @@ static const struct {
.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) {
sTypeId = wgtRegisterClass(&sClassTimer);
wgtRegisterApi("timer", &sApi);
wgtRegisterIface("timer", &sIface);
}

View file

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

View file

@ -1722,8 +1722,24 @@ static const struct {
.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) {
sTreeViewTypeId = wgtRegisterClass(&sClassTreeView);
sTreeItemTypeId = wgtRegisterClass(&sClassTreeItem);
wgtRegisterApi("treeview", &sApi);
wgtRegisterIface("treeview", &sIface);
}