# SplitBit Programs Makefile
# Anachronaut
#
# Builds every SplitBit program into build/, and keeps track of which libraries
# each one includes so that editing a library reassembles whatever depends on it.
#
#   make            Assemble everything.
#   make clean      Throw away build/.
#   make run-hello  Assemble and run one program.

ASM ?= ../Assembler
EMU ?= ../SplitBit
BUILD ?= build

# Libraries are included by bare name, so the assembler is told where to find them.
# CosmOS owns the filesystem library and the service names, so it is a place to look too.
INCLUDES = -I Libraries -I CosmOS/Source

# The programs worth building. Every one lives in a directory that says what kind it is:
# Examples/ is what you read to learn, Loader/ is the standalone loader CosmOS grew out of,
# CosmOS/ is the system. Files in Libraries/ are left out because they have no entry point
# of their own, and the ones in testPrograms/ are covered by 'make test' in the parent
# directory.
PROGRAMS = \
	CosmOS/Source/cosmos.asm \
	Examples/hello.asm \
	Examples/printHello.asm \
	Examples/inputTest.asm \
	Examples/replCalculator.asm \
	Examples/Fibonacci/8bitFibonacci.asm \
	Examples/Fibonacci/16bitFibonacci.asm \
	Examples/Fibonacci/32bitFibonacci.asm \
	Examples/primeSieve/8bitSieve.asm \
	Examples/primeSieve/16bitSegmentedSieve.asm \
	Examples/primeSieve/16bitSegmentedSieveModern.asm \
	Examples/gameOfLife/16x16Life.asm \
	Examples/gameOfLife/16x16LifeModern.asm \
	Loader/loader.asm \
	Loader/loadable.asm

BINARIES = $(PROGRAMS:%.asm=$(BUILD)/%.bin)
DEPENDENCIES = $(BINARIES:.bin=.d)

all: $(BINARIES)

# -M writes out which source files went into the binary, in the form of a make rule.
$(BUILD)/%.bin: %.asm
	@mkdir -p $(@D)
	$(ASM) $(INCLUDES) -M $(@:.bin=.d) -o $@ $<

# Assemble and run a single program, as in 'make run-hello'.
#
# THE NAME IS THE PROGRAM'S, NOT ITS PATH. This used to be a pattern rule against
# $(BUILD)/%.bin, which worked while every program sat at the top of this directory and
# stopped working the moment they were filed into Examples/ - 'make run-hello' had nothing
# to match. Somebody trying the one command the header advertises should not be the way
# that is discovered, so the name is now looked up among the programs instead.
run-%:
	@target=`echo $(BINARIES) | tr ' ' '\n' | grep -E "(^|/)$*\.bin$$" | head -1`; \
	if [ -z "$$target" ]; then \
		echo "There is no program called '$*'. What there is:"; \
		echo $(PROGRAMS) | tr ' ' '\n' | sed 's|.*/||;s|\.asm$$||;s|^|  |'; \
		exit 1; \
	fi; \
	$(MAKE) --no-print-directory "$$target" && $(EMU) "$$target"

# ---- CosmOS ----
#
#   make cosmos       Assemble the system and everything it can load.
#   make cosmos-disk  ... and put the loadable programs on a disk image.
#   make run-cosmos   ... and boot the machine with that disk in the drive.
#   make run-voyager  ... and boot the machine that has a screen instead of a terminal.
#
# Programs in Apps/ say where they live with #Base, so the assembler writes them out as
# loadable programs rather than as boot images. They are named .sbx to keep that
# difference visible: a .bin is something the machine boots, a .sbx is something a
# running system loads.

DISKTOOL ?= ../SplitDisk
COSMOS = $(BUILD)/CosmOS/Source/cosmos.bin
APPS = $(patsubst CosmOS/Apps/%.asm,$(BUILD)/CosmOS/Apps/%.sbx,$(wildcard CosmOS/Apps/*.asm))
COSMOS_DISK = $(BUILD)/cosmos.img

# ---- What starts the machine ----
#
# Stage two goes into a boot slot as RAW BYTES: stage one reads blocks into Program Memory
# and jumps to the first one, so a sixteen byte header would be sixteen bytes of nonsense
# executed first. Its Data Segment travels with it and it copies that down itself.
#
# Stage one is not here at all. It is the ROM, built into the emulator by the top level
# makefile from the same source, which is what makes it the one part of this that a disk
# cannot replace.
STAGE2 = $(BUILD)/Boot/stage2.raw
DEPENDENCIES += $(BUILD)/Boot/stage2.d

$(STAGE2): Boot/stage2.asm
	@mkdir -p $(@D)
	$(ASM) $(INCLUDES) -M $(BUILD)/Boot/stage2.d -o $(BUILD)/Boot/stage2.sbx $<
	tail -c +17 $(BUILD)/Boot/stage2.sbx > $@
DEPENDENCIES += $(APPS:.sbx=.d)

$(BUILD)/CosmOS/Apps/%.sbx: CosmOS/Apps/%.asm
	@mkdir -p $(@D)
	$(ASM) $(INCLUDES) -M $(@:.sbx=.d) -o $@ $<

# The assembler that runs on the machine. It is not in Apps/ because it is not one file:
# it has a directory of its own, the way the C assembler does. Its own pieces are found
# beside it without being told, since an include is looked for next to the file that asked
# for it before anywhere else; only services.asm needs the include path.
NATIVE_ASM = $(BUILD)/CosmOS/Assembler/Asm.sbx
DEPENDENCIES += $(NATIVE_ASM:.sbx=.d)

$(NATIVE_ASM): CosmOS/Assembler/Asm.asm
	@mkdir -p $(@D)
	$(ASM) $(INCLUDES) -M $(@:.sbx=.d) -o $@ $<

cosmos: $(COSMOS) $(APPS) $(NATIVE_ASM)

# Made from scratch every time, so that what is on it is what is in Apps/ now and not
# also whatever used to be.
#
# TWENTY FOUR DIRECTORY BLOCKS, WHICH IS ONE HUNDRED AND NINETY TWO NAMES. It was eight,
# and that is sixty four, of which thirty nine were already spoken for. The two ceilings a
# disk has were nowhere near each other: at the average file on here, twenty six blocks,
# sixty four names run out with the disk forty one per cent full. Names were going to be
# gone long before space was.
#
# A directory block is 256 bytes and holds eight entries, so the difference costs sixteen
# blocks of four thousand and ninety six - three tenths of one per cent - to buy a hundred
# and twenty eight more names. The superblock has carried this number per disk since the
# format was written, so nothing but this line knows what it is.
$(COSMOS_DISK): $(APPS) $(NATIVE_ASM) $(COSMOS) $(STAGE2) testPrograms/stringKeyword.asm \
                $(wildcard CosmOS/Apps/*.asm) \
                $(wildcard CosmOS/Source/*.asm) $(wildcard CosmOS/Assembler/*.asm)
	@mkdir -p $(@D)
	rm -f $@
	@# A BOOT AREA, so that this is a disk the machine can start itself from rather than
	@# one it has to be handed. Forty blocks a slot and two slots: stage two is about
	@# eight thousand bytes, and the second slot is what makes replacing it survivable,
	@# since raw blocks have no name and so nothing to rename.
	@# ---- Room for the whole source tree ----
	@#
	@# Sixteen thousand blocks is four megabytes, which is absurd for a machine with 128K
	@# of memory and exactly right for a disk: the sources alone are 2,850 blocks and the
	@# point of mirroring them is that nobody has to think about it again when they add
	@# one. A hundred and twenty-eight directory blocks is 1,024 entries against the 149
	@# the tree has now, for the same reason - it was 24, which is 192, and the mirror
	@# filled it on its first run.
	$(DISKTOOL) format $@ 16384 128 40
	$(DISKTOOL) boot $@ $(STAGE2) 0
	@# THREE DIRECTORIES, WHICH IS WHAT A CLEAN INSTALL LOOKS LIKE: what you run, what you
	@# assemble, and what those include. It was thirty nine files in one list with
	@# cosmos.asm sitting between fileStream.asm and sbfs.asm.
	@#
	@# The split is by ROLE rather than by which directory the host keeps them in. /Source
	@# holds the things you name to the assembler and /Lib the things they pull in, which is
	@# a distinction the host makes with -I and the machine now makes with a search path of
	@# its own: an include is looked for beside you and then in /Lib. Without that, every
	@# source that calls a service would have to sit in the same directory as services.asm
	@# and there would be nothing to organise.
	$(DISKTOOL) mkdir $@ /Apps
	$(DISKTOOL) mkdir $@ /Source
	$(DISKTOOL) mkdir $@ /Lib
	$(DISKTOOL) mkdir $@ /System
	$(DISKTOOL) mkdir $@ /System/Boot
	@# The system itself, as a file, which is the whole of what boot.cfg chooses between.
	@# No boot.cfg is written: stage two falls back to this name when there is none, and a
	@# clean install having nothing to configure is the right default.
	$(DISKTOOL) put $@ $(COSMOS) /System/Boot/cosmos.bin
	@# What you run. /Apps is the second place the shell looks when a word it does not know
	@# turns out to be a program, so anything in here starts by name from anywhere.
	@for app in $(APPS); do \
		$(DISKTOOL) put $@ $$app /Apps/`basename $$app` >/dev/null || exit 1; done
	$(DISKTOOL) put $@ $(NATIVE_ASM) /Apps/Asm.sbx
	@# ---- What you assemble: all of it ----
	@#
	@# MIRRORED RATHER THAN LISTED. A list in a makefile goes stale the moment somebody
	@# adds a program and forgets to name it here, and what they forgot is invisible until
	@# they go looking for it on the machine. Now putting a file where the others live is
	@# the whole of putting it on the disk.
	@#
	@# That matters most for the things nobody thought worth building an .sbx of. A demo
	@# that is not interesting enough to ship as a binary is still worth having the source
	@# of, because somebody curious can assemble it on the machine itself:
	@#
	@#   > cd /Source/Examples
	@#   /Source/Examples> Asm colours.asm
	@#
	@# build is left behind, because what a project builds is not what it wrote. Anything
	@# with a name longer than a directory entry holds is refused rather than skipped: a
	@# disk quietly missing a file is the failure a mirror exists to prevent.
	$(DISKTOOL) mirror $@ . /Source build
	@for f in CosmOS/Source/console.asm CosmOS/Source/fileStream.asm \
	          CosmOS/Source/sbfs.asm CosmOS/Source/services.asm CosmOS/Source/text.asm \
	          CosmOS/Source/config.asm \
	          CosmOS/Assembler/classify.asm CosmOS/Assembler/labels.asm \
	          CosmOS/Assembler/numbers.asm CosmOS/Assembler/scratch.asm \
	          CosmOS/Assembler/source.asm CosmOS/Assembler/table.asm \
	          CosmOS/Assembler/token.asm CosmOS/Assembler/vectors.asm; do \
		$(DISKTOOL) put $@ $$f /Lib/`basename $$f` >/dev/null || exit 1; done

# The system as well as the disk. Building only the image leaves whatever cosmos.bin was
# there before, or none at all, and then the disk is booted with a system that does not
# match the programs on it.
cosmos-disk: $(COSMOS) $(COSMOS_DISK)

# THE MACHINE STARTS ITSELF. No image is named, so the emulator shadows its ROM into
# Program Memory and that reads the disk for everything else - a boot slot, then a loader,
# then whatever /System/Boot/boot.cfg names, or cosmos.bin when it names nothing.
run-cosmos: $(COSMOS_DISK)
	$(EMU) --disk $(COSMOS_DISK)

# The same disk with the system handed over directly instead, which is what a debugger
# does: memory is placed from outside and nothing on the disk is consulted about it. Useful
# when the thing being debugged is the boot chain itself, since it skips the boot chain.
run-cosmos-direct: $(COSMOS) $(COSMOS_DISK)
	$(EMU) --disk $(COSMOS_DISK) $(COSMOS)

# ---- The same disk, on the machine with a screen ----
#
# Voyager rather than SplitBit, which is the only difference: same disk, same system, same
# programs, presented through a window instead of a terminal.
#
# IT DEPENDS ON THE DISK, and that matters more than it looks. What is on a disk is whatever
# was built when the disk was made, so a machine whose console has changed will happily boot
# an image full of programs written for the old one - and they will draw whatever the old
# way now means. Making the disk a dependency of running it is what stops that being a
# puzzle.
run-voyager: $(COSMOS_DISK)
	../Voyager --disk $(COSMOS_DISK)

run-voyager-direct: $(COSMOS) $(COSMOS_DISK)
	../Voyager --disk $(COSMOS_DISK) $(COSMOS)

clean:
	rm -rf $(BUILD)

# Pull in the dependency rules written by -M above, so that touching a library
# reassembles every program that includes it.
-include $(DEPENDENCIES)

.PHONY: all clean cosmos cosmos-disk run-cosmos run-cosmos-direct run-voyager run-voyager-direct
