# a demo is a name that can be make'd into an a.out,
# that designates a collection of compile units,
# each with a corresponding .src.c + .o file pair
SOURCES_IN_DEMO=$(wildcard $1/*.src.c)
OBJECTS_IN_DEMO=$(patsubst %.src.c,%.o,$(call SOURCES_IN_DEMO,$1))
ALL_OBJECTS=$(foreach demo,$(DEMOS),$(call OBJECTS_IN_DEMO,$(demo)))

# each recursive subdir gives a demo
# a demo name may have slashes in middle, but not at start or end (foo, foo/bar)
DIRS_REC=$(shell find ./* -type d)
DEMOS_FROM_DIRS=$(patsubst ./%,%,$1)
DEMO_OF_FILE=$(patsubst %/,%,$(dir $1))
DEMOS=$(call DEMOS_FROM_DIRS,$(DIRS_REC))

# demos of working code build under target `all`
# default assumption = demo is working
# demos named err-% recursively opt out
ERR_DIRPATS=err-%
DEMOS_NONERR=$(filter-out $(ERR_DIRPATS),$(DEMOS))
DEMO_EXECS_NONERR=$(DEMOS_NONERR:=/a.out)

# a recourse is a designated sub-demo of an error case
# that is expected to build successfully
RECOURSE_DIRPATS=%/recourse-classic
DEMOS_RECOURSE=$(filter $(RECOURSE_DIRPATS),$(DEMOS))
DEMO_EXECS_RECOURSE=$(DEMOS_RECOURSE:=/a.out)

# non-err and recourse demos try to build by default
ALL=$(DEMO_EXECS_NONERR) $(DEMO_EXECS_RECOURSE)

# a shred is a *.c or *.h file generated from a *.src.c
SHRED_EXTS=.impl.c .defn.h .tdcl.h
SHREDS_OF_FILE=$(foreach st,$(SHRED_EXTS),$(1:=$(st)))
SHREDS_IN_DEMO=$(foreach src,$(call SOURCES_IN_DEMO,$1),$(call SHREDS_OF_FILE,$(src:.src.c=)))
SHREDS_IN_DEMO_OF_FILE=$(call SHREDS_IN_DEMO,$(call DEMO_OF_FILE,$1))

# a 'classic' demo builds without shredding
CLASSIC_DIRPATS=%-classic
DEMOS_CLASSIC=$(filter $(CLASSIC_DIRPATS),$(DEMOS))
DEMOS_NONSHRED=$(DEMOS_CLASSIC)
OBJECTS_NONSHRED=$(foreach demo,$(DEMOS_NONSHRED),$(call OBJECTS_IN_DEMO,$(demo)))

# everything else builds via shredding
DEMOS_SHRED=$(filter-out $(RECOURSE_DIRPATS),$(DEMOS))
OBJECTS_SHRED=$(foreach demo,$(DEMOS_SHRED),$(call OBJECTS_IN_DEMO,$(demo)))
ALL_SHREDS=$(foreach demo,$(DEMOS),$(call SHREDS_IN_DEMO,$(demo)))


CC=gcc
CPPP=./cfa-cppp
CQFLAGS=-Wall -Wextra -Werror


.SECONDEXPANSION: # enable dynamic prereqs from $$
.SUFFIXES: # disable traditional rules like make .o from .c by calling $(CC)


all: $(ALL)

# troubleshooting, e.g. `make echo_DEMOS` runs `echo $(DEMOS)`
echo_% :
	@echo '$($(@:echo_%=%))'

#
# demo
#

%/a.out : DEMO=$(@:/a.out=)
%/a.out : OBJECTS=$(call OBJECTS_IN_DEMO,$(DEMO))
%/a.out : $$(OBJECTS)
	$(CC) $(OBJECTS) -o $@ $(CFLAGS) $(CQFLAGS)

$(DEMOS) : $$@/a.out

#
# object
#

# object of regular, shredded, demo
# each CU depends on all other CUs' shreds
# conservative for rebuild (obviates -MMD), but helps do initial build
$(OBJECTS_SHRED) : CC_SRC=$(@:.o=.impl.c)
$(OBJECTS_SHRED) : SHREDS=$(call SHREDS_IN_DEMO_OF_FILE,$@)
$(OBJECTS_SHRED) : $$(SHREDS)

# of nonshredded demo
$(OBJECTS_NONSHRED) : CC_SRC=$(@:.o=.src.c)
$(OBJECTS_NONSHRED) : CQFLAGS+= -MMD

-include $(OBJECTS_NONSHRED:.o=.d)

$(ALL_OBJECTS) :
	$(CC) $(CC_SRC) -c -o $@ $(CFLAGS) $(CQFLAGS)

#
# shred
#

define SINGLE_SHREDEXT_RULE
%$1: %.src.c $(CPPP)
	$(CPPP) $$@
endef

$(foreach sfx,$(call SHREDS_OF_FILE,@),$(eval $(call SINGLE_SHREDEXT_RULE,$(sfx:@%=%))))

#
# clean, misc
#

# 2-line body is significant; \n ending 1st line is literal
define newline


endef

CLEANPATS=$(call SHREDS_OF_FILE,*) *.o *.d a.out
clean:
	$(foreach p,$(CLEANPATS),find . -name '$p' -type f -print -delete$(newline))

.PRECIOUS: %.tdcl.h %.defn.h %.impl.c
.PHONY: all echo_% clean
