c71e249a4e
First substantive commit: the entire Sprinter C compiler tree on top of
the bare README+gitignore initial commit.
What's in here:
bin/sprinter-cc — driver script invoking SDCC + linker + mkexe
libc/ — Sprinter-specific libc layer over ESTEX/BIOS
(conio, gfx, io, mem, stdio + headers)
runtime/ — crt0 variants (default/small/banked/minimal)
+ heap + bank trampolines
toolchain/ — mkexe (SprintEXE packer, C + tests)
examples/ — 30 demo programs (gfx, file I/O, env, time, …)
lib/Makefile — builds the libc archive (sprinter.lib)
docs/ — converted Sprinter manuals + asm reference samples
third_party/ — solid-c reference compiler dump + sdcc setup script
release_docs/ — packaging / release notes
gitignore overhaul:
• Drop dangerous blanket patterns: *.asm (would hide docs/samples/*.asm)
and *.exe (case-insensitive match was hiding third_party/solid-c/*.EXE
on macOS APFS). Replaced with examples/*/*.{asm,exe,…} and lib/*.lib.
• Restore tracking of toolchain/mkexe/tests/{one,big}.bin — those are
INPUT fixtures, not build outputs.
• Collapse the duplicated SDCC/C/Sdcc sections into one section per
concern (build outputs / vendored / OS-junk).
• Add .sprinter-cc-*/, build/ (catches lib/build/ too), .claude/.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
167 lines
5.8 KiB
Bash
Executable File
167 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# sprinter-cc — wrapper around SDCC for building Sprinter .EXE files.
|
|
#
|
|
# Usage:
|
|
# sprinter-cc -o foo.exe foo.c [more.c ...] [options]
|
|
#
|
|
# Options:
|
|
# -o NAME output executable name (required)
|
|
# --crt0=TYPE crt0 variant: default | minimal | banked (default: default)
|
|
# -I PATH additional include path (repeatable)
|
|
# -L 0xADDR code load address (default: 0x4100)
|
|
# -E 0xADDR entry address (default: same as -L)
|
|
# -S 0xADDR stack address (default: 0xBFFE)
|
|
# --code-loc 0xN forwarded to SDCC --code-loc (default: 0x4100)
|
|
# --data-loc 0xN forwarded to SDCC --data-loc (default: 0x8000)
|
|
# -Wl FLAG extra linker flag (repeatable)
|
|
# --bank N=FILE.c compile FILE.c as bank N; repeatable; pulls crt0_banked
|
|
# automatically and adds -Wl-b_BANKN=0x{N}C000
|
|
# --mkexe FLAG extra mkexe flag (repeatable; e.g. --mkexe -p --mkexe 0)
|
|
# -v verbose (echo every command)
|
|
# -h, --help show this help
|
|
#
|
|
# Example:
|
|
# sprinter-cc -o hello.exe hello.c
|
|
# sprinter-cc -o app.exe main.c --bank 1=engine.c
|
|
# sprinter-cc -o tiny.exe tiny.c --crt0=minimal
|
|
|
|
set -eo pipefail
|
|
|
|
# ------- Locate the toolchain ------------------------------------------------
|
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
PROJ_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
|
|
|
|
SDCC_BIN="$PROJ_ROOT/third_party/sdcc/bin"
|
|
SDCC="$SDCC_BIN/sdcc"
|
|
SDASZ80="$SDCC_BIN/sdasz80"
|
|
MKEXE="$PROJ_ROOT/toolchain/mkexe/mkexe"
|
|
CHECK_BANKS="$PROJ_ROOT/toolchain/check_banks.py"
|
|
|
|
RUNTIME="$PROJ_ROOT/runtime"
|
|
LIB_DIR="$PROJ_ROOT/lib"
|
|
INC_DIR="$PROJ_ROOT/libc/include"
|
|
|
|
# ------- Defaults ------------------------------------------------------------
|
|
OUT=""
|
|
CRT0_TYPE="default"
|
|
CODE_LOC="0x8100" # TINY mode default — everything in W2 (single page)
|
|
DATA_LOC="0" # 0 = let linker auto-place after code areas
|
|
LOAD_ADDR=""
|
|
ENTRY_ADDR=""
|
|
STACK_ADDR="0xBFFE"
|
|
VERBOSE=0
|
|
USER_INCS=()
|
|
SOURCES=()
|
|
LD_EXTRA=()
|
|
MKEXE_EXTRA=()
|
|
BANK_SPECS=() # entries like "1=engine.c"
|
|
|
|
# ------- Parse args ----------------------------------------------------------
|
|
usage() {
|
|
sed -n '3,28p' "$0" | sed 's/^# \{0,1\}//'
|
|
exit "${1:-0}"
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-o) OUT="$2"; shift 2;;
|
|
--crt0=*) CRT0_TYPE="${1#*=}"; shift;;
|
|
-I) USER_INCS+=("-I" "$2"); shift 2;;
|
|
-L) LOAD_ADDR="$2"; shift 2;;
|
|
-E) ENTRY_ADDR="$2"; shift 2;;
|
|
-S) STACK_ADDR="$2"; shift 2;;
|
|
--code-loc) CODE_LOC="$2"; shift 2;;
|
|
--data-loc) DATA_LOC="$2"; shift 2;;
|
|
-Wl) LD_EXTRA+=("$2"); shift 2;;
|
|
--bank) BANK_SPECS+=("$2"); shift 2;;
|
|
--mkexe) MKEXE_EXTRA+=("$2"); shift 2;;
|
|
-v) VERBOSE=1; shift;;
|
|
-h|--help) usage 0;;
|
|
-*) echo "sprinter-cc: unknown option: $1" >&2; usage 1;;
|
|
*) SOURCES+=("$1"); shift;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$OUT" ]] && { echo "sprinter-cc: -o NAME is required" >&2; exit 1; }
|
|
[[ ${#SOURCES[@]} -eq 0 ]] && { echo "sprinter-cc: no input files" >&2; exit 1; }
|
|
|
|
LOAD_ADDR="${LOAD_ADDR:-$CODE_LOC}"
|
|
ENTRY_ADDR="${ENTRY_ADDR:-$LOAD_ADDR}"
|
|
|
|
# Pick crt0 source.
|
|
case "$CRT0_TYPE" in
|
|
default) CRT0_SRC="$RUNTIME/crt0.s"; ;;
|
|
minimal) CRT0_SRC="$RUNTIME/crt0_minimal.s";;
|
|
banked) CRT0_SRC="$RUNTIME/crt0_banked.s"; ;;
|
|
*) echo "sprinter-cc: bad --crt0 type: $CRT0_TYPE" >&2; exit 1;;
|
|
esac
|
|
|
|
# Banked builds always need crt0_banked, regardless of what the user asked.
|
|
if [[ ${#BANK_SPECS[@]} -gt 0 ]] && [[ "$CRT0_TYPE" != "banked" ]]; then
|
|
[[ $VERBOSE -eq 1 ]] && echo " --bank present → switching to crt0_banked"
|
|
CRT0_SRC="$RUNTIME/crt0_banked.s"
|
|
fi
|
|
|
|
# ------- Build in a per-output workdir ---------------------------------------
|
|
WORK="$(dirname "$OUT")/.sprinter-cc-$(basename "$OUT" .exe)"
|
|
mkdir -p "$WORK"
|
|
|
|
run() {
|
|
[[ $VERBOSE -eq 1 ]] && echo " $*"
|
|
"$@"
|
|
}
|
|
|
|
# 1. crt0
|
|
CRT0_REL="$WORK/$(basename "$CRT0_SRC" .s).rel"
|
|
run "$SDASZ80" -o "$CRT0_REL" "$CRT0_SRC"
|
|
|
|
# 2. user sources → .rel (HOME)
|
|
USER_RELS=()
|
|
CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" "${USER_INCS[@]}")
|
|
for src in "${SOURCES[@]}"; do
|
|
rel="$WORK/$(basename "$src" .c).rel"
|
|
run "$SDCC" "${CC_FLAGS[@]}" -c -o "$rel" "$src"
|
|
USER_RELS+=("$rel")
|
|
done
|
|
|
|
# 3. bank sources
|
|
BANK_RELS=()
|
|
BANK_LD_FLAGS=()
|
|
for spec in "${BANK_SPECS[@]}"; do
|
|
bank_n="${spec%%=*}"
|
|
bank_src="${spec#*=}"
|
|
rel="$WORK/bank${bank_n}_$(basename "$bank_src" .c).rel"
|
|
run "$SDCC" "${CC_FLAGS[@]}" \
|
|
--codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" --dataseg "BANK${bank_n}" \
|
|
-c -o "$rel" "$bank_src"
|
|
BANK_RELS+=("$rel")
|
|
# virtual address: bank_n in upper byte, 0xC000 low
|
|
addr=$(printf "0x%X" $(( (bank_n << 16) | 0xC000 )))
|
|
BANK_LD_FLAGS+=("-Wl-b_BANK${bank_n}=${addr}")
|
|
done
|
|
|
|
# 4. link → .ihx
|
|
IHX="$WORK/$(basename "$OUT" .exe).ihx"
|
|
LINK_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size
|
|
--code-loc "$CODE_LOC" --data-loc "$DATA_LOC")
|
|
LINK_FLAGS+=("${BANK_LD_FLAGS[@]}")
|
|
for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done
|
|
# libsprinter.lib via -l/-L (sdcc passes -lsprinter through to sdldz80)
|
|
run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \
|
|
"$CRT0_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \
|
|
"-L$LIB_DIR" "-lsprinter"
|
|
|
|
# Quick bank-size check (only meaningful if there are banks).
|
|
if [[ ${#BANK_SPECS[@]} -gt 0 ]] && [[ -f "${IHX%.ihx}.map" ]]; then
|
|
python3 "$CHECK_BANKS" "${IHX%.ihx}.map" || true
|
|
fi
|
|
|
|
# 5. mkexe → .exe
|
|
MK_FLAGS=(-L "$LOAD_ADDR" -E "$ENTRY_ADDR" -S "$STACK_ADDR" -o "$OUT" "$IHX")
|
|
[[ $VERBOSE -eq 1 ]] && MK_FLAGS=(-v "${MK_FLAGS[@]}")
|
|
for f in "${MKEXE_EXTRA[@]}"; do MK_FLAGS=("$f" "${MK_FLAGS[@]}"); done
|
|
run "$MKEXE" "${MK_FLAGS[@]}"
|
|
|
|
echo "sprinter-cc: wrote $OUT"
|