Huge amount of BASIC API work.

This commit is contained in:
Scott Duensing 2026-10-12 14:19:24 -05:00
parent e1e48c7619
commit 824fe02f8d
674 changed files with 87376 additions and 18737 deletions

View file

@ -39,7 +39,7 @@
LIBS = $(sort $(notdir $(patsubst %/Makefile,%,$(wildcard src/libs/kpunch/*/Makefile)))) LIBS = $(sort $(notdir $(patsubst %/Makefile,%,$(wildcard src/libs/kpunch/*/Makefile))))
CORE_LIBS = libdvx libtasks CORE_LIBS = libdvx libtasks
.PHONY: all clean $(LIBS) loader widgets apps tools installer dist deploy-helpsrc compile-help deploy-sdk test test-quick test-asan test-host test-modules test-samples coverage bench bench-compare .PHONY: all clean $(LIBS) loader widgets apps tools installer dist deploy-helpsrc compile-help deploy-sdk test test-quick test-asan test-host test-modules test-samples coverage test-qa test-fuzz bench bench-compare
# Every build ends by making the release media in dist/ (a second or # Every build ends by making the release media in dist/ (a second or
# two), so dist/ always holds the latest DVX. # two), so dist/ always holds the latest DVX.
@ -159,6 +159,18 @@ test-dos: all
coverage: tools coverage: tools
$(MAKE) -C src/test/coverage $(MAKE) -C src/test/coverage
# Report-only quality tools beyond make test (claudeDocs/qaTools.md):
# test-qa runs the clang host build, the lizard complexity report,
# MemorySanitizer and valgrind (src/test/lint qa; clang-tidy is in the
# lint gate, so in make test); test-fuzz runs
# every libFuzzer harness for FUZZ_SECONDS (default 60) each
# (src/test/fuzz run). Neither is part of make test.
test-qa:
$(MAKE) -C src/test/lint qa
test-fuzz:
$(MAKE) -C src/test/fuzz run
HLPC = bin/host/dvxhlpc HLPC = bin/host/dvxhlpc
HELP_FILES = src/tools/helpFiles.sh HELP_FILES = src/tools/helpFiles.sh
@ -171,7 +183,8 @@ HELP_FILES = src/tools/helpFiles.sh
USER_GUIDE_DHS = src/apps/kpunch/progman/userHelp.dhs USER_GUIDE_DHS = src/apps/kpunch/progman/userHelp.dhs
BASIC_DHS = src/apps/kpunch/dvxbasic/ideguide.dhs src/apps/kpunch/dvxbasic/langref.dhs \ BASIC_DHS = src/apps/kpunch/dvxbasic/ideguide.dhs src/apps/kpunch/dvxbasic/langref.dhs \
src/apps/kpunch/dvxbasic/ctrlover.dhs src/apps/kpunch/dvxbasic/form.dhs src/apps/kpunch/dvxbasic/ctrlover.dhs src/apps/kpunch/dvxbasic/form.dhs \
src/apps/kpunch/dvxbasic/userctl.dhs
# Needs bin/host/dvxhlpc, and every module built and its help deployed # Needs bin/host/dvxhlpc, and every module built and its help deployed
# (library and app help are classified by the modules found in bin/). # (library and app help are classified by the modules found in bin/).
@ -212,7 +225,7 @@ deploy-sdk: tools apps
@echo "Building SDK..." @echo "Building SDK..."
@# Start from an empty tree so a header dropped from the lists goes away. @# Start from an empty tree so a header dropped from the lists goes away.
rm -rf $(SDKDIR) rm -rf $(SDKDIR)
@mkdir -p $(SDKDIR)/bin $(SDKDIR)/include/core $(SDKDIR)/include/shell $(SDKDIR)/include/tasks $(SDKDIR)/include/sql $(SDKDIR)/include/texthelp $(SDKDIR)/include/listhelp $(SDKDIR)/include/fileops $(SDKDIR)/include/xfer $(SDKDIR)/include/conn $(SDKDIR)/include/print $(SDKDIR)/include/game $(SDKDIR)/include/joystick $(SDKDIR)/include/audio $(SDKDIR)/include/music $(SDKDIR)/include/net $(SDKDIR)/include/basic $(SDKDIR)/samples/basic/basdemo $(SDKDIR)/samples/basic/widshow @mkdir -p $(SDKDIR)/bin $(SDKDIR)/include/core $(SDKDIR)/include/shell $(SDKDIR)/include/tasks $(SDKDIR)/include/sql $(SDKDIR)/include/texthelp $(SDKDIR)/include/listhelp $(SDKDIR)/include/fileops $(SDKDIR)/include/hlpfile $(SDKDIR)/include/xfer $(SDKDIR)/include/conn $(SDKDIR)/include/print $(SDKDIR)/include/game $(SDKDIR)/include/joystick $(SDKDIR)/include/audio $(SDKDIR)/include/music $(SDKDIR)/include/net $(SDKDIR)/include/basic $(SDKDIR)/samples/basic/basdemo $(SDKDIR)/samples/basic/widshow
cp $(SDK_CORE_H) $(SDKDIR)/include/core/ cp $(SDK_CORE_H) $(SDKDIR)/include/core/
cp $(SDK_SHELL_H) $(SDKDIR)/include/shell/ cp $(SDK_SHELL_H) $(SDKDIR)/include/shell/
cp $(SDK_TASKS_H) $(SDKDIR)/include/tasks/ cp $(SDK_TASKS_H) $(SDKDIR)/include/tasks/
@ -220,6 +233,7 @@ deploy-sdk: tools apps
cp $(SDK_TEXTHELP_H) $(SDKDIR)/include/texthelp/ cp $(SDK_TEXTHELP_H) $(SDKDIR)/include/texthelp/
cp $(SDK_LISTHELP_H) $(SDKDIR)/include/listhelp/ cp $(SDK_LISTHELP_H) $(SDKDIR)/include/listhelp/
cp $(SDK_FILEOPS_H) $(SDKDIR)/include/fileops/ cp $(SDK_FILEOPS_H) $(SDKDIR)/include/fileops/
cp $(SDK_HLPFILE_H) $(SDKDIR)/include/hlpfile/
cp $(SDK_XFER_H) $(SDKDIR)/include/xfer/ cp $(SDK_XFER_H) $(SDKDIR)/include/xfer/
cp $(SDK_CONN_H) $(SDKDIR)/include/conn/ cp $(SDK_CONN_H) $(SDKDIR)/include/conn/
cp $(SDK_PRINT_H) $(SDKDIR)/include/print/ cp $(SDK_PRINT_H) $(SDKDIR)/include/print/

View file

@ -23,11 +23,13 @@
set -u set -u
cd "$(dirname "$0")" cd "$(dirname "$0")" || exit 1
# The DJGPP compiler the build uses (src/common.mk finds the toolchain; # The DJGPP compiler the build uses (src/common.mk finds the toolchain;
# DJGPP_PREFIX in the environment overrides it). # DJGPP_PREFIX in the environment overrides it). The query drops a
REAL_CC="$(make -s --no-print-directory -f src/common.mk --eval 'printCc: ; @echo $(CC)' printCc)" # calling make's flags: under make -j they bring its jobserver, and the
# query then prints directory lines into the path.
REAL_CC="$(env -u MAKEFLAGS -u MFLAGS -u MAKELEVEL make -s --no-print-directory -f src/common.mk --eval 'printCc: ; @echo $(CC)' printCc)"
if [ ! -x "$REAL_CC" ]; then if [ ! -x "$REAL_CC" ]; then
echo "analyze.sh: $REAL_CC not executable" >&2 echo "analyze.sh: $REAL_CC not executable" >&2

View file

@ -19,5 +19,7 @@ GIF = apps/kpunch/imgview/imgview.app
BAS = apps/kpunch/dvxbasic/dvxbasic.app BAS = apps/kpunch/dvxbasic/dvxbasic.app
DBP = apps/kpunch/dvxbasic/dvxbasic.app DBP = apps/kpunch/dvxbasic/dvxbasic.app
FRM = apps/kpunch/dvxbasic/dvxbasic.app FRM = apps/kpunch/dvxbasic/dvxbasic.app
CLS = apps/kpunch/dvxbasic/dvxbasic.app
CTL = apps/kpunch/dvxbasic/dvxbasic.app
HLP = apps/kpunch/dvxhelp/dvxhelp.app HLP = apps/kpunch/dvxhelp/dvxhelp.app
DHS = apps/kpunch/dvxhelp/helpedit.app DHS = apps/kpunch/dvxhelp/helpedit.app

View file

@ -15,7 +15,7 @@ set -e
cd "$(dirname "$0")" cd "$(dirname "$0")"
TARGETS="${@:-src}" TARGETS=("${@:-src}")
INCLUDES=( INCLUDES=(
-Isrc/libs/kpunch/libdvx -Isrc/libs/kpunch/libdvx
@ -72,4 +72,4 @@ exec cppcheck \
"${INCLUDES[@]}" \ "${INCLUDES[@]}" \
"${DEFINES[@]}" \ "${DEFINES[@]}" \
"${SUPPRESS[@]}" \ "${SUPPRESS[@]}" \
$TARGETS "${TARGETS[@]}"

File diff suppressed because it is too large Load diff

View file

@ -170,7 +170,7 @@ This is a warning message.
<p>Multiple input files are merged into a single help file. This allows per-widget or per-feature documentation fragments to be combined automatically.</p> <p>Multiple input files are merged into a single help file. This allows per-widget or per-feature documentation fragments to be combined automatically.</p>
<h2>Input Files</h2> <h2>Input Files</h2>
<p>Each input file starts fresh: no topic is current and no .list, .note, .code or .table block is left open from the previous file. Text before a file's first .topic is ignored, and directives there are errors. A .code or .table block still open at the end of a file is closed with a warning. Files named with .include are different: they are read in place, as if their lines were part of the including file.</p> <p>Each input file starts fresh: no topic is current and no .list, .note, .code or .table block is left open from the previous file. Text before a file's first .topic is ignored, and directives there are errors. A .code or .table block still open at the end of a file is closed with a warning. Files named with .include are different: they are read in place, as if their lines were part of the including file.</p>
<p>Source lines longer than 4094 characters are an error. The .image directive may name a path relative to the -i directory, including a subdirectory (for example sub/pic.png).</p> <p>Source lines longer than 4094 characters, and lines that contain a NUL byte, are errors. The .image directive may name a path relative to the -i directory, including a subdirectory (for example sub/pic.png).</p>
<p>If any error is reported the compile stops and the previous output file is kept.</p> <p>If any error is reported the compile stops and the previous output file is kept.</p>
<h2>Links</h2> <h2>Links</h2>
<p>Every .link must name a topic that is compiled into the same help file: a topic in any of the input files or the files they include, before or after the link. A link to any other ID is an error that names the file, the line and the missing ID. The one exception is the help DVX rebuilds by itself when it starts after programs, widgets or libraries were added or removed: there a link to a topic that is no longer present shows as plain text instead, so removing a piece of DVX never breaks the rest of the help. Links cannot reach into another help file; name the other file's page in the text instead. A .link line inside a .code block is example text and is never checked.</p> <p>Every .link must name a topic that is compiled into the same help file: a topic in any of the input files or the files they include, before or after the link. A link to any other ID is an error that names the file, the line and the missing ID. The one exception is the help DVX rebuilds by itself when it starts after programs, widgets or libraries were added or removed: there a link to a topic that is no longer present shows as plain text instead, so removing a piece of DVX never breaks the rest of the help. Links cannot reach into another help file; name the other file's page in the text instead. A .link line inside a .code block is example text and is never checked.</p>

File diff suppressed because it is too large Load diff

BIN
screenshots/86box/apps-basdemo.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/apps-helpedit.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/cpanel-cp-types.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/f1-h-basdem.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/f1-h-helped.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/hang-ended.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/hang-taskmgr.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/menus-m-design.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/pardemo-pardemo.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/pardsgn-pardsgn.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/parhlp-parhlp-l.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/parhlp-parhlp.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/parpm-parpm.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/sdk-sdktbx.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/86box/ucdemo-ucdemo.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/ucide-ucide-ds.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/ucide-ucide-ru.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/86box/ucide-ucide-tb.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/dosbox/apps-basdemo.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/dosbox/apps-helpedit.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/dosbox/cpanel-cp-types.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/dosbox/f1-h-basdem.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/dosbox/f1-h-helped.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/dosbox/hang-ended.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/dosbox/hang-taskmgr.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/dosbox/install-su-disk.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/dosbox/menus-m-design.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/dosbox/pardemo-pardemo.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/dosbox/pardsgn-pardsgn.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/dosbox/parhlp-parhlp-l.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/dosbox/parhlp-parhlp.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/dosbox/parpm-parpm.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/dosbox/sdk-sdktbx.png (Stored with Git LFS)

Binary file not shown.

BIN
screenshots/dosbox/ucdemo-ucdemo.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/dosbox/ucide-ucide-ds.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/dosbox/ucide-ucide-ru.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
screenshots/dosbox/ucide-ucide-tb.png (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,2 @@
# Task Manager's per-application memory figures (change with the code size)
372 117 58 49

View file

@ -0,0 +1,2 @@
# Task Manager's per-application memory figures (change with the code size)
372 117 58 49

View file

@ -0,0 +1,2 @@
# Setup's "DVX needs about N MB" figure (grows with the package) and the rest of that line
202 83 176 16

View file

@ -42,13 +42,30 @@ C_APP_MAINS = $(wildcard */*.c)
C_APPS = $(sort $(foreach f,$(C_APP_MAINS),$(if $(filter $(notdir $(basename $(f))),$(patsubst %/,%,$(dir $(f)))),$(patsubst %/,%,$(dir $(f)))))) C_APPS = $(sort $(foreach f,$(C_APP_MAINS),$(if $(filter $(notdir $(basename $(f))),$(patsubst %/,%,$(dir $(f)))),$(patsubst %/,%,$(dir $(f))))))
BASCOMP = ../../../bin/host/bascomp BASCOMP = ../../../bin/host/bascomp
# BASIC apps: each is a .dbp project in its own directory.
# BASIC-only notepad, imgview, etc. replace the old C versions.
BASIC_APPS = iconed notepad imgview helpedit resedit basdemo widshow
.PHONY: all clean $(C_APPS) dvxbasic $(BASIC_APPS) # BASIC apps: each is a .dbp project. An entry is the project's directory,
# or dir:out when the program goes into another program's folder (the Help
# Editor, dvxhelp/helpedit, installs beside the Help Viewer in dvxhelp).
# The program is bin/apps/kpunch/<out>/<name>.app, <name> being the last
# part of the directory.
BASIC_APPS = iconed notepad imgview dvxhelp/helpedit:dvxhelp resedit basdemo widshow clock welcome fileMgr terminal
all: $(C_APPS) dvxbasic $(BASIC_APPS) basicDir = $(firstword $(subst :, ,$(1)))
basicName = $(notdir $(call basicDir,$(1)))
basicOut = $(or $(word 2,$(subst :, ,$(1))),$(call basicName,$(1)))
basicApp = $(BINDIR)/kpunch/$(call basicOut,$(1))/$(call basicName,$(1)).app
# Everything a project is built from: the files in its directory and the
# modules its .dbp names (include libraries elsewhere).
basicDeps = $(wildcard $(1)/*.dbp $(1)/*.frm $(1)/*.bas $(1)/*.cls $(1)/*.BMP) \
$(addprefix $(1)/,$(shell sed -n 's/^File[0-9]* *= *\(.*\.bas\)\r*$$/\1/p' $(1)/$(notdir $(1)).dbp))
BASIC_TARGETS = $(foreach a,$(BASIC_APPS),$(call basicName,$(a)))
BASIC_OUTS = $(foreach a,$(BASIC_APPS),$(call basicApp,$(a)))
.PHONY: all clean $(C_APPS) dvxbasic $(BASIC_TARGETS)
all: $(C_APPS) dvxbasic $(BASIC_TARGETS)
dvxbasic: dvxbasic:
$(MAKE) -C dvxbasic $(MAKE) -C dvxbasic
@ -64,8 +81,27 @@ appObjs = $(patsubst $(1)/%.c,$(OBJDIR)/$(1)/%.o,$(call appSrcs,$(1)))
appRes = $(wildcard $(1)/$(1).res) appRes = $(wildcard $(1)/$(1).res)
appDeps = $(call appRes,$(1)) $(if $(call appRes,$(1)),$(wildcard $(1)/*.bmp)) appDeps = $(call appRes,$(1)) $(if $(call appRes,$(1)),$(wildcard $(1)/*.bmp))
# $(1) = app directory name. $(2) = extra shell commands run after linking # Data files a program installs beside itself, C or BASIC alike:
# (empty for most apps). # APP_DATA_<name> lists them (<name> being the last part of the program's
# source directory, the files relative to it); they are copied into the
# program's folder under bin/apps/kpunch.
APP_DATA_dvxdemo = logo.bmp new.bmp open.bmp sample.bmp save.bmp
appDataOut = $(addprefix $(BINDIR)/kpunch/$(2)/,$(APP_DATA_$(notdir $(1))))
# $(1) = source directory, $(2) = output folder under bin/apps/kpunch,
# $(3) = the program's make target.
define APP_DATA_RULES
$(3): $(call appDataOut,$(1),$(2))
$(call appDataOut,$(1),$(2)): $(BINDIR)/kpunch/$(2)/%: $(1)/%
@mkdir -p $$(dir $$@)
cp $$< $$@
endef
APP_DATA_OUTS = $(foreach a,$(C_APPS),$(call appDataOut,$(a),$(a))) $(foreach a,$(BASIC_APPS),$(call appDataOut,$(call basicDir,$(a)),$(call basicOut,$(a))))
# $(1) = app directory name.
define C_APP_RULES define C_APP_RULES
$(1): $(BINDIR)/kpunch/$(1)/$(1).app $(1): $(BINDIR)/kpunch/$(1)/$(1).app
@ -73,60 +109,34 @@ $(BINDIR)/kpunch/$(1)/$(1).app: $(call appObjs,$(1)) $(call appDeps,$(1))
@mkdir -p $$(dir $$@) @mkdir -p $$(dir $$@)
$$(DXE3GEN) -o $$@ -U $(call appObjs,$(1)) $$(DXE3GEN) -o $$@ -U $(call appObjs,$(1))
$(if $(call appRes,$(1)),cd $(1) && ../$$(DVXRES) build ../$$@ $(1).res) $(if $(call appRes,$(1)),cd $(1) && ../$$(DVXRES) build ../$$@ $(1).res)
$(2)
$(OBJDIR)/$(1)/%.o: $(1)/%.c $$(DVX_MAKEFILES) $(OBJDIR)/$(1)/%.o: $(1)/%.c $$(DVX_MAKEFILES)
@mkdir -p $$(dir $$@) @mkdir -p $$(dir $$@)
$$(CC) $$(CFLAGS) -c -o $$@ $$< $$(CC) $$(CFLAGS) -c -o $$@ $$<
endef endef
DVXDEMO_BMPS = logo.bmp new.bmp open.bmp sample.bmp save.bmp $(foreach a,$(C_APPS),$(eval $(call C_APP_RULES,$(a))))
DVXDEMO_POST = cp $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) $(BINDIR)/kpunch/dvxdemo/ $(foreach a,$(C_APPS),$(if $(APP_DATA_$(a)),$(eval $(call APP_DATA_RULES,$(a),$(a),$(a)))))
$(foreach a,$(C_APPS),$(eval $(call C_APP_RULES,$(a),$(if $(filter dvxdemo,$(a)),$(DVXDEMO_POST)))))
C_APP_OBJS = $(foreach a,$(C_APPS),$(call appObjs,$(a))) C_APP_OBJS = $(foreach a,$(C_APPS),$(call appObjs,$(a)))
# BASIC apps (compiled from .dbp projects via bascomp). Source for each app # BASIC apps (compiled from .dbp projects via bascomp), one rule set per
# is under the app's own directory. # BASIC_APPS entry.
# #
# F1 needs no resource: the shell presets an app's help file to <name>.hlp # F1 needs no resource: the shell presets an app's help file to <name>.hlp
# beside the .app (compiled there from <dir>/<name>.dhs by compile-help). # beside the .app (compiled there from <dir>/<name>.dhs by compile-help).
iconed: $(BINDIR)/kpunch/iconed/iconed.app # $(1) = BASIC_APPS entry.
define BASIC_APP_RULES
$(call basicName,$(1)): $(call basicApp,$(1))
$(BINDIR)/kpunch/iconed/iconed.app: iconed/iconed.dbp iconed/iconed.frm iconed/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/iconed dvxbasic $(call basicApp,$(1)): $(call basicDeps,$(call basicDir,$(1))) $$(BASCOMP) | dvxbasic
$(BASCOMP) iconed/iconed.dbp -o $@ -release @mkdir -p $$(dir $$@)
$$(BASCOMP) $(call basicDir,$(1))/$(call basicName,$(1)).dbp -o $$@ -release
endef
notepad: $(BINDIR)/kpunch/notepad/notepad.app $(foreach a,$(BASIC_APPS),$(eval $(call BASIC_APP_RULES,$(a))))
$(foreach a,$(BASIC_APPS),$(if $(APP_DATA_$(call basicName,$(a))),$(eval $(call APP_DATA_RULES,$(call basicDir,$(a)),$(call basicOut,$(a)),$(call basicName,$(a))))))
$(BINDIR)/kpunch/notepad/notepad.app: notepad/notepad.dbp notepad/notepad.frm notepad/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/notepad dvxbasic
$(BASCOMP) notepad/notepad.dbp -o $@ -release
imgview: $(BINDIR)/kpunch/imgview/imgview.app
$(BINDIR)/kpunch/imgview/imgview.app: imgview/imgview.dbp imgview/imgview.frm imgview/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/imgview dvxbasic
$(BASCOMP) imgview/imgview.dbp -o $@ -release
helpedit: $(BINDIR)/kpunch/dvxhelp/helpedit.app
$(BINDIR)/kpunch/dvxhelp/helpedit.app: dvxhelp/helpedit/helpedit.dbp dvxhelp/helpedit/helpedit.frm dvxhelp/helpedit/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/dvxhelp dvxbasic
$(BASCOMP) dvxhelp/helpedit/helpedit.dbp -o $@ -release
resedit: $(BINDIR)/kpunch/resedit/resedit.app
$(BINDIR)/kpunch/resedit/resedit.app: resedit/resedit.dbp resedit/resedit.frm resedit/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/resedit dvxbasic
$(BASCOMP) resedit/resedit.dbp -o $@ -release
basdemo: $(BINDIR)/kpunch/basdemo/basdemo.app
$(BINDIR)/kpunch/basdemo/basdemo.app: basdemo/basdemo.dbp basdemo/basdemo.frm basdemo/shape.cls basdemo/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/basdemo dvxbasic
$(BASCOMP) basdemo/basdemo.dbp -o $@ -release
widshow: $(BINDIR)/kpunch/widshow/widshow.app
$(BINDIR)/kpunch/widshow/widshow.app: widshow/widshow.dbp widshow/widshow.frm widshow/ICON32.BMP $(BASCOMP) | $(BINDIR)/kpunch/widshow dvxbasic
$(BASCOMP) widshow/widshow.dbp -o $@ -release
# Native host build (test harness): each C app becomes a plain ELF shared # Native host build (test harness): each C app becomes a plain ELF shared
# object under obj/host/apps/<name>.so with its resources appended by # object under obj/host/apps/<name>.so with its resources appended by
@ -161,25 +171,12 @@ host: $(HOST_SOS)
host-clean: host-clean:
rm -rf $(HOSTOBJDIR) rm -rf $(HOSTOBJDIR)
$(BINDIR)/kpunch/dvxhelp: ; mkdir -p $@
$(BINDIR)/kpunch/iconed: ; mkdir -p $@
$(BINDIR)/kpunch/notepad: ; mkdir -p $@
$(BINDIR)/kpunch/imgview: ; mkdir -p $@
$(BINDIR)/kpunch/resedit: ; mkdir -p $@
$(BINDIR)/kpunch/basdemo: ; mkdir -p $@
$(BINDIR)/kpunch/widshow: ; mkdir -p $@
-include $(C_APP_OBJS:.o=.d) $(HOST_APP_OBJS:.o=.d) -include $(C_APP_OBJS:.o=.d) $(HOST_APP_OBJS:.o=.d)
clean: clean:
rm -f $(C_APP_OBJS) $(C_APP_OBJS:.o=.d) rm -f $(C_APP_OBJS) $(C_APP_OBJS:.o=.d)
rm -f $(foreach a,$(C_APPS),$(BINDIR)/kpunch/$(a)/$(a).app) rm -f $(foreach a,$(C_APPS),$(BINDIR)/kpunch/$(a)/$(a).app)
rm -f $(addprefix $(BINDIR)/kpunch/dvxdemo/,$(DVXDEMO_BMPS)) rm -f $(APP_DATA_OUTS)
rm -f $(BINDIR)/kpunch/iconed/iconed.app rm -f $(BASIC_OUTS)
rm -f $(BINDIR)/kpunch/notepad/notepad.app
rm -f $(BINDIR)/kpunch/imgview/imgview.app
rm -f $(BINDIR)/kpunch/dvxhelp/helpedit.app
rm -f $(BINDIR)/kpunch/resedit/resedit.app
rm -f $(BINDIR)/kpunch/basdemo/basdemo.app
rm -f $(BINDIR)/kpunch/widshow/widshow.app
$(MAKE) -C dvxbasic clean $(MAKE) -C dvxbasic clean

View file

@ -9,13 +9,14 @@ This directory holds the source for the bundled applications plus
the BASIC-based sample apps. Two flavours of app live side by side: the BASIC-based sample apps. Two flavours of app live side by side:
* **C apps** -- written directly against the DVX SDK headers, * **C apps** -- written directly against the DVX SDK headers,
compiled to a DXE3 by `dxe3gen`. See `progman/`, `clock/`, compiled to a DXE3 by `dxe3gen`. See `progman/`, `dvxdemo/`,
`dvxdemo/`, `cpanel/`, `dvxhelp/`. `cpanel/`, `dvxhelp/`.
* **BASIC apps** -- `.dbp` projects compiled to `.app` files by the * **BASIC apps** -- `.dbp` projects compiled to `.app` files by the
host-side `bascomp`. The compiler links the BASIC bytecode into a host-side `bascomp`. The compiler links the BASIC bytecode into a
copy of `basstub.app`, which acts as the runtime host. See copy of `basstub.app`, which acts as the runtime host. See
`iconed/`, `notepad/`, `imgview/`, `basdemo/`, `resedit/`, `iconed/`, `notepad/`, `imgview/`, `basdemo/`, `resedit/`,
`widshow/`, `dvxhelp/helpedit/`. `widshow/`, `dvxhelp/helpedit/`, `clock/`, `welcome/`, `fileMgr/`,
`terminal/`.
The rest of this document covers writing a C app against the SDK. The rest of this document covers writing a C app against the SDK.
For BASIC apps, see `dvxbasic/README.md`. A new app outside this tree For BASIC apps, see `dvxbasic/README.md`. A new app outside this tree
@ -53,15 +54,15 @@ typedef struct {
} AppDescriptorT; } AppDescriptorT;
``` ```
Example (from `clock/clock.c`): Example (from the SDK's game template, `src/sdk/templates/game/tplName.c`):
```c ```c
SHELL_APP_DESCRIPTOR( SHELL_APP_DESCRIPTOR(
.name = "Clock", .name = TPLNAME_TITLE,
.hasMainLoop = true, .hasMainLoop = true,
.multiInstance = true, .multiInstance = false,
.stackSize = SHELL_STACK_DEFAULT, .stackSize = SHELL_STACK_DEFAULT,
.priority = TS_PRIORITY_LOW .priority = TS_PRIORITY_NORMAL
); );
``` ```
@ -105,8 +106,9 @@ The shell creates a cooperative task for the app and calls
the loop and returning. the loop and returning.
Use this pattern when the app needs continuous background work that Use this pattern when the app needs continuous background work that
does not map cleanly onto event callbacks: Clock (polls the system does not map cleanly onto event callbacks: terminal emulators, games,
clock every second), terminal emulators, games. and every compiled BASIC program (the BASIC stub runs the program and
its event loop in the app's task).
## Resource Conventions ## Resource Conventions
@ -133,13 +135,13 @@ unique name. The runtime reads them via
Typical `.res` manifest: Typical `.res` manifest:
``` ```
# clock.res # dvxdemo.res
icon32 icon icon32.bmp icon32 icon icon32.bmp
name text "Clock" name text "DVX Demo"
author text "Scott Duensing" author text "Scott Duensing"
copyright text "Copyright 2026 Scott Duensing" copyright text "Copyright 2026 Scott Duensing"
publisher text "Kangaroo Punch Studios" publisher text "Kangaroo Punch Studios"
description text "Digital clock with date display" description text "Widget toolkit demonstration"
``` ```
@ -215,18 +217,76 @@ menu (Cut, Copy, Paste, Select All). Built from a BASIC project
(`notepad.dbp` + `notepad.frm`); demonstrates the form designer, (`notepad.dbp` + `notepad.frm`); demonstrates the form designer,
TextInput widget, and common dialog integration. TextInput widget, and common dialog integration.
### Clock (clock) ### Clock (clock, BASIC)
| | | | | |
|---|---| |---|---|
| File | `apps/kpunch/clock/clock.app` | | File | `apps/kpunch/clock/clock.app` |
| Type | Main-loop | | Type | BASIC (main-loop via basstub) |
| Multi-instance | Yes | | Multi-instance | Yes |
Digital clock showing time and date. Polls the system clock every Digital clock showing the time and date, updated by a Timer. Each new
second and invalidates the window to trigger a repaint. Uses the raw copy opens a little lower and to the right of the previous one
`onPaint` callback (no widgets) to draw centered text. Reference (`ShellInstanceCount`, shell.bas). Built from `clock.dbp` +
implementation for main-loop apps. `clock.frm`; its help is `clock.dhs`.
### Welcome (welcome, BASIC)
| | |
|---|---|
| File | `apps/kpunch/welcome/welcome.app` |
| Type | BASIC (main-loop via basstub) |
| Multi-instance | No |
The window DVX shows after the desktop when it starts for the first
time (DVX.INI `[shell] welcome`, written by Setup). "Don't show this
again" removes the key when the window closes; unticked, it writes the
program's path back (`ShellSetWelcome`, shell.bas). "Open the User
Guide" opens the system help. Built from `welcome.dbp` +
`welcome.frm`; its help is `welcome.dhs`.
### File Manager (fileMgr, BASIC)
| | |
|---|---|
| File | `apps/kpunch/fileMgr/fileMgr.app` |
| Type | BASIC (main-loop via basstub) |
| Multi-instance | Yes |
Drive buttons, a path box, a folder tree and a sortable file list
with open / run, copy, move, cut / copy / paste between windows,
rename, delete, new folder, properties (attributes), selection and
filter commands. Copy, move and delete run as jobs with their own
progress windows, driven by a Timer, so the program stays usable.
Built from `fileMgr.dbp`: `fileMgr.frm` (the window), `fmProps.frm`
(Properties), `fileMgr.bas` (the job engine), `fmJob.cls`, with the
fileops.bas, shell.bas, commdlg.bas and help.bas libraries; its help
is `fileMgr.dhs`.
### Terminal (terminal, BASIC)
| | |
|---|---|
| File | `apps/kpunch/terminal/terminal.app` |
| Type | BASIC (main-loop via basstub) |
| Multi-instance | Yes (one per COM port, for example) |
A BBS / serial terminal: serial port, modem (dialing, redial, carrier
loss), the secure link to the DVX host proxy (with or without a
passphrase) and Telnet; a phone book of sessions (TERMINAL.INI in its
settings folder); the ANSI terminal with scrollback, colors, local echo,
answerback, capture to file and break; XMODEM, XMODEM-1K, YMODEM and
ZMODEM uploads and downloads (ZMODEM downloads start by themselves)
with a progress window that asks about existing files. The connection,
the terminal feed and the transfers run in the conn and xfer libraries
(conn.bas, xfer.bas); a 10 ms Timer moves them along. Started with
`telnet://host[:port]` it connects to that host. Built from
`terminal.dbp`: `terminal.frm` (the window), `termBook.frm` (Phone
Book), `termSess.frm` (Session Settings), `termXfer.frm` (Transfer
Options), `termPass.frm` (passphrase), `termProg.frm` (File Transfer),
`terminal.bas` (the phone book and shared state), `termMain.bas` (the
start), with the conn.bas, xfer.bas, prefs.bas, fileops.bas, shell.bas,
commdlg.bas and help.bas libraries; its help is `terminal.dhs`.
### DVX Demo (dvxdemo) ### DVX Demo (dvxdemo)
@ -315,7 +375,10 @@ editor, project manager, compiler, and runtime VM. See
Renders compiled `.hlp` files produced by `dvxhlpc`. Supports table Renders compiled `.hlp` files produced by `dvxhlpc`. Supports table
of contents, keyword index, full-text search (trigram), hyperlinks, of contents, keyword index, full-text search (trigram), hyperlinks,
and inline images. and inline images. The file is read with the `hlpfile` library
(`src/libs/kpunch/hlpfile`) and topics are drawn by the HelpView widget
(`src/widgets/kpunch/helpView`, BASIC type HelpPane), the same code a
BASIC program uses through `hlpfile.bas` and HelpPane.
### Icon Editor (iconed, BASIC) ### Icon Editor (iconed, BASIC)
@ -457,6 +520,14 @@ same directory are linked in too), drop a `.res` manifest next to the
source, and provide a 32x32 `icon32.bmp`. The Makefile finds the app by source, and provide a 32x32 `icon32.bmp`. The Makefile finds the app by
itself; `make <app>` then produces `bin/apps/kpunch/<app>/<app>.app`. itself; `make <app>` then produces `bin/apps/kpunch/<app>/<app>.app`.
Files a program reads at run time (pictures, data) are installed beside
it by one Makefile rule, for C and BASIC programs alike: list them in
`APP_DATA_<app>` (relative to the program's source directory, `<app>`
being the last part of that directory), as `APP_DATA_dvxdemo` does.
`make <app>` copies them into the program's folder and `make clean`
removes them; the program finds them through its own folder
(`App.Path` in BASIC, the app context's `appDir` in C).
## Testing Locally ## Testing Locally
@ -491,10 +562,6 @@ apps/kpunch/
progman.dhs Program Manager's own help progman.dhs Program Manager's own help
dvxhelp.hcf help-compiler config (system reference) dvxhelp.hcf help-compiler config (system reference)
userHelp.dhs end-user guide (first part of the system help) userHelp.dhs end-user guide (first part of the system help)
clock/
clock.c digital clock (main-loop reference)
clock.res
icon32.bmp
dvxdemo/ dvxdemo/
dvxdemo.c widget showcase dvxdemo.c widget showcase
dvxdemo.res dvxdemo.res
@ -506,10 +573,10 @@ apps/kpunch/
cpanel.res cpanel.res
icon32.bmp icon32.bmp
dvxhelp/ dvxhelp/
dvxhelp.c help viewer dvxhelp.c help viewer (reads .hlp files with the hlpfile
library, shows topics in the HelpView widget)
dvxhelp.res dvxhelp.res
help.dhs source for the dvxhelp app's own help help.dhs source for the dvxhelp app's own help
hlpformat.h binary format shared with the compiler
sample.dhs small sample source sample.dhs small sample source
icon32.bmp icon32.bmp
helpedit/ BASIC project: help editor helpedit/ BASIC project: help editor
@ -542,6 +609,36 @@ apps/kpunch/
widshow.dbp widshow.dbp
widshow.frm widshow.frm
ICON32.BMP ICON32.BMP
clock/
clock.dbp
clock.frm
clock.dhs
ICON32.BMP
welcome/
welcome.dbp
welcome.frm
welcome.dhs
ICON32.BMP
fileMgr/
fileMgr.dbp
fileMgr.frm the window
fmProps.frm Properties dialog
fileMgr.bas job engine (copy / move / delete windows)
fmJob.cls one job
fileMgr.dhs
ICON32.BMP
terminal/
terminal.dbp
terminal.frm the window
termBook.frm Phone Book
termSess.frm Session Settings
termXfer.frm Transfer Options
termPass.frm secure link passphrase
termProg.frm File Transfer (progress)
terminal.bas phone book (TERMINAL.INI), shared state
termMain.bas start
terminal.dhs
ICON32.BMP
dvxbasic/ see dvxbasic/README.md dvxbasic/ see dvxbasic/README.md
compiler/ lexer, parser, codegen, strip, obfuscate compiler/ lexer, parser, codegen, strip, obfuscate

View file

@ -1,295 +0,0 @@
// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// clock.c -- Clock DXE application (main-loop with tsYield)
//
// This is a main-loop DXE app (hasMainLoop = true), in contrast to callback-
// only apps like notepad or progman. The difference is fundamental:
//
// Callback-only: appMain creates UI, returns. Shell calls callbacks.
// Main-loop: appMain runs forever in its own task, calling tsYield()
// to cooperatively yield to the shell and other tasks.
//
// A main-loop app is needed when the app has ongoing work that can't be
// expressed purely as event callbacks -- here, polling the system clock
// every second and repainting. The shell allocates a dedicated task stack
// and schedules this task alongside the main event loop.
//
// The onPaint/onClose callbacks still execute in task 0 during dvxUpdate(),
// not in this task. The main loop only handles the clock-tick polling; the
// actual rendering and window management happen through the normal callback
// path. tsYield() is what makes this cooperative -- without it, this task
// would starve the shell and all other apps.
#include "dvxApp.h"
#include "dvxDlg.h"
#include "dvxWgt.h"
#include "dvxDraw.h"
#include "dvxVideo.h"
#include "dvxWm.h"
#include "shellApp.h"
#include "taskSwch.h"
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "dvxMem.h"
// ============================================================
// Module state
// ============================================================
#define CLOCK_WIN_W 200
#define CLOCK_FACE_H 100 // window height without the menu bar
#define CLOCK_WIN_H (CLOCK_FACE_H + CHROME_MENU_HEIGHT)
#define CLOCK_MARGIN 40
#define CLOCK_DATE_GAP 8
#define CLOCK_CASCADE 16 // offset between successive clock windows
#define CLOCK_TITLE "Clock"
// Menu command IDs
#define CMD_HELP_CONTENTS 100
#define CMD_HELP_ABOUT 101
typedef struct {
bool quit;
char timeStr[32];
char dateStr[32];
time_t lastUpdate;
} ClockStateT;
static DxeAppContextT *sCtx = NULL;
static WindowT *sWin = NULL;
static ClockStateT sState;
// ============================================================
// Prototypes
// ============================================================
void appShutdown(void);
static int32_t countClockWindows(const AppContextT *ac);
static void onClose(WindowT *win);
static void onMenu(WindowT *win, int32_t menuId);
static void onPaint(WindowT *win, RectT *dirty);
static void updateTime(void);
int32_t appMain(DxeAppContextT *ctx);
// ============================================================
// App descriptor
// ============================================================
// hasMainLoop = true tells the shell to create a dedicated task for appMain.
// TS_PRIORITY_LOW because clock updates are cosmetic and should never
// preempt interactive apps or the shell's event processing.
SHELL_APP_DESCRIPTOR(
.name = "Clock",
.hasMainLoop = true,
.multiInstance = true,
.stackSize = SHELL_STACK_DEFAULT,
.priority = TS_PRIORITY_LOW
);
// The shell calls appShutdown (if exported) when force-killing an app via
// Task Manager or during shell shutdown. For main-loop apps, this is the
// signal to break out of the main loop. Without this, shellForceKillApp
// would have to terminate the task forcibly, potentially leaking resources.
void appShutdown(void) {
sState.quit = true;
}
// Clock windows already open (from other instances), used to cascade a new
// instance instead of opening it exactly on top of the previous one.
static int32_t countClockWindows(const AppContextT *ac) {
int32_t n = 0;
for (int32_t i = 0; i < ac->stack.count; i++) {
if (strcmp(ac->stack.windows[i]->title, CLOCK_TITLE) == 0) {
n++;
}
}
return n;
}
// Setting quit = true tells the main loop (running in a separate task) to
// exit. The main loop then destroys the window and returns from appMain,
// which causes the shell to reap this task and unload the DXE.
static void onClose(WindowT *win) {
(void)win;
sState.quit = true;
}
// Menu callbacks run in the shell's task (like onPaint), not in the
// clock's main-loop task.
static void onMenu(WindowT *win, int32_t menuId) {
(void)win;
switch (menuId) {
case CMD_HELP_CONTENTS:
shellOpenHelp(sCtx->shellCtx, sCtx->helpFile);
break;
case CMD_HELP_ABOUT:
dvxInfoBox(sCtx->shellCtx, "About Clock", "Clock\n\nShows the current time and date.\n\nCopyright 2026 Scott Duensing");
break;
default:
break;
}
}
// onPaint demonstrates the raw paint callback approach (no widget tree).
// Instead of using wgtInitWindow + widgets, this app renders directly into
// the window's content buffer. This is the lower-level alternative to the
// widget system -- useful for custom rendering like this centered clock display.
//
// dvxContentDisplay gives a DisplayT aimed at the window's content buffer,
// so the standard drawing primitives (rectFill, drawText) draw into the
// window content without touching the global display state.
static void onPaint(WindowT *win, RectT *dirty) {
(void)dirty;
AppContextT *ac = sCtx->shellCtx;
const BlitOpsT *ops = dvxGetBlitOps(ac);
const BitmapFontT *font = dvxGetFont(ac);
const ColorSchemeT *colors = dvxGetColors(ac);
DisplayT cd;
dvxContentDisplay(ac, win, &cd);
// Background
rectFill(&cd, ops, 0, 0, win->contentW, win->contentH, colors->contentBg);
// Time string (large, centered)
int32_t timeW = textWidth(font, sState.timeStr);
int32_t timeX = (win->contentW - timeW) / 2;
int32_t timeY = win->contentH / 3;
drawText(&cd, ops, font, timeX, timeY, sState.timeStr, colors->contentFg, colors->contentBg, true);
// Date string (centered below)
int32_t dateW = textWidth(font, sState.dateStr);
int32_t dateX = (win->contentW - dateW) / 2;
int32_t dateY = timeY + font->charHeight + CLOCK_DATE_GAP;
drawText(&cd, ops, font, dateX, dateY, sState.dateStr, colors->contentFg, colors->contentBg, true);
}
static void updateTime(void) {
time_t now = time(NULL);
struct tm *tm = localtime(&now);
if (!tm) {
snprintf(sState.timeStr, sizeof(sState.timeStr), "--:--:--");
snprintf(sState.dateStr, sizeof(sState.dateStr), "----------");
sState.lastUpdate = now;
return;
}
int32_t hour12 = tm->tm_hour % 12;
const char *ampm = (tm->tm_hour < 12) ? "AM" : "PM";
if (hour12 == 0) {
hour12 = 12;
}
snprintf(sState.timeStr, sizeof(sState.timeStr), "%d:%02d:%02d %s", (int)hour12, tm->tm_min, tm->tm_sec, ampm);
snprintf(sState.dateStr, sizeof(sState.dateStr), "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
sState.lastUpdate = now;
}
// This runs in its own task. The shell creates the task before calling
// appMain, and reaps it when appMain returns.
int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx;
AppContextT *ac = ctx->shellCtx;
memset(&sState, 0, sizeof(sState));
updateTime();
// Position in the upper-right corner, out of the way of other windows,
// cascading down and left for each clock already open (back to the
// corner once the cascade would leave the screen).
int32_t winW = CLOCK_WIN_W;
int32_t winH = CLOCK_WIN_H;
int32_t offset = countClockWindows(ac) * CLOCK_CASCADE;
int32_t winX = ac->display.width - winW - CLOCK_MARGIN - offset;
int32_t winY = CLOCK_MARGIN + offset;
if (winX < 0 || winY + winH > ac->display.height) {
winX = ac->display.width - winW - CLOCK_MARGIN;
winY = CLOCK_MARGIN;
}
// resizable=false: clock has a fixed size, no resize handle
sWin = dvxCreateWindow(ac, CLOCK_TITLE, winX, winY, winW, winH, false);
if (!sWin) {
return -1;
}
sWin->onClose = onClose;
sWin->onMenu = onMenu;
sWin->onPaint = onPaint;
MenuBarT *menuBar = wmAddMenuBar(sWin);
MenuT *helpMenu = menuBar ? wmAddMenu(menuBar, "&Help") : NULL;
if (helpMenu) {
wmAddMenuItem(helpMenu, "&Contents\tF1", CMD_HELP_CONTENTS);
wmAddMenuSeparator(helpMenu);
wmAddMenuItem(helpMenu, "&About Clock...", CMD_HELP_ABOUT);
}
// A raw-paint window must resize its content buffer itself after the
// menu bar shrinks the content area.
wmReallocContentBuf(sWin, &ac->display);
// Main loop: check if the second has changed, repaint if so, then yield.
// First paint happens automatically on the next dvxUpdate frame.
// tsYield() transfers control back to the shell's task scheduler.
// On a 486, time() resolution is 1 second, so we yield many times per
// second between actual updates -- this keeps CPU usage near zero.
// dvxInvalidateWindow marks the window dirty and calls onPaint to
// update the content buffer before the compositor flushes it.
while (!sState.quit) {
time_t now = time(NULL);
if (now != sState.lastUpdate) {
updateTime();
dvxInvalidateWindow(ac, sWin);
}
tsYield();
}
dvxDestroyWindow(ac, sWin);
sWin = NULL;
return 0;
}

View file

@ -0,0 +1,21 @@
[Project]
Name = Clock
Author = Scott Duensing
Publisher = Kangaroo Punch Studios
Version = 1.00
Copyright = Copyright 2026 Scott Duensing
Description = Digital clock with date display
Icon = ICON32.BMP
[Modules]
File0 = ../../../include/basic/help.bas
File1 = ../../../include/basic/shell.bas
Count = 2
[Forms]
File0 = clock.frm
Count = 1
[Settings]
StartupForm = ClockForm
MultiInstance = True

View file

@ -0,0 +1,151 @@
VERSION DVX 1.00
Begin Form ClockForm
Caption = "Clock"
Layout = VBox
AutoSize = False
Resizable = False
Centered = False
Width = 200
Height = 120
Begin Menu mnuHelp
Caption = "&Help"
Begin Menu mnuHelpContents
Caption = "&Contents"
ShortcutText = "F1"
End
Begin Menu mnuHelpSep
Caption = "-"
End
Begin Menu mnuAbout
Caption = "&About Clock..."
End
End
Begin Spacer spcTop
Height = 12
Weight = 0
End
Begin Label lblTime
Caption = ""
Alignment = Center
Height = 20
End
Begin Label lblDate
Caption = ""
Alignment = Center
Height = 20
End
Begin Timer tmrTick
Interval = 100
Enabled = True
End
End
' The MIT License (MIT)
'
' Copyright (C) 2026 Scott Duensing
'
' Permission is hereby granted, free of charge, to any person obtaining a copy
' of this software and associated documentation files (the "Software"), to
' deal in the Software without restriction, including without limitation the
' rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
' sell copies of the Software, and to permit persons to whom the Software is
' furnished to do so, subject to the following conditions:
'
' The above copyright notice and this permission notice shall be included in
' all copies or substantial portions of the Software.
'
' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
' IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
' FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
' AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
' LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
' FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
' IN THE SOFTWARE.
' clock.frm -- the Clock: the time and today's date in a small window.
'
' The window has the C Clock's geometry: 200 x 120 with a menu bar, so the
' face below it is 188 x 68 pixels. The time is drawn a third of the way
' down (y 22) and the date 8 pixels below the time's line (y 46), both
' centred: a 12 pixel spacer and two labels 20 pixels high (the text sits
' 2 pixels into each) inside the form's 4 pixel padding and spacing fill
' the face exactly. A timer looks at the clock ten times a second and
' changes the labels when the second has changed.
'
' More than one Clock may run (MultiInstance in clock.dbp); each new one
' opens a little lower and further left than the one before.
OPTION EXPLICIT
CONST HELP_FILE = "clock.hlp"
' Placement: the upper right corner, CLOCK_MARGIN pixels from the edges,
' and CLOCK_CASCADE pixels down and left for every Clock already open.
CONST CLOCK_MARGIN = 40
CONST CLOCK_CASCADE = 16
CONST TIME_FORMAT = "h:nn:ss AM/PM"
CONST DATE_FORMAT = "yyyy-mm-dd"
SUB ClockForm_Load
DIM offset AS LONG
DIM winX AS LONG
DIM winY AS LONG
' Cascade from the corner; back to the corner once the cascade would
' leave the screen.
offset = (ShellInstanceCount() - 1) * CLOCK_CASCADE
winX = Screen.Width - ClockForm.Width - CLOCK_MARGIN - offset
winY = CLOCK_MARGIN + offset
IF winX < 0 OR winY + ClockForm.Height > Screen.Height THEN
winX = Screen.Width - ClockForm.Width - CLOCK_MARGIN
winY = CLOCK_MARGIN
END IF
ClockForm.Left = winX
ClockForm.Top = winY
ShowTime
END SUB
' Puts the current time and date on the face; a label is only changed when
' its text has, so the window is redrawn once a second, not at every tick.
SUB ShowTime
DIM timeText AS STRING
DIM dateText AS STRING
timeText = Format$(Now, TIME_FORMAT)
dateText = Format$(Now, DATE_FORMAT)
IF lblTime.Caption <> timeText THEN
lblTime.Caption = timeText
END IF
IF lblDate.Caption <> dateText THEN
lblDate.Caption = dateText
END IF
END SUB
SUB tmrTick_Timer
ShowTime
END SUB
SUB mnuHelpContents_Click
HelpView HELP_FILE
END SUB
SUB mnuAbout_Click
DIM msg AS STRING
msg = "Clock" + CHR$(10) + CHR$(10)
msg = msg + "Shows the current time and date." + CHR$(10) + CHR$(10)
msg = msg + "Copyright 2026 Scott Duensing"
MsgBox msg, vbOKOnly + vbInformation, "About Clock"
END SUB

View file

@ -1,7 +0,0 @@
# clock.res -- Resource manifest for Clock
icon32 icon icon32.bmp
name text "Clock"
author text "Scott Duensing"
copyright text "Copyright 2026 Scott Duensing"
publisher text "Kangaroo Punch Studios"
description text "Digital clock with date display"

View file

@ -49,7 +49,6 @@
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <strings.h>
#define CPJ_LABEL_W 90 #define CPJ_LABEL_W 90
#define CPJ_GAP 4 #define CPJ_GAP 4
@ -81,11 +80,16 @@
// The joystick library's functions (NULL when it is not installed) // The joystick library's functions (NULL when it is not installed)
typedef struct { typedef struct {
__typeof__(joyChoiceGet) *choiceGet;
__typeof__(joyChoices) *choices;
__typeof__(joyChoiceSet) *choiceSet;
__typeof__(joyClose) *close; __typeof__(joyClose) *close;
__typeof__(joyCount) *count; __typeof__(joyCount) *count;
__typeof__(joyGetDeadZone) *getDeadZone; __typeof__(joyGetDeadZone) *getDeadZone;
__typeof__(joyGetInfo) *getInfo; __typeof__(joyGetInfo) *getInfo;
__typeof__(joyGetRaw) *getRaw; __typeof__(joyGetRaw) *getRaw;
__typeof__(joyKbdSlotGet) *slotGet;
__typeof__(joyKbdSlotSet) *slotSet;
__typeof__(joyKeyCount) *keyCount; __typeof__(joyKeyCount) *keyCount;
__typeof__(joyKeyName) *keyName; __typeof__(joyKeyName) *keyName;
__typeof__(joyOpen) *open; __typeof__(joyOpen) *open;
@ -93,8 +97,6 @@ typedef struct {
__typeof__(joyRescan) *rescan; __typeof__(joyRescan) *rescan;
__typeof__(joySetCalibration) *setCal; __typeof__(joySetCalibration) *setCal;
__typeof__(joySetDeadZone) *setDeadZone; __typeof__(joySetDeadZone) *setDeadZone;
__typeof__(joySettingGet) *settingGet;
__typeof__(joySettingSet) *settingSet;
__typeof__(joySettingsLoad) *load; __typeof__(joySettingsLoad) *load;
__typeof__(joySettingsStore) *store; __typeof__(joySettingsStore) *store;
} JoyFnsT; } JoyFnsT;
@ -128,11 +130,16 @@ typedef struct {
#define JOY_FN_SYM(field, fn) { DVX_SYM(#fn), offsetof(JoyFnsT, field) } #define JOY_FN_SYM(field, fn) { DVX_SYM(#fn), offsetof(JoyFnsT, field) }
static const JoyFnSymT sSyms[] = { static const JoyFnSymT sSyms[] = {
JOY_FN_SYM(choiceGet, joyChoiceGet),
JOY_FN_SYM(choices, joyChoices),
JOY_FN_SYM(choiceSet, joyChoiceSet),
JOY_FN_SYM(close, joyClose), JOY_FN_SYM(close, joyClose),
JOY_FN_SYM(count, joyCount), JOY_FN_SYM(count, joyCount),
JOY_FN_SYM(getDeadZone, joyGetDeadZone), JOY_FN_SYM(getDeadZone, joyGetDeadZone),
JOY_FN_SYM(getInfo, joyGetInfo), JOY_FN_SYM(getInfo, joyGetInfo),
JOY_FN_SYM(getRaw, joyGetRaw), JOY_FN_SYM(getRaw, joyGetRaw),
JOY_FN_SYM(slotGet, joyKbdSlotGet),
JOY_FN_SYM(slotSet, joyKbdSlotSet),
JOY_FN_SYM(keyCount, joyKeyCount), JOY_FN_SYM(keyCount, joyKeyCount),
JOY_FN_SYM(keyName, joyKeyName), JOY_FN_SYM(keyName, joyKeyName),
JOY_FN_SYM(open, joyOpen), JOY_FN_SYM(open, joyOpen),
@ -140,23 +147,12 @@ static const JoyFnSymT sSyms[] = {
JOY_FN_SYM(rescan, joyRescan), JOY_FN_SYM(rescan, joyRescan),
JOY_FN_SYM(setCal, joySetCalibration), JOY_FN_SYM(setCal, joySetCalibration),
JOY_FN_SYM(setDeadZone, joySetDeadZone), JOY_FN_SYM(setDeadZone, joySetDeadZone),
JOY_FN_SYM(settingGet, joySettingGet),
JOY_FN_SYM(settingSet, joySettingSet),
JOY_FN_SYM(load, joySettingsLoad), JOY_FN_SYM(load, joySettingsLoad),
JOY_FN_SYM(store, joySettingsStore) JOY_FN_SYM(store, joySettingsStore)
}; };
// [joystick] gameport.mode and fallback: what the dropdowns show and store
static const char *sModeLabels[] = { "Two joysticks", "One joystick with 4 axes" };
static const char *sModeValues[] = { JOY_GAMEPORT_TWO, JOY_GAMEPORT_FOUR };
static const char *sFallbackLabels[] = { "When no joystick is found", "Always", "Never" };
static const char *sFallbackValues[] = { JOY_FALLBACK_AUTO, JOY_FALLBACK_ALWAYS, JOY_FALLBACK_NEVER };
static const char *const sAxisNames[CPJ_SHOWN_AXES] = { "X", "Y", "3", "4" }; static const char *const sAxisNames[CPJ_SHOWN_AXES] = { "X", "Y", "3", "4" };
static const char *const sSlotNames[JOY_KBD_SLOT_COUNT] = { JOY_KBD_SLOT_NAMES };
static const char *const sSlotDefaults[JOY_KBD_SLOT_COUNT] = { JOY_KBD_SLOT_DEFAULTS };
static const char *const sSlotLabels[JOY_KBD_SLOT_COUNT] = { "Up:", "Down:", "Left:", "Right:", "Button 1:", "Button 2:", "Button 3:", "Button 4:" }; static const char *const sSlotLabels[JOY_KBD_SLOT_COUNT] = { "Up:", "Down:", "Left:", "Right:", "Button 1:", "Button 2:", "Button 3:", "Button 4:" };
static const char *const sWizText[] = { static const char *const sWizText[] = {
@ -182,6 +178,8 @@ static WidgetT *sModeDrop = NULL;
static WidgetT *sKbdDrop = NULL; static WidgetT *sKbdDrop = NULL;
static WidgetT *sKeyDrops[JOY_KBD_SLOT_COUNT]; static WidgetT *sKeyDrops[JOY_KBD_SLOT_COUNT];
static const char **sKeyItems = NULL; // stb_ds array of the key names static const char **sKeyItems = NULL; // stb_ds array of the key names
static const char *sModeLabels[JOY_GAMEPORT_CHOICE_COUNT];
static const char *sFallbackLabels[JOY_FALLBACK_CHOICE_COUNT];
static int32_t sDevice = -1; static int32_t sDevice = -1;
static int32_t sHandle = -1; static int32_t sHandle = -1;
static JoyStateT sShown; static JoyStateT sShown;
@ -196,12 +194,13 @@ static CalWizT sWiz;
static WidgetT *addRow(WidgetT *parent, const char *label); static WidgetT *addRow(WidgetT *parent, const char *label);
static int32_t axisPixel(int32_t value, int32_t size); static int32_t axisPixel(int32_t value, int32_t size);
static void choiceDrop(WidgetT *drop, JoyChoiceE which, const char **labels);
void cpJoystickBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs); void cpJoystickBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs);
void cpJoystickFree(void); void cpJoystickFree(void);
void cpJoystickRevert(void); void cpJoystickRevert(void);
void cpJoystickStore(PrefsHandleT *prefs); void cpJoystickStore(PrefsHandleT *prefs);
static void drawView(void); static void drawView(void);
static int32_t findValue(const char *const *values, int32_t count, const char *value); static bool joyStateEqual(const JoyStateT *a, const JoyStateT *b);
static void onCalibrate(WidgetT *w); static void onCalibrate(WidgetT *w);
static void onDetect(WidgetT *w); static void onDetect(WidgetT *w);
static void onDevice(WidgetT *w); static void onDevice(WidgetT *w);
@ -219,7 +218,6 @@ static void refreshDevices(void);
static bool resolveLibrary(void); static bool resolveLibrary(void);
static void selectDevice(int32_t device); static void selectDevice(int32_t device);
static void showZone(void); static void showZone(void);
static void slotKey(int32_t slot, char *out, int32_t outSize);
static bool wizAxis(int32_t axis); static bool wizAxis(int32_t axis);
@ -240,6 +238,17 @@ static int32_t axisPixel(int32_t value, int32_t size) {
} }
// A listed [joystick] setting's choices in drop, its current one chosen.
static void choiceDrop(WidgetT *drop, JoyChoiceE which, const char **labels) {
int32_t count = 0;
const PrefsChoiceT *choices = sJoy.choices(which, &count);
prefsChoiceLabels(choices, count, labels);
wgtDropdownSetItems(drop, labels, count);
wgtDropdownSetSelected(drop, sJoy.choiceGet(which));
}
void cpJoystickBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) { void cpJoystickBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
sAc = ac; sAc = ac;
sPrefs = prefs; sPrefs = prefs;
@ -282,14 +291,12 @@ void cpJoystickBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
row = addRow(page, "Game port:"); row = addRow(page, "Game port:");
sModeDrop = wgtDropdown(row); sModeDrop = wgtDropdown(row);
sModeDrop->onChange = onMode; sModeDrop->onChange = onMode;
wgtDropdownSetItems(sModeDrop, sModeLabels, DVX_ARRAY_LEN(sModeLabels)); choiceDrop(sModeDrop, JoyChoiceGamePortE, sModeLabels);
wgtDropdownSetSelected(sModeDrop, findValue(sModeValues, DVX_ARRAY_LEN(sModeValues), sJoy.settingGet(JOY_KEY_GAMEPORT_MODE, JOY_GAMEPORT_TWO)));
row = addRow(page, "Keyboard:"); row = addRow(page, "Keyboard:");
sKbdDrop = wgtDropdown(row); sKbdDrop = wgtDropdown(row);
sKbdDrop->onChange = onFallback; sKbdDrop->onChange = onFallback;
wgtDropdownSetItems(sKbdDrop, sFallbackLabels, DVX_ARRAY_LEN(sFallbackLabels)); choiceDrop(sKbdDrop, JoyChoiceFallbackE, sFallbackLabels);
wgtDropdownSetSelected(sKbdDrop, findValue(sFallbackValues, DVX_ARRAY_LEN(sFallbackValues), sJoy.settingGet(JOY_KEY_FALLBACK, JOY_FALLBACK_AUTO)));
WidgetT *keys = wgtButton(row, "Keys..."); WidgetT *keys = wgtButton(row, "Keys...");
@ -404,15 +411,10 @@ static void drawView(void) {
} }
// The index of value in values (case-insensitive), 0 when not there. // Two states the view draws alike, compared field by field: the struct
static int32_t findValue(const char *const *values, int32_t count, const char *value) { // ends in a bool, and memcmp would compare its padding too.
for (int32_t i = 0; i < count; i++) { static bool joyStateEqual(const JoyStateT *a, const JoyStateT *b) {
if (strcasecmp(values[i], value) == 0) { return memcmp(a->axes, b->axes, sizeof(a->axes)) == 0 && memcmp(a->hats, b->hats, sizeof(a->hats)) == 0 && a->buttons == b->buttons && a->connected == b->connected;
return i;
}
}
return 0;
} }
@ -462,7 +464,8 @@ static void onCalibrate(WidgetT *w) {
} }
// Look for controllers again. The page's own handle is closed first; // Look for controllers again. The page's own handle is closed first
// (with every handle of the Control Panel);
// another program's handle stops the search. // another program's handle stops the search.
static void onDetect(WidgetT *w) { static void onDetect(WidgetT *w) {
(void)w; (void)w;
@ -483,15 +486,13 @@ static void onDevice(WidgetT *w) {
static void onFallback(WidgetT *w) { static void onFallback(WidgetT *w) {
sJoy.settingSet(JOY_KEY_FALLBACK, sFallbackValues[wgtDropdownGetSelected(w)]); sJoy.choiceSet(JoyChoiceFallbackE, wgtDropdownGetSelected(w));
onDetect(w); onDetect(w);
} }
// One dropdown of key names per keyboard joystick key. // One dropdown of key names per keyboard joystick key.
static void onKeys(WidgetT *w) { static void onKeys(WidgetT *w) {
char key[CPJ_KEY_BUF];
(void)w; (void)w;
if (!sKeyItems) { if (!sKeyItems) {
@ -507,10 +508,9 @@ static void onKeys(WidgetT *w) {
} }
for (int32_t i = 0; i < JOY_KBD_SLOT_COUNT; i++) { for (int32_t i = 0; i < JOY_KBD_SLOT_COUNT; i++) {
slotKey(i, key, sizeof(key));
sKeyDrops[i] = wgtDropdown(addRow(dlg->widgetRoot, sSlotLabels[i])); sKeyDrops[i] = wgtDropdown(addRow(dlg->widgetRoot, sSlotLabels[i]));
wgtDropdownSetItems(sKeyDrops[i], sKeyItems, (int32_t)arrlen(sKeyItems)); wgtDropdownSetItems(sKeyDrops[i], sKeyItems, (int32_t)arrlen(sKeyItems));
wgtDropdownSetSelected(sKeyDrops[i], findValue(sKeyItems, (int32_t)arrlen(sKeyItems), sJoy.settingGet(key, sSlotDefaults[i]))); wgtDropdownSetSelected(sKeyDrops[i], sJoy.slotGet(i));
} }
dvxDialogAddOkCancel(dlg, "OK", onKeysOk, NULL); dvxDialogAddOkCancel(dlg, "OK", onKeysOk, NULL);
@ -519,14 +519,11 @@ static void onKeys(WidgetT *w) {
static bool onKeysOk(WindowT *dlg, void *userCtx) { static bool onKeysOk(WindowT *dlg, void *userCtx) {
char key[CPJ_KEY_BUF];
(void)dlg; (void)dlg;
(void)userCtx; (void)userCtx;
for (int32_t i = 0; i < JOY_KBD_SLOT_COUNT; i++) { for (int32_t i = 0; i < JOY_KBD_SLOT_COUNT; i++) {
slotKey(i, key, sizeof(key)); sJoy.slotSet(i, wgtDropdownGetSelected(sKeyDrops[i]));
sJoy.settingSet(key, sKeyItems[wgtDropdownGetSelected(sKeyDrops[i])]);
} }
return true; return true;
@ -535,7 +532,7 @@ static bool onKeysOk(WindowT *dlg, void *userCtx) {
// The game port makes its joysticks anew in the other mode. // The game port makes its joysticks anew in the other mode.
static void onMode(WidgetT *w) { static void onMode(WidgetT *w) {
sJoy.settingSet(JOY_KEY_GAMEPORT_MODE, sModeValues[wgtDropdownGetSelected(w)]); sJoy.choiceSet(JoyChoiceGamePortE, wgtDropdownGetSelected(w));
onDetect(w); onDetect(w);
} }
@ -556,7 +553,7 @@ static void onTick(WidgetT *w) {
(void)w; (void)w;
if (!sJoy.read(sHandle, &st) || (sShownValid && memcmp(&st, &sShown, sizeof(st)) == 0)) { if (!sJoy.read(sHandle, &st) || (sShownValid && joyStateEqual(&st, &sShown))) {
return; return;
} }
@ -734,12 +731,6 @@ static void showZone(void) {
} }
// The [joystick] key of a keyboard joystick slot.
static void slotKey(int32_t slot, char *out, int32_t outSize) {
snprintf(out, (size_t)outSize, "%s%s", JOY_KBD_KEY_PREFIX, sSlotNames[slot]);
}
// Axis a of the controller being calibrated answers. // Axis a of the controller being calibrated answers.
static bool wizAxis(int32_t axis) { static bool wizAxis(int32_t axis) {
return (sWiz.info.axisPresent & (1U << axis)) != 0; return (sWiz.info.axisPresent & (1U << axis)) != 0;

View file

@ -61,6 +61,7 @@
// The library's functions (NULL when it is not installed) // The library's functions (NULL when it is not installed)
typedef struct { typedef struct {
__typeof__(netConfigEqual) *equal;
__typeof__(netConfigFromPrefs) *fromPrefs; __typeof__(netConfigFromPrefs) *fromPrefs;
__typeof__(netConfigToPrefs) *toPrefs; __typeof__(netConfigToPrefs) *toPrefs;
__typeof__(netConfigProblem) *problem; __typeof__(netConfigProblem) *problem;
@ -129,7 +130,7 @@ void cpNetApply(void) {
readPage(&cfg); readPage(&cfg);
if (memcmp(&cfg, &sOriginal, sizeof(cfg)) != 0) { if (!sNet.equal(&cfg, &sOriginal)) {
sNet.restart(); sNet.restart();
} }
} }
@ -141,13 +142,14 @@ void cpNetBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
char label[CN_LINE_MAX]; char label[CN_LINE_MAX];
sAc = ac; sAc = ac;
sNet.equal = (__typeof__(sNet.equal))dlsym(RTLD_DEFAULT, DVX_SYM("netConfigEqual"));
sNet.fromPrefs = (__typeof__(sNet.fromPrefs))dlsym(RTLD_DEFAULT, DVX_SYM("netConfigFromPrefs")); sNet.fromPrefs = (__typeof__(sNet.fromPrefs))dlsym(RTLD_DEFAULT, DVX_SYM("netConfigFromPrefs"));
sNet.toPrefs = (__typeof__(sNet.toPrefs))dlsym(RTLD_DEFAULT, DVX_SYM("netConfigToPrefs")); sNet.toPrefs = (__typeof__(sNet.toPrefs))dlsym(RTLD_DEFAULT, DVX_SYM("netConfigToPrefs"));
sNet.problem = (__typeof__(sNet.problem))dlsym(RTLD_DEFAULT, DVX_SYM("netConfigProblem")); sNet.problem = (__typeof__(sNet.problem))dlsym(RTLD_DEFAULT, DVX_SYM("netConfigProblem"));
sNet.status = (__typeof__(sNet.status))dlsym(RTLD_DEFAULT, DVX_SYM("netGetStatus")); sNet.status = (__typeof__(sNet.status))dlsym(RTLD_DEFAULT, DVX_SYM("netGetStatus"));
sNet.restart = (__typeof__(sNet.restart))dlsym(RTLD_DEFAULT, DVX_SYM("netRestart")); sNet.restart = (__typeof__(sNet.restart))dlsym(RTLD_DEFAULT, DVX_SYM("netRestart"));
if (!sNet.fromPrefs || !sNet.toPrefs || !sNet.problem || !sNet.status || !sNet.restart) { if (!sNet.equal || !sNet.fromPrefs || !sNet.toPrefs || !sNet.problem || !sNet.status || !sNet.restart) {
memset(&sNet, 0, sizeof(sNet)); memset(&sNet, 0, sizeof(sNet));
wgtLabel(page, "Networking is not installed."); wgtLabel(page, "Networking is not installed.");
return; return;

View file

@ -38,7 +38,6 @@
#include "textInput/textInpt.h" #include "textInput/textInpt.h"
#include <dlfcn.h> #include <dlfcn.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#define CPR_LABEL_W 110 #define CPR_LABEL_W 110
@ -48,9 +47,9 @@
// The library's functions and tables (NULL when it is not installed) // The library's functions and tables (NULL when it is not installed)
typedef struct { typedef struct {
__typeof__(printSettingsLoad) *load;
__typeof__(printSettingsRead) *read; __typeof__(printSettingsRead) *read;
__typeof__(printSettingsWrite) *write; __typeof__(printDeviceProblem) *problem;
__typeof__(printDeviceStore) *store;
__typeof__(printPageSetupDialog) *pageSetup; __typeof__(printPageSetupDialog) *pageSetup;
__typeof__(printDestLabel) *destLabel; __typeof__(printDestLabel) *destLabel;
__typeof__(printModelLabel) *modelLabel; __typeof__(printModelLabel) *modelLabel;
@ -91,14 +90,14 @@ void cpPrinterBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
PrintSettingsT s; PrintSettingsT s;
sAc = ac; sAc = ac;
sPrint.load = (__typeof__(sPrint.load))dlsym(RTLD_DEFAULT, DVX_SYM("printSettingsLoad"));
sPrint.read = (__typeof__(sPrint.read))dlsym(RTLD_DEFAULT, DVX_SYM("printSettingsRead")); sPrint.read = (__typeof__(sPrint.read))dlsym(RTLD_DEFAULT, DVX_SYM("printSettingsRead"));
sPrint.write = (__typeof__(sPrint.write))dlsym(RTLD_DEFAULT, DVX_SYM("printSettingsWrite")); sPrint.problem = (__typeof__(sPrint.problem))dlsym(RTLD_DEFAULT, DVX_SYM("printDeviceProblem"));
sPrint.store = (__typeof__(sPrint.store))dlsym(RTLD_DEFAULT, DVX_SYM("printDeviceStore"));
sPrint.pageSetup = (__typeof__(sPrint.pageSetup))dlsym(RTLD_DEFAULT, DVX_SYM("printPageSetupDialog")); sPrint.pageSetup = (__typeof__(sPrint.pageSetup))dlsym(RTLD_DEFAULT, DVX_SYM("printPageSetupDialog"));
sPrint.destLabel = (__typeof__(sPrint.destLabel))dlsym(RTLD_DEFAULT, DVX_SYM("printDestLabel")); sPrint.destLabel = (__typeof__(sPrint.destLabel))dlsym(RTLD_DEFAULT, DVX_SYM("printDestLabel"));
sPrint.modelLabel = (__typeof__(sPrint.modelLabel))dlsym(RTLD_DEFAULT, DVX_SYM("printModelLabel")); sPrint.modelLabel = (__typeof__(sPrint.modelLabel))dlsym(RTLD_DEFAULT, DVX_SYM("printModelLabel"));
if (!sPrint.load || !sPrint.read || !sPrint.write || !sPrint.pageSetup || !sPrint.destLabel || !sPrint.modelLabel) { if (!sPrint.read || !sPrint.problem || !sPrint.store || !sPrint.pageSetup || !sPrint.destLabel || !sPrint.modelLabel) {
memset(&sPrint, 0, sizeof(sPrint)); memset(&sPrint, 0, sizeof(sPrint));
wgtLabel(page, "Printing is not installed."); wgtLabel(page, "Printing is not installed.");
return; return;
@ -136,29 +135,22 @@ void cpPrinterBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
const char *cpPrinterProblem(void) { const char *cpPrinterProblem(void) {
if (sPrint.load && wgtDropdownGetSelected(sDest) == PrintDestFileE && !wgtGetText(sFile)[0]) { if (!sPrint.problem) {
return "Type the name of the file to print to, or connect the printer to a port.";
}
return NULL; return NULL;
} }
return sPrint.problem((PrintDestE)wgtDropdownGetSelected(sDest), wgtGetText(sFile));
}
// The page setup comes from DVX.INI as it is now (Page Setup... saves it
// there at once), so prefs, read when the Control Panel opened, does not // The library keeps the page setup as it is in DVX.INI now (Page
// put back an older one. // Setup... saves it there at once).
void cpPrinterStore(PrefsHandleT *prefs) { void cpPrinterStore(PrefsHandleT *prefs) {
PrintSettingsT s; if (!sPrint.store) {
if (!sPrint.load) {
return; return;
} }
sPrint.load(&s); sPrint.store(prefs, (PrintModelE)wgtDropdownGetSelected(sModel), (PrintDestE)wgtDropdownGetSelected(sDest), wgtGetText(sFile));
s.model = (PrintModelE)wgtDropdownGetSelected(sModel);
s.dest = (PrintDestE)wgtDropdownGetSelected(sDest);
snprintf(s.filePath, sizeof(s.filePath), "%s", wgtGetText(sFile));
sPrint.write(prefs, &s);
} }

View file

@ -56,20 +56,26 @@
#define CPS_LIST_H 148 // the event list: nine rows #define CPS_LIST_H 148 // the event list: nine rows
#define CPS_VOL_LBL_W 40 #define CPS_VOL_LBL_W 40
#define CPS_TEXT_BUF 128 #define CPS_TEXT_BUF 128
#define CPS_DRIVERS_MAX 16 #define CPS_DRV_ITEMS (AUDIO_DRIVERS_MAX + 2) // Automatic, the drivers, DVX.INI's own choice
#define CPS_DRV_ITEMS (CPS_DRIVERS_MAX + 2) // Automatic, the drivers, DVX.INI's own choice
#define PERCENT_MAX 100 #define PERCENT_MAX 100
// The sound library's functions (NULL when it is not installed) // The sound library's functions (NULL when it is not installed)
typedef struct { typedef struct {
__typeof__(audioDeskKey) *key; __typeof__(audioDeskFile) *file;
__typeof__(audioDeskLabel) *label; __typeof__(audioDeskLabel) *label;
__typeof__(audioDeskPlayFile) *playFile; __typeof__(audioDeskPlayFile) *playFile;
__typeof__(audioDeskReload) *reload; __typeof__(audioDeskReload) *reload;
__typeof__(audioDeskSetFile) *setFile;
__typeof__(audioDriverNames) *driverNames; __typeof__(audioDriverNames) *driverNames;
__typeof__(audioErrorText) *errorText; __typeof__(audioErrorText) *errorText;
__typeof__(audioGetInfo) *getInfo; __typeof__(audioGetInfo) *getInfo;
const PrefsChoiceT *midiChoices;
__typeof__(audioPercentToVolume) *toVolume;
__typeof__(audioSetMasterVolume) *setVolume; __typeof__(audioSetMasterVolume) *setVolume;
__typeof__(audioSettingDriver) *driver;
__typeof__(audioSettingMidi) *midi;
__typeof__(audioSettingsStore) *store;
__typeof__(audioSettingVolume) *volume;
} SndFnsT; } SndFnsT;
typedef struct { typedef struct {
@ -80,23 +86,26 @@ typedef struct {
#define SND_FN_SYM(field, fn) { DVX_SYM(#fn), offsetof(SndFnsT, field) } #define SND_FN_SYM(field, fn) { DVX_SYM(#fn), offsetof(SndFnsT, field) }
static const SndFnSymT sSyms[] = { static const SndFnSymT sSyms[] = {
SND_FN_SYM(key, audioDeskKey), SND_FN_SYM(file, audioDeskFile),
SND_FN_SYM(label, audioDeskLabel), SND_FN_SYM(label, audioDeskLabel),
SND_FN_SYM(playFile, audioDeskPlayFile), SND_FN_SYM(playFile, audioDeskPlayFile),
SND_FN_SYM(reload, audioDeskReload), SND_FN_SYM(reload, audioDeskReload),
SND_FN_SYM(setFile, audioDeskSetFile),
SND_FN_SYM(driverNames, audioDriverNames), SND_FN_SYM(driverNames, audioDriverNames),
SND_FN_SYM(errorText, audioErrorText), SND_FN_SYM(errorText, audioErrorText),
SND_FN_SYM(getInfo, audioGetInfo), SND_FN_SYM(getInfo, audioGetInfo),
SND_FN_SYM(setVolume, audioSetMasterVolume) SND_FN_SYM(midiChoices, audioMidiChoices),
SND_FN_SYM(toVolume, audioPercentToVolume),
SND_FN_SYM(setVolume, audioSetMasterVolume),
SND_FN_SYM(driver, audioSettingDriver),
SND_FN_SYM(midi, audioSettingMidi),
SND_FN_SYM(store, audioSettingsStore),
SND_FN_SYM(volume, audioSettingVolume)
}; };
// The sound files listed from the sounds folder // The sound files listed from the sounds folder
static const char *sSoundExts[] = { ".wav", ".voc" }; static const char *sSoundExts[] = { ".wav", ".voc" };
// [audio] midi: what the dropdown shows and stores
static const char *sMidiLabels[] = { "Automatic", "MIDI port (MPU-401)", "FM synthesizer chip", "Built-in synthesizer" };
static const char *sMidiValues[] = { AUDIO_MIDI_AUTO, AUDIO_MIDI_MPU401, AUDIO_MIDI_FM, AUDIO_MIDI_SOFTWARE };
// ============================================================ // ============================================================
// Module state // Module state
// ============================================================ // ============================================================
@ -116,6 +125,7 @@ static char sDrvNames[CPS_DRV_ITEMS][AUDIO_NAME_MAX]; // as stored
static const char *sDrvLabels[CPS_DRV_ITEMS]; static const char *sDrvLabels[CPS_DRV_ITEMS];
static int32_t sDrvCount = 0; static int32_t sDrvCount = 0;
static const char *sEventLabels[SoundCountE]; static const char *sEventLabels[SoundCountE];
static const char *sMidiLabels[AUDIO_MIDI_CHOICE_COUNT];
static int32_t sChoice[SoundCountE]; static int32_t sChoice[SoundCountE];
// ============================================================ // ============================================================
@ -132,7 +142,6 @@ void cpSoundsBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *pref
void cpSoundsFree(void); void cpSoundsFree(void);
void cpSoundsRevert(void); void cpSoundsRevert(void);
void cpSoundsStore(PrefsHandleT *prefs); void cpSoundsStore(PrefsHandleT *prefs);
static int32_t iniVolume(void);
static void onBrowse(WidgetT *w); static void onBrowse(WidgetT *w);
static void onEvent(WidgetT *w); static void onEvent(WidgetT *w);
static void onFile(WidgetT *w); static void onFile(WidgetT *w);
@ -242,7 +251,7 @@ void cpSoundsBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
for (int32_t i = 0; i < SoundCountE; i++) { for (int32_t i = 0; i < SoundCountE; i++) {
sEventLabels[i] = sSnd.label((DvxSoundE)i); sEventLabels[i] = sSnd.label((DvxSoundE)i);
sChoice[i] = addFile(prefsGetString(prefs, AUDIO_SOUNDS_SECTION, sSnd.key((DvxSoundE)i), "")); sChoice[i] = addFile(sSnd.file(prefs, (DvxSoundE)i));
} }
wgtLabel(page, "Events:"); wgtLabel(page, "Events:");
@ -272,14 +281,14 @@ void cpSoundsBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
sVolSldr->onChange = onVolume; sVolSldr->onChange = onVolume;
sVolLbl = wgtLabel(row, ""); sVolLbl = wgtLabel(row, "");
sVolLbl->prefW = wgtPixels(CPS_VOL_LBL_W); sVolLbl->prefW = wgtPixels(CPS_VOL_LBL_W);
wgtSliderSetValue(sVolSldr, iniVolume()); wgtSliderSetValue(sVolSldr, sSnd.volume(prefs));
showVolume(); showVolume();
// Driver: "Automatic", every installed driver, and DVX.INI's own // Driver: "Automatic", every installed driver, and DVX.INI's own
// choice even when that driver is missing (so OK keeps it). // choice even when that driver is missing (so OK keeps it).
char names[CPS_DRIVERS_MAX][AUDIO_NAME_MAX]; char names[AUDIO_DRIVERS_MAX][AUDIO_NAME_MAX];
int32_t count = sSnd.driverNames(names, CPS_DRIVERS_MAX); int32_t count = sSnd.driverNames(names, AUDIO_DRIVERS_MAX);
const char *want = prefsGetString(prefs, AUDIO_INI_SECTION, AUDIO_INI_DRIVER, AUDIO_DRIVER_AUTO); const char *want = sSnd.driver(prefs);
int32_t drvSel = -1; int32_t drvSel = -1;
sDrvCount = 0; sDrvCount = 0;
@ -301,16 +310,10 @@ void cpSoundsBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
wgtDropdownSetItems(sDrvDrop, sDrvLabels, sDrvCount); wgtDropdownSetItems(sDrvDrop, sDrvLabels, sDrvCount);
wgtDropdownSetSelected(sDrvDrop, drvSel); wgtDropdownSetSelected(sDrvDrop, drvSel);
const char *midi = prefsGetString(prefs, AUDIO_INI_SECTION, AUDIO_INI_MIDI, AUDIO_MIDI_AUTO); prefsChoiceLabels(sSnd.midiChoices, AUDIO_MIDI_CHOICE_COUNT, sMidiLabels);
int32_t midiSel = 0;
for (int32_t i = 0; i < DVX_ARRAY_LEN(sMidiValues); i++) {
midiSel = strcasecmp(sMidiValues[i], midi) == 0 ? i : midiSel;
}
sMidiDrop = wgtDropdown(addRow(page, "MIDI music:")); sMidiDrop = wgtDropdown(addRow(page, "MIDI music:"));
wgtDropdownSetItems(sMidiDrop, sMidiLabels, DVX_ARRAY_LEN(sMidiLabels)); wgtDropdownSetItems(sMidiDrop, sMidiLabels, AUDIO_MIDI_CHOICE_COUNT);
wgtDropdownSetSelected(sMidiDrop, midiSel); wgtDropdownSetSelected(sMidiDrop, sSnd.midi(prefs));
AudioInfoT info; AudioInfoT info;
char text[CPS_TEXT_BUF]; char text[CPS_TEXT_BUF];
@ -339,31 +342,21 @@ void cpSoundsFree(void) {
void cpSoundsRevert(void) { void cpSoundsRevert(void) {
if (sSnd.setVolume) { if (sSnd.setVolume) {
sSnd.setVolume(iniVolume() * AUDIO_VOLUME_MAX / PERCENT_MAX); sSnd.setVolume(sSnd.toVolume(sSnd.volume(sPrefs)));
} }
} }
void cpSoundsStore(PrefsHandleT *prefs) { void cpSoundsStore(PrefsHandleT *prefs) {
if (!sSnd.key) { if (!sSnd.store) {
return; return;
} }
for (int32_t i = 0; i < SoundCountE; i++) { for (int32_t i = 0; i < SoundCountE; i++) {
prefsSetString(prefs, AUDIO_SOUNDS_SECTION, sSnd.key((DvxSoundE)i), sFileValues[sChoice[i]]); sSnd.setFile(prefs, (DvxSoundE)i, sFileValues[sChoice[i]]);
} }
prefsSetInt(prefs, AUDIO_INI_SECTION, AUDIO_INI_VOLUME, wgtSliderGetValue(sVolSldr)); sSnd.store(prefs, sDrvNames[wgtDropdownGetSelected(sDrvDrop)], wgtDropdownGetSelected(sMidiDrop), wgtSliderGetValue(sVolSldr));
prefsSetString(prefs, AUDIO_INI_SECTION, AUDIO_INI_DRIVER, sDrvNames[wgtDropdownGetSelected(sDrvDrop)]);
prefsSetString(prefs, AUDIO_INI_SECTION, AUDIO_INI_MIDI, sMidiValues[wgtDropdownGetSelected(sMidiDrop)]);
}
// The master volume in DVX.INI as the Control Panel opened (percent).
static int32_t iniVolume(void) {
int32_t v = prefsGetInt(sPrefs, AUDIO_INI_SECTION, AUDIO_INI_VOLUME, PERCENT_MAX);
return v < 0 ? 0 : (v > PERCENT_MAX ? PERCENT_MAX : v);
} }
@ -428,7 +421,7 @@ static void onTest(WidgetT *w) {
static void onVolume(WidgetT *w) { static void onVolume(WidgetT *w) {
sSnd.setVolume(wgtSliderGetValue(w) * AUDIO_VOLUME_MAX / PERCENT_MAX); sSnd.setVolume(sSnd.toVolume(wgtSliderGetValue(w)));
showVolume(); showVolume();
} }

View file

@ -24,7 +24,7 @@
// //
// System configuration app with these tabs: // System configuration app with these tabs:
// Mouse -- scroll direction, wheel speed, 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 // Colors -- every system color, theme load/save
// Desktop -- wallpaper image (stretch mode) // Desktop -- wallpaper image (stretch mode)
// Video -- resolution and color depth // Video -- resolution and color depth
// File Types -- which program opens each kind of document (cpTypes.c) // File Types -- which program opens each kind of document (cpTypes.c)
@ -34,7 +34,8 @@
// Sounds -- the sound of each desktop event, volume, sound driver, MIDI music (cpSounds.c) // Sounds -- the sound of each desktop event, volume, sound driver, MIDI music (cpSounds.c)
// //
// Changes preview live. OK saves to DVX.INI, Cancel reverts to the // Changes preview live. OK saves to DVX.INI, Cancel reverts to the
// state captured when the control panel was opened. // state captured when the control panel was opened (an in-memory INI
// written and read back with the same libdvx routines as DVX.INI).
#include "dvxApp.h" #include "dvxApp.h"
#include "dvxDlg.h" #include "dvxDlg.h"
@ -143,18 +144,9 @@ static DxeAppContextT *sCtx = NULL;
static WindowT *sWin = NULL; static WindowT *sWin = NULL;
static PrefsHandleT *sPrefs = NULL; static PrefsHandleT *sPrefs = NULL;
// Saved state for Cancel // Saved state for Cancel: colors, mouse, desktop and video mode in the
static uint8_t sSavedColorRgb[ColorCountE][3]; // DVX.INI layout (prefsCreate; NULL when out of memory)
static int32_t sSavedWheelDir; static PrefsHandleT *sSnapshot = NULL;
static int32_t sSavedDblClick; // double-click ms (dvxSetMouseConfig input)
static int32_t sSavedAccelThresh; // accel threshold (dvxSetMouseConfig input)
static int32_t sSavedMickeys; // mickey ratio (dvxSetMouseConfig input)
static int32_t sSavedWheelStep;
static int32_t sSavedVideoW;
static int32_t sSavedVideoH;
static int32_t sSavedVideoBpp;
static WallpaperModeE sSavedWpMode;
static char sSavedWallpaperPath[DVX_MAX_PATH];
// Mouse tab widgets // Mouse tab widgets
static WidgetT *sWheelDrop = NULL; static WidgetT *sWheelDrop = NULL;
@ -178,7 +170,6 @@ static WidgetT *sColorSwatch = NULL;
static WidgetT *sWallpaperLbl = NULL; static WidgetT *sWallpaperLbl = NULL;
static WidgetT *sWpaperList = NULL; static WidgetT *sWpaperList = NULL;
static WidgetT *sWpModeDrop = NULL; static WidgetT *sWpModeDrop = NULL;
static char sWallpaperPath[DVX_MAX_PATH];
static FileEntryT *sWpaperEntries = NULL; // stb_ds dynamic array static FileEntryT *sWpaperEntries = NULL; // stb_ds dynamic array
static const char **sWpaperLabels = NULL; // stb_ds dynamic array static const char **sWpaperLabels = NULL; // stb_ds dynamic array
@ -199,7 +190,6 @@ static WidgetT *sThemeList = NULL;
// Prototypes // Prototypes
// ============================================================ // ============================================================
static int32_t accelIndexFromPrefs(void);
static void applyMouseConfig(void); static void applyMouseConfig(void);
void appShutdown(void); void appShutdown(void);
static void buildColorsTab(WidgetT *page); static void buildColorsTab(WidgetT *page);
@ -257,13 +247,6 @@ SHELL_APP_DESCRIPTOR(
); );
// Index of the acceleration level stored in the INI (the default level
// when missing or unknown).
static int32_t accelIndexFromPrefs(void) {
return prefsGetChoice(sPrefs, "mouse", "acceleration", prefsAccelChoices, PREFS_ACCEL_CHOICE_COUNT, MOUSE_ACCEL_DEFAULT);
}
static void applyMouseConfig(void) { static void applyMouseConfig(void) {
int32_t dirIdx = wgtDropdownGetSelected(sWheelDrop); int32_t dirIdx = wgtDropdownGetSelected(sWheelDrop);
int32_t dbl = wgtSliderGetValue(sDblClickSldr); int32_t dbl = wgtSliderGetValue(sDblClickSldr);
@ -307,6 +290,8 @@ void appShutdown(void) {
sLabelBufs = NULL; sLabelBufs = NULL;
prefsClose(sPrefs); prefsClose(sPrefs);
sPrefs = NULL; sPrefs = NULL;
prefsClose(sSnapshot);
sSnapshot = NULL;
cpTypesFree(); cpTypesFree();
cpJoystickFree(); cpJoystickFree();
cpSoundsFree(); cpSoundsFree();
@ -400,7 +385,7 @@ static void buildDesktopTab(WidgetT *page) {
sWpaperList->onDblClick = onApplyWallpaper; sWpaperList->onDblClick = onApplyWallpaper;
wgtListBoxSetItems(sWpaperList, sWpaperLabels, arrlen(sWpaperEntries)); wgtListBoxSetItems(sWpaperList, sWpaperLabels, arrlen(sWpaperEntries));
sWallpaperLbl = wgtLabel(page, sWallpaperPath[0] ? sWallpaperPath : "(none)"); sWallpaperLbl = wgtLabel(page, sAc->wallpaperPath[0] ? sAc->wallpaperPath : "(none)");
WidgetT *btnRow = wgtHBox(page); WidgetT *btnRow = wgtHBox(page);
btnRow->spacing = wgtPixels(CP_SPACING); btnRow->spacing = wgtPixels(CP_SPACING);
@ -462,8 +447,7 @@ static void buildMouseTab(WidgetT *page) {
sWheelStepSldr->weight = WGT_WEIGHT_FILL; sWheelStepSldr->weight = WGT_WEIGHT_FILL;
sWheelStepSldr->onChange = onWheelStepSlider; sWheelStepSldr->onChange = onWheelStepSlider;
int32_t wheelStep = prefsGetInt(sPrefs, "mouse", "wheelspeed", MOUSE_WHEEL_STEP_DEFAULT); wgtSliderSetValue(sWheelStepSldr, sAc->wheelStep);
wgtSliderSetValue(sWheelStepSldr, wheelStep);
wgtLabel(wheelStepRow, "Fast "); wgtLabel(wheelStepRow, "Fast ");
sWheelStepLbl = wgtLabel(wheelStepRow, ""); sWheelStepLbl = wgtLabel(wheelStepRow, "");
sWheelStepLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W); sWheelStepLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W);
@ -482,8 +466,7 @@ static void buildMouseTab(WidgetT *page) {
sDblClickSldr->weight = WGT_WEIGHT_FILL; sDblClickSldr->weight = WGT_WEIGHT_FILL;
sDblClickSldr->onChange = onDblClickSlider; sDblClickSldr->onChange = onDblClickSlider;
int32_t dblMs = prefsGetInt(sPrefs, "mouse", "doubleclick", MOUSE_DBLCLICK_DEFAULT_MS); wgtSliderSetValue(sDblClickSldr, sAc->dblClickMs);
wgtSliderSetValue(sDblClickSldr, dblMs);
wgtLabel(dblRow, "Slow "); wgtLabel(dblRow, "Slow ");
sDblClickLbl = wgtLabel(dblRow, ""); sDblClickLbl = wgtLabel(dblRow, "");
sDblClickLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W); sDblClickLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W);
@ -504,7 +487,7 @@ static void buildMouseTab(WidgetT *page) {
sAccelDrop->weight = WGT_WEIGHT_FILL; sAccelDrop->weight = WGT_WEIGHT_FILL;
sAccelDrop->onChange = onAccelChange; sAccelDrop->onChange = onAccelChange;
wgtDropdownSetItems(sAccelDrop, accelItems, PREFS_ACCEL_CHOICE_COUNT); wgtDropdownSetItems(sAccelDrop, accelItems, PREFS_ACCEL_CHOICE_COUNT);
wgtDropdownSetSelected(sAccelDrop, accelIndexFromPrefs()); wgtDropdownSetSelected(sAccelDrop, prefsChoiceIndexOfValue(prefsAccelChoices, PREFS_ACCEL_CHOICE_COUNT, sAc->mouseAccel));
wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL); wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL);
@ -519,10 +502,9 @@ static void buildMouseTab(WidgetT *page) {
sSpeedSldr->weight = WGT_WEIGHT_FILL; sSpeedSldr->weight = WGT_WEIGHT_FILL;
sSpeedSldr->onChange = onSpeedSlider; sSpeedSldr->onChange = onSpeedSlider;
// INI 'speed' holds the mickey ratio (the unit the platform and the // The context holds the mickey ratio (the unit the platform and the
// shell boot path use); convert to the inverted slider position. // INI 'speed' key use); convert to the inverted slider position.
int32_t mickeys = prefsGetInt(sPrefs, "mouse", "speed", MOUSE_SPEED_DEFAULT); wgtSliderSetValue(sSpeedSldr, CP_SPEED_INVERT - sAc->mouseMickeys);
wgtSliderSetValue(sSpeedSldr, CP_SPEED_INVERT - mickeys);
wgtLabel(speedRow, "Fast "); wgtLabel(speedRow, "Fast ");
sSpeedLbl = wgtLabel(speedRow, ""); sSpeedLbl = wgtLabel(speedRow, "");
sSpeedLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W); sSpeedLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W);
@ -685,7 +667,6 @@ static void onChooseWallpaper(WidgetT *w) {
static void onClearWallpaper(WidgetT *w) { static void onClearWallpaper(WidgetT *w) {
(void)w; (void)w;
dvxSetWallpaper(sAc, NULL); dvxSetWallpaper(sAc, NULL);
sWallpaperPath[0] = '\0';
wgtSetText(sWallpaperLbl, "(none)"); wgtSetText(sWallpaperLbl, "(none)");
} }
@ -696,6 +677,8 @@ static void onClose(WindowT *win) {
cpSoundsRevert(); cpSoundsRevert();
prefsClose(sPrefs); prefsClose(sPrefs);
sPrefs = NULL; sPrefs = NULL;
prefsClose(sSnapshot);
sSnapshot = NULL;
dvxDestroyWindow(sAc, win); dvxDestroyWindow(sAc, win);
sWin = NULL; sWin = NULL;
} }
@ -784,46 +767,10 @@ static void onOk(WidgetT *w) {
return; return;
} }
// Save mouse settings // Every change was applied live, so the context holds what to save.
int32_t wheelSel = wgtDropdownGetSelected(sWheelDrop); dvxMouseToPrefs(sAc, sPrefs);
int32_t accelIdx = wgtDropdownGetSelected(sAccelDrop); dvxColorsToPrefs(sAc, sPrefs);
dvxWallpaperToPrefs(sAc, sPrefs);
if (wheelSel >= 0 && wheelSel < PREFS_WHEEL_DIR_CHOICE_COUNT) {
prefsSetString(sPrefs, "mouse", "wheel", prefsWheelDirChoices[wheelSel].iniName);
}
prefsSetInt(sPrefs, "mouse", "doubleclick", wgtSliderGetValue(sDblClickSldr));
if (accelIdx >= 0 && accelIdx < PREFS_ACCEL_CHOICE_COUNT) {
prefsSetString(sPrefs, "mouse", "acceleration", prefsAccelChoices[accelIdx].iniName);
}
// Store the mickey ratio (not the raw slider position) so the boot
// path, which applies this value directly, matches the live setting.
prefsSetInt(sPrefs, "mouse", "speed", CP_SPEED_INVERT - wgtSliderGetValue(sSpeedSldr));
prefsSetInt(sPrefs, "mouse", "wheelspeed", wgtSliderGetValue(sWheelStepSldr));
// Save colors to INI
for (int32_t i = 0; i < ColorCountE; i++) {
uint8_t r;
uint8_t g;
uint8_t b;
dvxGetColor(sAc, (ColorIdE)i, &r, &g, &b);
char val[16];
snprintf(val, sizeof(val), "%d,%d,%d", r, g, b);
prefsSetString(sPrefs, "colors", dvxColorName((ColorIdE)i), val);
}
// Save desktop settings
if (sWallpaperPath[0]) {
prefsSetString(sPrefs, "desktop", "wallpaper", sWallpaperPath);
} else {
prefsRemove(sPrefs, "desktop", "wallpaper");
}
prefsSetString(sPrefs, "desktop", "mode", prefsWallpaperChoices[sAc->wallpaperMode].iniName);
prefsSetVideoMode(sPrefs, sAc->display.width, sAc->display.height, sAc->display.format.bitsPerPixel); prefsSetVideoMode(sPrefs, sAc->display.width, sAc->display.height, sAc->display.format.bitsPerPixel);
cpNetStore(sPrefs); cpNetStore(sPrefs);
@ -833,6 +780,8 @@ static void onOk(WidgetT *w) {
prefsSave(sPrefs); prefsSave(sPrefs);
prefsClose(sPrefs); prefsClose(sPrefs);
sPrefs = NULL; sPrefs = NULL;
prefsClose(sSnapshot);
sSnapshot = NULL;
cpTypesApply(); cpTypesApply();
cpNetApply(); cpNetApply();
cpSoundsApply(); cpSoundsApply();
@ -971,7 +920,7 @@ static void onVideoApply(WidgetT *w) {
PlatformTicksT now = platformClock(); PlatformTicksT now = platformClock();
if ((PlatformTicksT)(now - lastSecond) >= hz) { if (now - lastSecond >= hz) {
lastSecond = now; lastSecond = now;
secondsLeft--; secondsLeft--;
setIntLabel(msgLbl, CP_REVERT_PREFIX, secondsLeft, CP_REVERT_SUFFIX); setIntLabel(msgLbl, CP_REVERT_PREFIX, secondsLeft, CP_REVERT_SUFFIX);
@ -1030,54 +979,39 @@ static void onWheelStepSlider(WidgetT *w) {
static void restoreSnapshot(void) { static void restoreSnapshot(void) {
// Restore colors (a full repaint, so only when something changed) if (!sSnapshot) {
if (memcmp(sAc->colorRgb, sSavedColorRgb, sizeof(sSavedColorRgb)) != 0) { return;
memcpy(sAc->colorRgb, sSavedColorRgb, sizeof(sSavedColorRgb));
dvxApplyColorScheme(sAc);
} }
// Restore mouse to the pre-open state captured in saveSnapshot. // Each of these changes (and repaints) only what differs.
// The snapshot already holds the dvxSetMouseConfig inputs directly. dvxColorsFromPrefs(sAc, sSnapshot);
dvxSetMouseConfig(sAc, sSavedWheelDir, sSavedDblClick, sSavedAccelThresh, sSavedMickeys, sSavedWheelStep); dvxMouseFromPrefs(sAc, sSnapshot);
// Restore video mode if changed int32_t w;
if (sAc->display.width != sSavedVideoW || int32_t h;
sAc->display.height != sSavedVideoH || int32_t bpp;
sAc->display.format.bitsPerPixel != sSavedVideoBpp) {
dvxChangeVideoMode(sAc, sSavedVideoW, sSavedVideoH, sSavedVideoBpp); prefsGetVideoMode(sSnapshot, &w, &h, &bpp);
if (sAc->display.width != w || sAc->display.height != h || sAc->display.format.bitsPerPixel != bpp) {
dvxChangeVideoMode(sAc, w, h, bpp);
} }
// Restore wallpaper only if path or mode changed. A mode change dvxWallpaperFromPrefs(sAc, sSnapshot);
// alone rebuilds the scaled image from the current path.
if (strcmp(sAc->wallpaperPath, sSavedWallpaperPath) != 0) {
sAc->wallpaperMode = sSavedWpMode;
dvxSetWallpaper(sAc, sSavedWallpaperPath[0] ? sSavedWallpaperPath : NULL);
} else if (sAc->wallpaperMode != sSavedWpMode) {
dvxSetWallpaperMode(sAc, sSavedWpMode);
}
} }
// The state Cancel goes back to, written like DVX.INI but kept in memory.
static void saveSnapshot(void) { static void saveSnapshot(void) {
memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb)); prefsClose(sSnapshot);
sSavedWheelDir = sAc->wheelDirection; sSnapshot = prefsCreate();
// Double-click ms, accel and mickey ratio are captured from prefs,
// which hold the currently-applied values. The context only caches
// double-click as ticks (converting back loses precision each round
// trip), accel and mickeys are not cached at all, and the mouse-tab
// widgets do not exist yet (saveSnapshot runs before buildMouseTab).
sSavedDblClick = prefsGetInt(sPrefs, "mouse", "doubleclick", MOUSE_DBLCLICK_DEFAULT_MS);
sSavedAccelThresh = prefsAccelChoices[accelIndexFromPrefs()].value;
sSavedMickeys = prefsGetInt(sPrefs, "mouse", "speed", MOUSE_SPEED_DEFAULT);
sSavedWheelStep = sAc->wheelStep;
sSavedVideoW = sAc->display.width;
sSavedVideoH = sAc->display.height;
sSavedVideoBpp = sAc->display.format.bitsPerPixel;
// Save the active wallpaper path and mode from the context if (sSnapshot) {
snprintf(sSavedWallpaperPath, sizeof(sSavedWallpaperPath), "%s", sAc->wallpaperPath); dvxColorsToPrefs(sAc, sSnapshot);
snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sAc->wallpaperPath); dvxMouseToPrefs(sAc, sSnapshot);
sSavedWpMode = sAc->wallpaperMode; dvxWallpaperToPrefs(sAc, sSnapshot);
prefsSetVideoMode(sSnapshot, sAc->display.width, sAc->display.height, sAc->display.format.bitsPerPixel);
}
} }
@ -1163,8 +1097,7 @@ static void setWallpaperFromPath(const char *path) {
return; return;
} }
snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", path); wgtSetText(sWallpaperLbl, sAc->wallpaperPath);
wgtSetText(sWallpaperLbl, sWallpaperPath);
} }

View file

@ -25,10 +25,14 @@
# Builds: # Builds:
# basrt.lib -- BASIC runtime library (VM + values + form # basrt.lib -- BASIC runtime library (VM + values + form
# runtime + dlregsym init) # runtime + dlregsym init)
# dvxbasic.app -- BASIC IDE (compiler + UI, links against # basdev.lib -- BASIC development library (compiler, build
# basrt.lib, standalone stub embedded) # pipeline, validator, debugger; the IDE and
# BASIC programs through compiler.bas /
# debug.bas; standalone stub embedded)
# dvxbasic.app -- BASIC IDE (UI, links against basrt.lib and
# basdev.lib)
# basstub.app -- standalone app stub (obj/dvxbasic, embedded # basstub.app -- standalone app stub (obj/dvxbasic, embedded
# into the IDE and bascomp) # into basdev.lib and bascomp)
# bin/system/BASCOMP.EXE-- DOS command-line compiler # bin/system/BASCOMP.EXE-- DOS command-line compiler
# bin/host/bascomp -- host command-line compiler # bin/host/bascomp -- host command-line compiler
# bin/host/basrun -- host headless runner # bin/host/basrun -- host headless runner
@ -46,7 +50,7 @@ include ../../../common.mk
LIBDVX = ../../../libs/kpunch/libdvx LIBDVX = ../../../libs/kpunch/libdvx
TOOLSDIR = ../../../tools TOOLSDIR = ../../../tools
DVX_INCLUDES = -I$(LIBDVX) -I$(LIBDVX)/platform -I$(LIBDVX)/thirdparty -I. DVX_INCLUDES = -I$(LIBDVX) -I$(LIBDVX)/platform -I$(LIBDVX)/thirdparty -I../../../libs/kpunch/texthelp -I.
CFLAGS = $(DOS_CFLAGS) $(DVX_INCLUDES) -I../../../widgets/kpunch -I../../../libs/kpunch/dvxshell -I../../../libs/kpunch/libtasks CFLAGS = $(DOS_CFLAGS) $(DVX_INCLUDES) -I../../../widgets/kpunch -I../../../libs/kpunch/dvxshell -I../../../libs/kpunch/libtasks
OBJDIR = ../../../../obj/dvxbasic OBJDIR = ../../../../obj/dvxbasic
@ -68,13 +72,20 @@ RES_SRCS = $(LIBDVX)/dvxResource.c $(TOOLSDIR)/dvxResWrite.c
CLI_SRCS = basBuild.c $(COMP_SRCS) $(VM_LINK_SRCS) $(RES_SRCS) $(SUPPORT_SRCS) CLI_SRCS = basBuild.c $(COMP_SRCS) $(VM_LINK_SRCS) $(RES_SRCS) $(SUPPORT_SRCS)
# Runtime library objects (VM + values + form runtime + serialization) # Runtime library objects (VM + values + form runtime + serialization)
RT_OBJS = $(OBJDIR)/vm.o $(OBJDIR)/values.o $(OBJDIR)/dates.o $(OBJDIR)/gfx.o $(OBJDIR)/screen.o $(OBJDIR)/formrt.o $(OBJDIR)/formGfx.o $(OBJDIR)/frmParser.o $(OBJDIR)/basNativeDos.o $(OBJDIR)/serialize.o RT_OBJS = $(OBJDIR)/vm.o $(OBJDIR)/values.o $(OBJDIR)/dates.o $(OBJDIR)/gfx.o $(OBJDIR)/screen.o $(OBJDIR)/formrt.o $(OBJDIR)/formGfx.o $(OBJDIR)/formDesign.o $(OBJDIR)/formCtl.o $(OBJDIR)/frmParser.o $(OBJDIR)/frmModel.o $(OBJDIR)/frmModelBas.o $(OBJDIR)/basNativeDos.o $(OBJDIR)/serialize.o $(OBJDIR)/basRunHost.o
RT_TARGETDIR = $(LIBSDIR)/kpunch/basrt RT_TARGETDIR = $(LIBSDIR)/kpunch/basrt
RT_TARGET = $(RT_TARGETDIR)/basrt.lib RT_TARGET = $(RT_TARGETDIR)/basrt.lib
# Compiler objects (only needed by the IDE) # Compiler objects (the development library)
COMP_OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(COMP_SRCS))) $(OBJDIR)/basBuild.o COMP_OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(COMP_SRCS))) $(OBJDIR)/basBuild.o
# Development library: the compiler plus the shared validator, debugger
# and proc scanner and the BASIC entry points (basdev/)
DEV_SRCS = basdev/basValidate.c basdev/basDebug.c basdev/basProcs.c basdev/basSource.c basdev/basHelpMap.c basdev/basProject.c basdev/basDevBas.c
DEV_OBJS = $(COMP_OBJS) $(patsubst basdev/%.c,$(OBJDIR)/%.o,$(DEV_SRCS))
DEV_TARGETDIR = $(LIBSDIR)/kpunch/basdev
DEV_TARGET = $(DEV_TARGETDIR)/basdev.lib
# IDE app objects # IDE app objects
IDE_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideMenuEditor.o $(OBJDIR)/ideProject.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o IDE_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideMenuEditor.o $(OBJDIR)/ideProject.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o
APP_TARGET = $(APPDIR)/dvxbasic.app APP_TARGET = $(APPDIR)/dvxbasic.app
@ -142,7 +153,7 @@ HARNESS_WORK = ../../../../obj/test/harness
.PHONY: all clean tests .PHONY: all clean tests
all: $(RT_TARGET) $(RT_TARGETDIR)/basrt.dep $(STUB_TARGET) $(APP_TARGET) $(BASCOMP_TARGET) $(BASRUN_TARGET) $(SYSTEMDIR)/BASCOMP.EXE all: $(RT_TARGET) $(RT_TARGETDIR)/basrt.dep $(DEV_TARGET) $(DEV_TARGETDIR)/basdev.dep $(STUB_TARGET) $(APP_TARGET) $(BASCOMP_TARGET) $(BASRUN_TARGET) $(SYSTEMDIR)/BASCOMP.EXE
# Run every built harness and fail the build if any returns non-zero. # Run every built harness and fail the build if any returns non-zero.
# test_compiler, test_compact and test_suite each report a failure count # test_compiler, test_compact and test_suite each report a failure count
@ -208,15 +219,25 @@ $(RT_TARGET): $(RT_OBJS) | $(RT_TARGETDIR)
$(RT_TARGETDIR)/basrt.dep: basrt.dep | $(RT_TARGETDIR) $(RT_TARGETDIR)/basrt.dep: basrt.dep | $(RT_TARGETDIR)
sed 's/$$/\r/' $< > $@ sed 's/$$/\r/' $< > $@
# Standalone stub DXE (embedded as resource in IDE app) # Development library DXE. It carries the STUB and noicon resources every
# build inside DVX uses (BAS_BUILD_STUB_CARRIER in basBuild.h).
$(DEV_TARGET): $(DEV_OBJS) $(STUB_TARGET) noicon.bmp $(DVXRES) | $(DEV_TARGETDIR)
$(DXE3GEN) -o $(DEV_TARGETDIR)/basdev.dxe -U $(DEV_OBJS)
mv $(DEV_TARGETDIR)/basdev.dxe $@
$(DVXRES) add $@ STUB binary @$(STUB_TARGET)
$(DVXRES) add $@ noicon icon @noicon.bmp
$(DEV_TARGETDIR)/basdev.dep: basdev/basdev.dep | $(DEV_TARGETDIR)
sed 's/$$/\r/' $< > $@
# Standalone stub DXE (embedded as resource in basdev.lib and bascomp)
$(STUB_TARGET): $(STUB_OBJS) | $(OBJDIR) $(STUB_TARGET): $(STUB_OBJS) | $(OBJDIR)
$(DXE3GEN) -o $@ -U $(STUB_OBJS) $(DXE3GEN) -o $@ -U $(STUB_OBJS)
# IDE app DXE (compiler linked in, runtime from basrt.lib, stub embedded) # IDE app DXE (compiler from basdev.lib, runtime from basrt.lib)
$(APP_TARGET): $(COMP_OBJS) $(IDE_OBJS) $(STUB_TARGET) dvxbasic.res | $(APPDIR) $(APP_TARGET): $(IDE_OBJS) dvxbasic.res | $(APPDIR)
$(DXE3GEN) -o $@ -U $(COMP_OBJS) $(IDE_OBJS) $(DXE3GEN) -o $@ -U $(IDE_OBJS)
$(DVXRES) build $@ dvxbasic.res $(DVXRES) build $@ dvxbasic.res
$(DVXRES) add $@ STUB binary @$(STUB_TARGET)
# DOS object files. One pattern rule per source directory; header # DOS object files. One pattern rule per source directory; header
@ -237,6 +258,9 @@ $(OBJDIR)/%.o: ide/%.c $(DVX_MAKEFILES) | $(OBJDIR)
$(OBJDIR)/%.o: stub/%.c $(DVX_MAKEFILES) | $(OBJDIR) $(OBJDIR)/%.o: stub/%.c $(DVX_MAKEFILES) | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $< $(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/%.o: basdev/%.c $(DVX_MAKEFILES) | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/%.o: $(LIBDVX)/%.c $(DVX_MAKEFILES) | $(OBJDIR) $(OBJDIR)/%.o: $(LIBDVX)/%.c $(DVX_MAKEFILES) | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $< $(CC) $(CFLAGS) -c -o $@ $<
@ -259,11 +283,14 @@ $(OBJDIR):
$(RT_TARGETDIR): $(RT_TARGETDIR):
mkdir -p $(RT_TARGETDIR) mkdir -p $(RT_TARGETDIR)
$(DEV_TARGETDIR):
mkdir -p $(DEV_TARGETDIR)
$(APPDIR): $(APPDIR):
mkdir -p $(APPDIR) mkdir -p $(APPDIR)
clean: host-basrt-clean clean: host-basrt-clean
rm -rf $(RT_OBJS) $(COMP_OBJS) $(IDE_OBJS) $(STUB_OBJS) $(DOS_BASCOMP_OBJS) $(OBJDIR)/*.d $(RT_TARGET) $(APP_TARGET) $(STUB_TARGET) $(BASCOMP_TARGET) $(SYSTEMDIR)/BASCOMP.EXE $(RT_TARGETDIR)/basrt.dep $(RT_TARGETDIR) rm -rf $(RT_OBJS) $(DEV_OBJS) $(IDE_OBJS) $(STUB_OBJS) $(DOS_BASCOMP_OBJS) $(OBJDIR)/*.d $(RT_TARGET) $(APP_TARGET) $(STUB_TARGET) $(BASCOMP_TARGET) $(SYSTEMDIR)/BASCOMP.EXE $(RT_TARGETDIR)/basrt.dep $(RT_TARGETDIR) $(DEV_TARGET) $(DEV_TARGETDIR)
rm -rf $(OBJDIR)/host-plain $(OBJDIR)/host-san $(HARNESS_WORK) rm -rf $(OBJDIR)/host-plain $(OBJDIR)/host-san $(HARNESS_WORK)
rm -f $(TEST_COMPILER) $(TEST_COMPACT) $(TEST_SUITE) $(BASRUN_TARGET) $(HOSTDIR)/.testsan-on $(HOSTDIR)/.testsan-off rm -f $(TEST_COMPILER) $(TEST_COMPACT) $(TEST_SUITE) $(BASRUN_TARGET) $(HOSTDIR)/.testsan-on $(HOSTDIR)/.testsan-off
$(MAKE) -C $(BASIC_TESTDIR) clean $(MAKE) -C $(BASIC_TESTDIR) clean
@ -281,7 +308,7 @@ clean: host-basrt-clean
# of the host build. # of the host build.
HOSTRT_DIR = $(HOST_OBJROOT)/basrt HOSTRT_DIR = $(HOST_OBJROOT)/basrt
HOSTRT_TARGET = $(HOST_OBJROOT)/basrt.a HOSTRT_TARGET = $(HOST_OBJROOT)/basrt.a
HOSTRT_SRCS = $(COMP_SRCS) $(VM_SRCS) formrt/formrt.c formrt/formGfx.c formrt/frmParser.c formrt/basNativeHost.c HOSTRT_SRCS = $(COMP_SRCS) $(VM_SRCS) formrt/formrt.c formrt/formGfx.c formrt/formDesign.c formrt/formCtl.c formrt/frmParser.c formrt/frmModel.c formrt/frmModelBas.c formrt/basNativeHost.c formrt/basRunHost.c
HOSTRT_OBJS = $(patsubst %.c,$(HOSTRT_DIR)/%.o,$(notdir $(HOSTRT_SRCS))) HOSTRT_OBJS = $(patsubst %.c,$(HOSTRT_DIR)/%.o,$(notdir $(HOSTRT_SRCS)))
HOSTRT_CFLAGS = $(HOST_CFLAGS) -Wno-stringop-truncation $(DVX_INCLUDES) -I../../../widgets/kpunch -I../../../libs/kpunch/dvxshell -I../../../libs/kpunch/libtasks HOSTRT_CFLAGS = $(HOST_CFLAGS) -Wno-stringop-truncation $(DVX_INCLUDES) -I../../../widgets/kpunch -I../../../libs/kpunch/dvxshell -I../../../libs/kpunch/libtasks

View file

@ -39,6 +39,7 @@
// BAS_RES_AUTHOR / PUBLISHER / VERSION / COPYRIGHT / DESCRIPTION // BAS_RES_AUTHOR / PUBLISHER / VERSION / COPYRIGHT / DESCRIPTION
// BAS_RES_ICON32 (project icon, else the NOICON fallback) // BAS_RES_ICON32 (project icon, else the NOICON fallback)
// BAS_RES_HELPFILE (basename only) // BAS_RES_HELPFILE (basename only)
// BAS_RES_MULTIINSTANCE ("1", only when the project allows copies)
// BAS_RES_MODULE, BAS_RES_DEBUG (debug builds) // BAS_RES_MODULE, BAS_RES_DEBUG (debug builds)
// FORM0, FORM1, ... cut at the end of the outer Begin Form block // FORM0, FORM1, ... cut at the end of the outer Begin Form block
// so the .frm's BASIC code section (already compiled into // so the .frm's BASIC code section (already compiled into
@ -58,6 +59,7 @@
#include "stb_ds_wrap.h" #include "stb_ds_wrap.h"
#include <ctype.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -83,6 +85,10 @@
#define BAS_BUILD_WRAP_SLACK 8 #define BAS_BUILD_WRAP_SLACK 8
// Longest [Modules]/[Forms] key, "File" plus a decimal index. // Longest [Modules]/[Forms] key, "File" plus a decimal index.
#define BAS_BUILD_KEY_LEN 32 #define BAS_BUILD_KEY_LEN 32
// How a compiler message starts when it names the line ("Line N, Col M: ")
#define BAS_BUILD_LINE_PREFIX "Line "
#define BAS_BUILD_ERR_MEMORY "Out of memory."
#define BAS_BUILD_ERR_MODULE "Failed to build module."
// emitIcon's error text when it names the unreadable icon path (the // emitIcon's error text when it names the unreadable icon path (the
// other build errors are string literals) // other build errors are string literals)
@ -92,6 +98,7 @@ static char sIconError[BAS_BUILD_ERROR_LEN];
typedef enum { typedef enum {
BAS_BUILD_PASS_MODULES, BAS_BUILD_PASS_MODULES,
BAS_BUILD_PASS_CLASSES, BAS_BUILD_PASS_CLASSES,
BAS_BUILD_PASS_CONTROLS,
BAS_BUILD_PASS_FORMS, BAS_BUILD_PASS_FORMS,
BAS_BUILD_PASS_COUNT BAS_BUILD_PASS_COUNT
} BasBuildPassE; } BasBuildPassE;
@ -100,31 +107,54 @@ typedef enum {
static const char *const sWrapBegin[] = { static const char *const sWrapBegin[] = {
[BAS_BUILD_WRAP_MODULE] = BAS_WRAP_BEGINMODULE, [BAS_BUILD_WRAP_MODULE] = BAS_WRAP_BEGINMODULE,
[BAS_BUILD_WRAP_FORM] = BAS_WRAP_BEGINFORM, [BAS_BUILD_WRAP_FORM] = BAS_WRAP_BEGINFORM,
[BAS_BUILD_WRAP_CLASS] = BAS_WRAP_BEGINCLASS [BAS_BUILD_WRAP_CLASS] = BAS_WRAP_BEGINCLASS,
[BAS_BUILD_WRAP_CONTROL] = BAS_WRAP_BEGINCLASS
}; };
static const char *const sWrapEnd[] = { static const char *const sWrapEnd[] = {
[BAS_BUILD_WRAP_MODULE] = BAS_WRAP_ENDMODULE "\n", [BAS_BUILD_WRAP_MODULE] = BAS_WRAP_ENDMODULE "\n",
[BAS_BUILD_WRAP_FORM] = BAS_WRAP_ENDFORM "\n", [BAS_BUILD_WRAP_FORM] = BAS_WRAP_ENDFORM "\n",
[BAS_BUILD_WRAP_CLASS] = BAS_WRAP_ENDCLASS "\n" [BAS_BUILD_WRAP_CLASS] = BAS_WRAP_ENDCLASS "\n",
[BAS_BUILD_WRAP_CONTROL] = BAS_WRAP_ENDCLASS "\n"
}; };
// What follows a UserControl's name on its BEGINCLASS line.
#define BAS_BUILD_CTL_MARK " " BAS_UC_CLASS_KW
// A UserControl's toolbox name or path resource, the second and later
// "-n" suffixed (the numbers count from 1).
#define BAS_BUILD_CTL_FIRST 1
// Function prototypes (alphabetical) // Function prototypes (alphabetical)
static bool appendSource(BasProjectSourceT *ps, const char *text, int32_t len); static bool appendSource(BasProjectSourceT *ps, const char *text, int32_t len);
static int32_t appendText(const char *path, const char *name, const char *value); static int32_t appendText(const char *path, const char *name, const char *value);
bool basBuildAddSource(BasProjectSourceT *ps, const char *path, const char *text, int32_t len, BasBuildWrapE wrap);
const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec); const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec);
bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, const char *ctrlList, int32_t *textLine, int32_t *textLines); bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, const char *ctrlList, int32_t *textLine, int32_t *textLines);
BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit, const BasCtrlValidatorT *validator, const BasCompilerT **liveCompiler, BasCompileErrorT *err);
bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out); bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out);
const char *basBuildControl(const char *outPath, const BasBuildSpecT *spec);
void basBuildDirOf(const char *path, char *out, int32_t outSize); void basBuildDirOf(const char *path, char *out, int32_t outSize);
const char *basBuildErrorText(const char *message);
void basBuildFreeProject(BasProjectSourceT *out); void basBuildFreeProject(BasProjectSourceT *out);
bool basBuildIsControlProject(PrefsHandleT *prefs);
bool basBuildIsControlType(const char *type);
int32_t basBuildMapFileLine(const BasSourceMapT *map, const char *path, int32_t fileLine);
bool basBuildMapLine(const BasSourceMapT *map, int32_t line, const char **path, int32_t *fileLine); bool basBuildMapLine(const BasSourceMapT *map, int32_t line, const char **path, int32_t *fileLine);
bool basBuildOpenProject(const char *dbpPath, PrefsHandleT **prefs, char *projectDir, int32_t projectDirSize); bool basBuildOpenProject(const char *dbpPath, PrefsHandleT **prefs, char *projectDir, int32_t projectDirSize);
void basBuildSpecFromProject(PrefsHandleT *prefs, const char *projectDir, BasBuildSpecT *spec);
BasBuildWrapE basBuildWrapForPath(const char *path);
static void buildLog(const BasBuildSpecT *spec, const char *fmt, ...); static void buildLog(const BasBuildSpecT *spec, const char *fmt, ...);
static const char *copyHelpFile(const char *outPath, const BasBuildSpecT *spec); static const char *copyHelpFile(const char *outPath, const BasBuildSpecT *spec);
static int32_t countLines(const char *text, int32_t len); static int32_t countLines(const char *text, int32_t len);
static const char *emitControls(const char *outPath, const BasBuildSpecT *spec);
static const char *emitCtlTexts(const char *outPath, const BasBuildSpecT *spec);
static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec); static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec);
static const char *emitResources(const char *outPath, const BasBuildSpecT *spec, const uint8_t *modData, int32_t modLen, const uint8_t *dbgData, int32_t dbgLen, uint8_t **frmData, const int32_t *frmLens, const BasObfFrmT *obfFrms, int32_t frmCount); static const char *emitResources(const char *outPath, const BasBuildSpecT *spec, const uint8_t *modData, int32_t modLen, const uint8_t *dbgData, int32_t dbgLen, uint8_t **frmData, const int32_t *frmLens, const BasObfFrmT *obfFrms, int32_t frmCount);
static void freeFrmWork(uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms); static void freeFrmWork(uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms);
static bool pathsEqual(const char *a, const char *b);
static const char *prepareModule(const BasBuildSpecT *spec, uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms, int32_t frmCount, uint8_t **outMod, int32_t *outModLen, uint8_t **outDbg, int32_t *outDbgLen); static const char *prepareModule(const BasBuildSpecT *spec, uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms, int32_t frmCount, uint8_t **outMod, int32_t *outModLen, uint8_t **outDbg, int32_t *outDbgLen);
static const char *stubCarrier(const BasBuildSpecT *spec);
static const char *writeMarker(const char *outPath);
static const char *writeStub(const char *outPath, const BasBuildSpecT *spec); static const char *writeStub(const char *outPath, const BasBuildSpecT *spec);
@ -179,8 +209,109 @@ static int32_t appendText(const char *path, const char *name, const char *value)
} }
bool basBuildAddSource(BasProjectSourceT *ps, const char *path, const char *text, int32_t len, BasBuildWrapE wrap) {
BasSourceMapT entry;
const char *code = text;
char name[BAS_MAX_IDENT] = "";
char *ctrlList = NULL;
memset(&entry, 0, sizeof(entry));
snprintf(entry.path, sizeof(entry.path), "%s", path);
entry.firstFileLine = 1;
if (wrap == BAS_BUILD_WRAP_CLASS || wrap == BAS_BUILD_WRAP_CONTROL) {
// The class code follows the .cls / .ctl header; a UserControl is
// named by its Begin UserControl line
bool named = (wrap == BAS_BUILD_WRAP_CONTROL) ? basExtractCtlName(text, len, name, BAS_MAX_IDENT) : basExtractClassName(text, len, path, name, BAS_MAX_IDENT);
if (!named) {
snprintf(ps->error, sizeof(ps->error), (wrap == BAS_BUILD_WRAP_CONTROL) ? "No Begin UserControl line in %s" : "No class name for %s", path);
return false;
}
int32_t codeOff = basClassCodePos(text, len);
code = text + codeOff;
entry.firstFileLine = countLines(text, codeOff) + 1;
}
if (wrap == BAS_BUILD_WRAP_FORM) {
// A .frm without a Begin Form line would otherwise compile its
// code unwrapped, as if it were a module.
if (!basExtractFormName(text, name, BAS_MAX_IDENT)) {
snprintf(ps->error, sizeof(ps->error), "No Begin Form line in %s", path);
return false;
}
// The BASIC code section follows the outer Begin Form block; the
// BEGINFORM line lists the controls (never longer than the text)
int32_t codeOff = basFindFormEndPos(text, len);
code = text + codeOff;
entry.firstFileLine = countLines(text, codeOff) + 1;
ctrlList = (char *)malloc((size_t)len + 1);
if (!ctrlList || basFrmControlList(text, len, ctrlList, len + 1) < 0) {
snprintf(ps->error, sizeof(ps->error), BAS_BUILD_ERR_MEMORY);
free(ctrlList);
return false;
}
}
bool appended = basBuildAppendFile(ps, code, len - (int32_t)(code - text), wrap, name, ctrlList, &entry.startLine, &entry.lineCount);
free(ctrlList);
if (!appended) {
return false;
}
arrput(ps->map, entry);
ps->fileCount++;
// A UserControl's header: a CTLn resource, registered when the
// program starts
if (wrap == BAS_BUILD_WRAP_CONTROL) {
int32_t headerLen = (int32_t)(code - text);
char *header = (char *)malloc((size_t)headerLen + 1);
if (!header) {
snprintf(ps->error, sizeof(ps->error), BAS_BUILD_ERR_MEMORY);
return false;
}
memcpy(header, text, (size_t)headerLen);
header[headerLen] = '\0';
arrput(ps->ctlTexts, header);
return true;
}
if (wrap != BAS_BUILD_WRAP_FORM) {
return true;
}
// The .frm text itself: a resource of the built program, registered
// with the form runtime of a run
char *frmPath = strdup(path);
char *frmText = (char *)malloc((size_t)len + 1);
if (!frmPath || !frmText) {
snprintf(ps->error, sizeof(ps->error), BAS_BUILD_ERR_MEMORY);
free(frmPath);
free(frmText);
return false;
}
memcpy(frmText, text, (size_t)len);
frmText[len] = '\0';
arrput(ps->frmPaths, frmPath);
arrput(ps->frmTexts, frmText);
return true;
}
const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec) { const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec) {
if (!outPath || !spec || !spec->module || !spec->selfPath) { if (!outPath || !spec || !spec->module) {
return "Invalid build request."; return "Invalid build request.";
} }
@ -246,8 +377,9 @@ const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec) {
bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, const char *ctrlList, int32_t *textLine, int32_t *textLines) { bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, const char *ctrlList, int32_t *textLine, int32_t *textLines) {
if (wrap != BAS_BUILD_WRAP_NONE) { if (wrap != BAS_BUILD_WRAP_NONE) {
// One line: the directive, the name and a form's controls // One line: the directive, the name and a form's controls (a
const char *ctrls = (ctrlList != NULL) ? ctrlList : ""; // UserControl's mark)
const char *ctrls = (wrap == BAS_BUILD_WRAP_CONTROL) ? BAS_BUILD_CTL_MARK : ((ctrlList != NULL) ? ctrlList : "");
size_t cap = strlen(sWrapBegin[wrap]) + strlen(name) + strlen(ctrls) + BAS_BUILD_WRAP_SLACK; size_t cap = strlen(sWrapBegin[wrap]) + strlen(name) + strlen(ctrls) + BAS_BUILD_WRAP_SLACK;
char *line = (char *)malloc(cap); char *line = (char *)malloc(cap);
@ -288,21 +420,73 @@ bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, Ba
} }
BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit, const BasCtrlValidatorT *validator, const BasCompilerT **liveCompiler, BasCompileErrorT *err) {
memset(err, 0, sizeof(*err));
BasCompilerT *compiler = basCompilerCreate(source, len);
if (!compiler) {
err->result = BAS_COMPILE_NO_MEMORY;
snprintf(err->message, sizeof(err->message), BAS_BUILD_ERR_MEMORY);
return NULL;
}
basCompilerSetOptionExplicit(compiler, optionExplicit);
if (validator) {
basCompilerSetValidator(compiler, validator);
}
if (liveCompiler) {
*liveCompiler = compiler;
}
bool compiled = basCompile(compiler);
BasModuleT *mod = NULL;
if (liveCompiler) {
*liveCompiler = NULL;
}
if (compiled) {
mod = basCompilerBuildModule(compiler);
if (!mod) {
err->result = BAS_COMPILE_NO_MODULE;
snprintf(err->message, sizeof(err->message), BAS_BUILD_ERR_MODULE);
}
} else {
err->result = BAS_COMPILE_ERROR;
err->line = basCompilerErrorLine(compiler);
err->col = basCompilerErrorCol(compiler);
snprintf(err->message, sizeof(err->message), "%s", basCompilerError(compiler));
}
basCompilerDestroy(compiler);
return mod;
}
bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out) { bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out) {
static const char *const sections[BAS_BUILD_PASS_COUNT] = { static const char *const sections[BAS_BUILD_PASS_COUNT] = {
[BAS_BUILD_PASS_MODULES] = BAS_INI_SECTION_MODULES, [BAS_BUILD_PASS_MODULES] = BAS_INI_SECTION_MODULES,
[BAS_BUILD_PASS_CLASSES] = BAS_INI_SECTION_CLASSES, [BAS_BUILD_PASS_CLASSES] = BAS_INI_SECTION_CLASSES,
[BAS_BUILD_PASS_CONTROLS] = BAS_INI_SECTION_CONTROLS,
[BAS_BUILD_PASS_FORMS] = BAS_INI_SECTION_FORMS [BAS_BUILD_PASS_FORMS] = BAS_INI_SECTION_FORMS
}; };
static const BasBuildWrapE wraps[BAS_BUILD_PASS_COUNT] = {
[BAS_BUILD_PASS_MODULES] = BAS_BUILD_WRAP_MODULE,
[BAS_BUILD_PASS_CLASSES] = BAS_BUILD_WRAP_CLASS,
[BAS_BUILD_PASS_CONTROLS] = BAS_BUILD_WRAP_CONTROL,
[BAS_BUILD_PASS_FORMS] = BAS_BUILD_WRAP_FORM
};
memset(out, 0, sizeof(*out)); memset(out, 0, sizeof(*out));
out->optionExplicit = prefsGetBool(prefs, BAS_INI_SECTION_SETTINGS, BAS_INI_KEY_OPTIONEXPLICIT, false); out->optionExplicit = prefsGetBool(prefs, BAS_INI_SECTION_SETTINGS, BAS_INI_KEY_OPTIONEXPLICIT, false);
// .bas modules first, then .cls class modules, then .frm code sections. // .bas modules first, then .cls class modules, .ctl UserControls,
// then .frm code sections.
for (int32_t pass = 0; pass < BAS_BUILD_PASS_COUNT; pass++) { for (int32_t pass = 0; pass < BAS_BUILD_PASS_COUNT; pass++) {
bool isForm = (pass == BAS_BUILD_PASS_FORMS);
bool isClass = (pass == BAS_BUILD_PASS_CLASSES);
for (int32_t i = 0; ; i++) { for (int32_t i = 0; ; i++) {
char key[BAS_BUILD_KEY_LEN]; char key[BAS_BUILD_KEY_LEN];
snprintf(key, sizeof(key), "File%d", (int)i); snprintf(key, sizeof(key), "File%d", (int)i);
@ -312,95 +496,24 @@ bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProje
break; break;
} }
BasSourceMapT entry; char path[DVX_MAX_PATH];
memset(&entry, 0, sizeof(entry)); snprintf(path, sizeof(path), "%s%s%s", projectDir, BAS_BUILD_SEP, val);
snprintf(entry.path, sizeof(entry.path), "%s%s%s", projectDir, BAS_BUILD_SEP, val);
int32_t srcLen = 0; int32_t srcLen = 0;
char *src = platformReadFile(entry.path, &srcLen); char *src = platformReadFile(path, &srcLen);
if (!src) { if (!src) {
snprintf(out->error, sizeof(out->error), "Cannot read %s", entry.path); snprintf(out->error, sizeof(out->error), "Cannot read %s", path);
return false; return false;
} }
const char *code = src; bool added = basBuildAddSource(out, path, src, srcLen, wraps[pass]);
char formName[BAS_MAX_IDENT] = "";
BasBuildWrapE wrap = BAS_BUILD_WRAP_MODULE;
entry.firstFileLine = 1;
if (isClass) {
// The class code follows the .cls header
if (!basExtractClassName(src, srcLen, val, formName, BAS_MAX_IDENT)) {
snprintf(out->error, sizeof(out->error), "No class name for %s", entry.path);
free(src); free(src);
if (!added) {
return false; return false;
} }
int32_t codeOff = basClassCodePos(src, srcLen);
code = src + codeOff;
entry.firstFileLine = countLines(src, codeOff) + 1;
wrap = BAS_BUILD_WRAP_CLASS;
}
if (isForm) {
// A .frm without a Begin Form line would otherwise compile
// its code unwrapped, as if it were a module.
if (!basExtractFormName(src, formName, BAS_MAX_IDENT)) {
snprintf(out->error, sizeof(out->error), "No Begin Form line in %s", entry.path);
free(src);
return false;
}
// The BASIC code section follows the outer Begin Form block.
int32_t codeOff = basFindFormEndPos(src, srcLen);
code = src + codeOff;
entry.firstFileLine = countLines(src, codeOff) + 1;
wrap = BAS_BUILD_WRAP_FORM;
}
// A form's BEGINFORM line lists its controls (never longer than
// the .frm text that declares them)
char *ctrlList = isForm ? (char *)malloc((size_t)srcLen + 1) : NULL;
if (isForm && (!ctrlList || basFrmControlList(src, srcLen, ctrlList, srcLen + 1) < 0)) {
snprintf(out->error, sizeof(out->error), "Out of memory.");
free(ctrlList);
free(src);
return false;
}
bool appended = basBuildAppendFile(out, code, (int32_t)strlen(code), wrap, formName, ctrlList, &entry.startLine, &entry.lineCount);
free(ctrlList);
if (!appended) {
free(src);
return false;
}
arrput(out->map, entry);
if (isForm) {
char *frmPath = (char *)malloc(strlen(entry.path) + 1);
if (!frmPath) {
snprintf(out->error, sizeof(out->error), "Out of memory.");
free(src);
return false;
}
strcpy(frmPath, entry.path);
arrput(out->frmPaths, frmPath);
arrput(out->frmTexts, src);
} else {
free(src);
}
out->fileCount++;
} }
} }
@ -413,6 +526,43 @@ bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProje
} }
const char *basBuildControl(const char *outPath, const BasBuildSpecT *spec) {
if (!outPath || !spec || !spec->module) {
return "Invalid build request.";
}
if (spec->ctlCount <= 0) {
return "A control project needs at least one UserControl (.ctl).";
}
uint8_t *modData = NULL;
int32_t modLen = 0;
uint8_t *dbgData = NULL;
int32_t dbgLen = 0;
const char *failure = prepareModule(spec, NULL, NULL, NULL, 0, &modData, &modLen, &dbgData, &dbgLen);
if (!failure) {
failure = writeMarker(outPath);
}
if (!failure && (dvxResAppend(outPath, BAS_RES_MODULE, DVX_RES_BINARY, modData, (uint32_t)modLen) != 0 || (dbgData && dvxResAppend(outPath, BAS_RES_DEBUG, DVX_RES_BINARY, dbgData, (uint32_t)dbgLen) != 0))) {
failure = BAS_BUILD_ERR_WRITE_RES;
}
if (!failure) {
failure = emitControls(outPath, spec);
}
if (!failure) {
failure = copyHelpFile(outPath, spec);
}
free(modData);
free(dbgData);
return failure;
}
void basBuildDirOf(const char *path, char *out, int32_t outSize) { void basBuildDirOf(const char *path, char *out, int32_t outSize) {
snprintf(out, outSize, "%s", path); snprintf(out, outSize, "%s", path);
@ -426,6 +576,17 @@ void basBuildDirOf(const char *path, char *out, int32_t outSize) {
} }
const char *basBuildErrorText(const char *message) {
if (strncasecmp(message, BAS_BUILD_LINE_PREFIX, sizeof(BAS_BUILD_LINE_PREFIX) - 1) != 0) {
return message;
}
const char *colon = strchr(message, ':');
return colon ? dvxSkipWs(colon + 1) : message;
}
void basBuildFreeProject(BasProjectSourceT *out) { void basBuildFreeProject(BasProjectSourceT *out) {
for (int32_t i = 0; i < (int32_t)arrlen(out->frmPaths); i++) { for (int32_t i = 0; i < (int32_t)arrlen(out->frmPaths); i++) {
free(out->frmPaths[i]); free(out->frmPaths[i]);
@ -435,14 +596,44 @@ void basBuildFreeProject(BasProjectSourceT *out) {
free(out->frmTexts[i]); free(out->frmTexts[i]);
} }
for (int32_t i = 0; i < (int32_t)arrlen(out->ctlTexts); i++) {
free(out->ctlTexts[i]);
}
arrfree(out->frmPaths); arrfree(out->frmPaths);
arrfree(out->frmTexts); arrfree(out->frmTexts);
arrfree(out->ctlTexts);
arrfree(out->map); arrfree(out->map);
free(out->source); free(out->source);
memset(out, 0, sizeof(*out)); memset(out, 0, sizeof(*out));
} }
bool basBuildIsControlProject(PrefsHandleT *prefs) {
return basBuildIsControlType(prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_TYPE, ""));
}
bool basBuildIsControlType(const char *type) {
return type && strcasecmp(type, BAS_PROJECT_TYPE_CONTROL) == 0;
}
int32_t basBuildMapFileLine(const BasSourceMapT *map, const char *path, int32_t fileLine) {
for (int32_t i = 0; i < (int32_t)arrlen(map); i++) {
const BasSourceMapT *m = &map[i];
if (pathsEqual(m->path, path)) {
int32_t offset = fileLine - m->firstFileLine;
return (offset >= 0 && offset < m->lineCount) ? m->startLine + offset : 0;
}
}
return 0;
}
bool basBuildMapLine(const BasSourceMapT *map, int32_t line, const char **path, int32_t *fileLine) { bool basBuildMapLine(const BasSourceMapT *map, int32_t line, const char **path, int32_t *fileLine) {
for (int32_t i = 0; i < (int32_t)arrlen(map); i++) { for (int32_t i = 0; i < (int32_t)arrlen(map); i++) {
const BasSourceMapT *m = &map[i]; const BasSourceMapT *m = &map[i];
@ -480,6 +671,35 @@ bool basBuildOpenProject(const char *dbpPath, PrefsHandleT **prefs, char *projec
} }
void basBuildSpecFromProject(PrefsHandleT *prefs, const char *projectDir, BasBuildSpecT *spec) {
spec->projName = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_NAME, "");
spec->author = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_AUTHOR, "");
spec->publisher = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_PUBLISHER, "");
spec->version = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_VERSION, "");
spec->copyright = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_COPYRIGHT, "");
spec->description = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_DESCRIPTION, "");
spec->iconPath = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_ICON, "");
spec->helpFile = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_HELPFILE, "");
spec->multiInstance = prefsGetBool(prefs, BAS_INI_SECTION_SETTINGS, BAS_INI_KEY_MULTIINSTANCE, false);
spec->projectDir = projectDir;
}
BasBuildWrapE basBuildWrapForPath(const char *path) {
const char *base = platformPathBaseName(path);
if (dvxHasExt(base, BAS_FRM_EXT)) {
return BAS_BUILD_WRAP_FORM;
}
if (dvxHasExt(base, BAS_CTL_EXT)) {
return BAS_BUILD_WRAP_CONTROL;
}
return dvxHasExt(base, BAS_CLS_EXT) ? BAS_BUILD_WRAP_CLASS : BAS_BUILD_WRAP_MODULE;
}
static void buildLog(const BasBuildSpecT *spec, const char *fmt, ...) { static void buildLog(const BasBuildSpecT *spec, const char *fmt, ...) {
if (!spec->log) { if (!spec->log) {
return; return;
@ -534,8 +754,88 @@ static int32_t countLines(const char *text, int32_t len) {
} }
// Embed the project icon, or the NOICON fallback carried by the running // A control file's resources after its module: the project's metadata
// executable when the project names none. Returns NULL on success, else // (not its name: "name" is the first control's toolbox name), the help
// file's name, then for each UserControl its header (CTLn), its toolbox
// picture (icon24, the header's ToolboxIcon, a file in the project
// folder) and its toolbox name (the header's Description, else its own
// name), the second and later with "-n" suffixes.
static const char *emitControls(const char *outPath, const BasBuildSpecT *spec) {
int32_t rc = 0;
rc |= appendText(outPath, BAS_RES_AUTHOR, spec->author);
rc |= appendText(outPath, BAS_RES_PUBLISHER, spec->publisher);
rc |= appendText(outPath, BAS_RES_VERSION, spec->version);
rc |= appendText(outPath, BAS_RES_COPYRIGHT, spec->copyright);
rc |= appendText(outPath, BAS_RES_DESCRIPTION, spec->description);
if (spec->helpFile && spec->helpFile[0]) {
rc |= appendText(outPath, BAS_RES_HELPFILE, platformPathBaseName(spec->helpFile));
}
for (int32_t i = 0; rc == 0 && i < spec->ctlCount; i++) {
const char *text = spec->ctlSources[i];
int32_t len = (int32_t)strlen(text);
int32_t index = i + BAS_BUILD_CTL_FIRST;
char value[DVX_MAX_PATH];
char resName[BAS_CTL_RES_NAME_LEN];
if (basCtlHeaderValue(text, len, BAS_UC_KEY_TOOLBOXICON, value, (int32_t)sizeof(value)) && value[0]) {
char iconPath[DVX_MAX_PATH];
int32_t iconLen = 0;
snprintf(iconPath, sizeof(iconPath), "%s" BAS_BUILD_SEP "%s", spec->projectDir, value);
void *iconData = platformReadFile(iconPath, &iconLen);
if (!iconData) {
snprintf(sIconError, sizeof(sIconError), "Cannot read toolbox picture %s", iconPath);
return sIconError;
}
snprintf(resName, sizeof(resName), (index == BAS_BUILD_CTL_FIRST) ? "%s" : BAS_CTL_RES_SUFFIX_FMT, BAS_CTL_RES_ICON, (int)index);
rc |= dvxResAppend(outPath, resName, DVX_RES_ICON, iconData, (uint32_t)iconLen);
free(iconData);
}
if (!basCtlHeaderValue(text, len, BAS_UC_KEY_DESCRIPTION, value, (int32_t)sizeof(value)) || !value[0]) {
basExtractCtlName(text, len, value, (int32_t)sizeof(value));
}
snprintf(resName, sizeof(resName), (index == BAS_BUILD_CTL_FIRST) ? "%s" : BAS_CTL_RES_SUFFIX_FMT, BAS_CTL_RES_NAME, (int)index);
rc |= appendText(outPath, resName, value);
}
if (rc != 0) {
return BAS_BUILD_ERR_WRITE_RES;
}
return emitCtlTexts(outPath, spec);
}
// The UserControls' header texts, CTL0, CTL1 ...
static const char *emitCtlTexts(const char *outPath, const BasBuildSpecT *spec) {
if (spec->ctlCount > BAS_MAX_CTL_RESOURCES) {
return "Too many UserControls in one project.";
}
for (int32_t i = 0; i < spec->ctlCount; i++) {
char resName[BAS_RES_FORM_NAME_LEN];
snprintf(resName, sizeof(resName), BAS_RES_CTL_FMT, (long)i);
if (appendText(outPath, resName, spec->ctlSources[i]) != 0) {
return BAS_BUILD_ERR_WRITE_RES;
}
}
return NULL;
}
// Embed the project icon, or the NOICON fallback of the stub carrier
// when the project names none. Returns NULL on success, else
// an error message (a named icon that cannot be read names the path). // an error message (a named icon that cannot be read names the path).
static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec) { static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec) {
char iconPath[DVX_MAX_PATH]; char iconPath[DVX_MAX_PATH];
@ -554,7 +854,7 @@ static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec) {
return sIconError; return sIconError;
} }
} else { } else {
DvxResHandleT *selfRes = dvxResOpen(spec->selfPath); DvxResHandleT *selfRes = dvxResOpen(stubCarrier(spec));
if (selfRes) { if (selfRes) {
uint32_t size = 0; uint32_t size = 0;
@ -601,6 +901,10 @@ static const char *emitResources(const char *outPath, const BasBuildSpecT *spec,
rc |= appendText(outPath, BAS_RES_HELPFILE, platformPathBaseName(spec->helpFile)); rc |= appendText(outPath, BAS_RES_HELPFILE, platformPathBaseName(spec->helpFile));
} }
if (spec->multiInstance) {
rc |= appendText(outPath, BAS_RES_MULTIINSTANCE, BAS_RES_FLAG_ON);
}
rc |= dvxResAppend(outPath, BAS_RES_MODULE, DVX_RES_BINARY, modData, (uint32_t)modLen); rc |= dvxResAppend(outPath, BAS_RES_MODULE, DVX_RES_BINARY, modData, (uint32_t)modLen);
if (dbgData && dbgLen > 0) { if (dbgData && dbgLen > 0) {
@ -644,7 +948,11 @@ static const char *emitResources(const char *outPath, const BasBuildSpecT *spec,
outIdx++; outIdx++;
} }
return (rc == 0) ? NULL : BAS_BUILD_ERR_WRITE_RES; if (rc != 0) {
return BAS_BUILD_ERR_WRITE_RES;
}
return emitCtlTexts(outPath, spec);
} }
@ -661,6 +969,22 @@ static void freeFrmWork(uint8_t **frmData, int32_t *frmLens, BasObfFrmT *obfFrms
} }
// True when a and b name the same file the way DOS compares names:
// letters in any case, '/' and '\\' alike.
static bool pathsEqual(const char *a, const char *b) {
for (; *a && *b; a++, b++) {
bool sepA = (*a == '/' || *a == '\\');
bool sepB = (*b == '/' || *b == '\\');
if (sepA != sepB || (!sepA && tolower((unsigned char)*a) != tolower((unsigned char)*b))) {
return false;
}
}
return *a == *b;
}
// Serialize the module (and, for debug builds, its debug info). Release // Serialize the module (and, for debug builds, its debug info). Release
// builds run strip -> obfuscate -> compact on a private copy so the // builds run strip -> obfuscate -> compact on a private copy so the
// caller's module stays intact; obfuscation also fills obfFrms. // caller's module stays intact; obfuscation also fills obfFrms.
@ -727,10 +1051,32 @@ static const char *prepareModule(const BasBuildSpecT *spec, uint8_t **frmData, i
} }
// Extract the STUB resource from the running executable and write it as // The file carrying the STUB and NOICON resources.
static const char *stubCarrier(const BasBuildSpecT *spec) {
return (spec->selfPath && spec->selfPath[0]) ? spec->selfPath : BAS_BUILD_STUB_CARRIER;
}
// A control file starts with the resource-only marker (no code); its
// resources follow.
static const char *writeMarker(const char *outPath) {
FILE *f = fopen(outPath, "wb");
if (!f) {
return "Cannot create output file.";
}
size_t written = fwrite(DVX_RES_ONLY_MAGIC, 1, (size_t)DVX_RES_ONLY_MAGIC_LEN, f);
int32_t closed = fclose(f);
return (written == (size_t)DVX_RES_ONLY_MAGIC_LEN && closed == 0) ? NULL : "Failed writing output file.";
}
// Extract the STUB resource from the carrier (stubCarrier) and write it as
// the output file; every resource is appended behind it. // the output file; every resource is appended behind it.
static const char *writeStub(const char *outPath, const BasBuildSpecT *spec) { static const char *writeStub(const char *outPath, const BasBuildSpecT *spec) {
DvxResHandleT *selfRes = dvxResOpen(spec->selfPath); DvxResHandleT *selfRes = dvxResOpen(stubCarrier(spec));
if (!selfRes) { if (!selfRes) {
return "Cannot open own executable to read embedded stub."; return "Cannot open own executable to read embedded stub.";

View file

@ -36,6 +36,7 @@
#ifndef BAS_BUILD_H #ifndef BAS_BUILD_H
#define BAS_BUILD_H #define BAS_BUILD_H
#include "compiler/compiler.h"
#include "runtime/vm.h" #include "runtime/vm.h"
#include "dvxPrefs.h" #include "dvxPrefs.h"
#include "dvxTypes.h" #include "dvxTypes.h"
@ -65,8 +66,8 @@ typedef struct {
const char *iconPath; // NULL/empty: embed the NOICON fallback const char *iconPath; // NULL/empty: embed the NOICON fallback
const char *helpFile; // NULL/empty: no help file const char *helpFile; // NULL/empty: no help file
// Path of the running executable; it carries the STUB and NOICON // A file carrying the STUB and NOICON resources (bascomp: itself);
// resources. // NULL or "" means BAS_BUILD_STUB_CARRIER.
const char *selfPath; const char *selfPath;
// Compiled module. Never modified. // Compiled module. Never modified.
@ -77,10 +78,21 @@ typedef struct {
const char *const *frmSources; const char *const *frmSources;
int32_t frmCount; int32_t frmCount;
// .ctl header texts (the "Begin UserControl ... End" block of each, as
// basBuildAddSource keeps them), one per UserControl: CTLn resources.
const char *const *ctlSources;
int32_t ctlCount;
bool release; bool release;
bool multiInstance; // [Settings] MultiInstance: more than one copy may run
BasBuildLogFnT log; // may be NULL BasBuildLogFnT log; // may be NULL
} BasBuildSpecT; } BasBuildSpecT;
// Where a build inside DVX finds the STUB and NOICON resources: the
// basdev library carries them (it is installed wherever programs can be
// compiled), relative to the DVX folder like every installed path.
#define BAS_BUILD_STUB_CARRIER "libs" DVX_PATH_SEP "kpunch" DVX_PATH_SEP "basdev" DVX_PATH_SEP "basdev.lib"
// Build outPath from spec. Returns NULL on success, otherwise a static // Build outPath from spec. Returns NULL on success, otherwise a static
// error message. On failure outPath may be left partially written. // error message. On failure outPath may be left partially written.
const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec); const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec);
@ -99,12 +111,14 @@ typedef struct {
} BasSourceMapT; } BasSourceMapT;
// A project's BASIC source as the compiler consumes it: every [Modules] // A project's BASIC source as the compiler consumes it: every [Modules]
// file in order wrapped in BEGINMODULE "name" / ENDMODULE, then the code of every [Classes] file wrapped in // file in order wrapped in BEGINMODULE "name" / ENDMODULE, then the code
// BEGINCLASS "name" / ENDCLASS, then the code section of every [Forms] // of every [Classes] file wrapped in BEGINCLASS "name" / ENDCLASS, then
// file wrapped in BEGINFORM "name" / ENDFORM, each file ending on a // the code of every [Controls] file wrapped in BEGINCLASS "name"
// newline. frmPaths // USERCONTROL / ENDCLASS, then the code section of every [Forms] file
// and frmTexts list the .frm files (path and full text, as read) in the // wrapped in BEGINFORM "name" / ENDFORM, each file ending on a newline.
// same order for the resource step; map locates each file's lines. // frmPaths and frmTexts list the .frm files (path and full text, as read)
// in the same order for the resource step, ctlTexts the .ctl headers;
// map locates each file's lines.
typedef struct { typedef struct {
char *source; // NUL-terminated, malloc'd char *source; // NUL-terminated, malloc'd
int32_t sourceLen; int32_t sourceLen;
@ -112,8 +126,9 @@ typedef struct {
int32_t lineCount; // newlines in source (next text starts on lineCount + 1) int32_t lineCount; // newlines in source (next text starts on lineCount + 1)
char **frmPaths; // stb_ds array of malloc'd paths char **frmPaths; // stb_ds array of malloc'd paths
char **frmTexts; // stb_ds array of malloc'd .frm texts char **frmTexts; // stb_ds array of malloc'd .frm texts
char **ctlTexts; // stb_ds array of malloc'd .ctl header texts (Begin UserControl ... End)
BasSourceMapT *map; // stb_ds array, one entry per file BasSourceMapT *map; // stb_ds array, one entry per file
int32_t fileCount; // modules + classes + forms int32_t fileCount; // modules + classes + controls + forms
bool optionExplicit; // [Settings] OptionExplicit bool optionExplicit; // [Settings] OptionExplicit
char error[BAS_BUILD_ERROR_LEN]; char error[BAS_BUILD_ERROR_LEN];
} BasProjectSourceT; } BasProjectSourceT;
@ -123,13 +138,16 @@ typedef enum {
BAS_BUILD_WRAP_NONE, // the text as it is (a lone program) BAS_BUILD_WRAP_NONE, // the text as it is (a lone program)
BAS_BUILD_WRAP_MODULE, // a project's module: BEGINMODULE "name" / ENDMODULE (its Private CONST / ENUM stay in it) BAS_BUILD_WRAP_MODULE, // a project's module: BEGINMODULE "name" / ENDMODULE (its Private CONST / ENUM stay in it)
BAS_BUILD_WRAP_FORM, // a form's code: BEGINFORM "name" / ENDFORM BAS_BUILD_WRAP_FORM, // a form's code: BEGINFORM "name" / ENDFORM
BAS_BUILD_WRAP_CLASS // a class module's code: BEGINCLASS "name" / ENDCLASS BAS_BUILD_WRAP_CLASS, // a class module's code: BEGINCLASS "name" / ENDCLASS
BAS_BUILD_WRAP_CONTROL // a UserControl's code: BEGINCLASS "name" USERCONTROL / ENDCLASS
} BasBuildWrapE; } BasBuildWrapE;
// Appends one file's text to *ps (a zeroed struct is a valid empty // Appends one file's text to *ps (a zeroed struct is a valid empty
// project) and makes sure it ends on a newline, wrapped as wrap says in // project) and makes sure it ends on a newline, wrapped as wrap says in
// the directives naming name; a form's BEGINFORM line also lists its // the directives naming name; a form's BEGINFORM line also lists its
// controls, ctrlList (basFrmControlList's text; NULL or "" for none). // controls, ctrlList (basFrmControlList's text; NULL or "" for none),
// and a UserControl's BEGINCLASS line its USERCONTROL mark (ctrlList is
// not used).
// textLine and textLines (either may be // textLine and textLines (either may be
// NULL) receive the 1-based line the text starts on and the number of // NULL) receive the 1-based line the text starts on and the number of
// lines it occupies, wrapper lines excluded -- what an IDE source map // lines it occupies, wrapper lines excluded -- what an IDE source map
@ -147,18 +165,88 @@ void basBuildDirOf(const char *path, char *out, int32_t outSize);
// Releases everything basBuildConcatProject allocated. // Releases everything basBuildConcatProject allocated.
void basBuildFreeProject(BasProjectSourceT *out); void basBuildFreeProject(BasProjectSourceT *out);
// Why basBuildCompile failed.
typedef enum {
BAS_COMPILE_OK, // compiled
BAS_COMPILE_ERROR, // an error in the source (line / col set)
BAS_COMPILE_NO_MEMORY, // out of memory
BAS_COMPILE_NO_MODULE // compiled, but the module could not be built
} BasCompileResultE;
typedef struct {
BasCompileResultE result;
int32_t line; // 1-based line of the source (0: none)
int32_t col; // 1-based column (0: not known)
char message[BAS_BUILD_ERROR_LEN]; // basCompilerError's text, or the failure
} BasCompileErrorT;
// Compiles source (len bytes) into a module (the caller frees it with
// basModuleFree), with OPTION EXPLICIT for the whole program when
// optionExplicit is set and validator (may be NULL) checking
// CtrlName.Member references. *liveCompiler (may be NULL) holds the
// compiler while it runs: a validator reads its form scope from it.
// NULL on failure, with *err telling why.
BasModuleT *basBuildCompile(const char *source, int32_t len, bool optionExplicit, const BasCtrlValidatorT *validator, const BasCompilerT **liveCompiler, BasCompileErrorT *err);
// A compiler message without its "Line N, Col M: " prefix.
const char *basBuildErrorText(const char *message);
// Appends one source file (path names it in the source map; text is its
// whole text, len bytes, NUL-terminated) the way a project is built: a
// module wrapped in BEGINMODULE, a class module's code after its header
// in BEGINCLASS, a UserControl's code after its header in BEGINCLASS ...
// USERCONTROL (the header is kept in ctlTexts), a form's code section in
// BEGINFORM with its controls (the .frm text is kept in frmPaths /
// frmTexts for the resources and the run), a lone program
// (BAS_BUILD_WRAP_NONE) as it is. Returns false
// with ps->error set on failure.
bool basBuildAddSource(BasProjectSourceT *ps, const char *path, const char *text, int32_t len, BasBuildWrapE wrap);
// Maps a 1-based line of the concatenated source to the file and 1-based // Maps a 1-based line of the concatenated source to the file and 1-based
// line it came from, using a source map (stb_ds array, e.g. ps->map). // line it came from, using a source map (stb_ds array, e.g. ps->map).
// Returns false (outputs untouched) for a line outside every file, e.g. // Returns false (outputs untouched) for a line outside every file, e.g.
// a BEGINFORM/ENDFORM wrapper line. // a BEGINFORM/ENDFORM wrapper line.
bool basBuildMapLine(const BasSourceMapT *map, int32_t line, const char **path, int32_t *fileLine); bool basBuildMapLine(const BasSourceMapT *map, int32_t line, const char **path, int32_t *fileLine);
// The inverse of basBuildMapLine: the 1-based line of the concatenated
// source holding line fileLine of path (compared as DOS compares names:
// letters in any case, '/' and '\\' alike),
// or 0 when path is not in the map or the line was not compiled (a class
// module's header, a .frm's layout).
int32_t basBuildMapFileLine(const BasSourceMapT *map, const char *path, int32_t fileLine);
// Opens a .dbp project: *prefs receives the loaded INI and projectDir // Opens a .dbp project: *prefs receives the loaded INI and projectDir
// (projectDirSize bytes) the directory holding it. Returns false when // (projectDirSize bytes) the directory holding it. Returns false when
// the file cannot be opened (prefsLoad alone would open a missing file // the file cannot be opened (prefsLoad alone would open a missing file
// as an empty project). // as an empty project).
bool basBuildOpenProject(const char *dbpPath, PrefsHandleT **prefs, char *projectDir, int32_t projectDirSize); bool basBuildOpenProject(const char *dbpPath, PrefsHandleT **prefs, char *projectDir, int32_t projectDirSize);
// Fills spec's project metadata (name, author, publisher, version,
// copyright, description, icon, help file, MultiInstance) and projectDir
// from an open .dbp; the strings stay owned by prefs. The other fields
// are left alone.
void basBuildSpecFromProject(PrefsHandleT *prefs, const char *projectDir, BasBuildSpecT *spec);
// Build outPath, a control file (.wgt holding only resources: the marker
// DVX_RES_ONLY_MAGIC, then the module, each UserControl's header as CTLn
// with its toolbox icon24 and name resources -- "-2", "-3" ... for the
// second and later -- the project's metadata and help file) from spec, a
// control project's (no forms; ctlSources needed). NULL on success,
// else a static error message.
const char *basBuildControl(const char *outPath, const BasBuildSpecT *spec);
// True when the open .dbp is a control project ([Project] Type = Control).
bool basBuildIsControlProject(PrefsHandleT *prefs);
// True when type, a [Project] Type value, makes a control project (any
// case).
bool basBuildIsControlType(const char *type);
// The kind of project file path names, by its extension: a form (.frm),
// a class module (.cls), a UserControl (.ctl) or a module (anything
// else).
BasBuildWrapE basBuildWrapForPath(const char *path);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -37,7 +37,9 @@
#include <string.h> // memchr, strlen #include <string.h> // memchr, strlen
#include <strings.h> // strncasecmp #include <strings.h> // strncasecmp
#include "dvxRes.h" // DVX_RES_NAME_* resource names
#include "formrt/frmParser.h" // FRM_KW_* .frm keyword spellings #include "formrt/frmParser.h" // FRM_KW_* .frm keyword spellings
#include "compiler/basEvents.h" // BAS_UC_OBJECT, a .ctl header's block keyword
// dvxSkipWs is declared in platform/dvxPlat.h, which every translation // dvxSkipWs is declared in platform/dvxPlat.h, which every translation
// unit that includes this header also includes. We forward-declare it // unit that includes this header also includes. We forward-declare it
@ -65,8 +67,12 @@ const char *dvxSkipWs(const char *s);
#define BAS_INI_SECTION_SETTINGS "Settings" #define BAS_INI_SECTION_SETTINGS "Settings"
#define BAS_INI_KEY_STARTUPFORM "StartupForm" #define BAS_INI_KEY_STARTUPFORM "StartupForm"
#define BAS_INI_KEY_OPTIONEXPLICIT "OptionExplicit" #define BAS_INI_KEY_OPTIONEXPLICIT "OptionExplicit"
#define BAS_INI_KEY_MULTIINSTANCE "MultiInstance" // True: more than one copy may run at once
#define BAS_INI_SECTION_MODULES "Modules" #define BAS_INI_SECTION_MODULES "Modules"
#define BAS_INI_SECTION_CLASSES "Classes" #define BAS_INI_SECTION_CLASSES "Classes"
#define BAS_INI_SECTION_CONTROLS "Controls" // .ctl UserControl files
#define BAS_INI_KEY_TYPE "Type" // [Project] Type: Program (the default) or Control
#define BAS_PROJECT_TYPE_CONTROL "Control" // a control project: builds NAME.wgt
#define BAS_INI_SECTION_FORMS "Forms" #define BAS_INI_SECTION_FORMS "Forms"
// ------------------------------------------------------------ // ------------------------------------------------------------
@ -88,6 +94,20 @@ const char *dvxSkipWs(const char *s);
#define BAS_CLS_ATTR_PREFIX "VB_" #define BAS_CLS_ATTR_PREFIX "VB_"
#define BAS_CLS_ATTR_NAME "VB_Name" #define BAS_CLS_ATTR_NAME "VB_Name"
#define BAS_CLS_EXT ".cls" #define BAS_CLS_EXT ".cls"
#define BAS_CTL_EXT ".ctl" // a UserControl (header Begin UserControl Name ... End, then class code)
// The "Key = Value" lines of a .ctl header's Begin UserControl block.
#define BAS_UC_KEY_WIDTH "Width"
#define BAS_UC_KEY_HEIGHT "Height"
#define BAS_UC_KEY_CANGETFOCUS "CanGetFocus"
#define BAS_UC_KEY_BACKCOLOR "BackColor"
#define BAS_UC_KEY_TOOLBOXICON "ToolboxIcon"
#define BAS_UC_KEY_DESCRIPTION "Description"
#define BAS_UC_KEY_DEFAULTEVENT "DefaultEvent"
#define BAS_QUOTE_PAIR_LEN 2 // a quoted header value's two quote characters
#define BAS_APP_EXT ".app" // what a program project builds
#define BAS_WGT_EXT ".wgt" // what a control project builds
#define BAS_FRM_EXT ".frm" // a form file
// ------------------------------------------------------------ // ------------------------------------------------------------
// Resource names inside a compiled .app DXE // Resource names inside a compiled .app DXE
@ -106,9 +126,11 @@ const char *dvxSkipWs(const char *s);
#define BAS_RES_COPYRIGHT "copyright" #define BAS_RES_COPYRIGHT "copyright"
#define BAS_RES_DESCRIPTION "description" #define BAS_RES_DESCRIPTION "description"
#define BAS_RES_HELPFILE "helpfile" #define BAS_RES_HELPFILE "helpfile"
#define BAS_RES_MULTIINSTANCE "multiinstance" // present: [Settings] MultiInstance = True
#define BAS_RES_FLAG_ON "1" // text of a flag resource that is present
// Icon -- 32x32 application icon // Icon -- 32x32 application icon
#define BAS_RES_ICON32 "icon32" #define BAS_RES_ICON32 DVX_RES_NAME_ICON
// Binary payload -- bytecode + debug info // Binary payload -- bytecode + debug info
#define BAS_RES_MODULE "MODULE" #define BAS_RES_MODULE "MODULE"
@ -124,6 +146,22 @@ const char *dvxSkipWs(const char *s);
#define BAS_RES_FORM_FMT "FORM%ld" #define BAS_RES_FORM_FMT "FORM%ld"
#define BAS_RES_FORM_NAME_LEN 16 // buffer for a formatted FORMn resource name #define BAS_RES_FORM_NAME_LEN 16 // buffer for a formatted FORMn resource name
// Per-UserControl header text ("Begin UserControl Name ... End" of its
// .ctl), CTL0, CTL1, ... dense like FORMn: in a program (its own
// controls) and in a control file (.wgt). BAS_MAX_CTL_RESOURCES bounds
// the writer and the readers alike.
#define BAS_RES_CTL_FMT "CTL%ld"
#define BAS_MAX_CTL_RESOURCES 64
// A widget file's toolbox resources (formrt basCtlResPath, the IDE
// toolbox, a control project's build): the 24 x 24 icon and the tooltip
// text, room for a resource name, and the "-n" suffix of the second and
// later widgets of one file ("icon24-2").
#define BAS_CTL_RES_ICON DVX_RES_NAME_WIDGET_ICON
#define BAS_CTL_RES_NAME BAS_RES_NAME
#define BAS_CTL_RES_NAME_LEN DVX_RES_NAME_MAX
#define BAS_CTL_RES_SUFFIX_FMT "%s-%d"
// ------------------------------------------------------------ // ------------------------------------------------------------
// Form resource scan limit // Form resource scan limit
// ------------------------------------------------------------ // ------------------------------------------------------------
@ -144,12 +182,15 @@ const char *dvxSkipWs(const char *s);
static inline int32_t basClassCodePos(const char *text, int32_t len); static inline int32_t basClassCodePos(const char *text, int32_t len);
static inline bool basClassHeaderLine(const char *s, int32_t len); static inline bool basClassHeaderLine(const char *s, int32_t len);
static inline bool basClassNameAttr(const char *text, int32_t len, int32_t *outStart, int32_t *outLen); static inline bool basClassNameAttr(const char *text, int32_t len, int32_t *outStart, int32_t *outLen);
static inline bool basCtlHeaderValue(const char *text, int32_t len, const char *key, char *out, int32_t outSize);
static inline bool basExtractClassName(const char *text, int32_t len, const char *path, char *nameBuf, int32_t nameBufSize); static inline bool basExtractClassName(const char *text, int32_t len, const char *path, char *nameBuf, int32_t nameBufSize);
static inline bool basExtractCtlName(const char *text, int32_t len, char *nameBuf, int32_t nameBufSize);
static inline bool basExtractFormName(const char *frmText, char *nameBuf, int32_t nameBufSize); static inline bool basExtractFormName(const char *frmText, char *nameBuf, int32_t nameBufSize);
static inline int32_t basFindFormEndPos(const char *text, int32_t len); static inline int32_t basFindFormEndPos(const char *text, int32_t len);
static inline int32_t basFrmControlList(const char *frmText, int32_t frmLen, char *out, int32_t outSize); static inline int32_t basFrmControlList(const char *frmText, int32_t frmLen, char *out, int32_t outSize);
static inline bool basFrmListHas(const char *list, int32_t listLen, const char *name, int32_t nameLen); static inline bool basFrmListHas(const char *list, int32_t listLen, const char *name, int32_t nameLen);
static inline bool basLineIsWord(const char *s, int32_t len, const char *word); static inline bool basLineIsWord(const char *s, int32_t len, const char *word);
static inline int32_t basMatchBeginControl(const char *s, int32_t len);
static inline int32_t basMatchBeginForm(const char *s, int32_t len); static inline int32_t basMatchBeginForm(const char *s, int32_t len);
static inline int32_t basMatchKeywordWs(const char *s, int32_t len, const char *kw, int32_t kwLen); static inline int32_t basMatchKeywordWs(const char *s, int32_t len, const char *kw, int32_t kwLen);
@ -241,6 +282,82 @@ static inline bool basClassNameAttr(const char *text, int32_t len, int32_t *outS
} }
// The value of key in a .ctl header's "Begin UserControl <name> ... End"
// block ("Key = Value" lines, the value without surrounding quotes) in
// out (outSize bytes). False when the block has no such line.
static inline bool basCtlHeaderValue(const char *text, int32_t len, const char *key, char *out, int32_t outSize) {
int32_t keyLen = (int32_t)strlen(key);
int32_t pos = 0;
bool inBlock = false;
while (pos < len) {
int32_t lineStart = pos;
while (pos < len && text[pos] != '\n') {
pos++;
}
int32_t lineEnd = pos++;
while (lineEnd > lineStart && (text[lineEnd - 1] == '\r' || text[lineEnd - 1] == ' ' || text[lineEnd - 1] == '\t')) {
lineEnd--;
}
int32_t l = lineStart;
while (l < lineEnd && (text[l] == ' ' || text[l] == '\t')) {
l++;
}
if (!inBlock) {
inBlock = basMatchBeginControl(text + l, lineEnd - l) >= 0;
continue;
}
if (frmIsEndLine(text + l, lineEnd - l)) {
return false;
}
// Key, blanks, '=', blanks, the value
if (lineEnd - l <= keyLen || strncasecmp(text + l, key, keyLen) != 0) {
continue;
}
int32_t v = l + keyLen;
while (v < lineEnd && (text[v] == ' ' || text[v] == '\t')) {
v++;
}
if (v >= lineEnd || text[v] != '=') {
continue;
}
v++;
while (v < lineEnd && (text[v] == ' ' || text[v] == '\t')) {
v++;
}
// A quoted value loses its two quotes
int32_t valueLen = lineEnd - v;
if (valueLen >= BAS_QUOTE_PAIR_LEN && text[v] == '"' && text[lineEnd - 1] == '"') {
v++;
valueLen -= BAS_QUOTE_PAIR_LEN;
}
int32_t n = (valueLen < outSize - 1) ? valueLen : outSize - 1;
memcpy(out, text + v, (size_t)n);
out[n] = '\0';
return true;
}
return false;
}
// The class name of a .cls text: its Attribute VB_Name = "name" when the // The class name of a .cls text: its Attribute VB_Name = "name" when the
// header has one, else the base name of path (without directory and // header has one, else the base name of path (without directory and
// extension). Returns false when neither gives a name. // extension). Returns false when neither gives a name.
@ -280,6 +397,48 @@ static inline bool basExtractClassName(const char *text, int32_t len, const char
} }
// The UserControl name of a .ctl text: the name on its header's "Begin
// UserControl <name>" line (len bytes are looked at). False when there
// is none.
static inline bool basExtractCtlName(const char *text, int32_t len, char *nameBuf, int32_t nameBufSize) {
int32_t pos = 0;
nameBuf[0] = '\0';
while (pos < len) {
int32_t lineStart = pos;
while (pos < len && text[pos] != '\n') {
pos++;
}
int32_t l = lineStart;
while (l < pos && (text[l] == ' ' || text[l] == '\t')) {
l++;
}
int32_t off = basMatchBeginControl(text + l, pos - l);
if (off >= 0) {
int32_t n = 0;
while (l + off + n < pos && n < nameBufSize - 1 && text[l + off + n] != ' ' && text[l + off + n] != '\t' && text[l + off + n] != '\r') {
n++;
}
memcpy(nameBuf, text + l + off, (size_t)n);
nameBuf[n] = '\0';
return n > 0;
}
pos++;
}
return false;
}
// Extract the form name from a "Begin Form <name>" line in .frm text. // Extract the form name from a "Begin Form <name>" line in .frm text.
// Writes a NUL-terminated name into nameBuf (max nameBufSize bytes, // Writes a NUL-terminated name into nameBuf (max nameBufSize bytes,
// including the terminator) and returns true if a name was found. // including the terminator) and returns true if a name was found.
@ -512,6 +671,22 @@ static inline bool basLineIsWord(const char *s, int32_t len, const char *word) {
} }
// Match "Begin<ws>UserControl<ws>" at s[0..len) (s already left-
// trimmed): a .ctl header. Returns the offset of the control's name, or
// -1.
static inline int32_t basMatchBeginControl(const char *s, int32_t len) {
int32_t b = basMatchKeywordWs(s, len, FRM_KW_BEGIN, FRM_KW_BEGIN_LEN);
if (b < 0) {
return -1;
}
int32_t c = basMatchKeywordWs(s + b, len - b, BAS_UC_OBJECT, (int32_t)sizeof(BAS_UC_OBJECT) - 1);
return (c < 0) ? -1 : b + c;
}
// Match "Begin<ws>Form<ws>" at s[0..len) (s already left-trimmed). The // Match "Begin<ws>Form<ws>" at s[0..len) (s already left-trimmed). The
// single definition of the Begin Form syntax for basExtractFormName and // single definition of the Begin Form syntax for basExtractFormName and
// basFindFormEndPos. Returns the offset of the form name, or -1. // basFindFormEndPos. Returns the offset of the form name, or -1.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,148 @@
// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// basDebug.h -- the debugger's view of a paused BASIC program
//
// Which variables are visible where the program is paused, their slots
// and display text, variable paths (a(1, 2).field), the call stack, and
// the Immediate / Watch evaluation: an expression or statement is
// compiled as a snippet whose globals are the paused program's visible
// variables (their values copied in) and run in bounded slices.
//
// One implementation for the IDE (ideMain.c) and the basdev library
// (basDevBas.c). A "paused VM" argument is the program's VM while it is
// paused in the debugger, or NULL when there is none: then only
// expressions without the program's variables can be evaluated.
#ifndef BAS_DEBUG_H
#define BAS_DEBUG_H
#include "../runtime/vm.h"
#include <stdbool.h>
#include <stdint.h>
// Frame name of module-level code (the call stack's outermost frame)
#define BAS_DBG_MODULE_FRAME "(module)"
// Type names shown for a Collection and for any other object
#define BAS_DBG_COLLECTION_TYPE "Collection"
#define BAS_DBG_OBJECT_TYPE "Object"
// Watch / Immediate value text
#define BAS_DBG_VALUE_LEN 256
// Locals value column
#define BAS_DBG_LOCAL_VALUE_LEN BAS_MAX_IDENT
// Locals type column ("Integer()", "Variant/ClassName")
#define BAS_DBG_TYPE_LEN (BAS_MAX_IDENT + 16)
// Text of a watch that cannot be evaluated
#define BAS_DBG_EVAL_ERROR "<error>"
// The columns of a basDbgLocals row and of a basDbgCallStack row
#define BAS_DBG_LOCAL_COLS 3 // name, type, value
#define BAS_DBG_STACK_COLS 2 // procedure, line ("" for a caller)
// One display row: cellCount NUL-terminated cells.
typedef void (*BasDbgRowFnT)(void *ctx, const char *const *cells, int32_t cellCount);
// Append text (textLen bytes) to the NUL-terminated output buf of cap
// bytes holding len bytes, dropping whole leading lines while it would
// not fit so the newest text always survives (an oversize chunk keeps
// its tail). Returns the new length.
int32_t basDbgAppendDropping(char *buf, int32_t cap, int32_t len, const char *text, int32_t textLen);
// The call stack where vm is paused, innermost first: the current
// procedure (BAS_DBG_MODULE_FRAME at module level) with curLine when it
// is > 0, then each caller with an empty line cell. Returns the rows.
int32_t basDbgCallStack(BasVmT *vm, int32_t curLine, BasDbgRowFnT fn, void *ctx);
// The compiled procedure the paused vm is in, or -1 at module level.
int32_t basDbgCurProcIdx(const BasVmT *vm);
// A watch: a variable path is read directly, anything else is evaluated
// as an expression in the paused program (pausedVm NULL: fails). out
// receives the value text; false (out undefined) when it cannot be
// evaluated.
bool basDbgEval(BasVmT *pausedVm, const char *expr, char *out, int32_t outSize);
// An Immediate-window line: "var = expr" (LET optional) assigns to the
// paused program's variable; a line starting with PRINT, DIM or LET runs
// as it is; anything else is printed (PRINT expr). Everything it shows
// -- the output, "name = value" after an assignment, "Error: ..." -- goes
// to printFn. True when a variable of the program was changed.
bool basDbgExec(BasVmT *pausedVm, const char *line, BasPrintFnT printFn, void *ctx);
// The variable called name that is visible where vm is paused, or NULL.
const BasDebugVarT *basDbgFindVar(const BasVmT *vm, const char *name);
// Display text of v ("text" in quotes, #date#, arrays with their bounds,
// <ClassName>, Collection (n items) ...). vm names class instances.
void basDbgFormatValue(const BasVmT *vm, const BasValueT *v, char *buf, int32_t bufSize);
// What a class instance or Collection is: its class name, "Collection".
const char *basDbgInstanceTypeName(const BasVmT *vm, const BasObjectT *obj);
// Internal names ("Proc$Var" of a STATIC) the debugger hides.
bool basDbgIsMangledName(const char *name);
// The variables visible where vm is paused, one row each: name, type
// ("Integer()", "Variant/String", a class name ...) and value. Returns
// the rows.
int32_t basDbgLocals(BasVmT *vm, BasDbgRowFnT fn, void *ctx);
// Name of the procedure holding pc (BAS_DBG_MODULE_FRAME before every
// procedure).
const char *basDbgProcNameForPc(const BasVmT *vm, int32_t pc);
// The live slot a variable path names where vm is paused: var, var(i,
// ...), var.field, var(i).field (a UDT field or a class instance's member
// variable). NULL when it does not resolve. *endPtr (may be NULL) is
// set past the path.
BasValueT *basDbgResolvePath(BasVmT *vm, const char *path, const char **endPtr);
// Run a snippet VM (basDbgSnippetVm) in bounded slices, so an endless
// loop cannot hang the host. BAS_VM_STEP_LIMIT means it was abandoned.
BasVmResultE basDbgRunSnippet(BasVmT *vm);
// Set variable path (see basDbgResolvePath) of the paused program to the
// value of expr, converted to its type (scalars only). printFn receives
// "path = value" or why it failed. True when it was set.
bool basDbgSetVar(BasVmT *pausedVm, const char *path, const char *expr, BasPrintFnT printFn, void *ctx);
// Compile src as a snippet and return a VM ready to run it; with a
// paused VM, its visible variables are the snippet's globals, holding
// copies of their values. *outCaptureIdx (when captureName is given)
// receives the global slot of that snippet variable (-1: none). NULL on
// failure with the error text in err. The caller destroys the VM and
// frees *outMod.
BasVmT *basDbgSnippetVm(BasVmT *pausedVm, const char *src, BasModuleT **outMod, char *err, int32_t errSize, const char *captureName, int32_t *outCaptureIdx);
// BASIC name of a BAS_TYPE_* data type ("Integer", "String" ...).
const char *basDbgTypeName(uint8_t dt);
// The live slot of debug variable dv where vm is paused, or NULL.
BasValueT *basDbgVarSlot(BasVmT *vm, const BasDebugVarT *dv);
// Whether dv is in scope where vm is paused: locals of the current
// procedure, module variables (inside a procedure only DIM SHARED ones),
// the variables of the form whose code runs, and the member variables of
// the object a class procedure runs for.
bool basDbgVarVisible(const BasVmT *vm, const BasDebugVarT *dv, int32_t curProcIdx);
#endif // BAS_DEBUG_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,337 @@
// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// basHelpMap.c -- the DVX BASIC help topic of a word or control type
// (see basHelpMap.h)
#include "basHelpMap.h"
#include "dvxTypes.h"
#include <ctype.h>
#include <stddef.h>
#include <stdio.h>
#include <strings.h>
typedef struct {
const char *keyword;
const char *topic;
} BasHelpMapEntryT;
static const BasHelpMapEntryT sHelpMap[] = {
// Data types
{"Boolean", "lang.datatypes"},
{"Double", "lang.datatypes"},
{"Integer", "lang.datatypes"},
{"Long", "lang.datatypes"},
{"Single", "lang.datatypes"},
{"String", "lang.datatypes"},
// Declarations
{"ByRef", "lang.declarations"},
{"ByVal", "lang.declarations"},
{"Const", "lang.declarations"},
{"Declare", "lang.declarations"},
{"Dim", "lang.declarations"},
{"Option", "lang.declarations"},
{"ReDim", "lang.declarations"},
{"Shared", "lang.declarations"},
{"Static", "lang.declarations"},
{"Type", "lang.declarations"},
// Operators
{"And", "lang.operators"},
{"Like", "lang.operators"},
{"Mod", "lang.operators"},
{"Not", "lang.operators"},
{"Or", "lang.operators"},
{"Xor", "lang.operators"},
// Conditionals
{"Case", "lang.conditionals"},
{"Else", "lang.conditionals"},
{"ElseIf", "lang.conditionals"},
{"If", "lang.conditionals"},
{"Select", "lang.conditionals"},
{"Then", "lang.conditionals"},
// Loops
{"Do", "lang.loops"},
{"For", "lang.loops"},
{"Loop", "lang.loops"},
{"Next", "lang.loops"},
{"Step", "lang.loops"},
{"Until", "lang.loops"},
{"Wend", "lang.loops"},
{"While", "lang.loops"},
// Procedures
{"Call", "lang.procedures"},
{"Def", "lang.procedures"},
{"End", "lang.procedures"},
{"Function", "lang.procedures"},
{"Sub", "lang.procedures"},
// Flow control
{"Exit", "lang.flow"},
{"GoSub", "lang.flow"},
{"GoTo", "lang.flow"},
{"On", "lang.flow"},
{"Resume", "lang.flow"},
{"Return", "lang.flow"},
// I/O statements
{"Data", "lang.io"},
{"Input", "lang.io"},
{"Print", "lang.io"},
{"Read", "lang.io"},
{"Rem", "lang.io"},
{"Write", "lang.io"},
// Misc statements
{"DoEvents", "lang.misc"},
{"Err", "lang.misc"},
{"Error", "lang.misc"},
{"Error$", "lang.misc"},
{"Load", "lang.misc"},
{"Shell", "lang.misc"},
{"Sleep", "lang.misc"},
{"Unload", "lang.misc"},
// Sound, joysticks and frames
{"Beep", "lang.sound"},
{"FrameWait", "lang.sound"},
{"Play", "lang.sound"},
{"Sound", "lang.sound"},
{"Stick", "lang.sound"},
{"Strig", "lang.sound"},
// Console and graphics
{"Circle", "lang.screen"},
{"Cls", "lang.screen"},
{"Color", "lang.screen"},
{"CsrLin", "lang.screen"},
{"Draw", "lang.screen"},
{"FullScreen", "lang.screen"},
{"InKey$", "lang.screen"},
{"Locate", "lang.screen"},
{"Paint", "lang.screen"},
{"Palette", "lang.screen"},
{"PCopy", "lang.screen"},
{"Pen", "lang.screen"},
{"PMap", "lang.screen"},
{"Point", "lang.screen"},
{"Pos", "lang.screen"},
{"PReset", "lang.screen"},
{"PSet", "lang.screen"},
{"Screen", "lang.screen"},
{"VarPtr$", "lang.screen"},
{"View", "lang.screen"},
{"Width", "lang.screen"},
{"Window", "lang.screen"},
// File I/O
{"Close", "lang.fileio"},
{"Eof", "lang.func.fileio"},
{"FileAttr", "lang.func.fileio"},
{"FreeFile", "lang.func.fileio"},
{"Loc", "lang.func.fileio"},
{"Lof", "lang.func.fileio"},
{"Open", "lang.fileio"},
{"Reset", "lang.fileio"},
{"Seek", "lang.func.fileio"},
// String functions
{"Asc", "lang.func.string"},
{"Chr", "lang.func.string"},
{"Chr$", "lang.func.string"},
{"Environ", "lang.func.string"},
{"Environ$", "lang.func.string"},
{"Format", "lang.func.string"},
{"Format$", "lang.func.string"},
{"InStr", "lang.func.string"},
{"LCase", "lang.func.string"},
{"LCase$", "lang.func.string"},
{"LTrim", "lang.func.string"},
{"LTrim$", "lang.func.string"},
{"Left", "lang.func.string"},
{"Left$", "lang.func.string"},
{"Len", "lang.func.string"},
{"Mid", "lang.func.string"},
{"Mid$", "lang.func.string"},
{"RTrim", "lang.func.string"},
{"RTrim$", "lang.func.string"},
{"Right", "lang.func.string"},
{"Right$", "lang.func.string"},
{"Spc", "lang.func.string"},
{"Space", "lang.func.string"},
{"Space$", "lang.func.string"},
{"String$", "lang.func.string"},
{"Tab", "lang.func.string"},
{"Trim", "lang.func.string"},
{"Trim$", "lang.func.string"},
{"UCase", "lang.func.string"},
{"UCase$", "lang.func.string"},
// Math functions
{"Abs", "lang.func.math"},
{"Atn", "lang.func.math"},
{"Cos", "lang.func.math"},
{"Exp", "lang.func.math"},
{"Fix", "lang.func.math"},
{"Int", "lang.func.math"},
{"Log", "lang.func.math"},
{"Randomize", "lang.func.math"},
{"Rnd", "lang.func.math"},
{"Sgn", "lang.func.math"},
{"Sin", "lang.func.math"},
{"Sqr", "lang.func.math"},
{"Tan", "lang.func.math"},
{"Timer", "lang.func.math"},
// Conversion functions
{"CBool", "lang.func.conversion"},
{"CDbl", "lang.func.conversion"},
{"CInt", "lang.func.conversion"},
{"CLng", "lang.func.conversion"},
{"CSng", "lang.func.conversion"},
{"CStr", "lang.func.conversion"},
{"Hex", "lang.func.conversion"},
{"Hex$", "lang.func.conversion"},
{"Str", "lang.func.conversion"},
{"Str$", "lang.func.conversion"},
{"Val", "lang.func.conversion"},
// Misc functions
{"InputBox", "lang.func.misc"},
{"InputBox$", "lang.func.misc"},
{"LoadPicture", "lang.func.misc"},
{"MsgBox", "lang.func.misc"},
// SQL functions
{"SQLAffected", "lang.sql"},
{"SQLClose", "lang.sql"},
{"SQLEof", "lang.sql"},
{"SQLError", "lang.sql"},
{"SQLError$", "lang.sql"},
{"SQLExec", "lang.sql"},
{"SQLField", "lang.sql"},
{"SQLField$", "lang.sql"},
{"SQLFieldCount", "lang.sql"},
{"SQLFieldDbl", "lang.sql"},
{"SQLFieldInt", "lang.sql"},
{"SQLFreeResult", "lang.sql"},
{"SQLNext", "lang.sql"},
{"SQLOpen", "lang.sql"},
{"SQLQuery", "lang.sql"},
// App object
{"App", "lang.app"},
// INI functions
{"IniRead", "lang.ini"},
{"IniWrite", "lang.ini"},
// Constants
{"False", "lang.constants"},
{"True", "lang.constants"},
{"vbCancel", "lang.constants"},
{"vbCritical", "lang.constants"},
{"vbModal", "lang.constants"},
{"vbOK", "lang.constants"},
{"vbOKCancel", "lang.constants"},
{"vbYesNo", "lang.constants"},
// Variants
{"Array", "lang.variants"},
{"CVar", "lang.variants"},
{"Empty", "lang.variants"},
{"IsArray", "lang.variants"},
{"IsEmpty", "lang.variants"},
{"IsMissing", "lang.procedures"},
{"IsNull", "lang.variants"},
{"IsNumeric", "lang.variants"},
{"IsObject", "lang.variants"},
{"Null", "lang.variants"},
{"TypeName", "lang.variants"},
{"VarType", "lang.variants"},
{"Variant", "lang.variants"},
// Objects and class modules
{"Class_Initialize", "lang.classes"},
{"Class_Terminate", "lang.classes"},
{"Event", "lang.events"},
{"Friend", "lang.classes"},
{"Implements", "lang.interfaces"},
{"Is", "lang.classes"},
{"New", "lang.classes"},
{"Nothing", "lang.classes"},
{"Private", "lang.classes"},
{"Property", "lang.classes"},
{"Public", "lang.classes"},
{"RaiseEvent", "lang.events"},
{"Set", "lang.classes"},
{"TypeOf", "lang.classes"},
{"With", "lang.classes"},
{"WithEvents", "lang.events"},
// Collections
{"Collection", "lang.collection"},
{"Each", "lang.collection"},
// Form/control statements
{"Me", "lang.forms"},
};
#define HELP_MAP_COUNT DVX_ARRAY_LEN(sHelpMap)
// ============================================================
// Prototypes
// ============================================================
void basHelpCtrlTopic(const char *typeName, char *buf, int32_t bufSize);
const char *basHelpKeywordTopic(const char *word);
void basHelpCtrlTopic(const char *typeName, char *buf, int32_t bufSize) {
if (!buf || bufSize <= 0) {
return;
}
int32_t off = snprintf(buf, (size_t)bufSize, "%s", BAS_HELP_CTRL_PREFIX);
for (int32_t i = 0; typeName && typeName[i] && off < bufSize - 1; i++) {
buf[off++] = (char)tolower((unsigned char)typeName[i]);
}
buf[off < bufSize ? off : bufSize - 1] = '\0';
}
const char *basHelpKeywordTopic(const char *word) {
for (int32_t i = 0; word && i < HELP_MAP_COUNT; i++) {
if (strcasecmp(sHelpMap[i].keyword, word) == 0) {
return sHelpMap[i].topic;
}
}
return NULL;
}

View file

@ -0,0 +1,48 @@
// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// basHelpMap.h -- the DVX BASIC help topic of a word or control type
//
// What F1 opens in a code editor: the topic of the DVX BASIC help (the
// IDE's help file, dvxbasic.hlp) that explains a keyword, built-in
// function, statement or constant, and the topic of a control type
// (ctrl.<type in lower case>, the rule every control's help follows, so
// a new control's help is found without a table entry). The IDE uses
// these; BASIC programs reach them through compiler.bas
// (CompHelpTopic$, CompHelpCtrlTopic$).
#ifndef BAS_HELP_MAP_H
#define BAS_HELP_MAP_H
#include <stdint.h>
// The prefix of every control type's help topic
#define BAS_HELP_CTRL_PREFIX "ctrl."
// The topic of control type typeName (BAS_HELP_CTRL_PREFIX and the name
// in lower case) in buf (bufSize bytes).
void basHelpCtrlTopic(const char *typeName, char *buf, int32_t bufSize);
// The topic explaining word (any case), or NULL when it has none.
const char *basHelpKeywordTopic(const char *word);
#endif // BAS_HELP_MAP_H

View file

@ -0,0 +1,116 @@
// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// basProcs.c -- the SUB / FUNCTION blocks of BASIC source text (see basProcs.h)
#include "basProcs.h"
#include "dvxPlat.h"
#include <string.h>
#include <strings.h>
#define PROC_KW_SUB "SUB "
#define PROC_KW_FUNCTION "FUNCTION "
#define PROC_KW_PUBLIC "PUBLIC "
#define PROC_KW_PRIVATE "PRIVATE "
#define PROC_KW_LEN(s) ((int32_t)sizeof(s) - 1)
// Prototypes (alphabetical)
const char *basProcDeclAt(const char *p, bool *outIsSub);
void basProcNameFromDecl(const char *afterKw, char *out, int32_t outSize);
const char *basProcSkipToEnd(const char *declLine, bool isSub, int32_t *outEndLineOffset);
static bool kwMatch(const char *p, const char *kw);
static const char *skipLine(const char *p);
const char *basProcDeclAt(const char *p, bool *outIsSub) {
if (kwMatch(p, PROC_KW_PUBLIC)) {
p = dvxSkipWs(p + PROC_KW_LEN(PROC_KW_PUBLIC));
} else if (kwMatch(p, PROC_KW_PRIVATE)) {
p = dvxSkipWs(p + PROC_KW_LEN(PROC_KW_PRIVATE));
}
if (kwMatch(p, PROC_KW_SUB)) {
*outIsSub = true;
return p + PROC_KW_LEN(PROC_KW_SUB);
}
if (kwMatch(p, PROC_KW_FUNCTION)) {
*outIsSub = false;
return p + PROC_KW_LEN(PROC_KW_FUNCTION);
}
return NULL;
}
void basProcNameFromDecl(const char *afterKw, char *out, int32_t outSize) {
const char *p = dvxSkipWs(afterKw);
int32_t n = 0;
while (*p && *p != '(' && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' && n < outSize - 1) {
out[n++] = *p++;
}
out[n] = '\0';
}
const char *basProcSkipToEnd(const char *declLine, bool isSub, int32_t *outEndLineOffset) {
const char *endTag = isSub ? BAS_PROC_KW_END_SUB : BAS_PROC_KW_END_FUNCTION;
const char *scan = skipLine(declLine);
int32_t line = 1;
bool found = false;
while (*scan) {
if (kwMatch(dvxSkipWs(scan), endTag)) {
found = true;
scan = skipLine(scan);
break;
}
scan = skipLine(scan);
line++;
}
*outEndLineOffset = found ? line : line - 1;
return scan;
}
static bool kwMatch(const char *p, const char *kw) {
return strncasecmp(p, kw, strlen(kw)) == 0;
}
// Advance past the end of the current line (or to the NUL).
static const char *skipLine(const char *p) {
while (*p && *p != '\n') {
p++;
}
if (*p == '\n') {
p++;
}
return p;
}

View file

@ -0,0 +1,55 @@
// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// basProcs.h -- the SUB / FUNCTION blocks of BASIC source text
//
// The IDE shows one procedure at a time and fills its Object / Event
// lists from these blocks; the basdev library lists them for a BASIC
// IDE (CompProcList$). A block starts at a SUB or FUNCTION line (Public
// or Private in front allowed) and ends at its END SUB / END FUNCTION
// line. The scan is textual, so it works on code that does not compile.
#ifndef BAS_PROCS_H
#define BAS_PROCS_H
#include <stdbool.h>
#include <stdint.h>
// The lines that end a SUB / FUNCTION block (matched without regard to case)
#define BAS_PROC_KW_END_SUB "END SUB"
#define BAS_PROC_KW_END_FUNCTION "END FUNCTION"
// When p (already past leading blanks) starts a SUB or FUNCTION line,
// returns the text after the keyword and sets *outIsSub; else NULL.
const char *basProcDeclAt(const char *p, bool *outIsSub);
// Copy the procedure name that follows a SUB / FUNCTION keyword (up to
// '(' or a blank) into out.
void basProcNameFromDecl(const char *afterKw, char *out, int32_t outSize);
// From the start of a SUB / FUNCTION line, the position just past its
// END SUB / END FUNCTION line (or the end of the text).
// *outEndLineOffset receives how many lines after the declaration the
// END line is (the last line's when it is missing).
const char *basProcSkipToEnd(const char *declLine, bool isSub, int32_t *outEndLineOffset);
#endif // BAS_PROCS_H

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,287 @@
// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// basProject.h -- the DVX BASIC project (.dbp) model
//
// A project as a development tool edits it: its file list (modules,
// class modules, UserControls, forms) with in-memory edit buffers, its
// [Project] settings (name, type, author, icon, help file ...) and
// [Settings] (startup form, Option Explicit, more than one copy), load
// and save with the .dbp format, renaming a file on disk and in the
// project together, the source map of the last compile, and the header
// of a UserControl (.ctl) read and changed key by key. The DVX BASIC
// IDE uses it, and BASIC programs reach it through compiler.bas (Proj*).
// The window and dialogs around it stay in the IDE (ideProject.h).
#ifndef BAS_PROJECT_H
#define BAS_PROJECT_H
#include "../basBuild.h"
#include "../basRes.h"
#include "dvxApp.h"
#include "dvxPrefs.h"
#include "dvxTypes.h"
#include <stdbool.h>
#include <stdint.h>
// ============================================================
// Constants
// ============================================================
#define PRJ_MAX_NAME 32
#define PRJ_MAX_SOURCE 65536 // a project file must be smaller than this to be loaded or edited
#define PRJ_MAX_STRING 128
#define PRJ_MAX_DESC 512 // description buffer, including the NUL (saved as one INI line)
#define PRJ_MAX_FIELD PRJ_MAX_DESC // largest [Project] field buffer (description; paths are DVX_MAX_PATH)
#define PRJ_MAX_ARGS PRJ_MAX_FIELD // command line arguments for runs from the IDE (one INI line)
#define PRJ_MAX_RAW_SOURCE (PRJ_MAX_SOURCE * 2) // on-disk size limit: CR LF line ends at worst double the text
// Project, form and module file extensions; a class module's is
// BAS_CLS_EXT, a UserControl's BAS_CTL_EXT (basRes.h). A file's kind
// follows its extension (see prjKindForPath).
#define BAS_DBP_EXT ".dbp"
#define PRJ_FRM_EXT BAS_FRM_EXT
#define PRJ_BAS_EXT ".bas"
// An empty procedure as the IDE writes an event handler: its scope ("" or
// PRJ_PRIVATE), name and parameter list. A UserControl's own handlers
// (UserControl_Paint ...) are Private: Public ones would be methods.
#define PRJ_HANDLER_FMT "%sSub %s (%s)\n\nEnd Sub\n"
#define PRJ_PRIVATE "Private "
// ============================================================
// Project file kinds
// ============================================================
// The order is the compile order: modules, then class modules, then
// UserControls, then forms (see basBuildConcatProject).
typedef enum {
PrjKindModuleE, // .bas: [Modules]
PrjKindClassE, // .cls: [Classes]
PrjKindControlE, // .ctl: [Controls] (a UserControl)
PrjKindFormE, // .frm: [Forms]
PrjKindCountE
} PrjFileKindE;
// ============================================================
// Project file entry
// ============================================================
typedef struct {
// Fields are ordered by alignment to minimize struct padding.
char *buffer; // in-memory edit buffer (malloc'd, NULL = not loaded)
char path[DVX_MAX_PATH]; // relative path (8.3 DOS name)
char formName[BAS_MAX_IDENT]; // form object name (from "Begin Form <name>")
uint8_t kind; // PrjFileKindE
bool modified; // true = buffer has unsaved changes
} PrjFileT;
// ============================================================
// Project state
// ============================================================
typedef struct {
// Fields are ordered by alignment to minimize struct padding.
PrjFileT *files; // stb_ds dynamic array (prjFileCount)
BasSourceMapT *sourceMap; // stb_ds array: path = files[].path, firstFileLine = first code line
int32_t activeFileIdx; // index of file open in editor (-1 = none)
char name[PRJ_MAX_NAME];
char type[PRJ_MAX_NAME]; // [Project] Type: "" a program, BAS_PROJECT_TYPE_CONTROL a control project (builds NAME.wgt)
char projectPath[DVX_MAX_PATH]; // full path to .dbp file
char projectDir[DVX_MAX_PATH]; // directory containing .dbp
char startupForm[BAS_MAX_IDENT];
// Project metadata (for binary generation)
char author[PRJ_MAX_STRING];
char publisher[PRJ_MAX_STRING];
char version[PRJ_MAX_NAME];
char copyright[PRJ_MAX_STRING];
char description[PRJ_MAX_DESC];
char iconPath[DVX_MAX_PATH]; // relative path to icon BMP
char helpFile[DVX_MAX_PATH]; // relative path to .hlp file
char commandArgs[PRJ_MAX_ARGS]; // COMMAND$ for runs from the IDE (not built into the program)
bool optionExplicit; // require DIM before use
bool multiInstance; // the built program may run more than once at a time
bool dirty;
} PrjStateT;
// ============================================================
// Project management
// ============================================================
// Append a file (path relative to the project's directory) of kind; its
// index.
int32_t prjAddFile(PrjStateT *prj, const char *relativePath, PrjFileKindE kind);
// Offset of the first line of code in the text of a class module (len
// bytes), just past its header; *outFirstLine receives that line's
// 1-based number.
int32_t prjClassCodePos(const char *text, int32_t len, int32_t *outFirstLine);
// The class name of project file fileIdx given its text: a class
// module's Attribute VB_Name, else its file name; a UserControl's name
// (Begin UserControl Name). False when there is none.
bool prjClassName(const PrjStateT *prj, int32_t fileIdx, const char *text, char *out, int32_t outSize);
// Free everything the project holds; it is left empty (prjInit).
void prjClose(PrjStateT *prj);
// A [Project] or [Settings] value by its .dbp key (BAS_INI_KEY_*, any
// case: Name, Type, Author, Publisher, Version, Copyright, Description,
// Icon, HelpFile, CommandArgs, StartupForm, OptionExplicit,
// MultiInstance) as text in out; a flag reads "true" or "false". False
// for an unknown key.
bool prjFieldGet(const PrjStateT *prj, const char *key, char *out, int32_t outSize);
// Set a value by its key (a flag from true / yes / 1 / -1, anything else
// false; text on one line, cut to its buffer). False for an unknown key.
bool prjFieldSet(PrjStateT *prj, const char *key, const char *value);
int32_t prjFileCount(const PrjStateT *prj);
// Index of the project file whose relative path is path (any case), or -1.
int32_t prjFileIndexByPath(const PrjStateT *prj, const char *path);
// The full path of file fileIdx ("" for no such file).
void prjFullPath(const PrjStateT *prj, int32_t fileIdx, char *outPath, int32_t outSize);
void prjInit(PrjStateT *prj);
// The kind of source file path names, by its file name's extension (the
// build's rule, basBuildWrapForPath).
PrjFileKindE prjKindForPath(const char *path);
// How the build wraps a file of kind (BAS_BUILD_WRAP_MODULE / CLASS /
// CONTROL / FORM).
BasBuildWrapE prjKindWrap(PrjFileKindE kind);
// True when the project is a control project ([Project] Type = Control):
// it builds a control file (NAME.wgt) instead of a program.
bool prjIsControlProject(const PrjStateT *prj);
// Read the .dbp dbpPath into prj (what it held before is freed). False
// when the file does not exist or cannot be read (prj unchanged).
bool prjLoad(PrjStateT *prj, const char *dbpPath);
// Read every file not loaded yet into its edit buffer (and a form's
// name); with ctx the screen keeps updating between files.
void prjLoadAllFiles(PrjStateT *prj, AppContextT *ctx);
// The line of the concatenated source of the last compile holding line
// fileLine of file fileIdx, or 0 when the file is not in the map (not
// compiled since it was added) or the line is not compiled code (e.g. a
// class module's header).
int32_t prjMapFileLine(const PrjStateT *prj, int32_t fileIdx, int32_t fileLine);
// The file and line a line of the concatenated source of the last
// compile came from. False when the line belongs to no project file.
bool prjMapLine(const PrjStateT *prj, int32_t concatLine, int32_t *outFileIdx, int32_t *outLocalLine);
// A new, empty project called name in directory (its .dbp is
// directory\name.dbp, not written until prjSave).
void prjNew(PrjStateT *prj, const char *name, const char *directory);
// True when path can be opened for reading.
bool prjPathExists(const char *path);
// Read project file fileIdx from disk as the IDE edits it: carriage
// returns removed, NUL-terminated. Returns a malloc'd buffer, or NULL
// when the file cannot be read or its text (without CRs) is
// PRJ_MAX_SOURCE bytes or larger; *outMissing (may be NULL) then tells
// whether it simply does not exist.
char *prjReadFileText(const PrjStateT *prj, int32_t fileIdx, bool *outMissing);
// Remove file idx from the project (the file on disk stays).
void prjRemoveFile(PrjStateT *prj, int32_t idx);
// Rename project file idx to newName, a file name in the project's
// directory whose extension, when given, is the one of the file's kind
// (none: it is added). The file on disk (when it has been saved) is
// renamed and the project file (.dbp) written at once so the two agree;
// on any failure both are put back. An existing file is never
// overwritten. A name that differs only in case changes nothing (DOS
// names ignore case). False with a message in err (errSize bytes).
bool prjRenameFile(PrjStateT *prj, int32_t idx, const char *newName, char *err, int32_t errSize);
// Write the .dbp (crash-safe: the old file is replaced only once the new
// one is complete). The file buffers are not written.
bool prjSave(const PrjStateT *prj);
// Copy src into dst (dstSize bytes) with every CR and LF replaced by a
// space: an INI value is one line, so an embedded line break would split
// it and start bogus keys or sections.
void prjSingleLine(char *dst, int32_t dstSize, const char *src);
// ============================================================
// UserControl headers (.ctl)
// ============================================================
//
// A .ctl file starts with its header, VERSION DVX 1.00 and the block
// "Begin UserControl Name" ... "End" of "Key = Value" lines (keys
// BAS_UC_KEY_*); the class code follows. These read and change one key
// of the block -- or the control's name, key "Name", on its Begin line --
// and leave every other line of the text as it is.
// How a header key's value is written.
typedef enum {
PrjCtlNameE, // the control's name (Name, on the Begin UserControl line)
PrjCtlIntE, // a whole number (Width, Height)
PrjCtlBoolE, // True or False (CanGetFocus)
PrjCtlColorE, // &HBBGGRR& (BackColor), "" for none
PrjCtlTextE // a text in quotes (ToolboxIcon, Description, DefaultEvent), "" for none
} PrjCtlValueE;
typedef struct {
const char *key; // BAS_UC_KEY_*
PrjCtlValueE kind;
} PrjCtlKeyT;
// The text of a new UserControl called name: VERSION DVX 1.00, its
// header (Begin UserControl name with Width and Height ... End), Option
// Explicit and an empty UserControl_Paint. malloc'd; NULL when name is
// no identifier or memory runs out.
char *prjCtlNew(const char *name);
// The idx-th header key (0 = the first), NULL past the end: Name,
// Width, Height, CanGetFocus, BackColor, ToolboxIcon, Description,
// DefaultEvent.
const PrjCtlKeyT *prjCtlKeyAt(int32_t idx);
// The header key named key (any case), NULL when it is none.
const PrjCtlKeyT *prjCtlKeyFind(const char *key);
// The value of header key key of a UserControl's text as the control
// takes it -- the default when the header leaves the key out; a flag
// True or False, a color &HBBGGRR& or "" -- in out (outSize bytes).
// False when the text has no Begin UserControl block or key is not a
// header key.
bool prjCtlHeaderGet(const char *text, const char *key, char *out, int32_t outSize);
// A copy of a UserControl's text with header key key set to value: its
// line changed, or added before the block's End; "" removes the line (the
// default applies). The value is written the way its kind is (a text in
// quotes, a color &HBBGGRR&, a flag True or False); Name replaces the
// name on the Begin UserControl line. malloc'd; NULL when the text has
// no Begin UserControl block, key is not a header key, the value does not
// suit it (not a whole number, not a color, a text with a quote or a line
// break, a name that is no identifier or "") or memory runs out.
char *prjCtlHeaderSet(const char *text, const char *key, const char *value);
#endif // BAS_PROJECT_H

View file

@ -0,0 +1,385 @@
// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// basSource.c -- BASIC source text read with the compiler's lexer (see
// basSource.h)
#include "basSource.h"
#include "../compiler/compiler.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#define SRC_PARAMS_MAX 256 // an Event's parameter list text kept for the callback
#define SRC_QUOTE '"'
#define SRC_DOT '.'
#define SRC_UNDERSCORE '_'
static const char *const sKindNames[BasSrcKindCountE] = {
[BasSrcKeywordE] = "Keyword",
[BasSrcIdentE] = "Identifier",
[BasSrcStringE] = "String",
[BasSrcNumberE] = "Number",
[BasSrcDateE] = "Date",
[BasSrcSymbolE] = "Symbol",
[BasSrcCommentE] = "Comment",
[BasSrcErrorE] = "Error"
};
// ============================================================
// Prototypes
// ============================================================
void basSourceClassEvents(const char *src, BasSourceEventFnT fn, void *ctx);
bool basSourceInCode(const char *src, int32_t srcLen, int32_t pos);
const char *basSourceKindName(BasSrcKindE kind);
char *basSourceRename(const char *src, const char *oldName, const char *newName, bool codeOnly);
int32_t basSourceTokens(const char *src, int32_t srcLen, BasSourceTokenFnT fn, void *ctx);
void basSourceWithEvents(const char *src, BasSourceWithEventsFnT fn, void *ctx);
static BasSrcKindE kindOf(const BasLexerT *lex);
static void lexGuard(BasLexerT *lex);
static void lexNext(BasLexerT *lex);
static void lexStart(BasLexerT *lex, const char *src, int32_t srcLen);
static bool renameMatchAt(const char *src, int32_t srcLen, int32_t i, const char *oldName, int32_t oldLen, bool codeOnly);
// The [Public] Event Name[(params)] statements, read with the compiler's
// words so strings and comments never match.
void basSourceClassEvents(const char *src, BasSourceEventFnT fn, void *ctx) {
BasLexerT lex;
bool stmtStart = true;
lexStart(&lex, src, (int32_t)strlen(src));
while (lex.token.type != TOK_EOF && lex.token.type != TOK_ERROR) {
if (stmtStart && lex.token.type == TOK_PUBLIC) {
lexNext(&lex);
}
BasTokenTypeE tt = lex.token.type;
bool isEvent = stmtStart && tt == TOK_IDENT && strcasecmp(lex.token.text, BAS_KW_EVENT) == 0;
if (isEvent) {
lexNext(&lex);
}
if (isEvent && lex.token.type == TOK_IDENT) {
char name[BAS_MAX_IDENT];
char params[SRC_PARAMS_MAX] = "";
snprintf(name, sizeof(name), "%s", lex.token.text);
lexNext(&lex);
if (lex.token.type == TOK_LPAREN) {
int32_t start = lex.token.pos + 1;
int32_t depth = 1;
while (depth > 0) {
lexNext(&lex);
if (lex.token.type == TOK_EOF || lex.token.type == TOK_NEWLINE || lex.token.type == TOK_ERROR) {
break;
}
depth += (lex.token.type == TOK_LPAREN) - (lex.token.type == TOK_RPAREN);
}
int32_t len = lex.token.pos - start;
if (len > (int32_t)sizeof(params) - 1) {
len = (int32_t)sizeof(params) - 1;
}
if (len > 0) {
memcpy(params, src + start, (size_t)len);
params[len] = '\0';
}
}
fn(ctx, name, params);
tt = lex.token.type;
}
stmtStart = tt == TOK_NEWLINE || tt == TOK_COLON;
lexNext(&lex);
}
}
// Lexes the line holding pos from its start: pos is code unless a string
// literal (or a string without its closing quote) or a comment covers it.
bool basSourceInCode(const char *src, int32_t srcLen, int32_t pos) {
if (!src || pos < 0 || pos >= srcLen) {
return false;
}
int32_t lineStart = pos;
while (lineStart > 0 && src[lineStart - 1] != '\n' && src[lineStart - 1] != '\r') {
lineStart--;
}
BasLexerT lex;
int32_t rel = pos - lineStart;
lexStart(&lex, src + lineStart, srcLen - lineStart);
while (lex.token.type != TOK_EOF && lex.token.pos <= rel) {
BasSrcKindE kind = kindOf(&lex);
if (kind == BasSrcCommentE) {
return false;
}
if (lex.token.type == TOK_NEWLINE) {
return true;
}
if (rel < lex.pos) {
return kind != BasSrcStringE && !(kind == BasSrcErrorE && lex.source[lex.token.pos] == SRC_QUOTE);
}
lexNext(&lex);
}
return true;
}
const char *basSourceKindName(BasSrcKindE kind) {
return (kind >= 0 && kind < BasSrcKindCountE) ? sKindNames[kind] : "";
}
char *basSourceRename(const char *src, const char *oldName, const char *newName, bool codeOnly) {
if (!src || !oldName || !newName || !oldName[0]) {
return NULL;
}
int32_t oldLen = (int32_t)strlen(oldName);
int32_t newLen = (int32_t)strlen(newName);
int32_t srcLen = (int32_t)strlen(src);
int32_t count = 0;
// First pass: count the references to size the result
for (int32_t i = 0; i <= srcLen - oldLen; i++) {
if (renameMatchAt(src, srcLen, i, oldName, oldLen, codeOnly)) {
count++;
}
}
if (count == 0) {
return NULL;
}
// The new length in 64 bits: many references to a long new name can
// pass what an int32_t offset (and a 32-bit size_t) holds
int64_t outLen = (int64_t)srcLen + (int64_t)count * (newLen - oldLen);
if (outLen >= INT32_MAX) {
return NULL;
}
char *out = (char *)malloc((size_t)outLen + 1);
if (!out) {
return NULL;
}
int32_t op = 0;
for (int32_t i = 0; i < srcLen; ) {
if (i <= srcLen - oldLen && renameMatchAt(src, srcLen, i, oldName, oldLen, codeOnly)) {
memcpy(out + op, newName, (size_t)newLen);
op += newLen;
i += oldLen;
continue;
}
out[op++] = src[i++];
}
out[op] = '\0';
return out;
}
int32_t basSourceTokens(const char *src, int32_t srcLen, BasSourceTokenFnT fn, void *ctx) {
BasLexerT lex;
int32_t count = 0;
if (!src) {
return 0;
}
lexStart(&lex, src, srcLen);
while (lex.token.type != TOK_EOF) {
BasSrcKindE kind = kindOf(&lex);
if (kind != BasSrcKindCountE) {
fn(ctx, kind, lex.token.line, lex.token.col, lex.token.pos, lex.pos - lex.token.pos);
count++;
}
lexNext(&lex);
}
return count;
}
// One declarator per pass: [WithEvents] name [As type]; commas inside an
// array's bounds do not start a declarator.
void basSourceWithEvents(const char *src, BasSourceWithEventsFnT fn, void *ctx) {
BasLexerT lex;
bool stmtStart = true;
lexStart(&lex, src, (int32_t)strlen(src));
while (lex.token.type != TOK_EOF && lex.token.type != TOK_ERROR) {
BasTokenTypeE tt = lex.token.type;
if (!stmtStart || (tt != TOK_DIM && tt != TOK_PRIVATE && tt != TOK_PUBLIC)) {
stmtStart = tt == TOK_NEWLINE || tt == TOK_COLON;
lexNext(&lex);
continue;
}
do {
lexNext(&lex);
if (lex.token.type == TOK_IDENT && strcasecmp(lex.token.text, BAS_KW_WITHEVENTS) == 0) {
lexNext(&lex);
if (lex.token.type == TOK_IDENT) {
char name[BAS_MAX_IDENT];
snprintf(name, sizeof(name), "%s", lex.token.text);
lexNext(&lex);
if (lex.token.type == TOK_AS) {
lexNext(&lex);
if (lex.token.type == TOK_IDENT) {
fn(ctx, name, lex.token.text);
}
}
}
}
int32_t depth = 0;
while (lex.token.type != TOK_EOF && lex.token.type != TOK_ERROR && lex.token.type != TOK_NEWLINE && lex.token.type != TOK_COLON && (depth > 0 || lex.token.type != TOK_COMMA)) {
depth += (lex.token.type == TOK_LPAREN) - (lex.token.type == TOK_RPAREN);
lexNext(&lex);
}
} while (lex.token.type == TOK_COMMA);
stmtStart = false;
}
}
// The kind of the lexer's current token; BasSrcKindCountE for a line end
// (a comment ends its line too: its line-end token starts at the ' or
// REM).
static BasSrcKindE kindOf(const BasLexerT *lex) {
BasTokenTypeE type = lex->token.type;
switch (type) {
case TOK_INT_LIT:
case TOK_LONG_LIT:
case TOK_FLOAT_LIT:
case TOK_CUR_LIT:
return BasSrcNumberE;
case TOK_STRING_LIT:
return BasSrcStringE;
case TOK_DATE_LIT:
return BasSrcDateE;
case TOK_IDENT:
return BasSrcIdentE;
case TOK_ERROR:
return BasSrcErrorE;
case TOK_EOF:
return BasSrcKindCountE;
case TOK_NEWLINE: {
char c = lex->token.pos < lex->sourceLen ? lex->source[lex->token.pos] : '\n';
return (c == '\n' || c == '\r') ? BasSrcKindCountE : BasSrcCommentE;
}
default:
return (type >= TOK_FIRST_KEYWORD && type <= TOK_LAST_KEYWORD) ? BasSrcKeywordE : BasSrcSymbolE;
}
}
// A token that did not move the lexer (an error on a character it cannot
// read) is stepped over, so every scan of any text ends.
static void lexGuard(BasLexerT *lex) {
if (lex->token.type == TOK_ERROR && lex->pos == lex->token.pos && lex->pos < lex->sourceLen) {
lex->pos++;
lex->col++;
}
}
static void lexNext(BasLexerT *lex) {
basLexerNext(lex);
lexGuard(lex);
}
static void lexStart(BasLexerT *lex, const char *src, int32_t srcLen) {
basLexerInit(lex, src, srcLen);
lexGuard(lex);
}
// oldName at src[i]: a whole word start followed by '.' or '_', and code
// when codeOnly.
static bool renameMatchAt(const char *src, int32_t srcLen, int32_t i, const char *oldName, int32_t oldLen, bool codeOnly) {
if (strncasecmp(&src[i], oldName, (size_t)oldLen) != 0) {
return false;
}
char after = src[i + oldLen];
if (after != SRC_DOT && after != SRC_UNDERSCORE) {
return false;
}
if (i > 0 && basIsIdentChar(src[i - 1])) {
return false;
}
return !codeOnly || basSourceInCode(src, srcLen, i);
}

View file

@ -0,0 +1,92 @@
// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// basSource.h -- BASIC source text read with the compiler's lexer
//
// What a code editor needs to know about source text without compiling
// it: its tokens, whether a position is code (not inside a string
// literal or a comment), renaming a form or control in code, the
// WithEvents variables a file declares and the Events a class module
// declares. The IDE uses these, and BASIC programs reach them through
// compiler.bas (CompTokens$, CompInCode, CompRenameInCode$,
// CompWithEvents$, CompClassEvents$). Everything works line by line on
// text that need not compile.
#ifndef BAS_SOURCE_H
#define BAS_SOURCE_H
#include "../compiler/lexer.h"
#include <stdbool.h>
#include <stdint.h>
// What a token of BasSourceTokenFnT is
typedef enum {
BasSrcKeywordE, // a reserved word (IF, DIM, PRINT ...)
BasSrcIdentE, // a name (also the words a program may reuse: INTEGER, CLS ...)
BasSrcStringE, // a string literal, quotes included
BasSrcNumberE, // a number literal (&H and & / # suffixes included)
BasSrcDateE, // a #date# literal
BasSrcSymbolE, // an operator or punctuation (. , ; : ( ) + = ...)
BasSrcCommentE, // ' or REM up to the end of the line
BasSrcErrorE, // text the lexer cannot read (e.g. a string without its closing quote)
BasSrcKindCountE
} BasSrcKindE;
// One token: its kind, 1-based line and column, and the source text it
// covers (src + pos, len bytes).
typedef void (*BasSourceTokenFnT)(void *ctx, BasSrcKindE kind, int32_t line, int32_t col, int32_t pos, int32_t len);
// One WithEvents variable: its name and its class.
typedef void (*BasSourceWithEventsFnT)(void *ctx, const char *name, const char *className);
// One Event of a class module: its name and its parameter list text (""
// when it has none).
typedef void (*BasSourceEventFnT)(void *ctx, const char *name, const char *params);
// The Events a class module declares ([Public] Event Name[(params)]).
void basSourceClassEvents(const char *src, BasSourceEventFnT fn, void *ctx);
// True when byte offset pos of src is code: not inside a string literal
// or a comment. Only the line holding pos is read.
bool basSourceInCode(const char *src, int32_t srcLen, int32_t pos);
// The name of a BasSrcKindE as BASIC lists it ("Keyword", "Identifier" ...).
const char *basSourceKindName(BasSrcKindE kind);
// Rename references to a form or control oldName in src: oldName (any
// case) as a whole word followed by '.' (Command1.Caption,
// Form1.Command1.Text) or '_' (Command1_Click, Form1_Load). With
// codeOnly, strings and comments are left alone. Returns the new text
// (malloc'd), or NULL when nothing matched, the result would not fit an
// int32_t length or memory runs out.
char *basSourceRename(const char *src, const char *oldName, const char *newName, bool codeOnly);
// Every token of src (srcLen bytes) in order; line ends are not
// reported. Returns how many tokens were reported.
int32_t basSourceTokens(const char *src, int32_t srcLen, BasSourceTokenFnT fn, void *ctx);
// The WithEvents variables src declares (Dim / Private / Public
// WithEvents name As Class; several declarators per statement allowed).
void basSourceWithEvents(const char *src, BasSourceWithEventsFnT fn, void *ctx);
#endif // BAS_SOURCE_H

View file

@ -0,0 +1,308 @@
// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// basValidate.c -- compile-time CtrlName.Member validator (see basValidate.h)
#include "basValidate.h"
#include "../formrt/formrt.h"
#include "../formrt/frmParser.h"
#include "dvxWgt.h"
#include "stb_ds_wrap.h"
#include <stdio.h>
#include <string.h>
#include <strings.h>
#define VALIDATE_FORM_TYPE FRM_KW_FORM // type name of a form
#define VALIDATE_MENU_TYPE FRM_KW_MENU // type name of a menu item
// One basValidateScanFrm parse: the validator and the code hook.
typedef struct {
BasValidatorT *v;
BasFrmCodeFnT onCode;
void *codeCtx;
} ValidateScanT;
// Prototypes (alphabetical)
static void addEntry(BasValidatorT *v, const char *name, const char *wgtType);
void basValidateAddType(BasValidatorT *v, const char *typeName);
void basValidateCallbacks(BasValidatorT *v, BasCtrlValidatorT *out);
void basValidateFree(BasValidatorT *v);
bool basValidateIsMethodValid(void *ctx, const char *wgtType, const char *methodName);
bool basValidateIsPropValid(void *ctx, const char *wgtType, const char *propName);
const char *basValidateLookupCtrlType(void *ctx, const char *ctrlName);
void basValidateScanFrm(BasValidatorT *v, const char *text, int32_t len, BasFrmCodeFnT onCode, void *codeCtx);
static bool isFormOrPicture(const char *wgtType);
static const WgtIfaceT *liveIface(const BasValidatorT *v, const char *wgtType);
static void onCtrlBegin(void *ud, const char *typeName, const char *name);
static bool onFormBegin(void *ud, const char *name);
static void onFormEnd(void *ud, const char *trailingSrc, int32_t trailingLen);
static void onMenuBegin(void *ud, const char *name, int32_t level);
static bool ownType(const BasValidatorT *v, const char *typeName);
static void addEntry(BasValidatorT *v, const char *name, const char *wgtType) {
BasCtrlMapEntryT e;
memset(&e, 0, sizeof(e));
snprintf(e.name, sizeof(e.name), "%s", name ? name : "");
snprintf(e.wgtType, sizeof(e.wgtType), "%s", wgtType ? wgtType : "");
snprintf(e.formName, sizeof(e.formName), "%s", v->currentForm);
arrput(v->entries, e);
}
void basValidateAddType(BasValidatorT *v, const char *typeName) {
BasOwnTypeT t;
snprintf(t.name, sizeof(t.name), "%s", typeName ? typeName : "");
arrput(v->ownTypes, t);
}
void basValidateCallbacks(BasValidatorT *v, BasCtrlValidatorT *out) {
out->lookupCtrlType = basValidateLookupCtrlType;
out->isMethodValid = basValidateIsMethodValid;
out->isPropValid = basValidateIsPropValid;
out->ctx = v;
}
void basValidateFree(BasValidatorT *v) {
arrfree(v->entries);
arrfree(v->badTypes);
arrfree(v->ownTypes);
v->entries = NULL;
v->badTypes = NULL;
v->ownTypes = NULL;
}
bool basValidateIsMethodValid(void *ctx, const char *wgtType, const char *methodName) {
if (!wgtType || !methodName) {
return true; // be permissive on malformed input
}
if (basFormRtCommonMethodId(methodName) != BAS_CM_NONE) {
return true;
}
// Drawing methods of forms and PictureBoxes
if (isFormOrPicture(wgtType) && basFormRtIsDrawMember(methodName, false)) {
return true;
}
// Form-level methods
if (strcasecmp(wgtType, VALIDATE_FORM_TYPE) == 0) {
return basFormRtIsFormMethod(methodName);
}
// Menu items have no methods beyond common
if (strcasecmp(wgtType, VALIDATE_MENU_TYPE) == 0) {
return false;
}
const WgtIfaceT *iface = liveIface((const BasValidatorT *)ctx, wgtType);
if (!iface) {
return true; // unknown type -- skip validation
}
// A known type with no interface methods accepts only the common
// set (already checked above); being permissive here let typos
// like btnGo.Boggle compile.
for (int32_t i = 0; i < iface->methodCount; i++) {
if (strcasecmp(iface->methods[i].name, methodName) == 0) {
return true;
}
}
return false;
}
bool basValidateIsPropValid(void *ctx, const char *wgtType, const char *propName) {
if (!wgtType || !propName) {
return true;
}
// Drawing properties of forms and PictureBoxes
if (isFormOrPicture(wgtType) && basFormRtIsDrawMember(propName, true)) {
return true;
}
// Form-level properties (the runtime's own table)
if (strcasecmp(wgtType, VALIDATE_FORM_TYPE) == 0) {
return basFormRtFindFormProp(propName) != NULL;
}
// Menu items (the runtime's own table)
if (strcasecmp(wgtType, VALIDATE_MENU_TYPE) == 0) {
return basFormRtFindMenuProp(propName) != NULL;
}
// Properties every non-menu control accepts (the runtime's own table)
if (basFormRtFindCommonProp(propName)) {
return true;
}
const WgtIfaceT *iface = liveIface((const BasValidatorT *)ctx, wgtType);
// Same reasoning as basValidateIsMethodValid: an empty interface
// property table must not turn validation off for the whole type.
return !iface || wgtIfaceFindProp(iface, propName) != NULL;
}
const char *basValidateLookupCtrlType(void *ctx, const char *ctrlName) {
BasValidatorT *v = (BasValidatorT *)ctx;
const char *found = NULL;
bool ambiguous = false;
if (!v || !ctrlName) {
return NULL;
}
const char *scope = v->compiler ? basCompilerFormScope(v->compiler) : "";
for (int32_t i = 0; i < (int32_t)arrlen(v->entries); i++) {
const BasCtrlMapEntryT *e = &v->entries[i];
if (strcasecmp(e->name, ctrlName) != 0) {
continue;
}
if (scope[0] && strcasecmp(e->formName, scope) == 0) {
return e->wgtType;
}
ambiguous = ambiguous || (found && strcasecmp(found, e->wgtType) != 0);
found = e->wgtType;
}
return ambiguous ? NULL : found;
}
void basValidateScanFrm(BasValidatorT *v, const char *text, int32_t len, BasFrmCodeFnT onCode, void *codeCtx) {
ValidateScanT scan;
FrmParserCbsT cbs;
scan.v = v;
scan.onCode = onCode;
scan.codeCtx = codeCtx;
memset(&cbs, 0, sizeof(cbs));
cbs.userData = &scan;
cbs.onFormBegin = onFormBegin;
cbs.onCtrlBegin = onCtrlBegin;
cbs.onMenuBegin = onMenuBegin;
cbs.onFormEnd = onFormEnd;
frmParse(text, len, &cbs);
}
// Forms and PictureBoxes take the drawing members.
static bool isFormOrPicture(const char *wgtType) {
return strcasecmp(wgtType, VALIDATE_FORM_TYPE) == 0 || strcasecmp(wgtType, BAS_PICTUREBOX_TYPE) == 0;
}
// The interface a loaded widget library registers for the BASIC type
// name wgtType, or NULL when none does or the program defines the type
// itself (v may be NULL): a registered control of that name is then an
// earlier build of it (the IDE's designer) or another program's, so its
// members say nothing about this one's.
static const WgtIfaceT *liveIface(const BasValidatorT *v, const char *wgtType) {
const char *wgtName = (v && ownType(v, wgtType)) ? NULL : wgtFindByBasName(wgtType);
return wgtName ? wgtGetIface(wgtName) : NULL;
}
static void onCtrlBegin(void *ud, const char *typeName, const char *name) {
BasValidatorT *v = ((ValidateScanT *)ud)->v;
if (!v) {
return;
}
addEntry(v, name, typeName);
// Flag a type no loaded widget library registers and the program
// does not define (the runtime would silently drop the control and
// fail at its first use). Forms and menus are structural, not
// widgets.
if (typeName && typeName[0] && strcasecmp(typeName, VALIDATE_FORM_TYPE) != 0 && strcasecmp(typeName, VALIDATE_MENU_TYPE) != 0 && !wgtFindByBasName(typeName) && !ownType(v, typeName)) {
BasBadTypeT bad;
memset(&bad, 0, sizeof(bad));
snprintf(bad.ctrlName, sizeof(bad.ctrlName), "%s", name ? name : "?");
snprintf(bad.typeName, sizeof(bad.typeName), "%s", typeName);
snprintf(bad.formName, sizeof(bad.formName), "%s", v->currentForm);
arrput(v->badTypes, bad);
}
}
static bool onFormBegin(void *ud, const char *name) {
BasValidatorT *v = ((ValidateScanT *)ud)->v;
if (v) {
snprintf(v->currentForm, sizeof(v->currentForm), "%s", name ? name : "");
addEntry(v, v->currentForm, VALIDATE_FORM_TYPE);
}
return true;
}
static void onFormEnd(void *ud, const char *trailingSrc, int32_t trailingLen) {
ValidateScanT *scan = (ValidateScanT *)ud;
if (scan->onCode) {
scan->onCode(scan->codeCtx, trailingSrc, trailingLen);
}
}
static void onMenuBegin(void *ud, const char *name, int32_t level) {
(void)level;
BasValidatorT *v = ((ValidateScanT *)ud)->v;
if (v) {
addEntry(v, name, VALIDATE_MENU_TYPE);
}
}
// True when the program defines control type typeName itself.
static bool ownType(const BasValidatorT *v, const char *typeName) {
for (int32_t i = 0; i < (int32_t)arrlen(v->ownTypes); i++) {
if (strcasecmp(v->ownTypes[i].name, typeName) == 0) {
return true;
}
}
return false;
}

View file

@ -0,0 +1,116 @@
// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// basValidate.h -- compile-time CtrlName.Member validator
//
// Inside DVX the widget libraries are loaded, so their interface tables
// say which properties and methods every control type has. Together
// with a scan of the program's .frm files (control name -> widget type)
// the compiler can reject typos like LblStatus.Caphtion at compile time
// instead of at the first click. Controls made at run time
// (CreateControl) are not in the map and are not checked.
//
// Used by the IDE (ideMain.c) and the basdev library (basDevBas.c).
// The command-line compiler (bascomp) does not validate: it runs outside
// DVX where no widget library is loaded, so it cannot know the types; the
// runtime reports such typos when the line runs.
#ifndef BAS_VALIDATE_H
#define BAS_VALIDATE_H
#include "../compiler/compiler.h"
#include <stdbool.h>
#include <stdint.h>
// One line of the unknown-type report (form, control, type).
#define BAS_VALIDATE_BAD_TYPE_FMT "Form '%s' control '%s': type '%s' is not registered."
// Advice printed after the unknown-type lines.
#define BAS_VALIDATE_BAD_TYPE_HINT "DVX uses VB6-style widget names (e.g. SpinButton, OptionButton, HScrollBar, CheckBox, DropDown)."
// A control, form or menu item a scanned .frm declares.
typedef struct {
char name[BAS_MAX_IDENT];
char wgtType[BAS_MAX_IDENT]; // "Form" for the form itself, "Menu" for a menu item, else the type's BASIC name
char formName[BAS_MAX_IDENT]; // form that declares it (a form: its own name)
} BasCtrlMapEntryT;
// A control whose type no loaded widget library registers.
typedef struct {
char ctrlName[BAS_MAX_IDENT];
char typeName[BAS_MAX_IDENT];
char formName[BAS_MAX_IDENT];
} BasBadTypeT;
// A control type the program defines itself (a UserControl of the
// project).
typedef struct {
char name[BAS_MAX_IDENT];
} BasOwnTypeT;
// Validator state: zero it (memset) before the first scan, release it
// with basValidateFree.
typedef struct {
BasCtrlMapEntryT *entries; // stb_ds: every declared name
BasBadTypeT *badTypes; // stb_ds: unregistered control types
BasOwnTypeT *ownTypes; // stb_ds: the program's own control types (basValidateAddType)
const BasCompilerT *compiler; // compile in progress (its form scope), or NULL
char currentForm[BAS_MAX_IDENT]; // the last Begin Form seen
} BasValidatorT;
// Called with the code that follows a scanned form's outer End.
typedef void (*BasFrmCodeFnT)(void *ctx, const char *trailingSrc, int32_t trailingLen);
// Add a control type the program defines itself (a UserControl of the
// project), before the .frm files are scanned: controls of that type are
// not reported as unknown, and their members are not checked (a
// registered control of that name -- the IDE designer's or another
// program's -- may be an older build of it; the runtime reports a wrong
// member when the line runs).
void basValidateAddType(BasValidatorT *v, const char *typeName);
// The compiler's callbacks for v (fills out; v must outlive the compile).
void basValidateCallbacks(BasValidatorT *v, BasCtrlValidatorT *out);
// Release what the scans collected (v stays usable).
void basValidateFree(BasValidatorT *v);
// True when methodName is a method of controls of type wgtType (a form:
// "Form"; a menu item: "Menu"). Unknown types are not checked (true).
bool basValidateIsMethodValid(void *ctx, const char *wgtType, const char *methodName);
// True when propName is a property of controls of type wgtType.
bool basValidateIsPropValid(void *ctx, const char *wgtType, const char *propName);
// The widget type of ctrlName for the compiler's member checks, or NULL
// to skip them: a name declared on the form whose code is being compiled
// wins; any other name is looked up across every form and skipped when
// different forms give it different types.
const char *basValidateLookupCtrlType(void *ctx, const char *ctrlName);
// Parse one .frm text (len bytes): v (may be NULL: collect nothing)
// gets its form, controls and menu items, and onCode (may be NULL) the
// code after the form's outer End.
void basValidateScanFrm(BasValidatorT *v, const char *text, int32_t len, BasFrmCodeFnT onCode, void *codeCtx);
#endif // BAS_VALIDATE_H

View file

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

View file

@ -0,0 +1,127 @@
# The MIT License (MIT)
#
# Copyright (C) 2026 Scott Duensing
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
.section Technical Reference (for Developers)|Libraries
.topic lib.basdev
.title BASIC Development Library
.toc 0 BASIC Development Library
.index basdev
.index BASIC Development Library
.index basBuildCompile
.index basBuildAddSource
.index basBuildMapFileLine
.index basValidateScanFrm
.index basDbgLocals
.index basDbgEval
.index basDbgExec
.index basRunHostCreate
.index basRunHostSlice
.index basSourceTokens
.index basSourceRename
.index basHelpKeywordTopic
.index basProject.h
.index PrjStateT
.index prjLoad
.index compiler.bas
.index debug.bas
.h1 BASIC Development Library
basdev.lib is the DVX BASIC compiler and what a development tool builds on it: the build pipeline (basBuild.h), the compile-time Control.Member validator (basValidate.h), the debugger's view of a paused program (basDebug.h) and the SUB / FUNCTION scanner (basProcs.h). The DVX BASIC IDE uses it, and BASIC programs reach it through compiler.bas and debug.bas (basDevBas.c holds their entry points). It also carries the STUB and noicon resources every build inside DVX uses (BAS_BUILD_STUB_CARRIER). It needs basrt.lib, which holds the run host (basRunHost.h) the IDE, the standalone stub and debug.bas share.
Headers: apps/dvxbasic/basBuild.h, apps/dvxbasic/basdev/basValidate.h, apps/dvxbasic/basdev/basDebug.h, apps/dvxbasic/basdev/basProcs.h, apps/dvxbasic/basdev/basSource.h, apps/dvxbasic/basdev/basHelpMap.h, apps/dvxbasic/basdev/basProject.h, apps/dvxbasic/formrt/basRunHost.h
.h2 Compiling (basBuild.h)
.table
Function Description
-------- -----------
basBuildAddSource Append one file to a BasProjectSourceT the way a project is built: a module in BEGINMODULE, a class module's code in BEGINCLASS, a UserControl's code in BEGINCLASS ... USERCONTROL (its header is kept in ctlTexts), a form's code section in BEGINFORM with its controls (the .frm text is kept in frmTexts), a lone program unwrapped. Records the file in the source map.
basBuildConcatProject Read every file an open .dbp names (modules, class modules, UserControls, forms) into a BasProjectSourceT through basBuildAddSource.
basBuildCompile Compile source into a BasModuleT with OPTION EXPLICIT and an optional BasCtrlValidatorT. A BasCompileErrorT says why it failed: BAS_COMPILE_ERROR (line, column, message), BAS_COMPILE_NO_MEMORY or BAS_COMPILE_NO_MODULE.
basBuildErrorText A compiler message without its "Line N, Col M: " prefix.
basBuildMapLine Line of the concatenated source to file and file line.
basBuildMapFileLine File and file line to the line of the concatenated source (names compared as DOS does: any case, / and \ alike).
basBuildSpecFromProject Fill a BasBuildSpecT's project metadata from an open .dbp.
basBuildApp Build an .app from a module, the .frm texts and the .ctl headers (ctlSources). A NULL selfPath takes the stub from BAS_BUILD_STUB_CARRIER (this library).
basBuildControl Build a control file (.wgt, resources only) from a control project's module and .ctl headers: the module, each header as CTLn with its toolbox icon24 and name resources, the project's metadata and help file. The IDE's Make Control and CompBuildApp of a control project.
basBuildIsControlProject True when an open .dbp is a control project ([Project] Type = Control).
basBuildWrapForPath The kind of a project file by its extension.
.endtable
.h2 Validating Control Members (basValidate.h)
A BasValidatorT (zeroed) collects the forms, controls and menu items of the .frm texts basValidateScanFrm parses; its onCode hook also receives each form's code section, so one parse serves both. basValidateCallbacks fills the BasCtrlValidatorT the compiler takes; pass &validator.compiler as basBuildCompile's liveCompiler so a control name resolves on the form whose code is compiled. Member names are checked against the loaded widget interfaces (WgtIfaceT) and the form runtime's own property tables. badTypes lists controls whose type no loaded widget library registers (BAS_VALIDATE_BAD_TYPE_FMT formats one). The command-line compiler does not validate: no widget library is loaded there.
.h2 Debugging (basDebug.h)
Every function takes the paused program's VM (or NULL when there is none).
.table
Function Description
-------- -----------
basDbgCurProcIdx The compiled procedure the paused VM is in (-1: module level).
basDbgProcNameForPc Procedure name holding a code address (BAS_DBG_MODULE_FRAME before every procedure).
basDbgVarVisible Whether a debug variable is in scope where the VM is paused (QBasic rules: SHARED, STATIC, form and member variables).
basDbgFindVar The visible debug variable of a name.
basDbgVarSlot The live value slot of a debug variable.
basDbgResolvePath The live slot of var, var(i, j), var.field or var(i).field (a TYPE field or a class member).
basDbgFormatValue Display text of a value (strings quoted, #dates#, array bounds, class names, Collection item counts).
basDbgLocals The visible variables as rows (name, type, value) through a BasDbgRowFnT.
basDbgCallStack The call stack as rows (procedure, line).
basDbgEval A watch: a variable path read directly, any other expression compiled and run as a snippet.
basDbgExec An Immediate-window line: assignment to the paused program's variable, a statement, or an expression to print.
basDbgSetVar Set a variable path to the value of an expression (scalars).
basDbgSnippetVm Compile a snippet whose globals are the paused program's visible variables, with their values copied in.
basDbgRunSnippet Run a snippet in bounded slices (an endless loop is abandoned).
basDbgAppendDropping Append to a fixed output buffer, dropping the oldest lines.
.endtable
.h2 Procedures (basProcs.h)
basProcDeclAt, basProcSkipToEnd and basProcNameFromDecl find the SUB / FUNCTION blocks of source text without compiling it (BAS_PROC_KW_END_SUB / BAS_PROC_KW_END_FUNCTION end them). The IDE's one-procedure-at-a-time editor and CompProcList$ use them.
.h2 Source Text (basSource.h)
Source text read with the compiler's lexer, line by line, on text that need not compile: basSourceTokens reports every token with its kind (BasSrcKindE: keyword, identifier, string, number, date, symbol, comment, error), line, column and source span; basSourceInCode tells whether an offset is code (not in a string literal or a comment); basSourceRename renames a form or control (oldName followed by '.' or '_' as a whole word), optionally only in code; basSourceWithEvents and basSourceClassEvents report the WithEvents variables and the Events of a class module. The IDE's rename (ideRenameInCode, also after the Menu Editor renames items) and its Object / Event lists use them; compiler.bas has CompTokens$, CompInCode, CompRenameInCode$, CompWithEvents$ and CompClassEvents$.
.h2 Help Topics (basHelpMap.h)
basHelpKeywordTopic gives the DVX BASIC help topic of a keyword or built-in function (NULL for none) and basHelpCtrlTopic the topic of a control type (BAS_HELP_CTRL_PREFIX and the type name in lower case): what F1 opens in the IDE, and CompHelpTopic$ / CompHelpCtrlTopic$.
.h2 Projects (basProject.h)
The project (.dbp) model: PrjStateT with its file list (PrjFileT: path relative to the project's folder, kind -- PrjFileKindE: module, class module, UserControl ([Controls], .ctl) or form, in compile order -- edit buffer) and settings, prjLoad / prjSave (one table of [Project] / [Settings] keys drives load, save, prjFieldGet and prjFieldSet; [Project] Type = Control makes a control project, prjIsControlProject), prjNew, prjAddFile, prjRemoveFile, prjRenameFile (disk and project together), prjFileIndexByPath, prjKindForPath (the build's rule, basBuildWrapForPath), prjKindWrap, prjClassName (a class module's or UserControl's class name), prjReadFileText and the source map of the last compile (prjMapLine, prjMapFileLine). The IDE keeps only its Project window and Properties dialog (ideProject.c); compiler.bas has ProjOpen, ProjNew, ProjSave, ProjClose, ProjPath$, ProjFileCount, ProjFile$, ProjFilePath$, ProjFileKind, ProjFileFind, ProjAddFile, ProjRemoveFile, ProjRenameFile, ProjGet$ and ProjSet; CompKeywordClass and CompKeywordReserved expose the keyword table's flags.
A UserControl's header (the Begin UserControl block of a .ctl) is read and changed key by key, every other line of the text kept: prjCtlKeyAt / prjCtlKeyFind give the keys (PrjCtlKeyT: name and PrjCtlValueE kind -- Name, the control's name on the Begin line, then Width, Height, CanGetFocus, BackColor, ToolboxIcon, Description, DefaultEvent), prjCtlHeaderGet the value the control takes (its default when the header leaves the key out), prjCtlHeaderSet a malloc'd copy of the text with one key changed, added before End or removed (an empty value), written as its kind is (a text in quotes, a color &HBBGGRR&, True or False), and prjCtlNew the text of a new UserControl (its header with Width and Height, Option Explicit, an empty UserControl_Paint written with PRJ_HANDLER_FMT and PRJ_PRIVATE, the form the IDE writes event handlers in). The IDE's File > New UserControl and Properties window use them; compiler.bas has ProjCtlNew$, ProjCtlHeader$, ProjCtlSetHeader$ and ProjCtlKeys$ (the keys and their kinds, prjCtlKeyAt order).
.h2 Running a Program (basRunHost.h, in basrt.lib)
basRunHostCreate makes the VM and form runtime for a module from a BasRunSpecT: App.Path and its CONFIG / DATA folders, COMMAND$, the print, input and DoEvents callbacks, the screen window's name, the help file and whether runtime errors show their own box. basRunHostRegisterFrm registers a .frm text, basRunHostLoadForms loads the forms and shows the startup form, basRunHostSlice runs module code once under the step limit (an error's message and line are always set afterwards), basRunHostAlive says whether the event-loop phase goes on, basRunHostTagBreak puts BAS_RUN_BREAK_TAG on the captions while paused and basRunHostDestroy ends it. The runtime made inside another program's code does not become the current runtime (basFormRtCurrent / basFormRtSetCurrent); each of these calls makes it current while its code runs.
VM accessors a debugger host uses (vm.h): basVmSetAppPaths, basVmSetBreakpointCallback, basVmSetDebugPaused, basVmResume, basVmStop, basVmClearStepState, basVmGetCurrentLine, basVmGetErrorLine.
.h2 From BASIC
compiler.bas and debug.bas declare the entry points (see Include Libraries in the BASIC language reference). Source sets, modules and runs are handles freed with their program. A run is polled: RunPoll runs the child for a bounded time and never waits. A pause in the child's module code returns RUN_PAUSED; a pause inside one of its event handlers (or its forms' Load code, inside RunStart) waits in place while the child pumps DVX events, so the caller's Timer and menus can step, continue or stop it; RunPoll called from there returns at once. RunFree while the child's code is on the stack stops it at once and releases it at the next library call after that code has unwound.
CompDesignControls registers a source set's UserControls for the calling program's design forms (basCtlLoadDesign with its runtime, the current one: a child run's own when the child calls it) and holds the module -- one of the caller's own handles; another program's is refused -- until CompDesignRelease, the next CompDesignControls or the end of that runtime (appEnded for a program, runDestroyHost for a child run), CompModuleFree meanwhile only marking it.

View file

@ -39,6 +39,7 @@ Headers: apps/dvxbasic/runtime/vm.h, apps/dvxbasic/runtime/values.h
.link lib.basrt.values Value System (values.h) .link lib.basrt.values Value System (values.h)
.link lib.basrt.vm Virtual Machine (vm.h) .link lib.basrt.vm Virtual Machine (vm.h)
.link lib.basrt.frmmodel The .frm Model (frmModel.h)
.topic lib.basrt.values .topic lib.basrt.values
.title Value System .title Value System
@ -385,6 +386,7 @@ void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui);
hideForm void (*)(ctx, formRef) Hide a form (keep in memory). hideForm void (*)(ctx, formRef) Hide a form (keep in memory).
msgBox int32_t (*)(ctx, message, flags) Display a message box. msgBox int32_t (*)(ctx, message, flags) Display a message box.
inputBox BasStringT *(*)(ctx, prompt, title, defaultText) Display an input box. inputBox BasStringT *(*)(ctx, prompt, title, defaultText) Display an input box.
loadFormText void *(*)(ctx, text) LoadFormText: a form from .frm text (its own copy when the name is loaded), NULL when it does not load.
ctx void * User pointer passed to all callbacks. ctx void * User pointer passed to all callbacks.
.endtable .endtable
@ -429,16 +431,33 @@ void basVmSetExternCallbacks(BasVmT *vm,
.h3 BasExternCallbacksT .h3 BasExternCallbacksT
The compiler converts every argument to its declared parameter type, so callExtern receives values whose type is the declared type. The stock basExternCall pushes cdecl stack words by that type: STRING as a char pointer, SINGLE as one 4-byte float, DOUBLE as 8 bytes, everything else as one int32_t. DOUBLE and SINGLE returns are read from the x87 ST(0) register; INTEGER returns keep only the low 16 bits. The compiler converts every argument to its declared parameter type, so callExtern receives values whose type is the declared type. The stock basExternCall pushes cdecl stack words by that type: STRING as a char pointer, a BINARY STRING (binaryMask bit i set for argument i) as the char pointer followed by an int32_t length, SINGLE as one 4-byte float, DOUBLE as 8 bytes, everything else as one int32_t. When binaryMask has BAS_EXTERN_BINARY_RET the result is a BINARY STRING: an int32_t * is pushed last and the result is that many bytes at the returned pointer. DOUBLE and SINGLE returns are read from the x87 ST(0) register; INTEGER returns keep only the low 16 bits.
.table .table
Field Signature Description Field Signature Description
----- --------- ----------- ----- --------- -----------
resolveExtern void *(*)(ctx, libName, funcName) Resolve a native function by library and symbol name. Cached after first call. resolveExtern void *(*)(ctx, libName, funcName) Resolve a native function by library and symbol name. Cached after first call.
callExtern BasValueT (*)(ctx, funcPtr, libName, funcName, args, argc, retType) Call a resolved native function, marshalling arguments and return value. callExtern BasValueT (*)(ctx, funcPtr, libName, funcName, args, argc, retType, binaryMask) Call a resolved native function, marshalling arguments and return value.
ctx void * User pointer passed to both callbacks. ctx void * User pointer passed to both callbacks.
.endtable .endtable
A native SUB is called with OP_CALL_EXTERN like a FUNCTION and leaves a dummy result on the stack; a call written as a statement drops it, as it drops a FUNCTION's result.
.h3 Terminal Controls for Libraries
A library's BASIC functions can show a byte stream on a Terminal control the way SerAttach and CommAttach do (the connection library's ConnAttach is one). The form runtime (formrt.h, exported from basrt) keeps the binding safe:
.code
typedef int32_t (*BasTermWriteFnT)(void *ctx, const uint8_t *data, int32_t len);
typedef void (*BasTermUnbindFnT)(void *ctx);
WidgetT *basFormRtTermBind(const char *ctrlName, void *ctx, BasTermWriteFnT writeFn, BasTermUnbindFnT onUnbind);
void basFormRtTermUnbind(void *ctx);
void basFormRtTermForget(void *ctx);
.endcode
basFormRtTermBind finds the Terminal control ctrlName on the running program's forms, ends whatever fed it before (a serial port, a packet link, another binding) and ctx's own earlier binding, sends what is typed there to writeFn, and returns the widget for the library to write what arrives to (NULL when there is no Terminal of that name). When the control goes away -- its form unloads, it is removed, another binding takes it -- the runtime ends the binding and calls onUnbind(ctx): the library must forget the widget. basFormRtTermUnbind ends a binding the library no longer wants; basFormRtTermForget drops it without touching the widget, for a library whose program was ended (its windows are already gone). A library outside basrt looks these up with dlsym when it first needs them.
.h2 Form Context .h2 Form Context
Set the active form context during event dispatch. Set the active form context during event dispatch.
@ -449,6 +468,55 @@ void basVmSetCurrentFormVars(BasVmT *vm,
BasValueT *vars, int32_t count); BasValueT *vars, int32_t count);
.endcode .endcode
.h2 Collections for the Host
A host hands BASIC a Collection of its own objects (the form runtime's Form.Controls) with these; basValInstance of the new Collection takes its one reference.
.code
BasObjectT *basVmCollectionNew(BasVmT *vm);
int32_t basVmCollectionAdd(BasObjectT *coll, BasValueT item, const char *key); // key NULL: none; 0 or the BASIC error number
.endcode
.h2 Control Types and the Design Surface
formrt/formDesign.c holds what a form designer needs, shared by the DVX BASIC IDE's designer and by designer.bas (DECLARE LIBRARY "basrt": CtlTypeCount, CtlTypeName, CtlTypeFlags, CtlTypeNamePrefix, CtlTypeDefaultEvent, CtlTypeTooltip, CtlTypeIconFile, CtlPropCount, CtlPropName, CtlPropType, CtlPropEnum, CtlMethodList, CtlEventList, CtlEventParams, CtlTypeEventParams, and FrmFormName, FrmControlNames, FrmCode, ClsClassName over the basRes.h scanners). CtlTypeTooltip and CtlTypeIconFile fall back to a UserControl's header (its Description; its ToolboxIcon file, basUcIconPath) when the type has no widget file:
.table
Function Description
-------- -----------
basCtlIface(typeName) The widget interface of a BASIC type name, or NULL.
basCtlPropAt(typeName, idx, out) / basCtlPropFind(...) A type's properties as BasCtlPropT (name, type, readable, writable, enum names): the runtime table's ("Form", "Menu" or the common one), the widget's own winning on a name, then the widget's others. The IDE's properties window uses basCtlPropFind.
basCtlMethodAt / basCtlEventAt(typeName, idx) A type's methods (the widget's, then the common ones) and events (the master BAS_EVENT_LIST's for that kind of object, then the widget's); the IDE's event list uses basCtlEventAt.
basCtlEventParams(typeName, eventName) The parameter list of a handler of a type's event: the widget's own (WgtEventDescT params), else the master list's; typeName NULL searches every widget after the master list, BAS_UC_OBJECT ("UserControl", the object in a UserControl's own code) has BAS_UC_EVENT_LIST's alone. The IDE's handler skeletons, CtlEventParams and CtlTypeEventParams.
basCtlDefaultEvent, basCtlNamePrefix, basCtlIsContainer As the toolbox and designer need them.
basCtlResPath(typeName, resBase, resName, size) The widget file and the (suffixed) resource name of its toolbox icon (BAS_CTL_RES_ICON) or tooltip (BAS_CTL_RES_NAME).
basCtlTypeAt(rt, idx) The idx-th control type rt sees (basUcIfaceVisible; rt NULL: a C host) in alphabetical order of BASIC name, NULL past the end. The IDE's toolbox and CtlTypeName list the types in this order; the registry's own (wgtIfaceAt) is the order the widget files loaded in.
basDsgnHandleOrigin / basDsgnHandleAt / basDsgnCursorAt Selection handle geometry: where handle h of a widget is, the resize handle under a point, the mouse pointer there.
basDsgnPaintHandles(ctx, win, w) Paints a widget's eight selection handles over a window's painted content.
basDsgnPropertyChanged(w, propName) The code of UserControl widget w called PropertyChanged: a form in DesignMode holding it raises DesignPropertyChanged (CtrlName, PropertyName). formCtl.c calls it beside the C hook (basUcSetPropChangedHook).
basDsgnWidgetHit(w, x, y) True when a point is on a visible widget.
.endtable
Form.DesignMode replaces the form window's mouse, key, paint and cursor handlers while it is True (the controls get no input, and fireCtrlEvent drops their events), raises DesignMouse, DesignKey and DesignPropertyChanged, and paints DesignSelection's handles after the widgets. Form.Controls, Control.Parent / Container and MoveBefore (basFormRtMoveControl) are dispatched there too.
.h2 UserControls for a Host
formrt/formCtl.c registers UserControls (widget classes written in BASIC) with the widget system. A program's own controls (basFormRtRegisterCtl, the stub and the IDE's runs) and a designer's (basCtlLoadDesign) get private registry keys, the class name plus a serial, so the same project running and being designed, or two programs, never share one; a control file's (basCtlLoadFile) keeps its name. Resolve names through these instead of wgtGetIface:
.table
Function Description
-------- -----------
basCtlLoadDesign(rt, module, headers, count, path) Designer rt's controls (rt: a BASIC program designing forms, compiler.bas CompDesignControls; NULL: a C host, the IDE): the .ctl headers' classes of module (the caller keeps it until basCtlUnloadDesign or rt's end), run without the module-level code, App.Path the folder of path. Returns how many were registered (a name a native or installed control has, or rt's own control has, is not). The set rt registered before is let go first.
basCtlUnloadDesign(rt) Lets designer rt's controls go (their widgets keep their last picture and run no more code). False while their code runs. basUcRuntimeEnd lets a program's go when it ends.
basUcIconPath(iface, out, outSize) The ToolboxIcon picture file of a control file's or a designer's UserControl: the header's name in the folder of the path it was registered with (the project's, for a designer's). False when it names none or iface is no such control.
basUcResolveType(rt, typeName) The registry key of a BASIC type name in rt's program: its own UserControl first (its program's or the ones it registered for design; rt NULL: the C host's design controls), else wgtResolveTypeName -- never another owner's private control (NULL then).
basUcOwnIface(rt, typeName) The interface of rt's own UserControl (its program's or a design one; rt NULL: the C host's design controls), NULL for none. basCtlIface looks here first, for the current program (basFormRtCurrent).
basUcIfaceVisible(rt, iface) False when iface is a private UserControl of another owner; a list of control types (basCtlTypeAt) leaves those out.
basUcIfaceHeader(iface) The parsed header (BasUcHeaderT: name, size, BackColor, ToolboxIcon, Description, DefaultEvent) of a UserControl's interface, NULL for any other.
basUcSetPropChangedHook(fn) fn(widget, propName) runs when a control's code calls PropertyChanged: a C designer reads its property sheet again (a BASIC one gets DesignPropertyChanged, basDsgnPropertyChanged).
basDsgnSetDesignWindow(win, on) / basDsgnIsDesignWindow Marks a window as a designer's (unmark it when it is destroyed): UserControls on it see UserMode False and get no user input.
.endtable
.h2 Stack Access .h2 Stack Access
Push and pop values on the evaluation stack for host integration. Push and pop values on the evaluation stack for host integration.
@ -466,7 +534,9 @@ Both return true on success, false on stack overflow/underflow.
const char *basVmGetError(const BasVmT *vm); const char *basVmGetError(const BasVmT *vm);
.endcode .endcode
Returns the current error message string. Valid after basVmRun returns BAS_VM_ERROR. Returns the current error message string. Valid after basVmRun returns BAS_VM_ERROR, and after a call of a SUB from the host (an event handler) that an untrapped error stopped.
The form runtime ends the program on every untrapped error, in module-level code and in event handlers alike (basFormRtRuntimeError for errors the runtime finds, the same path for the VM's own): the error is logged to DVX.LOG (summary line, details indented), the VM halts (ended), the runtime is marked terminated so its event loop ends, and the BASIC Runtime Error box shows it -- unless the host set suppressErrorDialog (the IDE), which reports it itself. The VM's message ("Runtime error 11 on line 8: Division by zero") stays readable with basVmGetError.
.h2 Sub/Function Calls from Host .h2 Sub/Function Calls from Host
@ -500,7 +570,16 @@ void basVmStepInto(BasVmT *vm);
void basVmStepOver(BasVmT *vm); void basVmStepOver(BasVmT *vm);
void basVmStepOut(BasVmT *vm); void basVmStepOut(BasVmT *vm);
void basVmRunToCursor(BasVmT *vm, int32_t line); void basVmRunToCursor(BasVmT *vm, int32_t line);
void basVmClearStepState(BasVmT *vm);
void basVmSetBreakpointCallback(BasVmT *vm,
BasBreakpointFnT fn, void *ctx);
void basVmSetDebugPaused(BasVmT *vm, bool paused);
void basVmResume(BasVmT *vm);
void basVmStop(BasVmT *vm);
int32_t basVmGetCurrentLine(const BasVmT *vm); int32_t basVmGetCurrentLine(const BasVmT *vm);
int32_t basVmGetErrorLine(const BasVmT *vm);
void basVmSetAppPaths(BasVmT *vm, const char *path,
const char *config, const char *data);
.endcode .endcode
.table .table
@ -511,7 +590,14 @@ int32_t basVmGetCurrentLine(const BasVmT *vm);
basVmStepOver Break when call depth returns to the current level. basVmStepOver Break when call depth returns to the current level.
basVmStepOut Break when call depth drops below the current level. basVmStepOut Break when call depth drops below the current level.
basVmRunToCursor Break when execution reaches the specified source line. basVmRunToCursor Break when execution reaches the specified source line.
basVmClearStepState Cancel a pending step or run-to-cursor request.
basVmSetBreakpointCallback The pause notification for SUBs the host calls (see below).
basVmSetDebugPaused Break mode on or off: while on, basVmCallSub refuses to run event handlers.
basVmResume Leave break mode; a handler paused at a breakpoint carries on.
basVmStop Stop a debugged program: step state cleared, breakpoints detached, break mode ended, the running code unwinds.
basVmGetCurrentLine Get the current source line number (from the last OP_LINE instruction). basVmGetCurrentLine Get the current source line number (from the last OP_LINE instruction).
basVmGetErrorLine The source line where the current runtime error was raised (0: none).
basVmSetAppPaths Set App.Path, App.Config and App.Data.
.endtable .endtable
The breakpoint callback notifies the host when a breakpoint fires during nested sub calls: The breakpoint callback notifies the host when a breakpoint fires during nested sub calls:
@ -538,6 +624,18 @@ typedef struct {
int32_t procCount; int32_t procCount;
BasFormVarInfoT *formVarInfo; BasFormVarInfoT *formVarInfo;
int32_t formVarInfoCount; int32_t formVarInfoCount;
BasSlotInitT *globalInits;
int32_t globalInitCount;
BasSlotInitT *localInits;
int32_t localInitCount;
BasLineNumberT *lineNumbers;
int32_t lineNumberCount;
BasClassInfoT *classes;
int32_t classCount;
BasEnumInfoT *enums;
int32_t enumCount;
BasWithEventsT *withEvents;
int32_t withEventsCount;
BasDebugVarT *debugVars; BasDebugVarT *debugVars;
int32_t debugVarCount; int32_t debugVarCount;
BasDebugUdtDefT *debugUdtDefs; BasDebugUdtDefT *debugUdtDefs;
@ -560,8 +658,142 @@ typedef struct {
procCount Number of procedures. procCount Number of procedures.
formVarInfo Per-form variable counts and init code addresses. formVarInfo Per-form variable counts and init code addresses.
formVarInfoCount Number of forms with form-scoped variables. formVarInfoCount Number of forms with form-scoped variables.
globalInits The starting type of each typed global slot (kept in a release build).
globalInitCount Number of global slot entries.
localInits The starting type of each typed local slot, per procedure (BasProcEntryT initFirst / initCount).
localInitCount Number of local slot entries.
lineNumbers The program's QBasic line numbers in source order (ERL reports them).
lineNumberCount Number of line numbers.
classes Class modules and UserControls (BasClassInfoT): members, events, Implements maps.
classCount Number of classes.
enums The Enums UserControl properties are declared As (BasEnumInfoT: member names and values in declaration order; a class member's enumIdx names one), so a host shows and stores such a property by member name. Kept in a release build.
enumCount Number of Enums.
withEvents WithEvents variables and their handlers.
withEventsCount Number of WithEvents variables.
debugVars Variable names and metadata for the debugger. debugVars Variable names and metadata for the debugger.
debugVarCount Number of debug variable entries. debugVarCount Number of debug variable entries.
debugUdtDefs UDT type definitions for the debugger. debugUdtDefs UDT type definitions for the debugger.
debugUdtDefCount Number of debug UDT definitions. debugUdtDefCount Number of debug UDT definitions.
.endtable .endtable
.topic lib.basrt.frmmodel
.title The .frm Model
.toc 1 The .frm Model
.index FrmModelT
.index FrmCtrlT
.index FrmMenuItemT
.index frmModelLoad
.index frmModelWrite
.index frmModelText
.index frmModelFromForm
.h1 The .frm Model
A form file as data, with no widgets and no windows: the form's properties, its controls (type, name, control array index, parent, the built-in size and state fields and every other "Key = Value" pair as text), its menu items and its code section. frmModelLoad reads .frm text (through frmParse) and frmModelWrite writes the canonical text the DVX BASIC IDE saves. The IDE's designer, properties window, menu editor, copy and paste all work on this model and keep only their live widgets on the side (each control's userData); designer.bas binds it for BASIC programs (the Frm* functions, formrt/frmModelBas.c).
Header: apps/dvxbasic/formrt/frmModel.h (part of basrt.lib)
.h2 Types
.table
Type Description
---- -----------
FrmModelT A form: name, caption, width / height / left / top, padding, spacing, windowState, layout, helpTopic, contextMenu, centered / autoSize / resizable, controls (stb_ds array of FrmCtrlT pointers in tree order: each control directly followed by its children), menuItems (stb_ds array), props (other runtime form properties that were set: Icon, Tag, MousePointer ...), code (malloc'd, NULL = none), dirty (changed by an edit since it was made or loaded).
FrmCtrlT A control: name, typeName, parentName + parentIndex (the parent's control array index, so a child of one member of a container array belongs to that member), index (FRM_NO_INDEX: not in an array), left, top, width (MinWidth), height (MinHeight), maxWidth, maxHeight, weight, dragMode, helpTopic, visible, enabled, props (stb_ds array of FrmPropT: every other key, unquoted), userData (the caller's; the model never reads it and loads, copies and pastes start it at NULL).
FrmMenuItemT A menu item: caption ("-" for a separator), name, shortcut, shortcutText, level (0: a menu-bar menu or a popup; 1: its items; 2+: submenus), id (an editor identity, see frmModelMenuReplace), checked, checkable (Checked was written), radioCheck, enabled, visible (False on a level-0 item: a popup menu).
FrmPropT One stored "Key = Value": name, value.
FrmIntPropT One built-in integer property (Left, Top, MinWidth alias Width, MinHeight alias Height, MaxWidth, MaxHeight, Weight, DragMode): name, alias, offset into FrmCtrlT, saveRule (FRM_SAVE_ALWAYS / IF_NONZERO / IF_POSITIVE).
.endtable
Only values somebody set are stored and written: a property a control has by default (a PictureBox's BorderStyle, a TextBox's MaxLength) is not in the model and not in the file. Values are written in the canonical form for their widget interface type (STRING quoted, BOOL True / False, INT a number, FLOAT and ENUM unquoted); keys the widget does not know are written as quoted strings. The limits come from the parser's: FRM_MAX_NEST_DEPTH control levels, FRM_MAX_MENU_LEVEL menu levels, and no property line longer than FRM_MAX_LINE_LEN - 1 characters (the writer fails with FRM_WRITE_LINE_TOO_LONG rather than write one the parser would cut).
.h2 Loading and Writing
.table
Function Description
-------- -----------
frmModelNew(name) A blank form with the runtime form's defaults.
frmModelLoad(text, len) The model of .frm text; NULL when it is not a form (no Begin Form, frmParse refuses it, two controls share a name and index) or out of memory. A Key = Value line is the innermost open block's, also after the blocks inside it, as the runtime reads it; a child names its container by the container's final Index. Of several OptionButtons of one container saved with Value True, the last keeps it.
frmModelFree(m) Frees the model, its controls and menus.
frmModelWrite(m, buf, bufSize) The .frm text into buf: its length, or FRM_WRITE_TOO_LARGE / FRM_WRITE_LINE_TOO_LONG (never a partial text).
frmModelWriteSize(m) A buffer size that always holds the text.
frmModelText(m, &len) The text in a new malloc'd string; NULL with len set to the FRM_WRITE_* code.
frmModelSetCode(m, code) Replaces the code section.
frmCodeSection(trailing, len, &outLen) The code section of the text after a form's End (blank lines skipped, the first code line keeps its indent).
frmModelFromForm(rt, form) A model of a loaded form as it is now: its properties, its controls in their containers with each property that differs from a new control of the type (compared with one made in a hidden window), its menus; no code. What FormText$ writes.
.endtable
.h2 Properties
.table
Function Description
-------- -----------
frmModelFormProp(m, name, out, size) A form property as text (an unset design property gives "", "0" or "False"); false for run-time-only ones (Visible, DesignMode, DesignSelection, object properties).
frmModelSetFormProp(m, name, value) Sets one from text; Name renames the form; an empty value takes an optional one out.
frmCtrlPropText(ctrl, name, out, size) A control property as the control holds it (Name, Index, the built-in integers, HelpTopic, Visible / Enabled unless the widget defines them, the stored ones).
frmCtrlSetPropText(ctrl, key, value) Applies one the way the loader does (a VB TabIndex is dropped unless the widget defines it).
frmModelSetCtrlProp(m, ctrl, name, value) The same with an editor's rules: Name renames the control array, Index makes an array member, then the OptionButton group rule.
frmCtrlGetProp / frmCtrlSetProp / frmCtrlRemoveProp The stored properties.
frmIntPropAt / frmIntPropFind / frmIntPropGet / Set The built-in integer table.
frmIfaceProp(typeName, propName) The widget interface property of a type, NULL when none.
.endtable
.h2 Editing Controls
.table
Function Description
-------- -----------
frmModelAddCtrl(m, typeName, name, parent) Adds a control as the last child of parent (NULL: the form); name "" or NULL gives a free one (frmModelAutoName); Caption = its name. NULL when the name is taken or parent cannot hold it.
frmModelRemoveCtrl(m, ctrl) Removes ctrl and its subtree.
frmModelRenameCtrl(m, oldName, newName) Renames a whole control array; children and DataSource / MasterSource of a renamed Data control follow. Identifier rules are the caller's.
frmModelMakeArrayMember(m, ctrl, index) Gives a control array index; its children follow.
frmModelMoveCtrl(m, ctrl, before) Moves ctrl with its subtree before a sibling (NULL: after the last).
frmModelReparent(m, ctrl, parent) Makes ctrl the last child of another container (refuses cycles, non-containers and too deep a nesting).
frmModelSibling(m, idx, dir) The sibling next to a control (+1 after its subtree, -1 before).
frmModelFindCtrl / CtrlPos / FindParent / CtrlDepth Lookups.
frmModelIsDescendant / SubtreeEnd / NormalizeOrder The tree order.
frmModelNameInUse / AutoName / OptionSelected Names and the OptionButton group rule.
frmModelCtrlText(m, ctrl, &len) ctrl and its children as .frm control blocks (a copy for the Clipboard).
frmModelCtrlsFromText(text, len) Copied blocks read into a scratch model.
frmModelInsertTree(m, src, srcRoot, parent, name, index) Moves a subtree out of the scratch model into m: the root takes name / index (NULL: its own name when free, else a free one); a child whose name is taken gets a free one, the members of one array together; children follow their renamed parents.
.endtable
.h2 Menus
The frmMenu* functions work on any stb_ds array of FrmMenuItemT (an editor's working copy as well as m->menuItems) and keep the levels a tree.
.table
Function Description
-------- -----------
frmMenuItemInit(mi) A blank item (enabled, visible, level 0).
frmMenuInsert(&items, at, level) Inserts a blank item; the level is clamped so the menus stay a tree.
frmMenuRemove(&items, idx) Removes an item with its subtree.
frmMenuMove(&items, idx, dir) Moves an item with its subtree past its previous / next sibling; its new position or FRM_NO_INDEX.
frmMenuIndent(items, idx, delta) Indents / outdents an item with its subtree (at most one level below the item above, not past FRM_MAX_MENU_LEVEL).
frmMenuSubtreeEnd / CheckLevels / StripBlank / AutoName Tree helpers; AutoName makes mnuSep1 ... or "mnu" plus the caption's letters and digits.
frmMenuPropText / frmMenuSetPropText Menu properties as text (Checked makes a check item).
frmModelMenuValidate(m, items, isIdent, reason, size) The checks before items replace m's menus (levels; every name set, an identifier by isIdent, not the form's or a control's, unique; every Shortcut a key): the first bad item and the reason.
frmModelMenuReplace(m, items) Replaces m's menus by a copy; ContextMenu bindings follow renamed items, matched by id (give every item id = position + 1 before editing).
frmModelSetMenuProp(m, idx, key, value) Sets an item's property; a Name rename refuses a taken name and ContextMenu bindings follow.
.endtable
.code
// Load a form, rename a control, add a label inside a frame, save.
FrmModelT *m = frmModelLoad(text, len);
if (m) {
int32_t at = frmModelFindCtrl(m, "Frame1", FRM_NO_INDEX);
FrmCtrlT *frame = (at >= 0) ? m->controls[at] : NULL;
FrmCtrlT *lbl = frmModelAddCtrl(m, "Label", "", frame);
int32_t outLen = 0;
frmModelRenameCtrl(m, "Command1", "cmdOk");
frmModelSetCtrlProp(m, lbl, "Caption", "Name:");
char *out = frmModelText(m, &outLen);
// write out[0 .. outLen) to the file
free(out);
frmModelFree(m);
}
.endcode

View file

@ -93,7 +93,7 @@ typedef enum {
NK_RESULT, // the result slot of the FUNCTION being compiled (its name inside it) NK_RESULT, // the result slot of the FUNCTION being compiled (its name inside it)
NK_ME, // Me: the form, or the instance in a class module NK_ME, // Me: the form, or the instance in a class module
NK_LOADFORM, // name: the form of that name (OP_LOAD_FORM) NK_LOADFORM, // name: the form of that name (OP_LOAD_FORM)
NK_NEW, // name: New ClassName / New Collection; aux = class number after resolve NK_NEW, // name: New ClassName / New Collection / New FormName; aux after resolve = class number, BAS_CLASS_COLLECTION, or BAS_CLASS_NONE for a form
NK_OMIT, // an argument left out (f(a, , b)); the resolver replaces it NK_OMIT, // an argument left out (f(a, , b)); the resolver replaces it
NK_WITHREF, // BasMemberT: the object a leading '.' names inside WITH ... END WITH; target = the BasWithT NK_WITHREF, // BasMemberT: the object a leading '.' names inside WITH ... END WITH; target = the BasWithT
NK_NAMEDARG, // BasMemberT: an argument given by name (Name:=value): name, base = the value; the resolver puts it in its parameter's place NK_NAMEDARG, // BasMemberT: an argument given by name (Name:=value): name, base = the value; the resolver puts it in its parameter's place
@ -402,6 +402,7 @@ typedef struct {
int32_t tok; // TOK_INTEGER ... (a keyword type), else TOK_IDENT int32_t tok; // TOK_INTEGER ... (a keyword type), else TOK_IDENT
int32_t fixedLen;// STRING * n int32_t fixedLen;// STRING * n
bool isNew; // AS NEW bool isNew; // AS NEW
bool isBinary;// BINARY STRING (DECLARE LIBRARY only): pointer + length
} BasTypeSpecT; } BasTypeSpecT;
typedef struct { typedef struct {

View file

@ -23,7 +23,7 @@
// basEvents.h -- canonical list of BASIC event suffixes // basEvents.h -- canonical list of BASIC event suffixes
// //
// A procedure whose name ends with one of these suffixes is treated as an // A procedure whose name ends with one of these suffixes is treated as an
// event handler (Ctrl_Load, Form_Click, etc). Referenced by the stripper // event handler (Form1_Load, Command1_Click, etc). Referenced by the stripper
// (to retain handlers in release builds), the obfuscator (to preserve event // (to retain handlers in release builds), the obfuscator (to preserve event
// naming in rewritten forms), and the IDE (to populate the Object/Event // naming in rewritten forms), and the IDE (to populate the Object/Event
// dropdowns and generate handler stubs). Keep them in one place so adding // dropdowns and generate handler stubs). Keep them in one place so adding
@ -35,19 +35,27 @@
// params -- the handler's parameter list exactly as the runtime // params -- the handler's parameter list exactly as the runtime
// passes it (formrt.c fireCtrlEvent / FireEventWithCancel), // passes it (formrt.c fireCtrlEvent / FireEventWithCancel),
// "" for events with no arguments. Control-array handlers // "" for events with no arguments. Control-array handlers
// receive an extra leading "Index As Integer". // receive an extra leading "Index As Integer". NULL for a
// widget's own event (BAS_EVT_SCOPE_WIDGET): its parameters
// are written once, in the widget's WgtEventDescT (the
// form runtime's basCtlEventParams reads them there).
// The widget-own events are listed here only so the release-build
// stripper and the obfuscator keep their handler names.
// Consumers expand it with an X macro so no parallel list can drift. // Consumers expand it with an X macro so no parallel list can drift.
#ifndef BAS_EVENTS_H #ifndef BAS_EVENTS_H
#define BAS_EVENTS_H #define BAS_EVENTS_H
#include <stdbool.h> #include <stdbool.h>
#include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <strings.h>
typedef enum { typedef enum {
BAS_EVT_SCOPE_CTRL = 1 << 0, // fired on controls (widgets, Timer) BAS_EVT_SCOPE_CTRL = 1 << 0, // fired on controls (widgets, Timer)
BAS_EVT_SCOPE_FORM = 1 << 1, // fired on the form itself BAS_EVT_SCOPE_FORM = 1 << 1, // fired on the form itself
BAS_EVT_SCOPE_DRAW = 1 << 2 // fired on the controls the drawing methods draw on (PictureBox) BAS_EVT_SCOPE_DRAW = 1 << 2, // fired on the controls the drawing methods draw on (PictureBox)
BAS_EVT_SCOPE_WIDGET = 1 << 3 // fired only by the widgets whose interface lists it (wgtFireEvent)
} BasEventScopeE; } BasEventScopeE;
#define BAS_EVT_SCOPE_BOTH (BAS_EVT_SCOPE_CTRL | BAS_EVT_SCOPE_FORM) #define BAS_EVT_SCOPE_BOTH (BAS_EVT_SCOPE_CTRL | BAS_EVT_SCOPE_FORM)
@ -78,6 +86,13 @@ typedef enum {
#define BAS_EVT_DRAGDROP "DragDrop" #define BAS_EVT_DRAGDROP "DragDrop"
#define BAS_EVT_DRAGOVER "DragOver" #define BAS_EVT_DRAGOVER "DragOver"
#define BAS_EVT_PAINT "Paint" #define BAS_EVT_PAINT "Paint"
#define BAS_EVT_COLUMNCLICK "ColumnClick"
#define BAS_EVT_GUTTERCLICK "GutterClick"
#define BAS_EVT_DESIGNMOUSE "DesignMouse"
#define BAS_EVT_DESIGNKEY "DesignKey"
#define BAS_EVT_DSGNPROPCHG "DesignPropertyChanged"
#define BAS_EVT_LINKCLICK "LinkClick"
#define BAS_EVT_NEWARGS "NewArgs"
#define BAS_EVENT_LIST(X) \ #define BAS_EVENT_LIST(X) \
X(BAS_EVT_LOAD, BAS_EVT_SCOPE_FORM, "") \ X(BAS_EVT_LOAD, BAS_EVT_SCOPE_FORM, "") \
@ -103,7 +118,63 @@ typedef enum {
X(BAS_EVT_VALIDATE, BAS_EVT_SCOPE_CTRL, "Cancel As Integer") \ X(BAS_EVT_VALIDATE, BAS_EVT_SCOPE_CTRL, "Cancel As Integer") \
X(BAS_EVT_DRAGDROP, BAS_EVT_SCOPE_BOTH, "Source As Control, X As Integer, Y As Integer") \ X(BAS_EVT_DRAGDROP, BAS_EVT_SCOPE_BOTH, "Source As Control, X As Integer, Y As Integer") \
X(BAS_EVT_DRAGOVER, BAS_EVT_SCOPE_BOTH, "Source As Control, X As Integer, Y As Integer, State As Integer") \ X(BAS_EVT_DRAGOVER, BAS_EVT_SCOPE_BOTH, "Source As Control, X As Integer, Y As Integer, State As Integer") \
X(BAS_EVT_PAINT, BAS_EVT_SCOPE_FORM | BAS_EVT_SCOPE_DRAW, "") X(BAS_EVT_PAINT, BAS_EVT_SCOPE_FORM | BAS_EVT_SCOPE_DRAW, "") \
X(BAS_EVT_COLUMNCLICK, BAS_EVT_SCOPE_WIDGET, NULL) \
X(BAS_EVT_GUTTERCLICK, BAS_EVT_SCOPE_WIDGET, NULL) \
X(BAS_EVT_DESIGNMOUSE, BAS_EVT_SCOPE_FORM, "Action As Integer, Button As Integer, X As Integer, Y As Integer, CtrlName As String, Handle As Integer") \
X(BAS_EVT_DESIGNKEY, BAS_EVT_SCOPE_FORM, "KeyCode As Integer, Shift As Integer") \
X(BAS_EVT_DSGNPROPCHG, BAS_EVT_SCOPE_FORM, "CtrlName As String, PropertyName As String") \
X(BAS_EVT_LINKCLICK, BAS_EVT_SCOPE_WIDGET, NULL) \
X(BAS_EVT_NEWARGS, BAS_EVT_SCOPE_FORM, "")
// A UserControl (a widget class written in BASIC, a .ctl file): the name
// of its own object in its code (UserControl.Refresh), the word after the
// class name on its BEGINCLASS line, and the events the runtime calls on
// it. BAS_UC_EVENT_LIST is the one table of those events: the handler is
// the class procedure UserControl_<name> with exactly the parameters
// listed (VB5's names and order); the compiler finds each class's
// handlers by it and keeps their procedure numbers in the module
// (BasClassInfoT.ucProcs, indexed by BasUcEventE).
#define BAS_UC_OBJECT "UserControl"
#define BAS_UC_CLASS_KW "USERCONTROL"
#define BAS_UC_EVT_INIT "Initialize"
#define BAS_UC_EVT_TERM "Terminate"
#define BAS_UC_EVT_WHEEL "MouseWheel"
#define BAS_UC_MOUSE_PARAMS "Button As Integer, Shift As Integer, X As Single, Y As Single"
#define BAS_UC_KEY_PARAMS "KeyCode As Integer, Shift As Integer"
#define BAS_UC_EVENT_LIST(X) \
X(BAS_UC_EVT_INITIALIZE, BAS_UC_EVT_INIT, "") \
X(BAS_UC_EVT_TERMINATE, BAS_UC_EVT_TERM, "") \
X(BAS_UC_EVT_RESIZE, BAS_EVT_RESIZE, "") \
X(BAS_UC_EVT_PAINT, BAS_EVT_PAINT, "") \
X(BAS_UC_EVT_CLICK, BAS_EVT_CLICK, "") \
X(BAS_UC_EVT_DBLCLICK, BAS_EVT_DBLCLICK, "") \
X(BAS_UC_EVT_MOUSEDOWN, BAS_EVT_MOUSEDOWN, BAS_UC_MOUSE_PARAMS) \
X(BAS_UC_EVT_MOUSEMOVE, BAS_EVT_MOUSEMOVE, BAS_UC_MOUSE_PARAMS) \
X(BAS_UC_EVT_MOUSEUP, BAS_EVT_MOUSEUP, BAS_UC_MOUSE_PARAMS) \
X(BAS_UC_EVT_MOUSEWHEEL, BAS_UC_EVT_WHEEL, "Delta As Integer") \
X(BAS_UC_EVT_KEYDOWN, BAS_EVT_KEYDOWN, BAS_UC_KEY_PARAMS) \
X(BAS_UC_EVT_KEYUP, BAS_EVT_KEYUP, BAS_UC_KEY_PARAMS) \
X(BAS_UC_EVT_KEYPRESS, BAS_EVT_KEYPRESS, "KeyAscii As Integer") \
X(BAS_UC_EVT_GOTFOCUS, BAS_EVT_GOTFOCUS, "") \
X(BAS_UC_EVT_LOSTFOCUS, BAS_EVT_LOSTFOCUS, "")
#define BAS_UC_EVENT_ENUM(id, name, params) id,
typedef enum {
BAS_UC_EVENT_LIST(BAS_UC_EVENT_ENUM)
BAS_UC_EVT_COUNT
} BasUcEventE;
// One row of BAS_UC_EVENT_LIST: the event name (the handler is
// UserControl_<name>) and its parameter list.
typedef struct {
const char *name;
const char *params;
} BasUcEventInfoT;
#define BAS_UC_EVENT_ROW(id, name, params) { name, params },
// Drag and drop values, shared by the compiler's predefined vb* // Drag and drop values, shared by the compiler's predefined vb*
// constants and the form runtime: the DragMode property, the Drag // constants and the form runtime: the DragMode property, the Drag
@ -117,6 +188,29 @@ typedef enum {
#define BAS_DRAG_LEAVE 1 // vbLeave: the drag went off it #define BAS_DRAG_LEAVE 1 // vbLeave: the drag went off it
#define BAS_DRAG_OVER 2 // vbOver: the drag moved over it #define BAS_DRAG_OVER 2 // vbOver: the drag moved over it
// The object-valued members of forms and controls: Form.Controls (the
// collection of its controls), Control.Parent (its form),
// Control.Container (the control or form it is in), Screen / Form.
// ActiveControl (the control with the focus) and Form.Icon (a Picture).
// Before another '.' they are these members, not controls of that name.
#define BAS_FORM_CONTROLS "Controls"
#define BAS_CTRL_PARENT "Parent"
#define BAS_CTRL_CONTAINER "Container"
#define BAS_FORM_ACTIVECONTROL "ActiveControl"
#define BAS_FORM_ICON "Icon"
// The Screen object: in Screen.ActiveControl.Name the first name is the
// object, not a form to load.
#define BAS_SCREEN_OBJ_NAME "Screen"
// The members that give control and form names as text (Name, a form's
// DesignSelection; Controls also takes a name). A release build keeps
// the names of a program that uses one of them, or the DesignMouse or
// DesignPropertyChanged event, so its code sees the names it was written
// with.
#define BAS_CTRL_NAME "Name"
#define BAS_FORM_DESIGNSEL "DesignSelection"
// Clipboard formats (Clipboard.GetFormat / GetText / SetText): the DVX // Clipboard formats (Clipboard.GetFormat / GetText / SetText): the DVX
// clipboard holds text. // clipboard holds text.
#define BAS_CF_TEXT 1 // vbCFText #define BAS_CF_TEXT 1 // vbCFText
@ -152,6 +246,8 @@ typedef enum {
#define BAS_ROP_PATPAINT 0x00FB0A09 // vbPatPaint #define BAS_ROP_PATPAINT 0x00FB0A09 // vbPatPaint
#define BAS_ROP_PATINVERT 0x005A0049 // vbPatInvert #define BAS_ROP_PATINVERT 0x005A0049 // vbPatInvert
#define BAS_ROP_DSTINVERT 0x00550009 // vbDstInvert #define BAS_ROP_DSTINVERT 0x00550009 // vbDstInvert
#define BAS_ROP_GRAYCOPY 0x01000000 // vbGrayCopy (DVX): the picture greyed, as a disabled one
#define BAS_ROP_TRANSPCOPY 0x02000000 // vbTransparentCopy (DVX): every pixel but the key colour
#define BAS_ROP_TABLE_SHIFT 16 // the truth table's place in an opcode #define BAS_ROP_TABLE_SHIFT 16 // the truth table's place in an opcode
#define BAS_ROP_TABLE_MASK 0xFF #define BAS_ROP_TABLE_MASK 0xFF
@ -159,16 +255,86 @@ typedef enum {
#define BAS_PIC_TYPE_NONE 0 // vbPicTypeNone #define BAS_PIC_TYPE_NONE 0 // vbPicTypeNone
#define BAS_PIC_TYPE_BITMAP 1 // vbPicTypeBitmap #define BAS_PIC_TYPE_BITMAP 1 // vbPicTypeBitmap
// Form.WindowState (VB's values; vbNormal is also the file attribute 0).
#define BAS_WINSTATE_NORMAL 0 // vbNormal
#define BAS_WINSTATE_MINIMIZED 1 // vbMinimized
#define BAS_WINSTATE_MAXIMIZED 2 // vbMaximized
// Screen.Arrange: VB's MDIForm.Arrange values (3, vbArrangeIcons, has no
// DVX meaning) plus DVX's grid.
#define BAS_ARRANGE_CASCADE 0 // vbCascade
#define BAS_ARRANGE_TILE_HORZ 1 // vbTileHorizontal: windows stacked top to bottom
#define BAS_ARRANGE_TILE_VERT 2 // vbTileVertical: windows side by side
#define BAS_ARRANGE_TILE_GRID 4 // vbTileGrid: rows and columns
// MousePointer of controls, forms and Screen (VB's values for the
// pointers DVX has; no custom pointer).
#define BAS_POINTER_DEFAULT 0 // vbDefault: the control's own pointer
#define BAS_POINTER_ARROW 1 // vbArrow
#define BAS_POINTER_CROSSHAIR 2 // vbCrosshair
#define BAS_POINTER_SIZENESW 6 // vbSizeNESW
#define BAS_POINTER_SIZENS 7 // vbSizeNS
#define BAS_POINTER_SIZENWSE 8 // vbSizeNWSE
#define BAS_POINTER_SIZEWE 9 // vbSizeWE
#define BAS_POINTER_HOURGLASS 11 // vbHourglass
#define BAS_POINTER_NODROP 12 // vbNoDrop
// One row of BAS_EVENT_LIST, for consumers that want a runtime table // One row of BAS_EVENT_LIST, for consumers that want a runtime table
// (e.g. `static const BasEventInfoT sEvents[] = { BAS_EVENT_LIST(BAS_EVENT_ROW) };`). // (e.g. `static const BasEventInfoT sEvents[] = { BAS_EVENT_LIST(BAS_EVENT_ROW) };`).
typedef struct { typedef struct {
const char *suffix; const char *suffix;
uint8_t scope; // BasEventScopeE flags uint8_t scope; // BasEventScopeE flags
const char *params; // handler parameter list, "" when none const char *params; // handler parameter list, "" when none, NULL for a widget's own event
} BasEventInfoT; } BasEventInfoT;
#define BAS_EVENT_ROW(suffix, scope, params) { suffix, scope, params }, #define BAS_EVENT_ROW(suffix, scope, params) { suffix, scope, params },
// The parameter list the master table gives a handler of event eventName:
// "" when none, NULL for a widget's own event (its WgtEventDescT has it)
// and for an event not in the table. The form runtime's basCtlEventParams
// (IDE handler skeletons, designer.bas CtlEventParams$) adds the widget
// side.
static inline const char *basEventParams(const char *eventName) {
static const BasEventInfoT sEventRows[] = { BAS_EVENT_LIST(BAS_EVENT_ROW) };
for (int32_t i = 0; eventName && i < (int32_t)(sizeof(sEventRows) / sizeof(sEventRows[0])); i++) {
if (strcasecmp(sEventRows[i].suffix, eventName) == 0) {
return sEventRows[i].params;
}
}
return NULL;
}
// True when name is one of the object-valued members above (the
// compiler then reads obj.name.member as that member's member).
static inline bool basIsObjectMember(const char *name) {
return strcasecmp(name, BAS_FORM_CONTROLS) == 0 || strcasecmp(name, BAS_CTRL_PARENT) == 0 || strcasecmp(name, BAS_CTRL_CONTAINER) == 0 || strcasecmp(name, BAS_FORM_ACTIVECONTROL) == 0 || strcasecmp(name, BAS_FORM_ICON) == 0;
}
// The number of parameters in a parameter list as the tables above write
// it ("" none, else one more than its commas).
static inline int32_t basParamListCount(const char *params) {
int32_t n = (params != NULL && params[0] != '\0') ? 1 : 0;
for (const char *p = params; p != NULL && *p; p++) {
n += (*p == ',') ? 1 : 0;
}
return n;
}
// The row of UserControl event evt (BasUcEventE), NULL past the end.
static inline const BasUcEventInfoT *basUcEventInfo(int32_t evt) {
static const BasUcEventInfoT sUcRows[BAS_UC_EVT_COUNT] = { BAS_UC_EVENT_LIST(BAS_UC_EVENT_ROW) };
return (evt >= 0 && evt < BAS_UC_EVT_COUNT) ? &sUcRows[evt] : NULL;
}
// Expands to just the suffix string followed by a comma, for a plain // Expands to just the suffix string followed by a comma, for a plain
// NULL-terminated const char *[] view of the list. // NULL-terminated const char *[] view of the list.
#define BAS_EVENT_SUFFIX(suffix, scope, params) suffix, #define BAS_EVENT_SUFFIX(suffix, scope, params) suffix,
@ -187,4 +353,9 @@ bool basEventSuffixMatch(const char *suffix);
// the event name inside name. Defined in strip.c. // the event name inside name. Defined in strip.c.
bool basSplitEventName(const char *name, char *prefix, int32_t prefixSize, const char **outSuffix); bool basSplitEventName(const char *name, char *prefix, int32_t prefixSize, const char **outSuffix);
// The same split for any suffix: "<Prefix>_<Name>" with neither part empty
// (a form's handler of a widget's or UserControl's own event). Defined in
// strip.c.
bool basSplitHandlerName(const char *name, char *prefix, int32_t prefixSize, const char **outSuffix);
#endif // BAS_EVENTS_H #endif // BAS_EVENTS_H

View file

@ -31,6 +31,8 @@
#include "front.h" #include "front.h"
#include "basEvents.h" #include "basEvents.h"
#include "dvxTypes.h"
#include "textHelp.h"
#include "../runtime/dates.h" #include "../runtime/dates.h"
#include "thirdparty/stb_ds_wrap.h" #include "thirdparty/stb_ds_wrap.h"
@ -68,6 +70,23 @@ static const PredefConstT sPredefConsts[] = {
// Show mode // Show mode
{ "vbModal", 1 }, { "vbModal", 1 },
// Form.WindowState (vbNormal is below), Screen.Arrange, Screen.MousePointer
{ "vbMinimized", BAS_WINSTATE_MINIMIZED },
{ "vbMaximized", BAS_WINSTATE_MAXIMIZED },
{ "vbCascade", BAS_ARRANGE_CASCADE },
{ "vbTileHorizontal", BAS_ARRANGE_TILE_HORZ },
{ "vbTileVertical", BAS_ARRANGE_TILE_VERT },
{ "vbTileGrid", BAS_ARRANGE_TILE_GRID },
{ "vbDefault", BAS_POINTER_DEFAULT },
{ "vbArrow", BAS_POINTER_ARROW },
{ "vbCrosshair", BAS_POINTER_CROSSHAIR },
{ "vbSizeNESW", BAS_POINTER_SIZENESW },
{ "vbSizeNS", BAS_POINTER_SIZENS },
{ "vbSizeNWSE", BAS_POINTER_SIZENWSE },
{ "vbSizeWE", BAS_POINTER_SIZEWE },
{ "vbHourglass", BAS_POINTER_HOURGLASS },
{ "vbNoDrop", BAS_POINTER_NODROP },
// File attributes // File attributes
{ "vbNormal", 0 }, { "vbNormal", 0 },
{ "vbReadOnly", 1 }, { "vbReadOnly", 1 },
@ -108,6 +127,59 @@ static const PredefConstT sPredefConsts[] = {
// Clipboard formats // Clipboard formats
{ "vbCFText", BAS_CF_TEXT }, { "vbCFText", BAS_CF_TEXT },
// CallByName's call types
{ "vbMethod", BAS_CALLTYPE_METHOD },
{ "vbGet", BAS_CALLTYPE_GET },
{ "vbLet", BAS_CALLTYPE_LET },
{ "vbSet", BAS_CALLTYPE_SET },
// KeyDown key codes and Shift bits: DVX's own codes (a letter or other
// character is its ASCII code, Ctrl+A to Ctrl+Z are 1 to 26)
{ "vbKeyBack", KEY_BACKSPACE },
{ "vbKeyTab", KEY_TAB },
{ "vbKeyReturn", KEY_ENTER },
{ "vbKeyEscape", KEY_ESCAPE },
{ "vbKeySpace", KEY_SPACE },
{ "vbKeyPageUp", KEY_PGUP },
{ "vbKeyPageDown", KEY_PGDN },
{ "vbKeyEnd", KEY_END },
{ "vbKeyHome", KEY_HOME },
{ "vbKeyLeft", KEY_LEFT },
{ "vbKeyUp", KEY_UP },
{ "vbKeyRight", KEY_RIGHT },
{ "vbKeyDown", KEY_DOWN },
{ "vbKeyInsert", KEY_INSERT },
{ "vbKeyDelete", KEY_DELETE },
{ "vbKeyF1", KEY_F1 },
{ "vbKeyF2", KEY_F2 },
{ "vbKeyF3", KEY_F3 },
{ "vbKeyF4", KEY_F4 },
{ "vbKeyF5", KEY_F5 },
{ "vbKeyF6", KEY_F6 },
{ "vbKeyF7", KEY_F7 },
{ "vbKeyF8", KEY_F8 },
{ "vbKeyF9", KEY_F9 },
{ "vbKeyF10", KEY_F10 },
{ "vbKeyF11", KEY_F11 },
{ "vbKeyF12", KEY_F12 },
{ "vbShiftMask", KEY_MOD_SHIFT },
{ "vbCtrlMask", KEY_MOD_CTRL },
{ "vbAltMask", KEY_MOD_ALT },
// TextArea syntax colour indices (SetSyntaxColor, the Colorize event)
{ "vbSyntaxText", TEXT_SYNTAX_DEFAULT },
{ "vbSyntaxKeyword", TEXT_SYNTAX_KEYWORD },
{ "vbSyntaxString", TEXT_SYNTAX_STRING },
{ "vbSyntaxComment", TEXT_SYNTAX_COMMENT },
{ "vbSyntaxNumber", TEXT_SYNTAX_NUMBER },
{ "vbSyntaxOperator", TEXT_SYNTAX_OPERATOR },
{ "vbSyntaxType", TEXT_SYNTAX_TYPE },
// The Button argument of the mouse events
{ "vbLeftButton", MOUSE_LEFT },
{ "vbRightButton", MOUSE_RIGHT },
{ "vbMiddleButton", MOUSE_MIDDLE },
// Colours: RGB values (&HBBGGRR, as RGB() makes them) // Colours: RGB values (&HBBGGRR, as RGB() makes them)
{ "vbBlack", 0x000000 }, { "vbBlack", 0x000000 },
{ "vbRed", 0x0000FF }, { "vbRed", 0x0000FF },
@ -143,6 +215,14 @@ static const PredefConstT sPredefConsts[] = {
{ "vbPatPaint", BAS_ROP_PATPAINT }, { "vbPatPaint", BAS_ROP_PATPAINT },
{ "vbPatInvert", BAS_ROP_PATINVERT }, { "vbPatInvert", BAS_ROP_PATINVERT },
{ "vbDstInvert", BAS_ROP_DSTINVERT }, { "vbDstInvert", BAS_ROP_DSTINVERT },
{ "vbGrayCopy", BAS_ROP_GRAYCOPY },
{ "vbTransparentCopy", BAS_ROP_TRANSPCOPY },
// DrawArrow directions (the libdvx arrow glyph's)
{ "dvxArrowUp", ScrollArrowUpE },
{ "dvxArrowDown", ScrollArrowDownE },
{ "dvxArrowLeft", ScrollArrowLeftE },
{ "dvxArrowRight", ScrollArrowRightE },
{ "vbLPSmall", BAS_LP_SMALL }, { "vbLPSmall", BAS_LP_SMALL },
{ "vbLPLarge", BAS_LP_LARGE }, { "vbLPLarge", BAS_LP_LARGE },
{ "vbLPSmallShell", BAS_LP_SMALL_SHELL }, { "vbLPSmallShell", BAS_LP_SMALL_SHELL },
@ -178,6 +258,7 @@ static const PredefConstT sPredefConsts[] = {
// Function prototypes (alphabetical) // Function prototypes (alphabetical)
static bool addPredefConsts(BasCompilerT *c); static bool addPredefConsts(BasCompilerT *c);
static bool appendEnum(const BasCompilerT *c, BasModuleT *mod, const BasSymT *e, int32_t *outIdx);
static void applyMemberPatches(BasCompilerT *c); static void applyMemberPatches(BasCompilerT *c);
bool basCompile(BasCompilerT *c); bool basCompile(BasCompilerT *c);
BasModuleT *basCompilerBuildModule(BasCompilerT *c); BasModuleT *basCompilerBuildModule(BasCompilerT *c);
@ -194,13 +275,17 @@ void basCompilerSetValidator(BasCompilerT *c, const BasCtrlValidatorT *v);
void basErrorAt(BasCompilerT *c, int32_t line, int32_t col, const char *fmt, ...); void basErrorAt(BasCompilerT *c, int32_t line, int32_t col, const char *fmt, ...);
void basErrorNode(BasCompilerT *c, const BasNodeT *n, const char *fmt, ...); void basErrorNode(BasCompilerT *c, const BasNodeT *n, const char *fmt, ...);
static bool buildClasses(const BasCompilerT *c, BasModuleT *mod); static bool buildClasses(const BasCompilerT *c, BasModuleT *mod);
static bool buildClassEvents(const BasClassDefT *def, BasClassInfoT *info);
static bool buildEnums(const BasCompilerT *c, BasModuleT *mod);
static bool buildGlobalInits(const BasCompilerT *c, BasModuleT *mod); static bool buildGlobalInits(const BasCompilerT *c, BasModuleT *mod);
static bool buildInterfaceMap(const BasCompilerT *c, int32_t classIdx, int32_t iface, BasImplementsT *impl); static bool buildInterfaceMap(const BasCompilerT *c, int32_t classIdx, int32_t iface, BasImplementsT *impl);
static bool buildProcs(const BasCompilerT *c, BasModuleT *mod); static bool buildProcs(const BasCompilerT *c, BasModuleT *mod);
static void buildUserControl(const BasCompilerT *c, int32_t classIdx, const BasModuleT *mod, BasClassInfoT *info);
static bool buildWithEvents(const BasCompilerT *c, BasModuleT *mod); static bool buildWithEvents(const BasCompilerT *c, BasModuleT *mod);
static int32_t classMemberNamed(const BasClassDefT *cls, const char *name, uint8_t kind); static int32_t classMemberNamed(const BasClassDefT *cls, const char *name, uint8_t kind);
static void collectDebugGlobals(BasCompilerT *c); static void collectDebugGlobals(BasCompilerT *c);
static bool emittableProc(const BasSymT *s); static bool emittableProc(const BasSymT *s);
static const char *enumMemberName(const BasSymT *s, const BasSymT *e);
static void errorV(BasCompilerT *c, int32_t line, int32_t col, const char *fmt, va_list ap); static void errorV(BasCompilerT *c, int32_t line, int32_t col, const char *fmt, va_list ap);
static bool globalStartsTyped(const BasSymT *s); static bool globalStartsTyped(const BasSymT *s);
static bool joinParamNames(const BasProcInfoT *pi, char **out); static bool joinParamNames(const BasProcInfoT *pi, char **out);
@ -228,6 +313,53 @@ static bool addPredefConsts(BasCompilerT *c) {
} }
// Appends ENUM e to mod->enums with its members (the module scope's
// "Enum.Member" constants, enumMemberName) in declaration order. *outIdx
// receives its index, BAS_ENUM_NONE for an ENUM without members. False
// when memory runs out.
static bool appendEnum(const BasCompilerT *c, BasModuleT *mod, const BasSymT *e, int32_t *outIdx) {
int32_t count = 0;
*outIdx = BAS_ENUM_NONE;
for (const BasSymT *s = c->moduleTab.first; s != NULL; s = s->orderNext) {
count += (enumMemberName(s, e) != NULL) ? 1 : 0;
}
if (count == 0) {
return true;
}
BasEnumMemberT *members = (BasEnumMemberT *)calloc((size_t)count, sizeof(BasEnumMemberT));
BasEnumInfoT *grown = (members != NULL) ? (BasEnumInfoT *)realloc(mod->enums, (size_t)(mod->enumCount + 1) * sizeof(BasEnumInfoT)) : NULL;
if (grown == NULL) {
free(members);
return false;
}
BasEnumInfoT *en = &grown[mod->enumCount];
en->members = members;
en->memberCount = 0;
mod->enums = grown;
*outIdx = mod->enumCount++;
for (const BasSymT *s = c->moduleTab.first; s != NULL; s = s->orderNext) {
const char *name = enumMemberName(s, e);
if (name != NULL) {
BasEnumMemberT *m = &en->members[en->memberCount++];
snprintf(m->name, sizeof(m->name), "%s", name);
m->value = s->u.cval->intVal;
}
}
return true;
}
// Fills in the member operands of the early-bound instructions: the // Fills in the member operands of the early-bound instructions: the
// member's index in its class's public member table, complete now that // member's index in its class's public member table, complete now that
// every class is compiled. // every class is compiled.
@ -317,7 +449,7 @@ BasModuleT *basCompilerBuildModule(BasCompilerT *c) {
BasModuleT *mod = basCodeGenBuildModule(&c->cg); BasModuleT *mod = basCodeGenBuildModule(&c->cg);
// Each build step frees mod when it fails // Each build step frees mod when it fails
if (mod == NULL || !buildGlobalInits(c, mod) || !buildProcs(c, mod) || !buildClasses(c, mod) || !buildWithEvents(c, mod)) { if (mod == NULL || !buildGlobalInits(c, mod) || !buildProcs(c, mod) || !buildClasses(c, mod) || !buildEnums(c, mod) || !buildWithEvents(c, mod)) {
return NULL; return NULL;
} }
@ -391,15 +523,6 @@ void basCompilerDestroy(BasCompilerT *c) {
arrfree(c->withEvents[i].handlers); arrfree(c->withEvents[i].handlers);
} }
// A procedure left open by an error still owns its labels' lists
if (c->inProc) {
for (const BasSymT *l = c->localLabels.first; l != NULL; l = l->orderNext) {
arrfree(l->u.label->patches);
arrfree(l->u.label->patchDepths);
arrfree(l->u.label->dataPatches);
}
}
for (int32_t i = 0; i < (int32_t)arrlen(c->loops); i++) { for (int32_t i = 0; i < (int32_t)arrlen(c->loops); i++) {
arrfree(c->loops[i].exitPatches); arrfree(c->loops[i].exitPatches);
} }
@ -509,6 +632,13 @@ static bool buildClasses(const BasCompilerT *c, BasModuleT *mod) {
info->initCodeAddr = def->initCodeAddr; info->initCodeAddr = def->initCodeAddr;
info->terminateProc = (def->terminateSym != NULL) ? procIndexAt(mod, def->terminateSym->u.proc->codeAddr) : -1; info->terminateProc = (def->terminateSym != NULL) ? procIndexAt(mod, def->terminateSym->u.proc->codeAddr) : -1;
buildUserControl(c, k, mod, info);
if (!buildClassEvents(def, info)) {
basModuleFree(mod);
return false;
}
if (ni > 0) { if (ni > 0) {
info->implements = (BasImplementsT *)calloc(ni, sizeof(BasImplementsT)); info->implements = (BasImplementsT *)calloc(ni, sizeof(BasImplementsT));
@ -589,6 +719,99 @@ static bool buildClasses(const BasCompilerT *c, BasModuleT *mod) {
} }
// A class's Events as the module keeps them (BasClassEventT). False when
// memory runs out.
// gcc -fanalyzer loses the parameter lists stored at a variable index of
// the new table and reports them leaked when the table is handed to info
// (a false positive: basModuleFree frees them through info).
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
static bool buildClassEvents(const BasClassDefT *def, BasClassInfoT *info) {
int32_t count = (int32_t)arrlen(def->events);
if (count == 0) {
return true;
}
BasClassEventT *events = (BasClassEventT *)calloc((size_t)count, sizeof(BasClassEventT));
if (events == NULL) {
return false;
}
for (int32_t i = 0; i < count; i++) {
const BasEventDefT *ev = &def->events[i];
BasClassEventT *out = &events[i];
snprintf(out->name, sizeof(out->name), "%s", ev->name);
out->paramCount = DVX_MIN(ev->paramCount, BAS_VM_MAX_CALL_ARGS);
for (int32_t p = 0; p < out->paramCount; p++) {
out->paramTypes[p] = ev->paramTypes[p];
out->byValParams |= (ev->paramFlags[p] & PARAM_BYVAL) ? (1u << p) : 0u;
}
if (ev->paramNames != NULL && ev->paramNames[0] != '\0') {
char *names = strdup(ev->paramNames);
if (names == NULL) {
basClassEventsFree(events, i);
return false;
}
out->paramNames = names;
}
}
info->eventCount = count;
info->events = events;
return true;
}
#pragma GCC diagnostic pop
// The ENUMs UserControl properties are declared As (mod->enums, each one
// once) and every class member's enumIdx (BAS_ENUM_NONE for the members
// of other classes and for other values). Frees mod and returns false
// when memory runs out.
static bool buildEnums(const BasCompilerT *c, BasModuleT *mod) {
const BasSymT **syms = NULL; // stb_ds: the ENUM of each mod->enums entry
bool ok = true;
for (int32_t k = 0; ok && k < mod->classCount; k++) {
const BasClassDefT *def = &c->classes[k];
BasClassInfoT *info = &mod->classes[k];
for (int32_t i = 0; ok && i < info->memberCount; i++) {
const BasSymT *e = def->isUserControl ? def->members[i].enumSym : NULL;
int32_t idx = BAS_ENUM_NONE;
for (int32_t n = 0; e != NULL && n < (int32_t)arrlen(syms); n++) {
idx = (syms[n] == e) ? n : idx;
}
if (e != NULL && idx == BAS_ENUM_NONE) {
ok = appendEnum(c, mod, e, &idx);
if (idx != BAS_ENUM_NONE) {
arrput(syms, e);
}
}
info->members[i].enumIdx = idx;
}
}
arrfree(syms);
if (!ok) {
basModuleFree(mod);
}
return ok;
}
// Global scalars that start as their type's zero rather than a cleared // Global scalars that start as their type's zero rather than a cleared
// slot's INTEGER 0 (STRING, and the other typed scalars), a table that // slot's INTEGER 0 (STRING, and the other typed scalars), a table that
// survives stripping unlike the debug variables. Frees mod and returns // survives stripping unlike the debug variables. Frees mod and returns
@ -752,6 +975,32 @@ static bool buildProcs(const BasCompilerT *c, BasModuleT *mod) {
} }
// A UserControl class's flag, the slot of its UserControl object and the
// procedures of its UserControl_<event> handlers; for another class no
// slot.
static void buildUserControl(const BasCompilerT *c, int32_t classIdx, const BasModuleT *mod, BasClassInfoT *info) {
const BasClassDefT *def = &c->classes[classIdx];
char qualified[BAS_MAX_IDENT];
info->ucSlot = -1;
if (!def->isUserControl || !basQualify(c, classIdx, BAS_UC_OBJECT, qualified, (int32_t)sizeof(qualified))) {
return;
}
const BasSymT *slot = basScopeFind(&c->moduleTab, qualified);
info->flags = BAS_CLASS_USERCONTROL;
info->ucSlot = (slot != NULL && slot->kind == SK_VAR) ? slot->index : -1;
for (int32_t e = 0; e < BAS_UC_EVT_COUNT; e++) {
const BasSymT *sym = basUserControlProc(c, classIdx, e);
info->ucProcs[e] = (sym != NULL) ? procIndexAt(mod, sym->u.proc->codeAddr) : -1;
}
}
// The WithEvents variables, handlers by their index in mod->procs. Frees // The WithEvents variables, handlers by their index in mod->procs. Frees
// mod and returns false when memory runs out. // mod and returns false when memory runs out.
static bool buildWithEvents(const BasCompilerT *c, BasModuleT *mod) { static bool buildWithEvents(const BasCompilerT *c, BasModuleT *mod) {
@ -869,6 +1118,19 @@ static bool emittableProc(const BasSymT *s) {
} }
// The member name of module symbol s when s is a member of ENUM e (the
// constant "Enum.Member" resolve.c's addEnumMember made), else NULL.
static const char *enumMemberName(const BasSymT *s, const BasSymT *e) {
size_t len = strlen(e->name);
if (s->kind != SK_CONST || strncmp(s->name, e->name, len) != 0 || s->name[len] != BAS_CLASS_SEP[0]) {
return NULL;
}
return s->name + len + 1;
}
// Records the first error: "Line N, Col M: text" ("Line N: text" when the // Records the first error: "Line N, Col M: text" ("Line N: text" when the
// column is not known). // column is not known).
static void errorV(BasCompilerT *c, int32_t line, int32_t col, const char *fmt, va_list ap) { static void errorV(BasCompilerT *c, int32_t line, int32_t col, const char *fmt, va_list ap) {
@ -917,14 +1179,10 @@ static bool joinParamNames(const BasProcInfoT *pi, char **out) {
return false; return false;
} }
(*out)[0] = '\0'; size_t used = 0;
for (int32_t p = 0; p < pi->paramCount; p++) { for (int32_t p = 0; p < pi->paramCount; p++) {
if (p > 0) { used += (size_t)snprintf(*out + used, len - used, "%s%s", p > 0 ? BAS_PARAM_NAME_SEP : "", pi->paramNames[p]);
strcat(*out, BAS_PARAM_NAME_SEP);
}
strcat(*out, pi->paramNames[p]);
} }
return true; return true;

View file

@ -334,7 +334,7 @@ static bool constFromValue(BasCompilerT *c, BasValueT v, BasConstValT *out, cons
case BAS_TYPE_STRING: case BAS_TYPE_STRING:
if (v.strVal->len >= BAS_MAX_CONST_STR) { if (v.strVal->len >= BAS_MAX_CONST_STR) {
basErrorNode(c, at, "Constant string longer than %d characters", (int)(BAS_MAX_CONST_STR - 1)); basErrorNode(c, at, "Constant string longer than %d characters", BAS_MAX_CONST_STR - 1);
return false; return false;
} }

View file

@ -59,6 +59,10 @@
#define BAS_MAX_EXPR_DEPTH 64 #define BAS_MAX_EXPR_DEPTH 64
#define BAS_MAX_BLOCK_DEPTH 32 #define BAS_MAX_BLOCK_DEPTH 32
// blockDepth while a top-level unit's own statement is parsed (a
// procedure must be one: its scope lives in the unit arena).
#define BAS_UNIT_BLOCK_DEPTH 1
// Longest TYPE field chain in a store target (a.b.c ...). // Longest TYPE field chain in a store target (a.b.c ...).
#define BAS_MAX_FIELD_CHAIN BAS_LAYOUT_MAX_DEPTH #define BAS_MAX_FIELD_CHAIN BAS_LAYOUT_MAX_DEPTH
@ -145,6 +149,10 @@
// Built-in function flags. // Built-in function flags.
#define BI_PURE 0x01 // result depends on the arguments alone: allowed in constant expressions #define BI_PURE 0x01 // result depends on the arguments alone: allowed in constant expressions
#define BI_FOLD 0x02 // folded in ordinary code when its arguments are literals #define BI_FOLD 0x02 // folded in ordinary code when its arguments are literals
#define BI_OBJECT_ARG 0x04 // the first argument is an object (a bare control or form name is the object)
// The built-in function a statement may call too (CallByName obj, ...).
#define BAS_CALLBYNAME_NAME "CallByName"
// A built-in function (BasBuiltinT table in resolveExpr.c). An // A built-in function (BasBuiltinT table in resolveExpr.c). An
// OP_BUILTIN entry names its BasBuiltinFnE in func and takes its argument // OP_BUILTIN entry names its BasBuiltinFnE in func and takes its argument
@ -236,6 +244,7 @@ struct BasCompilerS {
bool parseInProc; // inside a SUB / FUNCTION / Property body bool parseInProc; // inside a SUB / FUNCTION / Property body
bool parseProcIsFunction; // ... a FUNCTION or Property Get (its name sets its result) bool parseProcIsFunction; // ... a FUNCTION or Property Get (its name sets its result)
bool parseProcIsStatic; // ... declared STATIC (its DIMs declare STATIC variables) bool parseProcIsStatic; // ... declared STATIC (its DIMs declare STATIC variables)
bool parseInDeclareLib; // inside a DECLARE LIBRARY block (BINARY STRING allowed)
const char *parseProcName; // the procedure being parsed (its own name sets its result) const char *parseProcName; // the procedure being parsed (its own name sets its result)
const char *parseClassName; // the class module being parsed (module arena; parseInClass) const char *parseClassName; // the class module being parsed (module arena; parseInClass)
BasWithT *parseWith; // the innermost WITH block being parsed (its '.name's) BasWithT *parseWith; // the innermost WITH block being parsed (its '.name's)
@ -388,6 +397,7 @@ void basDeclareMemberVars(BasCompilerT *c, const BasStmtT *dim, int32_t classIdx
void basDeclareMemberVarsEnd(BasCompilerT *c); void basDeclareMemberVarsEnd(BasCompilerT *c);
void basDeclareProc(BasCompilerT *c, BasProcT *proc, int32_t classIdx); void basDeclareProc(BasCompilerT *c, BasProcT *proc, int32_t classIdx);
void basDeclareTypeName(BasCompilerT *c, const char *name); void basDeclareTypeName(BasCompilerT *c, const char *name);
void basDeclareUserControlVar(BasCompilerT *c, int32_t classIdx);
BasSymT *basEnsureVariable(BasCompilerT *c, const char *name, const BasNodeT *at); BasSymT *basEnsureVariable(BasCompilerT *c, const char *name, const BasNodeT *at);
int32_t basExprClass(const BasCompilerT *c, const BasNodeT *n); int32_t basExprClass(const BasCompilerT *c, const BasNodeT *n);
int32_t basExprUdtType(const BasCompilerT *c, const BasNodeT *n); int32_t basExprUdtType(const BasCompilerT *c, const BasNodeT *n);
@ -435,6 +445,7 @@ BasScopeE basStorageScope(const BasSymT *sym);
void basSymChange(BasCompilerT *c, BasSymT *sym, const BasNodeT *at, uint8_t dataType, int32_t udtTypeId, uint32_t flags); void basSymChange(BasCompilerT *c, BasSymT *sym, const BasNodeT *at, uint8_t dataType, int32_t udtTypeId, uint32_t flags);
void basSymChangesUndo(BasCompilerT *c); void basSymChangesUndo(BasCompilerT *c);
BasNodeT *basTypedArg(BasCompilerT *c, const char *procName, int32_t idx, uint8_t dataType, bool byVal, BasNodeT *arg); BasNodeT *basTypedArg(BasCompilerT *c, const char *procName, int32_t idx, uint8_t dataType, bool byVal, BasNodeT *arg);
BasSymT *basUserControlProc(const BasCompilerT *c, int32_t classIdx, int32_t evt);
bool basValidateCtrlMember(BasCompilerT *c, const char *ctrlName, const char *member, bool isProp, const BasNodeT *at); bool basValidateCtrlMember(BasCompilerT *c, const char *ctrlName, const char *member, bool isProp, const BasNodeT *at);
BasNodeT *basWithObject(BasCompilerT *c, const BasNodeT *ref); BasNodeT *basWithObject(BasCompilerT *c, const BasNodeT *ref);
@ -467,6 +478,7 @@ void basGenJumpTo(BasCompilerT *c, uint8_t op, int32_t target);
void basGenLine(BasCompilerT *c, const BasNodeT *s); void basGenLine(BasCompilerT *c, const BasNodeT *s);
void basGenLoadTemp(BasCompilerT *c, int32_t slot); void basGenLoadTemp(BasCompilerT *c, int32_t slot);
void basGenLoadVar(BasCompilerT *c, const BasSymT *sym); void basGenLoadVar(BasCompilerT *c, const BasSymT *sym);
void basGenNew(BasCompilerT *c, const char *name, int32_t objClass);
void basGenObjMember(BasCompilerT *c, uint8_t op, const BasCallT *call); void basGenObjMember(BasCompilerT *c, uint8_t op, const BasCallT *call);
void basGenPatchHere(BasCompilerT *c, int32_t operandPos); void basGenPatchHere(BasCompilerT *c, int32_t operandPos);
void basGenPushCStr(BasCompilerT *c, const char *text); void basGenPushCStr(BasCompilerT *c, const char *text);

View file

@ -38,6 +38,9 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
// BAS_FN_NEW_FORM's argument: the form's name.
#define BAS_NEW_FORM_ARGS 1
// Function prototypes (alphabetical) // Function prototypes (alphabetical)
void basGenAddrOf(BasCompilerT *c, const BasSymT *sym); void basGenAddrOf(BasCompilerT *c, const BasSymT *sym);
@ -47,6 +50,7 @@ int32_t basGenJump(BasCompilerT *c, uint8_t op);
void basGenJumpTo(BasCompilerT *c, uint8_t op, int32_t target); void basGenJumpTo(BasCompilerT *c, uint8_t op, int32_t target);
void basGenLoadTemp(BasCompilerT *c, int32_t slot); void basGenLoadTemp(BasCompilerT *c, int32_t slot);
void basGenLoadVar(BasCompilerT *c, const BasSymT *sym); void basGenLoadVar(BasCompilerT *c, const BasSymT *sym);
void basGenNew(BasCompilerT *c, const char *name, int32_t objClass);
void basGenPatchHere(BasCompilerT *c, int32_t operandPos); void basGenPatchHere(BasCompilerT *c, int32_t operandPos);
void basGenPushCStr(BasCompilerT *c, const char *text); void basGenPushCStr(BasCompilerT *c, const char *text);
void basGenPushInt16(BasCompilerT *c, int16_t v); void basGenPushInt16(BasCompilerT *c, int16_t v);
@ -112,6 +116,7 @@ void basGenCall(BasCompilerT *c, BasSymT *sym, int32_t argc) {
basEmitU16(&c->cg, pi->externFuncIdx); basEmitU16(&c->cg, pi->externFuncIdx);
basEmit8(&c->cg, (uint8_t)argc); basEmit8(&c->cg, (uint8_t)argc);
basEmit8(&c->cg, sym->dataType); basEmit8(&c->cg, sym->dataType);
basEmit32(&c->cg, (int32_t)pi->externBinaryMask);
return; return;
} }
@ -251,6 +256,23 @@ void basGenLoadVar(BasCompilerT *c, const BasSymT *sym) {
} }
// New name: a new object of class objClass (a class number or
// BAS_CLASS_COLLECTION), or with BAS_CLASS_NONE a new copy of the form
// name.
void basGenNew(BasCompilerT *c, const char *name, int32_t objClass) {
if (objClass == BAS_CLASS_NONE) {
basGenPushCStr(c, name);
basEmit8(&c->cg, OP_BUILTIN);
basEmit8(&c->cg, BAS_FN_NEW_FORM);
basEmit8(&c->cg, BAS_NEW_FORM_ARGS);
return;
}
basEmit8(&c->cg, OP_NEW_OBJ);
basEmitU16(&c->cg, classOperand(objClass));
}
// Lands the forward jump whose operand is at operandPos here. // Lands the forward jump whose operand is at operandPos here.
void basGenPatchHere(BasCompilerT *c, int32_t operandPos) { void basGenPatchHere(BasCompilerT *c, int32_t operandPos) {
basPatch32(&c->cg, operandPos, basCodePos(&c->cg) - (operandPos + BAS_CODE_ADDR_SIZE)); basPatch32(&c->cg, operandPos, basCodePos(&c->cg) - (operandPos + BAS_CODE_ADDR_SIZE));
@ -604,8 +626,7 @@ static void genDeclarator(BasCompilerT *c, const BasDeclT *d) {
} }
if (d->spec != NULL && d->spec->isNew) { if (d->spec != NULL && d->spec->isNew) {
basEmit8(&c->cg, OP_NEW_OBJ); basGenNew(c, d->spec->name, sym->objClass);
basEmitU16(&c->cg, classOperand(sym->objClass));
basGenStore(c, sym); basGenStore(c, sym);
return; return;
} }
@ -728,16 +749,10 @@ static void genProc(BasCompilerT *c, const BasProcT *proc) {
basEmit8(&c->cg, OP_RET); basEmit8(&c->cg, OP_RET);
} }
// Parameters, locals and the temporaries share the frame // Parameters, locals and the temporaries share the frame (the labels'
// patch lists are freed when basResolveUnitEnd closes the scope)
pi->localCount = c->nextLocalSlot; pi->localCount = c->nextLocalSlot;
c->debugProcCount++; c->debugProcCount++;
// A label's patch lists live in the heap, its record in the unit arena
for (BasSymT *l = c->localLabels.first; l != NULL; l = l->orderNext) {
arrfree(l->u.label->patches);
arrfree(l->u.label->patchDepths);
arrfree(l->u.label->dataPatches);
}
} }
@ -772,7 +787,7 @@ static void genProcInits(BasCompilerT *c, const BasSymT *sym) {
pi->localInits = NULL; pi->localInits = NULL;
if (count > 0) { if (count > 0) {
pi->localInits = (BasSlotInitT *)basArenaAlloc(&c->modArena, (int32_t)(count * (int32_t)sizeof(BasSlotInitT))); pi->localInits = (BasSlotInitT *)basArenaAlloc(&c->modArena, count * (int32_t)sizeof(BasSlotInitT));
if (pi->localInits == NULL) { if (pi->localInits == NULL) {
basErrorAt(c, 0, 0, "Out of memory"); basErrorAt(c, 0, 0, "Out of memory");

View file

@ -152,8 +152,7 @@ void basGenExpr(BasCompilerT *c, const BasNodeT *n) {
break; break;
case NK_NEW: case NK_NEW:
basEmit8(&c->cg, OP_NEW_OBJ); basGenNew(c, ((const BasNameT *)n)->name, ((const BasNameT *)n)->aux);
basEmitU16(&c->cg, (uint16_t)((const BasNameT *)n)->aux);
break; break;
case NK_ELEM: case NK_ELEM:

View file

@ -376,6 +376,8 @@ void basGenStatement(BasCompilerT *c, const BasNodeT *s) {
if (st->name != NULL) { if (st->name != NULL) {
basGenPushCStr(c, st->name); basGenPushCStr(c, st->name);
basEmit8(&c->cg, OP_LOAD_FORM); basEmit8(&c->cg, OP_LOAD_FORM);
} else if (st->a != NULL) {
basGenExpr(c, st->a);
} else { } else {
basEmit8(&c->cg, OP_ME_REF); basEmit8(&c->cg, OP_ME_REF);
} }

View file

@ -25,6 +25,7 @@
// See obfuscate.h for the high-level description. // See obfuscate.h for the high-level description.
#include "obfuscate.h" #include "obfuscate.h"
#include "strip.h"
#include "basEvents.h" #include "basEvents.h"
#include "lexer.h" #include "lexer.h"
#include "../basRes.h" #include "../basRes.h"
@ -33,15 +34,17 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <strings.h>
#define BAS_OBF_TOKEN_LEN BAS_MAX_IDENT // longest form/control/type token kept from a .frm line #define BAS_OBF_TOKEN_LEN BAS_MAX_IDENT // longest form/control/type token kept from a .frm line
#define BAS_OBF_MAPPED_LEN 16 // "C" + decimal index + NUL #define BAS_OBF_MAPPED_LEN 16 // "C" + decimal index + NUL
#define BAS_OBF_MAP_INIT_CAP 16 // initial name-map capacity (doubles on growth) #define BAS_OBF_MAP_INIT_CAP 16 // initial name-map capacity (doubles on growth)
#define BAS_REM_KEYWORD_LEN 3 // "REM" #define BAS_REM_KEYWORD_LEN 3 // "REM"
// .frm properties whose value names another control on the form; only // .frm properties whose value names another control or a menu of the
// these values follow a control rename. // form; only these values follow a rename.
static const char *sNameRefKeys[] = { static const char *sNameRefKeys[] = {
"ContextMenu",
"DataSource", "DataSource",
"MasterSource", "MasterSource",
NULL NULL
@ -74,6 +77,7 @@ static const char *nameMapAdd(NameMapT *m, const char *name);
static void nameMapFree(NameMapT *m); static void nameMapFree(NameMapT *m);
static void nameMapInit(NameMapT *m); static void nameMapInit(NameMapT *m);
static const char *nameMapLookup(const NameMapT *m, const char *name); static const char *nameMapLookup(const NameMapT *m, const char *name);
static bool namesUsedAsData(const BasModuleT *mod);
static const char *readToken(const char *p, const char *end, char *buf, int32_t bufSize, bool *outTruncated); static const char *readToken(const char *p, const char *end, char *buf, int32_t bufSize, bool *outTruncated);
static bool replaceConstant(BasModuleT *mod, int32_t idx, const char *newText); static bool replaceConstant(BasModuleT *mod, int32_t idx, const char *newText);
static int32_t rewriteFrmText(const char *src, int32_t srcLen, const NameMapT *map, uint8_t *out, int32_t outCap); static int32_t rewriteFrmText(const char *src, int32_t srcLen, const NameMapT *map, uint8_t *out, int32_t outCap);
@ -122,7 +126,11 @@ bool basObfuscateNames(BasModuleT *mod, const char **frmTexts, const int32_t *fr
} }
} }
for (int32_t i = 0; ok && i < names.count; i++) { // A program that reads names as text keeps them all (see
// namesUsedAsData); comments are still stripped below.
bool keepNames = namesUsedAsData(mod);
for (int32_t i = 0; ok && !keepNames && i < names.count; i++) {
if (!nameMapLookup(&reserved, names.entries[i].orig)) { if (!nameMapLookup(&reserved, names.entries[i].orig)) {
ok = nameMapAdd(&map, names.entries[i].orig) != NULL; ok = nameMapAdd(&map, names.entries[i].orig) != NULL;
} }
@ -387,7 +395,7 @@ static const char *nameMapAdd(NameMapT *m, const char *name) {
} }
char mapped[BAS_OBF_MAPPED_LEN]; char mapped[BAS_OBF_MAPPED_LEN];
snprintf(mapped, sizeof(mapped), "C%ld", (long)(m->count + 1)); snprintf(mapped, sizeof(mapped), "C%ld", (long)m->count + 1);
char *origCopy = strdup(name); char *origCopy = strdup(name);
char *mappedCopy = strdup(mapped); char *mappedCopy = strdup(mapped);
@ -438,6 +446,43 @@ static const char *nameMapLookup(const NameMapT *m, const char *name) {
} }
// True when the program uses control and form names as text: it reads
// the Name property, a form's Controls or DesignSelection (the member
// names are string constants of the module), or handles DesignMouse or
// DesignPropertyChanged, whose CtrlName is a name. Renaming would then
// change what its code sees. A string literal of the same spelling
// counts too, which only keeps names that could have been renamed.
static bool namesUsedAsData(const BasModuleT *mod) {
static const char *const sNameMembers[] = { BAS_CTRL_NAME, BAS_FORM_CONTROLS, BAS_FORM_DESIGNSEL };
static const char *const sNameEvents[] = { BAS_EVT_DESIGNMOUSE, BAS_EVT_DSGNPROPCHG };
for (int32_t i = 0; i < mod->constCount; i++) {
for (int32_t m = 0; mod->constants[i] && m < (int32_t)(sizeof(sNameMembers) / sizeof(sNameMembers[0])); m++) {
if (strcasecmp(mod->constants[i]->data, sNameMembers[m]) == 0) {
return true;
}
}
}
for (int32_t i = 0; i < mod->procCount; i++) {
char prefix[BAS_MAX_IDENT];
const char *suffix;
if (!basSplitEventName(mod->procs[i].name, prefix, (int32_t)sizeof(prefix), &suffix)) {
continue;
}
for (int32_t e = 0; e < (int32_t)(sizeof(sNameEvents) / sizeof(sNameEvents[0])); e++) {
if (strcasecmp(suffix, sNameEvents[e]) == 0) {
return true;
}
}
}
return false;
}
// Copy the next whitespace-delimited token into buf and return a pointer // Copy the next whitespace-delimited token into buf and return a pointer
// past the WHOLE token. *outTruncated reports a token longer than buf. // past the WHOLE token. *outTruncated reports a token longer than buf.
static const char *readToken(const char *p, const char *end, char *buf, int32_t bufSize, bool *outTruncated) { static const char *readToken(const char *p, const char *end, char *buf, int32_t bufSize, bool *outTruncated) {
@ -661,11 +706,12 @@ static void rewriteModuleProcs(BasModuleT *mod, const NameMapT *map) {
} }
} }
// <Control>_<Event> handlers follow the control rename. // <Control>_<Event> handlers follow the control rename (a form's
// for any event: a UserControl raises its own, as the stripper keeps)
char prefix[BAS_MAX_IDENT]; char prefix[BAS_MAX_IDENT];
const char *suffix; const char *suffix;
if (!basSplitEventName(proc->name, prefix, (int32_t)sizeof(prefix), &suffix)) { if (!basSplitProcHandler(proc, prefix, (int32_t)sizeof(prefix), &suffix)) {
continue; continue;
} }

View file

@ -52,9 +52,12 @@ typedef struct {
// - Procedure names matching <OrigName>_<Event> // - Procedure names matching <OrigName>_<Event>
// - formVarInfo entries keyed by form name // - formVarInfo entries keyed by form name
// //
// Only the values of properties that name another control (DataSource, // Only the values of properties that name another control or a menu
// MasterSource) follow a rename; a Caption or Text that happens to equal // (ContextMenu, DataSource, MasterSource) follow a rename; a Caption or
// a control name is left alone. // Text that happens to equal a control name is left alone. A program
// that uses names as text (the Name property, Controls, DesignSelection,
// a DesignMouse handler) keeps every name: its code must see the names
// it was written with.
// //
// frmTexts / frmLens: input .frm buffers (one per form). // frmTexts / frmLens: input .frm buffers (one per form).
// outFrms: caller-allocated array of frmCount entries; function fills // outFrms: caller-allocated array of frmCount entries; function fills

View file

@ -449,7 +449,13 @@ typedef enum {
// second callback. This allows BASIC programs to use any library // second callback. This allows BASIC programs to use any library
// (serial, security, third-party) without recompiling the runtime. // (serial, security, third-party) without recompiling the runtime.
#define OP_CALL_EXTERN 0xCD // [uint16 libNameIdx] [uint16 funcNameIdx] [uint8 argc] [uint8 retType] #define OP_CALL_EXTERN 0xCD // [uint16 libNameIdx] [uint16 funcNameIdx] [uint8 argc] [uint8 retType] [uint32 binaryMask]
// OP_CALL_EXTERN binaryMask: bit i set = argument i was declared BINARY
// STRING (passed as pointer + int32 length); BAS_EXTERN_BINARY_RET set =
// the result is BINARY STRING (the callee gets a trailing int32_t * for
// its length). Arguments use bits 0 .. BAS_MAX_PARAMS - 1.
#define BAS_EXTERN_BINARY_RET 0x80000000u
#define OP_GET_BLUE 0xD0 // pop LONG color; push (color>>16) & 0xFF #define OP_GET_BLUE 0xD0 // pop LONG color; push (color>>16) & 0xFF
@ -606,9 +612,20 @@ typedef enum {
BAS_FN_ERROR_TEXT = 81, // ERROR$([n]) / ERROR([n]) -> the text of error n (none: the current error's) BAS_FN_ERROR_TEXT = 81, // ERROR$([n]) / ERROR([n]) -> the text of error n (none: the current error's)
BAS_FN_LIKE = 82, // a LIKE pattern: (a, pattern) -> a matches the pattern (Option Compare decides case) BAS_FN_LIKE = 82, // a LIKE pattern: (a, pattern) -> a matches the pattern (Option Compare decides case)
BAS_FN_FILEATTR = 83, // FILEATTR(n, attribute) -> the open mode of file n (BAS_FILEATTR_MODE) or its system handle (BAS_FILEATTR_HANDLE) BAS_FN_FILEATTR = 83, // FILEATTR(n, attribute) -> the open mode of file n (BAS_FILEATTR_MODE) or its system handle (BAS_FILEATTR_HANDLE)
BAS_FN_QBCOLOR = 84 // QBCOLOR(n) -> QBasic colour n (0..15) as an RGB value BAS_FN_QBCOLOR = 84, // QBCOLOR(n) -> QBasic colour n (0..15) as an RGB value
BAS_FN_NEW_FORM = 85, // New FormName (compiler-generated): (name) -> a new copy of the form (its own window, controls and form-level variables)
BAS_FN_CALLBYNAME = 86, // CALLBYNAME(object, name, callType [, args...]) -> the member's value or result (Empty for vbLet / vbSet)
BAS_FN_LOADFORMTEXT = 87 // LOADFORMTEXT(frmText) -> a form loaded from .frm text
} BasBuiltinFnE; } BasBuiltinFnE;
// CallByName's call types (VB's vbMethod, vbGet, vbLet, vbSet) and its
// fixed arguments (object, name, call type) before the member's own.
#define BAS_CALLTYPE_METHOD 1
#define BAS_CALLTYPE_GET 2
#define BAS_CALLTYPE_LET 4
#define BAS_CALLTYPE_SET 8
#define BAS_CALLBYNAME_FIXED 3
// BAS_FN_PARAMARRAY's first argument: the element type (low byte) and // BAS_FN_PARAMARRAY's first argument: the element type (low byte) and
// this flag for an array that starts at 1 (ARRAY under OPTION BASE 1). // this flag for an array that starts at 1 (ARRAY under OPTION BASE 1).
#define BAS_PARAMARRAY_TYPE_MASK 0xFF #define BAS_PARAMARRAY_TYPE_MASK 0xFF
@ -879,9 +896,9 @@ typedef enum {
#define BAS_OPERAND_I32 4 #define BAS_OPERAND_I32 4
#define BAS_OPERAND_MEMBER 5 // [uint16 class] [uint16 member] [uint8 argc] #define BAS_OPERAND_MEMBER 5 // [uint16 class] [uint16 member] [uint8 argc]
#define BAS_OPERAND_CALL 6 // [int32 addr] [uint8 argc] [uint8 baseSlot] #define BAS_OPERAND_CALL 6 // [int32 addr] [uint8 argc] [uint8 baseSlot]
#define BAS_OPERAND_CALL_EXTERN 6 // [uint16 lib] [uint16 func] [uint8 argc] [uint8 retType]
#define BAS_OPERAND_FOR 7 // [uint16 varIdx] [uint8 scope] [int32 offset] #define BAS_OPERAND_FOR 7 // [uint16 varIdx] [uint8 scope] [int32 offset]
#define BAS_OPERAND_F64 8 #define BAS_OPERAND_F64 8
#define BAS_OPERAND_CALL_EXTERN 10 // [uint16 lib] [uint16 func] [uint8 argc] [uint8 retType] [uint32 binaryMask]
#define BAS_OPERAND_UNKNOWN (-1) #define BAS_OPERAND_UNKNOWN (-1)
// Code addresses (OP_CALL / OP_CALL_ME targets, procedure and set-up // Code addresses (OP_CALL / OP_CALL_ME targets, procedure and set-up
@ -923,7 +940,7 @@ static inline bool basBuiltinArity(uint8_t fn, int32_t *minArgs, int32_t *maxArg
*maxArgs = 0; *maxArgs = 0;
return true; return true;
case BAS_FN_QBCOLOR: case BAS_FN_QBCOLOR: case BAS_FN_NEW_FORM: case BAS_FN_LOADFORMTEXT:
case BAS_FN_STRREVERSE: case BAS_FN_DATEVALUE: case BAS_FN_TIMEVALUE: case BAS_FN_STRREVERSE: case BAS_FN_DATEVALUE: case BAS_FN_TIMEVALUE:
case BAS_FN_YEAR: case BAS_FN_MONTH: case BAS_FN_DAY: case BAS_FN_YEAR: case BAS_FN_MONTH: case BAS_FN_DAY:
case BAS_FN_HOUR: case BAS_FN_MINUTE: case BAS_FN_SECOND: case BAS_FN_HOUR: case BAS_FN_MINUTE: case BAS_FN_SECOND:
@ -1050,6 +1067,11 @@ static inline bool basBuiltinArity(uint8_t fn, int32_t *minArgs, int32_t *maxArg
*maxArgs = BAS_BUILTIN_MAX_ARGS; *maxArgs = BAS_BUILTIN_MAX_ARGS;
return true; return true;
case BAS_FN_CALLBYNAME:
*minArgs = BAS_CALLBYNAME_FIXED;
*maxArgs = BAS_BUILTIN_MAX_ARGS;
return true;
case BAS_FN_PARAMARRAY: case BAS_FN_PARAMARRAY:
*minArgs = 1; *minArgs = 1;
*maxArgs = BAS_BUILTIN_MAX_ARGS; *maxArgs = BAS_BUILTIN_MAX_ARGS;

View file

@ -240,7 +240,7 @@ BasNodeT *basParseUnit(BasCompilerT *c) {
// name buffer they are copied into); the lexer accepts longer tokens. // name buffer they are copied into); the lexer accepts longer tokens.
static void checkIdentLength(BasCompilerT *c) { static void checkIdentLength(BasCompilerT *c) {
if (c->lex.token.type == TOK_IDENT && c->lex.token.textLen >= BAS_MAX_IDENT) { if (c->lex.token.type == TOK_IDENT && c->lex.token.textLen >= BAS_MAX_IDENT) {
basParseError(c, "Identifier too long (maximum is %d characters)", (int)(BAS_MAX_IDENT - 1)); basParseError(c, "Identifier too long (maximum is %d characters)", BAS_MAX_IDENT - 1);
} }
} }

View file

@ -469,6 +469,15 @@ static void declPassNames(BasCompilerT *c, DeclPointT **points) {
if (c->lex.token.type == TOK_STRING_LIT) { if (c->lex.token.type == TOK_STRING_LIT) {
if (isClass) { if (isClass) {
basDeclareClass(c, c->lex.token.text); basDeclareClass(c, c->lex.token.text);
// BEGINCLASS "Name" USERCONTROL: a .ctl's class
int32_t classIdx = basClassFind(c, c->lex.token.text);
declAdvance(c);
if (classIdx >= 0 && basParseCheckWord(c, BAS_UC_CLASS_KW)) {
c->classes[classIdx].isUserControl = true;
}
} else { } else {
basDeclareForm(c, c->lex.token.text); basDeclareForm(c, c->lex.token.text);
} }
@ -592,6 +601,12 @@ static void declSignatureAt(BasCompilerT *c, int32_t *classIdx, bool *isPrivate)
if (basParseCheckWord(c, BAS_WRAP_BEGINCLASS)) { if (basParseCheckWord(c, BAS_WRAP_BEGINCLASS)) {
declAdvance(c); declAdvance(c);
*classIdx = (c->lex.token.type == TOK_STRING_LIT) ? basClassFind(c, c->lex.token.text) : BAS_CLASS_NONE; *classIdx = (c->lex.token.type == TOK_STRING_LIT) ? basClassFind(c, c->lex.token.text) : BAS_CLASS_NONE;
// A UserControl's own object is its first member variable
if (*classIdx != BAS_CLASS_NONE && c->classes[*classIdx].isUserControl) {
basDeclareUserControlVar(c, *classIdx);
}
return; return;
} }
@ -973,8 +988,12 @@ static BasNodeT *parseDeclareLibrary(BasCompilerT *c) {
return NULL; return NULL;
} }
c->parseInDeclareLib = true;
BasProcT *proc = basParseProcHeader(c, kind, false); BasProcT *proc = basParseProcHeader(c, kind, false);
c->parseInDeclareLib = false;
if (proc == NULL) { if (proc == NULL) {
return NULL; return NULL;
} }
@ -1377,6 +1396,11 @@ static BasNodeT *parseProcDef(BasCompilerT *c, uint8_t memberKind, bool isPrivat
return NULL; return NULL;
} }
if (c->blockDepth > BAS_UNIT_BLOCK_DEPTH) {
basParseError(c, "%s cannot be defined inside IF, FOR, DO, WHILE, SELECT CASE or WITH", kwName);
return NULL;
}
BasProcT *proc = basParseProcHeader(c, memberKind, true); BasProcT *proc = basParseProcHeader(c, memberKind, true);
if (proc == NULL) { if (proc == NULL) {
@ -1675,6 +1699,25 @@ static BasTypeSpecT *parseTypeSpec(BasCompilerT *c, bool allowNew) {
basParseAdvance(c); basParseAdvance(c);
return t; return t;
// BINARY STRING: a native function's string as bytes + length
case TOK_BINARY:
if (!c->parseInDeclareLib) {
basParseError(c, "BINARY STRING is only allowed in a DECLARE LIBRARY block");
return NULL;
}
basParseAdvance(c);
if (!basParseCheck(c, TOK_STRING_KW)) {
basParseErrorExpected(c, "STRING after BINARY");
return NULL;
}
t->tok = TOK_STRING_KW;
t->isBinary = true;
basParseAdvance(c);
return t;
default: default:
basParseError(c, BAS_TYPE_EXPECTED); basParseError(c, BAS_TYPE_EXPECTED);
return NULL; return NULL;

View file

@ -1178,11 +1178,12 @@ static BasNodeT *parsePrimary(BasCompilerT *c) {
} }
case TOK_NEW: { case TOK_NEW: {
// New ClassName / New Collection // New ClassName / New Collection / New FormName (a copy of
// the form)
basParseAdvance(c); basParseAdvance(c);
if (!basParseCheck(c, TOK_IDENT) || (basClassFind(c, c->lex.token.text) < 0 && strcasecmp(c->lex.token.text, BAS_COLLECTION_TYPE_NAME) != 0)) { if (!basParseCheck(c, TOK_IDENT) || (basClassFind(c, c->lex.token.text) < 0 && strcasecmp(c->lex.token.text, BAS_COLLECTION_TYPE_NAME) != 0 && !basIsFormName(c, c->lex.token.text))) {
basParseErrorExpected(c, "class name (or Collection) after New"); basParseErrorExpected(c, "class name, form name or Collection after New");
return NULL; return NULL;
} }

View file

@ -683,7 +683,9 @@ BasNodeT *basParseStatement(BasCompilerT *c) {
} }
case TOK_UNLOAD: { case TOK_UNLOAD: {
// Unload Me / Unload FormName // Unload Me / Unload FormName / Unload formExpr (a Form
// variable, obj.Window ...): the resolver tells a form's name
// from a variable.
basParseAdvance(c); basParseAdvance(c);
BasStmtT *s = newStmt(c, ST_UNLOAD); BasStmtT *s = newStmt(c, ST_UNLOAD);
@ -693,13 +695,14 @@ BasNodeT *basParseStatement(BasCompilerT *c) {
} }
if (!basParseMatch(c, TOK_ME)) { if (!basParseMatch(c, TOK_ME)) {
if (!basParseCheck(c, TOK_IDENT)) { bool literal = basParseCheck(c, TOK_INT_LIT) || basParseCheck(c, TOK_LONG_LIT) || basParseCheck(c, TOK_FLOAT_LIT) || basParseCheck(c, TOK_STRING_LIT) || basParseCheck(c, TOK_DATE_LIT) || basParseCheck(c, TOK_CUR_LIT);
if (literal || basParseAtEnd(c)) {
basParseErrorExpected(c, "form name"); basParseErrorExpected(c, "form name");
break; break;
} }
s->name = basParseTokenName(c); s->a = basParseExpr(c);
basParseAdvance(c);
} }
n = &s->h; n = &s->h;
@ -801,6 +804,11 @@ BasNodeT *basParseStatement(BasCompilerT *c) {
basParseAdvance(c); basParseAdvance(c);
// A UserControl's class: the declaration pass marked it
if (isClass && basParseCheckWord(c, BAS_UC_CLASS_KW)) {
basParseAdvance(c);
}
// A form's controls follow its name // A form's controls follow its name
BasNodeT *tail = NULL; BasNodeT *tail = NULL;

View file

@ -71,7 +71,7 @@ typedef struct {
// Function prototypes (alphabetical) // Function prototypes (alphabetical)
static bool addClassMember(BasCompilerT *c, const char *name, BasSymT *sym, uint8_t kind, bool isPrivate, const BasNodeT *at); static bool addClassMember(BasCompilerT *c, const char *name, BasSymT *sym, uint8_t kind, const BasSymT *enumSym, bool isPrivate, const BasNodeT *at);
static bool addEnumMember(BasCompilerT *c, const BasSymT *enumSym, const char *member, bool filePrivate, int32_t value, const BasNodeT *at); static bool addEnumMember(BasCompilerT *c, const BasSymT *enumSym, const char *member, bool filePrivate, int32_t value, const BasNodeT *at);
static BasSymT *addModuleSym(BasCompilerT *c, const char *name, uint8_t kind, uint8_t dataType); static BasSymT *addModuleSym(BasCompilerT *c, const char *name, uint8_t kind, uint8_t dataType);
static bool aliasTargetShared(const BasCompilerT *c, const BasSymT *alias); static bool aliasTargetShared(const BasCompilerT *c, const BasSymT *alias);
@ -88,6 +88,7 @@ void basDeclareForm(BasCompilerT *c, const char *name);
void basDeclareLibrary(BasCompilerT *c, const BasStmtT *s); void basDeclareLibrary(BasCompilerT *c, const BasStmtT *s);
void basDeclareProc(BasCompilerT *c, BasProcT *proc, int32_t classIdx); void basDeclareProc(BasCompilerT *c, BasProcT *proc, int32_t classIdx);
void basDeclareTypeName(BasCompilerT *c, const char *name); void basDeclareTypeName(BasCompilerT *c, const char *name);
void basDeclareUserControlVar(BasCompilerT *c, int32_t classIdx);
int32_t basFieldIndex(const BasTypeDefT *td, const char *name); int32_t basFieldIndex(const BasTypeDefT *td, const char *name);
bool basIsDefFnName(const char *name); bool basIsDefFnName(const char *name);
bool basIsFormName(const BasCompilerT *c, const char *name); bool basIsFormName(const BasCompilerT *c, const char *name);
@ -102,6 +103,7 @@ void basResolveUnitEnd(BasCompilerT *c, BasNodeT *unit);
BasSymT *basStaticVariable(BasCompilerT *c, const char *name, uint8_t dataType, bool isTyped, int32_t objClass, int32_t fixedLen, const BasNodeT *at, BasSymT **outSlot); BasSymT *basStaticVariable(BasCompilerT *c, const char *name, uint8_t dataType, bool isTyped, int32_t objClass, int32_t fixedLen, const BasNodeT *at, BasSymT **outSlot);
void basSymChange(BasCompilerT *c, BasSymT *sym, const BasNodeT *at, uint8_t dataType, int32_t udtTypeId, uint32_t flags); void basSymChange(BasCompilerT *c, BasSymT *sym, const BasNodeT *at, uint8_t dataType, int32_t udtTypeId, uint32_t flags);
void basSymChangesUndo(BasCompilerT *c); void basSymChangesUndo(BasCompilerT *c);
BasSymT *basUserControlProc(const BasCompilerT *c, int32_t classIdx, int32_t evt);
static bool buildSignature(BasCompilerT *c, const BasProcT *proc, uint32_t flags, SignatureT *sig); static bool buildSignature(BasCompilerT *c, const BasProcT *proc, uint32_t flags, SignatureT *sig);
static int32_t callLeavesValue(const BasNodeT *call); static int32_t callLeavesValue(const BasNodeT *call);
static BasSymT *classEventProc(BasCompilerT *c, const char *eventName, const BasNodeT *at); static BasSymT *classEventProc(BasCompilerT *c, const char *eventName, const BasNodeT *at);
@ -109,6 +111,7 @@ static void copySignature(BasSymT *sym, const SignatureT *sig);
static void debugLocals(BasCompilerT *c, int32_t procIndex); static void debugLocals(BasCompilerT *c, int32_t procIndex);
static BasNodeT *defaultMemberTarget(BasCompilerT *c, BasNodeT *obj, bool host); static BasNodeT *defaultMemberTarget(BasCompilerT *c, BasNodeT *obj, bool host);
static void defineLabel(BasCompilerT *c, BasStmtT *s); static void defineLabel(BasCompilerT *c, BasStmtT *s);
static uint32_t externBinaryMask(const BasProcT *proc);
static BasSymT *findLabel(BasCompilerT *c, const char *name); static BasSymT *findLabel(BasCompilerT *c, const char *name);
static BasSymT *findOrAddProc(BasCompilerT *c, const char *name, uint8_t kind, uint8_t returnType, const BasNodeT *at); static BasSymT *findOrAddProc(BasCompilerT *c, const char *name, uint8_t kind, uint8_t returnType, const BasNodeT *at);
static bool fractionalLiteral(const BasNodeT *n); static bool fractionalLiteral(const BasNodeT *n);
@ -121,7 +124,9 @@ static bool numericLiteral(const BasNodeT *n);
static bool openProcScope(BasCompilerT *c, const char *name, uint8_t kind); static bool openProcScope(BasCompilerT *c, const char *name, uint8_t kind);
static bool plainValue(const BasNodeT *n); static bool plainValue(const BasNodeT *n);
static BasProcInfoT *procInfo(BasCompilerT *c, BasSymT *sym); static BasProcInfoT *procInfo(BasCompilerT *c, BasSymT *sym);
static int32_t procLeavesValue(const BasSymT *sym);
static bool procSymbolName(BasCompilerT *c, int32_t classIdx, const char *name, uint8_t kind, char *out, int32_t outSize, const BasNodeT *at); static bool procSymbolName(BasCompilerT *c, int32_t classIdx, const char *name, uint8_t kind, char *out, int32_t outSize, const BasNodeT *at);
static const BasSymT *procValueEnum(const BasCompilerT *c, const BasProcT *proc);
static BasSymT *refLabel(BasCompilerT *c, const char *name, const BasNodeT *at); static BasSymT *refLabel(BasCompilerT *c, const char *name, const BasNodeT *at);
static bool registerParams(BasCompilerT *c, BasProcT *proc, const SignatureT *sig); static bool registerParams(BasCompilerT *c, BasProcT *proc, const SignatureT *sig);
static void resolveAppend(BasCompilerT *c, BasStmtT *s); static void resolveAppend(BasCompilerT *c, BasStmtT *s);
@ -163,9 +168,12 @@ static void resolveSelect(BasCompilerT *c, BasSelectT *sel);
static void resolveShared(BasCompilerT *c, BasStmtT *s); static void resolveShared(BasCompilerT *c, BasStmtT *s);
static void resolveStatic(BasCompilerT *c, BasStmtT *s); static void resolveStatic(BasCompilerT *c, BasStmtT *s);
static void resolveTypeDef(BasCompilerT *c, BasStmtT *s); static void resolveTypeDef(BasCompilerT *c, BasStmtT *s);
static void resolveUnload(BasCompilerT *c, BasStmtT *s);
static void resolveUserControls(BasCompilerT *c);
static void resolveWith(BasCompilerT *c, BasWithT *w); static void resolveWith(BasCompilerT *c, BasWithT *w);
static void resolveWithEvents(BasCompilerT *c); static void resolveWithEvents(BasCompilerT *c);
static bool sameSignature(const BasSymT *sym, const SignatureT *sig); static bool sameSignature(const BasSymT *sym, const SignatureT *sig);
static const BasSymT *specEnum(const BasCompilerT *c, const BasTypeSpecT *t);
static void staticAllocation(BasCompilerT *c, BasSymT *local, BasSymT *slot, const BasDeclT *d, int32_t udt); static void staticAllocation(BasCompilerT *c, BasSymT *local, BasSymT *slot, const BasDeclT *d, int32_t udt);
static bool staticBound(BasCompilerT *c, BasNodeT *expr, BasConstValT *out); static bool staticBound(BasCompilerT *c, BasNodeT *expr, BasConstValT *out);
static BasConstValT *storeConst(BasCompilerT *c, const BasConstValT *v, BasArenaT *arena); static BasConstValT *storeConst(BasCompilerT *c, const BasConstValT *v, BasArenaT *arena);
@ -173,9 +181,10 @@ static bool symDefined(const BasCompilerT *c, const BasSymT *sym);
// Records a public member of the class being compiled (its name as // Records a public member of the class being compiled (its name as
// programs write it after the dot). Private members are not recorded. // programs write it after the dot, the ENUM its value is declared As or
// Two members may share a name only when both are Property procedures. // NULL). Private members are not recorded. Two members may share a name
static bool addClassMember(BasCompilerT *c, const char *name, BasSymT *sym, uint8_t kind, bool isPrivate, const BasNodeT *at) { // only when both are Property procedures.
static bool addClassMember(BasCompilerT *c, const char *name, BasSymT *sym, uint8_t kind, const BasSymT *enumSym, bool isPrivate, const BasNodeT *at) {
BasClassDefT *cls = &c->classes[c->classIdx]; BasClassDefT *cls = &c->classes[c->classIdx];
sym->ownerClass = c->classIdx; sym->ownerClass = c->classIdx;
@ -210,6 +219,7 @@ static bool addClassMember(BasCompilerT *c, const char *name, BasSymT *sym, uint
memset(&def, 0, sizeof(def)); memset(&def, 0, sizeof(def));
def.name = basArenaStrDup(&c->modArena, name, (int32_t)strlen(name)); def.name = basArenaStrDup(&c->modArena, name, (int32_t)strlen(name));
def.sym = sym; def.sym = sym;
def.enumSym = enumSym;
def.kind = kind; def.kind = kind;
arrput(cls->members, def); arrput(cls->members, def);
return true; return true;
@ -221,7 +231,7 @@ static bool addClassMember(BasCompilerT *c, const char *name, BasSymT *sym, uint
// then ambiguous and must be qualified, as in VB). // then ambiguous and must be qualified, as in VB).
static bool addEnumMember(BasCompilerT *c, const BasSymT *enumSym, const char *member, bool filePrivate, int32_t value, const BasNodeT *at) { static bool addEnumMember(BasCompilerT *c, const BasSymT *enumSym, const char *member, bool filePrivate, int32_t value, const BasNodeT *at) {
char qualified[BAS_MAX_IDENT]; char qualified[BAS_MAX_IDENT];
int n = snprintf(qualified, sizeof(qualified), "%s.%s", enumSym->name, member); int n = snprintf(qualified, sizeof(qualified), "%s" BAS_CLASS_SEP "%s", enumSym->name, member);
if (n < 0 || n >= (int)sizeof(qualified)) { if (n < 0 || n >= (int)sizeof(qualified)) {
basErrorNode(c, at, "ENUM member name '%s.%s' is too long", enumSym->name, member); basErrorNode(c, at, "ENUM member name '%s.%s' is too long", enumSym->name, member);
@ -355,6 +365,13 @@ static bool appendSafe(const BasNodeT *n) {
} }
case NK_BUILTIN: { case NK_BUILTIN: {
const BasBuiltinT *b = (const BasBuiltinT *)((const BasCallT *)n)->target;
// CallByName and LoadFormText run BASIC code (a member, Form_Load)
if (b != NULL && b->opcode == OP_BUILTIN && (b->func == BAS_FN_CALLBYNAME || b->func == BAS_FN_LOADFORMTEXT)) {
return false;
}
for (const BasNodeT *a = ((const BasCallT *)n)->args; a != NULL; a = a->next) { for (const BasNodeT *a = ((const BasCallT *)n)->args; a != NULL; a = a->next) {
if (!appendSafe(a)) { if (!appendSafe(a)) {
return false; return false;
@ -460,8 +477,15 @@ int32_t basClassFind(BasCompilerT *c, const char *name) {
} }
// Closes the scope openProcScope opened. // Closes the scope openProcScope opened. Its labels' records are in the
// unit arena, their patch lists in the heap.
void basCloseProcScope(BasCompilerT *c) { void basCloseProcScope(BasCompilerT *c) {
for (const BasSymT *l = c->localLabels.first; l != NULL; l = l->orderNext) {
arrfree(l->u.label->patches);
arrfree(l->u.label->patchDepths);
arrfree(l->u.label->dataPatches);
}
c->inProc = false; c->inProc = false;
c->inDefFn = false; c->inDefFn = false;
c->curProc = NULL; c->curProc = NULL;
@ -573,6 +597,7 @@ void basDeclareLibrary(BasCompilerT *c, const BasStmtT *s) {
sym->flags |= SF_DEFINED | SF_EXTERN; sym->flags |= SF_DEFINED | SF_EXTERN;
sym->u.proc->externLibIdx = libIdx; sym->u.proc->externLibIdx = libIdx;
sym->u.proc->externFuncIdx = basAddConstant(&c->cg, externName, (int32_t)strlen(externName)); sym->u.proc->externFuncIdx = basAddConstant(&c->cg, externName, (int32_t)strlen(externName));
sym->u.proc->externBinaryMask = externBinaryMask(proc);
copySignature(sym, &sig); copySignature(sym, &sig);
} }
} }
@ -677,6 +702,7 @@ void basDeclareProc(BasCompilerT *c, BasProcT *proc, int32_t classIdx) {
sym->dataType = returnType; sym->dataType = returnType;
sym->ownerClass = classIdx; sym->ownerClass = classIdx;
sym->objClass = cls; sym->objClass = cls;
sym->line = proc->h.line;
sym->flags &= ~(SF_PRIVATE | SF_PROPERTY | SF_DEFINED | SF_FRIEND); sym->flags &= ~(SF_PRIVATE | SF_PROPERTY | SF_DEFINED | SF_FRIEND);
sym->flags |= (proc->isPrivate ? SF_PRIVATE : 0) | (proc->isFriend ? SF_FRIEND : 0) | ((proc->memberKind == BAS_MEMBER_GET || proc->memberKind == BAS_MEMBER_LET || proc->memberKind == BAS_MEMBER_SET) ? SF_PROPERTY : 0); sym->flags |= (proc->isPrivate ? SF_PRIVATE : 0) | (proc->isFriend ? SF_FRIEND : 0) | ((proc->memberKind == BAS_MEMBER_GET || proc->memberKind == BAS_MEMBER_LET || proc->memberKind == BAS_MEMBER_SET) ? SF_PROPERTY : 0);
copySignature(sym, &sig); copySignature(sym, &sig);
@ -699,6 +725,24 @@ void basDeclareTypeName(BasCompilerT *c, const char *name) {
} }
// Declaration pass: the implicit UserControl member variable of a
// UserControl class (the control's own surface and host side, an Object
// the runtime sets before UserControl_Initialize). It takes the class's
// first member slot.
void basDeclareUserControlVar(BasCompilerT *c, int32_t classIdx) {
BasMemberVarDeclT decl;
memset(&decl, 0, sizeof(decl));
decl.name = BAS_UC_OBJECT;
decl.classIdx = classIdx;
decl.udtTypeId = -1;
decl.objClass = BAS_CLASS_NONE;
decl.dataType = BAS_TYPE_OBJECT;
decl.flags = SF_TYPED | SF_PRIVATE;
arrput(c->memberVarDecls, decl);
}
int32_t basFieldIndex(const BasTypeDefT *td, const char *name) { int32_t basFieldIndex(const BasTypeDefT *td, const char *name) {
for (int32_t i = 0; i < td->fieldCount; i++) { for (int32_t i = 0; i < td->fieldCount; i++) {
if (strcasecmp(td->fields[i].name, name) == 0) { if (strcasecmp(td->fields[i].name, name) == 0) {
@ -773,6 +817,7 @@ void basResolveModuleEnd(BasCompilerT *c) {
if (!c->hasError) { if (!c->hasError) {
resolveImplements(c); resolveImplements(c);
resolveWithEvents(c); resolveWithEvents(c);
resolveUserControls(c);
} }
for (const BasSymT *l = c->moduleLabels.first; l != NULL && !c->hasError; l = l->orderNext) { for (const BasSymT *l = c->moduleLabels.first; l != NULL && !c->hasError; l = l->orderNext) {
@ -898,7 +943,10 @@ void basResolveStatement(BasCompilerT *c, BasNodeT *s) {
case ST_END: case ST_END:
case ST_DATA: case ST_DATA:
case ST_LOAD: case ST_LOAD:
break;
case ST_UNLOAD: case ST_UNLOAD:
resolveUnload(c, (BasStmtT *)s);
break; break;
case ST_MIDASSIGN: case ST_MIDASSIGN:
@ -1040,6 +1088,13 @@ bool basResolveTypeSpec(BasCompilerT *c, const BasTypeSpecT *t, bool prescan, ui
return true; return true;
} }
// A form of the project stands for a Form object (that form or a copy
// of it made with New)
if (basIsFormName(c, t->name)) {
*outType = BAS_TYPE_OBJECT;
return true;
}
for (int32_t i = 0; i < DVX_ARRAY_LEN(sObjectTypeNames); i++) { for (int32_t i = 0; i < DVX_ARRAY_LEN(sObjectTypeNames); i++) {
if (strcasecmp(t->name, sObjectTypeNames[i]) == 0) { if (strcasecmp(t->name, sObjectTypeNames[i]) == 0) {
*outType = BAS_TYPE_OBJECT; *outType = BAS_TYPE_OBJECT;
@ -1065,7 +1120,9 @@ void basResolveUnit(BasCompilerT *c, BasNodeT *unit) {
// After the generator: a procedure's frame size checked, its locals given // After the generator: a procedure's frame size checked, its locals given
// to the debugger, its scope closed; a form's or class's scope closed. // to the debugger, its scope closed; a form's or class's scope closed. A
// procedure scope never outlives its unit (its symbols are in the unit
// arena), so one an error left open (a DEF FN, a procedure) closes too.
void basResolveUnitEnd(BasCompilerT *c, BasNodeT *unit) { void basResolveUnitEnd(BasCompilerT *c, BasNodeT *unit) {
// A form's or class's code ends after its closing marker is generated // A form's or class's code ends after its closing marker is generated
if (unit->kind == ST_ENDFORM && c->inForm) { if (unit->kind == ST_ENDFORM && c->inForm) {
@ -1077,7 +1134,7 @@ void basResolveUnitEnd(BasCompilerT *c, BasNodeT *unit) {
return; return;
} }
if (unit->kind == ST_ENDCLASS && c->inClass && !c->inProc) { if (unit->kind == ST_ENDCLASS && c->inClass) {
c->inClass = false; c->inClass = false;
c->classIdx = BAS_CLASS_NONE; c->classIdx = BAS_CLASS_NONE;
c->fileIdx = c->outerFileIdx; c->fileIdx = c->outerFileIdx;
@ -1085,13 +1142,13 @@ void basResolveUnitEnd(BasCompilerT *c, BasNodeT *unit) {
return; return;
} }
if (unit->kind != ST_PROC || !c->inProc) { if (!c->inProc) {
return; return;
} }
BasProcT *proc = (BasProcT *)unit; const BasProcT *proc = (const BasProcT *)unit;
if (!c->hasError && proc->sym != NULL) { if (unit->kind == ST_PROC && !c->hasError && proc->sym != NULL) {
int32_t localCount = proc->sym->u.proc->localCount; int32_t localCount = proc->sym->u.proc->localCount;
// A call frame holds BAS_VM_MAX_LOCALS slots: parameters, locals // A call frame holds BAS_VM_MAX_LOCALS slots: parameters, locals
@ -1194,6 +1251,29 @@ void basSymChangesUndo(BasCompilerT *c) {
} }
// The handler of UserControl event evt (BasUcEventE) in UserControl class
// classIdx: its procedure UserControl_<event>, NULL when it has none.
BasSymT *basUserControlProc(const BasCompilerT *c, int32_t classIdx, int32_t evt) {
const BasUcEventInfoT *info = basUcEventInfo(evt);
char member[BAS_MAX_IDENT];
char qualified[BAS_MAX_IDENT];
if (info == NULL) {
return NULL;
}
snprintf(member, sizeof(member), "%s_%s", BAS_UC_OBJECT, info->name);
if (!basQualify(c, classIdx, member, qualified, (int32_t)sizeof(qualified))) {
return NULL;
}
BasSymT *sym = basScopeFind(&c->moduleTab, qualified);
return (sym != NULL && (sym->kind == SK_SUB || sym->kind == SK_FUNCTION) && (sym->flags & SF_DEFINED)) ? sym : NULL;
}
// The signature of a parameter list: types (AS, suffix or DEFxxx; an // The signature of a parameter list: types (AS, suffix or DEFxxx; an
// untyped ParamArray holds Variants), ByVal, Optional, defaults (with // untyped ParamArray holds Variants), ByVal, Optional, defaults (with
// SIG_DEFINE folded to constants of the parameter's type). False after // SIG_DEFINE folded to constants of the parameter's type). False after
@ -1311,7 +1391,7 @@ static bool buildSignature(BasCompilerT *c, const BasProcT *proc, uint32_t flags
// The parameters' names, for arguments given by name // The parameters' names, for arguments given by name
if (i > 0) { if (i > 0) {
sig->info.paramNames = (const char **)basArenaAlloc(&c->modArena, (int32_t)(i * (int32_t)sizeof(const char *))); sig->info.paramNames = (const char **)basArenaAlloc(&c->modArena, i * (int32_t)sizeof(const char *));
if (sig->info.paramNames == NULL) { if (sig->info.paramNames == NULL) {
basErrorNode(c, &proc->h, "Out of memory"); basErrorNode(c, &proc->h, "Out of memory");
@ -1348,7 +1428,7 @@ static bool buildSignature(BasCompilerT *c, const BasProcT *proc, uint32_t flags
// called as a statement). // called as a statement).
static int32_t callLeavesValue(const BasNodeT *call) { static int32_t callLeavesValue(const BasNodeT *call) {
if (call->kind == NK_FCALL) { if (call->kind == NK_FCALL) {
return (((const BasSymT *)((const BasCallT *)call)->target)->kind == SK_FUNCTION) ? 1 : 0; return procLeavesValue((const BasSymT *)((const BasCallT *)call)->target);
} }
return (call->kind == NK_METHOD) ? 1 : 0; return (call->kind == NK_METHOD) ? 1 : 0;
@ -1466,6 +1546,25 @@ static void defineLabel(BasCompilerT *c, BasStmtT *s) {
// The label named name of the current procedure (a local label inside a // The label named name of the current procedure (a local label inside a
// SUB / FUNCTION, a module label outside), or NULL. // SUB / FUNCTION, a module label outside), or NULL.
// A DECLARE LIBRARY procedure's OP_CALL_EXTERN binary mask: bit i for
// each BINARY STRING parameter, BAS_EXTERN_BINARY_RET for a BINARY
// STRING result.
static uint32_t externBinaryMask(const BasProcT *proc) {
uint32_t mask = (proc->retSpec != NULL && proc->retSpec->isBinary) ? BAS_EXTERN_BINARY_RET : 0;
uint32_t bit = 1;
for (const BasNodeT *pn = proc->params; pn != NULL; pn = pn->next, bit <<= 1) {
const BasParamT *p = (const BasParamT *)pn;
if (p->spec != NULL && p->spec->isBinary) {
mask |= bit;
}
}
return mask;
}
static BasSymT *findLabel(BasCompilerT *c, const char *name) { static BasSymT *findLabel(BasCompilerT *c, const char *name) {
return basScopeFind(c->inProc && !c->inDefFn ? &c->localLabels : &c->moduleLabels, name); return basScopeFind(c->inProc && !c->inDefFn ? &c->localLabels : &c->moduleLabels, name);
} }
@ -1715,6 +1814,14 @@ static BasProcInfoT *procInfo(BasCompilerT *c, BasSymT *sym) {
} }
// 1 when calling procedure sym leaves a value on the stack: a FUNCTION's
// result, or the dummy result every native (DECLARE LIBRARY) call pushes,
// SUBs too; else 0.
static int32_t procLeavesValue(const BasSymT *sym) {
return (sym->kind == SK_FUNCTION || (sym->flags & SF_EXTERN)) ? 1 : 0;
}
// The symbol name of procedure name of member kind in class classIdx // The symbol name of procedure name of member kind in class classIdx
// (BAS_CLASS_NONE: not in a class): "Class.name", "Class.name.Let" / // (BAS_CLASS_NONE: not in a class): "Class.name", "Class.name.Let" /
// ".Set" for Property Let / Set; else name. False after the error when // ".Set" for Property Let / Set; else name. False after the error when
@ -1738,6 +1845,30 @@ static bool procSymbolName(BasCompilerT *c, int32_t classIdx, const char *name,
} }
// The ENUM a Property procedure's value is declared As: a Property Get's
// type, a Property Let's or Set's last parameter's; NULL for another type
// and for a SUB or FUNCTION.
static const BasSymT *procValueEnum(const BasCompilerT *c, const BasProcT *proc) {
const BasParamT *last = NULL;
for (const BasNodeT *p = proc->params; p != NULL; p = p->next) {
last = (const BasParamT *)p;
}
switch (proc->memberKind) {
case BAS_MEMBER_GET:
return specEnum(c, proc->retSpec);
case BAS_MEMBER_LET:
case BAS_MEMBER_SET:
return (last != NULL) ? specEnum(c, last->spec) : NULL;
default:
return NULL;
}
}
// The label a GOTO / GOSUB / ON ERROR names: defined there or later in the // The label a GOTO / GOSUB / ON ERROR names: defined there or later in the
// same procedure (or module), else an error at the end of it. // same procedure (or module), else an error at the end of it.
static BasSymT *refLabel(BasCompilerT *c, const char *name, const BasNodeT *at) { static BasSymT *refLabel(BasCompilerT *c, const char *name, const BasNodeT *at) {
@ -2056,7 +2187,7 @@ static void resolveBeginClass(BasCompilerT *c, BasStmtT *s) {
// BEGINFORM "Name": module-level DIMs make variables of the form. // BEGINFORM "Name": module-level DIMs make variables of the form.
static void resolveBeginForm(BasCompilerT *c, BasStmtT *s) { static void resolveBeginForm(BasCompilerT *c, BasStmtT *s) {
if ((int32_t)strlen(s->name) >= BAS_MAX_IDENT) { if ((int32_t)strlen(s->name) >= BAS_MAX_IDENT) {
basErrorNode(c, &s->h, "Form name too long (maximum is %d characters)", (int)(BAS_MAX_IDENT - 1)); basErrorNode(c, &s->h, "Form name too long (maximum is %d characters)", BAS_MAX_IDENT - 1);
return; return;
} }
@ -2165,7 +2296,7 @@ static void resolveCallStmt(BasCompilerT *c, BasStmtT *s) {
} }
s->a = basResolveCallee(c, call, true); s->a = basResolveCallee(c, call, true);
s->ival2 = (sym->kind == SK_FUNCTION) ? 1 : 0; s->ival2 = procLeavesValue(sym);
return; return;
} }
@ -2173,6 +2304,24 @@ static void resolveCallStmt(BasCompilerT *c, BasStmtT *s) {
BasNameT *n = (BasNameT *)callee; BasNameT *n = (BasNameT *)callee;
BasSymT *sym = basLookup(c, n->name); BasSymT *sym = basLookup(c, n->name);
// CallByName obj, name, callType, args: the function, its value
// dropped (VB's statement form)
if (sym == NULL && strcasecmp(n->name, BAS_CALLBYNAME_NAME) == 0) {
BasCallT *byName = (BasCallT *)basNodeNew(&c->unitArena, (int32_t)sizeof(BasCallT), NK_CALL, callee->line, callee->col);
if (byName == NULL) {
return;
}
byName->callee = callee;
byName->args = s->b;
byName->argc = (int16_t)s->ival2;
s->a = basResolveExpr(c, &byName->h, BAS_USE_VALUE);
s->b = NULL;
s->ival2 = 1;
return;
}
if (sym == NULL) { if (sym == NULL) {
basErrorNode(c, callee, "Undefined Sub: %s", n->name); basErrorNode(c, callee, "Undefined Sub: %s", n->name);
return; return;
@ -2200,7 +2349,7 @@ static void resolveCallStmt(BasCompilerT *c, BasStmtT *s) {
call->argc = (int16_t)argc; call->argc = (int16_t)argc;
s->a = &call->h; s->a = &call->h;
s->b = NULL; s->b = NULL;
s->ival2 = (sym->kind == SK_FUNCTION) ? 1 : 0; s->ival2 = procLeavesValue(sym);
return; return;
} }
@ -2276,8 +2425,11 @@ static void resolveDeclarator(BasCompilerT *c, BasDeclT *d, bool isShared, bool
fixedLen = d->spec->fixedLen; fixedLen = d->spec->fixedLen;
} }
if (asNew && (objClass == BAS_CLASS_NONE || d->isArray)) { // As New FormName: a copy of the form (an untyped Form object)
basErrorNode(c, &d->h, d->isArray ? "New cannot be used with an array" : "New needs a class name (or Collection)"); bool newForm = asNew && d->spec->name != NULL && basIsFormName(c, d->spec->name);
if (asNew && ((objClass == BAS_CLASS_NONE && !newForm) || d->isArray)) {
basErrorNode(c, &d->h, d->isArray ? "New cannot be used with an array" : "New needs a class name, a form name or Collection");
return; return;
} }
@ -2352,7 +2504,7 @@ static void resolveDeclarator(BasCompilerT *c, BasDeclT *d, bool isShared, bool
d->sym = sym; d->sym = sym;
if (isMember && !addClassMember(c, d->name, sym, BAS_MEMBER_VAR, isPrivate, &d->h)) { if (isMember && !addClassMember(c, d->name, sym, BAS_MEMBER_VAR, d->isArray ? NULL : specEnum(c, d->spec), isPrivate, &d->h)) {
return; return;
} }
@ -2703,6 +2855,8 @@ static void resolveEnum(BasCompilerT *c, BasStmtT *s) {
// Event Name(params) in a class module: no Optional or ParamArray // Event Name(params) in a class module: no Optional or ParamArray
// parameters (VB); an event and a member may not share a name. // parameters (VB); an event and a member may not share a name.
_Static_assert(BAS_MAX_PARAMS <= BAS_VM_MAX_CALL_ARGS, "an event's parameters must fit a RaiseEvent call");
static void resolveEvent(BasCompilerT *c, BasProcT *ev) { static void resolveEvent(BasCompilerT *c, BasProcT *ev) {
BasClassDefT *cls = &c->classes[c->classIdx]; BasClassDefT *cls = &c->classes[c->classIdx];
@ -2731,10 +2885,21 @@ static void resolveEvent(BasCompilerT *c, BasProcT *ev) {
return; return;
} }
// The parameters' names, comma-separated (a UserControl's host sees them)
char names[BAS_VM_MAX_CALL_ARGS * BAS_MAX_IDENT];
int32_t namesLen = 0;
names[0] = '\0';
for (const BasNodeT *p = ev->params; p != NULL; p = p->next) {
namesLen += snprintf(names + namesLen, sizeof(names) - (size_t)namesLen, "%s%s", (namesLen > 0) ? BAS_PARAM_NAME_SEP : "", ((const BasParamT *)p)->name);
}
BasEventDefT def; BasEventDefT def;
memset(&def, 0, sizeof(def)); memset(&def, 0, sizeof(def));
def.name = basArenaStrDup(&c->modArena, ev->name, (int32_t)strlen(ev->name)); def.name = basArenaStrDup(&c->modArena, ev->name, (int32_t)strlen(ev->name));
def.paramNames = basArenaStrDup(&c->modArena, names, namesLen);
def.paramCount = sig.info.paramCount; def.paramCount = sig.info.paramCount;
memcpy(def.paramTypes, sig.info.paramTypes, sizeof(def.paramTypes)); memcpy(def.paramTypes, sig.info.paramTypes, sizeof(def.paramTypes));
@ -3165,11 +3330,6 @@ static void resolveMemberCallStmt(BasCompilerT *c, BasStmtT *s) {
method->args = s->b; method->args = s->b;
method->argc = (int16_t)s->ival2; method->argc = (int16_t)s->ival2;
} else { } else {
if (s->ival2 > BAS_VM_MAX_CALL_ARGS) {
basErrorNode(c, &s->h, "Too many arguments (max %d)", (int)BAS_VM_MAX_CALL_ARGS);
return;
}
basLateArgs(c, method, s->b, s->ival2, !host); basLateArgs(c, method, s->b, s->ival2, !host);
} }
@ -3303,7 +3463,7 @@ static void resolveProc(BasCompilerT *c, BasProcT *proc) {
procSym->flags |= SF_DEFINED | (returnTyped ? SF_TYPED : 0) | (proc->isPrivate ? SF_PRIVATE : 0) | (proc->isFriend ? SF_FRIEND : 0) | (isProperty ? SF_PROPERTY : 0); procSym->flags |= SF_DEFINED | (returnTyped ? SF_TYPED : 0) | (proc->isPrivate ? SF_PRIVATE : 0) | (proc->isFriend ? SF_FRIEND : 0) | (isProperty ? SF_PROPERTY : 0);
copySignature(procSym, &sig); copySignature(procSym, &sig);
if (c->inClass && !addClassMember(c, proc->name, procSym, kind, proc->isPrivate, &proc->h)) { if (c->inClass && !addClassMember(c, proc->name, procSym, kind, procValueEnum(c, proc), proc->isPrivate, &proc->h)) {
return; return;
} }
@ -3699,6 +3859,51 @@ static void resolveTypeDef(BasCompilerT *c, BasStmtT *s) {
} }
// UNLOAD target: a bare name that is not a variable or a function is a
// form's name (loaded by name, as VB); anything else -- a Form variable,
// obj.Window, a function giving a form -- is evaluated for the form.
static void resolveUnload(BasCompilerT *c, BasStmtT *s) {
if (s->a == NULL) {
return;
}
if (s->a->kind == NK_NAME) {
const char *name = ((BasNameT *)s->a)->name;
const BasSymT *sym = basLookup(c, name);
if (sym == NULL || (sym->kind != SK_VAR && sym->kind != SK_FUNCTION)) {
s->name = name;
s->a = NULL;
return;
}
}
s->a = basResolveExpr(c, s->a, BAS_USE_OBJECT);
}
// End of the program: every UserControl event handler a control class
// has is a SUB with exactly the event's parameters (the runtime calls it
// with them).
static void resolveUserControls(BasCompilerT *c) {
for (int32_t k = 0; k < (int32_t)arrlen(c->classes) && !c->hasError; k++) {
if (!c->classes[k].isUserControl) {
continue;
}
for (int32_t e = 0; e < BAS_UC_EVT_COUNT && !c->hasError; e++) {
const BasSymT *sym = basUserControlProc(c, k, e);
const BasUcEventInfoT *info = basUcEventInfo(e);
int32_t want = basParamListCount(info->params);
if (sym != NULL && (sym->kind != SK_SUB || sym->u.proc->paramCount != want)) {
basErrorAt(c, sym->line, 0, "%s_%s must be a Sub with %d parameter(s)%s%s", BAS_UC_OBJECT, info->name, (int)want, (want > 0) ? ": " : "", info->params);
}
}
}
}
// WITH object ... END WITH: a control, form, Me or TYPE record (a // WITH object ... END WITH: a control, form, Me or TYPE record (a
// variable, element or field) is named again at each '.'; any other object // variable, element or field) is named again at each '.'; any other object
// is evaluated once into a hidden variable (VB), its class kept for early // is evaluated once into a hidden variable (VB), its class kept for early
@ -3800,6 +4005,15 @@ static bool sameSignature(const BasSymT *sym, const SignatureT *sig) {
} }
// The ENUM a type spec names (As EnumName), as basResolveTypeSpec finds
// it; NULL for no spec and any other type.
static const BasSymT *specEnum(const BasCompilerT *c, const BasTypeSpecT *t) {
const BasSymT *sym = (t != NULL && t->name != NULL) ? basModuleFind(c, t->name) : NULL;
return (sym != NULL && sym->kind == SK_ENUM) ? sym : NULL;
}
// A STATIC array or TYPE (local, its procedure's alias of slot): the // A STATIC array or TYPE (local, its procedure's alias of slot): the
// slot becomes an array of the declarator's constant bounds (none: a // slot becomes an array of the declarator's constant bounds (none: a
// dynamic array the procedure REDIMs) or a TYPE instance, allocated once // dynamic array the procedure REDIMs) or a TYPE instance, allocated once

View file

@ -235,6 +235,7 @@ static const BasBuiltinT sBuiltins[] = {
{"YEAR", OP_BUILTIN, BAS_FN_YEAR, 0, 0, BAS_NODE_TYPE_UNKNOWN, BI_PURE, 0, NULL}, {"YEAR", OP_BUILTIN, BAS_FN_YEAR, 0, 0, BAS_NODE_TYPE_UNKNOWN, BI_PURE, 0, NULL},
// Variants and objects // Variants and objects
{"CALLBYNAME", OP_BUILTIN, BAS_FN_CALLBYNAME, 0, 0, BAS_NODE_TYPE_UNKNOWN, BI_OBJECT_ARG, 0, NULL},
{"CVAR", OP_BUILTIN, BAS_FN_CVAR, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL}, {"CVAR", OP_BUILTIN, BAS_FN_CVAR, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
{"ISARRAY", OP_BUILTIN, BAS_FN_ISARRAY, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL}, {"ISARRAY", OP_BUILTIN, BAS_FN_ISARRAY, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
{"ISEMPTY", OP_BUILTIN, BAS_FN_ISEMPTY, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL}, {"ISEMPTY", OP_BUILTIN, BAS_FN_ISEMPTY, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
@ -242,6 +243,7 @@ static const BasBuiltinT sBuiltins[] = {
{"ISNULL", OP_BUILTIN, BAS_FN_ISNULL, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL}, {"ISNULL", OP_BUILTIN, BAS_FN_ISNULL, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
{"ISNUMERIC", OP_BUILTIN, BAS_FN_ISNUMERIC, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL}, {"ISNUMERIC", OP_BUILTIN, BAS_FN_ISNUMERIC, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
{"ISOBJECT", OP_BUILTIN, BAS_FN_ISOBJECT, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL}, {"ISOBJECT", OP_BUILTIN, BAS_FN_ISOBJECT, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
{"LOADFORMTEXT", OP_BUILTIN, BAS_FN_LOADFORMTEXT, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
{"LOADPICTURE", OP_BUILTIN, BAS_FN_LOADPICTURE, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL}, {"LOADPICTURE", OP_BUILTIN, BAS_FN_LOADPICTURE, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
{"TYPENAME", OP_BUILTIN, BAS_FN_TYPENAME, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL}, {"TYPENAME", OP_BUILTIN, BAS_FN_TYPENAME, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL},
{"VARTYPE", OP_BUILTIN, BAS_FN_VARTYPE, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL} {"VARTYPE", OP_BUILTIN, BAS_FN_VARTYPE, 0, 0, BAS_NODE_TYPE_UNKNOWN, 0, 0, NULL}
@ -608,11 +610,8 @@ int32_t basExprClass(const BasCompilerT *c, const BasNodeT *n) {
case NK_ME: case NK_ME:
return (((const BasNameT *)n)->aux == OP_ME_OBJ) ? c->classIdx : BAS_CLASS_NONE; return (((const BasNameT *)n)->aux == OP_ME_OBJ) ? c->classIdx : BAS_CLASS_NONE;
case NK_NEW: { case NK_NEW:
int32_t cls = ((const BasNameT *)n)->aux; return ((const BasNameT *)n)->aux;
return (cls == BAS_NEW_COLLECTION) ? BAS_CLASS_COLLECTION : cls;
}
case NK_REF: case NK_REF:
return basExprClass(c, ((const BasOpT *)n)->a); return basExprClass(c, ((const BasOpT *)n)->a);
@ -694,12 +693,18 @@ uint8_t basImplicitType(const BasCompilerT *c, const char *name, bool *outTyped)
// call: as basResolveMethodArgs gives them, those given by name (after the // call: as basResolveMethodArgs gives them, those given by name (after the
// positional ones) followed by a string of their names that OP_CALL_NAMED // positional ones) followed by a string of their names that OP_CALL_NAMED
// reads, call->aux2 how many (0: none). A control or form method takes // reads, call->aux2 how many (0: none). A control or form method takes
// no names. False after the error. // no names. False after the error. The argument limit is checked first:
// names holds a name (under BAS_MAX_IDENT characters) and a separator or
// the terminator per argument.
bool basLateArgs(BasCompilerT *c, BasCallT *call, BasNodeT *args, int32_t argc, bool byRef) { bool basLateArgs(BasCompilerT *c, BasCallT *call, BasNodeT *args, int32_t argc, bool byRef) {
char names[BAS_MAX_PARAMS * BAS_MAX_IDENT]; char names[BAS_VM_MAX_CALL_ARGS * BAS_MAX_IDENT];
int32_t namesLen = 0;
int32_t named = 0; int32_t named = 0;
names[0] = '\0'; if (argc > BAS_VM_MAX_CALL_ARGS) {
basErrorNode(c, &call->h, "Too many arguments (max %d)", (int)BAS_VM_MAX_CALL_ARGS);
return false;
}
for (BasNodeT **link = &args; *link != NULL; link = &(*link)->next) { for (BasNodeT **link = &args; *link != NULL; link = &(*link)->next) {
BasNodeT *a = *link; BasNodeT *a = *link;
@ -713,11 +718,7 @@ bool basLateArgs(BasCompilerT *c, BasCallT *call, BasNodeT *args, int32_t argc,
return false; return false;
} }
if (named > 0) { namesLen += snprintf(names + namesLen, sizeof(names) - (size_t)namesLen, "%s%s", named > 0 ? BAS_PARAM_NAME_SEP : "", ((const BasMemberT *)a)->name);
strcat(names, BAS_PARAM_NAME_SEP);
}
strcat(names, ((const BasMemberT *)a)->name);
// The value takes the named argument's place in the list // The value takes the named argument's place in the list
BasNodeT *value = ((BasMemberT *)a)->base; BasNodeT *value = ((BasMemberT *)a)->base;
@ -742,10 +743,9 @@ bool basLateArgs(BasCompilerT *c, BasCallT *call, BasNodeT *args, int32_t argc,
} }
// The node outlives this buffer: the names go in the unit arena // The node outlives this buffer: the names go in the unit arena
int32_t len = (int32_t)strlen(names); char *kept = basArenaStrDup(&c->unitArena, names, namesLen);
char *kept = basArenaStrDup(&c->unitArena, names, len);
tail->next = (kept != NULL) ? basNodeStr(c, kept, len, &call->h) : NULL; tail->next = (kept != NULL) ? basNodeStr(c, kept, namesLen, &call->h) : NULL;
if (tail->next == NULL) { if (tail->next == NULL) {
basErrorNode(c, &call->h, "Out of memory"); basErrorNode(c, &call->h, "Out of memory");
@ -1201,8 +1201,9 @@ BasNodeT *basResolveCallee(BasCompilerT *c, BasCallT *call, bool statement) {
return basBindMember(c, obj, cls, m->name, call->args, call->argc, statement ? BAS_ACCESS_STATEMENT : BAS_ACCESS_VALUE, &call->h); return basBindMember(c, obj, cls, m->name, call->args, call->argc, statement ? BAS_ACCESS_STATEMENT : BAS_ACCESS_VALUE, &call->h);
} }
// name.Ctrl(i).Member: a control array element // name.Ctrl(i).Member: a control array element (name.Controls(i) is
if (call->h.flags & NF_MEMBERBASE) { // an item of the form's collection of controls)
if ((call->h.flags & NF_MEMBERBASE) && strcasecmp(m->name, BAS_FORM_CONTROLS) != 0) {
if (call->argc != 1 || call->args->kind == NK_OMIT) { if (call->argc != 1 || call->args->kind == NK_OMIT) {
basErrorNode(c, &call->h, "Control array index expected"); basErrorNode(c, &call->h, "Control array index expected");
return NULL; return NULL;
@ -1314,7 +1315,13 @@ BasNodeT *basResolveExpr(BasCompilerT *c, BasNodeT *n, uint8_t use) {
return NULL; return NULL;
} }
nw->aux = (cls >= 0) ? cls : BAS_NEW_COLLECTION; // A class, a form (BAS_CLASS_NONE: a copy of it) or Collection
if (cls >= 0) {
nw->aux = cls;
} else {
nw->aux = basIsFormName(c, nw->name) ? BAS_CLASS_NONE : BAS_CLASS_COLLECTION;
}
return n; return n;
} }
@ -1540,7 +1547,7 @@ BasNodeT *basResolveMemberParts(BasCompilerT *c, BasMemberT *m, bool isProp, Bas
// the form the first name names or holds; objVar.Member.x: a member // the form the first name names or holds; objVar.Member.x: a member
// of the object a variable of a known class holds (a control of the // of the object a variable of a known class holds (a control of the
// form, Img.Picture.Width, is the control below) // form, Img.Picture.Width, is the control below)
if ((m->h.flags & NF_MEMBERBASE) && (sym == NULL || sym->kind != SK_CONTROL)) { if ((m->h.flags & NF_MEMBERBASE) && (sym == NULL || sym->kind != SK_CONTROL) && (sym != NULL || strcasecmp(bn->name, BAS_SCREEN_OBJ_NAME) != 0)) {
*outObj = (sym != NULL && sym->kind == SK_VAR) ? basNodeVar(c, sym, base) : basLoadFormNode(c, bn->name, base); *outObj = (sym != NULL && sym->kind == SK_VAR) ? basNodeVar(c, sym, base) : basLoadFormNode(c, bn->name, base);
if (*outObj != NULL && (*outObj)->kind == NK_VAR) { if (*outObj != NULL && (*outObj)->kind == NK_VAR) {
@ -1655,14 +1662,13 @@ BasNodeT *basResolveMemberParts(BasCompilerT *c, BasMemberT *m, bool isProp, Bas
BasNodeT *basResolveMethodArgs(BasCompilerT *c, BasNodeT *args, int32_t argc, bool byRef) { BasNodeT *basResolveMethodArgs(BasCompilerT *c, BasNodeT *args, int32_t argc, bool byRef) {
BasNodeT *head = NULL; BasNodeT *head = NULL;
BasNodeT *tail = NULL; BasNodeT *tail = NULL;
int32_t i = 0;
if (argc > BAS_VM_MAX_CALL_ARGS) { if (argc > BAS_VM_MAX_CALL_ARGS) {
basErrorNode(c, args, "Too many arguments (max %d)", (int)BAS_VM_MAX_CALL_ARGS); basErrorNode(c, args, "Too many arguments (max %d)", (int)BAS_VM_MAX_CALL_ARGS);
return NULL; return NULL;
} }
for (BasNodeT *a = args; a != NULL; i++) { for (BasNodeT *a = args; a != NULL;) {
BasNodeT *next = a->next; BasNodeT *next = a->next;
BasNodeT *r; BasNodeT *r;
@ -1700,10 +1706,12 @@ BasNodeT *basResolveObjectMember(BasCompilerT *c, BasNodeT *obj, const char *nam
return basBindMember(c, obj, memberClass, name, NULL, 0, BAS_ACCESS_READ, at); return basBindMember(c, obj, memberClass, name, NULL, 0, BAS_ACCESS_READ, at);
} }
// Before another '.', a member of a form is one of its controls; of a // Before another '.', a member of a form is one of its controls (but
// the object-valued members Controls / Parent / Container); of a
// control, a property whose value has members (Img.Picture.Width) // control, a property whose value has members (Img.Picture.Width)
bool control = obj != NULL && (obj->kind == NK_FINDCTRL || obj->kind == NK_FINDCTRLIDX); bool control = obj != NULL && (obj->kind == NK_FINDCTRL || obj->kind == NK_FINDCTRLIDX);
BasMemberT *m = (BasMemberT *)basNodeNew(&c->unitArena, (int32_t)sizeof(BasMemberT), ((flags & NF_MEMBERBASE) && !control) ? NK_FINDCTRL : NK_PROP, at->line, at->col); bool findIt = (flags & NF_MEMBERBASE) && !control && !basIsObjectMember(name);
BasMemberT *m = (BasMemberT *)basNodeNew(&c->unitArena, (int32_t)sizeof(BasMemberT), findIt ? NK_FINDCTRL : NK_PROP, at->line, at->col);
if (m == NULL) { if (m == NULL) {
return NULL; return NULL;
@ -2016,11 +2024,18 @@ static bool canIndexValue(const BasSymT *sym) {
static BasSymT *classSymbol(BasCompilerT *c, int32_t cls, const char *name, const char *suffix) { static BasSymT *classSymbol(BasCompilerT *c, int32_t cls, const char *name, const char *suffix) {
char qualified[BAS_MAX_IDENT]; char qualified[BAS_MAX_IDENT];
if (!basQualify(c, cls, name, qualified, (int32_t)sizeof(qualified)) || strlen(qualified) + strlen(suffix) >= sizeof(qualified)) { if (!basQualify(c, cls, name, qualified, (int32_t)sizeof(qualified))) {
return NULL; return NULL;
} }
strcat(qualified, suffix); size_t len = strlen(qualified);
size_t suffixLen = strlen(suffix);
if (len + suffixLen >= sizeof(qualified)) {
return NULL;
}
memcpy(qualified + len, suffix, suffixLen + 1);
BasSymT *sym = basScopeFind(&c->moduleTab, qualified); BasSymT *sym = basScopeFind(&c->moduleTab, qualified);
@ -2455,7 +2470,8 @@ static BasNodeT *resolveBuiltin(BasCompilerT *c, const BasBuiltinT *b, BasCallT
// OP_BUILTIN function is Missing: the VM gives it its default // OP_BUILTIN function is Missing: the VM gives it its default
for (BasNodeT *a = call->args; a != NULL;) { for (BasNodeT *a = call->args; a != NULL;) {
BasNodeT *next = a->next; BasNodeT *next = a->next;
BasNodeT *r = (a->kind == NK_OMIT && b->opcode == OP_BUILTIN) ? basNodeSpecial(c, BAS_SPECIAL_MISSING, a) : basResolveExpr(c, a, BAS_USE_VALUE); uint8_t use = (a == call->args && (b->flags & BI_OBJECT_ARG)) ? BAS_USE_OBJECT : BAS_USE_VALUE;
BasNodeT *r = (a->kind == NK_OMIT && b->opcode == OP_BUILTIN) ? basNodeSpecial(c, BAS_SPECIAL_MISSING, a) : basResolveExpr(c, a, use);
if (r == NULL) { if (r == NULL) {
return NULL; return NULL;
@ -2609,7 +2625,16 @@ static BasNodeT *resolveName(BasCompilerT *c, BasNameT *n, uint8_t use) {
return NULL; return NULL;
} }
// A FUNCTION named without an argument list is a call with none (QB) // Inside its own body a FUNCTION's name without an argument list is
// its result so far (VB: F = F + 1); F() calls it again
if (sym != NULL && sym->kind == SK_FUNCTION && c->inProc && sym == c->curProc) {
n->h.kind = NK_RESULT;
n->sym = sym;
return (BasNodeT *)n;
}
// Elsewhere a FUNCTION named without an argument list is a call with
// none (QB)
if (sym != NULL && sym->kind == SK_FUNCTION) { if (sym != NULL && sym->kind == SK_FUNCTION) {
BasCallT *call = (BasCallT *)basNodeNew(&c->unitArena, (int32_t)sizeof(BasCallT), NK_FCALL, n->h.line, n->h.col); BasCallT *call = (BasCallT *)basNodeNew(&c->unitArena, (int32_t)sizeof(BasCallT), NK_FCALL, n->h.line, n->h.col);
int32_t argc = 0; int32_t argc = 0;
@ -2821,34 +2846,13 @@ static uint8_t suffixType(const char *name, bool *outHasSuffix) {
// The name programs write for a scalar type (messages). // The name programs write for a scalar type (messages).
static const char *typeDisplayName(uint8_t dataType) { static const char *typeDisplayName(uint8_t dataType) {
const char *scalar = basValScalarTypeName(dataType);
if (scalar) {
return scalar;
}
switch (dataType) { switch (dataType) {
case BAS_TYPE_INTEGER:
return "Integer";
case BAS_TYPE_LONG:
return "Long";
case BAS_TYPE_SINGLE:
return "Single";
case BAS_TYPE_DOUBLE:
return "Double";
case BAS_TYPE_STRING:
return "String";
case BAS_TYPE_BOOLEAN:
return "Boolean";
case BAS_TYPE_DATE:
return "Date";
case BAS_TYPE_BYTE:
return "Byte";
case BAS_TYPE_CURRENCY:
return "Currency";
case BAS_TYPE_ARRAY: case BAS_TYPE_ARRAY:
return "an array"; return "an array";

View file

@ -135,6 +135,7 @@ typedef struct {
int16_t requiredParams; // up to the last non-Optional parameter int16_t requiredParams; // up to the last non-Optional parameter
uint16_t externLibIdx; // DECLARE LIBRARY: constant pool indices uint16_t externLibIdx; // DECLARE LIBRARY: constant pool indices
uint16_t externFuncIdx; uint16_t externFuncIdx;
uint32_t externBinaryMask; // DECLARE LIBRARY: BINARY STRING parameters (bit i) and result (BAS_EXTERN_BINARY_RET)
uint8_t paramTypes[BAS_MAX_PARAMS]; uint8_t paramTypes[BAS_MAX_PARAMS];
uint8_t paramFlags[BAS_MAX_PARAMS]; // PARAM_* uint8_t paramFlags[BAS_MAX_PARAMS]; // PARAM_*
BasConstValT *defaults; // arena: per parameter (only PARAM_DEFAULT ones used, BAS_DEFAULT_PENDING until evaluated); NULL when none BasConstValT *defaults; // arena: per parameter (only PARAM_DEFAULT ones used, BAS_DEFAULT_PENDING until evaluated); NULL when none
@ -219,6 +220,7 @@ typedef struct {
typedef struct { typedef struct {
const char *name; // as programs write it (obj.name), or "Iface.name" const char *name; // as programs write it (obj.name), or "Iface.name"
BasSymT *sym; // procedure or member variable BasSymT *sym; // procedure or member variable
const BasSymT *enumSym; // the ENUM its value is declared As (a variable's or Property Get's type, a Property Let's or Set's last parameter's); NULL: another type
uint8_t kind; // BasMemberKindE uint8_t kind; // BasMemberKindE
} BasClassMemberDefT; } BasClassMemberDefT;
@ -228,6 +230,7 @@ typedef struct {
int32_t paramCount; int32_t paramCount;
uint8_t paramTypes[BAS_MAX_PARAMS]; uint8_t paramTypes[BAS_MAX_PARAMS];
uint8_t paramFlags[BAS_MAX_PARAMS]; // PARAM_BYVAL, PARAM_TYPED uint8_t paramFlags[BAS_MAX_PARAMS]; // PARAM_BYVAL, PARAM_TYPED
const char *paramNames; // module arena: the parameters' names, comma-separated ("" for none)
} BasEventDefT; } BasEventDefT;
typedef struct { typedef struct {
@ -243,6 +246,7 @@ typedef struct {
int32_t implementsLine; int32_t implementsLine;
bool defined; bool defined;
bool isInterface; // some class Implements it bool isInterface; // some class Implements it
bool isUserControl; // BEGINCLASS "Name" USERCONTROL: a .ctl, a widget class written in BASIC
} BasClassDefT; } BasClassDefT;
// A class member variable the declaration pass read (registered once // A class member variable the declaration pass read (registered once

View file

@ -54,6 +54,8 @@ const char *basEventSuffixes[] = {
// Function prototypes (alphabetical) // Function prototypes (alphabetical)
bool basEventSuffixMatch(const char *suffix); bool basEventSuffixMatch(const char *suffix);
bool basSplitEventName(const char *name, char *prefix, int32_t prefixSize, const char **outSuffix); bool basSplitEventName(const char *name, char *prefix, int32_t prefixSize, const char **outSuffix);
bool basSplitHandlerName(const char *name, char *prefix, int32_t prefixSize, const char **outSuffix);
bool basSplitProcHandler(const BasProcEntryT *proc, char *prefix, int32_t prefixSize, const char **outSuffix);
void basStripModule(BasModuleT *mod); void basStripModule(BasModuleT *mod);
static int compareNamesCI(const void *a, const void *b); static int compareNamesCI(const void *a, const void *b);
static bool sortedHasName(const char **names, int32_t count, const char *name); static bool sortedHasName(const char **names, int32_t count, const char *name);
@ -71,9 +73,14 @@ bool basEventSuffixMatch(const char *suffix) {
bool basSplitEventName(const char *name, char *prefix, int32_t prefixSize, const char **outSuffix) { bool basSplitEventName(const char *name, char *prefix, int32_t prefixSize, const char **outSuffix) {
return basSplitHandlerName(name, prefix, prefixSize, outSuffix) && basEventSuffixMatch(*outSuffix);
}
bool basSplitHandlerName(const char *name, char *prefix, int32_t prefixSize, const char **outSuffix) {
const char *underscore = strrchr(name, '_'); const char *underscore = strrchr(name, '_');
if (!underscore || !basEventSuffixMatch(underscore + 1)) { if (!underscore || underscore == name || underscore[1] == '\0') {
return false; return false;
} }
@ -90,6 +97,15 @@ bool basSplitEventName(const char *name, char *prefix, int32_t prefixSize, const
} }
bool basSplitProcHandler(const BasProcEntryT *proc, char *prefix, int32_t prefixSize, const char **outSuffix) {
if (proc->formName[0] != '\0') {
return basSplitHandlerName(proc->name, prefix, prefixSize, outSuffix);
}
return basSplitEventName(proc->name, prefix, prefixSize, outSuffix);
}
void basStripModule(BasModuleT *mod) { void basStripModule(BasModuleT *mod) {
if (!mod) { if (!mod) {
return; return;
@ -138,7 +154,7 @@ void basStripModule(BasModuleT *mod) {
char prefix[BAS_MAX_IDENT]; char prefix[BAS_MAX_IDENT];
const char *suffix; const char *suffix;
mangle[i] = name[0] != '\0' && !basSplitEventName(name, prefix, (int32_t)sizeof(prefix), &suffix) && !sortedHasName(consts, nConst, name); mangle[i] = name[0] != '\0' && !basSplitProcHandler(&mod->procs[i], prefix, (int32_t)sizeof(prefix), &suffix) && !sortedHasName(consts, nConst, name);
if (!mangle[i]) { if (!mangle[i]) {
kept[nKept++] = name; kept[nKept++] = name;

View file

@ -41,4 +41,11 @@
// string constants (SetEvent dispatch targets). // string constants (SetEvent dispatch targets).
void basStripModule(BasModuleT *mod); void basStripModule(BasModuleT *mod);
// A procedure the form runtime finds by name as an event handler: split
// into its object prefix and event name. A form's Control_Event
// procedure may handle any event (a widget's or a UserControl's own), so
// any "<Prefix>_<Name>" counts there; elsewhere only the master list's
// events (basSplitEventName).
bool basSplitProcHandler(const BasProcEntryT *proc, char *prefix, int32_t prefixSize, const char **outSuffix);
#endif // DVXBASIC_STRIP_H #endif // DVXBASIC_STRIP_H

View file

@ -164,13 +164,12 @@ static uint8_t binaryType(uint8_t tok, uint8_t a, uint8_t b) {
switch (tok) { switch (tok) {
case TOK_PLUS: case TOK_PLUS:
if (a == BAS_TYPE_STRING && b == BAS_TYPE_STRING) { case TOK_MINUS:
case TOK_STAR:
if (tok == TOK_PLUS && a == BAS_TYPE_STRING && b == BAS_TYPE_STRING) {
return BAS_TYPE_STRING; return BAS_TYPE_STRING;
} }
// fall through
case TOK_MINUS:
case TOK_STAR:
if (isLong || currency) { if (isLong || currency) {
return isLong ? BAS_TYPE_LONG : BAS_TYPE_CURRENCY; return isLong ? BAS_TYPE_LONG : BAS_TYPE_CURRENCY;
} }

Some files were not shown because too many files have changed in this diff Show more