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>
126 lines
4.1 KiB
Bash
Executable File
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"
|