# 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 for DJGPP cross-compilation
#
# Builds:
#   basrt.lib    -- BASIC runtime library (VM + values + dlregsym init)
#   dvxbasic.app -- BASIC IDE (compiler + UI, links against basrt.lib)
#
# The runtime is a separate library so compiled BASIC apps can use
# it without including the compiler. The library's constructor calls
# dlregsym() to register its exports, making them available to DXEs
# loaded later (apps, widgets, etc.).

DJGPP_PREFIX  = $(HOME)/djgpp/djgpp
CC            = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
DXE3GEN       = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
WARNFLAGS     = -Wall -Wextra -Werror -Wno-type-limits -Wno-sign-compare -Wno-format-truncation
DVX_INCLUDES  = -I../../../libs/kpunch/libdvx -I../../../libs/kpunch/libdvx/platform -I../../../libs/kpunch/libdvx/thirdparty -I.
CFLAGS        = -O2 $(WARNFLAGS) -march=i486 -mtune=i586 $(DVX_INCLUDES) -I../../../widgets/kpunch -I../../../libs/kpunch/dvxshell -I../../../libs/kpunch/libtasks -MMD -MP

OBJDIR  = ../../../../obj/dvxbasic
LIBSDIR = ../../../../bin/libs
APPDIR  = ../../../../bin/apps/kpunch/dvxbasic
HOSTDIR = ../../../../bin/host
DVXRES  = $(HOSTDIR)/dvxres

# Runtime library objects (VM + values + form runtime + serialization)
RT_OBJS = $(OBJDIR)/vm.o $(OBJDIR)/values.o $(OBJDIR)/formrt.o $(OBJDIR)/frmParser.o $(OBJDIR)/basNativeDos.o $(OBJDIR)/serialize.o
RT_TARGETDIR = $(LIBSDIR)/kpunch/basrt
RT_TARGET    = $(RT_TARGETDIR)/basrt.lib

# Compiler objects (only needed by the IDE)
COMP_OBJS = $(OBJDIR)/lexer.o $(OBJDIR)/parser.o $(OBJDIR)/codegen.o $(OBJDIR)/symtab.o $(OBJDIR)/strip.o $(OBJDIR)/obfuscate.o $(OBJDIR)/compact.o $(OBJDIR)/basBuild.o

# IDE app objects
IDE_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideMenuEditor.o $(OBJDIR)/ideProject.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o
APP_OBJS = $(IDE_OBJS)
APP_TARGET = $(APPDIR)/dvxbasic.app

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

# Native test programs (host gcc, not cross-compiled).
# TESTSAN=1 rebuilds the harnesses under ASan/UBSan (make tests TESTSAN=1).
HOSTCC     = gcc
HOSTCFLAGS = -O2 $(WARNFLAGS) -Wno-stringop-truncation -D_GNU_SOURCE $(DVX_INCLUDES)
ifeq ($(TESTSAN),1)
HOSTCFLAGS += -g -fno-omit-frame-pointer -fsanitize=address,undefined
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
HOSTOBJDIR      = ../../../../obj/hostcov
HOSTCFLAGS     += -g --coverage
HOSTRT_COVFLAGS = --coverage
endif

# Every header a host harness can pull in; listed as a prerequisite so a
# header edit rebuilds the harness instead of leaving a stale binary.
HEADERS    = $(wildcard *.h compiler/*.h runtime/*.h formrt/*.h)

TEST_COMPILER = $(HOSTDIR)/test_compiler
TEST_COMPACT  = $(HOSTDIR)/test_compact
TEST_SUITE    = $(HOSTDIR)/test_suite

STB_DS_IMPL        = ../../../libs/kpunch/libdvx/thirdparty/stb_ds_impl.c
PLATFORM_UTIL     = ../../../libs/kpunch/libdvx/platform/dvxPlatformUtil.c
TEST_COMPILER_SRCS = test_compiler.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c runtime/serialize.c $(PLATFORM_UTIL) $(STB_DS_IMPL)
TEST_COMPACT_SRCS  = test_compact.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c compiler/strip.c compiler/compact.c runtime/vm.c runtime/values.c runtime/serialize.c $(PLATFORM_UTIL) $(STB_DS_IMPL)
TEST_SUITE_SRCS    = test_suite.c basBuild.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c compiler/strip.c compiler/obfuscate.c compiler/compact.c runtime/vm.c runtime/values.c runtime/serialize.c ../../../libs/kpunch/libdvx/dvxPrefs.c ../../../libs/kpunch/libdvx/dvxResource.c $(PLATFORM_UTIL) $(STB_DS_IMPL)

# Command-line compiler (host tool)
BASCOMP_SRCS   = stub/bascomp.c basBuild.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c compiler/strip.c compiler/obfuscate.c compiler/compact.c runtime/vm.c runtime/values.c runtime/serialize.c ../../../libs/kpunch/libdvx/dvxPrefs.c ../../../libs/kpunch/libdvx/dvxResource.c $(PLATFORM_UTIL) $(STB_DS_IMPL)
BASCOMP_TARGET = $(HOSTDIR)/bascomp

# Headless host runner (host tool; runs .app/.bas/.dbp with PRINT on stdout)
BASRUN_SRCS   = stub/basrun.c basBuild.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c compiler/strip.c compiler/obfuscate.c compiler/compact.c runtime/vm.c runtime/values.c runtime/serialize.c ../../../libs/kpunch/libdvx/dvxPrefs.c ../../../libs/kpunch/libdvx/dvxResource.c $(PLATFORM_UTIL) $(STB_DS_IMPL)
BASRUN_TARGET = $(HOSTDIR)/basrun

# Host-side BASIC test directory (basrun fixtures, fuzzers, compaction diff)
BASIC_TESTDIR = ../../../test/basic

# DOS command-line compiler
DOSCC       = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
DOSCFLAGS   = -O2 $(WARNFLAGS) -march=i486 -mtune=i586 $(DVX_INCLUDES)
EXE2COFF    = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/exe2coff
CWSDSTUB    = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/CWSDSTUB.EXE
SYSTEMDIR   = ../../../../bin/system

.PHONY: all clean tests

all: $(RT_TARGET) $(RT_TARGETDIR)/basrt.dep $(STUB_TARGET) $(APP_TARGET) $(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 harness binaries depend on the sanitizer stamp so switching TESTSAN
# on or off forces a rebuild instead of running a stale binary.
TESTSAN_STAMP = $(HOSTDIR)/.testsan-$(if $(filter 1,$(TESTSAN)),on,off)

# 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)
	$(TEST_COMPILER)
	$(TEST_COMPACT)
	$(TEST_SUITE)
	$(MAKE) -C $(BASIC_TESTDIR) test TESTSAN=$(TESTSAN)

$(BASRUN_TARGET): $(BASRUN_SRCS) $(HEADERS) $(TESTSAN_STAMP) | $(HOSTDIR)
	$(HOSTCC) $(HOSTCFLAGS) -o $@ $(BASRUN_SRCS) -lm

$(TEST_COMPILER): $(TEST_COMPILER_SRCS) $(HEADERS) $(TESTSAN_STAMP) | $(HOSTDIR)
	$(HOSTCC) $(HOSTCFLAGS) -o $@ $(TEST_COMPILER_SRCS) -lm

$(TEST_SUITE): $(TEST_SUITE_SRCS) $(HEADERS) $(TESTSAN_STAMP) | $(HOSTDIR)
	$(HOSTCC) $(HOSTCFLAGS) -I../../../tools -DBAS_TEST_SRC_DIR='"$(abspath .)"' -o $@ $(TEST_SUITE_SRCS) -lm

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

$(TEST_COMPACT): $(TEST_COMPACT_SRCS) $(HEADERS) $(TESTSAN_STAMP) | $(HOSTDIR)
	$(HOSTCC) $(HOSTCFLAGS) -o $@ $(TEST_COMPACT_SRCS) -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_SRCS) $(HEADERS) ../../../tools/dvxResWrite.h $(STUB_TARGET) noicon.bmp $(DVXRES) | $(HOSTDIR)
	$(HOSTCC) $(HOSTCFLAGS) -DBASCOMP_STANDALONE -I../../../tools -o $@ $(BASCOMP_SRCS) -lm
	$(DVXRES) add $@ STUB binary @$(STUB_TARGET)
	$(DVXRES) add $@ noicon icon @noicon.bmp

# DOS command-line compiler (same STUB / noicon embed as the host build)
$(SYSTEMDIR)/BASCOMP.EXE: $(BASCOMP_SRCS) $(HEADERS) ../../../tools/dvxResWrite.h $(STUB_TARGET) noicon.bmp $(DVXRES) | $(SYSTEMDIR)
	$(DOSCC) $(DOSCFLAGS) -DBASCOMP_STANDALONE -I../../../tools -o $(SYSTEMDIR)/bascomp.exe $(BASCOMP_SRCS) -lm
	$(EXE2COFF) $(SYSTEMDIR)/bascomp.exe
	cat $(CWSDSTUB) $(SYSTEMDIR)/bascomp > $@
	rm -f $(SYSTEMDIR)/bascomp $(SYSTEMDIR)/bascomp.exe
	$(DVXRES) add $@ STUB binary @$(STUB_TARGET)
	$(DVXRES) add $@ noicon icon @noicon.bmp

$(HOSTDIR):
	mkdir -p $(HOSTDIR)

$(SYSTEMDIR):
	mkdir -p $(SYSTEMDIR)

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

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

# Standalone stub DXE (embedded as resource in IDE app)
$(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) $(APP_OBJS) $(STUB_TARGET) dvxbasic.res | $(APPDIR)
	$(DXE3GEN) -o $@ -U $(COMP_OBJS) $(APP_OBJS)
	$(DVXRES) build $@ dvxbasic.res
	$(DVXRES) add $@ STUB binary @$(STUB_TARGET)


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

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

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

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

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

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

-include $(wildcard $(OBJDIR)/*.d)

# Directories
$(OBJDIR):
	mkdir -p $(OBJDIR)

$(LIBSDIR):
	mkdir -p $(LIBSDIR)

$(RT_TARGETDIR):
	mkdir -p $(RT_TARGETDIR)

$(APPDIR):
	mkdir -p $(APPDIR)

clean:
	rm -rf $(RT_OBJS) $(COMP_OBJS) $(IDE_OBJS) $(STUB_OBJS) $(OBJDIR)/*.d $(RT_TARGET) $(APP_TARGET) $(STUB_TARGET) $(BASCOMP_TARGET) $(SYSTEMDIR)/BASCOMP.EXE $(RT_TARGETDIR)/basrt.dep $(RT_TARGETDIR)
	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) -- T5 block, keep last
# ============================================================
#
# 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 must match the rest of the host build.
SAN            ?= 1
HOSTOBJDIR     ?= ../../../../obj/host
HOSTRT_DIR      = $(HOSTOBJDIR)/basrt
HOSTRT_TARGET   = $(HOSTOBJDIR)/basrt.a
HOSTRT_SRCS     = compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c compiler/strip.c compiler/obfuscate.c compiler/compact.c runtime/vm.c runtime/values.c runtime/serialize.c formrt/formrt.c formrt/frmParser.c formrt/basNativeHost.c
HOSTRT_OBJS     = $(patsubst %.c,$(HOSTRT_DIR)/%.o,$(notdir $(HOSTRT_SRCS)))
HOSTRT_CFLAGS   = -O1 -g $(WARNFLAGS) -Wno-stringop-truncation -D_GNU_SOURCE $(DVX_INCLUDES) -I../../../widgets/kpunch -I../../../libs/kpunch/dvxshell -I../../../libs/kpunch/libtasks $(HOSTRT_COVFLAGS)
ifeq ($(SAN),1)
HOSTRT_CFLAGS  += -fsanitize=address,undefined -fno-omit-frame-pointer
endif

.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 $(HEADERS)
	@mkdir -p $(HOSTRT_DIR)
	$(HOSTCC) $(HOSTRT_CFLAGS) -c -o $@ $<

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

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

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