Many BASIC compiler and VM fixes. Other fixes. New BASIC apps.
This commit is contained in:
parent
de60200f23
commit
dcd120d769
74 changed files with 6091 additions and 505 deletions
|
|
@ -11,11 +11,14 @@ BINDIR = ../bin/apps
|
|||
DVXRES = ../bin/host/dvxres
|
||||
|
||||
# App definitions: each is a subdir with a single .c file
|
||||
APPS = progman notepad clock dvxdemo cpanel imgview dvxhelp
|
||||
APPS = progman clock dvxdemo cpanel dvxhelp
|
||||
|
||||
.PHONY: all clean $(APPS) dvxbasic
|
||||
BASCOMP = ../bin/host/bascomp
|
||||
BASICAPPS = iconed notepad-bas imgview-bas helpedit resedit basicdemo
|
||||
|
||||
all: $(APPS) dvxbasic
|
||||
.PHONY: all clean $(APPS) dvxbasic $(BASICAPPS)
|
||||
|
||||
all: $(APPS) dvxbasic $(BASICAPPS)
|
||||
|
||||
dvxbasic:
|
||||
$(MAKE) -C dvxbasic
|
||||
|
|
@ -30,28 +33,28 @@ dvxhelp: $(BINDIR)/kpunch/dvxhelp/dvxhelp.app
|
|||
|
||||
$(BINDIR)/kpunch/cpanel/cpanel.app: $(OBJDIR)/cpanel.o cpanel/cpanel.res cpanel/icon32.bmp | $(BINDIR)/kpunch/cpanel
|
||||
$(DXE3GEN) -o $@ -U $<
|
||||
$(DVXRES) build $@ cpanel/cpanel.res
|
||||
cd cpanel && ../$(DVXRES) build ../$@ cpanel.res
|
||||
|
||||
$(BINDIR)/kpunch/imgview/imgview.app: $(OBJDIR)/imgview.o imgview/imgview.res imgview/icon32.bmp | $(BINDIR)/kpunch/imgview
|
||||
$(DXE3GEN) -o $@ -U $<
|
||||
$(DVXRES) build $@ imgview/imgview.res
|
||||
cd imgview && ../$(DVXRES) build ../$@ imgview.res
|
||||
|
||||
$(BINDIR)/kpunch/progman/progman.app: $(OBJDIR)/progman.o | $(BINDIR)/kpunch/progman
|
||||
$(DXE3GEN) -o $@ -U $<
|
||||
|
||||
$(BINDIR)/kpunch/notepad/notepad.app: $(OBJDIR)/notepad.o notepad/notepad.res notepad/icon32.bmp | $(BINDIR)/kpunch/notepad
|
||||
$(DXE3GEN) -o $@ -U $<
|
||||
$(DVXRES) build $@ notepad/notepad.res
|
||||
cd notepad && ../$(DVXRES) build ../$@ notepad.res
|
||||
|
||||
$(BINDIR)/kpunch/clock/clock.app: $(OBJDIR)/clock.o clock/clock.res clock/icon32.bmp | $(BINDIR)/kpunch/clock
|
||||
$(DXE3GEN) -o $@ -U $<
|
||||
$(DVXRES) build $@ clock/clock.res
|
||||
cd clock && ../$(DVXRES) build ../$@ clock.res
|
||||
|
||||
DVXDEMO_BMPS = logo.bmp new.bmp open.bmp sample.bmp save.bmp
|
||||
|
||||
$(BINDIR)/kpunch/dvxdemo/dvxdemo.app: $(OBJDIR)/dvxdemo.o $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) dvxdemo/dvxdemo.res dvxdemo/icon32.bmp | $(BINDIR)/kpunch/dvxdemo
|
||||
$(DXE3GEN) -o $@ -U $<
|
||||
$(DVXRES) build $@ dvxdemo/dvxdemo.res
|
||||
cd dvxdemo && ../$(DVXRES) build ../$@ dvxdemo.res
|
||||
cp $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) $(BINDIR)/kpunch/dvxdemo/
|
||||
|
||||
$(OBJDIR)/cpanel.o: cpanel/cpanel.c | $(OBJDIR)
|
||||
|
|
@ -74,11 +77,52 @@ $(OBJDIR)/dvxdemo.o: dvxdemo/dvxdemo.c | $(OBJDIR)
|
|||
|
||||
$(BINDIR)/kpunch/dvxhelp/dvxhelp.app: $(OBJDIR)/dvxhelp.o dvxhelp/dvxhelp.res dvxhelp/icon32.bmp | $(BINDIR)/kpunch/dvxhelp
|
||||
$(DXE3GEN) -o $@ -U $<
|
||||
$(DVXRES) build $@ dvxhelp/dvxhelp.res
|
||||
cd dvxhelp && ../$(DVXRES) build ../$@ dvxhelp.res
|
||||
|
||||
$(OBJDIR)/dvxhelp.o: dvxhelp/dvxhelp.c dvxhelp/hlpformat.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
# BASIC apps (compiled from .dbp projects via bascomp)
|
||||
iconed: $(BINDIR)/kpunch/iconed/iconed.app
|
||||
|
||||
$(BINDIR)/kpunch/iconed/iconed.app: ../sdk/samples/basic/iconed/iconed.dbp ../sdk/samples/basic/iconed/iconed.frm ../sdk/samples/basic/iconed/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/iconed dvxbasic
|
||||
$(BASCOMP) ../sdk/samples/basic/iconed/iconed.dbp -o $@ -release
|
||||
|
||||
notepad-bas: $(BINDIR)/kpunch/notepad/notepad.app
|
||||
|
||||
$(BINDIR)/kpunch/notepad/notepad.app: ../sdk/samples/basic/notepad/notepad.dbp ../sdk/samples/basic/notepad/notepad.frm ../sdk/samples/basic/notepad/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/notepad dvxbasic
|
||||
$(BASCOMP) ../sdk/samples/basic/notepad/notepad.dbp -o $@ -release
|
||||
|
||||
imgview-bas: $(BINDIR)/kpunch/imgview/imgview.app
|
||||
|
||||
$(BINDIR)/kpunch/imgview/imgview.app: ../sdk/samples/basic/imgview/imgview.dbp ../sdk/samples/basic/imgview/imgview.frm ../sdk/samples/basic/imgview/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/imgview dvxbasic
|
||||
$(BASCOMP) ../sdk/samples/basic/imgview/imgview.dbp -o $@ -release
|
||||
|
||||
helpedit: $(BINDIR)/kpunch/dvxhelp/helpedit.app
|
||||
|
||||
$(BINDIR)/kpunch/dvxhelp/helpedit.app: ../sdk/samples/basic/helpedit/helpedit.dbp ../sdk/samples/basic/helpedit/helpedit.frm ../sdk/samples/basic/helpedit/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/dvxhelp dvxbasic
|
||||
$(BASCOMP) ../sdk/samples/basic/helpedit/helpedit.dbp -o $@ -release
|
||||
$(DVXRES) add $@ helpfile text "dvxhelp.hlp"
|
||||
|
||||
resedit: $(BINDIR)/kpunch/resedit/resedit.app
|
||||
|
||||
$(BINDIR)/kpunch/resedit/resedit.app: ../sdk/samples/basic/resedit/resedit.dbp ../sdk/samples/basic/resedit/resedit.frm ../sdk/samples/basic/resedit/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/resedit dvxbasic
|
||||
$(BASCOMP) ../sdk/samples/basic/resedit/resedit.dbp -o $@ -release
|
||||
|
||||
$(BINDIR)/kpunch/resedit:
|
||||
mkdir -p $(BINDIR)/kpunch/resedit
|
||||
|
||||
basicdemo: $(BINDIR)/kpunch/basicdemo/basicdemo.app
|
||||
|
||||
$(BINDIR)/kpunch/basicdemo/basicdemo.app: ../sdk/samples/basic/basicdemo/basicdemo.dbp ../sdk/samples/basic/basicdemo/basicdemo.frm ../sdk/samples/basic/basicdemo/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/basicdemo dvxbasic
|
||||
$(BASCOMP) ../sdk/samples/basic/basicdemo/basicdemo.dbp -o $@ -release
|
||||
|
||||
$(BINDIR)/kpunch/basicdemo:
|
||||
mkdir -p $(BINDIR)/kpunch/basicdemo
|
||||
|
||||
$(BINDIR)/kpunch/iconed:
|
||||
mkdir -p $(BINDIR)/kpunch/iconed
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# clock.res -- Resource manifest for Clock
|
||||
icon32 icon clock/icon32.bmp
|
||||
icon32 icon icon32.bmp
|
||||
name text "Clock"
|
||||
author text "DVX Project"
|
||||
description text "Digital clock with date display"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// ctrlpanel.c -- DVX Control Panel
|
||||
//
|
||||
// System configuration app with four tabs:
|
||||
// Mouse -- scroll direction, double-click speed, acceleration, cursor speed
|
||||
// Mouse -- scroll direction, wheel speed, double-click speed, acceleration, cursor speed
|
||||
// Colors -- all 18 system colors, theme load/save
|
||||
// Desktop -- wallpaper image (stretch mode)
|
||||
// Video -- resolution and color depth
|
||||
|
|
@ -95,6 +95,7 @@ static int32_t sSavedWheelDir;
|
|||
static int32_t sSavedDblClick;
|
||||
static int32_t sSavedAccel;
|
||||
static int32_t sSavedSpeed;
|
||||
static int32_t sSavedWheelStep;
|
||||
static int32_t sSavedVideoW;
|
||||
static int32_t sSavedVideoH;
|
||||
static int32_t sSavedVideoBpp;
|
||||
|
|
@ -107,6 +108,8 @@ static WidgetT *sDblClickLbl = NULL;
|
|||
static WidgetT *sAccelDrop = NULL;
|
||||
static WidgetT *sSpeedSldr = NULL;
|
||||
static WidgetT *sSpeedLbl = NULL;
|
||||
static WidgetT *sWheelStepSldr = NULL;
|
||||
static WidgetT *sWheelStepLbl = NULL;
|
||||
static WidgetT *sDblTestLbl = NULL;
|
||||
|
||||
// Colors tab widgets
|
||||
|
|
@ -152,6 +155,7 @@ static const char *mapAccelValue(int32_t val);
|
|||
static void onAccelChange(WidgetT *w);
|
||||
static void onSpeedSlider(WidgetT *w);
|
||||
static void updateSpeedLabel(void);
|
||||
static void updateWheelStepLabel(void);
|
||||
static void applyMouseConfig(void);
|
||||
static void onApplyTheme(WidgetT *w);
|
||||
static void onBrowseTheme(WidgetT *w);
|
||||
|
|
@ -170,6 +174,7 @@ static void onOk(WidgetT *w);
|
|||
static void onSaveTheme(WidgetT *w);
|
||||
static void onVideoApply(WidgetT *w);
|
||||
static void onWheelChange(WidgetT *w);
|
||||
static void onWheelStepSlider(WidgetT *w);
|
||||
static void saveSnapshot(void);
|
||||
static void restoreSnapshot(void);
|
||||
static void scanWallpapers(void);
|
||||
|
|
@ -334,6 +339,26 @@ static void buildMouseTab(WidgetT *page) {
|
|||
|
||||
wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL);
|
||||
|
||||
// Wheel speed (lines per notch)
|
||||
wgtLabel(page, "Scroll Speed:");
|
||||
|
||||
WidgetT *wheelStepRow = wgtHBox(page);
|
||||
wheelStepRow->spacing = wgtPixels(CP_SPACING);
|
||||
|
||||
wgtLabel(wheelStepRow, "Slow");
|
||||
sWheelStepSldr = wgtSlider(wheelStepRow, MOUSE_WHEEL_STEP_MIN, MOUSE_WHEEL_STEP_MAX);
|
||||
sWheelStepSldr->weight = 100;
|
||||
sWheelStepSldr->onChange = onWheelStepSlider;
|
||||
|
||||
int32_t wheelStep = prefsGetInt(sPrefs, "mouse", "wheelspeed", MOUSE_WHEEL_STEP_DEFAULT);
|
||||
wgtSliderSetValue(sWheelStepSldr, wheelStep);
|
||||
wgtLabel(wheelStepRow, "Fast ");
|
||||
sWheelStepLbl = wgtLabel(wheelStepRow, "");
|
||||
sWheelStepLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W);
|
||||
updateWheelStepLabel();
|
||||
|
||||
wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL);
|
||||
|
||||
// Double-click speed
|
||||
wgtLabel(page, "Double-Click Speed:");
|
||||
|
||||
|
|
@ -500,6 +525,13 @@ static void onWheelChange(WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
static void onWheelStepSlider(WidgetT *w) {
|
||||
(void)w;
|
||||
updateWheelStepLabel();
|
||||
applyMouseConfig();
|
||||
}
|
||||
|
||||
|
||||
static void onDblClickSlider(WidgetT *w) {
|
||||
(void)w;
|
||||
updateDblClickLabel();
|
||||
|
|
@ -588,8 +620,8 @@ static void onBrowseTheme(WidgetT *w) {
|
|||
(void)w;
|
||||
|
||||
FileFilterT filters[] = {
|
||||
{ "Theme Files (*.thm)", "*.thm" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
{ "\1" },
|
||||
{ "\1" }
|
||||
};
|
||||
char path[DVX_MAX_PATH];
|
||||
|
||||
|
|
@ -604,8 +636,8 @@ static void onSaveTheme(WidgetT *w) {
|
|||
(void)w;
|
||||
|
||||
FileFilterT filters[] = {
|
||||
{ "Theme Files (*.thm)", "*.thm" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
{ "\1" },
|
||||
{ "\1" }
|
||||
};
|
||||
char path[DVX_MAX_PATH];
|
||||
|
||||
|
|
@ -650,8 +682,8 @@ static void onChooseWallpaper(WidgetT *w) {
|
|||
(void)w;
|
||||
|
||||
FileFilterT filters[] = {
|
||||
{ "Images (*.bmp;*.jpg;*.png)", "*.bmp;*.jpg;*.png" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
{ "\1" },
|
||||
{ "\1" }
|
||||
};
|
||||
char path[DVX_MAX_PATH];
|
||||
|
||||
|
|
@ -824,6 +856,7 @@ static void onOk(WidgetT *w) {
|
|||
prefsSetInt(sPrefs, "mouse", "doubleclick", wgtSliderGetValue(sDblClickSldr));
|
||||
prefsSetString(sPrefs, "mouse", "acceleration", mapAccelValue(wgtDropdownGetSelected(sAccelDrop)));
|
||||
prefsSetInt(sPrefs, "mouse", "speed", wgtSliderGetValue(sSpeedSldr));
|
||||
prefsSetInt(sPrefs, "mouse", "wheelspeed", wgtSliderGetValue(sWheelStepSldr));
|
||||
|
||||
// Save colors to INI
|
||||
for (int32_t i = 0; i < ColorCountE; i++) {
|
||||
|
|
@ -898,6 +931,7 @@ static void saveSnapshot(void) {
|
|||
sSavedDblClick = (int32_t)(sAc->dblClickTicks * 1000 / CLOCKS_PER_SEC);
|
||||
sSavedAccel = wgtDropdownGetSelected(sAccelDrop);
|
||||
sSavedSpeed = wgtSliderGetValue(sSpeedSldr);
|
||||
sSavedWheelStep = sAc->wheelStep;
|
||||
sSavedVideoW = sAc->display.width;
|
||||
sSavedVideoH = sAc->display.height;
|
||||
sSavedVideoBpp = sAc->display.format.bitsPerPixel;
|
||||
|
|
@ -918,7 +952,7 @@ static void restoreSnapshot(void) {
|
|||
const char *accelName = mapAccelValue(sSavedAccel);
|
||||
int32_t accelVal = mapAccelName(accelName);
|
||||
int32_t speedVal = 34 - sSavedSpeed;
|
||||
dvxSetMouseConfig(sAc, sSavedWheelDir, sSavedDblClick, accelVal, speedVal);
|
||||
dvxSetMouseConfig(sAc, sSavedWheelDir, sSavedDblClick, accelVal, speedVal, sSavedWheelStep);
|
||||
|
||||
// Restore video mode if changed
|
||||
if (sAc->display.width != sSavedVideoW ||
|
||||
|
|
@ -1097,6 +1131,18 @@ static void updateSpeedLabel(void) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// updateWheelStepLabel
|
||||
// ============================================================
|
||||
|
||||
static void updateWheelStepLabel(void) {
|
||||
static char buf[32];
|
||||
int32_t val = wgtSliderGetValue(sWheelStepSldr);
|
||||
snprintf(buf, sizeof(buf), "%ld", (long)val);
|
||||
wgtSetText(sWheelStepLbl, buf);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// applyMouseConfig -- gather current widget values and apply
|
||||
// ============================================================
|
||||
|
|
@ -1113,9 +1159,10 @@ static void applyMouseConfig(void) {
|
|||
// slider 2 -> 32 mickeys/8px (slowest)
|
||||
// slider 8 -> 26 (near default)
|
||||
// slider 32 -> 2 mickeys/8px (fastest)
|
||||
int32_t speedVal = 34 - wgtSliderGetValue(sSpeedSldr);
|
||||
int32_t speedVal = 34 - wgtSliderGetValue(sSpeedSldr);
|
||||
int32_t wheelStep = wgtSliderGetValue(sWheelStepSldr);
|
||||
|
||||
dvxSetMouseConfig(sAc, dir, dbl, accelVal, speedVal);
|
||||
dvxSetMouseConfig(sAc, dir, dbl, accelVal, speedVal, wheelStep);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# cpanel.res -- Resource manifest for Control Panel
|
||||
icon32 icon cpanel/icon32.bmp
|
||||
icon32 icon icon32.bmp
|
||||
name text "Control Panel"
|
||||
author text "DVX Project"
|
||||
description text "System settings and preferences"
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ RT_TARGETDIR = $(LIBSDIR)/kpunch/basrt
|
|||
RT_TARGET = $(RT_TARGETDIR)/basrt.lib
|
||||
|
||||
# Compiler objects (only needed by the IDE)
|
||||
COMP_OBJS = $(OBJDIR)/lexer.o $(OBJDIR)/parser.o $(OBJDIR)/codegen.o $(OBJDIR)/symtab.o $(OBJDIR)/strip.o
|
||||
COMP_OBJS = $(OBJDIR)/lexer.o $(OBJDIR)/parser.o $(OBJDIR)/codegen.o $(OBJDIR)/symtab.o $(OBJDIR)/strip.o $(OBJDIR)/obfuscate.o $(OBJDIR)/compact.o
|
||||
|
||||
# IDE app objects
|
||||
IDE_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideMenuEditor.o $(OBJDIR)/ideProject.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o
|
||||
|
|
@ -38,25 +38,39 @@ STUB_TARGET = $(OBJDIR)/basstub.app
|
|||
|
||||
# Native test programs (host gcc, not cross-compiled)
|
||||
HOSTCC = gcc
|
||||
HOSTCFLAGS = -O2 -Wall -Wextra -Wno-type-limits -Wno-sign-compare -I. -I../../core
|
||||
HOSTCFLAGS = -O2 -Wall -Wextra -Wno-type-limits -Wno-sign-compare -D_GNU_SOURCE -I. -I../../core -I../../core/platform -I../../core/thirdparty
|
||||
BINDIR = ../../bin
|
||||
|
||||
TEST_COMPILER = $(BINDIR)/test_compiler
|
||||
TEST_VM = $(BINDIR)/test_vm
|
||||
TEST_LEX = $(BINDIR)/test_lex
|
||||
TEST_QUICK = $(BINDIR)/test_quick
|
||||
TEST_COMPACT = $(BINDIR)/test_compact
|
||||
|
||||
STB_DS_IMPL = ../../core/thirdparty/stb_ds_impl.c
|
||||
TEST_COMPILER_SRCS = test_compiler.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c $(STB_DS_IMPL)
|
||||
TEST_VM_SRCS = test_vm.c runtime/vm.c runtime/values.c $(STB_DS_IMPL)
|
||||
TEST_COMPILER_SRCS = test_compiler.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c runtime/serialize.c $(STB_DS_IMPL)
|
||||
TEST_VM_SRCS = test_vm.c runtime/vm.c runtime/values.c runtime/serialize.c $(STB_DS_IMPL)
|
||||
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 $(STB_DS_IMPL)
|
||||
TEST_QUICK_SRCS = test_quick.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c runtime/serialize.c $(STB_DS_IMPL)
|
||||
TEST_COMPACT_SRCS = test_compact.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c compiler/strip.c compiler/compact.c runtime/vm.c runtime/values.c runtime/serialize.c $(STB_DS_IMPL)
|
||||
|
||||
# Command-line compiler (host tool)
|
||||
HOSTDIR = ../../bin/host
|
||||
BASCOMP_SRCS = stub/bascomp.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c compiler/strip.c compiler/obfuscate.c compiler/compact.c runtime/vm.c runtime/values.c runtime/serialize.c ../../core/dvxPrefs.c ../../core/dvxResource.c $(STB_DS_IMPL)
|
||||
BASCOMP_TARGET = $(HOSTDIR)/bascomp
|
||||
|
||||
# DOS command-line compiler
|
||||
DOSCC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
DOSCFLAGS = -O2 -Wall -Wextra -Werror -Wno-type-limits -Wno-sign-compare -Wno-format-truncation -march=i486 -mtune=i586 -I../../core -I../../core/platform -I../../core/thirdparty -I.
|
||||
EXE2COFF = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/exe2coff
|
||||
CWSDSTUB = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/CWSDSTUB.EXE
|
||||
SYSTEMDIR = ../../bin/system
|
||||
|
||||
.PHONY: all clean tests
|
||||
|
||||
all: $(RT_TARGET) $(RT_TARGETDIR)/basrt.dep $(STUB_TARGET) $(APP_TARGET)
|
||||
all: $(RT_TARGET) $(RT_TARGETDIR)/basrt.dep $(STUB_TARGET) $(APP_TARGET) $(BASCOMP_TARGET) $(SYSTEMDIR)/BASCOMP.EXE $(SYSTEMDIR)/BASSTUB.APP
|
||||
|
||||
tests: $(TEST_COMPILER) $(TEST_VM) $(TEST_LEX) $(TEST_QUICK)
|
||||
tests: $(TEST_COMPILER) $(TEST_VM) $(TEST_LEX) $(TEST_QUICK) $(TEST_COMPACT)
|
||||
|
||||
$(TEST_COMPILER): $(TEST_COMPILER_SRCS) | $(BINDIR)
|
||||
$(HOSTCC) $(HOSTCFLAGS) -o $@ $(TEST_COMPILER_SRCS) -lm
|
||||
|
|
@ -70,6 +84,31 @@ $(TEST_LEX): $(TEST_LEX_SRCS) | $(BINDIR)
|
|||
$(TEST_QUICK): $(TEST_QUICK_SRCS) | $(BINDIR)
|
||||
$(HOSTCC) $(HOSTCFLAGS) -o $@ $(TEST_QUICK_SRCS) -lm
|
||||
|
||||
$(TEST_COMPACT): $(TEST_COMPACT_SRCS) | $(BINDIR)
|
||||
$(HOSTCC) $(HOSTCFLAGS) -o $@ $(TEST_COMPACT_SRCS) -lm
|
||||
|
||||
# Host command-line compiler (stub deployed alongside)
|
||||
$(BASCOMP_TARGET): $(BASCOMP_SRCS) $(STUB_TARGET) | $(HOSTDIR)
|
||||
$(HOSTCC) $(HOSTCFLAGS) -DBASCOMP_STANDALONE -I../../tools -o $@ $(BASCOMP_SRCS) -lm
|
||||
cp $(STUB_TARGET) $(HOSTDIR)/BASSTUB.APP
|
||||
|
||||
# DOS command-line compiler
|
||||
$(SYSTEMDIR)/BASCOMP.EXE: $(BASCOMP_SRCS) | $(SYSTEMDIR)
|
||||
$(DOSCC) $(DOSCFLAGS) -DBASCOMP_STANDALONE -I../../tools -o $(SYSTEMDIR)/bascomp.exe $(BASCOMP_SRCS) -lm
|
||||
$(EXE2COFF) $(SYSTEMDIR)/bascomp.exe
|
||||
cat $(CWSDSTUB) $(SYSTEMDIR)/bascomp > $@
|
||||
rm -f $(SYSTEMDIR)/bascomp $(SYSTEMDIR)/bascomp.exe
|
||||
|
||||
# Deploy stub alongside DOS compiler
|
||||
$(SYSTEMDIR)/BASSTUB.APP: $(STUB_TARGET) | $(SYSTEMDIR)
|
||||
cp $(STUB_TARGET) $@
|
||||
|
||||
$(HOSTDIR):
|
||||
mkdir -p $(HOSTDIR)
|
||||
|
||||
$(SYSTEMDIR):
|
||||
mkdir -p $(SYSTEMDIR)
|
||||
|
||||
# Runtime library DXE (exports symbols via dlregsym constructor)
|
||||
$(RT_TARGET): $(RT_OBJS) | $(RT_TARGETDIR)
|
||||
$(DXE3GEN) -o $(RT_TARGETDIR)/basrt.dxe -U $(RT_OBJS)
|
||||
|
|
@ -105,6 +144,12 @@ $(OBJDIR)/serialize.o: runtime/serialize.c runtime/serialize.h runtime/vm.h | $(
|
|||
$(OBJDIR)/strip.o: compiler/strip.c compiler/strip.h compiler/opcodes.h runtime/vm.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/obfuscate.o: compiler/obfuscate.c compiler/obfuscate.h runtime/vm.h runtime/values.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/compact.o: compiler/compact.c compiler/compact.h compiler/opcodes.h runtime/vm.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/basstub.o: stub/basstub.c runtime/vm.h runtime/serialize.h formrt/formrt.h formrt/formcfm.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
|
|
@ -158,5 +203,5 @@ $(BINDIR):
|
|||
mkdir -p $(BINDIR)
|
||||
|
||||
clean:
|
||||
rm -rf $(RT_OBJS) $(COMP_OBJS) $(IDE_OBJS) $(STUB_OBJS) $(RT_TARGET) $(APP_TARGET) $(STUB_TARGET) $(RT_TARGETDIR)/basrt.dep $(RT_TARGETDIR) $(OBJDIR)/basrt_init.o
|
||||
rm -rf $(RT_OBJS) $(COMP_OBJS) $(IDE_OBJS) $(STUB_OBJS) $(RT_TARGET) $(APP_TARGET) $(STUB_TARGET) $(BASCOMP_TARGET) $(RT_TARGETDIR)/basrt.dep $(RT_TARGETDIR) $(OBJDIR)/basrt_init.o
|
||||
rm -f $(TEST_COMPILER) $(TEST_VM) $(TEST_LEX) $(TEST_QUICK)
|
||||
|
|
|
|||
|
|
@ -372,47 +372,6 @@ const BasProcEntryT *basModuleFindProc(const BasModuleT *mod, const char *name)
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 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->formVarInfo);
|
||||
free(mod->debugVars);
|
||||
|
||||
if (mod->debugUdtDefs) {
|
||||
for (int32_t i = 0; i < mod->debugUdtDefCount; i++) {
|
||||
free(mod->debugUdtDefs[i].fields);
|
||||
}
|
||||
|
||||
free(mod->debugUdtDefs);
|
||||
}
|
||||
|
||||
free(mod);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
564
apps/dvxbasic/compiler/compact.c
Normal file
564
apps/dvxbasic/compiler/compact.c
Normal file
|
|
@ -0,0 +1,564 @@
|
|||
// compact.c -- Release build bytecode compaction
|
||||
//
|
||||
// Walks the module's bytecode, removes OP_LINE instructions (3 bytes
|
||||
// each), and rewrites all code-address references so control flow
|
||||
// still lands on the correct instructions.
|
||||
//
|
||||
// Address references:
|
||||
// - BasProcEntryT::codeAddr (absolute)
|
||||
// - BasFormVarInfoT::initCodeAddr (absolute, 0 = no init)
|
||||
// - OP_CALL operand (absolute uint16)
|
||||
// - OP_JMP / OP_JMP_TRUE / OP_JMP_FALSE operand (relative int16)
|
||||
// - OP_FOR_NEXT loopTop operand (relative int16)
|
||||
// - OP_ON_ERROR handler operand (relative int16; 0 = disable, not remapped)
|
||||
// - GOSUB return address (emitted as OP_PUSH_INT32 followed by OP_JMP,
|
||||
// where the pushed value equals the PC immediately after the JMP)
|
||||
//
|
||||
// Safety: if any opcode cannot be sized, any jump overflows int16, or
|
||||
// the walk doesn't reach codeLen exactly, the module is left untouched.
|
||||
|
||||
#include "compact.h"
|
||||
#include "opcodes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Opcode operand size table
|
||||
// ============================================================
|
||||
// Returns operand byte count (excluding the 1-byte opcode), or -1 if unknown.
|
||||
|
||||
static int32_t opOperandSize(uint8_t op) {
|
||||
switch (op) {
|
||||
// No operand bytes
|
||||
case OP_NOP:
|
||||
case OP_PUSH_TRUE: case OP_PUSH_FALSE:
|
||||
case OP_POP: case OP_DUP:
|
||||
case OP_LOAD_REF: case OP_STORE_REF:
|
||||
case OP_ADD_INT: case OP_SUB_INT: case OP_MUL_INT:
|
||||
case OP_IDIV_INT: case OP_MOD_INT: case OP_NEG_INT:
|
||||
case OP_ADD_FLT: case OP_SUB_FLT: case OP_MUL_FLT:
|
||||
case OP_DIV_FLT: case OP_NEG_FLT: case OP_POW:
|
||||
case OP_STR_CONCAT: case OP_STR_LEFT: case OP_STR_RIGHT:
|
||||
case OP_STR_MID: case OP_STR_MID2: case OP_STR_LEN:
|
||||
case OP_STR_INSTR: case OP_STR_INSTR3:
|
||||
case OP_STR_UCASE: case OP_STR_LCASE:
|
||||
case OP_STR_TRIM: case OP_STR_LTRIM: case OP_STR_RTRIM:
|
||||
case OP_STR_CHR: case OP_STR_ASC: case OP_STR_SPACE:
|
||||
case OP_CMP_EQ: case OP_CMP_NE: case OP_CMP_LT:
|
||||
case OP_CMP_GT: case OP_CMP_LE: case OP_CMP_GE:
|
||||
case OP_AND: case OP_OR: case OP_NOT:
|
||||
case OP_XOR: case OP_EQV: case OP_IMP:
|
||||
case OP_GOSUB_RET: case OP_RET: case OP_RET_VAL:
|
||||
case OP_FOR_POP:
|
||||
case OP_CONV_INT_FLT: case OP_CONV_FLT_INT:
|
||||
case OP_CONV_INT_STR: case OP_CONV_STR_INT:
|
||||
case OP_CONV_FLT_STR: case OP_CONV_STR_FLT:
|
||||
case OP_CONV_INT_LONG: case OP_CONV_LONG_INT:
|
||||
case OP_PRINT: case OP_PRINT_NL: case OP_PRINT_TAB:
|
||||
case OP_INPUT:
|
||||
case OP_FILE_CLOSE: case OP_FILE_PRINT: case OP_FILE_INPUT:
|
||||
case OP_FILE_EOF: case OP_FILE_LINE_INPUT:
|
||||
case OP_LOAD_PROP: case OP_STORE_PROP:
|
||||
case OP_LOAD_FORM: case OP_UNLOAD_FORM:
|
||||
case OP_HIDE_FORM: case OP_DO_EVENTS:
|
||||
case OP_MSGBOX: case OP_INPUTBOX: case OP_ME_REF:
|
||||
case OP_CREATE_CTRL: case OP_FIND_CTRL: case OP_FIND_CTRL_IDX:
|
||||
case OP_CREATE_CTRL_EX:
|
||||
case OP_ERASE:
|
||||
case OP_RESUME: case OP_RESUME_NEXT:
|
||||
case OP_RAISE_ERR: case OP_ERR_NUM: case OP_ERR_CLEAR:
|
||||
case OP_MATH_ABS: case OP_MATH_INT: case OP_MATH_FIX:
|
||||
case OP_MATH_SGN: case OP_MATH_SQR: case OP_MATH_SIN:
|
||||
case OP_MATH_COS: case OP_MATH_TAN: case OP_MATH_ATN:
|
||||
case OP_MATH_LOG: case OP_MATH_EXP: case OP_MATH_RND:
|
||||
case OP_MATH_RANDOMIZE:
|
||||
case OP_RGB:
|
||||
case OP_GET_RED: case OP_GET_GREEN: case OP_GET_BLUE:
|
||||
case OP_STR_VAL: case OP_STR_STRF: case OP_STR_HEX:
|
||||
case OP_STR_STRING:
|
||||
case OP_MATH_TIMER: case OP_DATE_STR: case OP_TIME_STR:
|
||||
case OP_SLEEP: case OP_ENVIRON:
|
||||
case OP_READ_DATA: case OP_RESTORE:
|
||||
case OP_FILE_WRITE: case OP_FILE_WRITE_SEP: case OP_FILE_WRITE_NL:
|
||||
case OP_FILE_GET: case OP_FILE_PUT: case OP_FILE_SEEK:
|
||||
case OP_FILE_LOF: case OP_FILE_LOC: case OP_FILE_FREEFILE:
|
||||
case OP_FILE_INPUT_N:
|
||||
case OP_STR_MID_ASGN: case OP_PRINT_USING:
|
||||
case OP_PRINT_TAB_N: case OP_PRINT_SPC_N:
|
||||
case OP_FORMAT: case OP_SHELL:
|
||||
case OP_APP_PATH: case OP_APP_CONFIG: case OP_APP_DATA:
|
||||
case OP_INI_READ: case OP_INI_WRITE:
|
||||
case OP_FS_KILL: case OP_FS_NAME: case OP_FS_FILECOPY:
|
||||
case OP_FS_MKDIR: case OP_FS_RMDIR: case OP_FS_CHDIR:
|
||||
case OP_FS_CHDRIVE: case OP_FS_CURDIR: case OP_FS_DIR:
|
||||
case OP_FS_DIR_NEXT: case OP_FS_FILELEN:
|
||||
case OP_FS_GETATTR: case OP_FS_SETATTR:
|
||||
case OP_CREATE_FORM: case OP_SET_EVENT: case OP_REMOVE_CTRL:
|
||||
case OP_END: case OP_HALT:
|
||||
return 0;
|
||||
|
||||
case OP_LOAD_ARRAY: case OP_STORE_ARRAY:
|
||||
case OP_PRINT_SPC: case OP_FILE_OPEN:
|
||||
case OP_CALL_METHOD: case OP_SHOW_FORM:
|
||||
case OP_LBOUND: case OP_UBOUND:
|
||||
case OP_COMPARE_MODE:
|
||||
return 1;
|
||||
|
||||
case OP_PUSH_INT16: case OP_PUSH_STR:
|
||||
case OP_LOAD_LOCAL: case OP_STORE_LOCAL:
|
||||
case OP_LOAD_GLOBAL: case OP_STORE_GLOBAL:
|
||||
case OP_LOAD_FIELD: case OP_STORE_FIELD:
|
||||
case OP_PUSH_LOCAL_ADDR: case OP_PUSH_GLOBAL_ADDR:
|
||||
case OP_JMP: case OP_JMP_TRUE: case OP_JMP_FALSE:
|
||||
case OP_CTRL_REF:
|
||||
case OP_LOAD_FORM_VAR: case OP_STORE_FORM_VAR:
|
||||
case OP_PUSH_FORM_ADDR:
|
||||
case OP_DIM_ARRAY: case OP_REDIM:
|
||||
case OP_ON_ERROR:
|
||||
case OP_STR_FIXLEN:
|
||||
case OP_LINE:
|
||||
return 2;
|
||||
|
||||
case OP_STORE_ARRAY_FIELD:
|
||||
case OP_FOR_INIT:
|
||||
return 3;
|
||||
|
||||
case OP_PUSH_INT32: case OP_PUSH_FLT32:
|
||||
case OP_CALL:
|
||||
return 4;
|
||||
|
||||
case OP_FOR_NEXT:
|
||||
return 5;
|
||||
|
||||
case OP_CALL_EXTERN:
|
||||
return 6;
|
||||
|
||||
case OP_PUSH_FLT64:
|
||||
return 8;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Little-endian helpers (bytecode is always LE regardless of host)
|
||||
// ============================================================
|
||||
|
||||
static int16_t readI16LE(const uint8_t *p) {
|
||||
return (int16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
|
||||
}
|
||||
|
||||
|
||||
static uint16_t readU16LE(const uint8_t *p) {
|
||||
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
|
||||
}
|
||||
|
||||
|
||||
static int32_t readI32LE(const uint8_t *p) {
|
||||
return (int32_t)((uint32_t)p[0] |
|
||||
((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) |
|
||||
((uint32_t)p[3] << 24));
|
||||
}
|
||||
|
||||
|
||||
static void writeI16LE(uint8_t *p, int16_t v) {
|
||||
uint16_t u = (uint16_t)v;
|
||||
p[0] = (uint8_t)(u & 0xFF);
|
||||
p[1] = (uint8_t)((u >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
|
||||
static void writeU16LE(uint8_t *p, uint16_t v) {
|
||||
p[0] = (uint8_t)(v & 0xFF);
|
||||
p[1] = (uint8_t)((v >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
|
||||
static void writeI32LE(uint8_t *p, int32_t v) {
|
||||
uint32_t u = (uint32_t)v;
|
||||
p[0] = (uint8_t)(u & 0xFF);
|
||||
p[1] = (uint8_t)((u >> 8) & 0xFF);
|
||||
p[2] = (uint8_t)((u >> 16) & 0xFF);
|
||||
p[3] = (uint8_t)((u >> 24) & 0xFF);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Walk bytecode, build remap
|
||||
// ============================================================
|
||||
//
|
||||
// remap[oldPos] = newPos for every byte position in [0, oldCodeLen].
|
||||
// For OP_LINE bytes (removed): remap points at where the NEXT instruction
|
||||
// starts in the new code.
|
||||
// Final entry remap[oldCodeLen] = newCodeLen.
|
||||
//
|
||||
// Returns malloc'd array of size (oldCodeLen + 1), or NULL on failure.
|
||||
|
||||
static int32_t *buildRemap(const uint8_t *code, int32_t codeLen, int32_t *outNewLen) {
|
||||
int32_t *remap = (int32_t *)malloc((codeLen + 1) * sizeof(int32_t));
|
||||
|
||||
if (!remap) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32_t oldPc = 0;
|
||||
int32_t newPc = 0;
|
||||
|
||||
while (oldPc < codeLen) {
|
||||
uint8_t op = code[oldPc];
|
||||
int32_t operand = opOperandSize(op);
|
||||
|
||||
if (operand < 0) {
|
||||
free(remap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32_t instSize = 1 + operand;
|
||||
|
||||
if (oldPc + instSize > codeLen) {
|
||||
free(remap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (op == OP_LINE) {
|
||||
// These bytes are removed; they map to where the next instruction starts.
|
||||
for (int32_t i = 0; i < instSize; i++) {
|
||||
remap[oldPc + i] = newPc;
|
||||
}
|
||||
} else {
|
||||
for (int32_t i = 0; i < instSize; i++) {
|
||||
remap[oldPc + i] = newPc + i;
|
||||
}
|
||||
|
||||
newPc += instSize;
|
||||
}
|
||||
|
||||
oldPc += instSize;
|
||||
}
|
||||
|
||||
if (oldPc != codeLen) {
|
||||
free(remap);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
remap[codeLen] = newPc;
|
||||
*outNewLen = newPc;
|
||||
return remap;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// GOSUB pattern detection
|
||||
// ============================================================
|
||||
//
|
||||
// GOSUB emits:
|
||||
// oldPc: OP_PUSH_INT32 (1 byte)
|
||||
// oldPc+1: int32 value V (4 bytes)
|
||||
// oldPc+5: OP_JMP (1 byte)
|
||||
// oldPc+6: int16 offset (2 bytes)
|
||||
// oldPc+8: <next instruction>
|
||||
// The invariant is V == oldPc + 8 (the pushed return address).
|
||||
//
|
||||
// Returns true if the given position is the start of such a pattern.
|
||||
|
||||
static bool isGosubPush(const uint8_t *code, int32_t codeLen, int32_t pos) {
|
||||
if (pos + 8 > codeLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (code[pos] != OP_PUSH_INT32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (code[pos + 5] != OP_JMP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t value = readI32LE(code + pos + 1);
|
||||
return value == pos + 8;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Apply remap to a single instruction's operand
|
||||
// ============================================================
|
||||
//
|
||||
// Returns true on success, false if an offset overflows int16 or a
|
||||
// target doesn't land on a valid instruction in the old code.
|
||||
|
||||
static bool remapAbsU16(uint8_t *newCode, int32_t newOpPos, int32_t operandOffset, uint16_t oldAddr, const int32_t *remap, int32_t newCodeLen) {
|
||||
int32_t newAddr = remap[oldAddr];
|
||||
|
||||
if (newAddr < 0 || newAddr > newCodeLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (newAddr > 0xFFFF) {
|
||||
return false;
|
||||
}
|
||||
|
||||
writeU16LE(newCode + newOpPos + operandOffset, (uint16_t)newAddr);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Rewrite a relative int16 offset.
|
||||
// oldOpPos, oldPcAfter: position of opcode and PC after reading offset
|
||||
// newOpPos, newPcAfter: same in the new code
|
||||
// operandOffset: byte offset from opcode to the int16 operand
|
||||
// oldOffset: the offset as stored in the old code
|
||||
//
|
||||
// Handles ON_ERROR's special case (offset == 0 means "disable").
|
||||
// For ON_ERROR the caller passes allowZero=true; the zero is preserved as-is.
|
||||
|
||||
static bool remapRelI16(uint8_t *newCode, int32_t newOpPos, int32_t operandOffset, int16_t oldOffset, int32_t oldPcAfter, int32_t newPcAfter, const int32_t *remap, int32_t codeLen, int32_t newCodeLen, bool allowZero) {
|
||||
if (allowZero && oldOffset == 0) {
|
||||
writeI16LE(newCode + newOpPos + operandOffset, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t oldTarget = oldPcAfter + oldOffset;
|
||||
|
||||
if (oldTarget < 0 || oldTarget > codeLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t newTarget = remap[oldTarget];
|
||||
|
||||
if (newTarget < 0 || newTarget > newCodeLen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t newOffset = newTarget - newPcAfter;
|
||||
|
||||
if (newOffset < -32768 || newOffset > 32767) {
|
||||
return false;
|
||||
}
|
||||
|
||||
writeI16LE(newCode + newOpPos + operandOffset, (int16_t)newOffset);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// basCompactBytecode
|
||||
// ============================================================
|
||||
|
||||
int32_t basCompactBytecode(BasModuleT *mod) {
|
||||
if (!mod || !mod->code || mod->codeLen <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint8_t *oldCode = mod->code;
|
||||
int32_t oldCodeLen = mod->codeLen;
|
||||
|
||||
// Count OP_LINE occurrences. If none, nothing to do.
|
||||
int32_t lineCount = 0;
|
||||
{
|
||||
int32_t pc = 0;
|
||||
|
||||
while (pc < oldCodeLen) {
|
||||
uint8_t op = oldCode[pc];
|
||||
int32_t operand = opOperandSize(op);
|
||||
|
||||
if (operand < 0 || pc + 1 + operand > oldCodeLen) {
|
||||
return 0; // unknown opcode -- skip compaction
|
||||
}
|
||||
|
||||
if (op == OP_LINE) {
|
||||
lineCount++;
|
||||
}
|
||||
|
||||
pc += 1 + operand;
|
||||
}
|
||||
|
||||
if (pc != oldCodeLen) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (lineCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t newCodeLen = 0;
|
||||
int32_t *remap = buildRemap(oldCode, oldCodeLen, &newCodeLen);
|
||||
|
||||
if (!remap) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t *newCode = (uint8_t *)malloc(newCodeLen > 0 ? newCodeLen : 1);
|
||||
|
||||
if (!newCode) {
|
||||
free(remap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy bytes (skipping OP_LINE) and rewrite address operands.
|
||||
bool ok = true;
|
||||
int32_t oldPc = 0;
|
||||
|
||||
while (oldPc < oldCodeLen && ok) {
|
||||
uint8_t op = oldCode[oldPc];
|
||||
int32_t operand = opOperandSize(op);
|
||||
int32_t instSize = 1 + operand;
|
||||
|
||||
if (op == OP_LINE) {
|
||||
oldPc += instSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t newPc = remap[oldPc];
|
||||
|
||||
// Copy the instruction verbatim first; we'll overwrite operands that
|
||||
// need remapping below.
|
||||
memcpy(newCode + newPc, oldCode + oldPc, instSize);
|
||||
|
||||
switch (op) {
|
||||
case OP_CALL: {
|
||||
uint16_t oldAddr = readU16LE(oldCode + oldPc + 1);
|
||||
|
||||
if (!remapAbsU16(newCode, newPc, 1, oldAddr, remap, newCodeLen)) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_JMP:
|
||||
case OP_JMP_TRUE:
|
||||
case OP_JMP_FALSE: {
|
||||
int16_t oldOff = readI16LE(oldCode + oldPc + 1);
|
||||
|
||||
if (!remapRelI16(newCode, newPc, 1, oldOff,
|
||||
oldPc + 3, newPc + 3,
|
||||
remap, oldCodeLen, newCodeLen, false)) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_FOR_NEXT: {
|
||||
int16_t oldOff = readI16LE(oldCode + oldPc + 4);
|
||||
|
||||
if (!remapRelI16(newCode, newPc, 4, oldOff,
|
||||
oldPc + 6, newPc + 6,
|
||||
remap, oldCodeLen, newCodeLen, false)) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_ON_ERROR: {
|
||||
int16_t oldOff = readI16LE(oldCode + oldPc + 1);
|
||||
|
||||
if (!remapRelI16(newCode, newPc, 1, oldOff,
|
||||
oldPc + 3, newPc + 3,
|
||||
remap, oldCodeLen, newCodeLen, true)) {
|
||||
ok = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_PUSH_INT32: {
|
||||
// Detect GOSUB return-address push and remap the absolute address.
|
||||
if (isGosubPush(oldCode, oldCodeLen, oldPc)) {
|
||||
int32_t oldAddr = readI32LE(oldCode + oldPc + 1);
|
||||
|
||||
if (oldAddr < 0 || oldAddr > oldCodeLen) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
int32_t newAddr = remap[oldAddr];
|
||||
|
||||
if (newAddr < 0 || newAddr > newCodeLen) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
writeI32LE(newCode + newPc + 1, newAddr);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
oldPc += instSize;
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
free(newCode);
|
||||
free(remap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Rewrite proc entry points
|
||||
for (int32_t i = 0; i < mod->procCount; i++) {
|
||||
int32_t oldAddr = mod->procs[i].codeAddr;
|
||||
|
||||
if (oldAddr < 0 || oldAddr > oldCodeLen) {
|
||||
free(newCode);
|
||||
free(remap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
mod->procs[i].codeAddr = remap[oldAddr];
|
||||
}
|
||||
|
||||
// Rewrite form-var init code addresses. Negative means "no init code".
|
||||
for (int32_t i = 0; i < mod->formVarInfoCount; i++) {
|
||||
int32_t oldAddr = mod->formVarInfo[i].initCodeAddr;
|
||||
|
||||
if (oldAddr < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (oldAddr > oldCodeLen) {
|
||||
free(newCode);
|
||||
free(remap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t oldLen = mod->formVarInfo[i].initCodeLen;
|
||||
int32_t oldEnd = oldAddr + oldLen;
|
||||
|
||||
if (oldEnd > oldCodeLen) {
|
||||
oldEnd = oldCodeLen;
|
||||
}
|
||||
|
||||
int32_t newAddr = remap[oldAddr];
|
||||
int32_t newEnd = remap[oldEnd];
|
||||
|
||||
mod->formVarInfo[i].initCodeAddr = newAddr;
|
||||
mod->formVarInfo[i].initCodeLen = newEnd - newAddr;
|
||||
}
|
||||
|
||||
// Rewrite entry point
|
||||
if (mod->entryPoint >= 0 && mod->entryPoint <= oldCodeLen) {
|
||||
mod->entryPoint = remap[mod->entryPoint];
|
||||
}
|
||||
|
||||
// Swap in the new code
|
||||
free(mod->code);
|
||||
mod->code = newCode;
|
||||
mod->codeLen = newCodeLen;
|
||||
|
||||
int32_t removed = oldCodeLen - newCodeLen;
|
||||
|
||||
free(remap);
|
||||
return removed;
|
||||
}
|
||||
21
apps/dvxbasic/compiler/compact.h
Normal file
21
apps/dvxbasic/compiler/compact.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// compact.h -- Release build bytecode compaction
|
||||
//
|
||||
// Removes OP_LINE instructions from the module's bytecode and
|
||||
// rewrites all code-address references (proc entries, CALL operands,
|
||||
// JMP/FOR_NEXT/ON_ERROR relative offsets, GOSUB return addresses,
|
||||
// and formVarInfo init code addresses).
|
||||
//
|
||||
// Safe by construction: if any sanity check fails (unknown opcode,
|
||||
// offset overflow, walk not landing on codeLen, etc.), the module
|
||||
// is left unchanged and the function returns 0.
|
||||
|
||||
#ifndef DVXBASIC_COMPACT_H
|
||||
#define DVXBASIC_COMPACT_H
|
||||
|
||||
#include "../runtime/vm.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// Returns the number of bytes removed, or 0 if compaction was skipped.
|
||||
int32_t basCompactBytecode(BasModuleT *mod);
|
||||
|
||||
#endif // DVXBASIC_COMPACT_H
|
||||
|
|
@ -77,6 +77,7 @@ static const KeywordEntryT sKeywords[] = {
|
|||
{ "IF", TOK_IF },
|
||||
{ "IMP", TOK_IMP },
|
||||
{ "INIREAD", TOK_INIREAD },
|
||||
{ "INIREAD$", TOK_INIREAD },
|
||||
{ "INIWRITE", TOK_INIWRITE },
|
||||
{ "INPUT", TOK_INPUT },
|
||||
{ "INTEGER", TOK_INTEGER },
|
||||
|
|
@ -682,7 +683,16 @@ static BasTokenTypeE tokenizeIdentOrKeyword(BasLexerT *lex) {
|
|||
}
|
||||
}
|
||||
|
||||
BasTokenTypeE kwType = lookupKeyword(lex->token.text, baseLen);
|
||||
// Try the full text first (including any type suffix). Suffix-bearing
|
||||
// keywords like CURDIR$, DIR$, INIREAD$, INPUTBOX$ are listed in the
|
||||
// keyword table with their $ and will match here. If the full text
|
||||
// isn't a keyword, fall back to the base name (without suffix).
|
||||
BasTokenTypeE kwType = lookupKeyword(lex->token.text, idx);
|
||||
bool matchedWithSuffix = (kwType != TOK_IDENT && baseLen != idx);
|
||||
|
||||
if (kwType == TOK_IDENT && baseLen != idx) {
|
||||
kwType = lookupKeyword(lex->token.text, baseLen);
|
||||
}
|
||||
|
||||
// REM is a comment -- skip to end of line
|
||||
if (kwType == TOK_REM) {
|
||||
|
|
@ -694,9 +704,9 @@ static BasTokenTypeE tokenizeIdentOrKeyword(BasLexerT *lex) {
|
|||
return TOK_NEWLINE;
|
||||
}
|
||||
|
||||
// If it's a keyword and has no suffix, return the keyword token.
|
||||
// String-returning builtins (SQLError$, SQLField$) also match with $.
|
||||
if (kwType != TOK_IDENT && (baseLen == idx || kwType == TOK_INPUTBOX)) {
|
||||
// Accept the keyword if it's a plain keyword (no suffix on source) or
|
||||
// if it explicitly matched a $-suffixed entry in the keyword table.
|
||||
if (kwType != TOK_IDENT && (baseLen == idx || matchedWithSuffix)) {
|
||||
return kwType;
|
||||
}
|
||||
|
||||
|
|
|
|||
590
apps/dvxbasic/compiler/obfuscate.c
Normal file
590
apps/dvxbasic/compiler/obfuscate.c
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
// obfuscate.c -- Release build name obfuscation
|
||||
//
|
||||
// See obfuscate.h for the high-level description.
|
||||
|
||||
#include "obfuscate.h"
|
||||
#include "../runtime/values.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// ============================================================
|
||||
// Event suffix list (must match strip.c)
|
||||
// ============================================================
|
||||
|
||||
static const char *sEventSuffixes[] = {
|
||||
"Load", "Unload", "QueryUnload", "Resize", "Activate", "Deactivate",
|
||||
"Click", "DblClick", "Change", "Timer",
|
||||
"GotFocus", "LostFocus",
|
||||
"KeyPress", "KeyDown", "KeyUp",
|
||||
"MouseDown", "MouseUp", "MouseMove",
|
||||
"Scroll", "Reposition", "Validate",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Name map
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
char *orig; // original name (strdup'd, case-preserved)
|
||||
char *mapped; // new name (strdup'd, "C1" .. "Cn")
|
||||
} NameEntryT;
|
||||
|
||||
typedef struct {
|
||||
NameEntryT *entries;
|
||||
int32_t count;
|
||||
int32_t cap;
|
||||
} NameMapT;
|
||||
|
||||
|
||||
static void nameMapInit(NameMapT *m) {
|
||||
m->entries = NULL;
|
||||
m->count = 0;
|
||||
m->cap = 0;
|
||||
}
|
||||
|
||||
|
||||
static void nameMapFree(NameMapT *m) {
|
||||
for (int32_t i = 0; i < m->count; i++) {
|
||||
free(m->entries[i].orig);
|
||||
free(m->entries[i].mapped);
|
||||
}
|
||||
|
||||
free(m->entries);
|
||||
m->entries = NULL;
|
||||
m->count = 0;
|
||||
m->cap = 0;
|
||||
}
|
||||
|
||||
|
||||
// Look up an original name (case-insensitive). Returns mapped name or NULL.
|
||||
static const char *nameMapLookup(const NameMapT *m, const char *name) {
|
||||
for (int32_t i = 0; i < m->count; i++) {
|
||||
if (strcasecmp(m->entries[i].orig, name) == 0) {
|
||||
return m->entries[i].mapped;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// Add a name if not already present. Returns mapped name.
|
||||
static const char *nameMapAdd(NameMapT *m, const char *name) {
|
||||
const char *existing = nameMapLookup(m, name);
|
||||
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
if (m->count >= m->cap) {
|
||||
int32_t newCap = m->cap == 0 ? 16 : m->cap * 2;
|
||||
NameEntryT *newEntries = realloc(m->entries, newCap * sizeof(NameEntryT));
|
||||
|
||||
if (!newEntries) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
m->entries = newEntries;
|
||||
m->cap = newCap;
|
||||
}
|
||||
|
||||
char mapped[16];
|
||||
snprintf(mapped, sizeof(mapped), "C%ld", (long)(m->count + 1));
|
||||
|
||||
m->entries[m->count].orig = strdup(name);
|
||||
m->entries[m->count].mapped = strdup(mapped);
|
||||
m->count++;
|
||||
|
||||
return m->entries[m->count - 1].mapped;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// .frm parsing helpers
|
||||
// ============================================================
|
||||
|
||||
// Skip ASCII whitespace. Returns pointer past whitespace.
|
||||
static const char *skipWhitespace(const char *p, const char *end) {
|
||||
while (p < end && (*p == ' ' || *p == '\t')) {
|
||||
p++;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
// Copy next whitespace-delimited token into buf. Returns pointer after token.
|
||||
static const char *readToken(const char *p, const char *end, char *buf, int32_t bufSize) {
|
||||
int32_t len = 0;
|
||||
|
||||
while (p < end && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' && len < bufSize - 1) {
|
||||
buf[len++] = *p++;
|
||||
}
|
||||
|
||||
buf[len] = '\0';
|
||||
return p;
|
||||
}
|
||||
|
||||
|
||||
// Check if name is a valid identifier (letters, digits, underscore, starts non-digit)
|
||||
static bool isValidIdent(const char *name) {
|
||||
if (!name || !*name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isalpha((unsigned char)name[0]) && name[0] != '_') {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const char *p = name; *p; p++) {
|
||||
if (!isalnum((unsigned char)*p) && *p != '_') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Pass 1: collect all form/control names from .frm texts
|
||||
// ============================================================
|
||||
|
||||
// Scan a .frm text and add all "Begin <Type> <Name>" names to the map.
|
||||
static void collectNamesFromFrm(const char *text, int32_t len, NameMapT *map) {
|
||||
const char *p = text;
|
||||
const char *end = text + len;
|
||||
|
||||
while (p < end) {
|
||||
// Read one line
|
||||
const char *lineStart = p;
|
||||
|
||||
while (p < end && *p != '\n' && *p != '\r') {
|
||||
p++;
|
||||
}
|
||||
|
||||
const char *lineEnd = p;
|
||||
|
||||
if (p < end && *p == '\r') {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p < end && *p == '\n') {
|
||||
p++;
|
||||
}
|
||||
|
||||
// Trim leading whitespace
|
||||
const char *l = skipWhitespace(lineStart, lineEnd);
|
||||
|
||||
// Check "Begin "
|
||||
if ((lineEnd - l) < 6 || strncasecmp(l, "Begin ", 6) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
l += 6;
|
||||
l = skipWhitespace(l, lineEnd);
|
||||
|
||||
// Read type name
|
||||
char typeName[64];
|
||||
l = readToken(l, lineEnd, typeName, sizeof(typeName));
|
||||
|
||||
if (typeName[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read control name
|
||||
l = skipWhitespace(l, lineEnd);
|
||||
char ctrlName[64];
|
||||
l = readToken(l, lineEnd, ctrlName, sizeof(ctrlName));
|
||||
|
||||
if (ctrlName[0] && isValidIdent(ctrlName)) {
|
||||
nameMapAdd(map, ctrlName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Pass 2: strip BASIC code from .frm text (everything after outer End)
|
||||
// ============================================================
|
||||
|
||||
// Find the position just after the matching End of the outermost Begin Form.
|
||||
// Returns len of the stripped .frm. If no Begin Form found, returns original len.
|
||||
static int32_t findFormEndPos(const char *text, int32_t len) {
|
||||
int32_t nesting = 0;
|
||||
bool inForm = false;
|
||||
|
||||
const char *p = text;
|
||||
const char *end = text + len;
|
||||
|
||||
while (p < end) {
|
||||
const char *lineStart = p;
|
||||
|
||||
while (p < end && *p != '\n' && *p != '\r') {
|
||||
p++;
|
||||
}
|
||||
|
||||
const char *lineEnd = p;
|
||||
|
||||
if (p < end && *p == '\r') {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p < end && *p == '\n') {
|
||||
p++;
|
||||
}
|
||||
|
||||
const char *l = skipWhitespace(lineStart, lineEnd);
|
||||
|
||||
if ((lineEnd - l) >= 6 && strncasecmp(l, "Begin ", 6) == 0) {
|
||||
// Check for "Begin Form ..." to set inForm on outer open
|
||||
if (!inForm) {
|
||||
const char *r = l + 6;
|
||||
r = skipWhitespace(r, lineEnd);
|
||||
|
||||
if ((lineEnd - r) >= 5 && strncasecmp(r, "Form ", 5) == 0) {
|
||||
inForm = true;
|
||||
}
|
||||
}
|
||||
|
||||
nesting++;
|
||||
} else if ((lineEnd - l) >= 3 && strncasecmp(l, "End", 3) == 0 &&
|
||||
(lineEnd - l == 3 || l[3] == ' ' || l[3] == '\t' || l[3] == '\r')) {
|
||||
nesting--;
|
||||
|
||||
if (inForm && nesting == 0) {
|
||||
return (int32_t)(p - text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Pass 3: rewrite .frm text with mapped names
|
||||
// ============================================================
|
||||
|
||||
// Returns true if c is a valid identifier character.
|
||||
static bool isIdentChar(int c) {
|
||||
return isalnum(c) || c == '_';
|
||||
}
|
||||
|
||||
|
||||
// Scan text; for each identifier found outside of strings, if it's in
|
||||
// the map, emit the mapped name instead. Output to out (returns bytes written).
|
||||
static int32_t rewriteFrmText(const char *src, int32_t srcLen, const NameMapT *map, uint8_t *out, int32_t outCap) {
|
||||
int32_t outLen = 0;
|
||||
int32_t i = 0;
|
||||
bool inStr = false;
|
||||
|
||||
while (i < srcLen) {
|
||||
char c = src[i];
|
||||
|
||||
if (c == '"') {
|
||||
inStr = !inStr;
|
||||
|
||||
if (outLen < outCap) {
|
||||
out[outLen++] = (uint8_t)c;
|
||||
}
|
||||
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read identifier
|
||||
if (!inStr && (isalpha((unsigned char)c) || c == '_')) {
|
||||
int32_t identStart = i;
|
||||
|
||||
while (i < srcLen && isIdentChar((unsigned char)src[i])) {
|
||||
i++;
|
||||
}
|
||||
|
||||
int32_t identLen = i - identStart;
|
||||
char ident[128];
|
||||
|
||||
if (identLen >= (int32_t)sizeof(ident)) {
|
||||
identLen = (int32_t)sizeof(ident) - 1;
|
||||
}
|
||||
|
||||
memcpy(ident, src + identStart, identLen);
|
||||
ident[identLen] = '\0';
|
||||
|
||||
const char *mapped = nameMapLookup(map, ident);
|
||||
|
||||
if (mapped) {
|
||||
int32_t mLen = (int32_t)strlen(mapped);
|
||||
|
||||
for (int32_t k = 0; k < mLen && outLen < outCap; k++) {
|
||||
out[outLen++] = (uint8_t)mapped[k];
|
||||
}
|
||||
} else {
|
||||
for (int32_t k = 0; k < identLen && outLen < outCap; k++) {
|
||||
out[outLen++] = (uint8_t)ident[k];
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (outLen < outCap) {
|
||||
out[outLen++] = (uint8_t)c;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return outLen;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Module rewriting
|
||||
// ============================================================
|
||||
|
||||
// Replace the contents of a constant pool entry with a new string.
|
||||
static void replaceConstant(BasModuleT *mod, int32_t idx, const char *newText) {
|
||||
BasStringT *newStr = basStringNew(newText, (int32_t)strlen(newText));
|
||||
|
||||
if (!newStr) {
|
||||
return;
|
||||
}
|
||||
|
||||
basStringUnref(mod->constants[idx]);
|
||||
mod->constants[idx] = newStr;
|
||||
}
|
||||
|
||||
|
||||
static void rewriteModuleConstants(BasModuleT *mod, const NameMapT *map) {
|
||||
for (int32_t i = 0; i < mod->constCount; i++) {
|
||||
const BasStringT *s = mod->constants[i];
|
||||
|
||||
if (!s) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *mapped = nameMapLookup(map, s->data);
|
||||
|
||||
if (mapped) {
|
||||
replaceConstant(mod, i, mapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check if suffix is a known event name.
|
||||
static bool isEventSuffix(const char *suffix) {
|
||||
for (int32_t i = 0; sEventSuffixes[i]; i++) {
|
||||
if (strcasecmp(suffix, sEventSuffixes[i]) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static void rewriteModuleProcs(BasModuleT *mod, const NameMapT *map) {
|
||||
for (int32_t i = 0; i < mod->procCount; i++) {
|
||||
BasProcEntryT *proc = &mod->procs[i];
|
||||
|
||||
if (proc->name[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find last underscore
|
||||
char *underscore = strrchr(proc->name, '_');
|
||||
|
||||
if (!underscore) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *suffix = underscore + 1;
|
||||
|
||||
if (!isEventSuffix(suffix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split on underscore
|
||||
int32_t prefixLen = (int32_t)(underscore - proc->name);
|
||||
char prefix[BAS_MAX_PROC_NAME];
|
||||
|
||||
if (prefixLen >= (int32_t)sizeof(prefix)) {
|
||||
prefixLen = (int32_t)sizeof(prefix) - 1;
|
||||
}
|
||||
|
||||
memcpy(prefix, proc->name, prefixLen);
|
||||
prefix[prefixLen] = '\0';
|
||||
|
||||
const char *mapped = nameMapLookup(map, prefix);
|
||||
|
||||
if (mapped) {
|
||||
char newName[BAS_MAX_PROC_NAME];
|
||||
snprintf(newName, sizeof(newName), "%s_%s", mapped, suffix);
|
||||
snprintf(proc->name, sizeof(proc->name), "%s", newName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void rewriteModuleFormVars(BasModuleT *mod, const NameMapT *map) {
|
||||
for (int32_t i = 0; i < mod->formVarInfoCount; i++) {
|
||||
BasFormVarInfoT *fv = &mod->formVarInfo[i];
|
||||
const char *mapped = nameMapLookup(map, fv->formName);
|
||||
|
||||
if (mapped) {
|
||||
snprintf(fv->formName, sizeof(fv->formName), "%s", mapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// basStripFrmComments
|
||||
// ============================================================
|
||||
|
||||
int32_t basStripFrmComments(const char *src, int32_t srcLen, uint8_t *outBuf, int32_t outCap) {
|
||||
if (!src || srcLen <= 0 || !outBuf || outCap <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t outLen = 0;
|
||||
int32_t i = 0;
|
||||
|
||||
while (i < srcLen) {
|
||||
int32_t lineStart = i;
|
||||
|
||||
while (i < srcLen && src[i] != '\n' && src[i] != '\r') {
|
||||
i++;
|
||||
}
|
||||
|
||||
int32_t lineEnd = i;
|
||||
|
||||
if (i < srcLen && src[i] == '\r') {
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i < srcLen && src[i] == '\n') {
|
||||
i++;
|
||||
}
|
||||
|
||||
// Scan for first unquoted ' (comment start).
|
||||
bool inStr = false;
|
||||
int32_t commentStart = -1;
|
||||
|
||||
for (int32_t j = lineStart; j < lineEnd; j++) {
|
||||
char c = src[j];
|
||||
|
||||
if (c == '"') {
|
||||
inStr = !inStr;
|
||||
} else if (c == '\'' && !inStr) {
|
||||
commentStart = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t contentEnd = (commentStart >= 0) ? commentStart : lineEnd;
|
||||
|
||||
// Check for whole-line REM. Find first non-whitespace position.
|
||||
int32_t firstNonWs = lineStart;
|
||||
|
||||
while (firstNonWs < contentEnd && (src[firstNonWs] == ' ' || src[firstNonWs] == '\t')) {
|
||||
firstNonWs++;
|
||||
}
|
||||
|
||||
if (contentEnd - firstNonWs >= 3 &&
|
||||
strncasecmp(src + firstNonWs, "REM", 3) == 0 &&
|
||||
(contentEnd - firstNonWs == 3 ||
|
||||
src[firstNonWs + 3] == ' ' ||
|
||||
src[firstNonWs + 3] == '\t')) {
|
||||
contentEnd = firstNonWs;
|
||||
}
|
||||
|
||||
// Trim trailing whitespace.
|
||||
while (contentEnd > lineStart && (src[contentEnd - 1] == ' ' || src[contentEnd - 1] == '\t')) {
|
||||
contentEnd--;
|
||||
}
|
||||
|
||||
// Drop lines that have no non-whitespace content.
|
||||
if (contentEnd <= firstNonWs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t writeLen = contentEnd - lineStart;
|
||||
|
||||
if (outLen + writeLen + 1 >= outCap) {
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(outBuf + outLen, src + lineStart, writeLen);
|
||||
outLen += writeLen;
|
||||
outBuf[outLen++] = '\n';
|
||||
}
|
||||
|
||||
return outLen;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Top-level entry point
|
||||
// ============================================================
|
||||
|
||||
void basObfuscateNames(BasModuleT *mod, const char **frmTexts, const int32_t *frmLens, int32_t frmCount, BasObfFrmT *outFrms) {
|
||||
if (!mod || frmCount < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
NameMapT map;
|
||||
nameMapInit(&map);
|
||||
|
||||
// Pass 1: collect all names from all .frm texts
|
||||
for (int32_t i = 0; i < frmCount; i++) {
|
||||
if (frmTexts[i] && frmLens[i] > 0) {
|
||||
collectNamesFromFrm(frmTexts[i], frmLens[i], &map);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: rewrite each .frm
|
||||
for (int32_t i = 0; i < frmCount; i++) {
|
||||
outFrms[i].data = NULL;
|
||||
outFrms[i].len = 0;
|
||||
|
||||
if (!frmTexts[i] || frmLens[i] <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t strippedLen = findFormEndPos(frmTexts[i], frmLens[i]);
|
||||
|
||||
// Allocate generous output buffer (mapped names are usually shorter
|
||||
// than originals, but allow for growth and a trailing newline).
|
||||
int32_t outCap = strippedLen + 1024;
|
||||
uint8_t *outBuf = malloc(outCap);
|
||||
|
||||
if (!outBuf) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t outLen = rewriteFrmText(frmTexts[i], strippedLen, &map, outBuf, outCap);
|
||||
|
||||
// Ensure trailing newline
|
||||
if (outLen > 0 && outBuf[outLen - 1] != '\n' && outLen < outCap) {
|
||||
outBuf[outLen++] = '\n';
|
||||
}
|
||||
|
||||
outFrms[i].data = outBuf;
|
||||
outFrms[i].len = outLen;
|
||||
}
|
||||
|
||||
// Pass 3: rewrite module
|
||||
rewriteModuleConstants(mod, &map);
|
||||
rewriteModuleProcs(mod, &map);
|
||||
rewriteModuleFormVars(mod, &map);
|
||||
|
||||
nameMapFree(&map);
|
||||
}
|
||||
46
apps/dvxbasic/compiler/obfuscate.h
Normal file
46
apps/dvxbasic/compiler/obfuscate.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// obfuscate.h -- Release build name obfuscation
|
||||
//
|
||||
// Replaces form and control names with generated tokens (C1, C2, ...)
|
||||
// across the compiled module AND the raw .frm text resources. This
|
||||
// hinders casual decompilation by removing meaningful identifiers
|
||||
// from event handler names (e.g., BtnHello_Click -> C3_Click).
|
||||
|
||||
#ifndef DVXBASIC_OBFUSCATE_H
|
||||
#define DVXBASIC_OBFUSCATE_H
|
||||
|
||||
#include "../runtime/vm.h"
|
||||
|
||||
// One .frm text after obfuscation (data is newly-allocated).
|
||||
typedef struct {
|
||||
uint8_t *data;
|
||||
int32_t len;
|
||||
} BasObfFrmT;
|
||||
|
||||
// Obfuscate form/control names in the module and all .frm texts.
|
||||
//
|
||||
// Reads original names from the Begin declarations in each .frm,
|
||||
// generates C1..Cn, then rewrites:
|
||||
// - The .frm text (form/control name declarations, stripping the
|
||||
// trailing BASIC code section after the outer form closes)
|
||||
// - Module string constants matching any original name
|
||||
// - Procedure names matching <OrigName>_<Event>
|
||||
// - formVarInfo entries keyed by form name
|
||||
//
|
||||
// frmTexts / frmLens: input .frm buffers (one per form).
|
||||
// outFrms: caller-allocated array of frmCount entries; function fills
|
||||
// in .data (malloc'd, caller frees) and .len for each.
|
||||
void basObfuscateNames(BasModuleT *mod, const char **frmTexts, const int32_t *frmLens, int32_t frmCount, BasObfFrmT *outFrms);
|
||||
|
||||
|
||||
// Strip comments from .frm text.
|
||||
//
|
||||
// Removes both whole-line comments (lines whose first non-whitespace
|
||||
// token is `'` or `REM`) and trailing comments (everything from the
|
||||
// first unquoted `'` to end of line). Pure-whitespace lines are
|
||||
// dropped. Quoted string literals are preserved as-is.
|
||||
//
|
||||
// srcLen: input length; outBuf: caller-allocated buffer of outCap bytes.
|
||||
// Returns the number of bytes written.
|
||||
int32_t basStripFrmComments(const char *src, int32_t srcLen, uint8_t *outBuf, int32_t outCap);
|
||||
|
||||
#endif // DVXBASIC_OBFUSCATE_H
|
||||
|
|
@ -235,6 +235,9 @@
|
|||
#define OP_MATH_EXP 0xAA
|
||||
#define OP_MATH_RND 0xAB
|
||||
#define OP_MATH_RANDOMIZE 0xAC // seed on stack (or TIMER if -1)
|
||||
#define OP_RGB 0xAD // pop b, g, r; push LONG = (r<<16)|(g<<8)|b
|
||||
#define OP_GET_RED 0xAE // pop LONG color; push (color>>16) & 0xFF
|
||||
#define OP_GET_GREEN 0xAF // pop LONG color; push (color>>8) & 0xFF
|
||||
|
||||
// ============================================================
|
||||
// Conversion built-ins
|
||||
|
|
@ -317,6 +320,8 @@
|
|||
|
||||
#define OP_CALL_EXTERN 0xCD // [uint16 libNameIdx] [uint16 funcNameIdx] [uint8 argc] [uint8 retType]
|
||||
|
||||
#define OP_GET_BLUE 0xD0 // pop LONG color; push color & 0xFF
|
||||
|
||||
// App object
|
||||
#define OP_APP_PATH 0xDD // push App.Path string
|
||||
#define OP_APP_CONFIG 0xDE // push App.Config string
|
||||
|
|
|
|||
|
|
@ -77,8 +77,12 @@ static const BuiltinFuncT builtinFuncs[] = {
|
|||
{"COS", OP_MATH_COS, 1, 1, BAS_TYPE_DOUBLE},
|
||||
{"EXP", OP_MATH_EXP, 1, 1, BAS_TYPE_DOUBLE},
|
||||
{"FIX", OP_MATH_FIX, 1, 1, BAS_TYPE_INTEGER},
|
||||
{"GETBLUE", OP_GET_BLUE, 1, 1, BAS_TYPE_INTEGER},
|
||||
{"GETGREEN", OP_GET_GREEN, 1, 1, BAS_TYPE_INTEGER},
|
||||
{"GETRED", OP_GET_RED, 1, 1, BAS_TYPE_INTEGER},
|
||||
{"INT", OP_MATH_INT, 1, 1, BAS_TYPE_INTEGER},
|
||||
{"LOG", OP_MATH_LOG, 1, 1, BAS_TYPE_DOUBLE},
|
||||
{"RGB", OP_RGB, 3, 3, BAS_TYPE_LONG},
|
||||
{"RND", OP_MATH_RND, 0, 1, BAS_TYPE_DOUBLE},
|
||||
{"SGN", OP_MATH_SGN, 1, 1, BAS_TYPE_INTEGER},
|
||||
{"SIN", OP_MATH_SIN, 1, 1, BAS_TYPE_DOUBLE},
|
||||
|
|
@ -1250,7 +1254,9 @@ static void parsePrimary(BasParserT *p) {
|
|||
} else if (checkKeyword(p,"Config")) {
|
||||
advance(p);
|
||||
basEmit8(&p->cg, OP_APP_CONFIG);
|
||||
} else if (checkKeyword(p,"Data")) {
|
||||
} else if (checkKeyword(p,"Data") || p->lex.token.type == TOK_DATA) {
|
||||
// "Data" tokenizes as TOK_DATA (the DATA/READ keyword) rather
|
||||
// than TOK_IDENT, so accept it directly by token type too.
|
||||
advance(p);
|
||||
basEmit8(&p->cg, OP_APP_DATA);
|
||||
} else {
|
||||
|
|
@ -1482,23 +1488,32 @@ static void parsePrimary(BasParserT *p) {
|
|||
return;
|
||||
}
|
||||
|
||||
// MsgBox(message [, flags]) -- as function expression returning button ID
|
||||
// MsgBox(message [, flags [, title]]) -- function form returning button ID
|
||||
if (tt == TOK_MSGBOX) {
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p); // message
|
||||
|
||||
if (match(p, TOK_COMMA)) {
|
||||
parseExpression(p); // flags
|
||||
} else {
|
||||
basEmit8(&p->cg, OP_PUSH_INT16);
|
||||
basEmit16(&p->cg, 0); // default flags = vbOKOnly
|
||||
}
|
||||
|
||||
if (match(p, TOK_COMMA)) {
|
||||
parseExpression(p); // title
|
||||
} else {
|
||||
uint16_t emptyIdx = basAddConstant(&p->cg, "", 0);
|
||||
basEmit8(&p->cg, OP_PUSH_STR);
|
||||
basEmitU16(&p->cg, emptyIdx);
|
||||
}
|
||||
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_MSGBOX);
|
||||
return;
|
||||
}
|
||||
|
||||
// SQL expression functions -- all require parentheses
|
||||
// IniRead$(file, section, key, default)
|
||||
if (tt == TOK_INIREAD) {
|
||||
advance(p);
|
||||
|
|
@ -2251,6 +2266,17 @@ static void parseAssignOrCall(BasParserT *p) {
|
|||
error(p, buf);
|
||||
return;
|
||||
}
|
||||
|
||||
// External library SUB: emit OP_CALL_EXTERN
|
||||
if (sym->isExtern) {
|
||||
basEmit8(&p->cg, OP_CALL_EXTERN);
|
||||
basEmitU16(&p->cg, sym->externLibIdx);
|
||||
basEmitU16(&p->cg, sym->externFuncIdx);
|
||||
basEmit8(&p->cg, (uint8_t)argc);
|
||||
basEmit8(&p->cg, sym->dataType);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
uint8_t baseSlot = (sym->kind == SYM_FUNCTION) ? 1 : 0;
|
||||
basEmit8(&p->cg, OP_CALL);
|
||||
|
|
@ -2371,7 +2397,7 @@ static void parseClose(BasParserT *p) {
|
|||
|
||||
|
||||
static void parseConst(BasParserT *p) {
|
||||
// CONST name = value
|
||||
// CONST name [AS type] = value
|
||||
advance(p); // consume CONST
|
||||
|
||||
if (!check(p, TOK_IDENT)) {
|
||||
|
|
@ -2384,6 +2410,12 @@ static void parseConst(BasParserT *p) {
|
|||
name[BAS_MAX_TOKEN_LEN - 1] = '\0';
|
||||
advance(p);
|
||||
|
||||
// Optional type annotation (declarative; value's literal type still
|
||||
// determines runtime representation).
|
||||
if (match(p, TOK_AS)) {
|
||||
(void)resolveTypeName(p);
|
||||
}
|
||||
|
||||
expect(p, TOK_EQ);
|
||||
|
||||
// Parse the constant value (must be a literal)
|
||||
|
|
@ -2711,9 +2743,25 @@ static void parseDeclareLibrary(BasParserT *p) {
|
|||
char funcName[BAS_MAX_TOKEN_LEN];
|
||||
strncpy(funcName, p->lex.token.text, BAS_MAX_TOKEN_LEN - 1);
|
||||
funcName[BAS_MAX_TOKEN_LEN - 1] = '\0';
|
||||
uint16_t funcNameIdx = basAddConstant(&p->cg, funcName, (int32_t)strlen(funcName));
|
||||
advance(p);
|
||||
|
||||
// Strip type suffix ($%&!#) from extern name so dlsym finds the
|
||||
// C function. The suffix is still used for return type via suffixToType.
|
||||
char externName[BAS_MAX_TOKEN_LEN];
|
||||
strncpy(externName, funcName, BAS_MAX_TOKEN_LEN - 1);
|
||||
externName[BAS_MAX_TOKEN_LEN - 1] = '\0';
|
||||
int32_t enLen = (int32_t)strlen(externName);
|
||||
|
||||
if (enLen > 0) {
|
||||
char last = externName[enLen - 1];
|
||||
|
||||
if (last == '$' || last == '%' || last == '&' || last == '!' || last == '#') {
|
||||
externName[enLen - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t funcNameIdx = basAddConstant(&p->cg, externName, (int32_t)strlen(externName));
|
||||
|
||||
// Parse parameter list
|
||||
int32_t paramCount = 0;
|
||||
uint8_t paramTypes[BAS_MAX_PARAMS];
|
||||
|
|
@ -4191,20 +4239,49 @@ static void parsePrint(BasParserT *p) {
|
|||
// PRINT USING "fmt"; expr [; expr] ...
|
||||
advance(p); // consume PRINT
|
||||
|
||||
// Check for file I/O: PRINT #channel, expr
|
||||
// File I/O: PRINT #channel, expr [; expr | , expr ]* [;]
|
||||
//
|
||||
// Channel is pushed once and DUP'd per value. `;` means no separator
|
||||
// between values; `,` is treated the same (tab-zone separator not
|
||||
// supported for file output). Trailing `;` suppresses the final newline.
|
||||
if (check(p, TOK_HASH)) {
|
||||
advance(p); // consume #
|
||||
|
||||
// Channel number
|
||||
// Channel number -- stays on stack as "keep" for the whole statement.
|
||||
parseExpression(p);
|
||||
|
||||
// Comma separator
|
||||
expect(p, TOK_COMMA);
|
||||
|
||||
// Value to print
|
||||
parseExpression(p);
|
||||
bool trailingSep = false;
|
||||
|
||||
for (;;) {
|
||||
// Duplicate the channel for this OP_FILE_PRINT.
|
||||
basEmit8(&p->cg, OP_DUP);
|
||||
parseExpression(p);
|
||||
basEmit8(&p->cg, OP_FILE_PRINT);
|
||||
|
||||
if (check(p, TOK_SEMICOLON) || check(p, TOK_COMMA)) {
|
||||
advance(p);
|
||||
trailingSep = true;
|
||||
|
||||
if (check(p, TOK_NEWLINE) || check(p, TOK_EOF) || check(p, TOK_COLON) || check(p, TOK_ELSE)) {
|
||||
break;
|
||||
}
|
||||
|
||||
trailingSep = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// No trailing ; or , -> write newline. Otherwise, just drop the
|
||||
// kept channel value.
|
||||
if (trailingSep) {
|
||||
basEmit8(&p->cg, OP_POP);
|
||||
} else {
|
||||
basEmit8(&p->cg, OP_FILE_WRITE_NL);
|
||||
}
|
||||
|
||||
basEmit8(&p->cg, OP_FILE_PRINT);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -5187,21 +5264,31 @@ static void parseStatement(BasParserT *p) {
|
|||
basEmit8(&p->cg, OP_POP); // discard result
|
||||
break;
|
||||
|
||||
case TOK_MSGBOX:
|
||||
// MsgBox message [, flags] (statement form, discard result)
|
||||
case TOK_MSGBOX: {
|
||||
// MsgBox message [, flags [, title]] (statement form, discards result)
|
||||
advance(p);
|
||||
parseExpression(p); // message
|
||||
|
||||
if (match(p, TOK_COMMA)) {
|
||||
parseExpression(p); // flags
|
||||
} else {
|
||||
basEmit8(&p->cg, OP_PUSH_INT16);
|
||||
basEmit16(&p->cg, 0); // default flags = MB_OK
|
||||
}
|
||||
|
||||
if (match(p, TOK_COMMA)) {
|
||||
parseExpression(p); // title
|
||||
} else {
|
||||
uint16_t emptyIdx = basAddConstant(&p->cg, "", 0);
|
||||
basEmit8(&p->cg, OP_PUSH_STR);
|
||||
basEmitU16(&p->cg, emptyIdx);
|
||||
}
|
||||
|
||||
basEmit8(&p->cg, OP_MSGBOX);
|
||||
basEmit8(&p->cg, OP_POP); // discard result
|
||||
break;
|
||||
}
|
||||
|
||||
// SQL statement forms (no return value)
|
||||
case TOK_INIWRITE:
|
||||
// IniWrite file, section, key, value
|
||||
advance(p);
|
||||
|
|
|
|||
|
|
@ -3,19 +3,68 @@
|
|||
// Removes debug information from a compiled module:
|
||||
// - Clears debug variable info (names, scopes, types)
|
||||
// - Clears debug UDT definitions
|
||||
//
|
||||
// Procedure names are preserved because the form runtime uses
|
||||
// them for event dispatch (ControlName_EventName convention).
|
||||
// - Mangles procedure names that aren't needed for runtime dispatch.
|
||||
// The form runtime dispatches events by name (Control_Event pattern)
|
||||
// and SetEvent looks up handlers by name at runtime, so those proc
|
||||
// names must be preserved. Everything else becomes F1, F2, F3...
|
||||
//
|
||||
// OP_LINE removal is deferred to a future version (requires
|
||||
// bytecode compaction and offset rewriting).
|
||||
|
||||
#include "strip.h"
|
||||
#include "../runtime/values.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
// Events fired by name via basFormRtFireEvent* in formrt.c. Any proc
|
||||
// ending in "_<EventName>" must keep its name so the dispatcher can
|
||||
// find it.
|
||||
static const char *sEventSuffixes[] = {
|
||||
"Load", "Unload", "QueryUnload", "Resize", "Activate", "Deactivate",
|
||||
"Click", "DblClick", "Change", "Timer",
|
||||
"GotFocus", "LostFocus",
|
||||
"KeyPress", "KeyDown", "KeyUp",
|
||||
"MouseDown", "MouseUp", "MouseMove",
|
||||
"Scroll", "Reposition", "Validate",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
static bool nameEndsWithEventSuffix(const char *name) {
|
||||
const char *underscore = strrchr(name, '_');
|
||||
|
||||
if (!underscore) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *suffix = underscore + 1;
|
||||
|
||||
for (int32_t i = 0; sEventSuffixes[i]; i++) {
|
||||
if (strcasecmp(suffix, sEventSuffixes[i]) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool nameInConstantPool(const BasModuleT *mod, const char *name) {
|
||||
for (int32_t i = 0; i < mod->constCount; i++) {
|
||||
const BasStringT *s = mod->constants[i];
|
||||
|
||||
if (s && strcasecmp(s->data, name) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void basStripModule(BasModuleT *mod) {
|
||||
if (!mod) {
|
||||
return;
|
||||
|
|
@ -36,4 +85,27 @@ void basStripModule(BasModuleT *mod) {
|
|||
mod->debugUdtDefs = NULL;
|
||||
mod->debugUdtDefCount = 0;
|
||||
}
|
||||
|
||||
// Mangle proc names. Keep names that are needed for runtime name
|
||||
// lookup: event handlers (Control_Event pattern) and any name
|
||||
// referenced as a string constant (e.g. SetEvent's target name).
|
||||
int32_t nextMangled = 1;
|
||||
|
||||
for (int32_t i = 0; i < mod->procCount; i++) {
|
||||
BasProcEntryT *proc = &mod->procs[i];
|
||||
|
||||
if (proc->name[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nameEndsWithEventSuffix(proc->name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nameInConstantPool(mod, proc->name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
snprintf(proc->name, sizeof(proc->name), "F%ld", (long)nextMangled++);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
// strip.h -- Release build stripping
|
||||
//
|
||||
// Removes debug information from a compiled module to prevent
|
||||
// decompilation. Clears procedure names, debug variable info,
|
||||
// and debug UDT definitions.
|
||||
// Removes debug information from a compiled module to hinder
|
||||
// decompilation. Clears debug variable info and debug UDT
|
||||
// definitions, and mangles proc names that aren't needed for
|
||||
// runtime name-based dispatch.
|
||||
|
||||
#ifndef DVXBASIC_STRIP_H
|
||||
#define DVXBASIC_STRIP_H
|
||||
|
|
@ -10,9 +11,11 @@
|
|||
#include "../runtime/vm.h"
|
||||
|
||||
// Strip debug info from a module for release builds:
|
||||
// - Clear all procedure names
|
||||
// - Clear debug variable info
|
||||
// - Clear debug UDT definitions
|
||||
// - Mangle proc names to F1, F2, ... except for event handlers
|
||||
// (matched by Control_Event suffix) and names referenced as
|
||||
// string constants (SetEvent dispatch targets).
|
||||
void basStripModule(BasModuleT *mod);
|
||||
|
||||
#endif // DVXBASIC_STRIP_H
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "formcfm.h"
|
||||
#include "../compiler/opcodes.h"
|
||||
#include "dvxDlg.h"
|
||||
#include "dvxRes.h"
|
||||
#include "dvxWm.h"
|
||||
#include "box/box.h"
|
||||
#include "ansiTerm/ansiTerm.h"
|
||||
|
|
@ -1291,20 +1292,27 @@ void *basFormRtLoadForm(void *ctx, const char *formName) {
|
|||
}
|
||||
}
|
||||
|
||||
// Check the .frm cache for reload after unload
|
||||
for (int32_t i = 0; i < rt->frmCacheCount; i++) {
|
||||
if (strcasecmp(rt->frmCache[i].formName, formName) == 0) {
|
||||
// Save source and remove from cache BEFORE calling basFormRtLoadFrm,
|
||||
// because basFormRtLoadFrm internally calls basFormRtLoadForm (for the
|
||||
// "Begin Form" line) which would find this cache entry again and recurse.
|
||||
char *src = rt->frmCache[i].frmSource;
|
||||
int32_t srcLen = rt->frmCache[i].frmSourceLen;
|
||||
arrdel(rt->frmCache, i);
|
||||
rt->frmCacheCount = (int32_t)arrlen(rt->frmCache);
|
||||
// Check the .frm cache for reload after unload.
|
||||
// Use a static guard to prevent recursion: basFormRtLoadFrm calls
|
||||
// basFormRtLoadForm for the "Begin Form" line, which would re-enter
|
||||
// this function. The guard lets the recursive call fall through to
|
||||
// bare form creation, which basFormRtLoadFrm then populates.
|
||||
static bool sLoadingFrm = false;
|
||||
|
||||
BasFormT *form = basFormRtLoadFrm(rt, src, srcLen);
|
||||
free(src);
|
||||
return form;
|
||||
if (!sLoadingFrm) {
|
||||
for (int32_t i = 0; i < rt->frmCacheCount; i++) {
|
||||
if (strcasecmp(rt->frmCache[i].formName, formName) == 0) {
|
||||
char *src = rt->frmCache[i].frmSource;
|
||||
int32_t srcLen = rt->frmCache[i].frmSourceLen;
|
||||
arrdel(rt->frmCache, i);
|
||||
rt->frmCacheCount = (int32_t)arrlen(rt->frmCache);
|
||||
|
||||
sLoadingFrm = true;
|
||||
BasFormT *form = basFormRtLoadFrm(rt, src, srcLen);
|
||||
sLoadingFrm = false;
|
||||
free(src);
|
||||
return form;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1701,6 +1709,10 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
}
|
||||
|
||||
val = basValStringFromC(value + 1);
|
||||
} else if (strcasecmp(value, "True") == 0) {
|
||||
val = basValBool(true);
|
||||
} else if (strcasecmp(value, "False") == 0) {
|
||||
val = basValBool(false);
|
||||
} else {
|
||||
val = basValLong(atoi(value));
|
||||
}
|
||||
|
|
@ -1884,6 +1896,38 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
}
|
||||
}
|
||||
|
||||
// Allocate per-form variable storage and run init code.
|
||||
// This must happen AFTER controls are created (so init code can
|
||||
// reference controls by name) but BEFORE Form_Load fires.
|
||||
if (form && rt->module && rt->module->formVarInfo) {
|
||||
for (int32_t j = 0; j < rt->module->formVarInfoCount; j++) {
|
||||
if (strcasecmp(rt->module->formVarInfo[j].formName, form->name) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!form->formVars) {
|
||||
int32_t vc = rt->module->formVarInfo[j].varCount;
|
||||
|
||||
if (vc > 0) {
|
||||
form->formVars = (BasValueT *)calloc(vc, sizeof(BasValueT));
|
||||
form->formVarCount = vc;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t initAddr = rt->module->formVarInfo[j].initCodeAddr;
|
||||
|
||||
if (initAddr >= 0 && rt->vm) {
|
||||
basVmSetCurrentForm(rt->vm, form);
|
||||
basVmSetCurrentFormVars(rt->vm, form->formVars, form->formVarCount);
|
||||
basVmCallSub(rt->vm, initAddr);
|
||||
basVmSetCurrentForm(rt->vm, NULL);
|
||||
basVmSetCurrentFormVars(rt->vm, NULL, 0);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fire the Load event now that the form and controls are ready
|
||||
if (form) {
|
||||
basFormRtFireEvent(rt, form, form->name, "Load");
|
||||
|
|
@ -1928,10 +1972,10 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
// basFormRtMsgBox
|
||||
// ============================================================
|
||||
|
||||
int32_t basFormRtMsgBox(void *ctx, const char *message, int32_t flags) {
|
||||
int32_t basFormRtMsgBox(void *ctx, const char *message, int32_t flags, const char *title) {
|
||||
BasFormRtT *rt = (BasFormRtT *)ctx;
|
||||
|
||||
return dvxMessageBox(rt->ctx, "DVX BASIC", message, flags);
|
||||
return dvxMessageBox(rt->ctx, (title && title[0]) ? title : "DVX BASIC", message, flags);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2049,14 +2093,12 @@ void basFormRtSetProp(void *ctx, void *ctrlRef, const char *propName, BasValueT
|
|||
return;
|
||||
}
|
||||
|
||||
// "Caption" and "Text": save to the persistent textBuf and apply
|
||||
// immediately. Controls are heap-allocated so textBuf addresses
|
||||
// are stable across arrput calls.
|
||||
// "Caption" and "Text": pass directly to the widget (all widgets
|
||||
// strdup their text internally).
|
||||
if (strcasecmp(propName, "Caption") == 0 || strcasecmp(propName, "Text") == 0) {
|
||||
BasStringT *s = basValFormatString(value);
|
||||
snprintf(ctrl->textBuf, BAS_MAX_TEXT_BUF, "%s", s->data);
|
||||
wgtSetText(ctrl->widget, s->data);
|
||||
basStringUnref(s);
|
||||
wgtSetText(ctrl->widget, ctrl->textBuf);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2129,6 +2171,11 @@ void basFormRtUnloadForm(void *ctx, void *formRef) {
|
|||
return;
|
||||
}
|
||||
|
||||
// QueryUnload: give the form a chance to cancel
|
||||
if (basFormRtFireEventWithCancel(rt, form, form->name, "QueryUnload")) {
|
||||
return;
|
||||
}
|
||||
|
||||
basFormRtFireEvent(rt, form, form->name, "Unload");
|
||||
|
||||
// Release per-form variables
|
||||
|
|
@ -2253,8 +2300,12 @@ WidgetT *createWidget(const char *wgtTypeName, WidgetT *parent) {
|
|||
CreateParentBoolFnT fn = *(CreateParentBoolFnT *)api;
|
||||
return fn(parent, (bool)iface->createArgs[0]);
|
||||
}
|
||||
case WGT_CREATE_PARENT_DATA:
|
||||
return NULL;
|
||||
case WGT_CREATE_PARENT_DATA: {
|
||||
// create(parent, NULL, 0, 0, 0) -- empty widget, load content later via properties
|
||||
typedef WidgetT *(*CreateDataFnT)(WidgetT *, uint8_t *, int32_t, int32_t, int32_t);
|
||||
CreateDataFnT fn = *(CreateDataFnT *)api;
|
||||
return fn(parent, NULL, 0, 0, 0);
|
||||
}
|
||||
default: {
|
||||
CreateParentFnT fn = *(CreateParentFnT *)api;
|
||||
return fn(parent);
|
||||
|
|
@ -2769,8 +2820,7 @@ static void updateBoundControls(BasFormT *form, BasControlT *dataCtrl) {
|
|||
const char *val = wgtDataCtrlGetField(dataCtrl->widget, ctrl->dataField);
|
||||
|
||||
if (val) {
|
||||
snprintf(ctrl->textBuf, BAS_MAX_TEXT_BUF, "%s", val);
|
||||
wgtSetText(ctrl->widget, ctrl->textBuf);
|
||||
wgtSetText(ctrl->widget, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3214,21 +3264,64 @@ static BasValueT zeroValue(void) {
|
|||
// need C-side storage since BASIC strings can't be used as
|
||||
// writable char pointers.
|
||||
|
||||
// Parse a filter string into FileFilterT entries.
|
||||
//
|
||||
// Format: "Label (pattern)|Label (pattern)|..."
|
||||
// The pattern is extracted from inside the parentheses.
|
||||
// Example: "Images (*.bmp;*.png;*.jpg)|Text (*.txt)|All Files (*.*)"
|
||||
//
|
||||
// If no parentheses, the entire entry is used as both label and pattern.
|
||||
// If no pipe, treat as a single pattern for backward compatibility.
|
||||
|
||||
#define BAS_MAX_FILE_FILTERS 16
|
||||
|
||||
static int32_t parseFileFilters(const char *filter, FileFilterT *out, char *buf, int32_t bufSize) {
|
||||
if (!filter || !filter[0]) {
|
||||
out[0].label = "All Files (*.*)";
|
||||
return 1;
|
||||
}
|
||||
|
||||
// No pipe and no parentheses = old-style single pattern, wrap it
|
||||
if (!strchr(filter, '|') && !strchr(filter, '(')) {
|
||||
snprintf(buf, bufSize, "%s (%s)", filter, filter);
|
||||
out[0].label = buf;
|
||||
out[1].label = "All Files (*.*)";
|
||||
return 2;
|
||||
}
|
||||
|
||||
snprintf(buf, bufSize, "%s", filter);
|
||||
int32_t count = 0;
|
||||
char *p = buf;
|
||||
|
||||
while (*p && count < BAS_MAX_FILE_FILTERS) {
|
||||
char *pipe = strchr(p, '|');
|
||||
|
||||
if (pipe) {
|
||||
*pipe = '\0';
|
||||
}
|
||||
|
||||
out[count].label = p;
|
||||
count++;
|
||||
p = pipe ? pipe + 1 : p + strlen(p);
|
||||
}
|
||||
|
||||
return count > 0 ? count : 1;
|
||||
}
|
||||
|
||||
|
||||
const char *basFileOpen(const char *title, const char *filter) {
|
||||
if (!sFormRt) {
|
||||
return "";
|
||||
}
|
||||
|
||||
FileFilterT filters[2];
|
||||
filters[0].label = filter;
|
||||
filters[0].pattern = filter;
|
||||
filters[1].label = "All Files (*.*)";
|
||||
filters[1].pattern = "*.*";
|
||||
FileFilterT filters[BAS_MAX_FILE_FILTERS];
|
||||
char filterBuf[1024];
|
||||
int32_t count = parseFileFilters(filter, filters, filterBuf, sizeof(filterBuf));
|
||||
|
||||
static char path[260];
|
||||
static char path[DVX_MAX_PATH];
|
||||
path[0] = '\0';
|
||||
|
||||
if (dvxFileDialog(sFormRt->ctx, title, FD_OPEN, NULL, filters, 2, path, sizeof(path))) {
|
||||
if (dvxFileDialog(sFormRt->ctx, title, FD_OPEN, NULL, filters, count, path, sizeof(path))) {
|
||||
return path;
|
||||
}
|
||||
|
||||
|
|
@ -3241,16 +3334,14 @@ const char *basFileSave(const char *title, const char *filter) {
|
|||
return "";
|
||||
}
|
||||
|
||||
FileFilterT filters[2];
|
||||
filters[0].label = filter;
|
||||
filters[0].pattern = filter;
|
||||
filters[1].label = "All Files (*.*)";
|
||||
filters[1].pattern = "*.*";
|
||||
FileFilterT filters[BAS_MAX_FILE_FILTERS];
|
||||
char filterBuf[1024];
|
||||
int32_t count = parseFileFilters(filter, filters, filterBuf, sizeof(filterBuf));
|
||||
|
||||
static char path[260];
|
||||
static char path[DVX_MAX_PATH];
|
||||
path[0] = '\0';
|
||||
|
||||
if (dvxFileDialog(sFormRt->ctx, title, FD_SAVE, NULL, filters, 2, path, sizeof(path))) {
|
||||
if (dvxFileDialog(sFormRt->ctx, title, FD_SAVE, NULL, filters, count, path, sizeof(path))) {
|
||||
return path;
|
||||
}
|
||||
|
||||
|
|
@ -4277,10 +4368,244 @@ void HelpView(const char *hlpFile) {
|
|||
return;
|
||||
}
|
||||
|
||||
char viewerPath[260];
|
||||
// If hlpFile has no directory component, resolve it against the calling
|
||||
// app's directory. This matches the convention where apps bundle their
|
||||
// help file alongside the .app.
|
||||
char resolved[DVX_MAX_PATH];
|
||||
bool hasDir = strchr(hlpFile, '/') != NULL || strchr(hlpFile, '\\') != NULL || (hlpFile[0] && hlpFile[1] == ':');
|
||||
|
||||
if (!hasDir && sFormRt->vm && sFormRt->vm->appPath[0]) {
|
||||
snprintf(resolved, sizeof(resolved), "%s%c%s", sFormRt->vm->appPath, DVX_PATH_SEP, hlpFile);
|
||||
} else {
|
||||
snprintf(resolved, sizeof(resolved), "%s", hlpFile);
|
||||
}
|
||||
|
||||
char viewerPath[DVX_MAX_PATH];
|
||||
snprintf(viewerPath, sizeof(viewerPath), "APPS%cKPUNCH%cDVXHELP%cDVXHELP.APP", DVX_PATH_SEP, DVX_PATH_SEP, DVX_PATH_SEP);
|
||||
|
||||
sShellLoadApp(sFormRt->ctx, viewerPath, hlpFile);
|
||||
sShellLoadApp(sFormRt->ctx, viewerPath, resolved);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Resource file extern wrappers (DECLARE LIBRARY "basrt")
|
||||
//
|
||||
// Expose the DVX resource API to BASIC programs. Each function
|
||||
// is a thin wrapper around the C resource API.
|
||||
// ============================================================
|
||||
|
||||
// Maximum number of simultaneously open resource handles
|
||||
#define RES_MAX_HANDLES 8
|
||||
|
||||
static DvxResHandleT *sResHandles[RES_MAX_HANDLES];
|
||||
|
||||
|
||||
int32_t ResOpen(const char *path) {
|
||||
if (!path) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < RES_MAX_HANDLES; i++) {
|
||||
if (!sResHandles[i]) {
|
||||
sResHandles[i] = dvxResOpen(path);
|
||||
|
||||
if (sResHandles[i]) {
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void ResClose(int32_t handle) {
|
||||
int32_t idx = handle - 1;
|
||||
|
||||
if (idx < 0 || idx >= RES_MAX_HANDLES || !sResHandles[idx]) {
|
||||
return;
|
||||
}
|
||||
|
||||
dvxResClose(sResHandles[idx]);
|
||||
sResHandles[idx] = NULL;
|
||||
}
|
||||
|
||||
|
||||
int32_t ResCount(int32_t handle) {
|
||||
int32_t idx = handle - 1;
|
||||
|
||||
if (idx >= 0 && idx < RES_MAX_HANDLES && sResHandles[idx]) {
|
||||
return (int32_t)sResHandles[idx]->entryCount;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
const char *ResName(int32_t handle, int32_t index) {
|
||||
int32_t idx = handle - 1;
|
||||
|
||||
if (idx >= 0 && idx < RES_MAX_HANDLES && sResHandles[idx] &&
|
||||
index >= 0 && index < (int32_t)sResHandles[idx]->entryCount) {
|
||||
return sResHandles[idx]->entries[index].name;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
int32_t ResType(int32_t handle, int32_t index) {
|
||||
int32_t idx = handle - 1;
|
||||
|
||||
if (idx >= 0 && idx < RES_MAX_HANDLES && sResHandles[idx] &&
|
||||
index >= 0 && index < (int32_t)sResHandles[idx]->entryCount) {
|
||||
return (int32_t)sResHandles[idx]->entries[index].type;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int32_t ResSize(int32_t handle, int32_t index) {
|
||||
int32_t idx = handle - 1;
|
||||
|
||||
if (idx >= 0 && idx < RES_MAX_HANDLES && sResHandles[idx] &&
|
||||
index >= 0 && index < (int32_t)sResHandles[idx]->entryCount) {
|
||||
return (int32_t)sResHandles[idx]->entries[index].size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
const char *ResGetText(const char *path, const char *name) {
|
||||
static char sBuf[1024];
|
||||
sBuf[0] = '\0';
|
||||
|
||||
if (!path || !name) {
|
||||
return sBuf;
|
||||
}
|
||||
|
||||
DvxResHandleT *h = dvxResOpen(path);
|
||||
|
||||
if (!h) {
|
||||
return sBuf;
|
||||
}
|
||||
|
||||
uint32_t size = 0;
|
||||
void *data = dvxResRead(h, name, &size);
|
||||
dvxResClose(h);
|
||||
|
||||
if (data) {
|
||||
uint32_t copyLen = size;
|
||||
|
||||
if (copyLen >= sizeof(sBuf)) {
|
||||
copyLen = sizeof(sBuf) - 1;
|
||||
}
|
||||
|
||||
memcpy(sBuf, data, copyLen);
|
||||
sBuf[copyLen] = '\0';
|
||||
free(data);
|
||||
}
|
||||
|
||||
return sBuf;
|
||||
}
|
||||
|
||||
|
||||
int32_t ResAddText(const char *path, const char *name, const char *text) {
|
||||
if (!path || !name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
text = "";
|
||||
}
|
||||
|
||||
return dvxResAppend(path, name, DVX_RES_TEXT, text, (uint32_t)strlen(text) + 1) == 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
|
||||
int32_t ResAddFile(const char *path, const char *name, int32_t type, const char *srcFile) {
|
||||
if (!path || !name || !srcFile) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
FILE *f = fopen(srcFile, "rb");
|
||||
|
||||
if (!f) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
long fileSize = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
if (fileSize <= 0) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t *buf = (uint8_t *)malloc((size_t)fileSize);
|
||||
|
||||
if (!buf) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fread(buf, 1, (size_t)fileSize, f) != (size_t)fileSize) {
|
||||
free(buf);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
int32_t result = dvxResAppend(path, name, (uint32_t)type, buf, (uint32_t)fileSize) == 0 ? -1 : 0;
|
||||
free(buf);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
int32_t ResRemove(const char *path, const char *name) {
|
||||
if (!path || !name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return dvxResRemove(path, name) == 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
|
||||
int32_t ResExtract(const char *path, const char *name, const char *outFile) {
|
||||
if (!path || !name || !outFile) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
DvxResHandleT *h = dvxResOpen(path);
|
||||
|
||||
if (!h) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t size = 0;
|
||||
void *data = dvxResRead(h, name, &size);
|
||||
dvxResClose(h);
|
||||
|
||||
if (!data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
FILE *f = fopen(outFile, "wb");
|
||||
|
||||
if (!f) {
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t result = (fwrite(data, 1, size, f) == size) ? -1 : 0;
|
||||
fclose(f);
|
||||
free(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ typedef struct {
|
|||
// Control instance (a widget on a form)
|
||||
// ============================================================
|
||||
|
||||
#define BAS_MAX_TEXT_BUF 256
|
||||
#define BAS_MAX_EVENT_OVERRIDES 16
|
||||
|
||||
// Event handler override (SetEvent)
|
||||
|
|
@ -59,7 +58,6 @@ typedef struct BasControlT {
|
|||
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
|
||||
char dataSource[BAS_MAX_CTRL_NAME]; // name of Data control for binding
|
||||
char dataField[BAS_MAX_CTRL_NAME]; // column name for binding
|
||||
char helpTopic[BAS_MAX_CTRL_NAME]; // help topic ID for F1
|
||||
|
|
@ -171,7 +169,7 @@ 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);
|
||||
int32_t basFormRtMsgBox(void *ctx, const char *message, int32_t flags, const char *title);
|
||||
|
||||
// ---- Extern call callbacks (shared by IDE and stub) ----
|
||||
|
||||
|
|
|
|||
|
|
@ -3095,9 +3095,9 @@ static void recentOpen(int32_t index) {
|
|||
|
||||
static void loadFile(void) {
|
||||
FileFilterT filters[] = {
|
||||
{ "BASIC Files (*.bas)", "*.bas" },
|
||||
{ "Form Files (*.frm)", "*.frm" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
{ "BASIC Files (*.bas)" },
|
||||
{ "Form Files (*.frm)" },
|
||||
{ "All Files (*.*)" }
|
||||
};
|
||||
|
||||
char path[DVX_MAX_PATH];
|
||||
|
|
@ -3247,7 +3247,7 @@ static void newProject(void) {
|
|||
|
||||
// Ask for directory via save dialog (file = name.dbp)
|
||||
FileFilterT filters[] = {
|
||||
{ "Project Files (*.dbp)", "*.dbp" }
|
||||
{ "Project Files (*.dbp)" }
|
||||
};
|
||||
|
||||
char dbpPath[DVX_MAX_PATH];
|
||||
|
|
@ -3310,8 +3310,8 @@ static void newProject(void) {
|
|||
|
||||
static void openProject(void) {
|
||||
FileFilterT filters[] = {
|
||||
{ "Project Files (*.dbp)", "*.dbp" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
{ "Project Files (*.dbp)" },
|
||||
{ "All Files (*.*)" }
|
||||
};
|
||||
|
||||
char path[DVX_MAX_PATH];
|
||||
|
|
@ -3964,8 +3964,8 @@ static void makeExecutable(void) {
|
|||
|
||||
// Ask for output path
|
||||
FileFilterT filters[] = {
|
||||
{ "DVX Applications (*.app)", "*.app" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
{ "DVX Applications (*.app)" },
|
||||
{ "All Files (*.*)" }
|
||||
};
|
||||
char outPath[DVX_MAX_PATH];
|
||||
outPath[0] = '\0';
|
||||
|
|
@ -3975,7 +3975,7 @@ static void makeExecutable(void) {
|
|||
}
|
||||
|
||||
// Ask debug or release
|
||||
const char *modeItems[] = { "Debug (include error info)", "Release (stripped)" };
|
||||
const char *modeItems[] = { "Debug (include error info)" };
|
||||
int32_t modeChoice = 0;
|
||||
|
||||
if (!dvxChoiceDialog(sAc, "Build Mode", "Select build mode:", modeItems, 2, 0, &modeChoice)) {
|
||||
|
|
@ -4067,9 +4067,116 @@ static void makeExecutable(void) {
|
|||
fclose(outFile);
|
||||
free(stubData);
|
||||
|
||||
// Attach app name from project properties
|
||||
// Attach project property resources
|
||||
const char *projName = sProject.name[0] ? sProject.name : "BASIC App";
|
||||
dvxResAppend(outPath, "APPNAME", DVX_RES_TEXT, projName, (uint32_t)strlen(projName) + 1);
|
||||
dvxResAppend(outPath, "name", DVX_RES_TEXT, projName, (uint32_t)strlen(projName) + 1);
|
||||
|
||||
if (sProject.author[0]) {
|
||||
dvxResAppend(outPath, "author", DVX_RES_TEXT, sProject.author, (uint32_t)strlen(sProject.author) + 1);
|
||||
}
|
||||
|
||||
if (sProject.company[0]) {
|
||||
dvxResAppend(outPath, "company", DVX_RES_TEXT, sProject.company, (uint32_t)strlen(sProject.company) + 1);
|
||||
}
|
||||
|
||||
if (sProject.version[0]) {
|
||||
dvxResAppend(outPath, "version", DVX_RES_TEXT, sProject.version, (uint32_t)strlen(sProject.version) + 1);
|
||||
}
|
||||
|
||||
if (sProject.copyright[0]) {
|
||||
dvxResAppend(outPath, "copyright", DVX_RES_TEXT, sProject.copyright, (uint32_t)strlen(sProject.copyright) + 1);
|
||||
}
|
||||
|
||||
if (sProject.description[0]) {
|
||||
dvxResAppend(outPath, "description", DVX_RES_TEXT, sProject.description, (uint32_t)strlen(sProject.description) + 1);
|
||||
}
|
||||
|
||||
// Attach icon: project icon or fallback to IDE's noicon resource
|
||||
if (sProject.iconPath[0]) {
|
||||
char iconFullPath[DVX_MAX_PATH];
|
||||
snprintf(iconFullPath, sizeof(iconFullPath), "%s%c%s", sProject.projectDir, DVX_PATH_SEP, sProject.iconPath);
|
||||
|
||||
FILE *iconFile = fopen(iconFullPath, "rb");
|
||||
|
||||
if (iconFile) {
|
||||
fseek(iconFile, 0, SEEK_END);
|
||||
long iconSize = ftell(iconFile);
|
||||
fseek(iconFile, 0, SEEK_SET);
|
||||
void *iconData = malloc(iconSize);
|
||||
|
||||
if (iconData) {
|
||||
if (fread(iconData, 1, iconSize, iconFile) == (size_t)iconSize) {
|
||||
dvxResAppend(outPath, "icon32", DVX_RES_ICON, iconData, (uint32_t)iconSize);
|
||||
}
|
||||
|
||||
free(iconData);
|
||||
}
|
||||
|
||||
fclose(iconFile);
|
||||
}
|
||||
} else {
|
||||
// Use stock noicon from IDE resources
|
||||
DvxResHandleT *ideRes = dvxResOpen(sCtx->appPath);
|
||||
|
||||
if (ideRes) {
|
||||
uint32_t noiconSize = 0;
|
||||
void *noiconData = dvxResRead(ideRes, "noicon", &noiconSize);
|
||||
|
||||
if (noiconData) {
|
||||
dvxResAppend(outPath, "icon32", DVX_RES_ICON, noiconData, noiconSize);
|
||||
free(noiconData);
|
||||
}
|
||||
|
||||
dvxResClose(ideRes);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy help file alongside the output app (if specified in project)
|
||||
if (sProject.helpFile[0]) {
|
||||
// Store just the filename as a text resource so the stub can find it
|
||||
const char *helpBase = sProject.helpFile;
|
||||
const char *sep = strrchr(helpBase, DVX_PATH_SEP);
|
||||
|
||||
if (sep) {
|
||||
helpBase = sep + 1;
|
||||
}
|
||||
|
||||
dvxResAppend(outPath, "helpfile", DVX_RES_TEXT, helpBase, (uint32_t)strlen(helpBase) + 1);
|
||||
|
||||
// Copy the .hlp file to sit next to the output .app
|
||||
char helpSrc[DVX_MAX_PATH];
|
||||
snprintf(helpSrc, sizeof(helpSrc), "%s%c%s", sProject.projectDir, DVX_PATH_SEP, sProject.helpFile);
|
||||
|
||||
char outDir[DVX_MAX_PATH];
|
||||
snprintf(outDir, sizeof(outDir), "%s", outPath);
|
||||
char *lastSep = strrchr(outDir, DVX_PATH_SEP);
|
||||
|
||||
if (lastSep) {
|
||||
*lastSep = '\0';
|
||||
}
|
||||
|
||||
char helpDst[DVX_MAX_PATH];
|
||||
snprintf(helpDst, sizeof(helpDst), "%s%c%s", outDir, DVX_PATH_SEP, helpBase);
|
||||
|
||||
FILE *hSrc = fopen(helpSrc, "rb");
|
||||
|
||||
if (hSrc) {
|
||||
FILE *hDst = fopen(helpDst, "wb");
|
||||
|
||||
if (hDst) {
|
||||
char cpBuf[4096];
|
||||
size_t n;
|
||||
|
||||
while ((n = fread(cpBuf, 1, sizeof(cpBuf), hSrc)) > 0) {
|
||||
fwrite(cpBuf, 1, n, hDst);
|
||||
}
|
||||
|
||||
fclose(hDst);
|
||||
}
|
||||
|
||||
fclose(hSrc);
|
||||
}
|
||||
}
|
||||
|
||||
// Attach MODULE resource
|
||||
dvxResAppend(outPath, "MODULE", DVX_RES_BINARY, modData, (uint32_t)modLen);
|
||||
|
|
@ -8519,6 +8626,7 @@ static void updateProjectMenuState(void) {
|
|||
}
|
||||
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_SAVE_ALL, anyDirty);
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_MAKE_EXE, hasProject && isIdle);
|
||||
|
||||
// Edit menu
|
||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_FIND, hasProject);
|
||||
|
|
|
|||
|
|
@ -441,6 +441,7 @@ void basModuleFree(BasModuleT *mod) {
|
|||
}
|
||||
|
||||
free(mod->procs);
|
||||
free(mod->formVarInfo);
|
||||
free(mod->debugVars);
|
||||
|
||||
if (mod->debugUdtDefs) {
|
||||
|
|
@ -451,10 +452,6 @@ void basModuleFree(BasModuleT *mod) {
|
|||
free(mod->debugUdtDefs);
|
||||
}
|
||||
|
||||
if (mod->formVarInfo) {
|
||||
free(mod->formVarInfo);
|
||||
}
|
||||
|
||||
free(mod);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1144,14 +1144,26 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
varSlot = &vm->globals[varIdx];
|
||||
}
|
||||
|
||||
// Increment: var = var + step
|
||||
double varVal = basValToNumber(*varSlot);
|
||||
double stepVal = basValToNumber(fs->step);
|
||||
double limVal = basValToNumber(fs->limit);
|
||||
// Increment: var = var + step. Preserve the loop variable's
|
||||
// original type so extern calls that pass it as an int32_t
|
||||
// argument don't get an 8-byte double instead.
|
||||
double varVal = basValToNumber(*varSlot);
|
||||
double stepVal = basValToNumber(fs->step);
|
||||
double limVal = basValToNumber(fs->limit);
|
||||
uint8_t varType = varSlot->type;
|
||||
varVal += stepVal;
|
||||
|
||||
basValRelease(varSlot);
|
||||
*varSlot = basValDouble(varVal);
|
||||
|
||||
if (varType == BAS_TYPE_INTEGER) {
|
||||
*varSlot = basValInteger((int16_t)varVal);
|
||||
} else if (varType == BAS_TYPE_LONG) {
|
||||
*varSlot = basValLong((int32_t)varVal);
|
||||
} else if (varType == BAS_TYPE_SINGLE) {
|
||||
*varSlot = basValSingle((float)varVal);
|
||||
} else {
|
||||
*varSlot = basValDouble(varVal);
|
||||
}
|
||||
|
||||
// Test: if step > 0 then continue while var <= limit
|
||||
// if step < 0 then continue while var >= limit
|
||||
|
|
@ -1669,6 +1681,67 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
case OP_MATH_RANDOMIZE:
|
||||
return execMath(vm, op);
|
||||
|
||||
case OP_RGB: {
|
||||
// RGB(r, g, b) -> long color 0x00RRGGBB
|
||||
BasValueT vb;
|
||||
BasValueT vg;
|
||||
BasValueT vr;
|
||||
|
||||
if (!pop(vm, &vb) || !pop(vm, &vg) || !pop(vm, &vr)) {
|
||||
return BAS_VM_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
int32_t r = (int32_t)basValToNumber(vr);
|
||||
int32_t g = (int32_t)basValToNumber(vg);
|
||||
int32_t b = (int32_t)basValToNumber(vb);
|
||||
|
||||
basValRelease(&vr);
|
||||
basValRelease(&vg);
|
||||
basValRelease(&vb);
|
||||
|
||||
if (r < 0) { r = 0; }
|
||||
if (r > 255) { r = 255; }
|
||||
if (g < 0) { g = 0; }
|
||||
if (g > 255) { g = 255; }
|
||||
if (b < 0) { b = 0; }
|
||||
if (b > 255) { b = 255; }
|
||||
|
||||
if (!push(vm, basValLong((r << 16) | (g << 8) | b))) {
|
||||
return BAS_VM_STACK_OVERFLOW;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_GET_RED:
|
||||
case OP_GET_GREEN:
|
||||
case OP_GET_BLUE: {
|
||||
BasValueT vc;
|
||||
|
||||
if (!pop(vm, &vc)) {
|
||||
return BAS_VM_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
int32_t color = (int32_t)basValToNumber(vc);
|
||||
basValRelease(&vc);
|
||||
|
||||
int32_t component;
|
||||
|
||||
if (op == OP_GET_RED) {
|
||||
component = (color >> 16) & 0xFF;
|
||||
} else if (op == OP_GET_GREEN) {
|
||||
component = (color >> 8) & 0xFF;
|
||||
} else {
|
||||
component = color & 0xFF;
|
||||
}
|
||||
|
||||
if (!push(vm, basValInteger((int16_t)component))) {
|
||||
return BAS_VM_STACK_OVERFLOW;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Conversion built-ins
|
||||
// ============================================================
|
||||
|
|
@ -2887,11 +2960,12 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
}
|
||||
|
||||
case OP_MSGBOX: {
|
||||
// Stack: [message, flags] — flags on top
|
||||
// Stack: [message, flags, title] -- title on top
|
||||
BasValueT titleVal;
|
||||
BasValueT flagsVal;
|
||||
BasValueT msgVal;
|
||||
|
||||
if (!pop(vm, &flagsVal) || !pop(vm, &msgVal)) {
|
||||
if (!pop(vm, &titleVal) || !pop(vm, &flagsVal) || !pop(vm, &msgVal)) {
|
||||
return BAS_VM_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
|
|
@ -2900,10 +2974,13 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
|
||||
if (vm->ui.msgBox) {
|
||||
BasValueT sv = basValToString(msgVal);
|
||||
result = vm->ui.msgBox(vm->ui.ctx, sv.strVal->data, flags);
|
||||
BasValueT tv = basValToString(titleVal);
|
||||
result = vm->ui.msgBox(vm->ui.ctx, sv.strVal->data, flags, tv.strVal->data);
|
||||
basValRelease(&sv);
|
||||
basValRelease(&tv);
|
||||
}
|
||||
|
||||
basValRelease(&titleVal);
|
||||
basValRelease(&flagsVal);
|
||||
basValRelease(&msgVal);
|
||||
push(vm, basValInteger((int16_t)result));
|
||||
|
|
@ -3766,13 +3843,13 @@ static BasVmResultE execFileOp(BasVmT *vm, uint8_t op) {
|
|||
|
||||
switch (mode) {
|
||||
case FILE_MODE_INPUT:
|
||||
modeStr = "r";
|
||||
modeStr = "rb";
|
||||
break;
|
||||
case FILE_MODE_OUTPUT:
|
||||
modeStr = "w";
|
||||
modeStr = "wb";
|
||||
break;
|
||||
case FILE_MODE_APPEND:
|
||||
modeStr = "a";
|
||||
modeStr = "ab";
|
||||
break;
|
||||
case FILE_MODE_RANDOM:
|
||||
case FILE_MODE_BINARY:
|
||||
|
|
@ -3836,6 +3913,10 @@ static BasVmResultE execFileOp(BasVmT *vm, uint8_t op) {
|
|||
}
|
||||
|
||||
case OP_FILE_PRINT: {
|
||||
// Writes one value without a trailing newline. The parser emits
|
||||
// OP_FILE_WRITE_NL separately when the PRINT statement finishes
|
||||
// without a trailing ; so multi-value PRINT with `;` separator
|
||||
// works correctly.
|
||||
BasValueT val;
|
||||
BasValueT channelVal;
|
||||
|
||||
|
|
@ -3857,7 +3938,6 @@ static BasVmResultE execFileOp(BasVmT *vm, uint8_t op) {
|
|||
|
||||
if (s) {
|
||||
fputs(s->data, (FILE *)vm->files[channel].handle);
|
||||
fputc('\n', (FILE *)vm->files[channel].handle);
|
||||
basStringUnref(s);
|
||||
}
|
||||
|
||||
|
|
@ -3913,17 +3993,12 @@ static BasVmResultE execFileOp(BasVmT *vm, uint8_t op) {
|
|||
return BAS_VM_FILE_ERROR;
|
||||
}
|
||||
|
||||
// Peek ahead to detect EOF before the next read
|
||||
FILE *fp = (FILE *)vm->files[channel].handle;
|
||||
int ch = fgetc(fp);
|
||||
bool isEof;
|
||||
|
||||
if (ch == EOF) {
|
||||
isEof = true;
|
||||
} else {
|
||||
ungetc(ch, fp);
|
||||
isEof = false;
|
||||
}
|
||||
FILE *fp = (FILE *)vm->files[channel].handle;
|
||||
long curPos = ftell(fp);
|
||||
fseek(fp, 0, SEEK_END);
|
||||
long endPos = ftell(fp);
|
||||
fseek(fp, curPos, SEEK_SET);
|
||||
bool isEof = (curPos >= endPos);
|
||||
|
||||
if (!push(vm, basValBool(isEof))) {
|
||||
return BAS_VM_STACK_OVERFLOW;
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ typedef void (*BasUiShowFormFnT)(void *ctx, void *formRef, bool modal);
|
|||
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);
|
||||
typedef int32_t (*BasUiMsgBoxFnT)(void *ctx, const char *message, int32_t flags, const char *title);
|
||||
|
||||
// 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);
|
||||
|
|
|
|||
667
apps/dvxbasic/stub/bascomp.c
Normal file
667
apps/dvxbasic/stub/bascomp.c
Normal file
|
|
@ -0,0 +1,667 @@
|
|||
// bascomp.c -- DVX BASIC command-line compiler
|
||||
//
|
||||
// Compiles a .dbp project into a standalone .app file.
|
||||
//
|
||||
// Usage: BASCOMP project.dbp [-o output.app] [-release]
|
||||
//
|
||||
// The project file and all referenced source files (.bas, .frm)
|
||||
// are loaded relative to the directory containing the .dbp file.
|
||||
// The stub (basstub.app) is read from the same directory as the
|
||||
// compiler executable.
|
||||
|
||||
#include "../compiler/compact.h"
|
||||
#include "../compiler/lexer.h"
|
||||
#include "../compiler/obfuscate.h"
|
||||
#include "../compiler/parser.h"
|
||||
#include "../compiler/strip.h"
|
||||
#include "../compiler/opcodes.h"
|
||||
#include "../runtime/vm.h"
|
||||
#include "../runtime/values.h"
|
||||
#include "../runtime/serialize.h"
|
||||
#include "../../core/dvxRes.h"
|
||||
#include "../../core/dvxPrefs.h"
|
||||
#include "../../core/dvxTypes.h"
|
||||
#include "../../tools/dvxResWrite.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
// ============================================================
|
||||
// Limits
|
||||
// ============================================================
|
||||
|
||||
#define MAX_FILES 64
|
||||
#define MAX_PATH_LEN 260
|
||||
#define MAX_NAME 64
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
static void concatGrow(char **buf, int32_t *cap, int32_t need);
|
||||
static char *readFile(const char *path, int32_t *outLen);
|
||||
static const char *extractFormCode(const char *frmText);
|
||||
static void usage(void);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// extractFormCode -- skip past the form layout to the code section
|
||||
// ============================================================
|
||||
|
||||
static void concatGrow(char **buf, int32_t *cap, int32_t need) {
|
||||
while (*cap < need) {
|
||||
*cap *= 2;
|
||||
}
|
||||
|
||||
*buf = (char *)realloc(*buf, *cap);
|
||||
}
|
||||
|
||||
|
||||
static const char *extractFormCode(const char *frmText) {
|
||||
if (!frmText) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *p = frmText;
|
||||
int32_t depth = 0;
|
||||
bool inForm = false;
|
||||
|
||||
while (*p) {
|
||||
// Skip leading whitespace
|
||||
while (*p == ' ' || *p == '\t') { p++; }
|
||||
|
||||
if (strncasecmp(p, "Begin ", 6) == 0) {
|
||||
if (!inForm && strncasecmp(p + 6, "Form ", 5) == 0) {
|
||||
inForm = true;
|
||||
}
|
||||
|
||||
depth++;
|
||||
} else if (strncasecmp(p, "End", 3) == 0 && (p[3] == '\0' || p[3] == '\r' || p[3] == '\n' || p[3] == ' ')) {
|
||||
depth--;
|
||||
|
||||
if (depth <= 0 && inForm) {
|
||||
// Skip past this line
|
||||
while (*p && *p != '\n') { p++; }
|
||||
|
||||
if (*p == '\n') { p++; }
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip to next line
|
||||
while (*p && *p != '\n') { p++; }
|
||||
|
||||
if (*p == '\n') { p++; }
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// readFile
|
||||
// ============================================================
|
||||
|
||||
static char *readFile(const char *path, int32_t *outLen) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
|
||||
if (!f) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
long size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
char *buf = (char *)malloc(size + 1);
|
||||
|
||||
if (!buf) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32_t n = (int32_t)fread(buf, 1, size, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
|
||||
if (outLen) {
|
||||
*outLen = n;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// usage
|
||||
// ============================================================
|
||||
|
||||
static void usage(void) {
|
||||
fprintf(stderr, "DVX BASIC Compiler\n\n");
|
||||
fprintf(stderr, "Usage: BASCOMP project.dbp [-o output.app] [-release]\n\n");
|
||||
fprintf(stderr, " project.dbp DVX BASIC project file\n");
|
||||
fprintf(stderr, " -o output.app Output file (default: project name + .app)\n");
|
||||
fprintf(stderr, " -release Strip debug information\n");
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// main
|
||||
// ============================================================
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *dbpPath = NULL;
|
||||
const char *outputPath = NULL;
|
||||
bool release = false;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
|
||||
outputPath = argv[++i];
|
||||
} else if (strcmp(argv[i], "-release") == 0) {
|
||||
release = true;
|
||||
} else if (argv[i][0] != '-') {
|
||||
dbpPath = argv[i];
|
||||
} else {
|
||||
fprintf(stderr, "Unknown option: %s\n", argv[i]);
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!dbpPath) {
|
||||
fprintf(stderr, "Error: no project file specified.\n");
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Load the project file
|
||||
PrefsHandleT *prefs = prefsLoad(dbpPath);
|
||||
|
||||
if (!prefs) {
|
||||
fprintf(stderr, "Error: cannot open project file: %s\n", dbpPath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Derive project directory
|
||||
char projectDir[MAX_PATH_LEN];
|
||||
snprintf(projectDir, sizeof(projectDir), "%s", dbpPath);
|
||||
char *sep = strrchr(projectDir, '/');
|
||||
char *sep2 = strrchr(projectDir, '\\');
|
||||
|
||||
if (sep2 > sep) {
|
||||
sep = sep2;
|
||||
}
|
||||
|
||||
if (sep) {
|
||||
*sep = '\0';
|
||||
} else {
|
||||
projectDir[0] = '.';
|
||||
projectDir[1] = '\0';
|
||||
}
|
||||
|
||||
// Read project metadata
|
||||
const char *projName = prefsGetString(prefs, "Project", "Name", "App");
|
||||
const char *author = prefsGetString(prefs, "Project", "Author", "");
|
||||
const char *company = prefsGetString(prefs, "Project", "Company", "");
|
||||
const char *version = prefsGetString(prefs, "Project", "Version", "");
|
||||
const char *copyright = prefsGetString(prefs, "Project", "Copyright", "");
|
||||
const char *description = prefsGetString(prefs, "Project", "Description", "");
|
||||
const char *iconPath = prefsGetString(prefs, "Project", "Icon", "");
|
||||
const char *helpFile = prefsGetString(prefs, "Project", "HelpFile", "");
|
||||
const char *startupForm = prefsGetString(prefs, "Settings", "StartupForm", "");
|
||||
(void)startupForm; // used implicitly by stub's basFormRtLoadAllForms
|
||||
|
||||
// Derive output path
|
||||
char outBuf[MAX_PATH_LEN];
|
||||
|
||||
if (!outputPath) {
|
||||
snprintf(outBuf, sizeof(outBuf), "%s/%s.app", projectDir, projName);
|
||||
outputPath = outBuf;
|
||||
}
|
||||
|
||||
printf("Project: %s\n", projName);
|
||||
printf("Output: %s (%s)\n", outputPath, release ? "release" : "debug");
|
||||
|
||||
// Collect source files
|
||||
typedef struct {
|
||||
char path[MAX_PATH_LEN];
|
||||
bool isForm;
|
||||
} SrcFileT;
|
||||
|
||||
SrcFileT files[MAX_FILES];
|
||||
int32_t fileCount = 0;
|
||||
|
||||
// Modules
|
||||
for (int32_t i = 0; i < MAX_FILES; i++) {
|
||||
char key[16];
|
||||
snprintf(key, sizeof(key), "File%d", (int)i);
|
||||
const char *val = prefsGetString(prefs, "Modules", key, NULL);
|
||||
|
||||
if (!val) {
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(files[fileCount].path, MAX_PATH_LEN, "%s/%s", projectDir, val);
|
||||
files[fileCount].isForm = false;
|
||||
fileCount++;
|
||||
}
|
||||
|
||||
// Forms
|
||||
for (int32_t i = 0; i < MAX_FILES; i++) {
|
||||
char key[16];
|
||||
snprintf(key, sizeof(key), "File%d", (int)i);
|
||||
const char *val = prefsGetString(prefs, "Forms", key, NULL);
|
||||
|
||||
if (!val) {
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(files[fileCount].path, MAX_PATH_LEN, "%s/%s", projectDir, val);
|
||||
files[fileCount].isForm = true;
|
||||
fileCount++;
|
||||
}
|
||||
|
||||
if (fileCount == 0) {
|
||||
fprintf(stderr, "Error: project has no source files.\n");
|
||||
prefsClose(prefs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Concatenate sources (modules first, then form code)
|
||||
int32_t concatCap = 8192;
|
||||
char *concatBuf = (char *)malloc(concatCap);
|
||||
|
||||
if (!concatBuf) {
|
||||
fprintf(stderr, "Error: out of memory.\n");
|
||||
prefsClose(prefs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t pos = 0;
|
||||
|
||||
// Pass 0: .bas modules, Pass 1: .frm code sections
|
||||
for (int32_t pass = 0; pass < 2; pass++) {
|
||||
for (int32_t i = 0; i < fileCount; i++) {
|
||||
if (pass == 0 && files[i].isForm) { continue; }
|
||||
if (pass == 1 && !files[i].isForm) { continue; }
|
||||
|
||||
int32_t srcLen = 0;
|
||||
char *srcBuf = readFile(files[i].path, &srcLen);
|
||||
|
||||
if (!srcBuf) {
|
||||
fprintf(stderr, "Error: cannot read %s\n", files[i].path);
|
||||
free(concatBuf);
|
||||
prefsClose(prefs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *code = srcBuf;
|
||||
|
||||
if (files[i].isForm) {
|
||||
// Extract form name from "Begin Form <name>"
|
||||
char formName[MAX_NAME] = "";
|
||||
const char *bp = srcBuf;
|
||||
|
||||
while (*bp) {
|
||||
while (*bp == ' ' || *bp == '\t') { bp++; }
|
||||
|
||||
if (strncasecmp(bp, "Begin Form ", 11) == 0) {
|
||||
bp += 11;
|
||||
|
||||
while (*bp == ' ' || *bp == '\t') { bp++; }
|
||||
|
||||
int32_t ni = 0;
|
||||
|
||||
while (*bp && *bp != ' ' && *bp != '\t' && *bp != '\r' && *bp != '\n' && ni < MAX_NAME - 1) {
|
||||
formName[ni++] = *bp++;
|
||||
}
|
||||
|
||||
formName[ni] = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
while (*bp && *bp != '\n') { bp++; }
|
||||
|
||||
if (*bp == '\n') { bp++; }
|
||||
}
|
||||
|
||||
code = extractFormCode(srcBuf);
|
||||
|
||||
if (!code) {
|
||||
code = "";
|
||||
}
|
||||
|
||||
int32_t codeLen = (int32_t)strlen(code);
|
||||
concatGrow(&concatBuf, &concatCap, pos + codeLen + 128);
|
||||
|
||||
// Inject BEGINFORM directive before form code
|
||||
if (formName[0]) {
|
||||
pos += snprintf(concatBuf + pos, concatCap - pos, "BEGINFORM \"%s\"\n", formName);
|
||||
}
|
||||
|
||||
memcpy(concatBuf + pos, code, codeLen);
|
||||
pos += codeLen;
|
||||
|
||||
if (pos > 0 && concatBuf[pos - 1] != '\n') {
|
||||
concatBuf[pos++] = '\n';
|
||||
}
|
||||
|
||||
// Inject ENDFORM directive after form code
|
||||
if (formName[0]) {
|
||||
pos += snprintf(concatBuf + pos, concatCap - pos, "ENDFORM\n");
|
||||
}
|
||||
} else {
|
||||
int32_t codeLen = (int32_t)strlen(code);
|
||||
concatGrow(&concatBuf, &concatCap, pos + codeLen + 2);
|
||||
|
||||
memcpy(concatBuf + pos, code, codeLen);
|
||||
pos += codeLen;
|
||||
|
||||
if (pos > 0 && concatBuf[pos - 1] != '\n') {
|
||||
concatBuf[pos++] = '\n';
|
||||
}
|
||||
}
|
||||
|
||||
free(srcBuf);
|
||||
}
|
||||
}
|
||||
|
||||
concatBuf[pos] = '\0';
|
||||
|
||||
// Compile
|
||||
printf("Compiling %d file(s)...\n", (int)fileCount);
|
||||
|
||||
BasParserT *parser = (BasParserT *)malloc(sizeof(BasParserT));
|
||||
|
||||
if (!parser) {
|
||||
fprintf(stderr, "Error: out of memory.\n");
|
||||
free(concatBuf);
|
||||
prefsClose(prefs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
basParserInit(parser, concatBuf, pos);
|
||||
|
||||
if (!basParse(parser)) {
|
||||
fprintf(stderr, "Compile error at line %d: %s\n", (int)parser->errorLine, parser->error);
|
||||
basParserFree(parser);
|
||||
free(parser);
|
||||
free(concatBuf);
|
||||
prefsClose(prefs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
free(concatBuf);
|
||||
|
||||
BasModuleT *mod = basParserBuildModule(parser);
|
||||
basParserFree(parser);
|
||||
free(parser);
|
||||
|
||||
if (!mod) {
|
||||
fprintf(stderr, "Error: failed to build module.\n");
|
||||
prefsClose(prefs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf(" code: %d bytes, %d procs, %d constants\n", (int)mod->codeLen, (int)mod->procCount, (int)mod->constCount);
|
||||
|
||||
// Strip for release
|
||||
if (release) {
|
||||
basStripModule(mod);
|
||||
printf(" stripped debug info\n");
|
||||
}
|
||||
|
||||
// Read all .frm texts up front; they're used for obfuscation and
|
||||
// then embedded as FORM0, FORM1, ... resources below.
|
||||
int32_t frmCount = 0;
|
||||
char *frmData[MAX_FILES];
|
||||
int32_t frmLens[MAX_FILES];
|
||||
int32_t frmFileIdx[MAX_FILES]; // index into files[] for this frm
|
||||
|
||||
for (int32_t i = 0; i < fileCount; i++) {
|
||||
if (!files[i].isForm) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t flen = 0;
|
||||
char *fdata = readFile(files[i].path, &flen);
|
||||
|
||||
if (!fdata) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Strip comments from the .frm text unconditionally. Comments
|
||||
// are source-only; they shouldn't ship in the embedded resource
|
||||
// for either debug or release builds.
|
||||
int32_t stripCap = flen + 16;
|
||||
uint8_t *stripped = (uint8_t *)malloc(stripCap);
|
||||
|
||||
if (!stripped) {
|
||||
free(fdata);
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t strippedLen = basStripFrmComments(fdata, flen, stripped, stripCap);
|
||||
free(fdata);
|
||||
|
||||
frmData[frmCount] = (char *)stripped;
|
||||
frmLens[frmCount] = strippedLen;
|
||||
frmFileIdx[frmCount] = i;
|
||||
frmCount++;
|
||||
}
|
||||
|
||||
// Obfuscate form/control names in release mode
|
||||
BasObfFrmT obfFrms[MAX_FILES];
|
||||
|
||||
for (int32_t i = 0; i < MAX_FILES; i++) {
|
||||
obfFrms[i].data = NULL;
|
||||
obfFrms[i].len = 0;
|
||||
}
|
||||
|
||||
if (release && frmCount > 0) {
|
||||
const char *frmTexts[MAX_FILES];
|
||||
|
||||
for (int32_t i = 0; i < frmCount; i++) {
|
||||
frmTexts[i] = frmData[i];
|
||||
}
|
||||
|
||||
basObfuscateNames(mod, frmTexts, frmLens, frmCount, obfFrms);
|
||||
printf(" obfuscated %d form(s)\n", (int)frmCount);
|
||||
}
|
||||
|
||||
// Remove OP_LINE instructions and compact the bytecode.
|
||||
if (release) {
|
||||
int32_t removed = basCompactBytecode(mod);
|
||||
|
||||
if (removed > 0) {
|
||||
printf(" compacted bytecode (-%d bytes)\n", (int)removed);
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize module
|
||||
int32_t modLen = 0;
|
||||
uint8_t *modData = basModuleSerialize(mod, &modLen);
|
||||
|
||||
if (!modData) {
|
||||
fprintf(stderr, "Error: failed to serialize module.\n");
|
||||
basModuleFree(mod);
|
||||
prefsClose(prefs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Serialize debug info
|
||||
int32_t dbgLen = 0;
|
||||
uint8_t *dbgData = NULL;
|
||||
|
||||
if (!release) {
|
||||
dbgData = basDebugSerialize(mod, &dbgLen);
|
||||
}
|
||||
|
||||
basModuleFree(mod);
|
||||
|
||||
// Find the stub -- look in the compiler's own directory
|
||||
char compilerDir[MAX_PATH_LEN];
|
||||
snprintf(compilerDir, sizeof(compilerDir), "%s", argv[0]);
|
||||
sep = strrchr(compilerDir, '/');
|
||||
sep2 = strrchr(compilerDir, '\\');
|
||||
|
||||
if (sep2 > sep) {
|
||||
sep = sep2;
|
||||
}
|
||||
|
||||
if (sep) {
|
||||
*sep = '\0';
|
||||
} else {
|
||||
compilerDir[0] = '.';
|
||||
compilerDir[1] = '\0';
|
||||
}
|
||||
|
||||
char stubPath[MAX_PATH_LEN];
|
||||
snprintf(stubPath, sizeof(stubPath), "%s/BASSTUB.APP", compilerDir);
|
||||
|
||||
int32_t stubLen = 0;
|
||||
char *stubData = readFile(stubPath, &stubLen);
|
||||
|
||||
if (!stubData) {
|
||||
fprintf(stderr, "Error: cannot find stub at %s\n", stubPath);
|
||||
free(modData);
|
||||
free(dbgData);
|
||||
prefsClose(prefs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Write stub to output file
|
||||
FILE *out = fopen(outputPath, "wb");
|
||||
|
||||
if (!out) {
|
||||
fprintf(stderr, "Error: cannot create %s\n", outputPath);
|
||||
free(stubData);
|
||||
free(modData);
|
||||
free(dbgData);
|
||||
prefsClose(prefs);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fwrite(stubData, 1, stubLen, out);
|
||||
fclose(out);
|
||||
free(stubData);
|
||||
|
||||
// Attach resources
|
||||
dvxResAppendEntry(outputPath, "name", DVX_RES_TEXT, projName, (uint32_t)strlen(projName) + 1);
|
||||
|
||||
if (author[0]) { dvxResAppendEntry(outputPath, "author", DVX_RES_TEXT, author, (uint32_t)strlen(author) + 1); }
|
||||
if (company[0]) { dvxResAppendEntry(outputPath, "company", DVX_RES_TEXT, company, (uint32_t)strlen(company) + 1); }
|
||||
if (version[0]) { dvxResAppendEntry(outputPath, "version", DVX_RES_TEXT, version, (uint32_t)strlen(version) + 1); }
|
||||
if (copyright[0]) { dvxResAppendEntry(outputPath, "copyright", DVX_RES_TEXT, copyright, (uint32_t)strlen(copyright) + 1); }
|
||||
if (description[0]) { dvxResAppendEntry(outputPath, "description", DVX_RES_TEXT, description, (uint32_t)strlen(description) + 1); }
|
||||
|
||||
// Icon
|
||||
if (iconPath[0]) {
|
||||
char iconFullPath[MAX_PATH_LEN];
|
||||
snprintf(iconFullPath, sizeof(iconFullPath), "%s/%s", projectDir, iconPath);
|
||||
int32_t iconLen = 0;
|
||||
char *iconData = readFile(iconFullPath, &iconLen);
|
||||
|
||||
if (iconData) {
|
||||
dvxResAppendEntry(outputPath, "icon32", DVX_RES_ICON, iconData, (uint32_t)iconLen);
|
||||
free(iconData);
|
||||
}
|
||||
}
|
||||
|
||||
// Help file name (file is expected alongside the .app)
|
||||
if (helpFile[0]) {
|
||||
const char *helpBase = helpFile;
|
||||
const char *hs = strrchr(helpBase, '/');
|
||||
const char *hs2 = strrchr(helpBase, '\\');
|
||||
|
||||
if (hs2 > hs) {
|
||||
hs = hs2;
|
||||
}
|
||||
|
||||
if (hs) {
|
||||
helpBase = hs + 1;
|
||||
}
|
||||
|
||||
dvxResAppendEntry(outputPath, "helpfile", DVX_RES_TEXT, helpBase, (uint32_t)strlen(helpBase) + 1);
|
||||
|
||||
// Copy help file to output directory
|
||||
char helpSrc[MAX_PATH_LEN];
|
||||
snprintf(helpSrc, sizeof(helpSrc), "%s/%s", projectDir, helpFile);
|
||||
|
||||
char outDir[MAX_PATH_LEN];
|
||||
snprintf(outDir, sizeof(outDir), "%s", outputPath);
|
||||
char *outSep = strrchr(outDir, '/');
|
||||
char *outSep2 = strrchr(outDir, '\\');
|
||||
|
||||
if (outSep2 > outSep) {
|
||||
outSep = outSep2;
|
||||
}
|
||||
|
||||
if (outSep) {
|
||||
*outSep = '\0';
|
||||
} else {
|
||||
outDir[0] = '.';
|
||||
outDir[1] = '\0';
|
||||
}
|
||||
|
||||
char helpDst[MAX_PATH_LEN];
|
||||
snprintf(helpDst, sizeof(helpDst), "%s/%s", outDir, helpBase);
|
||||
|
||||
int32_t hLen = 0;
|
||||
char *hData = readFile(helpSrc, &hLen);
|
||||
|
||||
if (hData) {
|
||||
FILE *hf = fopen(helpDst, "wb");
|
||||
|
||||
if (hf) {
|
||||
fwrite(hData, 1, hLen, hf);
|
||||
fclose(hf);
|
||||
}
|
||||
|
||||
free(hData);
|
||||
}
|
||||
}
|
||||
|
||||
// MODULE resource
|
||||
dvxResAppendEntry(outputPath, "MODULE", DVX_RES_BINARY, modData, (uint32_t)modLen);
|
||||
free(modData);
|
||||
|
||||
// DEBUG resource
|
||||
if (dbgData) {
|
||||
dvxResAppendEntry(outputPath, "DEBUG", DVX_RES_BINARY, dbgData, (uint32_t)dbgLen);
|
||||
free(dbgData);
|
||||
}
|
||||
|
||||
// Form resources -- use obfuscated bytes in release mode, raw otherwise
|
||||
for (int32_t fi = 0; fi < frmCount; fi++) {
|
||||
char resName[16];
|
||||
snprintf(resName, sizeof(resName), "FORM%d", (int)fi);
|
||||
|
||||
if (release && obfFrms[fi].data) {
|
||||
dvxResAppendEntry(outputPath, resName, DVX_RES_BINARY, obfFrms[fi].data, (uint32_t)obfFrms[fi].len);
|
||||
} else {
|
||||
dvxResAppendEntry(outputPath, resName, DVX_RES_BINARY, frmData[fi], (uint32_t)frmLens[fi]);
|
||||
}
|
||||
}
|
||||
|
||||
// Free .frm buffers
|
||||
for (int32_t i = 0; i < frmCount; i++) {
|
||||
free(frmData[i]);
|
||||
free(obfFrms[i].data);
|
||||
}
|
||||
|
||||
(void)frmFileIdx; // unused (kept in case of future per-file metadata)
|
||||
|
||||
prefsClose(prefs);
|
||||
|
||||
printf("Created %s (%d bytes)\n", outputPath, (int)stubLen + (int)modLen);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -92,7 +92,7 @@ int32_t appMain(DxeAppContextT *ctx) {
|
|||
|
||||
// Read app name and update the shell's app record
|
||||
uint32_t nameSize = 0;
|
||||
char *appName = (char *)dvxResRead(res, "APPNAME", &nameSize);
|
||||
char *appName = (char *)dvxResRead(res, "name", &nameSize);
|
||||
|
||||
if (appName) {
|
||||
ShellAppT *app = shellGetApp(ctx->appId);
|
||||
|
|
@ -104,6 +104,15 @@ int32_t appMain(DxeAppContextT *ctx) {
|
|||
free(appName);
|
||||
}
|
||||
|
||||
// Set help file path if present
|
||||
uint32_t helpNameSize = 0;
|
||||
char *helpName = (char *)dvxResRead(res, "helpfile", &helpNameSize);
|
||||
|
||||
if (helpName) {
|
||||
snprintf(ctx->helpFile, sizeof(ctx->helpFile), "%s%c%s", ctx->appDir, DVX_PATH_SEP, helpName);
|
||||
free(helpName);
|
||||
}
|
||||
|
||||
// Load MODULE resource
|
||||
uint32_t modSize = 0;
|
||||
uint8_t *modData = (uint8_t *)dvxResRead(res, "MODULE", &modSize);
|
||||
|
|
|
|||
326
apps/dvxbasic/test_compact.c
Normal file
326
apps/dvxbasic/test_compact.c
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
// test_compact.c -- verify that strip + compact produces identical output
|
||||
//
|
||||
// Compiles each test program, runs it once without compaction and once
|
||||
// with strip + compact applied, and compares captured PRINT output.
|
||||
// Exits nonzero if any mismatch is found.
|
||||
|
||||
#include "compiler/parser.h"
|
||||
#include "compiler/strip.h"
|
||||
#include "compiler/compact.h"
|
||||
#include "runtime/vm.h"
|
||||
#include "runtime/values.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
#define MAX_OUT 65536
|
||||
|
||||
|
||||
typedef struct {
|
||||
char *buf;
|
||||
int32_t len;
|
||||
int32_t cap;
|
||||
} OutBufT;
|
||||
|
||||
|
||||
static void captureCallback(void *ctx, const char *text, bool newline) {
|
||||
OutBufT *ob = (OutBufT *)ctx;
|
||||
int32_t tlen = (int32_t)strlen(text);
|
||||
|
||||
if (ob->len + tlen + 2 >= ob->cap) {
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(ob->buf + ob->len, text, tlen);
|
||||
ob->len += tlen;
|
||||
|
||||
if (newline) {
|
||||
ob->buf[ob->len++] = '\n';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int32_t runAndCapture(const char *source, bool compact, char *outBuf, int32_t outCap) {
|
||||
BasParserT parser;
|
||||
basParserInit(&parser, source, (int32_t)strlen(source));
|
||||
|
||||
if (!basParse(&parser)) {
|
||||
fprintf(stderr, "compile error: %s\n", parser.error);
|
||||
basParserFree(&parser);
|
||||
return -1;
|
||||
}
|
||||
|
||||
BasModuleT *mod = basParserBuildModule(&parser);
|
||||
basParserFree(&parser);
|
||||
|
||||
if (!mod) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (compact) {
|
||||
basStripModule(mod);
|
||||
basCompactBytecode(mod);
|
||||
}
|
||||
|
||||
BasVmT *vm = basVmCreate();
|
||||
basVmLoadModule(vm, mod);
|
||||
|
||||
OutBufT ob;
|
||||
ob.buf = outBuf;
|
||||
ob.len = 0;
|
||||
ob.cap = outCap;
|
||||
|
||||
basVmSetPrintCallback(vm, captureCallback, &ob);
|
||||
|
||||
vm->callStack[0].localCount = mod->globalCount > 64 ? 64 : mod->globalCount;
|
||||
vm->callDepth = 1;
|
||||
|
||||
BasVmResultE result = basVmRun(vm);
|
||||
|
||||
if (result != BAS_VM_HALTED && result != BAS_VM_OK) {
|
||||
fprintf(stderr, "vm error %d: %s\n", result, basVmGetError(vm));
|
||||
}
|
||||
|
||||
basVmDestroy(vm);
|
||||
basModuleFree(mod);
|
||||
|
||||
outBuf[ob.len] = '\0';
|
||||
return ob.len;
|
||||
}
|
||||
|
||||
|
||||
static int32_t sTotal = 0;
|
||||
static int32_t sFailed = 0;
|
||||
|
||||
|
||||
static void testCompact(const char *name, const char *source) {
|
||||
sTotal++;
|
||||
|
||||
static char orig[MAX_OUT];
|
||||
static char comp[MAX_OUT];
|
||||
|
||||
int32_t origLen = runAndCapture(source, false, orig, MAX_OUT);
|
||||
int32_t compLen = runAndCapture(source, true, comp, MAX_OUT);
|
||||
|
||||
if (origLen < 0 || compLen < 0) {
|
||||
printf("FAIL: %s (runtime error)\n", name);
|
||||
sFailed++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (origLen != compLen || memcmp(orig, comp, origLen) != 0) {
|
||||
printf("FAIL: %s\n", name);
|
||||
printf(" uncompacted (%d bytes):\n%s\n", (int)origLen, orig);
|
||||
printf(" compacted (%d bytes):\n%s\n", (int)compLen, comp);
|
||||
sFailed++;
|
||||
return;
|
||||
}
|
||||
|
||||
printf("PASS: %s\n", name);
|
||||
}
|
||||
|
||||
|
||||
int main(void) {
|
||||
printf("DVX BASIC Bytecode Compaction Tests\n");
|
||||
printf("====================================\n\n");
|
||||
|
||||
basStringSystemInit();
|
||||
|
||||
// ---- Basic control flow ----
|
||||
|
||||
testCompact("FOR loop",
|
||||
"DIM i AS INTEGER\n"
|
||||
"FOR i = 1 TO 5\n"
|
||||
" PRINT i;\n"
|
||||
"NEXT i\n"
|
||||
"PRINT\n"
|
||||
);
|
||||
|
||||
testCompact("Nested FOR",
|
||||
"DIM i AS INTEGER\n"
|
||||
"DIM j AS INTEGER\n"
|
||||
"FOR i = 1 TO 3\n"
|
||||
" FOR j = 1 TO 2\n"
|
||||
" PRINT i * 10 + j;\n"
|
||||
" NEXT j\n"
|
||||
"NEXT i\n"
|
||||
"PRINT\n"
|
||||
);
|
||||
|
||||
testCompact("FOR with negative STEP",
|
||||
"DIM i AS INTEGER\n"
|
||||
"FOR i = 5 TO 1 STEP -1\n"
|
||||
" PRINT i;\n"
|
||||
"NEXT i\n"
|
||||
"PRINT\n"
|
||||
);
|
||||
|
||||
testCompact("EXIT FOR",
|
||||
"DIM i AS INTEGER\n"
|
||||
"FOR i = 1 TO 100\n"
|
||||
" IF i = 4 THEN EXIT FOR\n"
|
||||
" PRINT i;\n"
|
||||
"NEXT i\n"
|
||||
"PRINT\n"
|
||||
);
|
||||
|
||||
testCompact("DO WHILE",
|
||||
"DIM n AS INTEGER\n"
|
||||
"n = 0\n"
|
||||
"DO WHILE n < 3\n"
|
||||
" PRINT n;\n"
|
||||
" n = n + 1\n"
|
||||
"LOOP\n"
|
||||
"PRINT\n"
|
||||
);
|
||||
|
||||
testCompact("IF THEN ELSE",
|
||||
"DIM x AS INTEGER\n"
|
||||
"x = 5\n"
|
||||
"IF x > 3 THEN\n"
|
||||
" PRINT \"big\"\n"
|
||||
"ELSE\n"
|
||||
" PRINT \"small\"\n"
|
||||
"END IF\n"
|
||||
);
|
||||
|
||||
testCompact("SELECT CASE",
|
||||
"DIM g AS STRING\n"
|
||||
"g = \"B\"\n"
|
||||
"SELECT CASE g\n"
|
||||
" CASE \"A\"\n"
|
||||
" PRINT \"A\"\n"
|
||||
" CASE \"B\", \"C\"\n"
|
||||
" PRINT \"BC\"\n"
|
||||
" CASE ELSE\n"
|
||||
" PRINT \"?\"\n"
|
||||
"END SELECT\n"
|
||||
);
|
||||
|
||||
// ---- GOSUB: exercises the absolute address in OP_PUSH_INT32 pattern ----
|
||||
|
||||
testCompact("GOSUB",
|
||||
"DIM n AS INTEGER\n"
|
||||
"n = 10\n"
|
||||
"GOSUB doubler\n"
|
||||
"PRINT n\n"
|
||||
"GOSUB doubler\n"
|
||||
"PRINT n\n"
|
||||
"END\n"
|
||||
"doubler:\n"
|
||||
"n = n * 2\n"
|
||||
"RETURN\n"
|
||||
);
|
||||
|
||||
testCompact("Multiple GOSUBs",
|
||||
"DIM x AS INTEGER\n"
|
||||
"x = 1\n"
|
||||
"GOSUB a\n"
|
||||
"GOSUB b\n"
|
||||
"GOSUB c\n"
|
||||
"PRINT x\n"
|
||||
"END\n"
|
||||
"a:\n"
|
||||
"x = x + 10\n"
|
||||
"RETURN\n"
|
||||
"b:\n"
|
||||
"x = x + 100\n"
|
||||
"RETURN\n"
|
||||
"c:\n"
|
||||
"x = x + 1000\n"
|
||||
"RETURN\n"
|
||||
);
|
||||
|
||||
// ---- SUB / FUNCTION: exercises absolute CALL addresses + proc table ----
|
||||
|
||||
testCompact("SUB with CALL",
|
||||
"CALL Greet\n"
|
||||
"CALL Greet\n"
|
||||
"CALL Greet\n"
|
||||
"SUB Greet\n"
|
||||
" PRINT \"Hello\"\n"
|
||||
"END SUB\n"
|
||||
);
|
||||
|
||||
testCompact("FUNCTION return value",
|
||||
"DECLARE FUNCTION Square(x AS INTEGER) AS INTEGER\n"
|
||||
"DIM r AS INTEGER\n"
|
||||
"r = Square(5)\n"
|
||||
"PRINT r\n"
|
||||
"r = Square(7)\n"
|
||||
"PRINT r\n"
|
||||
"FUNCTION Square(x AS INTEGER) AS INTEGER\n"
|
||||
" Square = x * x\n"
|
||||
"END FUNCTION\n"
|
||||
);
|
||||
|
||||
testCompact("Recursive FUNCTION",
|
||||
"DECLARE FUNCTION Fact(n AS INTEGER) AS INTEGER\n"
|
||||
"PRINT Fact(5)\n"
|
||||
"FUNCTION Fact(n AS INTEGER) AS INTEGER\n"
|
||||
" IF n <= 1 THEN\n"
|
||||
" Fact = 1\n"
|
||||
" ELSE\n"
|
||||
" Fact = n * Fact(n - 1)\n"
|
||||
" END IF\n"
|
||||
"END FUNCTION\n"
|
||||
);
|
||||
|
||||
// ---- ON ERROR: exercises the relative int16 offset path with non-zero ----
|
||||
|
||||
testCompact("ON ERROR GOTO",
|
||||
"ON ERROR GOTO handler\n"
|
||||
"PRINT 10 / 0\n"
|
||||
"PRINT \"unreached\"\n"
|
||||
"END\n"
|
||||
"handler:\n"
|
||||
"PRINT \"caught\"\n"
|
||||
);
|
||||
|
||||
// ---- Mixed: many OP_LINE between jumps ----
|
||||
|
||||
testCompact("Long function with many lines",
|
||||
"DIM total AS INTEGER\n"
|
||||
"total = 0\n"
|
||||
"DIM i AS INTEGER\n"
|
||||
"FOR i = 1 TO 10\n"
|
||||
" IF i MOD 2 = 0 THEN\n"
|
||||
" total = total + i\n"
|
||||
" ELSE\n"
|
||||
" total = total + i * 2\n"
|
||||
" END IF\n"
|
||||
"NEXT i\n"
|
||||
"PRINT total\n"
|
||||
);
|
||||
|
||||
testCompact("GOSUB inside FOR",
|
||||
"DIM i AS INTEGER\n"
|
||||
"DIM sum AS INTEGER\n"
|
||||
"sum = 0\n"
|
||||
"FOR i = 1 TO 5\n"
|
||||
" GOSUB addit\n"
|
||||
"NEXT i\n"
|
||||
"PRINT sum\n"
|
||||
"END\n"
|
||||
"addit:\n"
|
||||
"sum = sum + i\n"
|
||||
"RETURN\n"
|
||||
);
|
||||
|
||||
testCompact("FOR inside SUB",
|
||||
"CALL Loop5\n"
|
||||
"SUB Loop5\n"
|
||||
" DIM k AS INTEGER\n"
|
||||
" FOR k = 0 TO 4\n"
|
||||
" PRINT k;\n"
|
||||
" NEXT k\n"
|
||||
" PRINT\n"
|
||||
"END SUB\n"
|
||||
);
|
||||
|
||||
printf("\n%d/%d tests passed\n", (int)(sTotal - sFailed), (int)sTotal);
|
||||
return sFailed > 0 ? 1 : 0;
|
||||
}
|
||||
|
|
@ -900,6 +900,107 @@ int main(void) {
|
|||
printf("\n");
|
||||
}
|
||||
|
||||
// Regression test: App.Data must parse even though Data is a BASIC keyword
|
||||
runProgram("App.Data parses",
|
||||
"DIM p AS STRING\n"
|
||||
"p = App.Data\n"
|
||||
"PRINT p\n"
|
||||
);
|
||||
|
||||
// Regression test: RGB(r,g,b) packs into 0x00RRGGBB
|
||||
runProgram("RGB packing",
|
||||
"PRINT RGB(255, 128, 0)\n"
|
||||
"PRINT RGB(0, 255, 0)\n"
|
||||
"PRINT RGB(0, 0, 255)\n"
|
||||
);
|
||||
// Expected: 16744448 (0xFF8000), 65280 (0x00FF00), 255 (0x0000FF)
|
||||
|
||||
// Regression test: GetRed/Green/Blue round-trip
|
||||
runProgram("RGB round-trip",
|
||||
"DIM c AS LONG\n"
|
||||
"c = RGB(17, 99, 200)\n"
|
||||
"PRINT GetRed(c); GetGreen(c); GetBlue(c)\n"
|
||||
);
|
||||
// Expected: 17 99 200
|
||||
|
||||
// Regression test: MsgBox function accepts optional title (3rd arg)
|
||||
// The test harness's default msgBox callback returns 1; we just verify
|
||||
// the program compiles and runs.
|
||||
runProgram("MsgBox 3-arg compile",
|
||||
"DIM r AS INTEGER\n"
|
||||
"r = MsgBox(\"hi\", 0, \"My Title\")\n"
|
||||
"PRINT \"ok\"\n"
|
||||
);
|
||||
|
||||
// Regression test: CONST accepts AS type
|
||||
runProgram("CONST AS type",
|
||||
"CONST PI AS DOUBLE = 3.14159\n"
|
||||
"CONST N AS INTEGER = 42\n"
|
||||
"PRINT PI\n"
|
||||
"PRINT N\n"
|
||||
);
|
||||
|
||||
// Regression test: PRINT #ch with ; separator
|
||||
runProgram("PRINT # with semicolon",
|
||||
"OPEN \"/tmp/dvxbasic_psemi.txt\" FOR OUTPUT AS #1\n"
|
||||
"PRINT #1, \"Line one\"\n"
|
||||
"PRINT #1, \"ans = \"; 42\n"
|
||||
"PRINT #1, \"x\"; \"y\"; \"z\"\n"
|
||||
"CLOSE #1\n"
|
||||
"DIM s AS STRING\n"
|
||||
"OPEN \"/tmp/dvxbasic_psemi.txt\" FOR INPUT AS #1\n"
|
||||
"DO WHILE NOT EOF(1)\n"
|
||||
" LINE INPUT #1, s\n"
|
||||
" PRINT s\n"
|
||||
"LOOP\n"
|
||||
"CLOSE #1\n"
|
||||
);
|
||||
// Expected: Line one / ans = 42 / xyz
|
||||
|
||||
// Regression test: IniRead$ with $ suffix must tokenize as keyword
|
||||
runProgram("IniRead$ tokenizes",
|
||||
"DIM v AS STRING\n"
|
||||
"v = IniRead$(\"/tmp/nofile.ini\", \"S\", \"K\", \"default\")\n"
|
||||
"PRINT v\n"
|
||||
);
|
||||
// Expected: default (file doesn't exist)
|
||||
|
||||
// Regression test: SUB extern call without parens must emit OP_CALL_EXTERN
|
||||
// (previously emitted OP_CALL which called nothing, because no internal
|
||||
// proc at address 0 existed for the extern.)
|
||||
{
|
||||
printf("=== DECLARE LIBRARY SUB no-parens ===\n");
|
||||
const char *src =
|
||||
"DECLARE LIBRARY \"basrt\"\n"
|
||||
" DECLARE SUB DoIt(BYVAL s AS STRING)\n"
|
||||
"END DECLARE\n"
|
||||
"DoIt \"hello\"\n";
|
||||
int32_t len = (int32_t)strlen(src);
|
||||
BasParserT parser;
|
||||
basParserInit(&parser, src, len);
|
||||
bool ok = basParse(&parser);
|
||||
|
||||
if (!ok) {
|
||||
printf("COMPILE ERROR: %s\n", parser.error);
|
||||
} else {
|
||||
BasModuleT *mod = basParserBuildModule(&parser);
|
||||
bool found = false;
|
||||
|
||||
for (int32_t i = 0; i < mod->codeLen; i++) {
|
||||
if (mod->code[i] == OP_CALL_EXTERN) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
printf(found ? "PASS: OP_CALL_EXTERN emitted\n" : "FAIL: OP_CALL_EXTERN not emitted\n");
|
||||
basModuleFree(mod);
|
||||
}
|
||||
|
||||
basParserFree(&parser);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
// Test: Procedure table populated for SUBs and FUNCTIONs
|
||||
{
|
||||
printf("=== Procedure table ===\n");
|
||||
|
|
@ -1213,6 +1314,56 @@ int main(void) {
|
|||
);
|
||||
// Expected: 1 2 / after
|
||||
|
||||
// Regression test: FOR loop inside a SUB with local loop variable
|
||||
runProgram("FOR inside SUB with local",
|
||||
"CALL MySub\n"
|
||||
"SUB MySub\n"
|
||||
" DIM i AS LONG\n"
|
||||
" FOR i = 0 TO 3\n"
|
||||
" PRINT i;\n"
|
||||
" NEXT i\n"
|
||||
" PRINT\n"
|
||||
"END SUB\n"
|
||||
);
|
||||
// Expected: 0 1 2 3
|
||||
|
||||
// Regression test: resedit pattern -- FOR in SUB with n = FUNC(...)
|
||||
runProgram("FOR with func-returned limit",
|
||||
"CALL DoIt\n"
|
||||
"SUB DoIt\n"
|
||||
" DIM n AS LONG\n"
|
||||
" n = GetCount()\n"
|
||||
" DIM i AS LONG\n"
|
||||
" FOR i = 0 TO n - 1\n"
|
||||
" PRINT i;\n"
|
||||
" NEXT i\n"
|
||||
" PRINT\n"
|
||||
"END SUB\n"
|
||||
"FUNCTION GetCount() AS LONG\n"
|
||||
" GetCount = 4\n"
|
||||
"END FUNCTION\n"
|
||||
);
|
||||
// Expected: 0 1 2 3
|
||||
|
||||
// Regression test: resedit pattern -- FOR in SUB inside a FORM
|
||||
runProgram("FOR in SUB inside FORM",
|
||||
"BEGINFORM \"Form1\"\n"
|
||||
"DIM formVar AS LONG\n"
|
||||
"formVar = 42\n"
|
||||
"CALL DoIt\n"
|
||||
"SUB DoIt\n"
|
||||
" DIM n AS LONG\n"
|
||||
" n = 4\n"
|
||||
" DIM ix AS LONG\n"
|
||||
" FOR ix = 0 TO n - 1\n"
|
||||
" PRINT ix;\n"
|
||||
" NEXT ix\n"
|
||||
" PRINT\n"
|
||||
"END SUB\n"
|
||||
"ENDFORM\n"
|
||||
);
|
||||
// Expected: 0 1 2 3
|
||||
|
||||
runProgram("EXIT DO",
|
||||
"DIM n AS INTEGER\n"
|
||||
"n = 0\n"
|
||||
|
|
|
|||
|
|
@ -153,11 +153,11 @@ static void onCloseMainCb(WindowT *win) {
|
|||
|
||||
|
||||
static const FileFilterT sFileFilters[] = {
|
||||
{"All Files (*.*)", "*.*"},
|
||||
{"Text Files (*.txt)", "*.txt"},
|
||||
{"Batch Files (*.bat)", "*.bat"},
|
||||
{"Executables (*.exe)", "*.exe"},
|
||||
{"Bitmap Files (*.bmp)", "*.bmp"}
|
||||
{"All Files (*.*)"},
|
||||
{"Text Files (*.txt)"},
|
||||
{"Batch Files (*.bat)"},
|
||||
{"Executables (*.exe)"},
|
||||
{"Bitmap Files (*.bmp)"}
|
||||
};
|
||||
|
||||
static void onMenuCb(WindowT *win, int32_t menuId) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# dvxdemo.res -- Resource manifest for DVX Demo
|
||||
icon32 icon dvxdemo/icon32.bmp
|
||||
icon32 icon icon32.bmp
|
||||
name text "DVX Demo"
|
||||
author text "DVX Project"
|
||||
description text "Widget toolkit demonstration"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# dvxhelp.res -- Resource manifest for DVX Help Viewer
|
||||
icon32 icon dvxhelp/icon32.bmp
|
||||
icon32 icon icon32.bmp
|
||||
name text "DVX Help"
|
||||
author text "DVX Project"
|
||||
description text "Help file viewer"
|
||||
|
|
|
|||
|
|
@ -328,8 +328,8 @@ static void onResize(WindowT *win, int32_t contentW, int32_t contentH) {
|
|||
|
||||
static void openFile(void) {
|
||||
FileFilterT filters[] = {
|
||||
{ "Images (*.bmp;*.jpg;*.png;*.gif)", "*.bmp;*.jpg;*.png;*.gif" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
{ "Images (*.bmp;*.jpg;*.png;*.gif)" },
|
||||
{ "All Files (*.*)" }
|
||||
};
|
||||
char path[DVX_MAX_PATH];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# imgview.res -- Resource manifest for Image Viewer
|
||||
icon32 icon imgview/icon32.bmp
|
||||
icon32 icon icon32.bmp
|
||||
name text "Image Viewer"
|
||||
author text "DVX Project"
|
||||
description text "BMP, PNG, JPEG, and GIF viewer"
|
||||
|
|
|
|||
|
|
@ -150,8 +150,8 @@ static void doOpen(void) {
|
|||
}
|
||||
|
||||
FileFilterT filters[] = {
|
||||
{ "Text Files (*.txt)", "*.txt" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
{ "Text Files (*.txt)" },
|
||||
{ "All Files (*.*)" }
|
||||
};
|
||||
char path[DVX_MAX_PATH];
|
||||
|
||||
|
|
@ -236,8 +236,8 @@ static void doSave(void) {
|
|||
|
||||
static void doSaveAs(void) {
|
||||
FileFilterT filters[] = {
|
||||
{ "Text Files (*.txt)", "*.txt" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
{ "Text Files (*.txt)" },
|
||||
{ "All Files (*.*)" }
|
||||
};
|
||||
char path[DVX_MAX_PATH];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# notepad.res -- Resource manifest for Notepad
|
||||
icon32 icon notepad/icon32.bmp
|
||||
icon32 icon icon32.bmp
|
||||
name text "Notepad"
|
||||
author text "DVX Project"
|
||||
description text "Simple text editor"
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@
|
|||
#define CMD_TILE_V 203
|
||||
#define CMD_MIN_ON_RUN 104
|
||||
#define CMD_RESTORE_ALONE 105
|
||||
#define CMD_RELOAD 106
|
||||
#define CMD_ABOUT 300
|
||||
#define CMD_TASK_MGR 301
|
||||
#define CMD_SYSINFO 302
|
||||
|
|
@ -166,6 +167,7 @@ static void buildPmWindow(void) {
|
|||
MenuBarT *menuBar = wmAddMenuBar(sPmWindow);
|
||||
MenuT *fileMenu = wmAddMenu(menuBar, "&File");
|
||||
wmAddMenuItem(fileMenu, "&Run...", CMD_RUN);
|
||||
wmAddMenuItem(fileMenu, "Re&load", CMD_RELOAD);
|
||||
wmAddMenuSeparator(fileMenu);
|
||||
wmAddMenuItem(fileMenu, "E&xit DVX", CMD_EXIT);
|
||||
|
||||
|
|
@ -313,8 +315,8 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
|
|||
case CMD_RUN:
|
||||
{
|
||||
FileFilterT filters[] = {
|
||||
{ "Applications (*.app)", "*.app" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
{ "Applications (*.app)" },
|
||||
{ "All Files (*.*)" }
|
||||
};
|
||||
char path[MAX_PATH_LEN];
|
||||
|
||||
|
|
@ -329,6 +331,14 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
|
|||
}
|
||||
break;
|
||||
|
||||
case CMD_RELOAD:
|
||||
dvxDestroyWindow(sAc, sPmWindow);
|
||||
sPmWindow = NULL;
|
||||
sStatusLabel = NULL;
|
||||
scanAppsDir();
|
||||
buildPmWindow();
|
||||
break;
|
||||
|
||||
case CMD_EXIT:
|
||||
onPmClose(sPmWindow);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ bpp = 16
|
|||
|
||||
; Mouse settings.
|
||||
; wheel: normal or reversed
|
||||
; wheelspeed: lines per wheel notch (1-10, default 3)
|
||||
; doubleclick: double-click speed in milliseconds (200-900, default 500)
|
||||
; acceleration: off, low, medium, high (default medium)
|
||||
; speed: cursor speed (2-32, default 8; higher = faster)
|
||||
|
||||
[mouse]
|
||||
wheel = normal
|
||||
wheelspeed = 3
|
||||
doubleclick = 500
|
||||
acceleration = medium
|
||||
speed = 8
|
||||
|
|
|
|||
|
|
@ -1277,8 +1277,12 @@ static void dispatchEvents(AppContextT *ctx) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle left button press
|
||||
// Handle left button press. Consume the edge immediately so that if
|
||||
// the click handler calls back into the event loop (e.g., BASIC's
|
||||
// DoEvents yielding while a QueryUnload handler runs), the same
|
||||
// physical click isn't re-seen as a fresh press.
|
||||
if ((buttons & MOUSE_LEFT) && !(prevBtn & MOUSE_LEFT)) {
|
||||
ctx->prevMouseButtons |= MOUSE_LEFT;
|
||||
handleMouseButton(ctx, mx, my, buttons);
|
||||
}
|
||||
|
||||
|
|
@ -1289,6 +1293,7 @@ static void dispatchEvents(AppContextT *ctx) {
|
|||
// that apply to all their children without requiring each child to have
|
||||
// its own menu, while still allowing per-widget overrides.
|
||||
if ((buttons & MOUSE_RIGHT) && !(prevBtn & MOUSE_RIGHT)) {
|
||||
ctx->prevMouseButtons |= MOUSE_RIGHT;
|
||||
int32_t hitPart;
|
||||
int32_t hitIdx = wmHitTest(&ctx->stack, mx, my, &hitPart);
|
||||
|
||||
|
|
@ -1396,7 +1401,7 @@ static void dispatchEvents(AppContextT *ctx) {
|
|||
if (target && wclsHas(target, WGT_METHOD_ON_KEY)) {
|
||||
int32_t delta = ctx->mouseWheel * ctx->wheelDirection;
|
||||
int32_t arrowKey = (delta > 0) ? (0x50 | 0x100) : (0x48 | 0x100);
|
||||
int32_t steps = abs(delta) * MOUSE_WHEEL_STEP;
|
||||
int32_t steps = abs(delta) * ctx->wheelStep;
|
||||
|
||||
for (int32_t s = 0; s < steps; s++) {
|
||||
wclsOnKey(target, arrowKey, 0);
|
||||
|
|
@ -1423,7 +1428,7 @@ static void dispatchEvents(AppContextT *ctx) {
|
|||
|
||||
if (sb) {
|
||||
int32_t oldValue = sb->value;
|
||||
sb->value += ctx->mouseWheel * ctx->wheelDirection * MOUSE_WHEEL_STEP;
|
||||
sb->value += ctx->mouseWheel * ctx->wheelDirection * ctx->wheelStep;
|
||||
|
||||
if (sb->value < sb->min) {
|
||||
sb->value = sb->min;
|
||||
|
|
@ -1721,11 +1726,18 @@ static void executeSysMenuCmd(AppContextT *ctx, int32_t cmd) {
|
|||
break;
|
||||
|
||||
case SysMenuCloseE:
|
||||
// Sys menu is already closed. Composite a clean frame first
|
||||
// so the menu's pixels are erased before onClose runs -- the
|
||||
// close handler may open a modal dialog and we don't want the
|
||||
// sys menu popup peeking out from under it.
|
||||
compositeAndFlush(ctx);
|
||||
|
||||
if (win->onClose) {
|
||||
WIN_CALLBACK(ctx, win, win->onClose(win));
|
||||
} else {
|
||||
dvxDestroyWindow(ctx, win);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1872,11 +1884,20 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
|
|||
ctx->lastCloseClickId = -1;
|
||||
closeSysMenu(ctx);
|
||||
|
||||
// Composite a clean frame first so the sys menu is
|
||||
// fully gone before onClose runs -- the close handler
|
||||
// may open a modal dialog and we don't want the menu
|
||||
// peeking out from under it.
|
||||
compositeAndFlush(ctx);
|
||||
|
||||
if (win->onClose) {
|
||||
WIN_CALLBACK(ctx, win, win->onClose(win));
|
||||
} else {
|
||||
dvxDestroyWindow(ctx, win);
|
||||
}
|
||||
|
||||
// Ensure sys menu is closed even if onClose re-opened it
|
||||
closeSysMenu(ctx);
|
||||
} else {
|
||||
ctx->lastCloseClickTime = now;
|
||||
ctx->lastCloseClickId = win->id;
|
||||
|
|
@ -1965,8 +1986,8 @@ static void initColorScheme(AppContextT *ctx) {
|
|||
|
||||
static void interactiveScreenshot(AppContextT *ctx) {
|
||||
FileFilterT filters[] = {
|
||||
{ "PNG Images (*.png)", "*.png" },
|
||||
{ "BMP Images (*.bmp)", "*.bmp" }
|
||||
{ "PNG Images (*.png)" },
|
||||
{ "BMP Images (*.bmp)" }
|
||||
};
|
||||
char path[DVX_MAX_PATH];
|
||||
|
||||
|
|
@ -2000,8 +2021,8 @@ static void interactiveWindowScreenshot(AppContextT *ctx, WindowT *win) {
|
|||
}
|
||||
|
||||
FileFilterT filters[] = {
|
||||
{ "PNG Images (*.png)", "*.png" },
|
||||
{ "BMP Images (*.bmp)", "*.bmp" }
|
||||
{ "PNG Images (*.png)" },
|
||||
{ "BMP Images (*.bmp)" }
|
||||
};
|
||||
char path[DVX_MAX_PATH];
|
||||
|
||||
|
|
@ -4309,6 +4330,7 @@ int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_
|
|||
ctx->lastTitleClickId = -1;
|
||||
ctx->lastTitleClickTime = 0;
|
||||
ctx->wheelDirection = 1;
|
||||
ctx->wheelStep = MOUSE_WHEEL_STEP_DEFAULT;
|
||||
ctx->dblClickTicks = DBLCLICK_THRESHOLD;
|
||||
sDblClickTicks = DBLCLICK_THRESHOLD;
|
||||
|
||||
|
|
@ -4833,11 +4855,21 @@ void dvxSetColor(AppContextT *ctx, ColorIdE id, uint8_t r, uint8_t g, uint8_t b)
|
|||
// dvxSetMouseConfig
|
||||
// ============================================================
|
||||
|
||||
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold, int32_t mickeyRatio) {
|
||||
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold, int32_t mickeyRatio, int32_t wheelStep) {
|
||||
ctx->wheelDirection = (wheelDir < 0) ? -1 : 1;
|
||||
ctx->dblClickTicks = (clock_t)dblClickMs * CLOCKS_PER_SEC / 1000;
|
||||
sDblClickTicks = ctx->dblClickTicks;
|
||||
|
||||
if (wheelStep < MOUSE_WHEEL_STEP_MIN) {
|
||||
wheelStep = MOUSE_WHEEL_STEP_MIN;
|
||||
}
|
||||
|
||||
if (wheelStep > MOUSE_WHEEL_STEP_MAX) {
|
||||
wheelStep = MOUSE_WHEEL_STEP_MAX;
|
||||
}
|
||||
|
||||
ctx->wheelStep = wheelStep;
|
||||
|
||||
if (accelThreshold > 0) {
|
||||
platformMouseSetAccel(accelThreshold);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ typedef struct AppContextT {
|
|||
uint32_t charHeightRecip; // fixed-point 16.16 reciprocal of font.charHeight
|
||||
// Mouse configuration (loaded from preferences)
|
||||
int32_t wheelDirection; // 1 = normal, -1 = reversed
|
||||
int32_t wheelStep; // lines per wheel notch (1-10, default 3)
|
||||
clock_t dblClickTicks; // double-click speed in clock() ticks
|
||||
// Color scheme source RGB values (unpacked, for theme save/get)
|
||||
uint8_t colorRgb[ColorCountE][3];
|
||||
|
|
@ -134,7 +135,8 @@ int32_t dvxChangeVideoMode(AppContextT *ctx, int32_t requestedW, int32_t request
|
|||
// dblClickMs: double-click speed in milliseconds (e.g. 500).
|
||||
// accelThreshold: double-speed threshold in mickeys/sec (0 = don't change).
|
||||
// mickeyRatio: mickeys per 8 pixels (0 = don't change, 8 = default speed).
|
||||
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold, int32_t mickeyRatio);
|
||||
// wheelStep: lines per wheel notch (1-10, default 3).
|
||||
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold, int32_t mickeyRatio, int32_t wheelStep);
|
||||
|
||||
// ============================================================
|
||||
// Color scheme
|
||||
|
|
|
|||
|
|
@ -1071,6 +1071,32 @@ typedef struct {
|
|||
static FileDialogStateT sFd;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// fdExtractPattern -- extract glob pattern from inside parentheses in label
|
||||
// ============================================================
|
||||
|
||||
static const char *fdExtractPattern(const FileFilterT *f, char *buf, int32_t bufSize) {
|
||||
const char *open = strchr(f->label, '(');
|
||||
const char *close = open ? strchr(open, ')') : NULL;
|
||||
|
||||
if (open && close && close > open + 1) {
|
||||
int32_t len = (int32_t)(close - open - 1);
|
||||
|
||||
if (len >= bufSize) {
|
||||
len = bufSize - 1;
|
||||
}
|
||||
|
||||
memcpy(buf, open + 1, len);
|
||||
buf[len] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
// No parens -- use the whole label as the pattern
|
||||
snprintf(buf, bufSize, "%s", f->label);
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// fdFilterMatch -- check if filename matches a glob pattern
|
||||
// ============================================================
|
||||
|
|
@ -1210,9 +1236,10 @@ static void fdLoadDir(void) {
|
|||
fdFreeEntries();
|
||||
|
||||
const char *pattern = NULL;
|
||||
char patBuf[128];
|
||||
|
||||
if (sFd.filters && sFd.activeFilter >= 0 && sFd.activeFilter < sFd.filterCount) {
|
||||
pattern = sFd.filters[sFd.activeFilter].pattern;
|
||||
pattern = fdExtractPattern(&sFd.filters[sFd.activeFilter], patBuf, sizeof(patBuf));
|
||||
}
|
||||
|
||||
DIR *dir = opendir(sFd.curDir);
|
||||
|
|
@ -1561,6 +1588,59 @@ static void fdOnOk(WidgetT *w) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Save dialog: if the filename has no extension, append the first
|
||||
// extension from the active filter pattern. Wildcards like "*.*" or
|
||||
// "*" don't add an extension.
|
||||
char nameWithExt[FD_MAX_PATH];
|
||||
|
||||
if ((sFd.flags & FD_SAVE) && !strchr(name, '.')) {
|
||||
char extPatBuf[128];
|
||||
const char *pattern = fdExtractPattern(&sFd.filters[sFd.activeFilter], extPatBuf, sizeof(extPatBuf));
|
||||
const char *ext = NULL;
|
||||
|
||||
// Find first non-wildcard extension in pattern (may be "*.txt;*.doc")
|
||||
if (pattern) {
|
||||
const char *p = pattern;
|
||||
|
||||
while (*p) {
|
||||
if (p[0] == '*' && p[1] == '.') {
|
||||
const char *e = p + 2;
|
||||
|
||||
// Skip wildcards like "*.*"
|
||||
if (*e != '*' && *e != '\0') {
|
||||
ext = p + 1; // points to ".ext"
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip to next semicolon-delimited pattern
|
||||
const char *semi = strchr(p, ';');
|
||||
|
||||
if (semi) {
|
||||
p = semi + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ext) {
|
||||
// Extract just the extension (up to ; or end)
|
||||
char extBuf[16];
|
||||
int32_t ei = 0;
|
||||
|
||||
while (ext[ei] && ext[ei] != ';' && ei < 15) {
|
||||
extBuf[ei] = ext[ei];
|
||||
ei++;
|
||||
}
|
||||
|
||||
extBuf[ei] = '\0';
|
||||
snprintf(nameWithExt, sizeof(nameWithExt), "%s%s", name, extBuf);
|
||||
name = nameWithExt;
|
||||
wgtSetText(sFd.nameInput, name);
|
||||
}
|
||||
}
|
||||
|
||||
// Accept the file (with confirmation if needed)
|
||||
fdAcceptFile(name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,9 +64,11 @@ int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message,
|
|||
// patterns (no semicolon-separated lists). This keeps the matching code
|
||||
// trivial for a DOS filesystem where filenames are short and simple.
|
||||
|
||||
// File filter for the file dialog. Pattern is extracted from inside
|
||||
// parentheses in the label. Example: "Text Files (*.txt)"
|
||||
// Multiple extensions: "Images (*.bmp;*.png;*.jpg;*.gif)"
|
||||
typedef struct {
|
||||
const char *label; // e.g. "Text Files (*.txt)"
|
||||
const char *pattern; // e.g. "*.txt" (case-insensitive, single pattern)
|
||||
const char *label; // e.g. "Text Files (*.txt)" -- pattern extracted from parens
|
||||
} FileFilterT;
|
||||
|
||||
// Display a modal file open/save dialog. The dialog shows a directory
|
||||
|
|
|
|||
|
|
@ -77,4 +77,8 @@ void dvxResClose(DvxResHandleT *h);
|
|||
// Returns 0 on success, -1 on error.
|
||||
int32_t dvxResAppend(const char *path, const char *name, uint32_t type, const void *data, uint32_t dataSize);
|
||||
|
||||
// Remove a resource by name from a DXE file.
|
||||
// Returns 0 on success, -1 on error (not found or I/O failure).
|
||||
int32_t dvxResRemove(const char *path, const char *name);
|
||||
|
||||
#endif // DVX_RES_H
|
||||
|
|
|
|||
|
|
@ -144,3 +144,105 @@ void dvxResClose(DvxResHandleT *h) {
|
|||
int32_t dvxResAppend(const char *path, const char *name, uint32_t type, const void *data, uint32_t dataSize) {
|
||||
return dvxResAppendEntry(path, name, type, data, dataSize);
|
||||
}
|
||||
|
||||
|
||||
int32_t dvxResRemove(const char *path, const char *name) {
|
||||
if (!path || !name) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
long dxeSize = dvxResDxeContentSize(path);
|
||||
|
||||
if (dxeSize < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
DvxResDirEntryT *entries = NULL;
|
||||
uint32_t count = 0;
|
||||
uint8_t **data = NULL;
|
||||
dvxResReadExisting(path, dxeSize, &entries, &count, &data);
|
||||
|
||||
if (!entries || count == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (strcmp(entries[i].name, name) == 0) {
|
||||
free(data[i]);
|
||||
|
||||
for (uint32_t j = i; j < count - 1; j++) {
|
||||
entries[j] = entries[j + 1];
|
||||
data[j] = data[j + 1];
|
||||
}
|
||||
|
||||
count--;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
free(data[i]);
|
||||
}
|
||||
|
||||
free(data);
|
||||
free(entries);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t result;
|
||||
|
||||
if (count == 0) {
|
||||
// No resources left -- truncate to DXE content only
|
||||
FILE *f = fopen(path, "rb");
|
||||
|
||||
if (!f) {
|
||||
free(data);
|
||||
free(entries);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t *dxeBuf = (uint8_t *)malloc((size_t)dxeSize);
|
||||
|
||||
if (!dxeBuf) {
|
||||
fclose(f);
|
||||
free(data);
|
||||
free(entries);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fread(dxeBuf, 1, (size_t)dxeSize, f) != (size_t)dxeSize) {
|
||||
free(dxeBuf);
|
||||
fclose(f);
|
||||
free(data);
|
||||
free(entries);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
f = fopen(path, "wb");
|
||||
|
||||
if (f) {
|
||||
fwrite(dxeBuf, 1, (size_t)dxeSize, f);
|
||||
fclose(f);
|
||||
result = 0;
|
||||
} else {
|
||||
result = -1;
|
||||
}
|
||||
|
||||
free(dxeBuf);
|
||||
} else {
|
||||
result = dvxResWriteBlock(path, dxeSize, entries, count, data);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
free(data[i]);
|
||||
}
|
||||
|
||||
free(data);
|
||||
free(entries);
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -611,8 +611,10 @@ typedef struct {
|
|||
#define MOUSE_RIGHT 2
|
||||
#define MOUSE_MIDDLE 4
|
||||
|
||||
// Scrollbar lines to scroll per mouse wheel notch
|
||||
#define MOUSE_WHEEL_STEP 3
|
||||
// Default scrollbar lines to scroll per mouse wheel notch
|
||||
#define MOUSE_WHEEL_STEP_DEFAULT 3
|
||||
#define MOUSE_WHEEL_STEP_MIN 1
|
||||
#define MOUSE_WHEEL_STEP_MAX 10
|
||||
|
||||
// ============================================================
|
||||
// Mouse cursor
|
||||
|
|
|
|||
1
run.sh
1
run.sh
|
|
@ -1,5 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
flatpak run com.dosbox_x.DOSBox-X -conf dosbox-x-overrides.conf
|
||||
|
||||
#SDL_VIDEO_X11_VISUALID= ~/bin/dosbox-staging/dosbox -conf dosbox-staging-overrides.conf
|
||||
|
|
|
|||
|
|
@ -8,10 +8,17 @@
|
|||
|
||||
DECLARE LIBRARY "basrt"
|
||||
' Show a file Open dialog. Returns selected path, or "" if cancelled.
|
||||
' filter$ is a DOS wildcard (e.g. "*.bmp", "*.txt").
|
||||
' filter$ specifies file type filters, pipe-delimited:
|
||||
' "Label (pattern)|Label (pattern)|..."
|
||||
' The pattern is extracted from inside the parentheses.
|
||||
' Examples:
|
||||
' "*.txt" (single pattern, legacy)
|
||||
' "Text Files (*.txt)|All Files (*.*)" (two filters)
|
||||
' "Images (*.bmp;*.png;*.jpg)|All Files (*.*)" (multi-extension)
|
||||
DECLARE FUNCTION basFileOpen(BYVAL title AS STRING, BYVAL filter AS STRING) AS STRING
|
||||
|
||||
' Show a file Save dialog. Returns selected path, or "" if cancelled.
|
||||
' filter$ uses the same format as basFileOpen.
|
||||
DECLARE FUNCTION basFileSave(BYVAL title AS STRING, BYVAL filter AS STRING) AS STRING
|
||||
|
||||
' Show a modal text input box. Returns entered text, or "" if cancelled.
|
||||
|
|
@ -29,6 +36,6 @@ DECLARE LIBRARY "basrt"
|
|||
END DECLARE
|
||||
|
||||
' Return value constants for basPromptSave
|
||||
CONST DVX_SAVE_YES = 0
|
||||
CONST DVX_SAVE_NO = 1
|
||||
CONST DVX_SAVE_CANCEL = 2
|
||||
CONST DVX_SAVE_YES = 1
|
||||
CONST DVX_SAVE_NO = 2
|
||||
CONST DVX_SAVE_CANCEL = 3
|
||||
|
|
|
|||
50
sdk/include/basic/resource.bas
Normal file
50
sdk/include/basic/resource.bas
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
' resource.bas -- DVX Resource File Library for DVX BASIC
|
||||
'
|
||||
' Provides access to DVX resource blocks appended to DXE3
|
||||
' files (.app, .wgt, .lib). Resources are named data entries
|
||||
' of type icon, text, or binary.
|
||||
'
|
||||
' Usage: Add this file to your project.
|
||||
|
||||
CONST RES_TYPE_ICON = 1
|
||||
CONST RES_TYPE_TEXT = 2
|
||||
CONST RES_TYPE_BINARY = 3
|
||||
|
||||
DECLARE LIBRARY "basrt"
|
||||
' Open a resource file for reading. Returns a handle (> 0)
|
||||
' or 0 on failure. Close with ResClose when done.
|
||||
DECLARE FUNCTION ResOpen(BYVAL path AS STRING) AS LONG
|
||||
|
||||
' Close a resource handle.
|
||||
DECLARE SUB ResClose(BYVAL handle AS LONG)
|
||||
|
||||
' Return the number of resources in an open handle.
|
||||
DECLARE FUNCTION ResCount(BYVAL handle AS LONG) AS LONG
|
||||
|
||||
' Return the name of a resource by zero-based index.
|
||||
DECLARE FUNCTION ResName$(BYVAL handle AS LONG, BYVAL idx AS LONG)
|
||||
|
||||
' Return the type of a resource by zero-based index.
|
||||
' Returns RES_TYPE_ICON (1), RES_TYPE_TEXT (2), or RES_TYPE_BINARY (3).
|
||||
DECLARE FUNCTION ResType(BYVAL handle AS LONG, BYVAL idx AS LONG) AS LONG
|
||||
|
||||
' Return the size in bytes of a resource by zero-based index.
|
||||
DECLARE FUNCTION ResSize(BYVAL handle AS LONG, BYVAL idx AS LONG) AS LONG
|
||||
|
||||
' Read a text resource by name. Opens and closes the file
|
||||
' internally. Returns "" if not found.
|
||||
DECLARE FUNCTION ResGetText$(BYVAL path AS STRING, BYVAL resName AS STRING)
|
||||
|
||||
' Add or replace a text resource. Returns True on success.
|
||||
DECLARE FUNCTION ResAddText(BYVAL path AS STRING, BYVAL resName AS STRING, BYVAL text AS STRING) AS LONG
|
||||
|
||||
' Add or replace a resource from a file. Type should be
|
||||
' RES_TYPE_ICON or RES_TYPE_BINARY. Returns True on success.
|
||||
DECLARE FUNCTION ResAddFile(BYVAL path AS STRING, BYVAL resName AS STRING, BYVAL resType AS LONG, BYVAL srcFile AS STRING) AS LONG
|
||||
|
||||
' Remove a resource by name. Returns True on success.
|
||||
DECLARE FUNCTION ResRemove(BYVAL path AS STRING, BYVAL resName AS STRING) AS LONG
|
||||
|
||||
' Extract a resource to a file. Returns True on success.
|
||||
DECLARE FUNCTION ResExtract(BYVAL path AS STRING, BYVAL resName AS STRING, BYVAL outFile AS STRING) AS LONG
|
||||
END DECLARE
|
||||
BIN
sdk/samples/basic/basicdemo/ICON32.BMP
(Stored with Git LFS)
Normal file
BIN
sdk/samples/basic/basicdemo/ICON32.BMP
(Stored with Git LFS)
Normal file
Binary file not shown.
16
sdk/samples/basic/basicdemo/basicdemo.dbp
Normal file
16
sdk/samples/basic/basicdemo/basicdemo.dbp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[Project]
|
||||
Name = BASIC Demo
|
||||
Author = DVX Project
|
||||
Description = Comprehensive tour of DVX BASIC features
|
||||
Icon = ICON32.BMP
|
||||
|
||||
[Modules]
|
||||
File0 = ../../../include/basic/commdlg.bas
|
||||
Count = 1
|
||||
|
||||
[Forms]
|
||||
File0 = basicdemo.frm
|
||||
Count = 1
|
||||
|
||||
[Settings]
|
||||
StartupForm = BasicDemo
|
||||
937
sdk/samples/basic/basicdemo/basicdemo.frm
Normal file
937
sdk/samples/basic/basicdemo/basicdemo.frm
Normal file
|
|
@ -0,0 +1,937 @@
|
|||
VERSION DVX 1.00
|
||||
|
||||
Begin Form BasicDemo
|
||||
Caption = "DVX BASIC Feature Tour"
|
||||
Layout = VBox
|
||||
AutoSize = False
|
||||
Resizable = True
|
||||
Centered = True
|
||||
Width = 600
|
||||
Height = 440
|
||||
|
||||
Begin Menu mnuFile
|
||||
Caption = "&File"
|
||||
Begin Menu mnuClear
|
||||
Caption = "&Clear OutArea"
|
||||
End
|
||||
Begin Menu mnuSaveOut
|
||||
Caption = "&Save OutArea..."
|
||||
End
|
||||
Begin Menu mnuSepF1
|
||||
Caption = "-"
|
||||
End
|
||||
Begin Menu mnuExit
|
||||
Caption = "E&xit"
|
||||
End
|
||||
End
|
||||
|
||||
Begin Menu mnuRun
|
||||
Caption = "&Demos"
|
||||
Begin Menu mnuRunAll
|
||||
Caption = "Run &All Text Demos"
|
||||
End
|
||||
Begin Menu mnuSepR1
|
||||
Caption = "-"
|
||||
End
|
||||
Begin Menu mnuGraphics
|
||||
Caption = "&Graphics..."
|
||||
End
|
||||
Begin Menu mnuDynamic
|
||||
Caption = "&Dynamic Form..."
|
||||
End
|
||||
Begin Menu mnuTimer
|
||||
Caption = "&Timer..."
|
||||
End
|
||||
End
|
||||
|
||||
Begin Menu mnuHelp
|
||||
Caption = "&Help"
|
||||
Begin Menu mnuAbout
|
||||
Caption = "&About..."
|
||||
End
|
||||
End
|
||||
|
||||
Begin Frame fraButtons
|
||||
Caption = "Language Demonstrations"
|
||||
Layout = VBox
|
||||
Weight = 0
|
||||
|
||||
Begin HBox rowA
|
||||
Weight = 0
|
||||
Begin CommandButton btnTypes
|
||||
Caption = "&Types"
|
||||
Weight = 1
|
||||
End
|
||||
Begin CommandButton btnMath
|
||||
Caption = "&Math"
|
||||
Weight = 1
|
||||
End
|
||||
Begin CommandButton btnStrings
|
||||
Caption = "&Strings"
|
||||
Weight = 1
|
||||
End
|
||||
Begin CommandButton btnArrays
|
||||
Caption = "&Arrays"
|
||||
Weight = 1
|
||||
End
|
||||
Begin CommandButton btnData
|
||||
Caption = "&DATA/READ"
|
||||
Weight = 1
|
||||
End
|
||||
End
|
||||
|
||||
Begin HBox rowB
|
||||
Weight = 0
|
||||
Begin CommandButton btnFlow
|
||||
Caption = "Control &Flow"
|
||||
Weight = 1
|
||||
End
|
||||
Begin CommandButton btnUdt
|
||||
Caption = "&UDT"
|
||||
Weight = 1
|
||||
End
|
||||
Begin CommandButton btnOpt
|
||||
Caption = "&Optional"
|
||||
Weight = 1
|
||||
End
|
||||
Begin CommandButton btnError
|
||||
Caption = "&Errors"
|
||||
Weight = 1
|
||||
End
|
||||
Begin CommandButton btnFormat
|
||||
Caption = "F&ormat"
|
||||
Weight = 1
|
||||
End
|
||||
End
|
||||
|
||||
Begin HBox rowC
|
||||
Weight = 0
|
||||
Begin CommandButton btnFileIO
|
||||
Caption = "File &I/O"
|
||||
Weight = 1
|
||||
End
|
||||
Begin CommandButton btnSystem
|
||||
Caption = "S&ystem"
|
||||
Weight = 1
|
||||
End
|
||||
Begin CommandButton btnIni
|
||||
Caption = "I&NI"
|
||||
Weight = 1
|
||||
End
|
||||
Begin CommandButton btnDialogs
|
||||
Caption = "Di&alogs"
|
||||
Weight = 1
|
||||
End
|
||||
Begin CommandButton btnClear
|
||||
Caption = "Clea&r"
|
||||
Weight = 1
|
||||
End
|
||||
End
|
||||
End
|
||||
|
||||
Begin TextArea OutArea
|
||||
Weight = 1
|
||||
End
|
||||
|
||||
Begin Label LblStatus
|
||||
Caption = "Ready. Click any button to run a demo."
|
||||
Weight = 0
|
||||
End
|
||||
End
|
||||
|
||||
|
||||
OPTION EXPLICIT
|
||||
|
||||
TYPE PointT
|
||||
x AS INTEGER
|
||||
y AS INTEGER
|
||||
END TYPE
|
||||
|
||||
|
||||
' ============================================================
|
||||
' OutArea helpers
|
||||
' ============================================================
|
||||
|
||||
SUB Say(s AS STRING)
|
||||
OutArea.AppendText s + CHR$(10)
|
||||
END SUB
|
||||
|
||||
|
||||
SUB Header(title AS STRING)
|
||||
Say ""
|
||||
Say "--- " + title + " ---"
|
||||
END SUB
|
||||
|
||||
|
||||
Load BasicDemo
|
||||
BasicDemo.Show
|
||||
|
||||
OutArea.SetShowLineNumbers False
|
||||
OutArea.SetReadOnly True
|
||||
Say "Welcome to the DVX BASIC Feature Tour!"
|
||||
Say "Each button below runs a self-contained example."
|
||||
Say "Check the Demos menu for graphics, dynamic UI, and timer demos."
|
||||
Say ""
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Menu handlers
|
||||
' ============================================================
|
||||
|
||||
SUB mnuClear_Click
|
||||
OutArea.Text = ""
|
||||
LblStatus.Caption = "OutArea cleared."
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuSaveOut_Click
|
||||
DIM path AS STRING
|
||||
path = basFileSave("Save OutArea", "Text Files (*.txt)|All Files (*.*)")
|
||||
IF path = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
OPEN path FOR OUTPUT AS #1
|
||||
PRINT #1, OutArea.Text
|
||||
CLOSE #1
|
||||
LblStatus.Caption = "Saved: " + path
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuExit_Click
|
||||
Unload BasicDemo
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuRunAll_Click
|
||||
btnTypes_Click
|
||||
btnMath_Click
|
||||
btnStrings_Click
|
||||
btnArrays_Click
|
||||
btnData_Click
|
||||
btnFlow_Click
|
||||
btnUdt_Click
|
||||
btnOpt_Click
|
||||
btnError_Click
|
||||
btnFormat_Click
|
||||
btnFileIO_Click
|
||||
btnSystem_Click
|
||||
LblStatus.Caption = "All text demos complete."
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuAbout_Click
|
||||
DIM msg AS STRING
|
||||
msg = "DVX BASIC Feature Tour" + CHR$(10) + CHR$(10)
|
||||
msg = msg + "A visual catalog of DVX BASIC language"
|
||||
msg = msg + " and runtime features." + CHR$(10) + CHR$(10)
|
||||
msg = msg + "(c) 2026 DVX Project"
|
||||
MsgBox msg, vbOKOnly, "About"
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Types: INTEGER, LONG, SINGLE, DOUBLE, STRING, BOOLEAN
|
||||
' ============================================================
|
||||
|
||||
SUB btnTypes_Click
|
||||
Header "Types"
|
||||
|
||||
DIM i AS INTEGER
|
||||
DIM l AS LONG
|
||||
DIM s AS SINGLE
|
||||
DIM d AS DOUBLE
|
||||
DIM t AS STRING
|
||||
DIM b AS BOOLEAN
|
||||
|
||||
i = 32767
|
||||
l = 2147483647
|
||||
s = 3.14159
|
||||
d = 2.718281828459045
|
||||
t = "Hello, DVX!"
|
||||
b = True
|
||||
|
||||
Say "INTEGER (16-bit): " + STR$(i)
|
||||
Say "LONG (32-bit): " + STR$(l)
|
||||
Say "SINGLE (float): " + STR$(s)
|
||||
Say "DOUBLE (double): " + STR$(d)
|
||||
Say "STRING: " + CHR$(34) + t + CHR$(34)
|
||||
Say "BOOLEAN True: " + STR$(b)
|
||||
|
||||
' CONST with AS type annotation
|
||||
CONST PI AS DOUBLE = 3.1415926535
|
||||
Say "CONST PI = " + STR$(PI)
|
||||
|
||||
LblStatus.Caption = "Types demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Math: integer + float operators, built-in functions
|
||||
' ============================================================
|
||||
|
||||
SUB btnMath_Click
|
||||
Header "Math"
|
||||
|
||||
DIM a AS INTEGER
|
||||
DIM b AS INTEGER
|
||||
a = 17
|
||||
b = 5
|
||||
|
||||
Say "a = 17, b = 5"
|
||||
Say "a + b = " + STR$(a + b)
|
||||
Say "a - b = " + STR$(a - b)
|
||||
Say "a * b = " + STR$(a * b)
|
||||
Say "a \ b = " + STR$(a \ b) + " (integer divide)"
|
||||
Say "a MOD b = " + STR$(a MOD b)
|
||||
Say "a / b = " + STR$(a / b) + " (float divide)"
|
||||
|
||||
Say ""
|
||||
Say "SQR(144) = " + STR$(SQR(144))
|
||||
Say "ABS(-42) = " + STR$(ABS(-42))
|
||||
Say "INT(3.7) = " + STR$(INT(3.7))
|
||||
Say "FIX(-3.7) = " + STR$(FIX(-3.7))
|
||||
Say "SGN(-9) = " + STR$(SGN(-9))
|
||||
Say "SIN(0) = " + STR$(SIN(0))
|
||||
Say "COS(0) = " + STR$(COS(0))
|
||||
Say "2 ^ 10 = " + STR$(2 ^ 10)
|
||||
|
||||
RANDOMIZE TIMER
|
||||
DIM r AS INTEGER
|
||||
r = INT(RND * 100)
|
||||
Say "RND (0-99) = " + STR$(r)
|
||||
Say "TIMER = " + STR$(TIMER) + " (seconds since midnight)"
|
||||
|
||||
LblStatus.Caption = "Math demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Strings: concatenation, LEFT$/RIGHT$/MID$/LEN/INSTR/UCASE$/LCASE$
|
||||
' ============================================================
|
||||
|
||||
SUB btnStrings_Click
|
||||
Header "Strings"
|
||||
|
||||
DIM s AS STRING
|
||||
s = "The quick brown fox"
|
||||
|
||||
Say "Source: " + CHR$(34) + s + CHR$(34)
|
||||
Say "LEN = " + STR$(LEN(s))
|
||||
Say "LEFT$(s, 3) = " + LEFT$(s, 3)
|
||||
Say "RIGHT$(s, 3) = " + RIGHT$(s, 3)
|
||||
Say "MID$(s, 5, 5) = " + MID$(s, 5, 5)
|
||||
Say "UCASE$ = " + UCASE$(s)
|
||||
Say "LCASE$('HELLO') = " + LCASE$("HELLO")
|
||||
Say "INSTR(s, 'brown')= " + STR$(INSTR(s, "brown"))
|
||||
Say "TRIM$(' hi ') = " + CHR$(34) + TRIM$(" hi ") + CHR$(34)
|
||||
Say "STRING$(5, 42) = " + STRING$(5, 42)
|
||||
Say "CHR$(65) = " + CHR$(65)
|
||||
Say "ASC('A') = " + STR$(ASC("A"))
|
||||
Say "HEX$(255) = " + HEX$(255)
|
||||
Say "VAL('42.5xyz') = " + STR$(VAL("42.5xyz"))
|
||||
|
||||
LblStatus.Caption = "Strings demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Arrays: 1D, 2D, LBOUND/UBOUND, REDIM PRESERVE
|
||||
' ============================================================
|
||||
|
||||
SUB btnArrays_Click
|
||||
Header "Arrays"
|
||||
|
||||
' 1D array
|
||||
DIM squares(9) AS INTEGER
|
||||
DIM i AS INTEGER
|
||||
FOR i = 0 TO 9
|
||||
squares(i) = i * i
|
||||
NEXT i
|
||||
|
||||
DIM lineS AS STRING
|
||||
lineS = "squares(0..9) = "
|
||||
FOR i = 0 TO 9
|
||||
lineS = lineS + STR$(squares(i)) + " "
|
||||
NEXT i
|
||||
Say lineS
|
||||
|
||||
' Bounds: DIM a(lo TO hi)
|
||||
DIM prices(1 TO 3) AS SINGLE
|
||||
prices(1) = 9.99
|
||||
prices(2) = 14.99
|
||||
prices(3) = 29.99
|
||||
Say "LBOUND(prices) = " + STR$(LBOUND(prices)) + ", UBOUND = " + STR$(UBOUND(prices))
|
||||
Say "prices(2) = " + STR$(prices(2))
|
||||
|
||||
' 2D array
|
||||
DIM matrix(2, 2) AS INTEGER
|
||||
matrix(0, 0) = 1 : matrix(0, 1) = 2 : matrix(0, 2) = 3
|
||||
matrix(1, 0) = 4 : matrix(1, 1) = 5 : matrix(1, 2) = 6
|
||||
matrix(2, 0) = 7 : matrix(2, 1) = 8 : matrix(2, 2) = 9
|
||||
Say "3x3 matrix:"
|
||||
DIM r AS INTEGER
|
||||
DIM c AS INTEGER
|
||||
FOR r = 0 TO 2
|
||||
lineS = " "
|
||||
FOR c = 0 TO 2
|
||||
lineS = lineS + STR$(matrix(r, c))
|
||||
NEXT c
|
||||
Say lineS
|
||||
NEXT r
|
||||
|
||||
' REDIM PRESERVE
|
||||
DIM nums(2) AS INTEGER
|
||||
nums(0) = 10
|
||||
nums(1) = 20
|
||||
nums(2) = 30
|
||||
REDIM PRESERVE nums(4) AS INTEGER
|
||||
nums(3) = 40
|
||||
nums(4) = 50
|
||||
Say "After REDIM PRESERVE: " + STR$(nums(0)) + " " + STR$(nums(1)) + " " + STR$(nums(2)) + " " + STR$(nums(3)) + " " + STR$(nums(4))
|
||||
|
||||
LblStatus.Caption = "Arrays demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' DATA / READ / RESTORE
|
||||
' ============================================================
|
||||
|
||||
SUB btnData_Click
|
||||
Header "DATA / READ / RESTORE"
|
||||
|
||||
DATA "Red", 255, 0, 0
|
||||
DATA "Green", 0, 255, 0
|
||||
DATA "Blue", 0, 0, 255
|
||||
|
||||
DIM colorName AS STRING
|
||||
DIM r AS INTEGER
|
||||
DIM g AS INTEGER
|
||||
DIM b AS INTEGER
|
||||
DIM i AS INTEGER
|
||||
|
||||
FOR i = 1 TO 3
|
||||
READ colorName
|
||||
READ r
|
||||
READ g
|
||||
READ b
|
||||
Say colorName + ": (" + STR$(r) + "," + STR$(g) + "," + STR$(b) + ")"
|
||||
NEXT i
|
||||
|
||||
Say ""
|
||||
Say "RESTORE resets pointer. Reading first entry again:"
|
||||
RESTORE
|
||||
READ colorName
|
||||
Say " first = " + colorName
|
||||
|
||||
LblStatus.Caption = "DATA/READ demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Control flow: IF, SELECT CASE, FOR, DO WHILE, GOSUB
|
||||
' ============================================================
|
||||
|
||||
SUB btnFlow_Click
|
||||
Header "Control Flow"
|
||||
|
||||
' FOR with STEP
|
||||
Say "FOR i = 10 TO 0 STEP -2:"
|
||||
DIM i AS INTEGER
|
||||
DIM lineS AS STRING
|
||||
lineS = " "
|
||||
FOR i = 10 TO 0 STEP -2
|
||||
lineS = lineS + STR$(i)
|
||||
NEXT i
|
||||
Say lineS
|
||||
|
||||
' DO WHILE
|
||||
Say ""
|
||||
Say "DO WHILE n < 32 (doubling):"
|
||||
DIM n AS LONG
|
||||
n = 1
|
||||
lineS = " "
|
||||
DO WHILE n < 32
|
||||
lineS = lineS + STR$(n)
|
||||
n = n * 2
|
||||
LOOP
|
||||
Say lineS
|
||||
|
||||
' IF / ELSEIF / ELSE
|
||||
Say ""
|
||||
DIM score AS INTEGER
|
||||
score = 78
|
||||
IF score >= 90 THEN
|
||||
Say "score " + STR$(score) + " -> A"
|
||||
ELSEIF score >= 80 THEN
|
||||
Say "score " + STR$(score) + " -> B"
|
||||
ELSEIF score >= 70 THEN
|
||||
Say "score " + STR$(score) + " -> C"
|
||||
ELSE
|
||||
Say "score " + STR$(score) + " -> F"
|
||||
END IF
|
||||
|
||||
' SELECT CASE
|
||||
Say ""
|
||||
DIM day AS INTEGER
|
||||
day = 3
|
||||
SELECT CASE day
|
||||
CASE 1
|
||||
Say "day 1 = Monday"
|
||||
CASE 2, 3
|
||||
Say "day " + STR$(day) + " = midweek"
|
||||
CASE 4 TO 5
|
||||
Say "day " + STR$(day) + " = late week"
|
||||
CASE ELSE
|
||||
Say "day " + STR$(day) + " = weekend"
|
||||
END SELECT
|
||||
|
||||
' GOSUB / RETURN
|
||||
Say ""
|
||||
Say "GOSUB to a local label:"
|
||||
GOSUB labelHello
|
||||
Say "back from subroutine"
|
||||
|
||||
LblStatus.Caption = "Control flow demo complete."
|
||||
EXIT SUB
|
||||
|
||||
labelHello:
|
||||
Say " inside GOSUB"
|
||||
RETURN
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' User-Defined Type
|
||||
' ============================================================
|
||||
|
||||
SUB btnUdt_Click
|
||||
Header "User-Defined Type"
|
||||
|
||||
DIM p AS PointT
|
||||
p.x = 10
|
||||
p.y = 20
|
||||
Say "PointT p = (" + STR$(p.x) + "," + STR$(p.y) + ")"
|
||||
|
||||
' Array of UDT
|
||||
DIM corners(3) AS PointT
|
||||
corners(0).x = 0 : corners(0).y = 0
|
||||
corners(1).x = 10 : corners(1).y = 0
|
||||
corners(2).x = 10 : corners(2).y = 10
|
||||
corners(3).x = 0 : corners(3).y = 10
|
||||
Say "Rectangle corners:"
|
||||
DIM i AS INTEGER
|
||||
FOR i = 0 TO 3
|
||||
Say " (" + STR$(corners(i).x) + "," + STR$(corners(i).y) + ")"
|
||||
NEXT i
|
||||
|
||||
LblStatus.Caption = "UDT demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Optional parameters (DVX extension)
|
||||
' ============================================================
|
||||
|
||||
FUNCTION Greet(who AS STRING, OPTIONAL greeting AS STRING) AS STRING
|
||||
IF greeting = "" THEN
|
||||
greeting = "Hello"
|
||||
END IF
|
||||
Greet = greeting + ", " + who + "!"
|
||||
END FUNCTION
|
||||
|
||||
|
||||
SUB btnOpt_Click
|
||||
Header "Optional Parameters"
|
||||
|
||||
Say Greet("World")
|
||||
Say Greet("Scott", "Howdy")
|
||||
Say Greet("DVX", "Greetings from")
|
||||
|
||||
LblStatus.Caption = "Optional params demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' ON ERROR GOTO
|
||||
' ============================================================
|
||||
|
||||
SUB btnError_Click
|
||||
Header "ON ERROR GOTO"
|
||||
|
||||
ON ERROR GOTO handler
|
||||
|
||||
DIM a AS INTEGER
|
||||
DIM b AS INTEGER
|
||||
a = 10
|
||||
b = 0
|
||||
Say "Attempting 10/0 ..."
|
||||
Say " 10 / 0 = " + STR$(a / b)
|
||||
Say "(should not reach here)"
|
||||
EXIT SUB
|
||||
|
||||
handler:
|
||||
Say " caught! ERR = " + STR$(ERR)
|
||||
LblStatus.Caption = "Error handler ran successfully."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' PRINT USING / FORMAT$
|
||||
' ============================================================
|
||||
|
||||
SUB btnFormat_Click
|
||||
Header "Formatting"
|
||||
|
||||
Say "FORMAT$(1234.5, '#,##0.00') = " + FORMAT$(1234.5, "#,##0.00")
|
||||
Say "FORMAT$(0.075, 'percent') = " + FORMAT$(0.075, "percent")
|
||||
Say "FORMAT$(-42, '+#0') = " + FORMAT$(-42, "+#0")
|
||||
Say "FORMAT$(3.14159, '0.00') = " + FORMAT$(3.14159, "0.00")
|
||||
|
||||
LblStatus.Caption = "Format demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' File I/O
|
||||
' ============================================================
|
||||
|
||||
SUB btnFileIO_Click
|
||||
Header "File I/O"
|
||||
|
||||
DIM path AS STRING
|
||||
path = App.Data + "/demo.txt"
|
||||
Say "Writing: " + path
|
||||
|
||||
OPEN path FOR OUTPUT AS #1
|
||||
PRINT #1, "Line one"
|
||||
PRINT #1, "Line two"
|
||||
PRINT #1, "The answer is "; 42
|
||||
CLOSE #1
|
||||
|
||||
Say "LOF = " + STR$(FILELEN(path))
|
||||
|
||||
Say "Reading back:"
|
||||
OPEN path FOR INPUT AS #1
|
||||
DIM ln AS STRING
|
||||
DO WHILE NOT EOF(1)
|
||||
LINE INPUT #1, ln
|
||||
Say " " + ln
|
||||
LOOP
|
||||
CLOSE #1
|
||||
|
||||
KILL path
|
||||
Say "Deleted."
|
||||
|
||||
LblStatus.Caption = "File I/O demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' System: App object, environment, current directory
|
||||
' ============================================================
|
||||
|
||||
SUB btnSystem_Click
|
||||
Header "System / App"
|
||||
|
||||
Say "App.Path = " + App.Path
|
||||
Say "App.Config = " + App.Config
|
||||
Say "App.Data = " + App.Data
|
||||
Say "CurDir = " + CurDir()
|
||||
Say "Date = " + Date$
|
||||
Say "Time = " + Time$
|
||||
Say "PATH env = " + LEFT$(Environ$("PATH"), 40) + "..."
|
||||
|
||||
LblStatus.Caption = "System demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' INI read/write
|
||||
' ============================================================
|
||||
|
||||
SUB btnIni_Click
|
||||
Header "INI Read/Write"
|
||||
|
||||
DIM path AS STRING
|
||||
path = App.Data + "/demo.ini"
|
||||
Say "Writing: " + path
|
||||
|
||||
IniWrite path, "General", "UserName", "Scott"
|
||||
IniWrite path, "General", "Version", "1.00"
|
||||
IniWrite path, "Options", "AutoSave", "True"
|
||||
|
||||
Say "Reading back:"
|
||||
Say " UserName = " + IniRead$(path, "General", "UserName", "(missing)")
|
||||
Say " Version = " + IniRead$(path, "General", "Version", "(missing)")
|
||||
Say " AutoSave = " + IniRead$(path, "Options", "AutoSave", "(missing)")
|
||||
Say " Missing = " + IniRead$(path, "General", "NotThere", "(default)")
|
||||
|
||||
KILL path
|
||||
|
||||
LblStatus.Caption = "INI demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Dialog demos (spawns the real dialogs)
|
||||
' ============================================================
|
||||
|
||||
SUB btnDialogs_Click
|
||||
Header "Dialogs"
|
||||
|
||||
DIM response AS INTEGER
|
||||
response = MsgBox("MessageBox demo." + CHR$(10) + "Are you enjoying the demo?", vbYesNo + vbQuestion, "Feedback")
|
||||
IF response = vbYes THEN
|
||||
Say "MsgBox: user said yes"
|
||||
ELSE
|
||||
Say "MsgBox: user said no"
|
||||
END IF
|
||||
|
||||
DIM text AS STRING
|
||||
text = basInputBox2("Input", "What is your name?", "Anonymous")
|
||||
Say "InputBox returned: " + text
|
||||
|
||||
DIM choice AS INTEGER
|
||||
choice = basChoiceDialog("Favorite", "Pick a color:", "Red|Green|Blue|Yellow", 1)
|
||||
IF choice >= 0 THEN
|
||||
Say "Choice index: " + STR$(choice)
|
||||
ELSE
|
||||
Say "Choice cancelled"
|
||||
END IF
|
||||
|
||||
DIM n AS INTEGER
|
||||
n = basIntInput("Number", "Pick a number (1-100):", 42, 1, 100)
|
||||
Say "IntInput: " + STR$(n)
|
||||
|
||||
LblStatus.Caption = "Dialog demo complete."
|
||||
END SUB
|
||||
|
||||
|
||||
SUB btnClear_Click
|
||||
mnuClear_Click
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Graphics demo (opens a second form with Canvas)
|
||||
' ============================================================
|
||||
|
||||
DIM gfxWin AS LONG
|
||||
gfxWin = 0
|
||||
|
||||
|
||||
SUB mnuGraphics_Click
|
||||
IF gfxWin <> 0 THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM frm AS LONG
|
||||
SET frm = CreateForm("GraphicsForm", 360, 320)
|
||||
GraphicsForm.Caption = "Graphics Demo"
|
||||
gfxWin = frm
|
||||
|
||||
DIM cv AS LONG
|
||||
SET cv = CreateControl(frm, "Canvas", "GfxCanvas")
|
||||
GfxCanvas.Width = 340
|
||||
GfxCanvas.Height = 260
|
||||
GfxCanvas.Weight = 1
|
||||
|
||||
DIM btnRow AS LONG
|
||||
SET btnRow = CreateControl(frm, "HBox", "GfxRow")
|
||||
|
||||
DIM bDraw AS LONG
|
||||
SET bDraw = CreateControl(frm, "CommandButton", "GfxDraw", btnRow)
|
||||
GfxDraw.Caption = "Draw"
|
||||
SetEvent bDraw, "Click", "GfxDrawAll"
|
||||
|
||||
DIM bClear AS LONG
|
||||
SET bClear = CreateControl(frm, "CommandButton", "GfxClear", btnRow)
|
||||
GfxClear.Caption = "Clear"
|
||||
SetEvent bClear, "Click", "GfxClearCanvas"
|
||||
|
||||
frm.Show
|
||||
|
||||
GfxDrawAll
|
||||
END SUB
|
||||
|
||||
|
||||
SUB GfxDrawAll
|
||||
DIM w AS LONG
|
||||
DIM h AS LONG
|
||||
w = 340
|
||||
h = 260
|
||||
|
||||
' Gradient background bars
|
||||
DIM y AS INTEGER
|
||||
DIM shade AS LONG
|
||||
FOR y = 0 TO h - 1 STEP 4
|
||||
shade = (y * 255) \ h
|
||||
GfxCanvas.FillRect 0, y, w, 4, RGB(shade, shade \ 2, 128)
|
||||
NEXT y
|
||||
|
||||
' Star field
|
||||
RANDOMIZE TIMER
|
||||
DIM s AS INTEGER
|
||||
DIM sx AS INTEGER
|
||||
DIM sy AS INTEGER
|
||||
FOR s = 1 TO 40
|
||||
sx = INT(RND * w)
|
||||
sy = INT(RND * h)
|
||||
GfxCanvas.SetPixel sx, sy, RGB(255, 255, 255)
|
||||
NEXT s
|
||||
|
||||
' Rectangle + outline
|
||||
GfxCanvas.FillRect 20, 20, 80, 50, RGB(240, 240, 0)
|
||||
GfxCanvas.DrawRect 20, 20, 80, 50, RGB(0, 0, 0)
|
||||
|
||||
' Circle approximated by line segments
|
||||
DIM cx AS INTEGER
|
||||
DIM cy AS INTEGER
|
||||
DIM r AS INTEGER
|
||||
cx = 250
|
||||
cy = 80
|
||||
r = 40
|
||||
DIM a AS DOUBLE
|
||||
DIM px AS INTEGER
|
||||
DIM py AS INTEGER
|
||||
DIM qx AS INTEGER
|
||||
DIM qy AS INTEGER
|
||||
px = cx + r
|
||||
py = cy
|
||||
FOR a = 0 TO 6.3 STEP 0.2
|
||||
qx = cx + INT(r * COS(a))
|
||||
qy = cy + INT(r * SIN(a))
|
||||
GfxCanvas.DrawLine px, py, qx, qy, RGB(255, 128, 0)
|
||||
px = qx
|
||||
py = qy
|
||||
NEXT a
|
||||
|
||||
' Text
|
||||
GfxCanvas.DrawText 60, 200, "Canvas + math + colors", RGB(255, 255, 255)
|
||||
GfxCanvas.DrawText 60, 220, "DVX BASIC graphics", RGB(255, 255, 0)
|
||||
|
||||
GfxCanvas.Refresh
|
||||
END SUB
|
||||
|
||||
|
||||
SUB GfxClearCanvas
|
||||
GfxCanvas.Clear RGB(0, 0, 0)
|
||||
GfxCanvas.Refresh
|
||||
END SUB
|
||||
|
||||
|
||||
SUB GraphicsForm_Unload
|
||||
gfxWin = 0
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Dynamic form demo
|
||||
' ============================================================
|
||||
|
||||
DIM dynForm AS LONG
|
||||
dynForm = 0
|
||||
|
||||
|
||||
SUB mnuDynamic_Click
|
||||
IF dynForm <> 0 THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM frm AS LONG
|
||||
SET frm = CreateForm("DynForm", 320, 200)
|
||||
DynForm.Caption = "Dynamic Form (built in code)"
|
||||
dynForm = frm
|
||||
|
||||
DIM lbl AS LONG
|
||||
SET lbl = CreateControl(frm, "Label", "DynLabel")
|
||||
DynLabel.Caption = "This form was created 100% in code."
|
||||
|
||||
DIM lbl2 AS LONG
|
||||
SET lbl2 = CreateControl(frm, "Label", "CountLabel")
|
||||
CountLabel.Caption = "Counter: 0"
|
||||
|
||||
DIM btns AS LONG
|
||||
SET btns = CreateControl(frm, "HBox", "DynBtns")
|
||||
|
||||
DIM bInc AS LONG
|
||||
SET bInc = CreateControl(frm, "CommandButton", "BInc", btns)
|
||||
BInc.Caption = "Count Up"
|
||||
SetEvent bInc, "Click", "DynInc"
|
||||
|
||||
DIM bBye AS LONG
|
||||
SET bBye = CreateControl(frm, "CommandButton", "BBye", btns)
|
||||
BBye.Caption = "Close"
|
||||
SetEvent bBye, "Click", "DynBye"
|
||||
|
||||
frm.Show
|
||||
END SUB
|
||||
|
||||
|
||||
DIM dynCount AS INTEGER
|
||||
dynCount = 0
|
||||
|
||||
|
||||
SUB DynInc
|
||||
dynCount = dynCount + 1
|
||||
CountLabel.Caption = "Counter: " + STR$(dynCount)
|
||||
END SUB
|
||||
|
||||
|
||||
SUB DynBye
|
||||
Unload DynForm
|
||||
END SUB
|
||||
|
||||
|
||||
SUB DynForm_Unload
|
||||
dynForm = 0
|
||||
dynCount = 0
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Timer demo
|
||||
' ============================================================
|
||||
|
||||
DIM timerWin AS LONG
|
||||
timerWin = 0
|
||||
|
||||
|
||||
SUB mnuTimer_Click
|
||||
IF timerWin <> 0 THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM frm AS LONG
|
||||
SET frm = CreateForm("TimerForm", 260, 140)
|
||||
TimerForm.Caption = "Timer Demo"
|
||||
timerWin = frm
|
||||
|
||||
DIM lbl AS LONG
|
||||
SET lbl = CreateControl(frm, "Label", "TickLabel")
|
||||
TickLabel.Caption = "Ticks: 0"
|
||||
|
||||
DIM t AS LONG
|
||||
SET t = CreateControl(frm, "Timer", "Ticker")
|
||||
Ticker.Interval = 500
|
||||
SetEvent t, "Timer", "TickHandler"
|
||||
|
||||
frm.Show
|
||||
END SUB
|
||||
|
||||
|
||||
DIM tickCount AS LONG
|
||||
tickCount = 0
|
||||
|
||||
|
||||
SUB TickHandler
|
||||
tickCount = tickCount + 1
|
||||
TickLabel.Caption = "Ticks: " + STR$(tickCount) + " Time: " + TIME$
|
||||
END SUB
|
||||
|
||||
|
||||
SUB TimerForm_Unload
|
||||
timerWin = 0
|
||||
tickCount = 0
|
||||
END SUB
|
||||
|
|
@ -62,14 +62,10 @@ CommDlgDemo.Show
|
|||
|
||||
PRINT "Common Dialog Demo started."
|
||||
|
||||
DO
|
||||
DoEvents
|
||||
LOOP
|
||||
|
||||
|
||||
SUB BtnFileOpen_Click
|
||||
DIM path AS STRING
|
||||
path = basFileOpen("Open a File", "*.bas")
|
||||
path = basFileOpen("Open a File", "BASIC Files (*.bas;*.frm)|All Files (*.*)")
|
||||
IF path <> "" THEN
|
||||
LblResult.Caption = "Opened: " + path
|
||||
PRINT "File Open: " + path
|
||||
|
|
@ -82,7 +78,7 @@ END SUB
|
|||
|
||||
SUB BtnFileSave_Click
|
||||
DIM path AS STRING
|
||||
path = basFileSave("Save a File", "*.txt")
|
||||
path = basFileSave("Save a File", "Text Files (*.txt)|All Files (*.*)")
|
||||
IF path <> "" THEN
|
||||
LblResult.Caption = "Save to: " + path
|
||||
PRINT "File Save: " + path
|
||||
|
|
|
|||
|
|
@ -47,10 +47,6 @@ frm.Show
|
|||
|
||||
PRINT "Dynamic form created. Try the buttons!"
|
||||
|
||||
DO
|
||||
DoEvents
|
||||
LOOP
|
||||
|
||||
|
||||
SUB OnHelloClick
|
||||
DIM name AS STRING
|
||||
|
|
@ -64,7 +60,7 @@ END SUB
|
|||
|
||||
SUB OnFileClick
|
||||
DIM path AS STRING
|
||||
path = basFileOpen("Open a File", "*.*")
|
||||
path = basFileOpen("Open a File", "All Files (*.*)")
|
||||
IF path <> "" THEN
|
||||
StatusLabel.Caption = "Selected: " + path
|
||||
PRINT "File: " + path
|
||||
|
|
|
|||
|
|
@ -1,205 +0,0 @@
|
|||
VERSION DVX 1.00
|
||||
' helpedit.frm -- DVX Help Editor
|
||||
'
|
||||
' A .dhs help source editor with syntax highlighting and
|
||||
' live preview via the DVX Help Viewer.
|
||||
'
|
||||
' Add commdlg.bas and help.bas to your project, then click Run.
|
||||
|
||||
Begin Form HelpEdit
|
||||
Caption = "DVX Help Editor"
|
||||
Layout = VBox
|
||||
AutoSize = False
|
||||
Resizable = True
|
||||
Centered = True
|
||||
Width = 640
|
||||
Height = 440
|
||||
Begin HBox ToolBar
|
||||
Weight = 0
|
||||
Begin CommandButton BtnNew
|
||||
Caption = "New"
|
||||
MinWidth = 60
|
||||
End
|
||||
Begin CommandButton BtnOpen
|
||||
Caption = "Open"
|
||||
MinWidth = 60
|
||||
End
|
||||
Begin CommandButton BtnSave
|
||||
Caption = "Save"
|
||||
MinWidth = 60
|
||||
End
|
||||
Begin Spacer Spc1
|
||||
MinWidth = 16
|
||||
End
|
||||
Begin CommandButton BtnCompile
|
||||
Caption = "Compile"
|
||||
MinWidth = 80
|
||||
End
|
||||
Begin CommandButton BtnPreview
|
||||
Caption = "Preview"
|
||||
MinWidth = 80
|
||||
End
|
||||
Begin Spacer Spc2
|
||||
Weight = 1
|
||||
End
|
||||
Begin Label LblStatus
|
||||
Caption = "Ready."
|
||||
MinWidth = 150
|
||||
End
|
||||
End
|
||||
Begin TextArea Editor
|
||||
Weight = 1
|
||||
End
|
||||
End
|
||||
|
||||
DIM currentFile AS STRING
|
||||
DIM hlpFile AS STRING
|
||||
currentFile = ""
|
||||
hlpFile = ""
|
||||
|
||||
Load HelpEdit
|
||||
HelpEdit.Show
|
||||
|
||||
' Configure the editor
|
||||
Editor.SetSyntaxMode "dhs"
|
||||
Editor.SetShowLineNumbers True
|
||||
Editor.SetAutoIndent True
|
||||
Editor.SetCaptureTabs True
|
||||
Editor.SetTabWidth 2
|
||||
|
||||
' Start with a template
|
||||
Editor.Text = ".topic intro" + CHR$(10) + ".title My Help File" + CHR$(10) + ".toc 0 My Help File" + CHR$(10) + ".default" + CHR$(10) + CHR$(10) + ".h1 Welcome" + CHR$(10) + CHR$(10) + "This is your help file. Edit the .dhs source here," + CHR$(10) + "then click Compile and Preview to see the result." + CHR$(10)
|
||||
|
||||
LblStatus.Caption = "New file."
|
||||
|
||||
DO
|
||||
DoEvents
|
||||
LOOP
|
||||
|
||||
|
||||
SUB BtnNew_Click
|
||||
currentFile = ""
|
||||
hlpFile = ""
|
||||
Editor.Text = ".topic intro" + CHR$(10) + ".title My Help File" + CHR$(10) + ".toc 0 My Help File" + CHR$(10) + ".default" + CHR$(10) + CHR$(10) + ".h1 Welcome" + CHR$(10) + CHR$(10)
|
||||
LblStatus.Caption = "New file."
|
||||
HelpEdit.Caption = "DVX Help Editor"
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnOpen_Click
|
||||
DIM path AS STRING
|
||||
path = basFileOpen("Open Help Source", "*.dhs")
|
||||
IF path = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
' Read the entire file
|
||||
DIM text AS STRING
|
||||
DIM line AS STRING
|
||||
text = ""
|
||||
|
||||
OPEN path FOR INPUT AS #1
|
||||
DO WHILE NOT EOF(1)
|
||||
LINE INPUT #1, line
|
||||
IF text <> "" THEN
|
||||
text = text + CHR$(10)
|
||||
END IF
|
||||
text = text + line
|
||||
LOOP
|
||||
CLOSE #1
|
||||
|
||||
Editor.Text = text
|
||||
currentFile = path
|
||||
hlpFile = ""
|
||||
LblStatus.Caption = path
|
||||
HelpEdit.Caption = "DVX Help Editor - " + path
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnSave_Click
|
||||
DIM path AS STRING
|
||||
|
||||
IF currentFile <> "" THEN
|
||||
path = currentFile
|
||||
ELSE
|
||||
path = basFileSave("Save Help Source", "*.dhs")
|
||||
IF path = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
END IF
|
||||
|
||||
' Write the editor contents to file
|
||||
OPEN path FOR OUTPUT AS #1
|
||||
PRINT #1, Editor.Text
|
||||
CLOSE #1
|
||||
|
||||
currentFile = path
|
||||
LblStatus.Caption = "Saved: " + path
|
||||
HelpEdit.Caption = "DVX Help Editor - " + path
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnCompile_Click
|
||||
' Save first if we have a file
|
||||
IF currentFile = "" THEN
|
||||
DIM path AS STRING
|
||||
path = basFileSave("Save Help Source", "*.dhs")
|
||||
IF path = "" THEN
|
||||
LblStatus.Caption = "Save cancelled."
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
OPEN path FOR OUTPUT AS #1
|
||||
PRINT #1, Editor.Text
|
||||
CLOSE #1
|
||||
currentFile = path
|
||||
ELSE
|
||||
OPEN currentFile FOR OUTPUT AS #1
|
||||
PRINT #1, Editor.Text
|
||||
CLOSE #1
|
||||
END IF
|
||||
|
||||
' Derive .hlp filename from .dhs filename
|
||||
DIM baseName AS STRING
|
||||
baseName = currentFile
|
||||
|
||||
' Strip .dhs extension if present
|
||||
IF LEN(baseName) > 4 THEN
|
||||
IF UCASE$(RIGHT$(baseName, 4)) = ".DHS" THEN
|
||||
baseName = LEFT$(baseName, LEN(baseName) - 4)
|
||||
END IF
|
||||
END IF
|
||||
|
||||
hlpFile = baseName + ".hlp"
|
||||
|
||||
LblStatus.Caption = "Compiling..."
|
||||
DoEvents
|
||||
|
||||
IF HelpCompile(currentFile, hlpFile) THEN
|
||||
LblStatus.Caption = "Compiled: " + hlpFile
|
||||
ELSE
|
||||
LblStatus.Caption = "Compile failed!"
|
||||
hlpFile = ""
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnPreview_Click
|
||||
IF hlpFile = "" THEN
|
||||
' Try compiling first
|
||||
BtnCompile_Click
|
||||
END IF
|
||||
|
||||
IF hlpFile = "" THEN
|
||||
LblStatus.Caption = "Nothing to preview. Compile first."
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
LblStatus.Caption = "Opening viewer..."
|
||||
HelpView hlpFile
|
||||
END SUB
|
||||
|
||||
|
||||
SUB HelpEdit_QueryUnload(Cancel AS INTEGER)
|
||||
' Could prompt to save here
|
||||
END SUB
|
||||
BIN
sdk/samples/basic/helpedit/ICON32.BMP
(Stored with Git LFS)
Normal file
BIN
sdk/samples/basic/helpedit/ICON32.BMP
(Stored with Git LFS)
Normal file
Binary file not shown.
17
sdk/samples/basic/helpedit/helpedit.dbp
Normal file
17
sdk/samples/basic/helpedit/helpedit.dbp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[Project]
|
||||
Name = Help Editor
|
||||
Author = DVX Project
|
||||
Description = DVX Help source editor with syntax highlighting and preview
|
||||
Icon = ICON32.BMP
|
||||
|
||||
[Modules]
|
||||
File0 = ../../../include/basic/commdlg.bas
|
||||
File1 = ../../../include/basic/help.bas
|
||||
Count = 2
|
||||
|
||||
[Forms]
|
||||
File0 = helpedit.frm
|
||||
Count = 1
|
||||
|
||||
[Settings]
|
||||
StartupForm = HelpEdit
|
||||
310
sdk/samples/basic/helpedit/helpedit.frm
Normal file
310
sdk/samples/basic/helpedit/helpedit.frm
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
VERSION DVX 1.00
|
||||
' helpedit.frm -- DVX Help Editor
|
||||
'
|
||||
' A .dhs help source editor with syntax highlighting and
|
||||
' live preview via the DVX Help Viewer.
|
||||
'
|
||||
' Add commdlg.bas and help.bas to your project, then click Run.
|
||||
|
||||
Begin Form HelpEdit
|
||||
Caption = "DVX Help Editor"
|
||||
Layout = VBox
|
||||
AutoSize = False
|
||||
Resizable = True
|
||||
Centered = True
|
||||
Width = 640
|
||||
Height = 440
|
||||
Begin Menu mnuFile
|
||||
Caption = "&File"
|
||||
Begin Menu mnuNew
|
||||
Caption = "&New"
|
||||
End
|
||||
Begin Menu mnuOpen
|
||||
Caption = "&Open..."
|
||||
End
|
||||
Begin Menu mnuSep1
|
||||
Caption = "-"
|
||||
End
|
||||
Begin Menu mnuSave
|
||||
Caption = "&Save"
|
||||
End
|
||||
Begin Menu mnuSaveAs
|
||||
Caption = "Save &As..."
|
||||
End
|
||||
Begin Menu mnuSep2
|
||||
Caption = "-"
|
||||
End
|
||||
Begin Menu mnuExit
|
||||
Caption = "E&xit"
|
||||
End
|
||||
End
|
||||
Begin Menu mnuBuild
|
||||
Caption = "&Build"
|
||||
Begin Menu mnuCompile
|
||||
Caption = "&Compile"
|
||||
End
|
||||
Begin Menu mnuPreview
|
||||
Caption = "&Preview"
|
||||
End
|
||||
End
|
||||
Begin Menu mnuHelp
|
||||
Caption = "&Help"
|
||||
Begin Menu mnuHelpTopic
|
||||
Caption = "Help &Topics"
|
||||
End
|
||||
End
|
||||
Begin TextArea Editor
|
||||
Weight = 1
|
||||
End
|
||||
Begin Label LblStatus
|
||||
Weight = 0
|
||||
End
|
||||
End
|
||||
|
||||
DIM currentFile AS STRING
|
||||
DIM hlpFile AS STRING
|
||||
DIM savedHash AS LONG
|
||||
DIM dirty AS INTEGER
|
||||
currentFile = ""
|
||||
hlpFile = ""
|
||||
savedHash = 0
|
||||
dirty = 0
|
||||
|
||||
Load HelpEdit
|
||||
HelpEdit.Show
|
||||
|
||||
' Configure the editor
|
||||
Editor.SetSyntaxMode "dhs"
|
||||
Editor.SetShowLineNumbers True
|
||||
Editor.SetAutoIndent True
|
||||
Editor.SetCaptureTabs True
|
||||
Editor.SetTabWidth 2
|
||||
|
||||
' Start with a template
|
||||
Editor.Text = ".topic intro" + CHR$(10) + ".title My Help File" + CHR$(10) + ".toc 0 My Help File" + CHR$(10) + ".default" + CHR$(10) + CHR$(10) + ".h1 Welcome" + CHR$(10) + CHR$(10) + "This is your help file. Edit the .dhs source here," + CHR$(10) + "then click Compile and Preview to see the result." + CHR$(10)
|
||||
|
||||
MarkClean
|
||||
LblStatus.Caption = "Ready."
|
||||
|
||||
|
||||
FUNCTION TextHash() AS LONG
|
||||
DIM h AS LONG
|
||||
DIM t AS STRING
|
||||
t = Editor.Text
|
||||
h = 0
|
||||
DIM i AS INTEGER
|
||||
FOR i = 1 TO LEN(t)
|
||||
h = h * 31 + ASC(MID$(t, i, 1))
|
||||
NEXT i
|
||||
TextHash = h
|
||||
END FUNCTION
|
||||
|
||||
|
||||
FUNCTION IsDirty() AS INTEGER
|
||||
IsDirty = (TextHash() <> savedHash)
|
||||
END FUNCTION
|
||||
|
||||
|
||||
FUNCTION AskSave() AS INTEGER
|
||||
IF NOT IsDirty() THEN
|
||||
AskSave = 0
|
||||
EXIT FUNCTION
|
||||
END IF
|
||||
DIM result AS INTEGER
|
||||
result = basPromptSave("Help Editor")
|
||||
IF result = DVX_SAVE_YES THEN
|
||||
FileSave
|
||||
AskSave = 0
|
||||
ELSEIF result = DVX_SAVE_NO THEN
|
||||
AskSave = 0
|
||||
ELSE
|
||||
AskSave = 1
|
||||
END IF
|
||||
END FUNCTION
|
||||
|
||||
|
||||
SUB UpdateTitle
|
||||
DIM title AS STRING
|
||||
IF currentFile <> "" THEN
|
||||
title = currentFile + " - DVX Help Editor"
|
||||
ELSE
|
||||
title = "DVX Help Editor"
|
||||
END IF
|
||||
IF dirty THEN
|
||||
title = "* " + title
|
||||
END IF
|
||||
HelpEdit.Caption = title
|
||||
END SUB
|
||||
|
||||
|
||||
SUB MarkClean
|
||||
dirty = 0
|
||||
savedHash = TextHash()
|
||||
UpdateTitle
|
||||
END SUB
|
||||
|
||||
|
||||
SUB Editor_Change
|
||||
IF NOT dirty THEN
|
||||
dirty = -1
|
||||
UpdateTitle
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuNew_Click
|
||||
IF AskSave() THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
currentFile = ""
|
||||
hlpFile = ""
|
||||
Editor.Text = ".topic intro" + CHR$(10) + ".title My Help File" + CHR$(10) + ".toc 0 My Help File" + CHR$(10) + ".default" + CHR$(10) + CHR$(10) + ".h1 Welcome" + CHR$(10) + CHR$(10)
|
||||
MarkClean
|
||||
LblStatus.Caption = "New file."
|
||||
UpdateTitle
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuOpen_Click
|
||||
IF AskSave() THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM path AS STRING
|
||||
path = basFileOpen("Open Help Source", "Help Source (*.dhs)|All Files (*.*)")
|
||||
IF path = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM text AS STRING
|
||||
DIM ln AS STRING
|
||||
text = ""
|
||||
|
||||
OPEN path FOR INPUT AS #1
|
||||
DO WHILE NOT EOF(1)
|
||||
LINE INPUT #1, ln
|
||||
IF text <> "" THEN
|
||||
text = text + CHR$(10)
|
||||
END IF
|
||||
text = text + ln
|
||||
LOOP
|
||||
CLOSE #1
|
||||
|
||||
Editor.Text = text
|
||||
currentFile = path
|
||||
hlpFile = ""
|
||||
MarkClean
|
||||
LblStatus.Caption = path
|
||||
UpdateTitle
|
||||
END SUB
|
||||
|
||||
|
||||
SUB FileSave
|
||||
IF currentFile = "" THEN
|
||||
FileSaveAs
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
OPEN currentFile FOR OUTPUT AS #1
|
||||
PRINT #1, Editor.Text
|
||||
CLOSE #1
|
||||
|
||||
MarkClean
|
||||
LblStatus.Caption = "Saved: " + currentFile
|
||||
UpdateTitle
|
||||
END SUB
|
||||
|
||||
|
||||
SUB FileSaveAs
|
||||
DIM path AS STRING
|
||||
path = basFileSave("Save Help Source", "Help Source (*.dhs)|All Files (*.*)")
|
||||
IF path = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
currentFile = path
|
||||
FileSave
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuSave_Click
|
||||
FileSave
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuSaveAs_Click
|
||||
FileSaveAs
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuExit_Click
|
||||
Unload HelpEdit
|
||||
END SUB
|
||||
|
||||
|
||||
SUB DoCompile
|
||||
' Save first
|
||||
IF currentFile = "" THEN
|
||||
FileSaveAs
|
||||
IF currentFile = "" THEN
|
||||
LblStatus.Caption = "Save cancelled."
|
||||
EXIT SUB
|
||||
END IF
|
||||
ELSE
|
||||
OPEN currentFile FOR OUTPUT AS #1
|
||||
PRINT #1, Editor.Text
|
||||
CLOSE #1
|
||||
MarkClean
|
||||
END IF
|
||||
|
||||
' Derive .hlp filename from .dhs filename
|
||||
DIM baseName AS STRING
|
||||
baseName = currentFile
|
||||
|
||||
IF LEN(baseName) > 4 THEN
|
||||
IF UCASE$(RIGHT$(baseName, 4)) = ".DHS" THEN
|
||||
baseName = LEFT$(baseName, LEN(baseName) - 4)
|
||||
END IF
|
||||
END IF
|
||||
|
||||
hlpFile = baseName + ".hlp"
|
||||
|
||||
LblStatus.Caption = "Compiling..."
|
||||
DoEvents
|
||||
|
||||
IF HelpCompile(currentFile, hlpFile) THEN
|
||||
LblStatus.Caption = "Compiled: " + hlpFile
|
||||
ELSE
|
||||
LblStatus.Caption = "Compile failed!"
|
||||
hlpFile = ""
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuCompile_Click
|
||||
DoCompile
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuPreview_Click
|
||||
IF hlpFile = "" THEN
|
||||
DoCompile
|
||||
END IF
|
||||
|
||||
IF hlpFile = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
LblStatus.Caption = "Opening viewer..."
|
||||
HelpView hlpFile
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuHelpTopic_Click
|
||||
HelpView "dvxhelp.hlp"
|
||||
END SUB
|
||||
|
||||
|
||||
SUB HelpEdit_QueryUnload(Cancel AS INTEGER)
|
||||
Cancel = AskSave()
|
||||
END SUB
|
||||
BIN
sdk/samples/basic/iconed/ICON32.BMP
(Stored with Git LFS)
BIN
sdk/samples/basic/iconed/ICON32.BMP
(Stored with Git LFS)
Binary file not shown.
|
|
@ -5,9 +5,11 @@ Company = Kangaroo Punch Studios
|
|||
Version = 1.00
|
||||
Copyright = Copyright 2026 Scott Duensing
|
||||
Description = Icon editor for DVX.
|
||||
Icon = ICON32.BMP
|
||||
|
||||
[Modules]
|
||||
Count = 0
|
||||
File0 = ../../../include/basic/commdlg.bas
|
||||
Count = 1
|
||||
|
||||
[Forms]
|
||||
File0 = iconed.frm
|
||||
|
|
|
|||
|
|
@ -459,6 +459,23 @@ SUB cvEditor_MouseUp(button AS INTEGER, x AS INTEGER, y AS INTEGER)
|
|||
drawing = False
|
||||
END SUB
|
||||
|
||||
SUB IconEd_QueryUnload(Cancel AS INTEGER)
|
||||
IF NOT dirty THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
DIM result AS INTEGER
|
||||
result = basPromptSave("Icon Editor")
|
||||
IF result = DVX_SAVE_YES THEN
|
||||
mnuSave_Click
|
||||
IF dirty THEN
|
||||
Cancel = 1
|
||||
END IF
|
||||
ELSEIF result = DVX_SAVE_CANCEL THEN
|
||||
Cancel = 1
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB cvPalette_MouseDown(button AS INTEGER, x AS INTEGER, y AS INTEGER)
|
||||
DIM col AS INTEGER
|
||||
DIM row AS INTEGER
|
||||
|
|
|
|||
BIN
sdk/samples/basic/imgview/ICON32.BMP
(Stored with Git LFS)
Normal file
BIN
sdk/samples/basic/imgview/ICON32.BMP
(Stored with Git LFS)
Normal file
Binary file not shown.
16
sdk/samples/basic/imgview/imgview.dbp
Normal file
16
sdk/samples/basic/imgview/imgview.dbp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[Project]
|
||||
Name = Image Viewer
|
||||
Author = DVX Project
|
||||
Description = BMP, PNG, JPEG, and GIF viewer
|
||||
Icon = ICON32.BMP
|
||||
|
||||
[Modules]
|
||||
File0 = ../../../include/basic/commdlg.bas
|
||||
Count = 1
|
||||
|
||||
[Forms]
|
||||
File0 = imgview.frm
|
||||
Count = 1
|
||||
|
||||
[Settings]
|
||||
StartupForm = ImgViewForm
|
||||
49
sdk/samples/basic/imgview/imgview.frm
Normal file
49
sdk/samples/basic/imgview/imgview.frm
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
VERSION DVX 1.00
|
||||
Begin Form ImgViewForm
|
||||
Caption = "Image Viewer"
|
||||
Layout = VBox
|
||||
AutoSize = False
|
||||
Resizable = True
|
||||
Centered = True
|
||||
Width = 400
|
||||
Height = 320
|
||||
Begin Menu mnuFile
|
||||
Caption = "&File"
|
||||
Begin Menu mnuOpen
|
||||
Caption = "&Open..."
|
||||
End
|
||||
Begin Menu mnuSep1
|
||||
Caption = "-"
|
||||
End
|
||||
Begin Menu mnuClose
|
||||
Caption = "&Close"
|
||||
End
|
||||
End
|
||||
Begin Image ImgDisplay
|
||||
Weight = 1
|
||||
Stretch = True
|
||||
End
|
||||
End
|
||||
|
||||
'$INCLUDE: 'commdlg.bas'
|
||||
|
||||
|
||||
SUB mnuOpen_Click
|
||||
DIM path AS STRING
|
||||
path = basFileOpen("Open Image", "Images (*.bmp;*.png;*.jpg;*.gif)|All Files (*.*)")
|
||||
IF path = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
ImgDisplay.Picture = path
|
||||
ImgViewForm.Caption = path + " - Image Viewer"
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuClose_Click
|
||||
Unload ImgViewForm
|
||||
END SUB
|
||||
|
||||
|
||||
SUB ImgDisplay_DblClick
|
||||
mnuOpen_Click
|
||||
END SUB
|
||||
BIN
sdk/samples/basic/notepad/ICON32.BMP
(Stored with Git LFS)
Normal file
BIN
sdk/samples/basic/notepad/ICON32.BMP
(Stored with Git LFS)
Normal file
Binary file not shown.
16
sdk/samples/basic/notepad/notepad.dbp
Normal file
16
sdk/samples/basic/notepad/notepad.dbp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[Project]
|
||||
Name = Notepad
|
||||
Author = DVX Project
|
||||
Description = Simple text editor
|
||||
Icon = ICON32.BMP
|
||||
|
||||
[Modules]
|
||||
File0 = ../../../include/basic/commdlg.bas
|
||||
Count = 1
|
||||
|
||||
[Forms]
|
||||
File0 = notepad.frm
|
||||
Count = 1
|
||||
|
||||
[Settings]
|
||||
StartupForm = NotepadForm
|
||||
237
sdk/samples/basic/notepad/notepad.frm
Normal file
237
sdk/samples/basic/notepad/notepad.frm
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
VERSION DVX 1.00
|
||||
Begin Form NotepadForm
|
||||
Caption = "Untitled - Notepad"
|
||||
Layout = VBox
|
||||
AutoSize = False
|
||||
Resizable = True
|
||||
Centered = True
|
||||
Width = 480
|
||||
Height = 320
|
||||
Begin Menu mnuFile
|
||||
Caption = "&File"
|
||||
Begin Menu mnuNew
|
||||
Caption = "&New"
|
||||
End
|
||||
Begin Menu mnuOpen
|
||||
Caption = "&Open..."
|
||||
End
|
||||
Begin Menu mnuSep1
|
||||
Caption = "-"
|
||||
End
|
||||
Begin Menu mnuSave
|
||||
Caption = "&Save"
|
||||
End
|
||||
Begin Menu mnuSaveAs
|
||||
Caption = "Save &As..."
|
||||
End
|
||||
Begin Menu mnuSep2
|
||||
Caption = "-"
|
||||
End
|
||||
Begin Menu mnuExit
|
||||
Caption = "E&xit"
|
||||
End
|
||||
End
|
||||
Begin Menu mnuEdit
|
||||
Caption = "&Edit"
|
||||
Begin Menu mnuCut
|
||||
Caption = "Cu&t"
|
||||
End
|
||||
Begin Menu mnuCopy
|
||||
Caption = "&Copy"
|
||||
End
|
||||
Begin Menu mnuPaste
|
||||
Caption = "&Paste"
|
||||
End
|
||||
Begin Menu mnuSep3
|
||||
Caption = "-"
|
||||
End
|
||||
Begin Menu mnuSelAll
|
||||
Caption = "Select &All"
|
||||
End
|
||||
End
|
||||
Begin TextArea Editor
|
||||
Weight = 1
|
||||
End
|
||||
End
|
||||
|
||||
'$INCLUDE: 'commdlg.bas'
|
||||
|
||||
DIM currentFile AS STRING
|
||||
DIM savedHash AS LONG
|
||||
DIM dirty AS INTEGER
|
||||
currentFile = ""
|
||||
savedHash = 0
|
||||
dirty = 0
|
||||
|
||||
|
||||
SUB NotepadForm_Load
|
||||
Editor.SetCaptureTabs True
|
||||
Editor.SetTabWidth 4
|
||||
END SUB
|
||||
|
||||
|
||||
FUNCTION TextHash() AS LONG
|
||||
DIM h AS LONG
|
||||
DIM t AS STRING
|
||||
t = Editor.Text
|
||||
h = 0
|
||||
DIM i AS INTEGER
|
||||
FOR i = 1 TO LEN(t)
|
||||
h = h * 31 + ASC(MID$(t, i, 1))
|
||||
NEXT i
|
||||
TextHash = h
|
||||
END FUNCTION
|
||||
|
||||
|
||||
FUNCTION IsDirty() AS INTEGER
|
||||
IsDirty = (TextHash() <> savedHash)
|
||||
END FUNCTION
|
||||
|
||||
|
||||
SUB UpdateTitle
|
||||
DIM title AS STRING
|
||||
IF currentFile <> "" THEN
|
||||
title = currentFile + " - Notepad"
|
||||
ELSE
|
||||
title = "Untitled - Notepad"
|
||||
END IF
|
||||
IF dirty THEN
|
||||
title = "* " + title
|
||||
END IF
|
||||
NotepadForm.Caption = title
|
||||
END SUB
|
||||
|
||||
|
||||
SUB MarkClean
|
||||
dirty = 0
|
||||
MarkClean
|
||||
END SUB
|
||||
|
||||
|
||||
SUB Editor_Change
|
||||
IF NOT dirty THEN
|
||||
dirty = -1
|
||||
UpdateTitle
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
FUNCTION AskSave() AS INTEGER
|
||||
IF NOT IsDirty() THEN
|
||||
AskSave = 0
|
||||
EXIT FUNCTION
|
||||
END IF
|
||||
DIM result AS INTEGER
|
||||
result = basPromptSave("Notepad")
|
||||
IF result = DVX_SAVE_YES THEN
|
||||
FileSave
|
||||
AskSave = 0
|
||||
ELSEIF result = DVX_SAVE_NO THEN
|
||||
AskSave = 0
|
||||
ELSE
|
||||
AskSave = 1
|
||||
END IF
|
||||
END FUNCTION
|
||||
|
||||
|
||||
SUB FileSave
|
||||
IF currentFile = "" THEN
|
||||
FileSaveAs
|
||||
EXIT SUB
|
||||
END IF
|
||||
OPEN currentFile FOR OUTPUT AS #1
|
||||
PRINT #1, Editor.Text
|
||||
CLOSE #1
|
||||
MarkClean
|
||||
END SUB
|
||||
|
||||
|
||||
SUB FileSaveAs
|
||||
DIM path AS STRING
|
||||
path = basFileSave("Save As", "Text Files (*.txt)|All Files (*.*)")
|
||||
IF path = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
currentFile = path
|
||||
FileSave
|
||||
UpdateTitle
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuNew_Click
|
||||
IF AskSave() THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
Editor.Text = ""
|
||||
currentFile = ""
|
||||
MarkClean
|
||||
UpdateTitle
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuOpen_Click
|
||||
IF AskSave() THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
DIM path AS STRING
|
||||
path = basFileOpen("Open", "Text Files (*.txt)|All Files (*.*)")
|
||||
IF path = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
DIM text AS STRING
|
||||
DIM ln AS STRING
|
||||
text = ""
|
||||
OPEN path FOR INPUT AS #1
|
||||
DO WHILE NOT EOF(1)
|
||||
LINE INPUT #1, ln
|
||||
IF text <> "" THEN
|
||||
text = text + CHR$(10)
|
||||
END IF
|
||||
text = text + ln
|
||||
LOOP
|
||||
CLOSE #1
|
||||
Editor.Text = text
|
||||
currentFile = path
|
||||
MarkClean
|
||||
UpdateTitle
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuSave_Click
|
||||
FileSave
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuSaveAs_Click
|
||||
FileSaveAs
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuExit_Click
|
||||
Unload NotepadForm
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuCut_Click
|
||||
Editor.Cut
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuCopy_Click
|
||||
Editor.Copy
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuPaste_Click
|
||||
Editor.Paste
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuSelAll_Click
|
||||
Editor.SelectAll
|
||||
END SUB
|
||||
|
||||
|
||||
SUB NotepadForm_QueryUnload(Cancel AS Integer)
|
||||
Cancel = AskSave()
|
||||
END SUB
|
||||
BIN
sdk/samples/basic/resedit/ICON32.BMP
(Stored with Git LFS)
Normal file
BIN
sdk/samples/basic/resedit/ICON32.BMP
(Stored with Git LFS)
Normal file
Binary file not shown.
17
sdk/samples/basic/resedit/resedit.dbp
Normal file
17
sdk/samples/basic/resedit/resedit.dbp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
[Project]
|
||||
Name = Resource Editor
|
||||
Author = DVX Project
|
||||
Description = DVX resource file editor
|
||||
Icon = ICON32.BMP
|
||||
|
||||
[Modules]
|
||||
File0 = ../../../include/basic/commdlg.bas
|
||||
File1 = ../../../include/basic/resource.bas
|
||||
Count = 2
|
||||
|
||||
[Forms]
|
||||
File0 = resedit.frm
|
||||
Count = 1
|
||||
|
||||
[Settings]
|
||||
StartupForm = ResEdit
|
||||
431
sdk/samples/basic/resedit/resedit.frm
Normal file
431
sdk/samples/basic/resedit/resedit.frm
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
VERSION DVX 1.00
|
||||
' resedit.frm -- DVX Resource Editor
|
||||
'
|
||||
' Graphical editor for the resource blocks appended to DXE3
|
||||
' files (.app, .wgt, .lib). View, add, remove, and extract
|
||||
' resources of type icon, text, or binary.
|
||||
'
|
||||
' Add commdlg.bas and resource.bas to your project, then click Run.
|
||||
|
||||
Begin Form ResEdit
|
||||
Caption = "DVX Resource Editor"
|
||||
Layout = VBox
|
||||
AutoSize = False
|
||||
Resizable = True
|
||||
Centered = True
|
||||
Width = 500
|
||||
Height = 340
|
||||
|
||||
Begin Menu mnuFile
|
||||
Caption = "&File"
|
||||
Begin Menu mnuOpen
|
||||
Caption = "&Open..."
|
||||
End
|
||||
Begin Menu mnuClose
|
||||
Caption = "&Close"
|
||||
Enabled = False
|
||||
End
|
||||
Begin Menu mnuSep1
|
||||
Caption = "-"
|
||||
End
|
||||
Begin Menu mnuExit
|
||||
Caption = "E&xit"
|
||||
End
|
||||
End
|
||||
|
||||
Begin Menu mnuResource
|
||||
Caption = "&Resource"
|
||||
Begin Menu mnuAddText
|
||||
Caption = "Add &Text..."
|
||||
Enabled = False
|
||||
End
|
||||
Begin Menu mnuAddFile
|
||||
Caption = "Add &File..."
|
||||
Enabled = False
|
||||
End
|
||||
Begin Menu mnuEditText
|
||||
Caption = "&Edit Text..."
|
||||
Enabled = False
|
||||
End
|
||||
Begin Menu mnuSep2
|
||||
Caption = "-"
|
||||
End
|
||||
Begin Menu mnuExtract
|
||||
Caption = "E&xtract..."
|
||||
Enabled = False
|
||||
End
|
||||
Begin Menu mnuRemove
|
||||
Caption = "&Remove"
|
||||
Enabled = False
|
||||
End
|
||||
End
|
||||
|
||||
Begin ListView ResList
|
||||
Weight = 1
|
||||
End
|
||||
|
||||
Begin Label LblStatus
|
||||
Caption = "No file loaded."
|
||||
Weight = 0
|
||||
End
|
||||
End
|
||||
|
||||
OPTION EXPLICIT
|
||||
|
||||
DIM filePath AS STRING
|
||||
DIM resHandle AS LONG
|
||||
filePath = ""
|
||||
resHandle = 0
|
||||
|
||||
Load ResEdit
|
||||
ResEdit.Show
|
||||
|
||||
ResList.SetColumns "Name,20|Type,8|Size,12"
|
||||
LblStatus.Caption = "Ready. Use File > Open to load a DXE file."
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Type name helper
|
||||
' ============================================================
|
||||
|
||||
FUNCTION TypeName$(t AS LONG)
|
||||
IF t = RES_TYPE_ICON THEN
|
||||
TypeName$ = "Icon"
|
||||
ELSEIF t = RES_TYPE_TEXT THEN
|
||||
TypeName$ = "Text"
|
||||
ELSEIF t = RES_TYPE_BINARY THEN
|
||||
TypeName$ = "Binary"
|
||||
ELSE
|
||||
TypeName$ = "Unknown"
|
||||
END IF
|
||||
END FUNCTION
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Format a byte size for display
|
||||
' ============================================================
|
||||
|
||||
FUNCTION FormatSize$(sz AS LONG)
|
||||
IF sz < 1024 THEN
|
||||
FormatSize$ = STR$(sz) + " B"
|
||||
ELSE
|
||||
FormatSize$ = STR$(sz \ 1024) + " KB"
|
||||
END IF
|
||||
END FUNCTION
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Refresh the resource list from the open handle
|
||||
' ============================================================
|
||||
|
||||
SUB RefreshList
|
||||
ResList.Clear
|
||||
|
||||
IF resHandle = 0 THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM n AS LONG
|
||||
n = ResCount(resHandle)
|
||||
|
||||
DIM ix AS LONG
|
||||
FOR ix = 0 TO n - 1
|
||||
ResList.AddItem ResName$(resHandle, ix)
|
||||
ResList.SetCell ix, 1, TypeName$(ResType(resHandle, ix))
|
||||
ResList.SetCell ix, 2, FormatSize$(ResSize(resHandle, ix))
|
||||
NEXT ix
|
||||
|
||||
LblStatus.Caption = filePath + " - " + STR$(n) + " resource(s)"
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Close the current file
|
||||
' ============================================================
|
||||
|
||||
SUB CloseFile
|
||||
IF resHandle <> 0 THEN
|
||||
ResClose resHandle
|
||||
resHandle = 0
|
||||
END IF
|
||||
filePath = ""
|
||||
ResList.Clear
|
||||
ResEdit.Caption = "DVX Resource Editor"
|
||||
mnuClose.Enabled = False
|
||||
mnuAddText.Enabled = False
|
||||
mnuAddFile.Enabled = False
|
||||
mnuEditText.Enabled = False
|
||||
mnuExtract.Enabled = False
|
||||
mnuRemove.Enabled = False
|
||||
LblStatus.Caption = "No file loaded."
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Reopen the file (after modification) and refresh
|
||||
' ============================================================
|
||||
|
||||
SUB ReopenAndRefresh
|
||||
DIM path AS STRING
|
||||
path = filePath
|
||||
|
||||
IF resHandle <> 0 THEN
|
||||
ResClose resHandle
|
||||
resHandle = 0
|
||||
END IF
|
||||
|
||||
resHandle = ResOpen(path)
|
||||
|
||||
IF resHandle = 0 THEN
|
||||
' File may have had all resources stripped
|
||||
filePath = path
|
||||
ResList.Clear
|
||||
LblStatus.Caption = path + " - 0 resource(s)"
|
||||
ELSE
|
||||
filePath = path
|
||||
RefreshList
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Enable/disable selection-dependent menus
|
||||
' ============================================================
|
||||
|
||||
SUB UpdateMenuState
|
||||
DIM hasSel AS INTEGER
|
||||
hasSel = (ResList.ListIndex >= 0)
|
||||
mnuExtract.Enabled = hasSel
|
||||
mnuRemove.Enabled = hasSel
|
||||
mnuEditText.Enabled = hasSel
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' Menu handlers
|
||||
' ============================================================
|
||||
|
||||
SUB mnuOpen_Click
|
||||
DIM path AS STRING
|
||||
path = basFileOpen("Open DXE File", "Applications (*.app)|Widget Modules (*.wgt)|Libraries (*.lib)|All Files (*.*)")
|
||||
IF path = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
CloseFile
|
||||
|
||||
filePath = path
|
||||
ResEdit.Caption = path + " - DVX Resource Editor"
|
||||
mnuClose.Enabled = True
|
||||
mnuAddText.Enabled = True
|
||||
mnuAddFile.Enabled = True
|
||||
|
||||
resHandle = ResOpen(path)
|
||||
IF resHandle = 0 THEN
|
||||
LblStatus.Caption = "No resources in " + path
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
RefreshList
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuClose_Click
|
||||
CloseFile
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuExit_Click
|
||||
Unload ResEdit
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuAddText_Click
|
||||
IF filePath = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM rName AS STRING
|
||||
rName = basInputBox2("Add Text Resource", "Resource name:", "")
|
||||
IF rName = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM text AS STRING
|
||||
text = basInputBox2("Add Text Resource", "Text value:", "")
|
||||
|
||||
IF ResAddText(filePath, rName, text) THEN
|
||||
ReopenAndRefresh
|
||||
LblStatus.Caption = "Added text resource: " + rName
|
||||
ELSE
|
||||
LblStatus.Caption = "Failed to add resource."
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuAddFile_Click
|
||||
IF filePath = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM rName AS STRING
|
||||
rName = basInputBox2("Add File Resource", "Resource name:", "")
|
||||
IF rName = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM typeChoice AS LONG
|
||||
typeChoice = basChoiceDialog("Resource Type", "Select resource type:", "Icon|Binary", 0)
|
||||
IF typeChoice < 0 THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM typeVal AS LONG
|
||||
IF typeChoice = 0 THEN
|
||||
typeVal = RES_TYPE_ICON
|
||||
ELSE
|
||||
typeVal = RES_TYPE_BINARY
|
||||
END IF
|
||||
|
||||
DIM srcPath AS STRING
|
||||
srcPath = basFileOpen("Select Source File", "All Files (*.*)")
|
||||
IF srcPath = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
IF ResAddFile(filePath, rName, typeVal, srcPath) THEN
|
||||
ReopenAndRefresh
|
||||
LblStatus.Caption = "Added resource: " + rName
|
||||
ELSE
|
||||
LblStatus.Caption = "Failed to add resource."
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuEditText_Click
|
||||
IF filePath = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM sel AS LONG
|
||||
sel = ResList.ListIndex
|
||||
IF sel < 0 THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM rName AS STRING
|
||||
rName = ResName$(resHandle, sel)
|
||||
|
||||
DIM t AS LONG
|
||||
t = ResType(resHandle, sel)
|
||||
IF t <> RES_TYPE_TEXT THEN
|
||||
LblStatus.Caption = "Only text resources can be edited inline."
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM oldText AS STRING
|
||||
oldText = ResGetText$(filePath, rName)
|
||||
|
||||
DIM newText AS STRING
|
||||
newText = basInputBox2("Edit Text Resource", "Value for '" + rName + "':", oldText)
|
||||
|
||||
IF ResAddText(filePath, rName, newText) THEN
|
||||
ReopenAndRefresh
|
||||
LblStatus.Caption = "Updated: " + rName
|
||||
ELSE
|
||||
LblStatus.Caption = "Failed to update resource."
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuExtract_Click
|
||||
IF filePath = "" OR resHandle = 0 THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM sel AS LONG
|
||||
sel = ResList.ListIndex
|
||||
IF sel < 0 THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM rName AS STRING
|
||||
rName = ResName$(resHandle, sel)
|
||||
|
||||
DIM outPath AS STRING
|
||||
outPath = basFileSave("Extract Resource", "All Files (*.*)")
|
||||
IF outPath = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
IF ResExtract(filePath, rName, outPath) THEN
|
||||
LblStatus.Caption = "Extracted '" + rName + "' to " + outPath
|
||||
ELSE
|
||||
LblStatus.Caption = "Failed to extract resource."
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB mnuRemove_Click
|
||||
IF filePath = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM sel AS LONG
|
||||
sel = ResList.ListIndex
|
||||
IF sel < 0 THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM rName AS STRING
|
||||
rName = ResName$(resHandle, sel)
|
||||
|
||||
DIM ans AS INTEGER
|
||||
ans = MsgBox("Remove resource '" + rName + "'?", vbYesNo)
|
||||
IF ans = vbNo THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
' Close handle before modifying
|
||||
ResClose resHandle
|
||||
resHandle = 0
|
||||
|
||||
IF ResRemove(filePath, rName) THEN
|
||||
ReopenAndRefresh
|
||||
LblStatus.Caption = "Removed: " + rName
|
||||
ELSE
|
||||
ReopenAndRefresh
|
||||
LblStatus.Caption = "Failed to remove resource."
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
' ============================================================
|
||||
' ListView selection change
|
||||
' ============================================================
|
||||
|
||||
SUB ResList_Click
|
||||
UpdateMenuState
|
||||
END SUB
|
||||
|
||||
|
||||
SUB ResList_DblClick
|
||||
IF filePath = "" OR resHandle = 0 THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM sel AS LONG
|
||||
sel = ResList.ListIndex
|
||||
IF sel < 0 THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
DIM t AS LONG
|
||||
t = ResType(resHandle, sel)
|
||||
|
||||
IF t = RES_TYPE_TEXT THEN
|
||||
mnuEditText_Click
|
||||
ELSE
|
||||
mnuExtract_Click
|
||||
END IF
|
||||
END SUB
|
||||
|
|
@ -283,10 +283,11 @@ int shellMain(int argc, char *argv[]) {
|
|||
else if (strcmp(accelStr, "medium") == 0) { accelVal = 64; }
|
||||
else if (strcmp(accelStr, "high") == 0) { accelVal = 32; }
|
||||
|
||||
int32_t speed = prefsGetInt(sPrefs, "mouse", "speed", 8);
|
||||
int32_t speed = prefsGetInt(sPrefs, "mouse", "speed", 8);
|
||||
int32_t wheelStep = prefsGetInt(sPrefs, "mouse", "wheelspeed", MOUSE_WHEEL_STEP_DEFAULT);
|
||||
|
||||
dvxSetMouseConfig(&sCtx, wheelDir, dblClick, accelVal, speed);
|
||||
dvxLog("Preferences: mouse wheel=%s doubleclick=%ldms accel=%s speed=%ld", wheelStr, (long)dblClick, accelStr, (long)speed);
|
||||
dvxSetMouseConfig(&sCtx, wheelDir, dblClick, accelVal, speed, wheelStep);
|
||||
dvxLog("Preferences: mouse wheel=%s doubleclick=%ldms accel=%s speed=%ld wheelspeed=%ld", wheelStr, (long)dblClick, accelStr, (long)speed, (long)wheelStep);
|
||||
|
||||
// Apply saved color scheme
|
||||
bool colorsLoaded = false;
|
||||
|
|
|
|||
|
|
@ -137,8 +137,8 @@ static void onTmRun(WidgetT *w) {
|
|||
(void)w;
|
||||
|
||||
FileFilterT filters[] = {
|
||||
{ "Applications (*.app)", "*.app" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
{ "Applications (*.app)" },
|
||||
{ "All Files (*.*)" }
|
||||
};
|
||||
char path[TM_MAX_PATH];
|
||||
|
||||
|
|
|
|||
|
|
@ -352,10 +352,103 @@ static void writeBmp(const char *path) {
|
|||
fclose(f);
|
||||
}
|
||||
|
||||
|
||||
// Icon editor: pixel grid with pencil drawing a pixel
|
||||
static void iconIconEd(void) {
|
||||
clear(220, 220, 220);
|
||||
|
||||
// Draw a 5x5 grid of colored pixels (the "canvas")
|
||||
int gx = 3;
|
||||
int gy = 3;
|
||||
int cs = 5;
|
||||
|
||||
// Grid lines
|
||||
for (int i = 0; i <= 5; i++) {
|
||||
hline(gx, gy + i * cs, 5 * cs + 1, 160, 160, 160);
|
||||
vline(gx + i * cs, gy, 5 * cs + 1, 160, 160, 160);
|
||||
}
|
||||
|
||||
// Some colored pixels in the grid
|
||||
rect(gx + 1, gy + 1, cs - 1, cs - 1, 255, 0, 0);
|
||||
rect(gx + cs + 1, gy + 1, cs - 1, cs - 1, 0, 0, 255);
|
||||
rect(gx + 1, gy + cs + 1, cs - 1, cs - 1, 0, 180, 0);
|
||||
rect(gx + cs + 1, gy + cs + 1, cs - 1, cs - 1, 255, 255, 0);
|
||||
rect(gx + 2*cs+1, gy + 2*cs+1, cs - 1, cs - 1, 255, 128, 0);
|
||||
rect(gx + 3*cs+1, gy + 1, cs - 1, cs - 1, 128, 0, 255);
|
||||
|
||||
// Pencil (diagonal, bottom-right area)
|
||||
for (int i = 0; i < 12; i++) {
|
||||
pixel(18 + i, 28 - i, 230, 200, 80);
|
||||
pixel(19 + i, 28 - i, 230, 200, 80);
|
||||
pixel(18 + i, 29 - i, 230, 200, 80);
|
||||
}
|
||||
|
||||
// Pencil tip
|
||||
pixel(17, 29, 40, 40, 40);
|
||||
pixel(17, 30, 40, 40, 40);
|
||||
pixel(18, 30, 40, 40, 40);
|
||||
|
||||
// Eraser end
|
||||
pixel(29, 17, 240, 150, 150);
|
||||
pixel(30, 16, 240, 150, 150);
|
||||
pixel(30, 17, 240, 150, 150);
|
||||
pixel(29, 16, 240, 150, 150);
|
||||
|
||||
// Border
|
||||
rect(0, 0, 32, 1, 128, 128, 128);
|
||||
rect(0, 31, 32, 1, 128, 128, 128);
|
||||
rect(0, 0, 1, 32, 128, 128, 128);
|
||||
rect(31, 0, 1, 32, 128, 128, 128);
|
||||
}
|
||||
|
||||
|
||||
// Resource editor: box with stacked resource entries
|
||||
static void iconResedit(void) {
|
||||
clear(220, 220, 220);
|
||||
|
||||
// Outer box (document frame)
|
||||
rect(3, 2, 26, 28, 255, 255, 255);
|
||||
rect(3, 2, 26, 1, 80, 80, 80);
|
||||
rect(3, 29, 26, 1, 80, 80, 80);
|
||||
rect(3, 2, 1, 28, 80, 80, 80);
|
||||
rect(28, 2, 1, 28, 80, 80, 80);
|
||||
|
||||
// Title bar
|
||||
rect(4, 3, 24, 4, 0, 0, 128);
|
||||
|
||||
// Resource entry rows (alternating light shading)
|
||||
rect(4, 8, 24, 4, 240, 240, 255);
|
||||
rect(4, 12, 24, 1, 180, 180, 200);
|
||||
rect(4, 13, 24, 4, 255, 255, 255);
|
||||
rect(4, 17, 24, 1, 180, 180, 200);
|
||||
rect(4, 18, 24, 4, 240, 240, 255);
|
||||
rect(4, 22, 24, 1, 180, 180, 200);
|
||||
rect(4, 23, 24, 4, 255, 255, 255);
|
||||
|
||||
// Type indicator colored dots in each row
|
||||
circle(8, 10, 1, 0, 160, 0); // icon (green)
|
||||
circle(8, 15, 1, 0, 0, 200); // text (blue)
|
||||
circle(8, 20, 1, 200, 0, 0); // binary (red)
|
||||
circle(8, 25, 1, 0, 0, 200); // text (blue)
|
||||
|
||||
// Short "name" bars in each row
|
||||
rect(11, 9, 10, 2, 60, 60, 60);
|
||||
rect(11, 14, 14, 2, 60, 60, 60);
|
||||
rect(11, 19, 8, 2, 60, 60, 60);
|
||||
rect(11, 24, 12, 2, 60, 60, 60);
|
||||
|
||||
// Border
|
||||
rect(0, 0, 32, 1, 128, 128, 128);
|
||||
rect(0, 31, 32, 1, 128, 128, 128);
|
||||
rect(0, 0, 1, 32, 128, 128, 128);
|
||||
rect(31, 0, 1, 32, 128, 128, 128);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "Usage: mkicon <output.bmp> <type>\n");
|
||||
fprintf(stderr, "Types: noicon, clock, notepad, cpanel, dvxdemo, imgview, basic, help\n");
|
||||
fprintf(stderr, "Types: noicon, clock, notepad, cpanel, dvxdemo, imgview, basic, help, iconed, resedit\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -378,6 +471,10 @@ int main(int argc, char **argv) {
|
|||
iconImgview();
|
||||
} else if (strcmp(type, "help") == 0) {
|
||||
iconHelp();
|
||||
} else if (strcmp(type, "iconed") == 0) {
|
||||
iconIconEd();
|
||||
} else if (strcmp(type, "resedit") == 0) {
|
||||
iconResedit();
|
||||
} else {
|
||||
fprintf(stderr, "Unknown icon type: %s\n", type);
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -1024,6 +1024,13 @@ void wgtAnsiTermSetScrollback(WidgetT *w, int32_t maxLines) {
|
|||
}
|
||||
|
||||
|
||||
static int32_t wgtAnsiTermGetScrollback(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, 0);
|
||||
AnsiTermDataT *at = (AnsiTermDataT *)w->data;
|
||||
return at->scrollbackMax;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// BASIC-facing accessors
|
||||
// ============================================================
|
||||
|
|
@ -1072,7 +1079,7 @@ static const struct {
|
|||
static const WgtPropDescT sProps[] = {
|
||||
{ "Cols", WGT_IFACE_INT, (void *)wgtAnsiTermGetCols, NULL, NULL },
|
||||
{ "Rows", WGT_IFACE_INT, (void *)wgtAnsiTermGetRows, NULL, NULL },
|
||||
{ "Scrollback", WGT_IFACE_INT, NULL, (void *)wgtAnsiTermSetScrollback, NULL }
|
||||
{ "Scrollback", WGT_IFACE_INT, (void *)wgtAnsiTermGetScrollback, (void *)wgtAnsiTermSetScrollback, NULL }
|
||||
};
|
||||
|
||||
static const WgtMethodDescT sMethods[] = {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ typedef struct {
|
|||
bool pressed;
|
||||
bool hasTransparency;
|
||||
uint32_t keyColor;
|
||||
char picturePath[DVX_MAX_PATH];
|
||||
bool stretch; // true = scale to fit widget bounds
|
||||
} ImageDataT;
|
||||
|
||||
|
||||
|
|
@ -55,8 +57,17 @@ void widgetImageDestroy(WidgetT *w) {
|
|||
void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
(void)font;
|
||||
ImageDataT *d = (ImageDataT *)w->data;
|
||||
w->calcMinW = d->imgW;
|
||||
w->calcMinH = d->imgH;
|
||||
|
||||
if (d->stretch) {
|
||||
// Stretch mode: don't force the widget to the image size.
|
||||
// Let the layout engine determine the size; the paint method
|
||||
// will scale the image to fit.
|
||||
w->calcMinW = 0;
|
||||
w->calcMinH = 0;
|
||||
} else {
|
||||
w->calcMinW = d->imgW;
|
||||
w->calcMinH = d->imgH;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -94,61 +105,108 @@ void widgetImagePaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const Bit
|
|||
(void)colors;
|
||||
ImageDataT *d = (ImageDataT *)w->data;
|
||||
|
||||
if (!d->pixelData) {
|
||||
if (!d->pixelData || d->imgW <= 0 || d->imgH <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Center the image within the widget bounds
|
||||
int32_t imgW = d->imgW;
|
||||
int32_t imgH = d->imgH;
|
||||
int32_t dx = w->x + (w->w - imgW) / 2;
|
||||
int32_t dy = w->y + (w->h - imgH) / 2;
|
||||
int32_t wgtW = w->w;
|
||||
int32_t wgtH = w->h;
|
||||
|
||||
// Scale to fit (if stretch enabled) or use native size
|
||||
int32_t fitW;
|
||||
int32_t fitH;
|
||||
|
||||
if (d->stretch && (imgW > wgtW || imgH > wgtH)) {
|
||||
fitW = wgtW;
|
||||
fitH = (imgH * wgtW) / imgW;
|
||||
|
||||
if (fitH > wgtH) {
|
||||
fitH = wgtH;
|
||||
fitW = (imgW * wgtH) / imgH;
|
||||
}
|
||||
|
||||
if (fitW <= 0) { fitW = 1; }
|
||||
if (fitH <= 0) { fitH = 1; }
|
||||
} else {
|
||||
fitW = imgW;
|
||||
fitH = imgH;
|
||||
}
|
||||
|
||||
// Center in widget bounds
|
||||
int32_t dx = w->x + (wgtW - fitW) / 2;
|
||||
int32_t dy = w->y + (wgtH - fitH) / 2;
|
||||
|
||||
// Offset by 1px when pressed (button-press effect)
|
||||
if (d->pressed) {
|
||||
dx++;
|
||||
dy++;
|
||||
}
|
||||
|
||||
if (w->enabled) {
|
||||
if (d->hasTransparency) {
|
||||
// If image fits at 1:1, blit directly (no scaling needed)
|
||||
if (fitW == imgW && fitH == imgH) {
|
||||
uint8_t *src = w->enabled ? d->pixelData : d->grayData;
|
||||
|
||||
if (!src) {
|
||||
src = d->pixelData;
|
||||
}
|
||||
|
||||
if (d->hasTransparency && w->enabled) {
|
||||
rectCopyTransparent(disp, ops, dx, dy,
|
||||
d->pixelData, d->imgPitch,
|
||||
src, d->imgPitch,
|
||||
0, 0, imgW, imgH, d->keyColor);
|
||||
} else {
|
||||
rectCopy(disp, ops, dx, dy,
|
||||
d->pixelData, d->imgPitch,
|
||||
src, d->imgPitch,
|
||||
0, 0, imgW, imgH);
|
||||
}
|
||||
} else {
|
||||
if (!d->grayData) {
|
||||
int32_t bufSize = d->imgPitch * d->imgH;
|
||||
d->grayData = (uint8_t *)malloc(bufSize);
|
||||
|
||||
if (d->grayData) {
|
||||
DisplayT tmp = *disp;
|
||||
tmp.backBuf = d->grayData;
|
||||
tmp.width = d->imgW;
|
||||
tmp.height = d->imgH;
|
||||
tmp.pitch = d->imgPitch;
|
||||
tmp.clipX = 0;
|
||||
tmp.clipY = 0;
|
||||
tmp.clipW = d->imgW;
|
||||
tmp.clipH = d->imgH;
|
||||
rectCopyGrayscale(&tmp, ops, 0, 0,
|
||||
d->pixelData, d->imgPitch,
|
||||
0, 0, d->imgW, d->imgH);
|
||||
return;
|
||||
}
|
||||
|
||||
// Nearest-neighbor scale: sample source pixels directly into the
|
||||
// display backbuffer. This avoids allocating a scaled copy.
|
||||
int32_t bpp = disp->format.bitsPerPixel;
|
||||
|
||||
for (int32_t y = 0; y < fitH; y++) {
|
||||
int32_t screenY = dy + y;
|
||||
|
||||
if (screenY < disp->clipY || screenY >= disp->clipY + disp->clipH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t srcY = (y * imgH) / fitH;
|
||||
|
||||
if (srcY >= imgH) {
|
||||
srcY = imgH - 1;
|
||||
}
|
||||
|
||||
uint8_t *srcRow = d->pixelData + srcY * d->imgPitch;
|
||||
|
||||
for (int32_t x = 0; x < fitW; x++) {
|
||||
int32_t screenX = dx + x;
|
||||
|
||||
if (screenX < disp->clipX || screenX >= disp->clipX + disp->clipW) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (d->grayData) {
|
||||
rectCopy(disp, ops, dx, dy,
|
||||
d->grayData, d->imgPitch,
|
||||
0, 0, imgW, imgH);
|
||||
} else {
|
||||
rectCopy(disp, ops, dx, dy,
|
||||
d->pixelData, d->imgPitch,
|
||||
0, 0, imgW, imgH);
|
||||
int32_t srcX = (x * imgW) / fitW;
|
||||
|
||||
if (srcX >= imgW) {
|
||||
srcX = imgW - 1;
|
||||
}
|
||||
|
||||
// Copy one pixel from source to display
|
||||
if (bpp == 16 || bpp == 15) {
|
||||
uint16_t px = ((uint16_t *)srcRow)[srcX];
|
||||
((uint16_t *)(disp->backBuf + screenY * disp->pitch))[screenX] = px;
|
||||
} else if (bpp == 32) {
|
||||
uint32_t px = ((uint32_t *)srcRow)[srcX];
|
||||
((uint32_t *)(disp->backBuf + screenY * disp->pitch))[screenX] = px;
|
||||
} else if (bpp == 8) {
|
||||
uint8_t px = srcRow[srcX];
|
||||
(disp->backBuf + screenY * disp->pitch)[screenX] = px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -261,19 +319,48 @@ static void wgtImageLoadFile(WidgetT *w, const char *path) {
|
|||
return;
|
||||
}
|
||||
|
||||
dvxSetBusy(ctx, true);
|
||||
|
||||
int32_t imgW;
|
||||
int32_t imgH;
|
||||
int32_t pitch;
|
||||
uint8_t *buf = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);
|
||||
|
||||
dvxSetBusy(ctx, false);
|
||||
|
||||
if (!buf) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImageDataT *d = (ImageDataT *)w->data;
|
||||
snprintf(d->picturePath, sizeof(d->picturePath), "%s", path);
|
||||
|
||||
wgtImageSetData(w, buf, imgW, imgH, pitch);
|
||||
}
|
||||
|
||||
|
||||
static bool wgtImageGetStretch(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, false);
|
||||
ImageDataT *d = (ImageDataT *)w->data;
|
||||
return d->stretch;
|
||||
}
|
||||
|
||||
|
||||
static void wgtImageSetStretch(WidgetT *w, bool stretch) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
ImageDataT *d = (ImageDataT *)w->data;
|
||||
d->stretch = stretch;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
static const char *wgtImageGetPicture(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, "");
|
||||
ImageDataT *d = (ImageDataT *)w->data;
|
||||
return d->picturePath;
|
||||
}
|
||||
|
||||
|
||||
static int32_t wgtImageGetWidth(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, 0);
|
||||
ImageDataT *d = (ImageDataT *)w->data;
|
||||
|
|
@ -308,15 +395,16 @@ static const struct {
|
|||
};
|
||||
|
||||
static const WgtPropDescT sProps[] = {
|
||||
{ "Picture", WGT_IFACE_STRING, NULL, (void *)wgtImageLoadFile, NULL },
|
||||
{ "ImageHeight", WGT_IFACE_INT, (void *)wgtImageGetHeight, NULL, NULL },
|
||||
{ "ImageWidth", WGT_IFACE_INT, (void *)wgtImageGetWidth, NULL, NULL },
|
||||
{ "ImageHeight", WGT_IFACE_INT, (void *)wgtImageGetHeight, NULL, NULL }
|
||||
{ "Picture", WGT_IFACE_STRING, (void *)wgtImageGetPicture, (void *)wgtImageLoadFile, NULL },
|
||||
{ "Stretch", WGT_IFACE_BOOL, (void *)wgtImageGetStretch, (void *)wgtImageSetStretch, NULL }
|
||||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
.basName = "Image",
|
||||
.props = sProps,
|
||||
.propCount = 3,
|
||||
.propCount = 4,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.events = NULL,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ typedef struct {
|
|||
int32_t imgH;
|
||||
int32_t imgPitch;
|
||||
bool pressed;
|
||||
char picturePath[DVX_MAX_PATH];
|
||||
} ImageButtonDataT;
|
||||
|
||||
|
||||
|
|
@ -324,10 +325,20 @@ static void wgtImageButtonLoadFile(WidgetT *w, const char *path) {
|
|||
return;
|
||||
}
|
||||
|
||||
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
|
||||
snprintf(d->picturePath, sizeof(d->picturePath), "%s", path);
|
||||
|
||||
wgtImageButtonSetData(w, buf, imgW, imgH, pitch);
|
||||
}
|
||||
|
||||
|
||||
static const char *wgtImageButtonGetPicture(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, "");
|
||||
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
|
||||
return d->picturePath;
|
||||
}
|
||||
|
||||
|
||||
static int32_t wgtImageButtonGetWidth(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, 0);
|
||||
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
|
||||
|
|
@ -360,7 +371,7 @@ static const struct {
|
|||
};
|
||||
|
||||
static const WgtPropDescT sImgBtnProps[] = {
|
||||
{ "Picture", WGT_IFACE_STRING, NULL, (void *)wgtImageButtonLoadFile, NULL },
|
||||
{ "Picture", WGT_IFACE_STRING, (void *)wgtImageButtonGetPicture, (void *)wgtImageButtonLoadFile, NULL },
|
||||
{ "ImageWidth", WGT_IFACE_INT, (void *)wgtImageButtonGetWidth, NULL, NULL },
|
||||
{ "ImageHeight", WGT_IFACE_INT, (void *)wgtImageButtonGetHeight, NULL, NULL }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ typedef struct {
|
|||
char **ownedCells; // stb_ds dynamic array of strdup'd strings
|
||||
bool ownsCells; // true if cellData points to ownedCells
|
||||
int32_t nextCell; // next cell position for AddItem (row-major index)
|
||||
// Owned column definitions (deep copies for BASIC callers)
|
||||
ListViewColT *ownedColDefs; // heap array of column defs (NULL if not owned)
|
||||
char **ownedColTitles; // heap array of strdup'd title strings
|
||||
int32_t ownedColCount;
|
||||
} ListViewDataT;
|
||||
|
||||
|
||||
|
|
@ -369,6 +373,13 @@ void widgetListViewDestroy(WidgetT *w) {
|
|||
free(lv->ownedCells[i]);
|
||||
}
|
||||
arrfree(lv->ownedCells);
|
||||
|
||||
for (int32_t i = 0; i < lv->ownedColCount; i++) {
|
||||
free(lv->ownedColTitles[i]);
|
||||
}
|
||||
free(lv->ownedColTitles);
|
||||
free(lv->ownedColDefs);
|
||||
|
||||
free(lv->selBits);
|
||||
free(lv->sortIndex);
|
||||
free(lv);
|
||||
|
|
@ -1673,14 +1684,20 @@ void wgtListViewAddItem(WidgetT *w, const char *text) {
|
|||
return;
|
||||
}
|
||||
|
||||
// If at a row boundary, add colCount empty cells to start a new row
|
||||
if (lv->nextCell % lv->colCount == 0) {
|
||||
for (int32_t c = 0; c < lv->colCount; c++) {
|
||||
arrput(lv->ownedCells, strdup(""));
|
||||
}
|
||||
// Always align to the next row boundary so AddItem always starts a new row,
|
||||
// even if SetCell was used to fill remaining columns of the previous row.
|
||||
int32_t rem = lv->nextCell % lv->colCount;
|
||||
|
||||
if (rem != 0) {
|
||||
lv->nextCell += lv->colCount - rem;
|
||||
}
|
||||
|
||||
// Replace the next empty cell with the provided text
|
||||
// Add colCount empty cells for the new row
|
||||
for (int32_t c = 0; c < lv->colCount; c++) {
|
||||
arrput(lv->ownedCells, strdup(""));
|
||||
}
|
||||
|
||||
// Set the first cell of the new row to the provided text
|
||||
int32_t idx = lv->nextCell;
|
||||
free(lv->ownedCells[idx]);
|
||||
lv->ownedCells[idx] = strdup(text ? text : "");
|
||||
|
|
@ -1945,15 +1962,61 @@ static void basSetColumns(WidgetT *w, const char *spec) {
|
|||
return;
|
||||
}
|
||||
|
||||
static char buf[1024];
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
ListViewDataT *lv = (ListViewDataT *)w->data;
|
||||
|
||||
// Free previous owned columns
|
||||
for (int32_t i = 0; i < lv->ownedColCount; i++) {
|
||||
free(lv->ownedColTitles[i]);
|
||||
}
|
||||
|
||||
free(lv->ownedColTitles);
|
||||
free(lv->ownedColDefs);
|
||||
lv->ownedColTitles = NULL;
|
||||
lv->ownedColDefs = NULL;
|
||||
lv->ownedColCount = 0;
|
||||
|
||||
// Parse spec into a temporary buffer
|
||||
char buf[1024];
|
||||
snprintf(buf, sizeof(buf), "%s", spec);
|
||||
|
||||
ListViewColT cols[BAS_MAX_LISTVIEW_COLS];
|
||||
int32_t count = 0;
|
||||
char *tok = buf;
|
||||
// First pass: count columns
|
||||
int32_t count = 0;
|
||||
char *p = buf;
|
||||
|
||||
while (*tok && count < BAS_MAX_LISTVIEW_COLS) {
|
||||
// Parse "Title,Width" or just "Title"
|
||||
while (*p) {
|
||||
count++;
|
||||
char *sep = strchr(p, '|');
|
||||
|
||||
if (sep) {
|
||||
p = sep + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (count <= 0 || count > BAS_MAX_LISTVIEW_COLS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate owned storage
|
||||
lv->ownedColDefs = (ListViewColT *)calloc(count, sizeof(ListViewColT));
|
||||
lv->ownedColTitles = (char **)calloc(count, sizeof(char *));
|
||||
|
||||
if (!lv->ownedColDefs || !lv->ownedColTitles) {
|
||||
free(lv->ownedColDefs);
|
||||
free(lv->ownedColTitles);
|
||||
lv->ownedColDefs = NULL;
|
||||
lv->ownedColTitles = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// Second pass: parse and deep-copy
|
||||
snprintf(buf, sizeof(buf), "%s", spec);
|
||||
int32_t idx = 0;
|
||||
char *tok = buf;
|
||||
|
||||
while (*tok && idx < count) {
|
||||
char *sep = strchr(tok, '|');
|
||||
|
||||
if (sep) {
|
||||
|
|
@ -1964,16 +2027,18 @@ static void basSetColumns(WidgetT *w, const char *spec) {
|
|||
|
||||
if (comma) {
|
||||
*comma = '\0';
|
||||
cols[count].title = tok;
|
||||
cols[count].width = wgtChars(atoi(comma + 1));
|
||||
cols[count].align = ListViewAlignLeftE;
|
||||
lv->ownedColTitles[idx] = strdup(tok);
|
||||
lv->ownedColDefs[idx].title = lv->ownedColTitles[idx];
|
||||
lv->ownedColDefs[idx].width = wgtChars(atoi(comma + 1));
|
||||
lv->ownedColDefs[idx].align = ListViewAlignLeftE;
|
||||
} else {
|
||||
cols[count].title = tok;
|
||||
cols[count].width = 0;
|
||||
cols[count].align = ListViewAlignLeftE;
|
||||
lv->ownedColTitles[idx] = strdup(tok);
|
||||
lv->ownedColDefs[idx].title = lv->ownedColTitles[idx];
|
||||
lv->ownedColDefs[idx].width = 0;
|
||||
lv->ownedColDefs[idx].align = ListViewAlignLeftE;
|
||||
}
|
||||
|
||||
count++;
|
||||
idx++;
|
||||
|
||||
if (sep) {
|
||||
tok = sep + 1;
|
||||
|
|
@ -1982,9 +2047,8 @@ static void basSetColumns(WidgetT *w, const char *spec) {
|
|||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
wgtListViewSetColumns(w, cols, count);
|
||||
}
|
||||
lv->ownedColCount = idx;
|
||||
wgtListViewSetColumns(w, lv->ownedColDefs, idx);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3760,7 +3760,7 @@ static const WgtIfaceT sIfaceTextArea = {
|
|||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT_INT,
|
||||
.createArgs = { 4096 },
|
||||
.createArgs = { 65536 },
|
||||
.defaultEvent = "Change"
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -133,6 +133,16 @@ bool wgtTimerIsRunning(const WidgetT *w) {
|
|||
}
|
||||
|
||||
|
||||
int32_t wgtTimerGetInterval(const WidgetT *w) {
|
||||
if (!w || w->type != sTypeId) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
TimerDataT *d = (TimerDataT *)w->data;
|
||||
return d->intervalMs;
|
||||
}
|
||||
|
||||
|
||||
void wgtTimerSetInterval(WidgetT *w, int32_t intervalMs) {
|
||||
if (!w || w->type != sTypeId) {
|
||||
return;
|
||||
|
|
@ -230,7 +240,7 @@ static const struct {
|
|||
|
||||
static const WgtPropDescT sProps[] = {
|
||||
{ "Enabled", WGT_IFACE_BOOL, (void *)wgtTimerIsRunning, (void *)wgtTimerSetEnabled, NULL },
|
||||
{ "Interval", WGT_IFACE_INT, NULL, (void *)wgtTimerSetInterval, NULL }
|
||||
{ "Interval", WGT_IFACE_INT, (void *)wgtTimerGetInterval, (void *)wgtTimerSetInterval, NULL }
|
||||
};
|
||||
|
||||
static const WgtMethodDescT sMethods[] = {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue