Files
rose-ash/lib/hyperscript/test.sh
giles 783ffc2ddd Fix JIT compile-let shadow binding: evaluate init before defining local
compile-let called scope-define-local eagerly as part of the let
binding, adding the new local to the scope before compile-expr ran
for the init expression. When nested lets rebound the same variable
(e.g. the hyperscript parser's 4 chained `parts` bindings), the init
expression resolved the name to the new uninitialized slot instead of
the outer one — producing nil where it should have read the previous
value.

Move scope-define-local after compile-expr so init expressions see the
outer scope's binding. Fixes all 11 JIT hyperscript parser failures.
3127/3127 JIT + non-JIT, 25/25 standalone hyperscript tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:03:12 +00:00

157 lines
5.5 KiB
Bash

#!/usr/bin/env bash
# Fast hyperscript test runner — pipes directly to sx_server.exe via epoch protocol.
# No MCP, no Docker, no web server. Runs in <2 seconds.
#
# Usage:
# bash lib/hyperscript/test.sh # run all tests
# bash lib/hyperscript/test.sh -v # verbose — show actual output
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
if [ ! -x "$SX_SERVER" ]; then
echo "ERROR: $SX_SERVER not found. Run: cd hosts/ocaml && dune build"
exit 1
fi
VERBOSE="${1:-}"
PASS=0
FAIL=0
ERRORS=""
TMPFILE=$(mktemp)
trap "rm -f $TMPFILE" EXIT
# ── Write epoch commands to temp file ─────────────────────────────
cat > "$TMPFILE" << 'EPOCHS'
(epoch 1)
(load "lib/r7rs.sx")
(epoch 2)
(load "lib/hyperscript/tokenizer.sx")
(epoch 3)
(load "lib/hyperscript/parser.sx")
(epoch 4)
(load "lib/hyperscript/compiler.sx")
(epoch 10)
(eval "(hs-compile \"on click add .red to me\")")
(epoch 11)
(eval "(hs-compile \"on click toggle .active on me\")")
(epoch 12)
(eval "(hs-compile \"on click add .red to me then remove .blue from me\")")
(epoch 13)
(eval "(hs-compile \"on click set my innerHTML to 'hello'\")")
(epoch 14)
(eval "(hs-compile \"on click log me\")")
(epoch 15)
(eval "(hs-compile \"add .highlight to me\")")
(epoch 16)
(eval "(hs-compile \"remove .highlight from me\")")
(epoch 17)
(eval "(hs-compile \"toggle .visible on me\")")
(epoch 18)
(eval "(hs-compile \"hide me\")")
(epoch 19)
(eval "(hs-compile \"show me\")")
(epoch 20)
(eval "(hs-compile \"wait 500ms\")")
(epoch 21)
(eval "(hs-compile \"wait 2s\")")
(epoch 22)
(eval "(hs-compile \"set x to 1 + 2\")")
(epoch 23)
(eval "(hs-compile \"set x to 3 * 4\")")
(epoch 24)
(eval "(hs-compile \"init add .loaded to me end\")")
(epoch 25)
(eval "(hs-compile \"set x to 42\")")
(epoch 26)
(eval "(hs-compile \"put 'hello' into me\")")
(epoch 27)
(eval "(hs-compile \"increment x\")")
(epoch 28)
(eval "(hs-compile \"decrement x\")")
(epoch 29)
(eval "(hs-compile \"on every click log me end\")")
(epoch 30)
(eval "(hs-compile \"on click from .btn log me end\")")
(epoch 40)
(eval "(hs-to-sx-from-source \"on click add .red to me\")")
(epoch 41)
(eval "(hs-to-sx-from-source \"on click toggle .active on me\")")
(epoch 42)
(eval "(hs-to-sx-from-source \"on click set my innerHTML to 'hello'\")")
(epoch 43)
(eval "(hs-to-sx-from-source \"on click add .red to me then remove .blue from me\")")
EPOCHS
# ── Run ───────────────────────────────────────────────────────────
OUTPUT=$(timeout 30 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
# ── Check function ────────────────────────────────────────────────
check() {
local epoch="$1" desc="$2" expected="$3"
local actual
actual=$(echo "$OUTPUT" | grep -A1 "^(ok-len $epoch " | tail -1)
if [ -z "$actual" ]; then
actual=$(echo "$OUTPUT" | grep "^(ok $epoch " || true)
fi
if [ -z "$actual" ]; then
actual=$(echo "$OUTPUT" | grep "^(error $epoch " || true)
fi
if [ -z "$actual" ]; then
actual="<no output for epoch $epoch>"
fi
if echo "$actual" | grep -qF "$expected"; then
PASS=$((PASS + 1))
[ "$VERBOSE" = "-v" ] && echo "$desc"
else
FAIL=$((FAIL + 1))
ERRORS+="$desc (epoch $epoch)
expected: $expected
actual: $actual
"
fi
}
# ── Parser assertions ─────────────────────────────────────────────
check 10 "on click basic" '(on "click" (add-class "red" (me)))'
check 11 "on click toggle" '(on "click" (toggle-class "active" (me)))'
check 12 "on click chain" '(on "click" (do (add-class "red" (me)) (remove-class "blue" (me))))'
check 13 "on click set prop" '(on "click" (set!'
check 14 "on click log" '(on "click" (log (me)))'
check 15 "add class cmd" '(add-class "highlight" (me))'
check 16 "remove class cmd" '(remove-class "highlight" (me))'
check 17 "toggle class cmd" '(toggle-class "visible" (me))'
check 18 "hide cmd" '(hide (me))'
check 19 "show cmd" '(show (me))'
check 20 "wait ms" '(wait 500)'
check 21 "wait seconds" '(wait 2000)'
check 22 "arithmetic add" '+'
check 23 "arithmetic mul" '*'
check 24 "init feature" '(init'
check 25 "set variable" '(set! (ref "x") 42)'
check 26 "put into" '(set! (me) "hello")'
check 27 "increment" 'increment'
check 28 "decrement" 'decrement'
check 29 "on every click" '(on'
check 30 "on click from" '(on'
# ── Compiler assertions ───────────────────────────────────────────
check 40 "compiled: on click" '(hs-on me "click"'
check 41 "compiled: toggle" 'hs-toggle-class!'
check 42 "compiled: set prop" 'dom-set-prop'
check 43 "compiled: chain" 'dom-remove-class'
# ── Report ────────────────────────────────────────────────────────
TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then
echo "$PASS/$TOTAL hyperscript tests passed"
else
echo "$PASS/$TOTAL passed, $FAIL failed:"
echo ""
echo "$ERRORS"
fi
[ $FAIL -eq 0 ]