# 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.

# DVX BASIC Makefile
#
# Builds:
#   basrt.lib             -- BASIC runtime library (VM + values + form
#                            runtime + dlregsym init)
#   basdev.lib            -- BASIC development library (compiler, build
#                            pipeline, validator, debugger, the .frm
#                            model; the IDE and BASIC programs through
#                            compiler.bas / debug.bas / designer.bas's
#                            Frm*; standalone stub embedded)
#   dvxbasic.app          -- BASIC IDE (UI, links against basrt.lib and
#                            basdev.lib)
#   basstub.app           -- standalone app stub (obj/dvxbasic, embedded
#                            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
#   test_compiler, test_compact, test_suite -- host test harnesses
#                            ("make tests" builds and runs them)
#   obj/host/basrt.a      -- host compiler + runtime + form runtime for the
#                            src/test/formrt suite ("make host-basrt")
#
# The runtime is a separate library so compiled BASIC apps can use
# it without including the compiler. The library's constructor calls
# dlregsym() to register its exports, making them available to DXEs
# loaded later (apps, widgets, etc.).

include ../../../common.mk

LIBDVX        = ../../../libs/kpunch/libdvx
TOOLSDIR      = ../../../tools
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
LIBSDIR = ../../../../bin/libs
APPDIR  = ../../../../bin/apps/kpunch/dvxbasic
HOSTDIR = ../../../../bin/host
DVXRES  = $(HOSTDIR)/dvxres
SYSTEMDIR = ../../../../bin/system

# Sources every command-line build (bascomp, basrun, test_suite) links:
# the build pipeline, the full compiler and the runtime without forms.
COMP_SRCS    = compiler/lexer.c compiler/ast.c compiler/scope.c compiler/parse.c compiler/parseExpr.c compiler/parseStmt.c compiler/parseDecl.c compiler/resolve.c compiler/resolveExpr.c compiler/typecheck.c compiler/fold.c compiler/gen.c compiler/genExpr.c compiler/genStmt.c compiler/compiler.c compiler/codegen.c compiler/strip.c compiler/obfuscate.c compiler/compact.c
VM_SRCS      = runtime/vm.c runtime/values.c runtime/dates.c runtime/serialize.c runtime/gfx.c runtime/screen.c
# Standalone programs that link the VM also need what libdvx provides it
# at run time on the target (INI access through dvxPrefs, Unicode through
# dvxFontFmt).
VM_LINK_SRCS = $(VM_SRCS) $(LIBDVX)/dvxPrefs.c $(LIBDVX)/dvxFontFmt.c
SUPPORT_SRCS = $(LIBDVX)/platform/dvxPlatformUtil.c $(LIBDVX)/thirdparty/stb_ds_impl.c
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)/formPrinter.o $(OBJDIR)/formDesign.o $(OBJDIR)/formCtl.o $(OBJDIR)/frmParser.o $(OBJDIR)/basNativeDos.o $(OBJDIR)/serialize.o $(OBJDIR)/basRunHost.o
RT_TARGETDIR = $(LIBSDIR)/kpunch/basrt
RT_TARGET    = $(RT_TARGETDIR)/basrt.lib

# 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,
# proc scanner, the .frm model the designer works on, and the BASIC
# entry points (basdev/: compiler.bas, debug.bas, designer.bas's Frm*)
DEV_SRCS      = basdev/basValidate.c basdev/basDebug.c basdev/basProcs.c basdev/basSource.c basdev/basHelpMap.c basdev/basProject.c basdev/basDevBas.c basdev/frmModel.c basdev/frmModelBas.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 (Make Package calls the package library, LIBS\KPUNCH\PKG)
IDE_OBJS   = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideMenuEditor.o $(OBJDIR)/ideProject.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o $(OBJDIR)/idePackage.o
PKG_INCLUDE = -I../../../libs/kpunch/pkg

$(OBJDIR)/idePackage.o: CFLAGS += $(PKG_INCLUDE)
APP_TARGET = $(APPDIR)/dvxbasic.app

# Standalone stub (embedded as IDE resource)
STUB_OBJS   = $(OBJDIR)/basstub.o $(OBJDIR)/basModuleRes.o
STUB_TARGET = $(OBJDIR)/basstub.app

# DOS command-line compiler: the same DOS objects the IDE uses plus the
# resource and support code the loader normally provides.
DOS_BASCOMP_OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(notdir stub/bascomp.c $(CLI_SRCS)))

# ---- Native host tools and harnesses (host gcc, not cross-compiled) ----
#
# TESTSAN=1 rebuilds the harnesses, bascomp and basrun under ASan/UBSan
# (make tests TESTSAN=1).  Objects live in one directory per mode; the
# binaries share bin/host and depend on a mode stamp so switching TESTSAN
# relinks them instead of running a stale binary.
HOSTCFLAGS  = $(HOST_TOOL_CFLAGS) -Wno-stringop-truncation -D_GNU_SOURCE $(DVX_INCLUDES)
HOSTLDFLAGS =
HOSTTOOL_OBJDIR = $(OBJDIR)/host-$(if $(filter 1,$(TESTSAN)),san,plain)
ifeq ($(TESTSAN),1)
HOSTCFLAGS  += -g $(SAN_FLAGS)
HOSTLDFLAGS += $(SAN_FLAGS)
endif
# COV=1 (make coverage): every host build (harnesses, bascomp, basrun,
# basrt.a) gets gcov instrumentation and lands under obj/hostcov so it
# never mixes with the sanitized or release host binaries.  dvxres is
# still the release tool from bin/host.  Must precede the rules below
# because target names expand when they are read.
ifeq ($(COV),1)
HOSTDIR          = ../../../../obj/hostcov/basic
DVXRES           = ../../../../bin/host/dvxres
HOSTTOOL_OBJDIR  = $(HOSTDIR)/obj
HOSTCFLAGS      += -g --coverage
HOSTLDFLAGS     += --coverage
endif

TESTSAN_STAMP = $(HOSTDIR)/.testsan-$(if $(filter 1,$(TESTSAN)),on,off)

# hostObjs SRCS -> the host tool objects for SRCS
hostObjs = $(patsubst %.c,$(HOSTTOOL_OBJDIR)/%.o,$(notdir $1))

TEST_COMPILER = $(HOSTDIR)/test_compiler
TEST_COMPACT  = $(HOSTDIR)/test_compact
TEST_SUITE    = $(HOSTDIR)/test_suite
BASCOMP_TARGET = $(HOSTDIR)/bascomp
BASRUN_TARGET  = $(HOSTDIR)/basrun

TEST_COMPILER_OBJS = $(call hostObjs,test_compiler.c $(COMP_SRCS) $(VM_LINK_SRCS) $(SUPPORT_SRCS))
TEST_COMPACT_OBJS  = $(call hostObjs,test_compact.c $(COMP_SRCS) $(VM_LINK_SRCS) $(SUPPORT_SRCS))
TEST_SUITE_OBJS    = $(call hostObjs,test_suite.c $(CLI_SRCS))
BASCOMP_OBJS       = $(call hostObjs,stub/bascomp.c $(CLI_SRCS))
BASRUN_OBJS        = $(call hostObjs,stub/basrun.c stub/basModuleRes.c $(CLI_SRCS))
HOSTTOOL_OBJS      = $(sort $(TEST_COMPILER_OBJS) $(TEST_COMPACT_OBJS) $(TEST_SUITE_OBJS) $(BASCOMP_OBJS) $(BASRUN_OBJS))

# Extra per-object host flags
HOSTFLAGS_test_suite = -DBAS_TEST_SRC_DIR='"$(abspath .)"'

# Host-side BASIC test directory (basrun fixtures, fuzzers, compaction
# diff) and the scratch directory the harnesses run in (they write files
# into the working directory).
BASIC_TESTDIR = ../../../test/basic
HARNESS_WORK  = ../../../../obj/test/harness

.PHONY: all clean tests

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
# and return non-zero on failure.  make stops at the first one that fails.
# The src/test/basic suite (sample runs, fixtures, fuzzers, compaction
# differential) needs bascomp and basrun; it inherits TESTSAN.
tests: $(TEST_COMPILER) $(TEST_COMPACT) $(TEST_SUITE) $(BASCOMP_TARGET) $(BASRUN_TARGET)
	mkdir -p $(HARNESS_WORK)
	cd $(HARNESS_WORK) && $(abspath $(TEST_COMPILER))
	cd $(HARNESS_WORK) && $(abspath $(TEST_COMPACT))
	cd $(HARNESS_WORK) && $(abspath $(TEST_SUITE))
	$(MAKE) -C $(BASIC_TESTDIR) test TESTSAN=$(TESTSAN)

$(TEST_COMPILER): $(TEST_COMPILER_OBJS) $(TESTSAN_STAMP) | $(HOSTDIR)
	$(HOSTCC) $(HOSTLDFLAGS) -o $@ $(TEST_COMPILER_OBJS) -lm

$(TEST_COMPACT): $(TEST_COMPACT_OBJS) $(TESTSAN_STAMP) | $(HOSTDIR)
	$(HOSTCC) $(HOSTLDFLAGS) -o $@ $(TEST_COMPACT_OBJS) -lm

$(TEST_SUITE): $(TEST_SUITE_OBJS) $(TESTSAN_STAMP) | $(HOSTDIR)
	$(HOSTCC) $(HOSTLDFLAGS) -o $@ $(TEST_SUITE_OBJS) -lm

$(BASRUN_TARGET): $(BASRUN_OBJS) $(TESTSAN_STAMP) | $(HOSTDIR)
	$(HOSTCC) $(HOSTLDFLAGS) -o $@ $(BASRUN_OBJS) -lm

# Host command-line compiler -- basstub.app is appended as a STUB
# resource so bascomp is self-contained (no BASSTUB.APP companion file),
# and noicon.bmp as the ICON32 fallback for projects without an icon.
$(BASCOMP_TARGET): $(BASCOMP_OBJS) $(TESTSAN_STAMP) $(STUB_TARGET) noicon.bmp $(DVXRES) | $(HOSTDIR)
	$(HOSTCC) $(HOSTLDFLAGS) -o $@ $(BASCOMP_OBJS) -lm
	$(DVXRES) add $@ STUB binary @$(STUB_TARGET)
	$(DVXRES) add $@ noicon icon @noicon.bmp

$(TESTSAN_STAMP): | $(HOSTDIR)
	rm -f $(HOSTDIR)/.testsan-on $(HOSTDIR)/.testsan-off
	touch $@

# One host object rule per source (the sources live in several dirs).
define HOST_OBJ_RULE
$(call hostObjs,$1): $1 $$(DVX_MAKEFILES) | $$(HOSTTOOL_OBJDIR)
	$$(HOSTCC) $$(HOSTCFLAGS) $$(HOSTFLAGS_$(basename $(notdir $1))) -c -o $$@ $$<
endef

$(foreach src,$(sort test_compiler.c test_compact.c test_suite.c stub/bascomp.c stub/basrun.c stub/basModuleRes.c $(CLI_SRCS)),$(eval $(call HOST_OBJ_RULE,$(src))))

# DOS command-line compiler (same STUB / noicon embed as the host build)
$(SYSTEMDIR)/BASCOMP.EXE: $(DOS_BASCOMP_OBJS) $(STUB_TARGET) noicon.bmp $(DVXRES) | $(SYSTEMDIR)
	$(CC) -o $(SYSTEMDIR)/bascomp.exe $(DOS_BASCOMP_OBJS) -lm
	$(EXE2COFF) $(SYSTEMDIR)/bascomp.exe
	cat $(CWSDSTUB) $(SYSTEMDIR)/bascomp > $@
	rm -f $(SYSTEMDIR)/bascomp $(SYSTEMDIR)/bascomp.exe
	$(DVXRES) add $@ STUB binary @$(STUB_TARGET)
	$(DVXRES) add $@ noicon icon @noicon.bmp

$(HOSTDIR) $(HOSTTOOL_OBJDIR) $(SYSTEMDIR):
	mkdir -p $@

# Runtime library DXE (exports symbols via dlregsym constructor)
$(RT_TARGET): $(RT_OBJS) | $(RT_TARGETDIR)
	$(DXE3GEN) -o $(RT_TARGETDIR)/basrt.dxe -U $(RT_OBJS)
	mv $(RT_TARGETDIR)/basrt.dxe $@

$(RT_TARGETDIR)/basrt.dep: basrt.dep | $(RT_TARGETDIR)
	sed 's/$$/\r/' $< > $@

# 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 from basdev.lib, runtime from basrt.lib)
$(APP_TARGET): $(IDE_OBJS) dvxbasic.res | $(APPDIR)
	$(DXE3GEN) -o $@ -U $(IDE_OBJS)
	$(DVXRES) build $@ dvxbasic.res


# DOS object files.  One pattern rule per source directory; header
# dependencies come from the compiler (DEPFLAGS writes a .d next to each
# object) so no hand-written list can drift out of date.
$(OBJDIR)/%.o: compiler/%.c $(DVX_MAKEFILES) | $(OBJDIR)
	$(CC) $(CFLAGS) -c -o $@ $<

$(OBJDIR)/%.o: runtime/%.c $(DVX_MAKEFILES) | $(OBJDIR)
	$(CC) $(CFLAGS) -c -o $@ $<

$(OBJDIR)/%.o: formrt/%.c $(DVX_MAKEFILES) | $(OBJDIR)
	$(CC) $(CFLAGS) -c -o $@ $<

$(OBJDIR)/%.o: ide/%.c $(DVX_MAKEFILES) | $(OBJDIR)
	$(CC) $(CFLAGS) -c -o $@ $<

$(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 $@ $<

$(OBJDIR)/%.o: $(LIBDVX)/platform/%.c $(DVX_MAKEFILES) | $(OBJDIR)
	$(CC) $(CFLAGS) -c -o $@ $<

$(OBJDIR)/%.o: $(LIBDVX)/thirdparty/%.c $(DVX_MAKEFILES) | $(OBJDIR)
	$(CC) $(CFLAGS) -c -o $@ $<

$(OBJDIR)/%.o: $(TOOLSDIR)/%.c $(DVX_MAKEFILES) | $(OBJDIR)
	$(CC) $(CFLAGS) -c -o $@ $<

$(OBJDIR)/%.o: %.c $(DVX_MAKEFILES) | $(OBJDIR)
	$(CC) $(CFLAGS) -c -o $@ $<

# Directories
$(OBJDIR):
	mkdir -p $(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) $(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


# ============================================================
# Host basrt archive (src/test/formrt suite)
# ============================================================
#
# Compiler + runtime + form runtime built with the native toolchain into
# obj/host/basrt.a, linked by the form-runtime test suite against the
# host libdvx.a and the widget .so files.  basNativeHost.c stands in for
# the i386 basNativeDos.c trampoline (Makefile object-list selection, no
# preprocessor conditionals).  SAN/COV per common.mk; must match the rest
# 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/formPrinter.c formrt/formDesign.c formrt/formCtl.c formrt/frmParser.c basdev/frmModel.c basdev/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

.PHONY: host-basrt host-basrt-clean

host-basrt: $(HOSTRT_TARGET)

$(HOSTRT_TARGET): $(HOSTRT_OBJS)
	rm -f $@
	ar rcs $@ $(HOSTRT_OBJS)

$(HOSTRT_DIR)/%.o: compiler/%.c $(DVX_MAKEFILES)
	@mkdir -p $(HOSTRT_DIR)
	$(HOSTCC) $(HOSTRT_CFLAGS) -c -o $@ $<

$(HOSTRT_DIR)/%.o: runtime/%.c $(DVX_MAKEFILES)
	@mkdir -p $(HOSTRT_DIR)
	$(HOSTCC) $(HOSTRT_CFLAGS) -c -o $@ $<

$(HOSTRT_DIR)/%.o: formrt/%.c $(DVX_MAKEFILES)
	@mkdir -p $(HOSTRT_DIR)
	$(HOSTCC) $(HOSTRT_CFLAGS) -c -o $@ $<

$(HOSTRT_DIR)/%.o: basdev/%.c $(DVX_MAKEFILES)
	@mkdir -p $(HOSTRT_DIR)
	$(HOSTCC) $(HOSTRT_CFLAGS) -c -o $@ $<

host-basrt-clean:
	rm -rf $(HOSTRT_DIR) $(HOSTRT_TARGET)

-include $(wildcard $(OBJDIR)/*.d) $(HOSTTOOL_OBJS:.o=.d) $(HOSTRT_OBJS:.o=.d)
