GUEST: step 1 — lib/guest/conformance.{sx,sh} config-driven driver
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) <noreply@anthropic.com>
This commit is contained in:
@@ -1,121 +1,122 @@
|
|||||||
{
|
{
|
||||||
"lang": "haskell",
|
"lang": "haskell",
|
||||||
"captured": "2026-05-06T22:01:00Z",
|
"captured": "2026-05-06T22:46:16Z",
|
||||||
"suite_command": "bash lib/haskell/conformance.sh",
|
"suite_command": "bash lib/haskell/conformance.sh",
|
||||||
"totals": {
|
"totals": {
|
||||||
"pass": 0,
|
"pass": 156,
|
||||||
"fail": 18,
|
"fail": 0,
|
||||||
"total": 18
|
"total": 156
|
||||||
},
|
},
|
||||||
"suites": [
|
"suites": [
|
||||||
{
|
{
|
||||||
"name": "fib",
|
"name": "fib",
|
||||||
"pass": 0,
|
"pass": 2,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sieve",
|
"name": "sieve",
|
||||||
"pass": 0,
|
"pass": 2,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "quicksort",
|
"name": "quicksort",
|
||||||
"pass": 0,
|
"pass": 5,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nqueens",
|
"name": "nqueens",
|
||||||
"pass": 0,
|
"pass": 2,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "calculator",
|
"name": "calculator",
|
||||||
"pass": 0,
|
"pass": 5,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "collatz",
|
"name": "collatz",
|
||||||
"pass": 0,
|
"pass": 11,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 11
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "palindrome",
|
"name": "palindrome",
|
||||||
"pass": 0,
|
"pass": 8,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "maybe",
|
"name": "maybe",
|
||||||
"pass": 0,
|
"pass": 12,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 12
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fizzbuzz",
|
"name": "fizzbuzz",
|
||||||
"pass": 0,
|
"pass": 12,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 12
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "anagram",
|
"name": "anagram",
|
||||||
"pass": 0,
|
"pass": 9,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 9
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "roman",
|
"name": "roman",
|
||||||
"pass": 0,
|
"pass": 14,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 14
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "binary",
|
"name": "binary",
|
||||||
"pass": 0,
|
"pass": 12,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 12
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "either",
|
"name": "either",
|
||||||
"pass": 0,
|
"pass": 12,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 12
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "primes",
|
"name": "primes",
|
||||||
"pass": 0,
|
"pass": 12,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 12
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "zipwith",
|
"name": "zipwith",
|
||||||
"pass": 0,
|
"pass": 9,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 9
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "matrix",
|
"name": "matrix",
|
||||||
"pass": 0,
|
"pass": 8,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "wordcount",
|
"name": "wordcount",
|
||||||
"pass": 0,
|
"pass": 7,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"total": 7
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "powers",
|
"name": "powers",
|
||||||
"pass": 0,
|
"pass": 14,
|
||||||
"fail": 1,
|
"fail": 0,
|
||||||
"total": 1
|
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
221
lib/guest/conformance.sh
Executable file
221
lib/guest/conformance.sh
Executable file
@@ -0,0 +1,221 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# lib/guest/conformance.sh — shared, config-driven conformance driver.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# bash lib/guest/conformance.sh <conf-file>
|
||||||
|
#
|
||||||
|
# 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 <conf-file>" >&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<n; i++)); do
|
||||||
|
sep=","; [ $i -eq $((n-1)) ] && sep=""
|
||||||
|
printf '\n {"name":"%s","passed":%d,"failed":%d,"total":%d}%s' \
|
||||||
|
"${GC_NAMES[$i]}" "${GC_PASS[$i]}" "${GC_FAIL[$i]}" "${GC_TOTAL_S[$i]}" "$sep"
|
||||||
|
done
|
||||||
|
printf '\n ],\n'
|
||||||
|
printf ' "generated": "%s"\n' "$(date -Iseconds 2>/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<n; i++)); do
|
||||||
|
status="ok"; [ "${GC_FAIL[$i]}" -gt 0 ] && status="FAIL"
|
||||||
|
printf '| %s | %d | %d | %s |\n' \
|
||||||
|
"${GC_NAMES[$i]}" "${GC_PASS[$i]}" "${GC_TOTAL_S[$i]}" "$status"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$CONF"
|
||||||
|
|
||||||
|
if [ -z "$LANG_NAME" ]; then
|
||||||
|
echo "LANG_NAME not set in $CONF" >&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."
|
||||||
40
lib/guest/conformance.sx
Normal file
40
lib/guest/conformance.sx
Normal file
@@ -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)))))
|
||||||
76
lib/haskell/conformance.conf
Normal file
76
lib/haskell/conformance.conf
Normal file
@@ -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<n; i++)); do
|
||||||
|
sep=","; [ $i -eq $((n-1)) ] && sep=""
|
||||||
|
printf ' "%s": {"pass": %d, "fail": %d}%s\n' \
|
||||||
|
"${GC_NAMES[$i]}" "${GC_PASS[$i]}" "${GC_FAIL[$i]}" "$sep"
|
||||||
|
done
|
||||||
|
printf ' }\n'
|
||||||
|
printf '}\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_scoreboard_md() {
|
||||||
|
local n=${#GC_NAMES[@]}
|
||||||
|
local i status p f t prog_pass=0 prog_total=$n date_only
|
||||||
|
date_only=$(date '+%Y-%m-%d')
|
||||||
|
for ((i=0; i<n; i++)); do
|
||||||
|
[ "${GC_FAIL[$i]}" -eq 0 ] && prog_pass=$((prog_pass + 1))
|
||||||
|
done
|
||||||
|
printf '# Haskell-on-SX Scoreboard\n\n'
|
||||||
|
printf 'Updated %s · Phase 6 (prelude extras + 18 programs)\n\n' "$date_only"
|
||||||
|
printf '| Program | Tests | Status |\n'
|
||||||
|
printf '|---------|-------|--------|\n'
|
||||||
|
for ((i=0; i<n; i++)); do
|
||||||
|
p=${GC_PASS[$i]}; f=${GC_FAIL[$i]}; t=${GC_TOTAL_S[$i]}
|
||||||
|
[ "$f" -eq 0 ] && status="✓" || status="✗"
|
||||||
|
printf '| %s.hs | %d/%d | %s |\n' "${GC_NAMES[$i]}" "$p" "$t" "$status"
|
||||||
|
done
|
||||||
|
printf '| **Total** | **%d/%d** | **%d/%d programs** |\n' \
|
||||||
|
"$GC_TOTAL_PASS" "$GC_TOTAL" "$prog_pass" "$prog_total"
|
||||||
|
}
|
||||||
@@ -1,140 +1,3 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# lib/haskell/conformance.sh — run the classic-program test suites.
|
# Thin wrapper — see lib/guest/conformance.sh and lib/haskell/conformance.conf.
|
||||||
# Writes lib/haskell/scoreboard.json and lib/haskell/scoreboard.md.
|
exec bash "$(dirname "$0")/../guest/conformance.sh" "$(dirname "$0")/conformance.conf" "$@"
|
||||||
#
|
|
||||||
# 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 collatz palindrome maybe fizzbuzz anagram roman binary either primes zipwith matrix wordcount powers)
|
|
||||||
PASS_COUNTS=()
|
|
||||||
FAIL_COUNTS=()
|
|
||||||
|
|
||||||
run_suite() {
|
|
||||||
local prog="$1"
|
|
||||||
local FILE="lib/haskell/tests/program-${prog}.sx"
|
|
||||||
local TMPFILE
|
|
||||||
TMPFILE=$(mktemp)
|
|
||||||
cat > "$TMPFILE" <<EPOCHS
|
|
||||||
(epoch 1)
|
|
||||||
(load "lib/haskell/tokenizer.sx")
|
|
||||||
(load "lib/haskell/layout.sx")
|
|
||||||
(load "lib/haskell/parser.sx")
|
|
||||||
(load "lib/haskell/desugar.sx")
|
|
||||||
(load "lib/haskell/runtime.sx")
|
|
||||||
(load "lib/haskell/match.sx")
|
|
||||||
(load "lib/haskell/eval.sx")
|
|
||||||
(load "lib/haskell/testlib.sx")
|
|
||||||
(epoch 2)
|
|
||||||
(load "$FILE")
|
|
||||||
(epoch 3)
|
|
||||||
(eval "(list hk-test-pass hk-test-fail)")
|
|
||||||
EPOCHS
|
|
||||||
local OUTPUT
|
|
||||||
OUTPUT=$(timeout 120 "$SX_SERVER" < "$TMPFILE" 2>&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 ]
|
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"date": "2026-05-06",
|
"date": "2026-05-06",
|
||||||
"total_pass": 0,
|
"total_pass": 156,
|
||||||
"total_fail": 18,
|
"total_fail": 0,
|
||||||
"programs": {
|
"programs": {
|
||||||
"fib": {"pass": 0, "fail": 1},
|
"fib": {"pass": 2, "fail": 0},
|
||||||
"sieve": {"pass": 0, "fail": 1},
|
"sieve": {"pass": 2, "fail": 0},
|
||||||
"quicksort": {"pass": 0, "fail": 1},
|
"quicksort": {"pass": 5, "fail": 0},
|
||||||
"nqueens": {"pass": 0, "fail": 1},
|
"nqueens": {"pass": 2, "fail": 0},
|
||||||
"calculator": {"pass": 0, "fail": 1},
|
"calculator": {"pass": 5, "fail": 0},
|
||||||
"collatz": {"pass": 0, "fail": 1},
|
"collatz": {"pass": 11, "fail": 0},
|
||||||
"palindrome": {"pass": 0, "fail": 1},
|
"palindrome": {"pass": 8, "fail": 0},
|
||||||
"maybe": {"pass": 0, "fail": 1},
|
"maybe": {"pass": 12, "fail": 0},
|
||||||
"fizzbuzz": {"pass": 0, "fail": 1},
|
"fizzbuzz": {"pass": 12, "fail": 0},
|
||||||
"anagram": {"pass": 0, "fail": 1},
|
"anagram": {"pass": 9, "fail": 0},
|
||||||
"roman": {"pass": 0, "fail": 1},
|
"roman": {"pass": 14, "fail": 0},
|
||||||
"binary": {"pass": 0, "fail": 1},
|
"binary": {"pass": 12, "fail": 0},
|
||||||
"either": {"pass": 0, "fail": 1},
|
"either": {"pass": 12, "fail": 0},
|
||||||
"primes": {"pass": 0, "fail": 1},
|
"primes": {"pass": 12, "fail": 0},
|
||||||
"zipwith": {"pass": 0, "fail": 1},
|
"zipwith": {"pass": 9, "fail": 0},
|
||||||
"matrix": {"pass": 0, "fail": 1},
|
"matrix": {"pass": 8, "fail": 0},
|
||||||
"wordcount": {"pass": 0, "fail": 1},
|
"wordcount": {"pass": 7, "fail": 0},
|
||||||
"powers": {"pass": 0, "fail": 1}
|
"powers": {"pass": 14, "fail": 0}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,22 +4,22 @@ Updated 2026-05-06 · Phase 6 (prelude extras + 18 programs)
|
|||||||
|
|
||||||
| Program | Tests | Status |
|
| Program | Tests | Status |
|
||||||
|---------|-------|--------|
|
|---------|-------|--------|
|
||||||
| fib.hs | 0/1 | ✗ |
|
| fib.hs | 2/2 | ✓ |
|
||||||
| sieve.hs | 0/1 | ✗ |
|
| sieve.hs | 2/2 | ✓ |
|
||||||
| quicksort.hs | 0/1 | ✗ |
|
| quicksort.hs | 5/5 | ✓ |
|
||||||
| nqueens.hs | 0/1 | ✗ |
|
| nqueens.hs | 2/2 | ✓ |
|
||||||
| calculator.hs | 0/1 | ✗ |
|
| calculator.hs | 5/5 | ✓ |
|
||||||
| collatz.hs | 0/1 | ✗ |
|
| collatz.hs | 11/11 | ✓ |
|
||||||
| palindrome.hs | 0/1 | ✗ |
|
| palindrome.hs | 8/8 | ✓ |
|
||||||
| maybe.hs | 0/1 | ✗ |
|
| maybe.hs | 12/12 | ✓ |
|
||||||
| fizzbuzz.hs | 0/1 | ✗ |
|
| fizzbuzz.hs | 12/12 | ✓ |
|
||||||
| anagram.hs | 0/1 | ✗ |
|
| anagram.hs | 9/9 | ✓ |
|
||||||
| roman.hs | 0/1 | ✗ |
|
| roman.hs | 14/14 | ✓ |
|
||||||
| binary.hs | 0/1 | ✗ |
|
| binary.hs | 12/12 | ✓ |
|
||||||
| either.hs | 0/1 | ✗ |
|
| either.hs | 12/12 | ✓ |
|
||||||
| primes.hs | 0/1 | ✗ |
|
| primes.hs | 12/12 | ✓ |
|
||||||
| zipwith.hs | 0/1 | ✗ |
|
| zipwith.hs | 9/9 | ✓ |
|
||||||
| matrix.hs | 0/1 | ✗ |
|
| matrix.hs | 8/8 | ✓ |
|
||||||
| wordcount.hs | 0/1 | ✗ |
|
| wordcount.hs | 7/7 | ✓ |
|
||||||
| powers.hs | 0/1 | ✗ |
|
| powers.hs | 14/14 | ✓ |
|
||||||
| **Total** | **0/18** | **0/18 programs** |
|
| **Total** | **156/156** | **18/18 programs** |
|
||||||
|
|||||||
80
lib/prolog/conformance.conf
Normal file
80
lib/prolog/conformance.conf
Normal file
@@ -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<n; i++)); do
|
||||||
|
sep=","; [ $i -eq $((n-1)) ] && sep=""
|
||||||
|
printf '"%s":{"passed":%d,"total":%d,"failed":%d}%s' \
|
||||||
|
"${GC_NAMES[$i]}" "${GC_PASS[$i]}" "${GC_TOTAL_S[$i]}" "${GC_FAIL[$i]}" "$sep"
|
||||||
|
done
|
||||||
|
printf '},\n'
|
||||||
|
printf ' "generated": "%s"\n' "$(date -Iseconds 2>/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<n; i++)); do
|
||||||
|
status="ok"; [ "${GC_FAIL[$i]}" -gt 0 ] && status="FAIL"
|
||||||
|
printf '| %s | %d | %d | %s |\n' \
|
||||||
|
"${GC_NAMES[$i]}" "${GC_PASS[$i]}" "${GC_TOTAL_S[$i]}" "$status"
|
||||||
|
done
|
||||||
|
printf '\nRun `bash lib/prolog/conformance.sh` to refresh. Override the binary\n'
|
||||||
|
printf 'with `SX_SERVER=path/to/sx_server.exe bash …`.\n'
|
||||||
|
}
|
||||||
@@ -1,129 +1,3 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Run every Prolog test suite via sx_server and refresh scoreboard.{json,md}.
|
# Thin wrapper — see lib/guest/conformance.sh and lib/prolog/conformance.conf.
|
||||||
# Exit 0 if all green, 1 if any failures.
|
exec bash "$(dirname "$0")/../guest/conformance.sh" "$(dirname "$0")/conformance.conf" "$@"
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
ROOT="$(cd "$HERE/../.." && pwd)"
|
|
||||||
SX="${SX_SERVER:-/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe}"
|
|
||||||
|
|
||||||
if [[ ! -x "$SX" ]]; then
|
|
||||||
echo "sx_server not found at $SX (set SX_SERVER env to override)" >&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" <<JSON
|
|
||||||
{
|
|
||||||
"total_passed": $TOTAL_PASS,
|
|
||||||
"total_failed": $TOTAL_FAIL,
|
|
||||||
"total": $TOTAL,
|
|
||||||
"suites": {$JSON_SUITES},
|
|
||||||
"generated": "$WHEN"
|
|
||||||
}
|
|
||||||
JSON
|
|
||||||
|
|
||||||
cat > "$HERE/scoreboard.md" <<MD
|
|
||||||
# Prolog scoreboard
|
|
||||||
|
|
||||||
**$TOTAL_PASS / $TOTAL passing** ($TOTAL_FAIL failure(s)).
|
|
||||||
Generated $WHEN.
|
|
||||||
|
|
||||||
| Suite | Passed | Total | Status |
|
|
||||||
|-------|--------|-------|--------|
|
|
||||||
$MD_ROWS
|
|
||||||
Run \`bash lib/prolog/conformance.sh\` to refresh. Override the binary
|
|
||||||
with \`SX_SERVER=path/to/sx_server.exe bash …\`.
|
|
||||||
MD
|
|
||||||
|
|
||||||
if [[ "$TOTAL_FAIL" -gt 0 ]]; then
|
|
||||||
echo "$TOTAL_FAIL failure(s) across $TOTAL tests" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "All $TOTAL tests pass."
|
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
"total_failed": 0,
|
"total_failed": 0,
|
||||||
"total": 590,
|
"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}},
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Prolog scoreboard
|
# Prolog scoreboard
|
||||||
|
|
||||||
**590 / 590 passing** (0 failure(s)).
|
**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 |
|
| Suite | Passed | Total | Status |
|
||||||
|-------|--------|-------|--------|
|
|-------|--------|-------|--------|
|
||||||
|
|||||||
Reference in New Issue
Block a user