Files
snark13 c71e249a4e Add full compiler toolchain, libc, examples and reference docs
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>
2026-06-03 16:13:21 +03:00

126 lines
4.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
MKEXE=./mkexe
TESTS=tests
fail=0
total=0
assert_hex_eq() {
local file=$1 offset=$2 expected=$3 label=$4
local actual
actual=$(xxd -s "$offset" -l $((${#expected} / 2)) -p "$file")
if [ "$actual" != "$expected" ]; then
echo "FAIL: $label"
echo " file: $file"
echo " offset: $offset"
echo " expected: $expected"
echo " actual: $actual"
fail=$((fail + 1))
fi
}
run_case() {
local name=$1; shift
total=$((total + 1))
if "$@" >/dev/null 2>&1; then
:
else
echo "FAIL: $name (exit nonzero)"
fail=$((fail + 1))
return
fi
}
expect_failure() {
local name=$1; shift
total=$((total + 1))
if "$@" >/dev/null 2>&1; then
echo "FAIL: $name (expected nonzero exit, got success)"
fail=$((fail + 1))
fi
}
# Case 1: minimal .ihx with one byte RET at 0x4100
run_case "build-single-byte" \
$MKEXE -L 0x4100 -E 0x4100 -S 0xBFFE -o $TESTS/case1.exe $TESTS/hello.ihx
assert_hex_eq $TESTS/case1.exe 0 "455845" "case1: signature EXE"
assert_hex_eq $TESTS/case1.exe 3 "00" "case1: version 0"
assert_hex_eq $TESTS/case1.exe 4 "00020000" "case1: offset 0x00000200"
assert_hex_eq $TESTS/case1.exe 8 "0000" "case1: loader=0"
assert_hex_eq $TESTS/case1.exe 0x10 "0041" "case1: load=0x4100"
assert_hex_eq $TESTS/case1.exe 0x12 "0041" "case1: start=0x4100"
assert_hex_eq $TESTS/case1.exe 0x14 "febf" "case1: stack=0xBFFE"
assert_hex_eq $TESTS/case1.exe 0x200 "c9" "case1: image byte = RET (C9)"
# File size = 512 + 1
size=$(stat -f "%z" $TESTS/case1.exe 2>/dev/null || stat -c "%s" $TESTS/case1.exe)
if [ "$size" != "513" ]; then
echo "FAIL: case1: file size $size != 513"
fail=$((fail + 1))
fi
total=$((total + 1))
# Case 2: defaults — load and start auto-derived from .ihx, stack default = 0xBFFE
run_case "defaults-from-ihx" \
$MKEXE -o $TESTS/case2.exe $TESTS/hello.ihx
assert_hex_eq $TESTS/case2.exe 0x10 "0041" "case2: load defaults to 0x4100 (from ihx)"
assert_hex_eq $TESTS/case2.exe 0x12 "0041" "case2: start defaults to load"
assert_hex_eq $TESTS/case2.exe 0x14 "febf" "case2: stack defaults to 0xBFFE"
# Case 3: .bin input
printf '\xC9' > $TESTS/one.bin
run_case "build-from-bin" \
$MKEXE -L 0x4100 -o $TESTS/case3.exe $TESTS/one.bin
assert_hex_eq $TESTS/case3.exe 0x10 "0041" "case3: bin load=0x4100"
assert_hex_eq $TESTS/case3.exe 0x200 "c9" "case3: bin image"
# Case 4: image extending past 0xFFFF must fail
printf '\xFF%.0s' {1..4096} > $TESTS/big.bin
expect_failure "reject-past-FFFF" \
$MKEXE -L 0xF800 -o $TESTS/case4.exe $TESTS/big.bin
# Case 5: load above 0xFFFF must fail
expect_failure "reject-load-out-of-range" \
$MKEXE -L 0x10000 -o $TESTS/case5.exe $TESTS/one.bin
# Case 6: bad checksum in .ihx must fail
cat > $TESTS/bad.ihx <<'EOF'
:01410000C900
:00000001FF
EOF
expect_failure "reject-bad-checksum" \
$MKEXE -o $TESTS/case6.exe $TESTS/bad.ihx
# Case 7: multi-bank IHX → loader auto-set to HOME size, bank 1 appended (16 KB)
# ELA "0001" puts the next records into bank 1 (virtual 0x1C000+).
# Note: Intel HEX address fields are big-endian inside the record
# :01 C000 00 77 C8 means addr=0xC000 (record bytes "C000" = high then low)
cat > $TESTS/banked.ihx <<'EOF'
:01410000C9F5
:020000040001F9
:01C0000077C8
:00000001FF
EOF
run_case "banked-auto-loader" \
$MKEXE -L 0x4100 -o $TESTS/case7.exe $TESTS/banked.ihx
assert_hex_eq $TESTS/case7.exe 8 "0100" "case7: loader=HOME size (1 byte)"
size=$(stat -f "%z" $TESTS/case7.exe 2>/dev/null || stat -c "%s" $TESTS/case7.exe)
if [ "$size" != "16897" ]; then
echo "FAIL: case7: file size $size != 16897 (512 hdr + 1 HOME + 16384 bank1)"
fail=$((fail + 1))
fi
# Byte at file offset 0x201 (first byte of bank1) should be the 0x77 we put at bank1's 0xC000
assert_hex_eq $TESTS/case7.exe 0x201 "77" "case7: bank1 first byte"
total=$((total + 9))
if [ "$fail" -gt 0 ]; then
echo
echo "FAILED: $fail of $total assertions/cases"
exit 1
fi
echo "OK: $total cases passed"