Huge amount of BASIC API work.
This commit is contained in:
parent
e1e48c7619
commit
824fe02f8d
674 changed files with 87376 additions and 18737 deletions
20
Makefile
20
Makefile
|
|
@ -39,7 +39,7 @@
|
|||
LIBS = $(sort $(notdir $(patsubst %/Makefile,%,$(wildcard src/libs/kpunch/*/Makefile))))
|
||||
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
|
||||
# two), so dist/ always holds the latest DVX.
|
||||
|
|
@ -159,6 +159,18 @@ test-dos: all
|
|||
coverage: tools
|
||||
$(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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
# (library and app help are classified by the modules found in bin/).
|
||||
|
|
@ -212,7 +225,7 @@ deploy-sdk: tools apps
|
|||
@echo "Building SDK..."
|
||||
@# Start from an empty tree so a header dropped from the lists goes away.
|
||||
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_SHELL_H) $(SDKDIR)/include/shell/
|
||||
cp $(SDK_TASKS_H) $(SDKDIR)/include/tasks/
|
||||
|
|
@ -220,6 +233,7 @@ deploy-sdk: tools apps
|
|||
cp $(SDK_TEXTHELP_H) $(SDKDIR)/include/texthelp/
|
||||
cp $(SDK_LISTHELP_H) $(SDKDIR)/include/listhelp/
|
||||
cp $(SDK_FILEOPS_H) $(SDKDIR)/include/fileops/
|
||||
cp $(SDK_HLPFILE_H) $(SDKDIR)/include/hlpfile/
|
||||
cp $(SDK_XFER_H) $(SDKDIR)/include/xfer/
|
||||
cp $(SDK_CONN_H) $(SDKDIR)/include/conn/
|
||||
cp $(SDK_PRINT_H) $(SDKDIR)/include/print/
|
||||
|
|
|
|||
|
|
@ -23,11 +23,13 @@
|
|||
|
||||
set -u
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
cd "$(dirname "$0")" || exit 1
|
||||
|
||||
# The DJGPP compiler the build uses (src/common.mk finds the toolchain;
|
||||
# DJGPP_PREFIX in the environment overrides it).
|
||||
REAL_CC="$(make -s --no-print-directory -f src/common.mk --eval 'printCc: ; @echo $(CC)' printCc)"
|
||||
# DJGPP_PREFIX in the environment overrides it). The query drops a
|
||||
# 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
|
||||
echo "analyze.sh: $REAL_CC not executable" >&2
|
||||
|
|
|
|||
|
|
@ -19,5 +19,7 @@ GIF = apps/kpunch/imgview/imgview.app
|
|||
BAS = apps/kpunch/dvxbasic/dvxbasic.app
|
||||
DBP = 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
|
||||
DHS = apps/kpunch/dvxhelp/helpedit.app
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ set -e
|
|||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
TARGETS="${@:-src}"
|
||||
TARGETS=("${@:-src}")
|
||||
|
||||
INCLUDES=(
|
||||
-Isrc/libs/kpunch/libdvx
|
||||
|
|
@ -72,4 +72,4 @@ exec cppcheck \
|
|||
"${INCLUDES[@]}" \
|
||||
"${DEFINES[@]}" \
|
||||
"${SUPPRESS[@]}" \
|
||||
$TARGETS
|
||||
"${TARGETS[@]}"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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>
|
||||
<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>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>
|
||||
<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>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
BIN
screenshots/86box/apps-basdemo.png
(Stored with Git LFS)
BIN
screenshots/86box/apps-basdemo.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/apps-helpedit.png
(Stored with Git LFS)
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)
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)
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)
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)
BIN
screenshots/86box/hang-ended.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/86box/hang-taskmgr.png
(Stored with Git LFS)
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)
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
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
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
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
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
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)
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
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
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
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
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)
BIN
screenshots/dosbox/apps-basdemo.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/apps-helpedit.png
(Stored with Git LFS)
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)
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)
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)
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)
BIN
screenshots/dosbox/hang-ended.png
(Stored with Git LFS)
Binary file not shown.
BIN
screenshots/dosbox/hang-taskmgr.png
(Stored with Git LFS)
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)
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)
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
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
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
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
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
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)
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
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
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
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
BIN
screenshots/dosbox/ucide-ucide-tb.png
(Stored with Git LFS)
Normal file
Binary file not shown.
2
screenshots/masks/hang-ended.mask
Normal file
2
screenshots/masks/hang-ended.mask
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Task Manager's per-application memory figures (change with the code size)
|
||||
372 117 58 49
|
||||
2
screenshots/masks/hang-taskmgr.mask
Normal file
2
screenshots/masks/hang-taskmgr.mask
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Task Manager's per-application memory figures (change with the code size)
|
||||
372 117 58 49
|
||||
2
screenshots/masks/upgrade-su-upgr.mask
Normal file
2
screenshots/masks/upgrade-su-upgr.mask
Normal 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
|
||||
|
|
@ -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))))))
|
||||
|
||||
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:
|
||||
$(MAKE) -C dvxbasic
|
||||
|
|
@ -64,8 +81,27 @@ appObjs = $(patsubst $(1)/%.c,$(OBJDIR)/$(1)/%.o,$(call appSrcs,$(1)))
|
|||
appRes = $(wildcard $(1)/$(1).res)
|
||||
appDeps = $(call appRes,$(1)) $(if $(call appRes,$(1)),$(wildcard $(1)/*.bmp))
|
||||
|
||||
# $(1) = app directory name. $(2) = extra shell commands run after linking
|
||||
# (empty for most apps).
|
||||
# Data files a program installs beside itself, C or BASIC alike:
|
||||
# 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
|
||||
$(1): $(BINDIR)/kpunch/$(1)/$(1).app
|
||||
|
||||
|
|
@ -73,60 +109,34 @@ $(BINDIR)/kpunch/$(1)/$(1).app: $(call appObjs,$(1)) $(call appDeps,$(1))
|
|||
@mkdir -p $$(dir $$@)
|
||||
$$(DXE3GEN) -o $$@ -U $(call appObjs,$(1))
|
||||
$(if $(call appRes,$(1)),cd $(1) && ../$$(DVXRES) build ../$$@ $(1).res)
|
||||
$(2)
|
||||
|
||||
$(OBJDIR)/$(1)/%.o: $(1)/%.c $$(DVX_MAKEFILES)
|
||||
@mkdir -p $$(dir $$@)
|
||||
$$(CC) $$(CFLAGS) -c -o $$@ $$<
|
||||
endef
|
||||
|
||||
DVXDEMO_BMPS = logo.bmp new.bmp open.bmp sample.bmp save.bmp
|
||||
DVXDEMO_POST = cp $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) $(BINDIR)/kpunch/dvxdemo/
|
||||
|
||||
$(foreach a,$(C_APPS),$(eval $(call C_APP_RULES,$(a),$(if $(filter dvxdemo,$(a)),$(DVXDEMO_POST)))))
|
||||
$(foreach a,$(C_APPS),$(eval $(call C_APP_RULES,$(a))))
|
||||
$(foreach a,$(C_APPS),$(if $(APP_DATA_$(a)),$(eval $(call APP_DATA_RULES,$(a),$(a),$(a)))))
|
||||
|
||||
C_APP_OBJS = $(foreach a,$(C_APPS),$(call appObjs,$(a)))
|
||||
|
||||
# BASIC apps (compiled from .dbp projects via bascomp). Source for each app
|
||||
# is under the app's own directory.
|
||||
# BASIC apps (compiled from .dbp projects via bascomp), one rule set per
|
||||
# BASIC_APPS entry.
|
||||
#
|
||||
# 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).
|
||||
|
||||
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
|
||||
$(BASCOMP) iconed/iconed.dbp -o $@ -release
|
||||
$(call basicApp,$(1)): $(call basicDeps,$(call basicDir,$(1))) $$(BASCOMP) | dvxbasic
|
||||
@mkdir -p $$(dir $$@)
|
||||
$$(BASCOMP) $(call basicDir,$(1))/$(call basicName,$(1)).dbp -o $$@ -release
|
||||
endef
|
||||
|
||||
notepad: $(BINDIR)/kpunch/notepad/notepad.app
|
||||
|
||||
$(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
|
||||
$(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))))))
|
||||
|
||||
# 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
|
||||
|
|
@ -161,25 +171,12 @@ host: $(HOST_SOS)
|
|||
host-clean:
|
||||
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)
|
||||
|
||||
clean:
|
||||
rm -f $(C_APP_OBJS) $(C_APP_OBJS:.o=.d)
|
||||
rm -f $(foreach a,$(C_APPS),$(BINDIR)/kpunch/$(a)/$(a).app)
|
||||
rm -f $(addprefix $(BINDIR)/kpunch/dvxdemo/,$(DVXDEMO_BMPS))
|
||||
rm -f $(BINDIR)/kpunch/iconed/iconed.app
|
||||
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
|
||||
rm -f $(APP_DATA_OUTS)
|
||||
rm -f $(BASIC_OUTS)
|
||||
$(MAKE) -C dvxbasic clean
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
* **C apps** -- written directly against the DVX SDK headers,
|
||||
compiled to a DXE3 by `dxe3gen`. See `progman/`, `clock/`,
|
||||
`dvxdemo/`, `cpanel/`, `dvxhelp/`.
|
||||
compiled to a DXE3 by `dxe3gen`. See `progman/`, `dvxdemo/`,
|
||||
`cpanel/`, `dvxhelp/`.
|
||||
* **BASIC apps** -- `.dbp` projects compiled to `.app` files by the
|
||||
host-side `bascomp`. The compiler links the BASIC bytecode into a
|
||||
copy of `basstub.app`, which acts as the runtime host. See
|
||||
`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.
|
||||
For BASIC apps, see `dvxbasic/README.md`. A new app outside this tree
|
||||
|
|
@ -53,15 +54,15 @@ typedef struct {
|
|||
} AppDescriptorT;
|
||||
```
|
||||
|
||||
Example (from `clock/clock.c`):
|
||||
Example (from the SDK's game template, `src/sdk/templates/game/tplName.c`):
|
||||
|
||||
```c
|
||||
SHELL_APP_DESCRIPTOR(
|
||||
.name = "Clock",
|
||||
.name = TPLNAME_TITLE,
|
||||
.hasMainLoop = true,
|
||||
.multiInstance = true,
|
||||
.multiInstance = false,
|
||||
.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.
|
||||
|
||||
Use this pattern when the app needs continuous background work that
|
||||
does not map cleanly onto event callbacks: Clock (polls the system
|
||||
clock every second), terminal emulators, games.
|
||||
does not map cleanly onto event callbacks: 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
|
||||
|
|
@ -133,13 +135,13 @@ unique name. The runtime reads them via
|
|||
Typical `.res` manifest:
|
||||
|
||||
```
|
||||
# clock.res
|
||||
# dvxdemo.res
|
||||
icon32 icon icon32.bmp
|
||||
name text "Clock"
|
||||
name text "DVX Demo"
|
||||
author text "Scott Duensing"
|
||||
copyright text "Copyright 2026 Scott Duensing"
|
||||
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,
|
||||
TextInput widget, and common dialog integration.
|
||||
|
||||
### Clock (clock)
|
||||
### Clock (clock, BASIC)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| File | `apps/kpunch/clock/clock.app` |
|
||||
| Type | Main-loop |
|
||||
| Type | BASIC (main-loop via basstub) |
|
||||
| Multi-instance | Yes |
|
||||
|
||||
Digital clock showing time and date. Polls the system clock every
|
||||
second and invalidates the window to trigger a repaint. Uses the raw
|
||||
`onPaint` callback (no widgets) to draw centered text. Reference
|
||||
implementation for main-loop apps.
|
||||
Digital clock showing the time and date, updated by a Timer. Each new
|
||||
copy opens a little lower and to the right of the previous one
|
||||
(`ShellInstanceCount`, shell.bas). Built from `clock.dbp` +
|
||||
`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)
|
||||
|
||||
|
|
@ -315,7 +375,10 @@ editor, project manager, compiler, and runtime VM. See
|
|||
|
||||
Renders compiled `.hlp` files produced by `dvxhlpc`. Supports table
|
||||
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)
|
||||
|
||||
|
|
@ -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
|
||||
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
|
||||
|
||||
|
|
@ -491,10 +562,6 @@ apps/kpunch/
|
|||
progman.dhs Program Manager's own help
|
||||
dvxhelp.hcf help-compiler config (system reference)
|
||||
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.c widget showcase
|
||||
dvxdemo.res
|
||||
|
|
@ -506,10 +573,10 @@ apps/kpunch/
|
|||
cpanel.res
|
||||
icon32.bmp
|
||||
dvxhelp/
|
||||
dvxhelp.c help viewer
|
||||
dvxhelp.c help viewer (reads .hlp files with the hlpfile
|
||||
library, shows topics in the HelpView widget)
|
||||
dvxhelp.res
|
||||
help.dhs source for the dvxhelp app's own help
|
||||
hlpformat.h binary format shared with the compiler
|
||||
sample.dhs small sample source
|
||||
icon32.bmp
|
||||
helpedit/ BASIC project: help editor
|
||||
|
|
@ -542,6 +609,36 @@ apps/kpunch/
|
|||
widshow.dbp
|
||||
widshow.frm
|
||||
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
|
||||
compiler/ lexer, parser, codegen, strip, obfuscate
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
21
src/apps/kpunch/clock/clock.dbp
Normal file
21
src/apps/kpunch/clock/clock.dbp
Normal 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
|
||||
151
src/apps/kpunch/clock/clock.frm
Normal file
151
src/apps/kpunch/clock/clock.frm
Normal 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
|
||||
|
|
@ -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"
|
||||
|
|
@ -49,7 +49,6 @@
|
|||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
#define CPJ_LABEL_W 90
|
||||
#define CPJ_GAP 4
|
||||
|
|
@ -81,11 +80,16 @@
|
|||
|
||||
// The joystick library's functions (NULL when it is not installed)
|
||||
typedef struct {
|
||||
__typeof__(joyChoiceGet) *choiceGet;
|
||||
__typeof__(joyChoices) *choices;
|
||||
__typeof__(joyChoiceSet) *choiceSet;
|
||||
__typeof__(joyClose) *close;
|
||||
__typeof__(joyCount) *count;
|
||||
__typeof__(joyGetDeadZone) *getDeadZone;
|
||||
__typeof__(joyGetInfo) *getInfo;
|
||||
__typeof__(joyGetRaw) *getRaw;
|
||||
__typeof__(joyKbdSlotGet) *slotGet;
|
||||
__typeof__(joyKbdSlotSet) *slotSet;
|
||||
__typeof__(joyKeyCount) *keyCount;
|
||||
__typeof__(joyKeyName) *keyName;
|
||||
__typeof__(joyOpen) *open;
|
||||
|
|
@ -93,8 +97,6 @@ typedef struct {
|
|||
__typeof__(joyRescan) *rescan;
|
||||
__typeof__(joySetCalibration) *setCal;
|
||||
__typeof__(joySetDeadZone) *setDeadZone;
|
||||
__typeof__(joySettingGet) *settingGet;
|
||||
__typeof__(joySettingSet) *settingSet;
|
||||
__typeof__(joySettingsLoad) *load;
|
||||
__typeof__(joySettingsStore) *store;
|
||||
} JoyFnsT;
|
||||
|
|
@ -128,11 +130,16 @@ typedef struct {
|
|||
#define JOY_FN_SYM(field, fn) { DVX_SYM(#fn), offsetof(JoyFnsT, field) }
|
||||
|
||||
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(count, joyCount),
|
||||
JOY_FN_SYM(getDeadZone, joyGetDeadZone),
|
||||
JOY_FN_SYM(getInfo, joyGetInfo),
|
||||
JOY_FN_SYM(getRaw, joyGetRaw),
|
||||
JOY_FN_SYM(slotGet, joyKbdSlotGet),
|
||||
JOY_FN_SYM(slotSet, joyKbdSlotSet),
|
||||
JOY_FN_SYM(keyCount, joyKeyCount),
|
||||
JOY_FN_SYM(keyName, joyKeyName),
|
||||
JOY_FN_SYM(open, joyOpen),
|
||||
|
|
@ -140,23 +147,12 @@ static const JoyFnSymT sSyms[] = {
|
|||
JOY_FN_SYM(rescan, joyRescan),
|
||||
JOY_FN_SYM(setCal, joySetCalibration),
|
||||
JOY_FN_SYM(setDeadZone, joySetDeadZone),
|
||||
JOY_FN_SYM(settingGet, joySettingGet),
|
||||
JOY_FN_SYM(settingSet, joySettingSet),
|
||||
JOY_FN_SYM(load, joySettingsLoad),
|
||||
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 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 sWizText[] = {
|
||||
|
|
@ -182,6 +178,8 @@ static WidgetT *sModeDrop = NULL;
|
|||
static WidgetT *sKbdDrop = NULL;
|
||||
static WidgetT *sKeyDrops[JOY_KBD_SLOT_COUNT];
|
||||
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 sHandle = -1;
|
||||
static JoyStateT sShown;
|
||||
|
|
@ -196,12 +194,13 @@ static CalWizT sWiz;
|
|||
|
||||
static WidgetT *addRow(WidgetT *parent, const char *label);
|
||||
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 cpJoystickFree(void);
|
||||
void cpJoystickRevert(void);
|
||||
void cpJoystickStore(PrefsHandleT *prefs);
|
||||
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 onDetect(WidgetT *w);
|
||||
static void onDevice(WidgetT *w);
|
||||
|
|
@ -219,7 +218,6 @@ static void refreshDevices(void);
|
|||
static bool resolveLibrary(void);
|
||||
static void selectDevice(int32_t device);
|
||||
static void showZone(void);
|
||||
static void slotKey(int32_t slot, char *out, int32_t outSize);
|
||||
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) {
|
||||
sAc = ac;
|
||||
sPrefs = prefs;
|
||||
|
|
@ -282,14 +291,12 @@ void cpJoystickBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
|
|||
row = addRow(page, "Game port:");
|
||||
sModeDrop = wgtDropdown(row);
|
||||
sModeDrop->onChange = onMode;
|
||||
wgtDropdownSetItems(sModeDrop, sModeLabels, DVX_ARRAY_LEN(sModeLabels));
|
||||
wgtDropdownSetSelected(sModeDrop, findValue(sModeValues, DVX_ARRAY_LEN(sModeValues), sJoy.settingGet(JOY_KEY_GAMEPORT_MODE, JOY_GAMEPORT_TWO)));
|
||||
choiceDrop(sModeDrop, JoyChoiceGamePortE, sModeLabels);
|
||||
|
||||
row = addRow(page, "Keyboard:");
|
||||
sKbdDrop = wgtDropdown(row);
|
||||
sKbdDrop->onChange = onFallback;
|
||||
wgtDropdownSetItems(sKbdDrop, sFallbackLabels, DVX_ARRAY_LEN(sFallbackLabels));
|
||||
wgtDropdownSetSelected(sKbdDrop, findValue(sFallbackValues, DVX_ARRAY_LEN(sFallbackValues), sJoy.settingGet(JOY_KEY_FALLBACK, JOY_FALLBACK_AUTO)));
|
||||
choiceDrop(sKbdDrop, JoyChoiceFallbackE, sFallbackLabels);
|
||||
|
||||
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.
|
||||
static int32_t findValue(const char *const *values, int32_t count, const char *value) {
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
if (strcasecmp(values[i], value) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
// Two states the view draws alike, compared field by field: the struct
|
||||
// ends in a bool, and memcmp would compare its padding too.
|
||||
static bool joyStateEqual(const JoyStateT *a, const JoyStateT *b) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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.
|
||||
static void onDetect(WidgetT *w) {
|
||||
(void)w;
|
||||
|
|
@ -483,15 +486,13 @@ static void onDevice(WidgetT *w) {
|
|||
|
||||
|
||||
static void onFallback(WidgetT *w) {
|
||||
sJoy.settingSet(JOY_KEY_FALLBACK, sFallbackValues[wgtDropdownGetSelected(w)]);
|
||||
sJoy.choiceSet(JoyChoiceFallbackE, wgtDropdownGetSelected(w));
|
||||
onDetect(w);
|
||||
}
|
||||
|
||||
|
||||
// One dropdown of key names per keyboard joystick key.
|
||||
static void onKeys(WidgetT *w) {
|
||||
char key[CPJ_KEY_BUF];
|
||||
|
||||
(void)w;
|
||||
|
||||
if (!sKeyItems) {
|
||||
|
|
@ -507,10 +508,9 @@ static void onKeys(WidgetT *w) {
|
|||
}
|
||||
|
||||
for (int32_t i = 0; i < JOY_KBD_SLOT_COUNT; i++) {
|
||||
slotKey(i, key, sizeof(key));
|
||||
sKeyDrops[i] = wgtDropdown(addRow(dlg->widgetRoot, sSlotLabels[i]));
|
||||
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);
|
||||
|
|
@ -519,14 +519,11 @@ static void onKeys(WidgetT *w) {
|
|||
|
||||
|
||||
static bool onKeysOk(WindowT *dlg, void *userCtx) {
|
||||
char key[CPJ_KEY_BUF];
|
||||
|
||||
(void)dlg;
|
||||
(void)userCtx;
|
||||
|
||||
for (int32_t i = 0; i < JOY_KBD_SLOT_COUNT; i++) {
|
||||
slotKey(i, key, sizeof(key));
|
||||
sJoy.settingSet(key, sKeyItems[wgtDropdownGetSelected(sKeyDrops[i])]);
|
||||
sJoy.slotSet(i, wgtDropdownGetSelected(sKeyDrops[i]));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -535,7 +532,7 @@ static bool onKeysOk(WindowT *dlg, void *userCtx) {
|
|||
|
||||
// The game port makes its joysticks anew in the other mode.
|
||||
static void onMode(WidgetT *w) {
|
||||
sJoy.settingSet(JOY_KEY_GAMEPORT_MODE, sModeValues[wgtDropdownGetSelected(w)]);
|
||||
sJoy.choiceSet(JoyChoiceGamePortE, wgtDropdownGetSelected(w));
|
||||
onDetect(w);
|
||||
}
|
||||
|
||||
|
|
@ -556,7 +553,7 @@ static void onTick(WidgetT *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;
|
||||
}
|
||||
|
||||
|
|
@ -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.
|
||||
static bool wizAxis(int32_t axis) {
|
||||
return (sWiz.info.axisPresent & (1U << axis)) != 0;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@
|
|||
|
||||
// The library's functions (NULL when it is not installed)
|
||||
typedef struct {
|
||||
__typeof__(netConfigEqual) *equal;
|
||||
__typeof__(netConfigFromPrefs) *fromPrefs;
|
||||
__typeof__(netConfigToPrefs) *toPrefs;
|
||||
__typeof__(netConfigProblem) *problem;
|
||||
|
|
@ -129,7 +130,7 @@ void cpNetApply(void) {
|
|||
|
||||
readPage(&cfg);
|
||||
|
||||
if (memcmp(&cfg, &sOriginal, sizeof(cfg)) != 0) {
|
||||
if (!sNet.equal(&cfg, &sOriginal)) {
|
||||
sNet.restart();
|
||||
}
|
||||
}
|
||||
|
|
@ -141,13 +142,14 @@ void cpNetBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
|
|||
char label[CN_LINE_MAX];
|
||||
|
||||
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.toPrefs = (__typeof__(sNet.toPrefs))dlsym(RTLD_DEFAULT, DVX_SYM("netConfigToPrefs"));
|
||||
sNet.problem = (__typeof__(sNet.problem))dlsym(RTLD_DEFAULT, DVX_SYM("netConfigProblem"));
|
||||
sNet.status = (__typeof__(sNet.status))dlsym(RTLD_DEFAULT, DVX_SYM("netGetStatus"));
|
||||
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));
|
||||
wgtLabel(page, "Networking is not installed.");
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@
|
|||
#include "textInput/textInpt.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define CPR_LABEL_W 110
|
||||
|
|
@ -48,9 +47,9 @@
|
|||
|
||||
// The library's functions and tables (NULL when it is not installed)
|
||||
typedef struct {
|
||||
__typeof__(printSettingsLoad) *load;
|
||||
__typeof__(printSettingsRead) *read;
|
||||
__typeof__(printSettingsWrite) *write;
|
||||
__typeof__(printDeviceProblem) *problem;
|
||||
__typeof__(printDeviceStore) *store;
|
||||
__typeof__(printPageSetupDialog) *pageSetup;
|
||||
__typeof__(printDestLabel) *destLabel;
|
||||
__typeof__(printModelLabel) *modelLabel;
|
||||
|
|
@ -91,14 +90,14 @@ void cpPrinterBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
|
|||
PrintSettingsT s;
|
||||
|
||||
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.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.destLabel = (__typeof__(sPrint.destLabel))dlsym(RTLD_DEFAULT, DVX_SYM("printDestLabel"));
|
||||
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));
|
||||
wgtLabel(page, "Printing is not installed.");
|
||||
return;
|
||||
|
|
@ -136,29 +135,22 @@ void cpPrinterBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
|
|||
|
||||
|
||||
const char *cpPrinterProblem(void) {
|
||||
if (sPrint.load && wgtDropdownGetSelected(sDest) == PrintDestFileE && !wgtGetText(sFile)[0]) {
|
||||
return "Type the name of the file to print to, or connect the printer to a port.";
|
||||
}
|
||||
|
||||
if (!sPrint.problem) {
|
||||
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
|
||||
// put back an older one.
|
||||
|
||||
// The library keeps the page setup as it is in DVX.INI now (Page
|
||||
// Setup... saves it there at once).
|
||||
void cpPrinterStore(PrefsHandleT *prefs) {
|
||||
PrintSettingsT s;
|
||||
|
||||
if (!sPrint.load) {
|
||||
if (!sPrint.store) {
|
||||
return;
|
||||
}
|
||||
|
||||
sPrint.load(&s);
|
||||
s.model = (PrintModelE)wgtDropdownGetSelected(sModel);
|
||||
s.dest = (PrintDestE)wgtDropdownGetSelected(sDest);
|
||||
snprintf(s.filePath, sizeof(s.filePath), "%s", wgtGetText(sFile));
|
||||
sPrint.write(prefs, &s);
|
||||
sPrint.store(prefs, (PrintModelE)wgtDropdownGetSelected(sModel), (PrintDestE)wgtDropdownGetSelected(sDest), wgtGetText(sFile));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -56,20 +56,26 @@
|
|||
#define CPS_LIST_H 148 // the event list: nine rows
|
||||
#define CPS_VOL_LBL_W 40
|
||||
#define CPS_TEXT_BUF 128
|
||||
#define CPS_DRIVERS_MAX 16
|
||||
#define CPS_DRV_ITEMS (CPS_DRIVERS_MAX + 2) // Automatic, the drivers, DVX.INI's own choice
|
||||
#define CPS_DRV_ITEMS (AUDIO_DRIVERS_MAX + 2) // Automatic, the drivers, DVX.INI's own choice
|
||||
#define PERCENT_MAX 100
|
||||
|
||||
// The sound library's functions (NULL when it is not installed)
|
||||
typedef struct {
|
||||
__typeof__(audioDeskKey) *key;
|
||||
__typeof__(audioDeskFile) *file;
|
||||
__typeof__(audioDeskLabel) *label;
|
||||
__typeof__(audioDeskPlayFile) *playFile;
|
||||
__typeof__(audioDeskReload) *reload;
|
||||
__typeof__(audioDeskSetFile) *setFile;
|
||||
__typeof__(audioDriverNames) *driverNames;
|
||||
__typeof__(audioErrorText) *errorText;
|
||||
__typeof__(audioGetInfo) *getInfo;
|
||||
const PrefsChoiceT *midiChoices;
|
||||
__typeof__(audioPercentToVolume) *toVolume;
|
||||
__typeof__(audioSetMasterVolume) *setVolume;
|
||||
__typeof__(audioSettingDriver) *driver;
|
||||
__typeof__(audioSettingMidi) *midi;
|
||||
__typeof__(audioSettingsStore) *store;
|
||||
__typeof__(audioSettingVolume) *volume;
|
||||
} SndFnsT;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -80,23 +86,26 @@ typedef struct {
|
|||
#define SND_FN_SYM(field, fn) { DVX_SYM(#fn), offsetof(SndFnsT, field) }
|
||||
|
||||
static const SndFnSymT sSyms[] = {
|
||||
SND_FN_SYM(key, audioDeskKey),
|
||||
SND_FN_SYM(file, audioDeskFile),
|
||||
SND_FN_SYM(label, audioDeskLabel),
|
||||
SND_FN_SYM(playFile, audioDeskPlayFile),
|
||||
SND_FN_SYM(reload, audioDeskReload),
|
||||
SND_FN_SYM(setFile, audioDeskSetFile),
|
||||
SND_FN_SYM(driverNames, audioDriverNames),
|
||||
SND_FN_SYM(errorText, audioErrorText),
|
||||
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
|
||||
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
|
||||
// ============================================================
|
||||
|
|
@ -116,6 +125,7 @@ static char sDrvNames[CPS_DRV_ITEMS][AUDIO_NAME_MAX]; // as stored
|
|||
static const char *sDrvLabels[CPS_DRV_ITEMS];
|
||||
static int32_t sDrvCount = 0;
|
||||
static const char *sEventLabels[SoundCountE];
|
||||
static const char *sMidiLabels[AUDIO_MIDI_CHOICE_COUNT];
|
||||
static int32_t sChoice[SoundCountE];
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -132,7 +142,6 @@ void cpSoundsBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *pref
|
|||
void cpSoundsFree(void);
|
||||
void cpSoundsRevert(void);
|
||||
void cpSoundsStore(PrefsHandleT *prefs);
|
||||
static int32_t iniVolume(void);
|
||||
static void onBrowse(WidgetT *w);
|
||||
static void onEvent(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++) {
|
||||
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:");
|
||||
|
|
@ -272,14 +281,14 @@ void cpSoundsBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
|
|||
sVolSldr->onChange = onVolume;
|
||||
sVolLbl = wgtLabel(row, "");
|
||||
sVolLbl->prefW = wgtPixels(CPS_VOL_LBL_W);
|
||||
wgtSliderSetValue(sVolSldr, iniVolume());
|
||||
wgtSliderSetValue(sVolSldr, sSnd.volume(prefs));
|
||||
showVolume();
|
||||
|
||||
// Driver: "Automatic", every installed driver, and DVX.INI's own
|
||||
// choice even when that driver is missing (so OK keeps it).
|
||||
char names[CPS_DRIVERS_MAX][AUDIO_NAME_MAX];
|
||||
int32_t count = sSnd.driverNames(names, CPS_DRIVERS_MAX);
|
||||
const char *want = prefsGetString(prefs, AUDIO_INI_SECTION, AUDIO_INI_DRIVER, AUDIO_DRIVER_AUTO);
|
||||
char names[AUDIO_DRIVERS_MAX][AUDIO_NAME_MAX];
|
||||
int32_t count = sSnd.driverNames(names, AUDIO_DRIVERS_MAX);
|
||||
const char *want = sSnd.driver(prefs);
|
||||
int32_t drvSel = -1;
|
||||
|
||||
sDrvCount = 0;
|
||||
|
|
@ -301,16 +310,10 @@ void cpSoundsBuild(AppContextT *ac, WidgetT *page, PrefsHandleT *prefs) {
|
|||
wgtDropdownSetItems(sDrvDrop, sDrvLabels, sDrvCount);
|
||||
wgtDropdownSetSelected(sDrvDrop, drvSel);
|
||||
|
||||
const char *midi = prefsGetString(prefs, AUDIO_INI_SECTION, AUDIO_INI_MIDI, AUDIO_MIDI_AUTO);
|
||||
int32_t midiSel = 0;
|
||||
|
||||
for (int32_t i = 0; i < DVX_ARRAY_LEN(sMidiValues); i++) {
|
||||
midiSel = strcasecmp(sMidiValues[i], midi) == 0 ? i : midiSel;
|
||||
}
|
||||
|
||||
prefsChoiceLabels(sSnd.midiChoices, AUDIO_MIDI_CHOICE_COUNT, sMidiLabels);
|
||||
sMidiDrop = wgtDropdown(addRow(page, "MIDI music:"));
|
||||
wgtDropdownSetItems(sMidiDrop, sMidiLabels, DVX_ARRAY_LEN(sMidiLabels));
|
||||
wgtDropdownSetSelected(sMidiDrop, midiSel);
|
||||
wgtDropdownSetItems(sMidiDrop, sMidiLabels, AUDIO_MIDI_CHOICE_COUNT);
|
||||
wgtDropdownSetSelected(sMidiDrop, sSnd.midi(prefs));
|
||||
|
||||
AudioInfoT info;
|
||||
char text[CPS_TEXT_BUF];
|
||||
|
|
@ -339,31 +342,21 @@ void cpSoundsFree(void) {
|
|||
|
||||
void cpSoundsRevert(void) {
|
||||
if (sSnd.setVolume) {
|
||||
sSnd.setVolume(iniVolume() * AUDIO_VOLUME_MAX / PERCENT_MAX);
|
||||
sSnd.setVolume(sSnd.toVolume(sSnd.volume(sPrefs)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void cpSoundsStore(PrefsHandleT *prefs) {
|
||||
if (!sSnd.key) {
|
||||
if (!sSnd.store) {
|
||||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
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);
|
||||
sSnd.store(prefs, sDrvNames[wgtDropdownGetSelected(sDrvDrop)], wgtDropdownGetSelected(sMidiDrop), wgtSliderGetValue(sVolSldr));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -428,7 +421,7 @@ static void onTest(WidgetT *w) {
|
|||
|
||||
|
||||
static void onVolume(WidgetT *w) {
|
||||
sSnd.setVolume(wgtSliderGetValue(w) * AUDIO_VOLUME_MAX / PERCENT_MAX);
|
||||
sSnd.setVolume(sSnd.toVolume(wgtSliderGetValue(w)));
|
||||
showVolume();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
//
|
||||
// System configuration app with these tabs:
|
||||
// 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)
|
||||
// Video -- resolution and color depth
|
||||
// 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)
|
||||
//
|
||||
// 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 "dvxDlg.h"
|
||||
|
|
@ -143,18 +144,9 @@ static DxeAppContextT *sCtx = NULL;
|
|||
static WindowT *sWin = NULL;
|
||||
static PrefsHandleT *sPrefs = NULL;
|
||||
|
||||
// Saved state for Cancel
|
||||
static uint8_t sSavedColorRgb[ColorCountE][3];
|
||||
static int32_t sSavedWheelDir;
|
||||
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];
|
||||
// Saved state for Cancel: colors, mouse, desktop and video mode in the
|
||||
// DVX.INI layout (prefsCreate; NULL when out of memory)
|
||||
static PrefsHandleT *sSnapshot = NULL;
|
||||
|
||||
// Mouse tab widgets
|
||||
static WidgetT *sWheelDrop = NULL;
|
||||
|
|
@ -178,7 +170,6 @@ static WidgetT *sColorSwatch = NULL;
|
|||
static WidgetT *sWallpaperLbl = NULL;
|
||||
static WidgetT *sWpaperList = NULL;
|
||||
static WidgetT *sWpModeDrop = NULL;
|
||||
static char sWallpaperPath[DVX_MAX_PATH];
|
||||
static FileEntryT *sWpaperEntries = NULL; // stb_ds dynamic array
|
||||
static const char **sWpaperLabels = NULL; // stb_ds dynamic array
|
||||
|
||||
|
|
@ -199,7 +190,6 @@ static WidgetT *sThemeList = NULL;
|
|||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
static int32_t accelIndexFromPrefs(void);
|
||||
static void applyMouseConfig(void);
|
||||
void appShutdown(void);
|
||||
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) {
|
||||
int32_t dirIdx = wgtDropdownGetSelected(sWheelDrop);
|
||||
int32_t dbl = wgtSliderGetValue(sDblClickSldr);
|
||||
|
|
@ -307,6 +290,8 @@ void appShutdown(void) {
|
|||
sLabelBufs = NULL;
|
||||
prefsClose(sPrefs);
|
||||
sPrefs = NULL;
|
||||
prefsClose(sSnapshot);
|
||||
sSnapshot = NULL;
|
||||
cpTypesFree();
|
||||
cpJoystickFree();
|
||||
cpSoundsFree();
|
||||
|
|
@ -400,7 +385,7 @@ static void buildDesktopTab(WidgetT *page) {
|
|||
sWpaperList->onDblClick = onApplyWallpaper;
|
||||
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);
|
||||
btnRow->spacing = wgtPixels(CP_SPACING);
|
||||
|
|
@ -462,8 +447,7 @@ static void buildMouseTab(WidgetT *page) {
|
|||
sWheelStepSldr->weight = WGT_WEIGHT_FILL;
|
||||
sWheelStepSldr->onChange = onWheelStepSlider;
|
||||
|
||||
int32_t wheelStep = prefsGetInt(sPrefs, "mouse", "wheelspeed", MOUSE_WHEEL_STEP_DEFAULT);
|
||||
wgtSliderSetValue(sWheelStepSldr, wheelStep);
|
||||
wgtSliderSetValue(sWheelStepSldr, sAc->wheelStep);
|
||||
wgtLabel(wheelStepRow, "Fast ");
|
||||
sWheelStepLbl = wgtLabel(wheelStepRow, "");
|
||||
sWheelStepLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W);
|
||||
|
|
@ -482,8 +466,7 @@ static void buildMouseTab(WidgetT *page) {
|
|||
sDblClickSldr->weight = WGT_WEIGHT_FILL;
|
||||
sDblClickSldr->onChange = onDblClickSlider;
|
||||
|
||||
int32_t dblMs = prefsGetInt(sPrefs, "mouse", "doubleclick", MOUSE_DBLCLICK_DEFAULT_MS);
|
||||
wgtSliderSetValue(sDblClickSldr, dblMs);
|
||||
wgtSliderSetValue(sDblClickSldr, sAc->dblClickMs);
|
||||
wgtLabel(dblRow, "Slow ");
|
||||
sDblClickLbl = wgtLabel(dblRow, "");
|
||||
sDblClickLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W);
|
||||
|
|
@ -504,7 +487,7 @@ static void buildMouseTab(WidgetT *page) {
|
|||
sAccelDrop->weight = WGT_WEIGHT_FILL;
|
||||
sAccelDrop->onChange = onAccelChange;
|
||||
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);
|
||||
|
||||
|
|
@ -519,10 +502,9 @@ static void buildMouseTab(WidgetT *page) {
|
|||
sSpeedSldr->weight = WGT_WEIGHT_FILL;
|
||||
sSpeedSldr->onChange = onSpeedSlider;
|
||||
|
||||
// INI 'speed' holds the mickey ratio (the unit the platform and the
|
||||
// shell boot path use); convert to the inverted slider position.
|
||||
int32_t mickeys = prefsGetInt(sPrefs, "mouse", "speed", MOUSE_SPEED_DEFAULT);
|
||||
wgtSliderSetValue(sSpeedSldr, CP_SPEED_INVERT - mickeys);
|
||||
// The context holds the mickey ratio (the unit the platform and the
|
||||
// INI 'speed' key use); convert to the inverted slider position.
|
||||
wgtSliderSetValue(sSpeedSldr, CP_SPEED_INVERT - sAc->mouseMickeys);
|
||||
wgtLabel(speedRow, "Fast ");
|
||||
sSpeedLbl = wgtLabel(speedRow, "");
|
||||
sSpeedLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W);
|
||||
|
|
@ -685,7 +667,6 @@ static void onChooseWallpaper(WidgetT *w) {
|
|||
static void onClearWallpaper(WidgetT *w) {
|
||||
(void)w;
|
||||
dvxSetWallpaper(sAc, NULL);
|
||||
sWallpaperPath[0] = '\0';
|
||||
wgtSetText(sWallpaperLbl, "(none)");
|
||||
}
|
||||
|
||||
|
|
@ -696,6 +677,8 @@ static void onClose(WindowT *win) {
|
|||
cpSoundsRevert();
|
||||
prefsClose(sPrefs);
|
||||
sPrefs = NULL;
|
||||
prefsClose(sSnapshot);
|
||||
sSnapshot = NULL;
|
||||
dvxDestroyWindow(sAc, win);
|
||||
sWin = NULL;
|
||||
}
|
||||
|
|
@ -784,46 +767,10 @@ static void onOk(WidgetT *w) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Save mouse settings
|
||||
int32_t wheelSel = wgtDropdownGetSelected(sWheelDrop);
|
||||
int32_t accelIdx = wgtDropdownGetSelected(sAccelDrop);
|
||||
|
||||
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);
|
||||
|
||||
// Every change was applied live, so the context holds what to save.
|
||||
dvxMouseToPrefs(sAc, sPrefs);
|
||||
dvxColorsToPrefs(sAc, sPrefs);
|
||||
dvxWallpaperToPrefs(sAc, sPrefs);
|
||||
prefsSetVideoMode(sPrefs, sAc->display.width, sAc->display.height, sAc->display.format.bitsPerPixel);
|
||||
|
||||
cpNetStore(sPrefs);
|
||||
|
|
@ -833,6 +780,8 @@ static void onOk(WidgetT *w) {
|
|||
prefsSave(sPrefs);
|
||||
prefsClose(sPrefs);
|
||||
sPrefs = NULL;
|
||||
prefsClose(sSnapshot);
|
||||
sSnapshot = NULL;
|
||||
cpTypesApply();
|
||||
cpNetApply();
|
||||
cpSoundsApply();
|
||||
|
|
@ -971,7 +920,7 @@ static void onVideoApply(WidgetT *w) {
|
|||
|
||||
PlatformTicksT now = platformClock();
|
||||
|
||||
if ((PlatformTicksT)(now - lastSecond) >= hz) {
|
||||
if (now - lastSecond >= hz) {
|
||||
lastSecond = now;
|
||||
secondsLeft--;
|
||||
setIntLabel(msgLbl, CP_REVERT_PREFIX, secondsLeft, CP_REVERT_SUFFIX);
|
||||
|
|
@ -1030,54 +979,39 @@ static void onWheelStepSlider(WidgetT *w) {
|
|||
|
||||
|
||||
static void restoreSnapshot(void) {
|
||||
// Restore colors (a full repaint, so only when something changed)
|
||||
if (memcmp(sAc->colorRgb, sSavedColorRgb, sizeof(sSavedColorRgb)) != 0) {
|
||||
memcpy(sAc->colorRgb, sSavedColorRgb, sizeof(sSavedColorRgb));
|
||||
dvxApplyColorScheme(sAc);
|
||||
if (!sSnapshot) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore mouse to the pre-open state captured in saveSnapshot.
|
||||
// The snapshot already holds the dvxSetMouseConfig inputs directly.
|
||||
dvxSetMouseConfig(sAc, sSavedWheelDir, sSavedDblClick, sSavedAccelThresh, sSavedMickeys, sSavedWheelStep);
|
||||
// Each of these changes (and repaints) only what differs.
|
||||
dvxColorsFromPrefs(sAc, sSnapshot);
|
||||
dvxMouseFromPrefs(sAc, sSnapshot);
|
||||
|
||||
// Restore video mode if changed
|
||||
if (sAc->display.width != sSavedVideoW ||
|
||||
sAc->display.height != sSavedVideoH ||
|
||||
sAc->display.format.bitsPerPixel != sSavedVideoBpp) {
|
||||
dvxChangeVideoMode(sAc, sSavedVideoW, sSavedVideoH, sSavedVideoBpp);
|
||||
int32_t w;
|
||||
int32_t h;
|
||||
int32_t bpp;
|
||||
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
dvxWallpaperFromPrefs(sAc, sSnapshot);
|
||||
}
|
||||
|
||||
|
||||
// The state Cancel goes back to, written like DVX.INI but kept in memory.
|
||||
static void saveSnapshot(void) {
|
||||
memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb));
|
||||
sSavedWheelDir = sAc->wheelDirection;
|
||||
// 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;
|
||||
prefsClose(sSnapshot);
|
||||
sSnapshot = prefsCreate();
|
||||
|
||||
// Save the active wallpaper path and mode from the context
|
||||
snprintf(sSavedWallpaperPath, sizeof(sSavedWallpaperPath), "%s", sAc->wallpaperPath);
|
||||
snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sAc->wallpaperPath);
|
||||
sSavedWpMode = sAc->wallpaperMode;
|
||||
if (sSnapshot) {
|
||||
dvxColorsToPrefs(sAc, sSnapshot);
|
||||
dvxMouseToPrefs(sAc, sSnapshot);
|
||||
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;
|
||||
}
|
||||
|
||||
snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", path);
|
||||
wgtSetText(sWallpaperLbl, sWallpaperPath);
|
||||
wgtSetText(sWallpaperLbl, sAc->wallpaperPath);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,10 +25,14 @@
|
|||
# Builds:
|
||||
# basrt.lib -- BASIC runtime library (VM + values + form
|
||||
# runtime + dlregsym init)
|
||||
# dvxbasic.app -- BASIC IDE (compiler + UI, links against
|
||||
# basrt.lib, standalone stub embedded)
|
||||
# basdev.lib -- BASIC development library (compiler, build
|
||||
# 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
|
||||
# into the IDE and bascomp)
|
||||
# into basdev.lib and bascomp)
|
||||
# bin/system/BASCOMP.EXE-- DOS command-line compiler
|
||||
# bin/host/bascomp -- host command-line compiler
|
||||
# bin/host/basrun -- host headless runner
|
||||
|
|
@ -46,7 +50,7 @@ include ../../../common.mk
|
|||
|
||||
LIBDVX = ../../../libs/kpunch/libdvx
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
# 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_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
|
||||
|
||||
# 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_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideMenuEditor.o $(OBJDIR)/ideProject.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o
|
||||
APP_TARGET = $(APPDIR)/dvxbasic.app
|
||||
|
|
@ -142,7 +153,7 @@ HARNESS_WORK = ../../../../obj/test/harness
|
|||
|
||||
.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.
|
||||
# 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)
|
||||
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)
|
||||
$(DXE3GEN) -o $@ -U $(STUB_OBJS)
|
||||
|
||||
# IDE app DXE (compiler linked in, runtime from basrt.lib, stub embedded)
|
||||
$(APP_TARGET): $(COMP_OBJS) $(IDE_OBJS) $(STUB_TARGET) dvxbasic.res | $(APPDIR)
|
||||
$(DXE3GEN) -o $@ -U $(COMP_OBJS) $(IDE_OBJS)
|
||||
# IDE app DXE (compiler from basdev.lib, runtime from basrt.lib)
|
||||
$(APP_TARGET): $(IDE_OBJS) dvxbasic.res | $(APPDIR)
|
||||
$(DXE3GEN) -o $@ -U $(IDE_OBJS)
|
||||
$(DVXRES) build $@ dvxbasic.res
|
||||
$(DVXRES) add $@ STUB binary @$(STUB_TARGET)
|
||||
|
||||
|
||||
# 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)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/%.o: basdev/%.c $(DVX_MAKEFILES) | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/%.o: $(LIBDVX)/%.c $(DVX_MAKEFILES) | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
|
|
@ -259,11 +283,14 @@ $(OBJDIR):
|
|||
$(RT_TARGETDIR):
|
||||
mkdir -p $(RT_TARGETDIR)
|
||||
|
||||
$(DEV_TARGETDIR):
|
||||
mkdir -p $(DEV_TARGETDIR)
|
||||
|
||||
$(APPDIR):
|
||||
mkdir -p $(APPDIR)
|
||||
|
||||
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 -f $(TEST_COMPILER) $(TEST_COMPACT) $(TEST_SUITE) $(BASRUN_TARGET) $(HOSTDIR)/.testsan-on $(HOSTDIR)/.testsan-off
|
||||
$(MAKE) -C $(BASIC_TESTDIR) clean
|
||||
|
|
@ -281,7 +308,7 @@ clean: host-basrt-clean
|
|||
# of the host build.
|
||||
HOSTRT_DIR = $(HOST_OBJROOT)/basrt
|
||||
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_CFLAGS = $(HOST_CFLAGS) -Wno-stringop-truncation $(DVX_INCLUDES) -I../../../widgets/kpunch -I../../../libs/kpunch/dvxshell -I../../../libs/kpunch/libtasks
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
// BAS_RES_AUTHOR / PUBLISHER / VERSION / COPYRIGHT / DESCRIPTION
|
||||
// BAS_RES_ICON32 (project icon, else the NOICON fallback)
|
||||
// BAS_RES_HELPFILE (basename only)
|
||||
// BAS_RES_MULTIINSTANCE ("1", only when the project allows copies)
|
||||
// BAS_RES_MODULE, BAS_RES_DEBUG (debug builds)
|
||||
// FORM0, FORM1, ... cut at the end of the outer Begin Form block
|
||||
// so the .frm's BASIC code section (already compiled into
|
||||
|
|
@ -58,6 +59,7 @@
|
|||
|
||||
#include "stb_ds_wrap.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -83,6 +85,10 @@
|
|||
#define BAS_BUILD_WRAP_SLACK 8
|
||||
// Longest [Modules]/[Forms] key, "File" plus a decimal index.
|
||||
#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
|
||||
// other build errors are string literals)
|
||||
|
|
@ -92,6 +98,7 @@ static char sIconError[BAS_BUILD_ERROR_LEN];
|
|||
typedef enum {
|
||||
BAS_BUILD_PASS_MODULES,
|
||||
BAS_BUILD_PASS_CLASSES,
|
||||
BAS_BUILD_PASS_CONTROLS,
|
||||
BAS_BUILD_PASS_FORMS,
|
||||
BAS_BUILD_PASS_COUNT
|
||||
} BasBuildPassE;
|
||||
|
|
@ -100,31 +107,54 @@ typedef enum {
|
|||
static const char *const sWrapBegin[] = {
|
||||
[BAS_BUILD_WRAP_MODULE] = BAS_WRAP_BEGINMODULE,
|
||||
[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[] = {
|
||||
[BAS_BUILD_WRAP_MODULE] = BAS_WRAP_ENDMODULE "\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)
|
||||
static bool appendSource(BasProjectSourceT *ps, const char *text, int32_t len);
|
||||
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);
|
||||
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);
|
||||
const char *basBuildControl(const char *outPath, const BasBuildSpecT *spec);
|
||||
void basBuildDirOf(const char *path, char *out, int32_t outSize);
|
||||
const char *basBuildErrorText(const char *message);
|
||||
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 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 const char *copyHelpFile(const char *outPath, const BasBuildSpecT *spec);
|
||||
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 *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 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 *stubCarrier(const BasBuildSpecT *spec);
|
||||
static const char *writeMarker(const char *outPath);
|
||||
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) {
|
||||
if (!outPath || !spec || !spec->module || !spec->selfPath) {
|
||||
if (!outPath || !spec || !spec->module) {
|
||||
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) {
|
||||
if (wrap != BAS_BUILD_WRAP_NONE) {
|
||||
// One line: the directive, the name and a form's controls
|
||||
const char *ctrls = (ctrlList != NULL) ? ctrlList : "";
|
||||
// One line: the directive, the name and a form's controls (a
|
||||
// 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;
|
||||
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) {
|
||||
static const char *const sections[BAS_BUILD_PASS_COUNT] = {
|
||||
[BAS_BUILD_PASS_MODULES] = BAS_INI_SECTION_MODULES,
|
||||
[BAS_BUILD_PASS_CLASSES] = BAS_INI_SECTION_CLASSES,
|
||||
[BAS_BUILD_PASS_CONTROLS] = BAS_INI_SECTION_CONTROLS,
|
||||
[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));
|
||||
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++) {
|
||||
bool isForm = (pass == BAS_BUILD_PASS_FORMS);
|
||||
bool isClass = (pass == BAS_BUILD_PASS_CLASSES);
|
||||
|
||||
for (int32_t i = 0; ; i++) {
|
||||
char key[BAS_BUILD_KEY_LEN];
|
||||
snprintf(key, sizeof(key), "File%d", (int)i);
|
||||
|
|
@ -312,95 +496,24 @@ bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProje
|
|||
break;
|
||||
}
|
||||
|
||||
BasSourceMapT entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
snprintf(entry.path, sizeof(entry.path), "%s%s%s", projectDir, BAS_BUILD_SEP, val);
|
||||
char path[DVX_MAX_PATH];
|
||||
snprintf(path, sizeof(path), "%s%s%s", projectDir, BAS_BUILD_SEP, val);
|
||||
|
||||
int32_t srcLen = 0;
|
||||
char *src = platformReadFile(entry.path, &srcLen);
|
||||
char *src = platformReadFile(path, &srcLen);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const char *code = src;
|
||||
char formName[BAS_MAX_IDENT] = "";
|
||||
BasBuildWrapE wrap = BAS_BUILD_WRAP_MODULE;
|
||||
bool added = basBuildAddSource(out, path, src, srcLen, wraps[pass]);
|
||||
|
||||
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);
|
||||
|
||||
if (!added) {
|
||||
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) {
|
||||
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) {
|
||||
for (int32_t i = 0; i < (int32_t)arrlen(out->frmPaths); i++) {
|
||||
free(out->frmPaths[i]);
|
||||
|
|
@ -435,14 +596,44 @@ void basBuildFreeProject(BasProjectSourceT *out) {
|
|||
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->frmTexts);
|
||||
arrfree(out->ctlTexts);
|
||||
arrfree(out->map);
|
||||
free(out->source);
|
||||
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) {
|
||||
for (int32_t i = 0; i < (int32_t)arrlen(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, ...) {
|
||||
if (!spec->log) {
|
||||
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
|
||||
// executable when the project names none. Returns NULL on success, else
|
||||
// A control file's resources after its module: the project's metadata
|
||||
// (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).
|
||||
static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec) {
|
||||
char iconPath[DVX_MAX_PATH];
|
||||
|
|
@ -554,7 +854,7 @@ static const char *emitIcon(const char *outPath, const BasBuildSpecT *spec) {
|
|||
return sIconError;
|
||||
}
|
||||
} else {
|
||||
DvxResHandleT *selfRes = dvxResOpen(spec->selfPath);
|
||||
DvxResHandleT *selfRes = dvxResOpen(stubCarrier(spec));
|
||||
|
||||
if (selfRes) {
|
||||
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));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (dbgData && dbgLen > 0) {
|
||||
|
|
@ -644,7 +948,11 @@ static const char *emitResources(const char *outPath, const BasBuildSpecT *spec,
|
|||
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
|
||||
// builds run strip -> obfuscate -> compact on a private copy so the
|
||||
// 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.
|
||||
static const char *writeStub(const char *outPath, const BasBuildSpecT *spec) {
|
||||
DvxResHandleT *selfRes = dvxResOpen(spec->selfPath);
|
||||
DvxResHandleT *selfRes = dvxResOpen(stubCarrier(spec));
|
||||
|
||||
if (!selfRes) {
|
||||
return "Cannot open own executable to read embedded stub.";
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
#ifndef BAS_BUILD_H
|
||||
#define BAS_BUILD_H
|
||||
|
||||
#include "compiler/compiler.h"
|
||||
#include "runtime/vm.h"
|
||||
#include "dvxPrefs.h"
|
||||
#include "dvxTypes.h"
|
||||
|
|
@ -65,8 +66,8 @@ typedef struct {
|
|||
const char *iconPath; // NULL/empty: embed the NOICON fallback
|
||||
const char *helpFile; // NULL/empty: no help file
|
||||
|
||||
// Path of the running executable; it carries the STUB and NOICON
|
||||
// resources.
|
||||
// A file carrying the STUB and NOICON resources (bascomp: itself);
|
||||
// NULL or "" means BAS_BUILD_STUB_CARRIER.
|
||||
const char *selfPath;
|
||||
|
||||
// Compiled module. Never modified.
|
||||
|
|
@ -77,10 +78,21 @@ typedef struct {
|
|||
const char *const *frmSources;
|
||||
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 multiInstance; // [Settings] MultiInstance: more than one copy may run
|
||||
BasBuildLogFnT log; // may be NULL
|
||||
} 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
|
||||
// error message. On failure outPath may be left partially written.
|
||||
const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec);
|
||||
|
|
@ -99,12 +111,14 @@ typedef struct {
|
|||
} BasSourceMapT;
|
||||
|
||||
// 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
|
||||
// BEGINCLASS "name" / ENDCLASS, then the code section of every [Forms]
|
||||
// file wrapped in BEGINFORM "name" / ENDFORM, each file ending on a
|
||||
// newline. frmPaths
|
||||
// and frmTexts list the .frm files (path and full text, as read) in the
|
||||
// same order for the resource step; map locates each file's lines.
|
||||
// file in order wrapped in BEGINMODULE "name" / ENDMODULE, then the code
|
||||
// of every [Classes] file wrapped in BEGINCLASS "name" / ENDCLASS, then
|
||||
// the code of every [Controls] file wrapped in BEGINCLASS "name"
|
||||
// USERCONTROL / ENDCLASS, then the code section of every [Forms] file
|
||||
// wrapped in BEGINFORM "name" / ENDFORM, each file ending on a newline.
|
||||
// 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 {
|
||||
char *source; // NUL-terminated, malloc'd
|
||||
int32_t sourceLen;
|
||||
|
|
@ -112,8 +126,9 @@ typedef struct {
|
|||
int32_t lineCount; // newlines in source (next text starts on lineCount + 1)
|
||||
char **frmPaths; // stb_ds array of malloc'd paths
|
||||
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
|
||||
int32_t fileCount; // modules + classes + forms
|
||||
int32_t fileCount; // modules + classes + controls + forms
|
||||
bool optionExplicit; // [Settings] OptionExplicit
|
||||
char error[BAS_BUILD_ERROR_LEN];
|
||||
} BasProjectSourceT;
|
||||
|
|
@ -123,13 +138,16 @@ typedef enum {
|
|||
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_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;
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
|
|
@ -147,18 +165,88 @@ void basBuildDirOf(const char *path, char *out, int32_t outSize);
|
|||
// Releases everything basBuildConcatProject allocated.
|
||||
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
|
||||
// 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.
|
||||
// a BEGINFORM/ENDFORM wrapper line.
|
||||
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
|
||||
// (projectDirSize bytes) the directory holding it. Returns false when
|
||||
// the file cannot be opened (prefsLoad alone would open a missing file
|
||||
// as an empty project).
|
||||
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
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@
|
|||
#include <string.h> // memchr, strlen
|
||||
#include <strings.h> // strncasecmp
|
||||
|
||||
#include "dvxRes.h" // DVX_RES_NAME_* resource names
|
||||
#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
|
||||
// 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_KEY_STARTUPFORM "StartupForm"
|
||||
#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_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"
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
|
@ -88,6 +94,20 @@ const char *dvxSkipWs(const char *s);
|
|||
#define BAS_CLS_ATTR_PREFIX "VB_"
|
||||
#define BAS_CLS_ATTR_NAME "VB_Name"
|
||||
#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
|
||||
|
|
@ -106,9 +126,11 @@ const char *dvxSkipWs(const char *s);
|
|||
#define BAS_RES_COPYRIGHT "copyright"
|
||||
#define BAS_RES_DESCRIPTION "description"
|
||||
#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
|
||||
#define BAS_RES_ICON32 "icon32"
|
||||
#define BAS_RES_ICON32 DVX_RES_NAME_ICON
|
||||
|
||||
// Binary payload -- bytecode + debug info
|
||||
#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_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
|
||||
// ------------------------------------------------------------
|
||||
|
|
@ -144,12 +182,15 @@ const char *dvxSkipWs(const char *s);
|
|||
static inline int32_t basClassCodePos(const char *text, 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 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 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 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 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 int32_t basMatchBeginControl(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);
|
||||
|
||||
|
|
@ -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
|
||||
// header has one, else the base name of path (without directory and
|
||||
// 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.
|
||||
// Writes a NUL-terminated name into nameBuf (max nameBufSize bytes,
|
||||
// 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
|
||||
// single definition of the Begin Form syntax for basExtractFormName and
|
||||
// basFindFormEndPos. Returns the offset of the form name, or -1.
|
||||
|
|
|
|||
1039
src/apps/kpunch/dvxbasic/basdev/basDebug.c
Normal file
1039
src/apps/kpunch/dvxbasic/basdev/basDebug.c
Normal file
File diff suppressed because it is too large
Load diff
148
src/apps/kpunch/dvxbasic/basdev/basDebug.h
Normal file
148
src/apps/kpunch/dvxbasic/basdev/basDebug.h
Normal 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
|
||||
2013
src/apps/kpunch/dvxbasic/basdev/basDevBas.c
Normal file
2013
src/apps/kpunch/dvxbasic/basdev/basDevBas.c
Normal file
File diff suppressed because it is too large
Load diff
337
src/apps/kpunch/dvxbasic/basdev/basHelpMap.c
Normal file
337
src/apps/kpunch/dvxbasic/basdev/basHelpMap.c
Normal 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;
|
||||
}
|
||||
48
src/apps/kpunch/dvxbasic/basdev/basHelpMap.h
Normal file
48
src/apps/kpunch/dvxbasic/basdev/basHelpMap.h
Normal 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
|
||||
116
src/apps/kpunch/dvxbasic/basdev/basProcs.c
Normal file
116
src/apps/kpunch/dvxbasic/basdev/basProcs.c
Normal 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;
|
||||
}
|
||||
55
src/apps/kpunch/dvxbasic/basdev/basProcs.h
Normal file
55
src/apps/kpunch/dvxbasic/basdev/basProcs.h
Normal 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
|
||||
1048
src/apps/kpunch/dvxbasic/basdev/basProject.c
Normal file
1048
src/apps/kpunch/dvxbasic/basdev/basProject.c
Normal file
File diff suppressed because it is too large
Load diff
287
src/apps/kpunch/dvxbasic/basdev/basProject.h
Normal file
287
src/apps/kpunch/dvxbasic/basdev/basProject.h
Normal 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
|
||||
385
src/apps/kpunch/dvxbasic/basdev/basSource.c
Normal file
385
src/apps/kpunch/dvxbasic/basdev/basSource.c
Normal 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);
|
||||
}
|
||||
92
src/apps/kpunch/dvxbasic/basdev/basSource.h
Normal file
92
src/apps/kpunch/dvxbasic/basdev/basSource.h
Normal 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
|
||||
308
src/apps/kpunch/dvxbasic/basdev/basValidate.c
Normal file
308
src/apps/kpunch/dvxbasic/basdev/basValidate.c
Normal 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;
|
||||
}
|
||||
116
src/apps/kpunch/dvxbasic/basdev/basValidate.h
Normal file
116
src/apps/kpunch/dvxbasic/basdev/basValidate.h
Normal 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
|
||||
2
src/apps/kpunch/dvxbasic/basdev/basdev.dep
Normal file
2
src/apps/kpunch/dvxbasic/basdev/basdev.dep
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
basrt
|
||||
libdvx
|
||||
127
src/apps/kpunch/dvxbasic/basdev/basdev.dhs
Normal file
127
src/apps/kpunch/dvxbasic/basdev/basdev.dhs
Normal 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.
|
||||
|
|
@ -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.vm Virtual Machine (vm.h)
|
||||
.link lib.basrt.frmmodel The .frm Model (frmModel.h)
|
||||
|
||||
.topic lib.basrt.values
|
||||
.title Value System
|
||||
|
|
@ -385,6 +386,7 @@ void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui);
|
|||
hideForm void (*)(ctx, formRef) Hide a form (keep in memory).
|
||||
msgBox int32_t (*)(ctx, message, flags) Display a message 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.
|
||||
.endtable
|
||||
|
||||
|
|
@ -429,16 +431,33 @@ void basVmSetExternCallbacks(BasVmT *vm,
|
|||
|
||||
.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
|
||||
Field Signature Description
|
||||
----- --------- -----------
|
||||
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.
|
||||
.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
|
||||
|
||||
Set the active form context during event dispatch.
|
||||
|
|
@ -449,6 +468,55 @@ void basVmSetCurrentFormVars(BasVmT *vm,
|
|||
BasValueT *vars, int32_t count);
|
||||
.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
|
||||
|
||||
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);
|
||||
.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
|
||||
|
||||
|
|
@ -500,7 +570,16 @@ void basVmStepInto(BasVmT *vm);
|
|||
void basVmStepOver(BasVmT *vm);
|
||||
void basVmStepOut(BasVmT *vm);
|
||||
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 basVmGetErrorLine(const BasVmT *vm);
|
||||
void basVmSetAppPaths(BasVmT *vm, const char *path,
|
||||
const char *config, const char *data);
|
||||
.endcode
|
||||
|
||||
.table
|
||||
|
|
@ -511,7 +590,14 @@ int32_t basVmGetCurrentLine(const BasVmT *vm);
|
|||
basVmStepOver Break when call depth returns to the current level.
|
||||
basVmStepOut Break when call depth drops below the current level.
|
||||
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).
|
||||
basVmGetErrorLine The source line where the current runtime error was raised (0: none).
|
||||
basVmSetAppPaths Set App.Path, App.Config and App.Data.
|
||||
.endtable
|
||||
|
||||
The breakpoint callback notifies the host when a breakpoint fires during nested sub calls:
|
||||
|
|
@ -538,6 +624,18 @@ typedef struct {
|
|||
int32_t procCount;
|
||||
BasFormVarInfoT *formVarInfo;
|
||||
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;
|
||||
int32_t debugVarCount;
|
||||
BasDebugUdtDefT *debugUdtDefs;
|
||||
|
|
@ -560,8 +658,142 @@ typedef struct {
|
|||
procCount Number of procedures.
|
||||
formVarInfo Per-form variable counts and init code addresses.
|
||||
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.
|
||||
debugVarCount Number of debug variable entries.
|
||||
debugUdtDefs UDT type definitions for the debugger.
|
||||
debugUdtDefCount Number of debug UDT definitions.
|
||||
.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
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ typedef enum {
|
|||
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_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_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
|
||||
|
|
@ -402,6 +402,7 @@ typedef struct {
|
|||
int32_t tok; // TOK_INTEGER ... (a keyword type), else TOK_IDENT
|
||||
int32_t fixedLen;// STRING * n
|
||||
bool isNew; // AS NEW
|
||||
bool isBinary;// BINARY STRING (DECLARE LIBRARY only): pointer + length
|
||||
} BasTypeSpecT;
|
||||
|
||||
typedef struct {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
// basEvents.h -- canonical list of BASIC event suffixes
|
||||
//
|
||||
// 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
|
||||
// naming in rewritten forms), and the IDE (to populate the Object/Event
|
||||
// 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
|
||||
// passes it (formrt.c fireCtrlEvent / FireEventWithCancel),
|
||||
// "" 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.
|
||||
|
||||
#ifndef BAS_EVENTS_H
|
||||
#define BAS_EVENTS_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <strings.h>
|
||||
|
||||
typedef enum {
|
||||
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_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;
|
||||
|
||||
#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_DRAGOVER "DragOver"
|
||||
#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) \
|
||||
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_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_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*
|
||||
// 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_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 holds text.
|
||||
#define BAS_CF_TEXT 1 // vbCFText
|
||||
|
|
@ -152,6 +246,8 @@ typedef enum {
|
|||
#define BAS_ROP_PATPAINT 0x00FB0A09 // vbPatPaint
|
||||
#define BAS_ROP_PATINVERT 0x005A0049 // vbPatInvert
|
||||
#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_MASK 0xFF
|
||||
|
||||
|
|
@ -159,16 +255,86 @@ typedef enum {
|
|||
#define BAS_PIC_TYPE_NONE 0 // vbPicTypeNone
|
||||
#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
|
||||
// (e.g. `static const BasEventInfoT sEvents[] = { BAS_EVENT_LIST(BAS_EVENT_ROW) };`).
|
||||
typedef struct {
|
||||
const char *suffix;
|
||||
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;
|
||||
|
||||
#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
|
||||
// NULL-terminated const char *[] view of the list.
|
||||
#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.
|
||||
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
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@
|
|||
|
||||
#include "front.h"
|
||||
#include "basEvents.h"
|
||||
#include "dvxTypes.h"
|
||||
#include "textHelp.h"
|
||||
#include "../runtime/dates.h"
|
||||
#include "thirdparty/stb_ds_wrap.h"
|
||||
|
||||
|
|
@ -68,6 +70,23 @@ static const PredefConstT sPredefConsts[] = {
|
|||
// Show mode
|
||||
{ "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
|
||||
{ "vbNormal", 0 },
|
||||
{ "vbReadOnly", 1 },
|
||||
|
|
@ -108,6 +127,59 @@ static const PredefConstT sPredefConsts[] = {
|
|||
// Clipboard formats
|
||||
{ "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)
|
||||
{ "vbBlack", 0x000000 },
|
||||
{ "vbRed", 0x0000FF },
|
||||
|
|
@ -143,6 +215,14 @@ static const PredefConstT sPredefConsts[] = {
|
|||
{ "vbPatPaint", BAS_ROP_PATPAINT },
|
||||
{ "vbPatInvert", BAS_ROP_PATINVERT },
|
||||
{ "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 },
|
||||
{ "vbLPLarge", BAS_LP_LARGE },
|
||||
{ "vbLPSmallShell", BAS_LP_SMALL_SHELL },
|
||||
|
|
@ -178,6 +258,7 @@ static const PredefConstT sPredefConsts[] = {
|
|||
|
||||
// Function prototypes (alphabetical)
|
||||
static bool addPredefConsts(BasCompilerT *c);
|
||||
static bool appendEnum(const BasCompilerT *c, BasModuleT *mod, const BasSymT *e, int32_t *outIdx);
|
||||
static void applyMemberPatches(BasCompilerT *c);
|
||||
bool basCompile(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 basErrorNode(BasCompilerT *c, const BasNodeT *n, const char *fmt, ...);
|
||||
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 buildInterfaceMap(const BasCompilerT *c, int32_t classIdx, int32_t iface, BasImplementsT *impl);
|
||||
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 int32_t classMemberNamed(const BasClassDefT *cls, const char *name, uint8_t kind);
|
||||
static void collectDebugGlobals(BasCompilerT *c);
|
||||
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 bool globalStartsTyped(const BasSymT *s);
|
||||
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
|
||||
// member's index in its class's public member table, complete now that
|
||||
// every class is compiled.
|
||||
|
|
@ -317,7 +449,7 @@ BasModuleT *basCompilerBuildModule(BasCompilerT *c) {
|
|||
BasModuleT *mod = basCodeGenBuildModule(&c->cg);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
|
@ -391,15 +523,6 @@ void basCompilerDestroy(BasCompilerT *c) {
|
|||
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++) {
|
||||
arrfree(c->loops[i].exitPatches);
|
||||
}
|
||||
|
|
@ -509,6 +632,13 @@ static bool buildClasses(const BasCompilerT *c, BasModuleT *mod) {
|
|||
info->initCodeAddr = def->initCodeAddr;
|
||||
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) {
|
||||
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
|
||||
// slot's INTEGER 0 (STRING, and the other typed scalars), a table that
|
||||
// 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
|
||||
// mod and returns false when memory runs out.
|
||||
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
|
||||
// column is not known).
|
||||
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;
|
||||
}
|
||||
|
||||
(*out)[0] = '\0';
|
||||
size_t used = 0;
|
||||
|
||||
for (int32_t p = 0; p < pi->paramCount; p++) {
|
||||
if (p > 0) {
|
||||
strcat(*out, BAS_PARAM_NAME_SEP);
|
||||
}
|
||||
|
||||
strcat(*out, pi->paramNames[p]);
|
||||
used += (size_t)snprintf(*out + used, len - used, "%s%s", p > 0 ? BAS_PARAM_NAME_SEP : "", pi->paramNames[p]);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ static bool constFromValue(BasCompilerT *c, BasValueT v, BasConstValT *out, cons
|
|||
|
||||
case BAS_TYPE_STRING:
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@
|
|||
#define BAS_MAX_EXPR_DEPTH 64
|
||||
#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 ...).
|
||||
#define BAS_MAX_FIELD_CHAIN BAS_LAYOUT_MAX_DEPTH
|
||||
|
||||
|
|
@ -145,6 +149,10 @@
|
|||
// Built-in function flags.
|
||||
#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_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
|
||||
// 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 parseProcIsFunction; // ... a FUNCTION or Property Get (its name sets its result)
|
||||
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 *parseClassName; // the class module being parsed (module arena; parseInClass)
|
||||
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 basDeclareProc(BasCompilerT *c, BasProcT *proc, int32_t classIdx);
|
||||
void basDeclareTypeName(BasCompilerT *c, const char *name);
|
||||
void basDeclareUserControlVar(BasCompilerT *c, int32_t classIdx);
|
||||
BasSymT *basEnsureVariable(BasCompilerT *c, const char *name, const BasNodeT *at);
|
||||
int32_t basExprClass(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 basSymChangesUndo(BasCompilerT *c);
|
||||
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);
|
||||
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 basGenLoadTemp(BasCompilerT *c, int32_t slot);
|
||||
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 basGenPatchHere(BasCompilerT *c, int32_t operandPos);
|
||||
void basGenPushCStr(BasCompilerT *c, const char *text);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
// BAS_FN_NEW_FORM's argument: the form's name.
|
||||
#define BAS_NEW_FORM_ARGS 1
|
||||
|
||||
|
||||
// Function prototypes (alphabetical)
|
||||
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 basGenLoadTemp(BasCompilerT *c, int32_t slot);
|
||||
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 basGenPushCStr(BasCompilerT *c, const char *text);
|
||||
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);
|
||||
basEmit8(&c->cg, (uint8_t)argc);
|
||||
basEmit8(&c->cg, sym->dataType);
|
||||
basEmit32(&c->cg, (int32_t)pi->externBinaryMask);
|
||||
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.
|
||||
void basGenPatchHere(BasCompilerT *c, int32_t operandPos) {
|
||||
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) {
|
||||
basEmit8(&c->cg, OP_NEW_OBJ);
|
||||
basEmitU16(&c->cg, classOperand(sym->objClass));
|
||||
basGenNew(c, d->spec->name, sym->objClass);
|
||||
basGenStore(c, sym);
|
||||
return;
|
||||
}
|
||||
|
|
@ -728,16 +749,10 @@ static void genProc(BasCompilerT *c, const BasProcT *proc) {
|
|||
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;
|
||||
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;
|
||||
|
||||
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) {
|
||||
basErrorAt(c, 0, 0, "Out of memory");
|
||||
|
|
|
|||
|
|
@ -152,8 +152,7 @@ void basGenExpr(BasCompilerT *c, const BasNodeT *n) {
|
|||
break;
|
||||
|
||||
case NK_NEW:
|
||||
basEmit8(&c->cg, OP_NEW_OBJ);
|
||||
basEmitU16(&c->cg, (uint16_t)((const BasNameT *)n)->aux);
|
||||
basGenNew(c, ((const BasNameT *)n)->name, ((const BasNameT *)n)->aux);
|
||||
break;
|
||||
|
||||
case NK_ELEM:
|
||||
|
|
|
|||
|
|
@ -376,6 +376,8 @@ void basGenStatement(BasCompilerT *c, const BasNodeT *s) {
|
|||
if (st->name != NULL) {
|
||||
basGenPushCStr(c, st->name);
|
||||
basEmit8(&c->cg, OP_LOAD_FORM);
|
||||
} else if (st->a != NULL) {
|
||||
basGenExpr(c, st->a);
|
||||
} else {
|
||||
basEmit8(&c->cg, OP_ME_REF);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
// See obfuscate.h for the high-level description.
|
||||
|
||||
#include "obfuscate.h"
|
||||
#include "strip.h"
|
||||
#include "basEvents.h"
|
||||
#include "lexer.h"
|
||||
#include "../basRes.h"
|
||||
|
|
@ -33,15 +34,17 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.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_MAPPED_LEN 16 // "C" + decimal index + NUL
|
||||
#define BAS_OBF_MAP_INIT_CAP 16 // initial name-map capacity (doubles on growth)
|
||||
#define BAS_REM_KEYWORD_LEN 3 // "REM"
|
||||
|
||||
// .frm properties whose value names another control on the form; only
|
||||
// these values follow a control rename.
|
||||
// .frm properties whose value names another control or a menu of the
|
||||
// form; only these values follow a rename.
|
||||
static const char *sNameRefKeys[] = {
|
||||
"ContextMenu",
|
||||
"DataSource",
|
||||
"MasterSource",
|
||||
NULL
|
||||
|
|
@ -74,6 +77,7 @@ static const char *nameMapAdd(NameMapT *m, const char *name);
|
|||
static void nameMapFree(NameMapT *m);
|
||||
static void nameMapInit(NameMapT *m);
|
||||
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 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);
|
||||
|
|
@ -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)) {
|
||||
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];
|
||||
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 *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
|
||||
// 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) {
|
||||
|
|
@ -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];
|
||||
const char *suffix;
|
||||
|
||||
if (!basSplitEventName(proc->name, prefix, (int32_t)sizeof(prefix), &suffix)) {
|
||||
if (!basSplitProcHandler(proc, prefix, (int32_t)sizeof(prefix), &suffix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,9 +52,12 @@ typedef struct {
|
|||
// - Procedure names matching <OrigName>_<Event>
|
||||
// - formVarInfo entries keyed by form name
|
||||
//
|
||||
// Only the values of properties that name another control (DataSource,
|
||||
// MasterSource) follow a rename; a Caption or Text that happens to equal
|
||||
// a control name is left alone.
|
||||
// Only the values of properties that name another control or a menu
|
||||
// (ContextMenu, DataSource, MasterSource) follow a rename; a Caption or
|
||||
// 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).
|
||||
// outFrms: caller-allocated array of frmCount entries; function fills
|
||||
|
|
|
|||
|
|
@ -449,7 +449,13 @@ typedef enum {
|
|||
// second callback. This allows BASIC programs to use any library
|
||||
// (serial, security, third-party) without recompiling the runtime.
|
||||
|
||||
#define OP_CALL_EXTERN 0xCD // [uint16 libNameIdx] [uint16 funcNameIdx] [uint8 argc] [uint8 retType]
|
||||
#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
|
||||
|
||||
|
|
@ -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_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_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;
|
||||
|
||||
// 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
|
||||
// this flag for an array that starts at 1 (ARRAY under OPTION BASE 1).
|
||||
#define BAS_PARAMARRAY_TYPE_MASK 0xFF
|
||||
|
|
@ -879,9 +896,9 @@ typedef enum {
|
|||
#define BAS_OPERAND_I32 4
|
||||
#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_EXTERN 6 // [uint16 lib] [uint16 func] [uint8 argc] [uint8 retType]
|
||||
#define BAS_OPERAND_FOR 7 // [uint16 varIdx] [uint8 scope] [int32 offset]
|
||||
#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)
|
||||
|
||||
// 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;
|
||||
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_YEAR: case BAS_FN_MONTH: case BAS_FN_DAY:
|
||||
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;
|
||||
return true;
|
||||
|
||||
case BAS_FN_CALLBYNAME:
|
||||
*minArgs = BAS_CALLBYNAME_FIXED;
|
||||
*maxArgs = BAS_BUILTIN_MAX_ARGS;
|
||||
return true;
|
||||
|
||||
case BAS_FN_PARAMARRAY:
|
||||
*minArgs = 1;
|
||||
*maxArgs = BAS_BUILTIN_MAX_ARGS;
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ BasNodeT *basParseUnit(BasCompilerT *c) {
|
|||
// name buffer they are copied into); the lexer accepts longer tokens.
|
||||
static void checkIdentLength(BasCompilerT *c) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -469,6 +469,15 @@ static void declPassNames(BasCompilerT *c, DeclPointT **points) {
|
|||
if (c->lex.token.type == TOK_STRING_LIT) {
|
||||
if (isClass) {
|
||||
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 {
|
||||
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)) {
|
||||
declAdvance(c);
|
||||
*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;
|
||||
}
|
||||
|
||||
|
|
@ -973,8 +988,12 @@ static BasNodeT *parseDeclareLibrary(BasCompilerT *c) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
c->parseInDeclareLib = true;
|
||||
|
||||
BasProcT *proc = basParseProcHeader(c, kind, false);
|
||||
|
||||
c->parseInDeclareLib = false;
|
||||
|
||||
if (proc == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -1377,6 +1396,11 @@ static BasNodeT *parseProcDef(BasCompilerT *c, uint8_t memberKind, bool isPrivat
|
|||
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);
|
||||
|
||||
if (proc == NULL) {
|
||||
|
|
@ -1675,6 +1699,25 @@ static BasTypeSpecT *parseTypeSpec(BasCompilerT *c, bool allowNew) {
|
|||
basParseAdvance(c);
|
||||
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:
|
||||
basParseError(c, BAS_TYPE_EXPECTED);
|
||||
return NULL;
|
||||
|
|
|
|||
|
|
@ -1178,11 +1178,12 @@ static BasNodeT *parsePrimary(BasCompilerT *c) {
|
|||
}
|
||||
|
||||
case TOK_NEW: {
|
||||
// New ClassName / New Collection
|
||||
// New ClassName / New Collection / New FormName (a copy of
|
||||
// the form)
|
||||
basParseAdvance(c);
|
||||
|
||||
if (!basParseCheck(c, TOK_IDENT) || (basClassFind(c, c->lex.token.text) < 0 && strcasecmp(c->lex.token.text, BAS_COLLECTION_TYPE_NAME) != 0)) {
|
||||
basParseErrorExpected(c, "class name (or Collection) after New");
|
||||
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, form name or Collection after New");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -683,7 +683,9 @@ BasNodeT *basParseStatement(BasCompilerT *c) {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
BasStmtT *s = newStmt(c, ST_UNLOAD);
|
||||
|
|
@ -693,13 +695,14 @@ BasNodeT *basParseStatement(BasCompilerT *c) {
|
|||
}
|
||||
|
||||
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");
|
||||
break;
|
||||
}
|
||||
|
||||
s->name = basParseTokenName(c);
|
||||
basParseAdvance(c);
|
||||
s->a = basParseExpr(c);
|
||||
}
|
||||
|
||||
n = &s->h;
|
||||
|
|
@ -801,6 +804,11 @@ BasNodeT *basParseStatement(BasCompilerT *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
|
||||
BasNodeT *tail = NULL;
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ typedef struct {
|
|||
|
||||
|
||||
// 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 BasSymT *addModuleSym(BasCompilerT *c, const char *name, uint8_t kind, uint8_t dataType);
|
||||
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 basDeclareProc(BasCompilerT *c, BasProcT *proc, int32_t classIdx);
|
||||
void basDeclareTypeName(BasCompilerT *c, const char *name);
|
||||
void basDeclareUserControlVar(BasCompilerT *c, int32_t classIdx);
|
||||
int32_t basFieldIndex(const BasTypeDefT *td, const char *name);
|
||||
bool basIsDefFnName(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);
|
||||
void basSymChange(BasCompilerT *c, BasSymT *sym, const BasNodeT *at, uint8_t dataType, int32_t udtTypeId, uint32_t flags);
|
||||
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 int32_t callLeavesValue(const BasNodeT *call);
|
||||
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 BasNodeT *defaultMemberTarget(BasCompilerT *c, BasNodeT *obj, bool host);
|
||||
static void defineLabel(BasCompilerT *c, BasStmtT *s);
|
||||
static uint32_t externBinaryMask(const BasProcT *proc);
|
||||
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 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 plainValue(const BasNodeT *n);
|
||||
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 const BasSymT *procValueEnum(const BasCompilerT *c, const BasProcT *proc);
|
||||
static BasSymT *refLabel(BasCompilerT *c, const char *name, const BasNodeT *at);
|
||||
static bool registerParams(BasCompilerT *c, BasProcT *proc, const SignatureT *sig);
|
||||
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 resolveStatic(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 resolveWithEvents(BasCompilerT *c);
|
||||
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 bool staticBound(BasCompilerT *c, BasNodeT *expr, BasConstValT *out);
|
||||
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
|
||||
// programs write it after the dot). Private members are not recorded.
|
||||
// Two members may share a name only when both are Property procedures.
|
||||
static bool addClassMember(BasCompilerT *c, const char *name, BasSymT *sym, uint8_t kind, bool isPrivate, const BasNodeT *at) {
|
||||
// programs write it after the dot, the ENUM its value is declared As or
|
||||
// NULL). Private members are not recorded. Two members may share a name
|
||||
// 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];
|
||||
|
||||
sym->ownerClass = c->classIdx;
|
||||
|
|
@ -210,6 +219,7 @@ static bool addClassMember(BasCompilerT *c, const char *name, BasSymT *sym, uint
|
|||
memset(&def, 0, sizeof(def));
|
||||
def.name = basArenaStrDup(&c->modArena, name, (int32_t)strlen(name));
|
||||
def.sym = sym;
|
||||
def.enumSym = enumSym;
|
||||
def.kind = kind;
|
||||
arrput(cls->members, def);
|
||||
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).
|
||||
static bool addEnumMember(BasCompilerT *c, const BasSymT *enumSym, const char *member, bool filePrivate, int32_t value, const BasNodeT *at) {
|
||||
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)) {
|
||||
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: {
|
||||
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) {
|
||||
if (!appendSafe(a)) {
|
||||
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) {
|
||||
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->inDefFn = false;
|
||||
c->curProc = NULL;
|
||||
|
|
@ -573,6 +597,7 @@ void basDeclareLibrary(BasCompilerT *c, const BasStmtT *s) {
|
|||
sym->flags |= SF_DEFINED | SF_EXTERN;
|
||||
sym->u.proc->externLibIdx = libIdx;
|
||||
sym->u.proc->externFuncIdx = basAddConstant(&c->cg, externName, (int32_t)strlen(externName));
|
||||
sym->u.proc->externBinaryMask = externBinaryMask(proc);
|
||||
copySignature(sym, &sig);
|
||||
}
|
||||
}
|
||||
|
|
@ -677,6 +702,7 @@ void basDeclareProc(BasCompilerT *c, BasProcT *proc, int32_t classIdx) {
|
|||
sym->dataType = returnType;
|
||||
sym->ownerClass = classIdx;
|
||||
sym->objClass = cls;
|
||||
sym->line = proc->h.line;
|
||||
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);
|
||||
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) {
|
||||
for (int32_t i = 0; i < td->fieldCount; i++) {
|
||||
if (strcasecmp(td->fields[i].name, name) == 0) {
|
||||
|
|
@ -773,6 +817,7 @@ void basResolveModuleEnd(BasCompilerT *c) {
|
|||
if (!c->hasError) {
|
||||
resolveImplements(c);
|
||||
resolveWithEvents(c);
|
||||
resolveUserControls(c);
|
||||
}
|
||||
|
||||
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_DATA:
|
||||
case ST_LOAD:
|
||||
break;
|
||||
|
||||
case ST_UNLOAD:
|
||||
resolveUnload(c, (BasStmtT *)s);
|
||||
break;
|
||||
|
||||
case ST_MIDASSIGN:
|
||||
|
|
@ -1040,6 +1088,13 @@ bool basResolveTypeSpec(BasCompilerT *c, const BasTypeSpecT *t, bool prescan, ui
|
|||
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++) {
|
||||
if (strcasecmp(t->name, sObjectTypeNames[i]) == 0) {
|
||||
*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
|
||||
// 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) {
|
||||
// A form's or class's code ends after its closing marker is generated
|
||||
if (unit->kind == ST_ENDFORM && c->inForm) {
|
||||
|
|
@ -1077,7 +1134,7 @@ void basResolveUnitEnd(BasCompilerT *c, BasNodeT *unit) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (unit->kind == ST_ENDCLASS && c->inClass && !c->inProc) {
|
||||
if (unit->kind == ST_ENDCLASS && c->inClass) {
|
||||
c->inClass = false;
|
||||
c->classIdx = BAS_CLASS_NONE;
|
||||
c->fileIdx = c->outerFileIdx;
|
||||
|
|
@ -1085,13 +1142,13 @@ void basResolveUnitEnd(BasCompilerT *c, BasNodeT *unit) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (unit->kind != ST_PROC || !c->inProc) {
|
||||
if (!c->inProc) {
|
||||
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;
|
||||
|
||||
// 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
|
||||
// untyped ParamArray holds Variants), ByVal, Optional, defaults (with
|
||||
// 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
|
||||
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) {
|
||||
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).
|
||||
static int32_t callLeavesValue(const BasNodeT *call) {
|
||||
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;
|
||||
|
|
@ -1466,6 +1546,25 @@ static void defineLabel(BasCompilerT *c, BasStmtT *s) {
|
|||
|
||||
// The label named name of the current procedure (a local label inside a
|
||||
// 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) {
|
||||
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
|
||||
// (BAS_CLASS_NONE: not in a class): "Class.name", "Class.name.Let" /
|
||||
// ".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
|
||||
// same procedure (or module), else an error at the end of it.
|
||||
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.
|
||||
static void resolveBeginForm(BasCompilerT *c, BasStmtT *s) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -2165,7 +2296,7 @@ static void resolveCallStmt(BasCompilerT *c, BasStmtT *s) {
|
|||
}
|
||||
|
||||
s->a = basResolveCallee(c, call, true);
|
||||
s->ival2 = (sym->kind == SK_FUNCTION) ? 1 : 0;
|
||||
s->ival2 = procLeavesValue(sym);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2173,6 +2304,24 @@ static void resolveCallStmt(BasCompilerT *c, BasStmtT *s) {
|
|||
BasNameT *n = (BasNameT *)callee;
|
||||
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) {
|
||||
basErrorNode(c, callee, "Undefined Sub: %s", n->name);
|
||||
return;
|
||||
|
|
@ -2200,7 +2349,7 @@ static void resolveCallStmt(BasCompilerT *c, BasStmtT *s) {
|
|||
call->argc = (int16_t)argc;
|
||||
s->a = &call->h;
|
||||
s->b = NULL;
|
||||
s->ival2 = (sym->kind == SK_FUNCTION) ? 1 : 0;
|
||||
s->ival2 = procLeavesValue(sym);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2276,8 +2425,11 @@ static void resolveDeclarator(BasCompilerT *c, BasDeclT *d, bool isShared, bool
|
|||
fixedLen = d->spec->fixedLen;
|
||||
}
|
||||
|
||||
if (asNew && (objClass == BAS_CLASS_NONE || d->isArray)) {
|
||||
basErrorNode(c, &d->h, d->isArray ? "New cannot be used with an array" : "New needs a class name (or Collection)");
|
||||
// As New FormName: a copy of the form (an untyped Form object)
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -2352,7 +2504,7 @@ static void resolveDeclarator(BasCompilerT *c, BasDeclT *d, bool isShared, bool
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -2703,6 +2855,8 @@ static void resolveEnum(BasCompilerT *c, BasStmtT *s) {
|
|||
|
||||
// Event Name(params) in a class module: no Optional or ParamArray
|
||||
// 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) {
|
||||
BasClassDefT *cls = &c->classes[c->classIdx];
|
||||
|
||||
|
|
@ -2731,10 +2885,21 @@ static void resolveEvent(BasCompilerT *c, BasProcT *ev) {
|
|||
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;
|
||||
|
||||
memset(&def, 0, sizeof(def));
|
||||
def.name = basArenaStrDup(&c->modArena, ev->name, (int32_t)strlen(ev->name));
|
||||
def.paramNames = basArenaStrDup(&c->modArena, names, namesLen);
|
||||
def.paramCount = sig.info.paramCount;
|
||||
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->argc = (int16_t)s->ival2;
|
||||
} 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);
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
// 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
|
||||
|
|
@ -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
|
||||
// slot becomes an array of the declarator's constant bounds (none: a
|
||||
// dynamic array the procedure REDIMs) or a TYPE instance, allocated once
|
||||
|
|
|
|||
|
|
@ -235,6 +235,7 @@ static const BasBuiltinT sBuiltins[] = {
|
|||
{"YEAR", OP_BUILTIN, BAS_FN_YEAR, 0, 0, BAS_NODE_TYPE_UNKNOWN, BI_PURE, 0, NULL},
|
||||
|
||||
// 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},
|
||||
{"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},
|
||||
|
|
@ -242,6 +243,7 @@ static const BasBuiltinT sBuiltins[] = {
|
|||
{"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},
|
||||
{"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},
|
||||
{"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}
|
||||
|
|
@ -608,11 +610,8 @@ int32_t basExprClass(const BasCompilerT *c, const BasNodeT *n) {
|
|||
case NK_ME:
|
||||
return (((const BasNameT *)n)->aux == OP_ME_OBJ) ? c->classIdx : BAS_CLASS_NONE;
|
||||
|
||||
case NK_NEW: {
|
||||
int32_t cls = ((const BasNameT *)n)->aux;
|
||||
|
||||
return (cls == BAS_NEW_COLLECTION) ? BAS_CLASS_COLLECTION : cls;
|
||||
}
|
||||
case NK_NEW:
|
||||
return ((const BasNameT *)n)->aux;
|
||||
|
||||
case NK_REF:
|
||||
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
|
||||
// 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
|
||||
// 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) {
|
||||
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;
|
||||
|
||||
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) {
|
||||
BasNodeT *a = *link;
|
||||
|
|
@ -713,11 +718,7 @@ bool basLateArgs(BasCompilerT *c, BasCallT *call, BasNodeT *args, int32_t argc,
|
|||
return false;
|
||||
}
|
||||
|
||||
if (named > 0) {
|
||||
strcat(names, BAS_PARAM_NAME_SEP);
|
||||
}
|
||||
|
||||
strcat(names, ((const BasMemberT *)a)->name);
|
||||
namesLen += snprintf(names + namesLen, sizeof(names) - (size_t)namesLen, "%s%s", named > 0 ? BAS_PARAM_NAME_SEP : "", ((const BasMemberT *)a)->name);
|
||||
|
||||
// The value takes the named argument's place in the list
|
||||
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
|
||||
int32_t len = (int32_t)strlen(names);
|
||||
char *kept = basArenaStrDup(&c->unitArena, names, len);
|
||||
char *kept = basArenaStrDup(&c->unitArena, names, namesLen);
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
// name.Ctrl(i).Member: a control array element
|
||||
if (call->h.flags & NF_MEMBERBASE) {
|
||||
// name.Ctrl(i).Member: a control array element (name.Controls(i) is
|
||||
// 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) {
|
||||
basErrorNode(c, &call->h, "Control array index expected");
|
||||
return NULL;
|
||||
|
|
@ -1314,7 +1315,13 @@ BasNodeT *basResolveExpr(BasCompilerT *c, BasNodeT *n, uint8_t use) {
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
// of the object a variable of a known class holds (a control of the
|
||||
// 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);
|
||||
|
||||
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 *head = NULL;
|
||||
BasNodeT *tail = NULL;
|
||||
int32_t i = 0;
|
||||
|
||||
if (argc > BAS_VM_MAX_CALL_ARGS) {
|
||||
basErrorNode(c, args, "Too many arguments (max %d)", (int)BAS_VM_MAX_CALL_ARGS);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (BasNodeT *a = args; a != NULL; i++) {
|
||||
for (BasNodeT *a = args; a != NULL;) {
|
||||
BasNodeT *next = a->next;
|
||||
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);
|
||||
}
|
||||
|
||||
// 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)
|
||||
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) {
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
@ -2455,7 +2470,8 @@ static BasNodeT *resolveBuiltin(BasCompilerT *c, const BasBuiltinT *b, BasCallT
|
|||
// OP_BUILTIN function is Missing: the VM gives it its default
|
||||
for (BasNodeT *a = call->args; a != NULL;) {
|
||||
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) {
|
||||
return NULL;
|
||||
|
|
@ -2609,7 +2625,16 @@ static BasNodeT *resolveName(BasCompilerT *c, BasNameT *n, uint8_t use) {
|
|||
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) {
|
||||
BasCallT *call = (BasCallT *)basNodeNew(&c->unitArena, (int32_t)sizeof(BasCallT), NK_FCALL, n->h.line, n->h.col);
|
||||
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).
|
||||
static const char *typeDisplayName(uint8_t dataType) {
|
||||
const char *scalar = basValScalarTypeName(dataType);
|
||||
|
||||
if (scalar) {
|
||||
return scalar;
|
||||
}
|
||||
|
||||
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:
|
||||
return "an array";
|
||||
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ typedef struct {
|
|||
int16_t requiredParams; // up to the last non-Optional parameter
|
||||
uint16_t externLibIdx; // DECLARE LIBRARY: constant pool indices
|
||||
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 paramFlags[BAS_MAX_PARAMS]; // PARAM_*
|
||||
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 {
|
||||
const char *name; // as programs write it (obj.name), or "Iface.name"
|
||||
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
|
||||
} BasClassMemberDefT;
|
||||
|
||||
|
|
@ -228,6 +230,7 @@ typedef struct {
|
|||
int32_t paramCount;
|
||||
uint8_t paramTypes[BAS_MAX_PARAMS];
|
||||
uint8_t paramFlags[BAS_MAX_PARAMS]; // PARAM_BYVAL, PARAM_TYPED
|
||||
const char *paramNames; // module arena: the parameters' names, comma-separated ("" for none)
|
||||
} BasEventDefT;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -243,6 +246,7 @@ typedef struct {
|
|||
int32_t implementsLine;
|
||||
bool defined;
|
||||
bool isInterface; // some class Implements it
|
||||
bool isUserControl; // BEGINCLASS "Name" USERCONTROL: a .ctl, a widget class written in BASIC
|
||||
} BasClassDefT;
|
||||
|
||||
// A class member variable the declaration pass read (registered once
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ const char *basEventSuffixes[] = {
|
|||
// Function prototypes (alphabetical)
|
||||
bool basEventSuffixMatch(const char *suffix);
|
||||
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);
|
||||
static int compareNamesCI(const void *a, const void *b);
|
||||
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) {
|
||||
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, '_');
|
||||
|
||||
if (!underscore || !basEventSuffixMatch(underscore + 1)) {
|
||||
if (!underscore || underscore == name || underscore[1] == '\0') {
|
||||
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) {
|
||||
if (!mod) {
|
||||
return;
|
||||
|
|
@ -138,7 +154,7 @@ void basStripModule(BasModuleT *mod) {
|
|||
char prefix[BAS_MAX_IDENT];
|
||||
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]) {
|
||||
kept[nKept++] = name;
|
||||
|
|
|
|||
|
|
@ -41,4 +41,11 @@
|
|||
// string constants (SetEvent dispatch targets).
|
||||
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
|
||||
|
|
|
|||
|
|
@ -164,13 +164,12 @@ static uint8_t binaryType(uint8_t tok, uint8_t a, uint8_t b) {
|
|||
|
||||
switch (tok) {
|
||||
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;
|
||||
}
|
||||
|
||||
// fall through
|
||||
case TOK_MINUS:
|
||||
case TOK_STAR:
|
||||
if (isLong || 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
Loading…
Add table
Reference in a new issue