Ctrl K
ComunidadGuías…

Makefile

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# OS detection and portable commands
ifeq ($(OS),Windows_NT)
    EXE = .exe
    RM = rm -rf
    MKDIR = mkdir -p
    CP = cp -af
    LN = ln -sf
    INSTALL = install
else
    EXE =
    RM = rm -rf
    MKDIR = mkdir -p
    CP = cp -af
    LN = ln -sf
    INSTALL = install
endif

# Compiler configuration
# Default: gcc
# To build with clang: make CC=clang
# To build with zig:   make CC="zig cc"
GIT_DESC := $(shell git describe --tags --always --dirty 2>/dev/null)
ifneq ($(GIT_DESC),)
  GIT_VERSION := $(GIT_DESC)
else
  GIT_VERSION := $(shell cat .version 2>/dev/null || echo "unknown")
endif
# Re-check for CI overrides
ifneq ($(GITHUB_RUN_NUMBER),)
  GIT_VERSION := $(GIT_VERSION)+ci.$(GITHUB_RUN_NUMBER)
endif
SHAREDIR ?= /usr/local/share/zenc
DEFINES = -DZEN_VERSION=\"$(GIT_VERSION)\" -DZEN_SHARE_DIR=\"$(SHAREDIR)\" -DZC_ALLOW_INTERNAL
WERROR ?= 1
# TCC does not support -MMD -MP, may not search /usr/local/include, and maxes out at C11
ifeq ($(findstring tcc,$(CC)),tcc)
    DEPFLAGS =
    TCC_EXTRA = -I/usr/local/include -L/usr/local/lib
    override C_STD := gnu11
else
    DEPFLAGS = -MMD -MP
    TCC_EXTRA =
endif
# Base flags shared by all compilers
# Override C_STD to compile with a different standard, e.g.: make C_STD=gnu11
C_STD ?= gnu23
C_STD_SUPPORTED := $(shell echo "int x;" | $(CC) -std=$(C_STD) -x c - -c -o /dev/null 2>/dev/null && echo 1)
ifeq ($(C_STD_SUPPORTED),)
$(warning Compiler does not support -std=$(C_STD), falling back to gnu11)
override C_STD := gnu11
endif
# Probe for constexpr support (GCC 14+ supports it, Clang's C23 is partial)
CONSTEXPR_SUPPORTED := $(shell echo "constexpr int x = 42; int main(void){return x;}" | $(CC) -std=$(C_STD) -x c - -c -o /dev/null 2>/dev/null && echo 1)
ifneq ($(CONSTEXPR_SUPPORTED),)
DEFINES += -DHAS_CONSTEXPR
endif
# Warning for returning the address of a local: GCC (and clang >= 14) call it
# -Wreturn-local-addr, older clang only -Wreturn-stack-address. An unknown warning
# option is an error under -Werror, so use the name the compiler accepts.
RETURN_ADDR_WARN := $(shell echo "int x;" | $(CC) -Werror -Wreturn-local-addr -x c - -c -o /dev/null 2>/dev/null && echo -Wreturn-local-addr || (echo "int x;" | $(CC) -Werror -Wreturn-stack-address -x c - -c -o /dev/null 2>/dev/null && echo -Wreturn-stack-address))
CFLAGS = -std=$(C_STD) -g -Wall -Wextra -Wshadow -Wformat=2 -Wmissing-prototypes -Wstrict-prototypes -Wnull-dereference -Wundef -Wfloat-equal -Wmissing-field-initializers -Wsign-compare -Wtype-limits -Wuninitialized -Wdouble-promotion -Wtautological-compare -Wshift-negative-value -Wdangling-else $(RETURN_ADDR_WARN) -Wconversion -Wno-float-conversion -Wswitch-default -Wvla -Wimplicit-fallthrough -Wredundant-decls -Wcast-align -Wpacked -Wdisabled-optimization -fstack-protector-strong $(DEPFLAGS) $(TCC_EXTRA) $(if $(filter 1,$(WERROR)),-Werror -Wno-error=sign-conversion,) -I./src -I./src/ast -I./src/parser -I./src/codegen -I./plugins -I./src/zen -I./src/utils -I./src/lexer -I./src/analysis -I./src/lsp -I./src/diagnostics $(DEFINES)

# Detect Clang by macro (works even when CC=cc on macOS, or CC=clang on Linux)
# Uses recursive = so it re-evaluates with target-specific CC overrides (e.g. msan: CC=clang)
# Fast path: skip shell probe when $(CC) explicitly contains "clang"
CC_IS_CLANG = $(if $(findstring clang,$(CC)),1,$(shell echo 'int x = __clang__;' | $(CC) -x c - -c -o /dev/null 2>/dev/null && echo 1 || echo 0))

# Flags only GCC supports (filtered out when compiling with clang)
GCC_WARN_FLAGS = -Wduplicated-cond -Wlogical-op -Wformat-signedness -Wunsafe-loop-optimizations -Wsuggest-attribute=noreturn -Wsuggest-attribute=const

# Clang-specific flags (added back when forcing clang for e.g. fuzz targets)
CLANG_WARN_FLAGS = -Wno-format-nonliteral -Wassign-enum -Wcomma -Wsometimes-uninitialized -Wloop-analysis -Wsizeof-array-div

# Deferred $(if ...) evaluated at CFLAGS expansion time (respects target-specific CC overrides)
CFLAGS += $(if $(filter 1,$(CC_IS_CLANG)),$(CLANG_WARN_FLAGS),$(GCC_WARN_FLAGS))

# TCC only supports a subset of -W flags; build a simplified CFLAGS to avoid unknown-flag errors
ifeq ($(findstring tcc,$(CC)),tcc)
TCC_BASE = -std=$(C_STD) -Wall -Wextra -Werror -g -DZC_ALLOW_INTERNAL -DZC_HAS_JIT
TCC_DEFS = $(filter -DZC_% -DZEN_% -DHAS_%,$(DEFINES))
TCC_INCS = -I/usr/local/include -I./src -I./src/ast -I./src/parser -I./src/codegen -I./plugins -I./src/zen -I./src/utils -I./src/lexer -I./src/analysis -I./src/lsp -I./src/diagnostics
CFLAGS = $(TCC_BASE) $(TCC_DEFS) $(TCC_INCS)
endif

# Toggle plugins
ifeq ($(NO_PLUGINS), 1)
    CFLAGS += -DZC_NO_PLUGINS
    PLUGINS =
    LIBS = -lm -lpthread
else
    LIBS = -lm -lpthread -ldl
endif

# The REPL's JIT needs libtcc. Unless ZC_HAS_JIT is given (1 forces it, 0 turns
# it off), it is enabled only when a small program using libtcc compiles and links.
ifeq ($(origin ZC_HAS_JIT),undefined)
    ifeq ($(findstring tcc,$(CC)),tcc)
        ZC_HAS_JIT := 1
    else
        TCC_PROBE_HASH := \#
        TCC_PROBE_DIRS := $(if $(wildcard /usr/local/lib/libtcc.a),-I/usr/local/include -L/usr/local/lib)
        ZC_HAS_JIT := $(shell printf '$(TCC_PROBE_HASH)include <libtcc.h>\nint main(void) { tcc_delete(tcc_new()); return 0; }\n' | $(CC) -x c - -o /dev/null $(TCC_PROBE_DIRS) -ltcc >/dev/null 2>&1 && echo 1 || echo 0)
    endif
endif
ZC_RUN ?= ./zc
ifeq ($(ZC_HAS_JIT), 1)
    CFLAGS += -DZC_HAS_JIT
endif

TARGET = zc$(EXE)
# Standalone tools built alongside the compiler (Unix-style separation). zc-lsp and zc-format
# need the LSP sources and zc-repl the REPL ones, so ZC_LSP=0 / ZC_REPL=0 leave them out.
ALL_TOOLS = zc-lsp zc-repl zc-doc zc-format
TOOLS = $(if $(filter 1,$(ZC_LSP)),zc-lsp zc-format) $(if $(filter 1,$(ZC_REPL)),zc-repl) zc-doc
ifeq ($(OS),Windows_NT)
    LIBS = -lws2_32
    ifeq ($(ZC_HAS_JIT), 1)
        LIBS += -ltcc
    endif
else
    ifeq ($(ZC_HAS_JIT), 1)
        LIBS += -ltcc
        # afl-clang-fast/lld don't search /usr/local/lib by default
        ifneq ($(wildcard /usr/local/lib/libtcc.a),)
            LIBS += -L/usr/local/lib
        endif
    endif
endif

 # Feature selection (default: all enabled)
 # Override with ZC_LSP=0, ZC_REPL=0, etc. for smaller builds.
ZC_LSP ?= 1
ZC_REPL ?= 1
ZC_PLUGINS ?= 1
ZC_BACKENDS ?= 1

DEFINES += -DZC_HAS_PLUGINS=$(ZC_PLUGINS)
DEFINES += -DZC_HAS_CPP_BACKEND=$(ZC_BACKENDS)
DEFINES += -DZC_HAS_CUDA_BACKEND=$(ZC_BACKENDS)
DEFINES += -DZC_HAS_OBJC_BACKEND=$(ZC_BACKENDS)
DEFINES += -DZC_HAS_JSON_BACKEND=$(ZC_BACKENDS)
DEFINES += -DZC_HAS_LISP_BACKEND=$(ZC_BACKENDS)
DEFINES += -DZC_HAS_DOT_BACKEND=$(ZC_BACKENDS)
DEFINES += -DZC_HAS_ASTDUMP_BACKEND=$(ZC_BACKENDS)

# Source files read from src-sources.txt, filtered by feature selection.
ALL_SRCS := $(shell cat src-sources.txt)
ZC_FILTER_LSP = $(if $(filter-out 1,$(ZC_LSP)),src/lsp/lsp_main.c src/lsp/lsp_analysis.c src/lsp/lsp_semantic.c src/lsp/lsp_index.c src/lsp/lsp_locals.c src/lsp/lsp_formatter.c src/lsp/lsp_project.c src/lsp/lsp_c_headers.c src/lsp/lsp_config.c src/lsp/json_rpc.c)
ZC_FILTER_REPL = $(if $(filter-out 1,$(ZC_REPL)),src/repl/% src/platform/console.c)
ZC_FILTER_PLUGINS = $(if $(filter-out 1,$(ZC_PLUGINS)),src/plugins/% src/parser/utils/utils_plugins.c)
ZC_FILTER_BACKENDS = $(if $(filter-out 1,$(ZC_BACKENDS)),src/codegen/codegen_backend_cpp.c src/codegen/codegen_backend_cuda.c src/codegen/codegen_backend_objc.c src/codegen/codegen_backend_json.c src/codegen/codegen_backend_lisp.c src/codegen/codegen_backend_dot.c src/codegen/codegen_backend_astdump.c)
SRCS = $(filter-out $(ZC_FILTER_LSP) $(ZC_FILTER_REPL) $(ZC_FILTER_PLUGINS) $(ZC_FILTER_BACKENDS),$(ALL_SRCS))

OBJ_DIR = obj
OBJS = $(patsubst %.c, $(OBJ_DIR)/%.o, $(SRCS))
DEPS = $(OBJS:.o=.d)

# Pull in header dependency files (auto-generated by -MMD -MP)
-include $(DEPS)

# Installation paths
PREFIX ?= /usr/local
BINDIR = $(PREFIX)/bin
MANDIR = $(PREFIX)/share/man
SHAREDIR = $(PREFIX)/share/zenc
INCLUDEDIR = $(PREFIX)/include/zenc

PLUGINS = plugins/befunge.so plugins/brainfuck.so plugins/forth.so plugins/lisp.so plugins/sql.so

# Fil-C configuration
FILCC_DIR := $(firstword $(wildcard filc-*/build/bin))
FILCC     ?= $(FILCC_DIR)/filcc
FILC_LIB  ?= $(firstword $(wildcard filc-*/pizfix/lib))

# APE (Actually Portable Executable) configuration
COSMOCC = cosmocc
OUT_STAGE = out/stage
OUT_BIN = out/bin
ZC_ENTRY_O = $(OUT_STAGE)/zc_entry.o
ZC_COM_BIN = $(OUT_STAGE)/zc.com
ZC_COM = $(OUT_BIN)/zc.com
ZC_BOOT_SRC = ape/boot/boot.zc
ZC_BOOT_COM_BIN = $(OUT_STAGE)/zc-boot.com
ZC_BOOT_COM = $(OUT_BIN)/zc-boot.com

# Discover plugins for static linking
PLUGIN_FILES = $(wildcard plugins/*.zc)
PLUGIN_NAMES = $(patsubst plugins/%.zc,%,$(PLUGIN_FILES))
PLUGIN_APE_OBJS = $(patsubst plugins/%.zc,obj-ape/plugins/%.o,$(PLUGIN_FILES))

# Function to sanitize plugin names for C identifiers
# Usage: $(call sanitize_name,name)
sanitize_name = $(shell echo $(1) | sed 's/[^a-zA-Z0-9]/_/g')

# Default target
.DEFAULT_GOAL := all
all: $(TARGET) $(PLUGINS) $(TOOLS)

# APE target
ape: $(ZC_COM) $(ZC_BOOT_COM)

# Build plugins

plugins/%.so: plugins/%.zc $(TARGET)
	$(ZC_RUN) build $< -shared -o $@

# Library groupings by directory
CORE_OBJS    = $(filter $(OBJ_DIR)/src/ast/% $(OBJ_DIR)/src/parser/% $(OBJ_DIR)/src/lexer/%, $(OBJS))
ANALYSIS_OBJS = $(filter $(OBJ_DIR)/src/analysis/%, $(OBJS))
CODEGEN_OBJS  = $(filter $(OBJ_DIR)/src/codegen/%, $(OBJS))
MISRA_OBJS   = $(filter $(OBJ_DIR)/src/platform/misra%, $(OBJS))
PLATFORM_OBJS = $(filter $(OBJ_DIR)/src/platform/os% $(OBJ_DIR)/src/platform/console% $(OBJ_DIR)/src/platform/dylib%, $(OBJS))
UTILS_OBJS   = $(filter $(OBJ_DIR)/src/utils/%, $(OBJS))
LSP_OBJS     = $(filter $(OBJ_DIR)/src/lsp/%, $(OBJS))
REPL_OBJS    = $(filter $(OBJ_DIR)/src/repl/%, $(OBJS))
ZEN_OBJS     = $(filter $(OBJ_DIR)/src/zen/%, $(OBJS))
PLUGIN_OBJS  = $(filter $(OBJ_DIR)/src/plugins/%, $(OBJS))
DIAG_OBJS    = $(filter $(OBJ_DIR)/src/diagnostics/%, $(OBJS))
DRIVER_OBJS  = $(filter $(OBJ_DIR)/src/driver/%, $(OBJS))
TOOLS_COMMON = $(OBJ_DIR)/src/tools/tool_common.o

# Objects shared by zc and the standalone tools, excluding the tool-specific
# lsp/repl/zen sources (those only ship in their respective binaries).
TOOL_BASE_OBJS = $(CORE_OBJS) $(ANALYSIS_OBJS) $(CODEGEN_OBJS) $(MISRA_OBJS) \
                 $(PLATFORM_OBJS) $(UTILS_OBJS) $(DIAG_OBJS) $(PLUGIN_OBJS)
ZC_BASE_OBJS   = $(filter-out $(LSP_OBJS) $(REPL_OBJS) $(ZEN_OBJS), $(OBJS))


ALL_LIBS = libzc-core.a libzc-analysis.a libzc-codegen.a libzc-misra.a \
           libzc-platform.a libzc-utils.a libzc-lsp.a libzc-repl.a \
           libzc-zen.a libzc-plugin.a libzc-diag.a libzc-driver.a

libzc-core.a: $(CORE_OBJS)
	ar rcs $@ $^

libzc-analysis.a: $(ANALYSIS_OBJS)
	ar rcs $@ $^

libzc-codegen.a: $(CODEGEN_OBJS)
	ar rcs $@ $^

libzc-misra.a: $(MISRA_OBJS)
	ar rcs $@ $^

libzc-platform.a: $(PLATFORM_OBJS)
	ar rcs $@ $^

libzc-utils.a: $(UTILS_OBJS)
	ar rcs $@ $^

libzc-lsp.a: $(LSP_OBJS)
	ar rcs $@ $^

libzc-repl.a: $(REPL_OBJS)
	ar rcs $@ $^

libzc-zen.a: $(ZEN_OBJS)
	ar rcs $@ $^

libzc-plugin.a: $(PLUGIN_OBJS)
	ar rcs $@ $^

libzc-diag.a: $(DIAG_OBJS)
	ar rcs $@ $^

libzc-driver.a: $(DRIVER_OBJS)
	ar rcs $@ $^

# Default: direct .o linking (reliable incremental builds).
# zc links everything except the lsp/repl/zen sources, which now live in the
# standalone tools (zc-lsp, zc-repl, zc-doc, zc-format).
$(TARGET): $(ZC_BASE_OBJS)
	@$(MKDIR) $(dir $@)
	$(CC) $(CFLAGS) -o $@ $(ZC_BASE_OBJS) $(LIBS)
	@echo "=> Build complete: $(TARGET)"

# Standalone tools (Unix-style: one focused program each)
zc-lsp: $(TOOL_BASE_OBJS) $(LSP_OBJS) $(TOOLS_COMMON) $(OBJ_DIR)/src/tools/main_lsp.o
	@$(MKDIR) $(dir $@)
	$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
	@echo "=> Build complete: $@"

zc-repl: $(TOOL_BASE_OBJS) $(REPL_OBJS) $(TOOLS_COMMON) $(OBJ_DIR)/src/tools/main_repl.o
	@$(MKDIR) $(dir $@)
	$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
	@echo "=> Build complete: $@"

zc-doc: $(TOOL_BASE_OBJS) $(ZEN_OBJS) $(TOOLS_COMMON) $(OBJ_DIR)/src/tools/main_doc.o
	@$(MKDIR) $(dir $@)
	$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
	@echo "=> Build complete: $@"

zc-format: $(TOOL_BASE_OBJS) $(LSP_OBJS) $(TOOLS_COMMON) $(OBJ_DIR)/src/tools/main_format.o
	@$(MKDIR) $(dir $@)
	$(CC) $(CFLAGS) -o $@ $^ $(LIBS)
	@echo "=> Build complete: $@"

$(OBJ_DIR)/src/tools/main_lsp.o: src/tools/main_lsp.c $(TOOLS_COMMON)
	@$(MKDIR) $(@D)
	$(CC) $(CFLAGS) -c $< -o $@

$(OBJ_DIR)/src/tools/main_repl.o: src/tools/main_repl.c $(TOOLS_COMMON)
	@$(MKDIR) $(@D)
	$(CC) $(CFLAGS) -c $< -o $@

$(OBJ_DIR)/src/tools/main_doc.o: src/tools/main_doc.c $(TOOLS_COMMON)
	@$(MKDIR) $(@D)
	$(CC) $(CFLAGS) -c $< -o $@

$(OBJ_DIR)/src/tools/main_format.o: src/tools/main_format.c $(TOOLS_COMMON)
	@$(MKDIR) $(@D)
	$(CC) $(CFLAGS) -c $< -o $@

# Library-based build (for systems without .o linking or for packaging)
libs: $(ALL_LIBS)
	$(CC) $(CFLAGS) -o $(TARGET) $(OBJ_DIR)/src/main.o $(TOOLS_COMMON) \
		libzc-driver.a libzc-repl.a libzc-lsp.a \
		libzc-codegen.a libzc-analysis.a \
		libzc-core.a libzc-misra.a libzc-zen.a libzc-plugin.a \
		libzc-diag.a libzc-utils.a libzc-platform.a \
		$(LIBS)
	@echo "=> Build complete: $(TARGET) (libraries)"

# Alias for backward compatibility
monolith: $(TARGET)
	@true

# Compile
$(OBJ_DIR)/%.o: %.c
	@$(MKDIR) $(dir $@)
	$(CC) $(CFLAGS) -c $< -o $@

# Rebuild the version-printing objects (and thus relink every tool) whenever
# GIT_VERSION changes. The stamp only rewrites when the version differs, so a
# plain `make` does not recompile anything.
$(OBJ_DIR)/version.stamp: FORCE
	@$(MKDIR) $(OBJ_DIR)
	@if [ -f $@ ] && [ "$$(cat $@)" = "$(GIT_VERSION)" ]; then :; else echo "$(GIT_VERSION)" > $@; fi

$(OBJ_DIR)/src/utils/cmd.o $(OBJ_DIR)/src/repl/repl.o: $(OBJ_DIR)/version.stamp

# APE targets
$(ZC_ENTRY_O): ape/zc_entry.c
	@$(MKDIR) $(@D)
	$(COSMOCC) -c $< -o $@

$(ZC_COM_BIN): $(ZC_ENTRY_O) $(SRCS) src/plugins/static_plugins.c $(PLUGIN_APE_OBJS)
	@$(MKDIR) $(@D)
	$(MAKE) \
		PLUGINS= \
		ZC_HAS_JIT=0 \
		CC=$(COSMOCC) \
		OBJ_DIR=obj-ape \
		CFLAGS='$(CFLAGS) -DZC_STATIC_PLUGINS -fno-stack-protector -Wno-redundant-decls -Wno-switch-default -Wno-sign-conversion -Wno-sign-compare -Wno-cast-align -Wno-implicit-fallthrough' \
		DEFINES='$(DEFINES) -DZC_STATIC_PLUGINS' \
		LIBS="$(abspath $(ZC_ENTRY_O)) $(PLUGIN_APE_OBJS) -Wl,--wrap=main" \
		SRCS="$(SRCS)" \
		TARGET="$(abspath $@)" \
		monolith

src/plugins/static_plugins.c: $(PLUGIN_FILES)
	@echo "=> Generating static plugin registry: $@"
	@echo '#include "plugin_manager.h"' > $@
	@echo '#include <string.h>' >> $@
	@echo '#ifdef ZC_STATIC_PLUGINS' >> $@
	@for p in $(PLUGIN_NAMES); do \
		psafe=$$(echo $$p | sed 's/[^a-zA-Z0-9]/_/g'); \
		echo "extern ZPlugin *z_plugin_init_$$psafe(void);" >> $@; \
	done
	@echo 'ZPlugin *zptr_get_static_plugin(const char *name) {' >> $@
	@for p in $(PLUGIN_NAMES); do \
		psafe=$$(echo $$p | sed 's/[^a-zA-Z0-9]/_/g'); \
		echo "  if (strcmp(name, \"$$p\") == 0 || strcmp(name, \"plugins/$$p\") == 0) return z_plugin_init_$$psafe();" >> $@; \
	done
	@echo '  return NULL;' >> $@
	@echo '}' >> $@
	@echo '#else' >> $@
	@echo 'ZPlugin *zptr_get_static_plugin(const char *name) { (void)name; return NULL; }' >> $@
	@echo '#endif' >> $@

obj-ape/plugins/%.c: plugins/%.zc $(TARGET)
	@$(MKDIR) $(@D)
	./$(TARGET) transpile $< -o $@

obj-ape/plugins/%.o: obj-ape/plugins/%.c
	@$(MKDIR) $(@D)
	@psafe=$$(echo $* | sed 's/[^a-zA-Z0-9]/_/g'); \
	$(COSMOCC) $(CFLAGS) \
		-fno-stack-protector \
		-DZC_STATIC_PLUGIN -Dz_plugin_init=z_plugin_init_$$psafe -c $< -o $@

$(ZC_COM): $(ZC_COM_BIN)
	@$(MKDIR) $(@D)
	@$(CP) $(ZC_COM_BIN) $(wildcard $(ZC_COM_BIN).*) "$(@D)"; \
	cp src/misc/zenc.json zenc.json; \
	cp src/zen/facts.json facts.json; \
	cp src/repl/docs.json docs.json; \
	zip -r "$(abspath $@)" std.zc std LICENSE zenc.json facts.json docs.json; \
	rm -f zenc.json facts.json docs.json

$(ZC_BOOT_COM_BIN): $(ZC_BOOT_SRC) $(ZC_COM)
	@$(MKDIR) $(@D)
	./$(ZC_COM) build --cc $(COSMOCC) -o $@ $< || { \
		echo "warning: boot.com build failed (non-fatal)"; \
		touch $@; \
	}

$(ZC_BOOT_COM): $(ZC_BOOT_COM_BIN) ape/boot/.args
	@$(MKDIR) $(@D)
	@$(CP) $(ZC_BOOT_COM_BIN) $(wildcard $(ZC_BOOT_COM_BIN).*) "$(@D)"; \
	cp src/misc/zenc.json zenc.json; \
	cp src/repl/docs.json docs.json; \
	(cd ape/boot && zip "$(abspath $@)" .args hello.zc instructions.txt Makefile); \
	zip "$(abspath $@)" LICENSE zenc.json docs.json; \
	rm -f zenc.json docs.json

# Install
install: $(TARGET) install-tools
	$(INSTALL) -d $(BINDIR)
	$(INSTALL) -m 755 $(TARGET) $(BINDIR)/$(TARGET)

	# Install man pages
	$(INSTALL) -d $(MANDIR)/man1 $(MANDIR)/man5 $(MANDIR)/man7
	test -f man/zc.1 && $(INSTALL) -m 644 man/zc.1 $(MANDIR)/man1/zc.1 || true
	test -f man/zc.5 && $(INSTALL) -m 644 man/zc.5 $(MANDIR)/man5/zc.5 || true
	test -f man/zc.7 && $(INSTALL) -m 644 man/zc.7 $(MANDIR)/man7/zc.7 || true
	test -f man/zc-stdlib.7 && $(INSTALL) -m 644 man/zc-stdlib.7 $(MANDIR)/man7/zc-stdlib.7 || true

	# Install standard library
	$(INSTALL) -d $(SHAREDIR)
	$(INSTALL) -m 644 std.zc $(SHAREDIR)/std.zc
	$(CP) std $(SHAREDIR)/

	# Install repl/compiler data
	$(INSTALL) -m 644 src/repl/docs.json $(SHAREDIR)/docs.json
	$(INSTALL) -m 644 src/misc/zenc.json $(SHAREDIR)/zenc.json

	# Install plugin headers
	$(INSTALL) -d $(INCLUDEDIR)
	$(INSTALL) -m 644 plugins/zprep_plugin.h $(INCLUDEDIR)/zprep_plugin.h

	# Install public API headers and their dependencies
	$(INSTALL) -m 644 src/public/*.h $(INCLUDEDIR)/
	$(INSTALL) -m 644 src/token.h src/arena.h $(INCLUDEDIR)/
	$(INSTALL) -m 644 src/utils/emitter.h src/utils/zvec.h src/utils/zalloc.h $(INCLUDEDIR)/

	# Install compiled plugins
	$(INSTALL) -d $(SHAREDIR)/plugins
	$(CP) plugins/*.so $(SHAREDIR)/plugins/
	@echo "=> Installed to $(BINDIR)/$(TARGET)"
	@echo "=> Man pages installed to $(MANDIR)"
	@echo "=> Standard library installed to $(SHAREDIR)/std"

# Install the standalone tools
install-tools: $(TOOLS)
	$(INSTALL) -d $(BINDIR)
	for t in $(TOOLS); do $(INSTALL) -m 755 $$t $(BINDIR)/$$t; done
	# Backward-compat alias: the LSP was previously installed as `zls`
	$(if $(filter zc-lsp,$(TOOLS)),$(INSTALL) -m 755 zc-lsp $(BINDIR)/zls)

# Uninstall
uninstall:
	$(RM) $(BINDIR)/$(TARGET) $(BINDIR)/zls
	for t in $(ALL_TOOLS); do $(RM) $(BINDIR)/$$t; done
	$(RM) $(MANDIR)/man1/zc.1
	$(RM) $(MANDIR)/man5/zc.5
	$(RM) $(MANDIR)/man7/zc.7
	$(RM) $(MANDIR)/man7/zc-stdlib.7
	$(RM) $(SHAREDIR)
	@echo "=> Uninstalled from $(BINDIR)/$(TARGET)"
	@echo "=> Removed man pages from $(MANDIR)"
	@echo "=> Removed $(SHAREDIR)"

# Install APE
install-ape: ape
	$(INSTALL) -d $(BINDIR)
	$(INSTALL) -m 755 $(ZC_COM) $(BINDIR)/zc.com
	$(INSTALL) -m 755 $(ZC_BOOT_COM) $(BINDIR)/zc-boot.com
	$(LN) $(BINDIR)/zc.com $(BINDIR)/zc

	# Install standard library (shared)
	$(INSTALL) -d $(SHAREDIR)
	$(INSTALL) -m 644 std.zc $(SHAREDIR)/std.zc
	$(CP) std $(SHAREDIR)/
	@echo "=> Installed APE binaries to $(BINDIR)"
	@echo "=> Alias 'zc' points to zc.com"
	@echo "=> Standard library installed to $(SHAREDIR)/std"

# Uninstall APE
uninstall-ape:
	$(RM) $(BINDIR)/zc
	$(RM) $(BINDIR)/zc.com
	$(RM) $(BINDIR)/zc-boot.com
	$(RM) $(SHAREDIR)
	@echo "=> Uninstalled APE binaries from $(BINDIR)"
	@echo "=> Removed $(SHAREDIR)"

# Clean
clean:
	$(RM) $(OBJ_DIR) obj-ape obj-fuzz obj-fuzz-cmplog $(TARGET) $(ALL_TOOLS) libzc-*.a
	$(RM) out.c out.cpp out.m out.cu plugins/*.so a.out* out test_out_* rule_*
	$(RM) *.gcda *.gcno *.gcov coverage.info
	$(RM) -r coverage-report/
	$(RM) .bench_* bench_* benchmarks_result.json
	$(RM) *_suite.c *_suite.cpp test_runner
	@echo "=> Clean complete!"

# Code Formatting
CLANG_FORMAT ?= clang-format
SRC_FILES = $(filter %.c %.h, $(shell find src -type f \( -name '*.c' -o -name '*.h' \)))

format:
	$(CLANG_FORMAT) -i $(SRC_FILES)
	@echo "=> Formatted $(words $(SRC_FILES)) source files"

format-check:
	$(CLANG_FORMAT) --dry-run --Werror $(SRC_FILES)
	@echo "=> All source files are properly formatted"

# Linting
lint: format-check

check-codebase:
	@echo "Running codebase convention checks..."
	@scripts/check-codebase.sh
	@echo "=> Running shellcheck on test scripts"
	-shellcheck tests/scripts/*.sh
	@echo "=> Lint complete"

# Benchmarks
bench: $(TARGET)
	./tests/scripts/run_benchmarks.sh

# Test
# Supports running specific tests:
#	make test only="tests/std/test_hash.zc examples/arena_test.zc"
test: $(TARGET) $(PLUGINS)
	./tests/scripts/run_tests.sh -- $(filter %.zc,$(only))
	./tests/scripts/run_codegen_tests.sh $(filter %.zc,$(only))
	./tests/scripts/run_example_transpile.sh $(filter %.zc,$(only))
	./tests/scripts/run_example_build.sh $(filter %.zc,$(only))
	./tests/scripts/run_repl_tests.sh
	./tests/scripts/run_cli_tests.sh
	./tests/scripts/run_diagnostic_output_tests.sh
	./tests/scripts/run_check_clean.sh
	$(MAKE) test-unit
	$(MAKE) test-misra

# Files that the full typechecker (`zc check`) must accept with no errors.
test-check: $(TARGET)
	./tests/scripts/run_check_clean.sh

test-misra: $(TARGET)
	./tests/scripts/run_misra_tests.sh

test-tcc: $(TARGET) $(PLUGINS)
	./tests/scripts/run_tests.sh --cc tcc

test-filcc: $(TARGET) $(PLUGINS)
	@if [ -z "$(FILCC_DIR)" ]; then \
		echo "ERROR: Fil-C distribution not found. Expected 'filc-*/build/bin/' directory."; \
		exit 1; \
	fi
	FILC_LIBRARY_PATH="$(FILC_LIB)" ./tests/scripts/run_tests.sh --cc "$(FILCC)"

test-lsp: $(TARGET) $(PLUGINS) zc-lsp
	@echo "=> Building LSP Test Runner"
	$(CC) $(CFLAGS) -DZC_NO_ARENA tests/compiler/lsp/lsp_test_runner.c src/utils/cJSON.c -o tests/compiler/lsp/test_runner
	@echo "=> Running LSP Tests"
	./tests/compiler/lsp/test_runner

# Unit tests in C of the compiler's internals (tests/unit/, with utest.h). They link
# the compiler objects, without main.o: tests/unit/unit_main.c provides main().
UNIT_TEST_SRCS = $(wildcard tests/unit/*.c tests/unit/*/*.c)
# tests/unit/lsp/ also links the LSP pieces that only need the core; skipped without the LSP.
UNIT_TEST_LSP_OBJS = $(if $(filter 1,$(ZC_LSP)),$(OBJ_DIR)/src/lsp/lsp_index.o $(OBJ_DIR)/src/lsp/lsp_locals.o)
UNIT_TEST_SRCS := $(if $(filter 1,$(ZC_LSP)),$(UNIT_TEST_SRCS),$(filter-out tests/unit/lsp/%,$(UNIT_TEST_SRCS)))

test-unit: $(TOOL_BASE_OBJS) $(DRIVER_OBJS) $(UNIT_TEST_LSP_OBJS)
	@echo "=> Building unit tests"
	@$(MKDIR) $(OBJ_DIR)/tests/unit
	$(CC) $(CFLAGS) -isystem tests/vendor -I tests/unit $(UNIT_TEST_SRCS) $(TOOL_BASE_OBJS) $(DRIVER_OBJS) $(UNIT_TEST_LSP_OBJS) -o $(OBJ_DIR)/tests/unit/run_unit $(LIBS)
	@echo "=> Running unit tests"
	./$(OBJ_DIR)/tests/unit/run_unit

test-backends: $(TARGET)
	@echo "=> Testing all output backends"
	./tests/scripts/test_backends.sh

test-lisp: $(TARGET)
	@echo "=> Testing Lisp backend"
	@./tests/scripts/test_lisp.sh; echo "=> Lisp suite done"

test-lsp-smoke: $(TARGET) $(PLUGINS)
	@echo "=> Running LSP Smoke Test"
	./tests/scripts/run_lsp_smoke_test.py --zc ./zc

# Build with alternative compilers
zig:
	$(MAKE) CC="zig cc"

clang:
	$(MAKE) CC=clang

filcc:
	$(MAKE) CC="$(FILCC)" ZC_HAS_JIT=0

windows:
	$(MAKE) CC="x86_64-w64-mingw32-gcc" TARGET="zc.exe" LIBS="-static -lm -lpthread"

asan: CFLAGS += -fsanitize=address,undefined -O1 -g -fno-omit-frame-pointer
asan: LIBS += -fsanitize=address,undefined
asan: ZC_RUN = ASAN_OPTIONS=detect_leaks=0 ./zc
asan: $(TARGET) $(PLUGINS)

test-asan: clean asan
	ASAN_OPTIONS=detect_leaks=0 ./tests/scripts/run_tests.sh
	ASAN_OPTIONS=detect_leaks=0 ./tests/scripts/run_codegen_tests.sh
	ASAN_OPTIONS=detect_leaks=0 ./tests/scripts/run_example_transpile.sh

# TSAN (ThreadSanitizer), MSAN (MemorySanitizer), LSAN (LeakSanitizer),
# and the GCC Static Analyzer are build-only targets.
# They compile the compiler with extra instrumentation to catch bugs,
# but do not run the test suite (test-generated code may trigger false positives).

# LeakSanitizer — built into ASAN
lsan: asan
	@echo "LeakSanitizer: see asan build above"

# ThreadSanitizer
tsan: CFLAGS += -fsanitize=thread -O1 -g -fno-omit-frame-pointer
tsan: LIBS += -fsanitize=thread
tsan: $(TARGET) $(PLUGINS)

# MemorySanitizer (Clang only) — builds the compiler only, not plugins.
# MSAN requires ALL linked libraries (including glibc) to be MSAN-instrumented,
# which is not feasible in a standard CI environment. Plugins cannot be built
# because the MSAN-instrumented compiler aborts on use-of-uninitialized-value
# at runtime during compilation.
msan: CC = clang
msan: CFLAGS += -fsanitize=memory -O1 -g -fno-omit-frame-pointer -fsanitize-memory-track-origins
msan: LIBS += -fsanitize=memory
msan: $(TARGET)

# GCC Static Analyzer (slow, ~5x build time)
analyzer: CFLAGS += -fanalyzer -Wno-analyzer-infinite-recursion
analyzer: $(TARGET) $(PLUGINS)

# Code coverage (GCC only, incompatible with sanitizers)
GCOV ?= gcov
coverage: CFLAGS += --coverage -O0
coverage: LIBS += --coverage
coverage: $(TARGET) $(PLUGINS)
	@echo "=> Running tests with coverage instrumentation..."
	./tests/scripts/run_tests.sh --no-source -j 2
	@echo "=> Coverage data generated"

coverage-report: coverage
	lcov --capture --directory obj --output-file coverage.info \
	     --gcov-tool $(GCOV) --include '*/src/*' --ignore-errors mismatch
	genhtml coverage.info --output-directory coverage-report --ignore-errors empty
	@echo "=> Coverage report: coverage-report/index.html"

test-plugins: $(TARGET) $(PLUGINS)
	./zc run tests/language/features/test_plugins_suite.zc

# Convenience targets for modular builds
# zc no longer links lsp/repl/zen at all; these variants additionally strip
# plugins/backends and skip building the standalone tools.
core:
	$(MAKE) ZC_LSP=0 ZC_REPL=0 ZC_PLUGINS=0 ZC_BACKENDS=0 TOOLS=

lite:
	$(MAKE) ZC_LSP=0 ZC_REPL=0 TOOLS=

minimal:
	$(MAKE) ZC_LSP=0 ZC_REPL=0 ZC_PLUGINS=0 ZC_BACKENDS=0 TOOLS=

# Fuzzing
FUZZ_TARGET = zc-fuzz
FUZZ_CMPLOG_TARGET = zc-fuzz-cmplog
FUZZ_CC ?= afl-clang-fast
FUZZ_DIR = fuzz
FUZZ_OUT = $(FUZZ_DIR)/out
FUZZ_CORPUS = $(FUZZ_DIR)/corpus
FUZZ_DICT = $(FUZZ_DIR)/zen_c.dict

# High-performance flags
FUZZ_CFLAGS = -O3 -march=native -D__AFL_HAVE_MANUAL_CONTROL

fuzz-build:
	@$(MKDIR) $(OBJ_DIR)/fuzz
	$(MAKE) CC=$(FUZZ_CC) CFLAGS='$(CFLAGS) $(FUZZ_CFLAGS)' OBJ_DIR=obj-fuzz TARGET=$(FUZZ_TARGET) SRCS="$(filter-out src/main.c,$(SRCS)) fuzz/harness.c"
	@echo "=> Fuzzing target built (Persistent Mode): $(FUZZ_TARGET)"

fuzz-cmplog-build:
	@$(MKDIR) $(OBJ_DIR)/fuzz-cmplog
	AFL_LLVM_CMPLOG=1 $(MAKE) CC=$(FUZZ_CC) CFLAGS='$(CFLAGS) $(FUZZ_CFLAGS)' OBJ_DIR=obj-fuzz-cmplog TARGET=$(FUZZ_CMPLOG_TARGET) SRCS="$(filter-out src/main.c,$(SRCS)) fuzz/harness.c"
	@echo "=> CmpLog target built: $(FUZZ_CMPLOG_TARGET)"

# LibFuzzer targets
fuzz-libfuzzer-build:
	@$(MKDIR) $(OBJ_DIR)/fuzz-libfuzzer
	clang $(filter-out $(GCC_WARN_FLAGS),$(CFLAGS)) $(CLANG_WARN_FLAGS) \
	      -Wno-sign-conversion -Wno-switch-default -Wno-cast-align -Wno-implicit-fallthrough -Wno-redundant-decls \
	      -fsanitize=fuzzer,address,undefined \
	      $(filter-out src/main.c,$(SRCS)) fuzz/harness.c -o zc-fuzz-libfuzzer $(LIBS)
	@echo "=> LibFuzzer target built: zc-fuzz-libfuzzer"

fuzz-corpus:
	@$(MKDIR) $(FUZZ_CORPUS)
	@find tests -name '*.zc' -exec cp {} $(FUZZ_CORPUS)/ \; 2>/dev/null || true
	@echo "=> Seed corpus created from existing tests ($(shell ls -1 $(FUZZ_CORPUS) 2>/dev/null | wc -l) files)"

fuzz-run: fuzz-build fuzz-corpus
	@$(MKDIR) $(FUZZ_OUT)
	@echo "-> Starting fuzzer (Persistent Mode enabled)"
	@echo "-> Tip: For parallel runs, use '-M main' and '-S secondaryN'"
	AFL_SKIP_CPUFREQ=1 AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 afl-fuzz -i $(FUZZ_CORPUS) -o $(FUZZ_OUT) $(if $(wildcard $(FUZZ_DICT)),-x $(FUZZ_DICT),) -- ./$(FUZZ_TARGET)

# Fuzz regression: replay known crash/leak/timeout inputs to verify they no longer crash
FUZZ_CRASH_FILES = $(wildcard crash-*) $(wildcard leak-*) $(wildcard timeout-*)
FUZZ_HARNESS = zc-fuzz-harness

test-fuzz-regression: $(FUZZ_HARNESS) $(FUZZ_CRASH_FILES)
	@echo "=== Fuzz Regression Tests ==="
	@total=0; pass=0; \
	for f in $(FUZZ_CRASH_FILES); do \
		total=$$((total + 1)); \
		result=0; \
		./$(FUZZ_HARNESS) "$$f" 2>/dev/null || result=$$?; \
		if [ $$result -eq 0 ]; then \
			echo "  [PASS] $$f"; \
			pass=$$((pass + 1)); \
		else \
			echo "  [FAIL] $$f (exit code $$result)"; \
		fi; \
	done; \
	echo "---"; \
	if [ $$pass -eq $$total ]; then \
		echo "All $$total fuzz regression tests passed."; \
	else \
		echo "$$pass/$$total fuzz regression tests passed."; \
		exit 1; \
	fi

$(FUZZ_HARNESS): fuzz/harness.c $(OBJS)
	$(CC) $(CFLAGS) -D__AFL_HAVE_MANUAL_CONTROL -Wno-switch-default -Wno-sign-conversion -Wno-redundant-decls $(filter-out src/main.c,$(SRCS)) fuzz/harness.c -o $@ $(LIBS)

fuzz-clean:
	rm -rf $(FUZZ_OUT)/*
	rm -f $(FUZZ_TARGET) $(FUZZ_CMPLOG_TARGET) $(FUZZ_HARNESS)
	rm -rf obj-fuzz obj-fuzz-cmplog

.PHONY: all clean install uninstall install-ape uninstall-ape install-tools format format-check lint bench test test-misra test-tcc test-filcc test-lsp test-unit test-asan test-plugins zig clang filcc ape windows asan tsan msan lsan analyzer coverage coverage-report core lite minimal fuzz-build fuzz-run fuzz-clean test-fuzz-regression zc-lsp zc-repl zc-doc zc-format FORCE