#!/usr/bin/env bash # lib/haskell/conformance.sh — run the 5 classic-program test suites. # Writes lib/haskell/scoreboard.json and lib/haskell/scoreboard.md. # # Usage: # bash lib/haskell/conformance.sh # run + write scoreboards # bash lib/haskell/conformance.sh --check # run only, exit 1 on failure set -euo pipefail cd "$(git rev-parse --show-toplevel)" SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe" if [ ! -x "$SX_SERVER" ]; then MAIN_ROOT=$(git worktree list | head -1 | awk '{print $1}') if [ -x "$MAIN_ROOT/$SX_SERVER" ]; then SX_SERVER="$MAIN_ROOT/$SX_SERVER" else echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build" exit 1 fi fi PROGRAMS=(fib sieve quicksort nqueens calculator) PASS_COUNTS=() FAIL_COUNTS=() run_suite() { local prog="$1" local FILE="lib/haskell/tests/program-${prog}.sx" local TMPFILE TMPFILE=$(mktemp) cat > "$TMPFILE" <&1 || true) rm -f "$TMPFILE" local LINE LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 3 / {getline; print; exit}') if [ -z "$LINE" ]; then LINE=$(echo "$OUTPUT" | grep -E '^\(ok 3 \([0-9]+ [0-9]+\)\)' | tail -1 \ | sed -E 's/^\(ok 3 //; s/\)$//' || true) fi if [ -z "$LINE" ]; then echo "0 1" else local P F P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/' || echo "0") F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/' || echo "1") echo "$P $F" fi } for prog in "${PROGRAMS[@]}"; do RESULT=$(run_suite "$prog") P=$(echo "$RESULT" | cut -d' ' -f1) F=$(echo "$RESULT" | cut -d' ' -f2) PASS_COUNTS+=("$P") FAIL_COUNTS+=("$F") T=$((P + F)) if [ "$F" -eq 0 ]; then printf '✓ %-14s %d/%d\n' "${prog}.hs" "$P" "$T" else printf '✗ %-14s %d/%d\n' "${prog}.hs" "$P" "$T" fi done TOTAL_PASS=0 TOTAL_FAIL=0 PROG_PASS=0 for i in "${!PROGRAMS[@]}"; do TOTAL_PASS=$((TOTAL_PASS + PASS_COUNTS[i])) TOTAL_FAIL=$((TOTAL_FAIL + FAIL_COUNTS[i])) [ "${FAIL_COUNTS[$i]}" -eq 0 ] && PROG_PASS=$((PROG_PASS + 1)) done PROG_TOTAL=${#PROGRAMS[@]} echo "" echo "Classic programs: ${TOTAL_PASS}/$((TOTAL_PASS + TOTAL_FAIL)) tests | ${PROG_PASS}/${PROG_TOTAL} programs passing" if [[ "${1:-}" == "--check" ]]; then [ $TOTAL_FAIL -eq 0 ] exit $? fi DATE=$(date '+%Y-%m-%d') # scoreboard.json { printf '{\n' printf ' "date": "%s",\n' "$DATE" printf ' "total_pass": %d,\n' "$TOTAL_PASS" printf ' "total_fail": %d,\n' "$TOTAL_FAIL" printf ' "programs": {\n' last=$((${#PROGRAMS[@]} - 1)) for i in "${!PROGRAMS[@]}"; do prog="${PROGRAMS[$i]}" if [ $i -lt $last ]; then printf ' "%s": {"pass": %d, "fail": %d},\n' "$prog" "${PASS_COUNTS[$i]}" "${FAIL_COUNTS[$i]}" else printf ' "%s": {"pass": %d, "fail": %d}\n' "$prog" "${PASS_COUNTS[$i]}" "${FAIL_COUNTS[$i]}" fi done printf ' }\n' printf '}\n' } > lib/haskell/scoreboard.json # scoreboard.md { printf '# Haskell-on-SX Scoreboard\n\n' printf 'Updated %s · Phase 3 (laziness + classic programs)\n\n' "$DATE" printf '| Program | Tests | Status |\n' printf '|---------|-------|--------|\n' for i in "${!PROGRAMS[@]}"; do prog="${PROGRAMS[$i]}" P=${PASS_COUNTS[$i]} F=${FAIL_COUNTS[$i]} T=$((P + F)) [ "$F" -eq 0 ] && STATUS="✓" || STATUS="✗" printf '| %s | %d/%d | %s |\n' "${prog}.hs" "$P" "$T" "$STATUS" done printf '| **Total** | **%d/%d** | **%d/%d programs** |\n' \ "$TOTAL_PASS" "$((TOTAL_PASS + TOTAL_FAIL))" "$PROG_PASS" "$PROG_TOTAL" } > lib/haskell/scoreboard.md echo "Wrote lib/haskell/scoreboard.json and lib/haskell/scoreboard.md" [ $TOTAL_FAIL -eq 0 ]