From 58dcff2639cad2e91368db85df91925bab7a95ae Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 6 May 2026 22:46:48 +0000 Subject: [PATCH] =?UTF-8?q?GUEST:=20step=201=20=E2=80=94=20lib/guest/confo?= =?UTF-8?q?rmance.{sx,sh}=20config-driven=20driver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracted the duplicated conformance plumbing into a single driver: - lib/guest/conformance.sx — two helper fns that emit (gc-result NAME P F T) lines for the bash side to grep: gc-dict-result for runners returning a {:passed :failed :total} dict, and gc-counters-result for guests that bump a global pass/fail counter from a test file load. - lib/guest/conformance.sh — config-driven bash driver. Sources a per-lang conf, locates sx_server, runs sx_server in either single-session "dict" mode (one preload + many suite evals) or per-suite "counters" mode (fresh sx_server per suite, with shared preloads). Aggregates and writes scoreboard.{json,md} via per-lang emit_scoreboard_* functions. - Ported lib/prolog/conformance.sh and lib/haskell/conformance.sh down to one-line wrappers that exec the shared driver against their .conf file. Verification: - Prolog: 590/590 — diff vs baseline is timestamp-only. - Haskell: 156/156 — significantly higher than the 0/18 in baseline. The old conformance.sh was buggy (its `(ok-len 3 ...)` grep never matched, defaulting every program to 0 pass / 1 fail). Updated baseline to the true count; no actual test regressed. Plan baseline cell updated. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/guest/baseline/haskell.json | 119 ++++++++--------- lib/guest/conformance.sh | 221 ++++++++++++++++++++++++++++++++ lib/guest/conformance.sx | 40 ++++++ lib/haskell/conformance.conf | 76 +++++++++++ lib/haskell/conformance.sh | 141 +------------------- lib/haskell/scoreboard.json | 40 +++--- lib/haskell/scoreboard.md | 38 +++--- lib/prolog/conformance.conf | 80 ++++++++++++ lib/prolog/conformance.sh | 130 +------------------ lib/prolog/scoreboard.json | 2 +- lib/prolog/scoreboard.md | 2 +- 11 files changed, 522 insertions(+), 367 deletions(-) create mode 100755 lib/guest/conformance.sh create mode 100644 lib/guest/conformance.sx create mode 100644 lib/haskell/conformance.conf create mode 100644 lib/prolog/conformance.conf diff --git a/lib/guest/baseline/haskell.json b/lib/guest/baseline/haskell.json index 5bc88e71..16e94e30 100644 --- a/lib/guest/baseline/haskell.json +++ b/lib/guest/baseline/haskell.json @@ -1,121 +1,122 @@ { "lang": "haskell", - "captured": "2026-05-06T22:01:00Z", + "captured": "2026-05-06T22:46:16Z", "suite_command": "bash lib/haskell/conformance.sh", "totals": { - "pass": 0, - "fail": 18, - "total": 18 + "pass": 156, + "fail": 0, + "total": 156 }, "suites": [ { "name": "fib", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 2, + "fail": 0, + "total": 2 }, { "name": "sieve", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 2, + "fail": 0, + "total": 2 }, { "name": "quicksort", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 5, + "fail": 0, + "total": 5 }, { "name": "nqueens", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 2, + "fail": 0, + "total": 2 }, { "name": "calculator", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 5, + "fail": 0, + "total": 5 }, { "name": "collatz", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 11, + "fail": 0, + "total": 11 }, { "name": "palindrome", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 8, + "fail": 0, + "total": 8 }, { "name": "maybe", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 12, + "fail": 0, + "total": 12 }, { "name": "fizzbuzz", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 12, + "fail": 0, + "total": 12 }, { "name": "anagram", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 9, + "fail": 0, + "total": 9 }, { "name": "roman", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 14, + "fail": 0, + "total": 14 }, { "name": "binary", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 12, + "fail": 0, + "total": 12 }, { "name": "either", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 12, + "fail": 0, + "total": 12 }, { "name": "primes", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 12, + "fail": 0, + "total": 12 }, { "name": "zipwith", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 9, + "fail": 0, + "total": 9 }, { "name": "matrix", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 8, + "fail": 0, + "total": 8 }, { "name": "wordcount", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 7, + "fail": 0, + "total": 7 }, { "name": "powers", - "pass": 0, - "fail": 1, - "total": 1 + "pass": 14, + "fail": 0, + "total": 14 } ], - "source_scoreboard": "lib/haskell/scoreboard.json" + "source_scoreboard": "lib/haskell/scoreboard.json", + "note": "Step 1: previous baseline (0/18) was an artefact of the old conformance.sh bug \u2014 its (ok-len 3 ...) grep never matched, defaulting every program to 0 pass / 1 fail. Shared driver in Step 1 reads counters correctly." } diff --git a/lib/guest/conformance.sh b/lib/guest/conformance.sh new file mode 100755 index 00000000..7f0c6509 --- /dev/null +++ b/lib/guest/conformance.sh @@ -0,0 +1,221 @@ +#!/usr/bin/env bash +# lib/guest/conformance.sh — shared, config-driven conformance driver. +# +# Usage: +# bash lib/guest/conformance.sh +# +# The conf file is a bash file that sets: +# LANG_NAME e.g. prolog +# PRELOADS=( ... ) .sx files to load before any suite (path from repo root) +# SUITES=( ... ) colon-separated entries; format depends on MODE +# MODE "dict" or "counters" +# COUNTERS_PASS (counters mode) global symbol for the pass counter +# COUNTERS_FAIL (counters mode) global symbol for the fail counter +# TIMEOUT_PER_SUITE (optional, counters mode) seconds per suite, default 120 +# SCOREBOARD_DIR (optional) defaults to lib/$LANG_NAME +# +# It may override the bash functions emit_scoreboard_json / emit_scoreboard_md +# to produce the per-language scoreboard schema. Defaults are provided. +# +# Suite formats: +# MODE=dict — "name:test-file:(runner-fn)" +# The runner expression is evaluated and is expected to +# return a dict with :passed/:failed/:total. +# MODE=counters — "name:test-file" +# Each suite is run in a fresh sx_server session: preloads +# are loaded, then the test file, then counters are read. +# The suite is treated as starting from counters (0, 0). +# +# Output: +# Writes $SCOREBOARD_DIR/scoreboard.json and $SCOREBOARD_DIR/scoreboard.md. +# Exits 0 if every suite is green, 1 otherwise. + +set -uo pipefail +cd "$(git rev-parse --show-toplevel)" + +if [ "$#" -lt 1 ]; then + echo "usage: $0 " >&2 + exit 2 +fi + +CONF="$1" +if [ ! -f "$CONF" ]; then + echo "config not found: $CONF" >&2 + exit 2 +fi + +# Defaults — the conf file may override these. +LANG_NAME= +PRELOADS=() +SUITES=() +MODE=dict +COUNTERS_PASS= +COUNTERS_FAIL= +TIMEOUT_PER_SUITE=120 +SCOREBOARD_DIR= + +emit_scoreboard_json() { + # Generic schema. Per-lang configs override this for byte-equality with + # historical scoreboards. + local n=${#GC_NAMES[@]} i sep + printf '{\n' + printf ' "lang": "%s",\n' "$LANG_NAME" + printf ' "total_passed": %d,\n' "$GC_TOTAL_PASS" + printf ' "total_failed": %d,\n' "$GC_TOTAL_FAIL" + printf ' "total": %d,\n' "$GC_TOTAL" + printf ' "suites": [' + for ((i=0; i/dev/null || date)" + printf '}\n' +} + +emit_scoreboard_md() { + local n=${#GC_NAMES[@]} i status + printf '# %s scoreboard\n\n' "$LANG_NAME" + printf '**%d / %d passing** (%d failure(s)).\n\n' "$GC_TOTAL_PASS" "$GC_TOTAL" "$GC_TOTAL_FAIL" + printf '| Suite | Passed | Total | Status |\n' + printf '|-------|--------|-------|--------|\n' + for ((i=0; i&2 + exit 2 +fi +SCOREBOARD_DIR="${SCOREBOARD_DIR:-lib/$LANG_NAME}" + +SX="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}" +if [ ! -x "$SX" ]; then + MAIN_ROOT=$(git worktree list 2>/dev/null | head -1 | awk '{print $1}') + if [ -n "${MAIN_ROOT:-}" ] && [ -x "$MAIN_ROOT/$SX" ]; then + SX="$MAIN_ROOT/$SX" + else + echo "ERROR: sx_server.exe not found (set SX_SERVER to override)." >&2 + exit 2 + fi +fi + +GC_NAMES=() +GC_PASS=() +GC_FAIL=() +GC_TOTAL_S=() + +parse_result_line() { + # Match a (gc-result "name" P F T) line. + local line="$1" + if [[ "$line" =~ ^\(gc-result\ \"([^\"]+)\"\ ([0-9]+)\ ([0-9]+)\ ([0-9]+)\)$ ]]; then + GC_NAMES+=("${BASH_REMATCH[1]}") + GC_PASS+=("${BASH_REMATCH[2]}") + GC_FAIL+=("${BASH_REMATCH[3]}") + GC_TOTAL_S+=("${BASH_REMATCH[4]}") + return 0 + fi + return 1 +} + +case "$MODE" in + dict) + SCRIPT='(epoch 1) +' + for f in "${PRELOADS[@]}"; do + SCRIPT+='(load "'"$f"'") +' + done + SCRIPT+='(load "lib/guest/conformance.sx") +' + for entry in "${SUITES[@]}"; do + IFS=: read -r _ file _ <<< "$entry" + SCRIPT+='(load "'"$file"'") +' + done + SCRIPT+='(epoch 2) +' + for entry in "${SUITES[@]}"; do + IFS=: read -r name _ runner <<< "$entry" + SCRIPT+='(eval "(gc-dict-result \"'"$name"'\" '"$runner"')") +' + done + OUTPUT=$(printf '%s' "$SCRIPT" | "$SX" 2>&1) + expected=${#SUITES[@]} + matched=0 + while IFS= read -r line; do + if parse_result_line "$line"; then + matched=$((matched + 1)) + fi + done <<< "$OUTPUT" + if [ "$matched" -ne "$expected" ]; then + echo "Expected $expected suite results, got $matched" >&2 + echo "---- raw output ----" >&2 + printf '%s\n' "$OUTPUT" >&2 + exit 3 + fi + ;; + counters) + if [ -z "$COUNTERS_PASS" ] || [ -z "$COUNTERS_FAIL" ]; then + echo "MODE=counters requires COUNTERS_PASS and COUNTERS_FAIL in $CONF" >&2 + exit 2 + fi + for entry in "${SUITES[@]}"; do + IFS=: read -r name file <<< "$entry" + TMPFILE=$(mktemp) + { + printf '(epoch 1)\n' + for f in "${PRELOADS[@]}"; do printf '(load "%s")\n' "$f"; done + printf '(load "lib/guest/conformance.sx")\n' + printf '(epoch 2)\n' + printf '(load "%s")\n' "$file" + printf '(epoch 3)\n' + printf '(eval "(gc-counters-result \\"%s\\" 0 0 %s %s)")\n' \ + "$name" "$COUNTERS_PASS" "$COUNTERS_FAIL" + } > "$TMPFILE" + OUTPUT=$(timeout "$TIMEOUT_PER_SUITE" "$SX" < "$TMPFILE" 2>&1 || true) + rm -f "$TMPFILE" + result=$(printf '%s\n' "$OUTPUT" | grep -E '^\(gc-result ' | tail -1 || true) + if [ -n "$result" ] && parse_result_line "$result"; then + : + else + # Suite hung or crashed before emitting a result. Record 0/1 so it + # shows up as a failure rather than vanishing. + GC_NAMES+=("$name") + GC_PASS+=(0) + GC_FAIL+=(1) + GC_TOTAL_S+=(1) + fi + done + ;; + *) + echo "Unknown MODE=$MODE in $CONF (expected dict|counters)" >&2 + exit 2 + ;; +esac + +GC_TOTAL_PASS=0 +GC_TOTAL_FAIL=0 +GC_TOTAL=0 +for ((i=0; i<${#GC_NAMES[@]}; i++)); do + GC_TOTAL_PASS=$((GC_TOTAL_PASS + GC_PASS[i])) + GC_TOTAL_FAIL=$((GC_TOTAL_FAIL + GC_FAIL[i])) + GC_TOTAL=$((GC_TOTAL + GC_TOTAL_S[i])) +done + +mkdir -p "$SCOREBOARD_DIR" +emit_scoreboard_json > "$SCOREBOARD_DIR/scoreboard.json" +emit_scoreboard_md > "$SCOREBOARD_DIR/scoreboard.md" + +if [ "$GC_TOTAL_FAIL" -gt 0 ]; then + echo "$GC_TOTAL_FAIL failure(s) across $GC_TOTAL tests" >&2 + exit 1 +fi +echo "All $GC_TOTAL tests pass." diff --git a/lib/guest/conformance.sx b/lib/guest/conformance.sx new file mode 100644 index 00000000..323bfb6f --- /dev/null +++ b/lib/guest/conformance.sx @@ -0,0 +1,40 @@ +;; lib/guest/conformance.sx — shared helpers for the guest conformance driver. +;; +;; The bash driver lib/guest/conformance.sh loads this file and then for each +;; suite emits an (eval "...") form whose result is a tagged list: +;; +;; (gc-result NAME PASSED FAILED TOTAL) +;; +;; The driver greps these from sx_server's output and aggregates them. +;; +;; Two suite shapes are supported: +;; +;; :dict — runner expression returns a dict with :passed/:failed/:total. +;; (gc-dict-result "parse" (pl-parse-tests-run!)) +;; +;; :counters — runner has no return value, mutates pass/fail global counters. +;; (gc-counters-result NAME P0 F0 PASS FAIL) +;; where P0/F0 are the counters captured BEFORE the suite ran +;; and PASS/FAIL are the counters AFTER. + +(define + gc-dict-result + (fn + (name r) + (list + (quote gc-result) + name + (get r :passed) + (get r :failed) + (get r :total)))) + +(define + gc-counters-result + (fn + (name p0 f0 p1 f1) + (list + (quote gc-result) + name + (- p1 p0) + (- f1 f0) + (- (+ p1 f1) (+ p0 f0))))) diff --git a/lib/haskell/conformance.conf b/lib/haskell/conformance.conf new file mode 100644 index 00000000..ab67d88d --- /dev/null +++ b/lib/haskell/conformance.conf @@ -0,0 +1,76 @@ +# Haskell-on-SX conformance config — sourced by lib/guest/conformance.sh. + +LANG_NAME=haskell +MODE=counters +COUNTERS_PASS=hk-test-pass +COUNTERS_FAIL=hk-test-fail +TIMEOUT_PER_SUITE=120 + +PRELOADS=( + lib/haskell/tokenizer.sx + lib/haskell/layout.sx + lib/haskell/parser.sx + lib/haskell/desugar.sx + lib/haskell/runtime.sx + lib/haskell/match.sx + lib/haskell/eval.sx + lib/haskell/testlib.sx +) + +SUITES=( + "fib:lib/haskell/tests/program-fib.sx" + "sieve:lib/haskell/tests/program-sieve.sx" + "quicksort:lib/haskell/tests/program-quicksort.sx" + "nqueens:lib/haskell/tests/program-nqueens.sx" + "calculator:lib/haskell/tests/program-calculator.sx" + "collatz:lib/haskell/tests/program-collatz.sx" + "palindrome:lib/haskell/tests/program-palindrome.sx" + "maybe:lib/haskell/tests/program-maybe.sx" + "fizzbuzz:lib/haskell/tests/program-fizzbuzz.sx" + "anagram:lib/haskell/tests/program-anagram.sx" + "roman:lib/haskell/tests/program-roman.sx" + "binary:lib/haskell/tests/program-binary.sx" + "either:lib/haskell/tests/program-either.sx" + "primes:lib/haskell/tests/program-primes.sx" + "zipwith:lib/haskell/tests/program-zipwith.sx" + "matrix:lib/haskell/tests/program-matrix.sx" + "wordcount:lib/haskell/tests/program-wordcount.sx" + "powers:lib/haskell/tests/program-powers.sx" +) + +emit_scoreboard_json() { + local n=${#GC_NAMES[@]} i sep date_only + date_only=$(date '+%Y-%m-%d') + printf '{\n' + printf ' "date": "%s",\n' "$date_only" + printf ' "total_pass": %d,\n' "$GC_TOTAL_PASS" + printf ' "total_fail": %d,\n' "$GC_TOTAL_FAIL" + printf ' "programs": {\n' + for ((i=0; i "$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 6 (prelude extras + 18 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 ] +# Thin wrapper — see lib/guest/conformance.sh and lib/haskell/conformance.conf. +exec bash "$(dirname "$0")/../guest/conformance.sh" "$(dirname "$0")/conformance.conf" "$@" diff --git a/lib/haskell/scoreboard.json b/lib/haskell/scoreboard.json index de023105..6f7884c9 100644 --- a/lib/haskell/scoreboard.json +++ b/lib/haskell/scoreboard.json @@ -1,25 +1,25 @@ { "date": "2026-05-06", - "total_pass": 0, - "total_fail": 18, + "total_pass": 156, + "total_fail": 0, "programs": { - "fib": {"pass": 0, "fail": 1}, - "sieve": {"pass": 0, "fail": 1}, - "quicksort": {"pass": 0, "fail": 1}, - "nqueens": {"pass": 0, "fail": 1}, - "calculator": {"pass": 0, "fail": 1}, - "collatz": {"pass": 0, "fail": 1}, - "palindrome": {"pass": 0, "fail": 1}, - "maybe": {"pass": 0, "fail": 1}, - "fizzbuzz": {"pass": 0, "fail": 1}, - "anagram": {"pass": 0, "fail": 1}, - "roman": {"pass": 0, "fail": 1}, - "binary": {"pass": 0, "fail": 1}, - "either": {"pass": 0, "fail": 1}, - "primes": {"pass": 0, "fail": 1}, - "zipwith": {"pass": 0, "fail": 1}, - "matrix": {"pass": 0, "fail": 1}, - "wordcount": {"pass": 0, "fail": 1}, - "powers": {"pass": 0, "fail": 1} + "fib": {"pass": 2, "fail": 0}, + "sieve": {"pass": 2, "fail": 0}, + "quicksort": {"pass": 5, "fail": 0}, + "nqueens": {"pass": 2, "fail": 0}, + "calculator": {"pass": 5, "fail": 0}, + "collatz": {"pass": 11, "fail": 0}, + "palindrome": {"pass": 8, "fail": 0}, + "maybe": {"pass": 12, "fail": 0}, + "fizzbuzz": {"pass": 12, "fail": 0}, + "anagram": {"pass": 9, "fail": 0}, + "roman": {"pass": 14, "fail": 0}, + "binary": {"pass": 12, "fail": 0}, + "either": {"pass": 12, "fail": 0}, + "primes": {"pass": 12, "fail": 0}, + "zipwith": {"pass": 9, "fail": 0}, + "matrix": {"pass": 8, "fail": 0}, + "wordcount": {"pass": 7, "fail": 0}, + "powers": {"pass": 14, "fail": 0} } } diff --git a/lib/haskell/scoreboard.md b/lib/haskell/scoreboard.md index 7f0407e8..500f8394 100644 --- a/lib/haskell/scoreboard.md +++ b/lib/haskell/scoreboard.md @@ -4,22 +4,22 @@ Updated 2026-05-06 · Phase 6 (prelude extras + 18 programs) | Program | Tests | Status | |---------|-------|--------| -| fib.hs | 0/1 | ✗ | -| sieve.hs | 0/1 | ✗ | -| quicksort.hs | 0/1 | ✗ | -| nqueens.hs | 0/1 | ✗ | -| calculator.hs | 0/1 | ✗ | -| collatz.hs | 0/1 | ✗ | -| palindrome.hs | 0/1 | ✗ | -| maybe.hs | 0/1 | ✗ | -| fizzbuzz.hs | 0/1 | ✗ | -| anagram.hs | 0/1 | ✗ | -| roman.hs | 0/1 | ✗ | -| binary.hs | 0/1 | ✗ | -| either.hs | 0/1 | ✗ | -| primes.hs | 0/1 | ✗ | -| zipwith.hs | 0/1 | ✗ | -| matrix.hs | 0/1 | ✗ | -| wordcount.hs | 0/1 | ✗ | -| powers.hs | 0/1 | ✗ | -| **Total** | **0/18** | **0/18 programs** | +| fib.hs | 2/2 | ✓ | +| sieve.hs | 2/2 | ✓ | +| quicksort.hs | 5/5 | ✓ | +| nqueens.hs | 2/2 | ✓ | +| calculator.hs | 5/5 | ✓ | +| collatz.hs | 11/11 | ✓ | +| palindrome.hs | 8/8 | ✓ | +| maybe.hs | 12/12 | ✓ | +| fizzbuzz.hs | 12/12 | ✓ | +| anagram.hs | 9/9 | ✓ | +| roman.hs | 14/14 | ✓ | +| binary.hs | 12/12 | ✓ | +| either.hs | 12/12 | ✓ | +| primes.hs | 12/12 | ✓ | +| zipwith.hs | 9/9 | ✓ | +| matrix.hs | 8/8 | ✓ | +| wordcount.hs | 7/7 | ✓ | +| powers.hs | 14/14 | ✓ | +| **Total** | **156/156** | **18/18 programs** | diff --git a/lib/prolog/conformance.conf b/lib/prolog/conformance.conf new file mode 100644 index 00000000..e31e74f0 --- /dev/null +++ b/lib/prolog/conformance.conf @@ -0,0 +1,80 @@ +# Prolog conformance config — sourced by lib/guest/conformance.sh. + +LANG_NAME=prolog +MODE=dict + +PRELOADS=( + lib/prolog/tokenizer.sx + lib/prolog/parser.sx + lib/prolog/runtime.sx + lib/prolog/query.sx + lib/prolog/compiler.sx + lib/prolog/hs-bridge.sx +) + +SUITES=( + "parse:lib/prolog/tests/parse.sx:(pl-parse-tests-run!)" + "unify:lib/prolog/tests/unify.sx:(pl-unify-tests-run!)" + "clausedb:lib/prolog/tests/clausedb.sx:(pl-clausedb-tests-run!)" + "solve:lib/prolog/tests/solve.sx:(pl-solve-tests-run!)" + "operators:lib/prolog/tests/operators.sx:(pl-operators-tests-run!)" + "dynamic:lib/prolog/tests/dynamic.sx:(pl-dynamic-tests-run!)" + "findall:lib/prolog/tests/findall.sx:(pl-findall-tests-run!)" + "term_inspect:lib/prolog/tests/term_inspect.sx:(pl-term-inspect-tests-run!)" + "append:lib/prolog/tests/programs/append.sx:(pl-append-tests-run!)" + "reverse:lib/prolog/tests/programs/reverse.sx:(pl-reverse-tests-run!)" + "member:lib/prolog/tests/programs/member.sx:(pl-member-tests-run!)" + "nqueens:lib/prolog/tests/programs/nqueens.sx:(pl-nqueens-tests-run!)" + "family:lib/prolog/tests/programs/family.sx:(pl-family-tests-run!)" + "atoms:lib/prolog/tests/atoms.sx:(pl-atom-tests-run!)" + "query_api:lib/prolog/tests/query_api.sx:(pl-query-api-tests-run!)" + "iso_predicates:lib/prolog/tests/iso_predicates.sx:(pl-iso-predicates-tests-run!)" + "meta_predicates:lib/prolog/tests/meta_predicates.sx:(pl-meta-predicates-tests-run!)" + "list_predicates:lib/prolog/tests/list_predicates.sx:(pl-list-predicates-tests-run!)" + "meta_call:lib/prolog/tests/meta_call.sx:(pl-meta-call-tests-run!)" + "set_predicates:lib/prolog/tests/set_predicates.sx:(pl-set-predicates-tests-run!)" + "char_predicates:lib/prolog/tests/char_predicates.sx:(pl-char-predicates-tests-run!)" + "io_predicates:lib/prolog/tests/io_predicates.sx:(pl-io-predicates-tests-run!)" + "assert_rules:lib/prolog/tests/assert_rules.sx:(pl-assert-rules-tests-run!)" + "string_agg:lib/prolog/tests/string_agg.sx:(pl-string-agg-tests-run!)" + "advanced:lib/prolog/tests/advanced.sx:(pl-advanced-tests-run!)" + "compiler:lib/prolog/tests/compiler.sx:(pl-compiler-tests-run!)" + "cross_validate:lib/prolog/tests/cross_validate.sx:(pl-cross-validate-tests-run!)" + "integration:lib/prolog/tests/integration.sx:(pl-integration-tests-run!)" + "hs_bridge:lib/prolog/tests/hs_bridge.sx:(pl-hs-bridge-tests-run!)" +) + +emit_scoreboard_json() { + local n=${#GC_NAMES[@]} i sep + printf '{\n' + printf ' "total_passed": %d,\n' "$GC_TOTAL_PASS" + printf ' "total_failed": %d,\n' "$GC_TOTAL_FAIL" + printf ' "total": %d,\n' "$GC_TOTAL" + printf ' "suites": {' + for ((i=0; i/dev/null || date)" + printf '}\n' +} + +emit_scoreboard_md() { + local n=${#GC_NAMES[@]} i status when + when="$(date -Iseconds 2>/dev/null || date)" + printf '# Prolog scoreboard\n\n' + printf '**%d / %d passing** (%d failure(s)).\n' \ + "$GC_TOTAL_PASS" "$GC_TOTAL" "$GC_TOTAL_FAIL" + printf 'Generated %s.\n\n' "$when" + printf '| Suite | Passed | Total | Status |\n' + printf '|-------|--------|-------|--------|\n' + for ((i=0; i&2 - exit 2 -fi - -cd "$ROOT" - -# name : test-file : runner-fn -SUITES=( - "parse:lib/prolog/tests/parse.sx:pl-parse-tests-run!" - "unify:lib/prolog/tests/unify.sx:pl-unify-tests-run!" - "clausedb:lib/prolog/tests/clausedb.sx:pl-clausedb-tests-run!" - "solve:lib/prolog/tests/solve.sx:pl-solve-tests-run!" - "operators:lib/prolog/tests/operators.sx:pl-operators-tests-run!" - "dynamic:lib/prolog/tests/dynamic.sx:pl-dynamic-tests-run!" - "findall:lib/prolog/tests/findall.sx:pl-findall-tests-run!" - "term_inspect:lib/prolog/tests/term_inspect.sx:pl-term-inspect-tests-run!" - "append:lib/prolog/tests/programs/append.sx:pl-append-tests-run!" - "reverse:lib/prolog/tests/programs/reverse.sx:pl-reverse-tests-run!" - "member:lib/prolog/tests/programs/member.sx:pl-member-tests-run!" - "nqueens:lib/prolog/tests/programs/nqueens.sx:pl-nqueens-tests-run!" - "family:lib/prolog/tests/programs/family.sx:pl-family-tests-run!" - "atoms:lib/prolog/tests/atoms.sx:pl-atom-tests-run!" - "query_api:lib/prolog/tests/query_api.sx:pl-query-api-tests-run!" - "iso_predicates:lib/prolog/tests/iso_predicates.sx:pl-iso-predicates-tests-run!" - "meta_predicates:lib/prolog/tests/meta_predicates.sx:pl-meta-predicates-tests-run!" - "list_predicates:lib/prolog/tests/list_predicates.sx:pl-list-predicates-tests-run!" - "meta_call:lib/prolog/tests/meta_call.sx:pl-meta-call-tests-run!" - "set_predicates:lib/prolog/tests/set_predicates.sx:pl-set-predicates-tests-run!" - "char_predicates:lib/prolog/tests/char_predicates.sx:pl-char-predicates-tests-run!" - "io_predicates:lib/prolog/tests/io_predicates.sx:pl-io-predicates-tests-run!" - "assert_rules:lib/prolog/tests/assert_rules.sx:pl-assert-rules-tests-run!" - "string_agg:lib/prolog/tests/string_agg.sx:pl-string-agg-tests-run!" - "advanced:lib/prolog/tests/advanced.sx:pl-advanced-tests-run!" - "compiler:lib/prolog/tests/compiler.sx:pl-compiler-tests-run!" - "cross_validate:lib/prolog/tests/cross_validate.sx:pl-cross-validate-tests-run!" - "integration:lib/prolog/tests/integration.sx:pl-integration-tests-run!" - "hs_bridge:lib/prolog/tests/hs_bridge.sx:pl-hs-bridge-tests-run!" -) - -SCRIPT='(epoch 1) -(load "lib/prolog/tokenizer.sx") -(load "lib/prolog/parser.sx") -(load "lib/prolog/runtime.sx") -(load "lib/prolog/query.sx") -(load "lib/prolog/compiler.sx") -(load "lib/prolog/hs-bridge.sx")' -for entry in "${SUITES[@]}"; do - IFS=: read -r _ file _ <<< "$entry" - SCRIPT+=$'\n(load "'"$file"$'")' -done -for entry in "${SUITES[@]}"; do - IFS=: read -r _ _ fn <<< "$entry" - SCRIPT+=$'\n(eval "('"$fn"$')")' -done - -OUTPUT="$(printf '%s\n' "$SCRIPT" | "$SX" 2>&1)" - -mapfile -t LINES < <(printf '%s\n' "$OUTPUT" | grep -E '^\{:failed') - -if [[ ${#LINES[@]} -ne ${#SUITES[@]} ]]; then - echo "Expected ${#SUITES[@]} suite results, got ${#LINES[@]}" >&2 - echo "---- raw output ----" >&2 - printf '%s\n' "$OUTPUT" >&2 - exit 3 -fi - -TOTAL_PASS=0 -TOTAL_FAIL=0 -TOTAL=0 -JSON_SUITES="" -MD_ROWS="" - -for i in "${!SUITES[@]}"; do - IFS=: read -r name _ _ <<< "${SUITES[$i]}" - line="${LINES[$i]}" - passed=$(grep -oE ':passed [0-9]+' <<< "$line" | grep -oE '[0-9]+') - total=$(grep -oE ':total [0-9]+' <<< "$line" | grep -oE '[0-9]+') - failed=$(grep -oE ':failed [0-9]+' <<< "$line" | grep -oE '[0-9]+') - TOTAL_PASS=$((TOTAL_PASS + passed)) - TOTAL_FAIL=$((TOTAL_FAIL + failed)) - TOTAL=$((TOTAL + total)) - status="ok" - [[ "$failed" -gt 0 ]] && status="FAIL" - [[ -n "$JSON_SUITES" ]] && JSON_SUITES+="," - JSON_SUITES+="\"$name\":{\"passed\":$passed,\"total\":$total,\"failed\":$failed}" - MD_ROWS+="| $name | $passed | $total | $status |"$'\n' -done - -WHEN="$(date -Iseconds 2>/dev/null || date)" - -cat > "$HERE/scoreboard.json" < "$HERE/scoreboard.md" <&2 - exit 1 -fi - -echo "All $TOTAL tests pass." +# Thin wrapper — see lib/guest/conformance.sh and lib/prolog/conformance.conf. +exec bash "$(dirname "$0")/../guest/conformance.sh" "$(dirname "$0")/conformance.conf" "$@" diff --git a/lib/prolog/scoreboard.json b/lib/prolog/scoreboard.json index 837fc75c..2c1d47ab 100644 --- a/lib/prolog/scoreboard.json +++ b/lib/prolog/scoreboard.json @@ -3,5 +3,5 @@ "total_failed": 0, "total": 590, "suites": {"parse":{"passed":25,"total":25,"failed":0},"unify":{"passed":47,"total":47,"failed":0},"clausedb":{"passed":14,"total":14,"failed":0},"solve":{"passed":62,"total":62,"failed":0},"operators":{"passed":19,"total":19,"failed":0},"dynamic":{"passed":11,"total":11,"failed":0},"findall":{"passed":11,"total":11,"failed":0},"term_inspect":{"passed":14,"total":14,"failed":0},"append":{"passed":6,"total":6,"failed":0},"reverse":{"passed":6,"total":6,"failed":0},"member":{"passed":7,"total":7,"failed":0},"nqueens":{"passed":6,"total":6,"failed":0},"family":{"passed":10,"total":10,"failed":0},"atoms":{"passed":34,"total":34,"failed":0},"query_api":{"passed":16,"total":16,"failed":0},"iso_predicates":{"passed":29,"total":29,"failed":0},"meta_predicates":{"passed":25,"total":25,"failed":0},"list_predicates":{"passed":33,"total":33,"failed":0},"meta_call":{"passed":15,"total":15,"failed":0},"set_predicates":{"passed":15,"total":15,"failed":0},"char_predicates":{"passed":27,"total":27,"failed":0},"io_predicates":{"passed":24,"total":24,"failed":0},"assert_rules":{"passed":15,"total":15,"failed":0},"string_agg":{"passed":25,"total":25,"failed":0},"advanced":{"passed":21,"total":21,"failed":0},"compiler":{"passed":17,"total":17,"failed":0},"cross_validate":{"passed":17,"total":17,"failed":0},"integration":{"passed":20,"total":20,"failed":0},"hs_bridge":{"passed":19,"total":19,"failed":0}}, - "generated": "2026-05-06T21:34:26+00:00" + "generated": "2026-05-06T22:23:38+00:00" } diff --git a/lib/prolog/scoreboard.md b/lib/prolog/scoreboard.md index 96d43d57..d0d4d3c5 100644 --- a/lib/prolog/scoreboard.md +++ b/lib/prolog/scoreboard.md @@ -1,7 +1,7 @@ # Prolog scoreboard **590 / 590 passing** (0 failure(s)). -Generated 2026-05-06T21:34:26+00:00. +Generated 2026-05-06T22:23:38+00:00. | Suite | Passed | Total | Status | |-------|--------|-------|--------|