From 912de5a2745e5d851816b7df7e8b81f23b8a8c8a Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 1 May 2026 22:49:38 +0000 Subject: [PATCH] phase-22 APL: runtime.sx vectors/bitwise/sets/reduce/format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lib/apl/runtime.sx (60 forms): - Core: apl-iota (1..N), apl-rho (shape), apl-at (1-indexed access). - Rank-polymorphic apl-dyadic/apl-monadic helpers: scalar×scalar, scalar×vector, vector×vector all supported uniformly. - Arithmetic: add/sub/mul/div/mod/pow/max/min, neg/abs/floor/ceil/sqrt. - Comparison: eq/neq/lt/le/gt/ge → 0/1 result vectors. - Boolean: and/or/not on 0/1 values, element-wise. - Bitwise: bitand/bitor/bitxor/bitnot/lshift/rshift — element-wise. - Reduction: reduce-add/mul/max/min/and/or; scan-add/mul. - Vector ops: reverse, cat (scalar/vector catenate), take (±N), drop (±N), rotate, compress (boolean mask), index (multi-index). - Set ops: member (∊, → 0/1), nub (∪, unique preserve-order), union, intersect (∩), without (~). All use SX make-set internally. - Format (⍕): vector → space-separated string, scalar → str. lib/apl/tests/runtime.sx + lib/apl/test.sh: 73/73 pass. Co-Authored-By: Claude Sonnet 4.6 --- lib/apl/runtime.sx | 289 ++++++++++++++++++++ lib/apl/test.sh | 51 ++++ lib/apl/tests/runtime.sx | 327 +++++++++++++++++++++++ plans/agent-briefings/primitives-loop.md | 4 +- 4 files changed, 670 insertions(+), 1 deletion(-) create mode 100644 lib/apl/runtime.sx create mode 100755 lib/apl/test.sh create mode 100644 lib/apl/tests/runtime.sx diff --git a/lib/apl/runtime.sx b/lib/apl/runtime.sx new file mode 100644 index 00000000..76c48ed7 --- /dev/null +++ b/lib/apl/runtime.sx @@ -0,0 +1,289 @@ +;; lib/apl/runtime.sx — APL primitives on SX +;; +;; APL vectors are represented as SX lists (functional, immutable results). +;; Operations are rank-polymorphic: scalar/vector arguments both accepted. +;; Index origin: 1 (traditional APL). +;; +;; Primitives used: +;; map (multi-arg, Phase 1) +;; bitwise-and/or/xor/not/arithmetic-shift (Phase 7) +;; make-set/set-member?/set-add!/set->list (Phase 18) + +;; --------------------------------------------------------------------------- +;; 1. Core vector constructors +;; --------------------------------------------------------------------------- + +;; ⍳N — iota: generate integer vector 1, 2, ..., N +(define + (apl-iota n) + (letrec + ((go (fn (i acc) (if (< i 1) acc (go (- i 1) (cons i acc)))))) + (go n (list)))) + +;; ⍴A — shape (length of a vector) +(define (apl-rho v) (if (list? v) (len v) 1)) + +;; A[I] — 1-indexed access +(define (apl-at v i) (nth v (- i 1))) + +;; Scalar predicate +(define (apl-scalar? v) (not (list? v))) + +;; --------------------------------------------------------------------------- +;; 2. Rank-polymorphic helpers +;; dyadic: scalar/vector × scalar/vector → scalar/vector +;; monadic: scalar/vector → scalar/vector +;; --------------------------------------------------------------------------- + +(define + (apl-dyadic op a b) + (cond + ((and (list? a) (list? b)) (map op a b)) + ((list? a) (map (fn (x) (op x b)) a)) + ((list? b) (map (fn (y) (op a y)) b)) + (else (op a b)))) + +(define (apl-monadic op a) (if (list? a) (map op a) (op a))) + +;; --------------------------------------------------------------------------- +;; 3. Arithmetic (element-wise, rank-polymorphic) +;; --------------------------------------------------------------------------- + +(define (apl-add a b) (apl-dyadic + a b)) +(define (apl-sub a b) (apl-dyadic - a b)) +(define (apl-mul a b) (apl-dyadic * a b)) +(define (apl-div a b) (apl-dyadic / a b)) +(define (apl-mod a b) (apl-dyadic modulo a b)) +(define (apl-pow a b) (apl-dyadic pow a b)) +(define (apl-max a b) (apl-dyadic (fn (x y) (if (> x y) x y)) a b)) +(define (apl-min a b) (apl-dyadic (fn (x y) (if (< x y) x y)) a b)) + +(define (apl-neg a) (apl-monadic (fn (x) (- 0 x)) a)) +(define (apl-abs a) (apl-monadic abs a)) +(define (apl-floor a) (apl-monadic floor a)) +(define (apl-ceil a) (apl-monadic ceil a)) +(define (apl-sqrt a) (apl-monadic sqrt a)) +(define (apl-exp a) (apl-monadic exp a)) +(define (apl-log a) (apl-monadic log a)) + +;; --------------------------------------------------------------------------- +;; 4. Comparison (element-wise, returns 0/1 booleans) +;; --------------------------------------------------------------------------- + +(define (apl-bool v) (if v 1 0)) + +(define (apl-eq a b) (apl-dyadic (fn (x y) (apl-bool (= x y))) a b)) +(define + (apl-neq a b) + (apl-dyadic (fn (x y) (apl-bool (not (= x y)))) a b)) +(define (apl-lt a b) (apl-dyadic (fn (x y) (apl-bool (< x y))) a b)) +(define (apl-le a b) (apl-dyadic (fn (x y) (apl-bool (<= x y))) a b)) +(define (apl-gt a b) (apl-dyadic (fn (x y) (apl-bool (> x y))) a b)) +(define (apl-ge a b) (apl-dyadic (fn (x y) (apl-bool (>= x y))) a b)) + +;; Boolean logic (0/1 vectors) +(define + (apl-and a b) + (apl-dyadic + (fn + (x y) + (if + (and (not (= x 0)) (not (= y 0))) + 1 + 0)) + a + b)) +(define + (apl-or a b) + (apl-dyadic + (fn + (x y) + (if + (or (not (= x 0)) (not (= y 0))) + 1 + 0)) + a + b)) +(define + (apl-not a) + (apl-monadic (fn (x) (if (= x 0) 1 0)) a)) + +;; --------------------------------------------------------------------------- +;; 5. Bitwise operations (element-wise) +;; --------------------------------------------------------------------------- + +(define (apl-bitand a b) (apl-dyadic bitwise-and a b)) +(define (apl-bitor a b) (apl-dyadic bitwise-or a b)) +(define (apl-bitxor a b) (apl-dyadic bitwise-xor a b)) +(define (apl-bitnot a) (apl-monadic bitwise-not a)) +(define + (apl-lshift a b) + (apl-dyadic (fn (x n) (arithmetic-shift x n)) a b)) +(define + (apl-rshift a b) + (apl-dyadic (fn (x n) (arithmetic-shift x (- 0 n))) a b)) + +;; --------------------------------------------------------------------------- +;; 6. Reduction (fold) and scan +;; --------------------------------------------------------------------------- + +(define (apl-reduce-add v) (reduce + 0 v)) +(define (apl-reduce-mul v) (reduce * 1 v)) +(define + (apl-reduce-max v) + (reduce (fn (acc x) (if (> acc x) acc x)) (first v) (rest v))) +(define + (apl-reduce-min v) + (reduce (fn (acc x) (if (< acc x) acc x)) (first v) (rest v))) +(define + (apl-reduce-and v) + (reduce + (fn + (acc x) + (if + (and (not (= acc 0)) (not (= x 0))) + 1 + 0)) + 1 + v)) +(define + (apl-reduce-or v) + (reduce + (fn + (acc x) + (if + (or (not (= acc 0)) (not (= x 0))) + 1 + 0)) + 0 + v)) + +;; Scan: prefix reduction (yields a vector of running totals) +(define + (apl-scan op v) + (if + (= (len v) 0) + (list) + (letrec + ((go (fn (xs acc result) (if (= (len xs) 0) (reverse result) (let ((next (op acc (first xs)))) (go (rest xs) next (cons next result))))))) + (go (rest v) (first v) (list (first v)))))) + +(define (apl-scan-add v) (apl-scan + v)) +(define (apl-scan-mul v) (apl-scan * v)) + +;; --------------------------------------------------------------------------- +;; 7. Vector manipulation +;; --------------------------------------------------------------------------- + +;; ⌽A — reverse +(define (apl-reverse v) (reverse v)) + +;; A,B — catenate +(define + (apl-cat a b) + (cond + ((and (list? a) (list? b)) (append a b)) + ((list? a) (append a (list b))) + ((list? b) (cons a b)) + (else (list a b)))) + +;; ↑N A — take first N elements (negative: take last N) +(define + (apl-take n v) + (if + (>= n 0) + (letrec + ((go (fn (xs i) (if (or (= i 0) (= (len xs) 0)) (list) (cons (first xs) (go (rest xs) (- i 1))))))) + (go v n)) + (apl-reverse (apl-take (- 0 n) (apl-reverse v))))) + +;; ↓N A — drop first N elements +(define + (apl-drop n v) + (if + (>= n 0) + (letrec + ((go (fn (xs i) (if (or (= i 0) (= (len xs) 0)) xs (go (rest xs) (- i 1)))))) + (go v n)) + (apl-reverse (apl-drop (- 0 n) (apl-reverse v))))) + +;; Rotate left by n positions +(define + (apl-rotate n v) + (let ((m (modulo n (len v)))) (append (apl-drop m v) (apl-take m v)))) + +;; Compression: A/B — select elements of B where A is 1 +(define + (apl-compress mask v) + (if + (= (len mask) 0) + (list) + (let + ((rest-result (apl-compress (rest mask) (rest v)))) + (if + (not (= (first mask) 0)) + (cons (first v) rest-result) + rest-result)))) + +;; Indexing: A[B] — select elements at indices B (1-indexed) +(define (apl-index v indices) (map (fn (i) (apl-at v i)) indices)) + +;; Grade up: indices that would sort the vector ascending +(define + (apl-grade-up v) + (let + ((indexed (map (fn (x i) (list x i)) v (apl-iota (len v))))) + (map (fn (p) (nth p 1)) (sort indexed)))) + +;; --------------------------------------------------------------------------- +;; 8. Set operations (∊ ∪ ∩ ~) +;; --------------------------------------------------------------------------- + +;; Membership ∊: for each element in A, is it in B? → 0/1 vector +(define + (apl-member a b) + (let + ((bset (let ((s (make-set))) (for-each (fn (x) (set-add! s x)) b) s))) + (if + (list? a) + (map (fn (x) (apl-bool (set-member? bset x))) a) + (apl-bool (set-member? bset a))))) + +;; Nub ∪A — unique elements, preserving order +(define + (apl-nub v) + (let + ((seen (make-set))) + (letrec + ((go (fn (xs acc) (if (= (len xs) 0) (reverse acc) (if (set-member? seen (first xs)) (go (rest xs) acc) (begin (set-add! seen (first xs)) (go (rest xs) (cons (first xs) acc)))))))) + (go v (list))))) + +;; Union A∪B — nub of concatenation +(define (apl-union a b) (apl-nub (apl-cat a b))) + +;; Intersection A∩B +(define + (apl-intersect a b) + (let + ((bset (let ((s (make-set))) (for-each (fn (x) (set-add! s x)) b) s))) + (filter (fn (x) (set-member? bset x)) a))) + +;; Without A~B +(define + (apl-without a b) + (let + ((bset (let ((s (make-set))) (for-each (fn (x) (set-add! s x)) b) s))) + (filter (fn (x) (not (set-member? bset x))) a))) + +;; --------------------------------------------------------------------------- +;; 9. Format (⍕) — APL-style display +;; --------------------------------------------------------------------------- + +(define + (apl-format v) + (if + (list? v) + (letrec + ((go (fn (xs acc) (if (= (len xs) 0) acc (go (rest xs) (str acc (if (= acc "") "" " ") (str (first xs)))))))) + (go v "")) + (str v))) diff --git a/lib/apl/test.sh b/lib/apl/test.sh new file mode 100755 index 00000000..a8a967c0 --- /dev/null +++ b/lib/apl/test.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# lib/apl/test.sh — smoke-test the APL runtime layer. + +set -uo pipefail +cd "$(git rev-parse --show-toplevel)" + +SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}" +if [ ! -x "$SX_SERVER" ]; then + SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe" +fi +if [ ! -x "$SX_SERVER" ]; then + echo "ERROR: sx_server.exe not found." + exit 1 +fi + +TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT + +cat > "$TMPFILE" << 'EPOCHS' +(epoch 1) +(load "spec/stdlib.sx") +(load "lib/apl/runtime.sx") +(epoch 2) +(load "lib/apl/tests/runtime.sx") +(epoch 3) +(eval "(list apl-test-pass apl-test-fail)") +EPOCHS + +OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) + +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/\)$//') +fi +if [ -z "$LINE" ]; then + echo "ERROR: could not extract summary" + echo "$OUTPUT" | tail -10 + exit 1 +fi + +P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/') +F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/') +TOTAL=$((P + F)) + +if [ "$F" -eq 0 ]; then + echo "ok $P/$TOTAL lib/apl tests passed" +else + echo "FAIL $P/$TOTAL passed, $F failed" +fi + +[ "$F" -eq 0 ] diff --git a/lib/apl/tests/runtime.sx b/lib/apl/tests/runtime.sx new file mode 100644 index 00000000..8087872d --- /dev/null +++ b/lib/apl/tests/runtime.sx @@ -0,0 +1,327 @@ +;; lib/apl/tests/runtime.sx — Tests for lib/apl/runtime.sx + +;; --- Test framework --- +(define apl-test-pass 0) +(define apl-test-fail 0) +(define apl-test-fails (list)) + +(define + (apl-test name got expected) + (if + (= got expected) + (set! apl-test-pass (+ apl-test-pass 1)) + (begin + (set! apl-test-fail (+ apl-test-fail 1)) + (set! apl-test-fails (append apl-test-fails (list {:got got :expected expected :name name})))))) + +;; --------------------------------------------------------------------------- +;; 1. Core vector constructors +;; --------------------------------------------------------------------------- + +(apl-test + "iota 5" + (apl-iota 5) + (list 1 2 3 4 5)) +(apl-test "iota 1" (apl-iota 1) (list 1)) +(apl-test "iota 0" (apl-iota 0) (list)) +(apl-test + "rho list" + (apl-rho (list 1 2 3)) + 3) +(apl-test "rho scalar" (apl-rho 42) 1) +(apl-test + "at 1" + (apl-at (list 10 20 30) 1) + 10) +(apl-test + "at 3" + (apl-at (list 10 20 30) 3) + 30) + +;; --------------------------------------------------------------------------- +;; 2. Arithmetic — element-wise and rank-polymorphic +;; --------------------------------------------------------------------------- + +(apl-test + "add v+v" + (apl-add + (list 1 2 3) + (list 10 20 30)) + (list 11 22 33)) +(apl-test + "add s+v" + (apl-add 10 (list 1 2 3)) + (list 11 12 13)) +(apl-test + "add v+s" + (apl-add (list 1 2 3) 100) + (list 101 102 103)) +(apl-test "add s+s" (apl-add 3 4) 7) +(apl-test + "sub v-v" + (apl-sub + (list 5 4 3) + (list 1 2 3)) + (list 4 2 0)) +(apl-test + "mul v*s" + (apl-mul (list 1 2 3) 3) + (list 3 6 9)) +(apl-test + "neg -v" + (apl-neg (list 1 -2 3)) + (list -1 2 -3)) +(apl-test + "abs v" + (apl-abs (list -1 2 -3)) + (list 1 2 3)) +(apl-test + "floor v" + (apl-floor (list 1.7 2.2 3.9)) + (list 1 2 3)) +(apl-test + "ceil v" + (apl-ceil (list 1.1 2.5 3)) + (list 2 3 3)) +(apl-test + "max v v" + (apl-max + (list 1 5 3) + (list 4 2 6)) + (list 4 5 6)) +(apl-test + "min v v" + (apl-min + (list 1 5 3) + (list 4 2 6)) + (list 1 2 3)) + +;; --------------------------------------------------------------------------- +;; 3. Comparison (returns 0/1) +;; --------------------------------------------------------------------------- + +(apl-test "eq 3 3" (apl-eq 3 3) 1) +(apl-test "eq 3 4" (apl-eq 3 4) 0) +(apl-test + "gt v>s" + (apl-gt (list 1 5 3 7) 4) + (list 0 1 0 1)) +(apl-test + "lt v=s" + (apl-ge (list 3 4 5) 4) + (list 0 1 1)) +(apl-test + "neq v!=s" + (apl-neq (list 1 2 3) 2) + (list 1 0 1)) + +;; --------------------------------------------------------------------------- +;; 4. Boolean logic (0/1 values) +;; --------------------------------------------------------------------------- + +(apl-test "and 1 1" (apl-and 1 1) 1) +(apl-test "and 1 0" (apl-and 1 0) 0) +(apl-test "or 0 1" (apl-or 0 1) 1) +(apl-test "or 0 0" (apl-or 0 0) 0) +(apl-test "not 0" (apl-not 0) 1) +(apl-test "not 1" (apl-not 1) 0) +(apl-test + "not vec" + (apl-not (list 1 0 1 0)) + (list 0 1 0 1)) + +;; --------------------------------------------------------------------------- +;; 5. Bitwise operations +;; --------------------------------------------------------------------------- + +(apl-test "bitand s" (apl-bitand 5 3) 1) +(apl-test "bitor s" (apl-bitor 5 3) 7) +(apl-test "bitxor s" (apl-bitxor 5 3) 6) +(apl-test "bitnot 0" (apl-bitnot 0) -1) +(apl-test "lshift 1 4" (apl-lshift 1 4) 16) +(apl-test "rshift 16 2" (apl-rshift 16 2) 4) +(apl-test + "bitand vec" + (apl-bitand (list 5 6) (list 3 7)) + (list 1 6)) +(apl-test + "bitor vec" + (apl-bitor (list 5 6) (list 3 7)) + (list 7 7)) + +;; --------------------------------------------------------------------------- +;; 6. Reduction and scan +;; --------------------------------------------------------------------------- + +(apl-test + "reduce-add" + (apl-reduce-add + (list 1 2 3 4 5)) + 15) +(apl-test + "reduce-mul" + (apl-reduce-mul (list 1 2 3 4)) + 24) +(apl-test + "reduce-max" + (apl-reduce-max + (list 3 1 4 1 5)) + 5) +(apl-test + "reduce-min" + (apl-reduce-min + (list 3 1 4 1 5)) + 1) +(apl-test + "reduce-and" + (apl-reduce-and (list 1 1 1)) + 1) +(apl-test + "reduce-and0" + (apl-reduce-and (list 1 0 1)) + 0) +(apl-test + "reduce-or" + (apl-reduce-or (list 0 1 0)) + 1) +(apl-test + "scan-add" + (apl-scan-add (list 1 2 3 4)) + (list 1 3 6 10)) +(apl-test + "scan-mul" + (apl-scan-mul (list 1 2 3 4)) + (list 1 2 6 24)) + +;; --------------------------------------------------------------------------- +;; 7. Vector manipulation +;; --------------------------------------------------------------------------- + +(apl-test + "reverse" + (apl-reverse (list 1 2 3 4)) + (list 4 3 2 1)) +(apl-test + "cat v v" + (apl-cat (list 1 2) (list 3 4)) + (list 1 2 3 4)) +(apl-test + "cat v s" + (apl-cat (list 1 2) 3) + (list 1 2 3)) +(apl-test + "cat s v" + (apl-cat 1 (list 2 3)) + (list 1 2 3)) +(apl-test + "cat s s" + (apl-cat 1 2) + (list 1 2)) +(apl-test + "take 3" + (apl-take + 3 + (list 10 20 30 40 50)) + (list 10 20 30)) +(apl-test + "take 0" + (apl-take 0 (list 1 2 3)) + (list)) +(apl-test + "take neg" + (apl-take -2 (list 10 20 30)) + (list 20 30)) +(apl-test + "drop 2" + (apl-drop 2 (list 10 20 30 40)) + (list 30 40)) +(apl-test + "drop neg" + (apl-drop -1 (list 10 20 30)) + (list 10 20)) +(apl-test + "rotate 2" + (apl-rotate + 2 + (list 1 2 3 4 5)) + (list 3 4 5 1 2)) +(apl-test + "compress" + (apl-compress + (list 1 0 1 0) + (list 10 20 30 40)) + (list 10 30)) +(apl-test + "index" + (apl-index + (list 10 20 30 40) + (list 2 4)) + (list 20 40)) + +;; --------------------------------------------------------------------------- +;; 8. Set operations +;; --------------------------------------------------------------------------- + +(apl-test + "member yes" + (apl-member + (list 1 2 5) + (list 2 4 6)) + (list 0 1 0)) +(apl-test + "member s" + (apl-member 2 (list 1 2 3)) + 1) +(apl-test + "member no" + (apl-member 9 (list 1 2 3)) + 0) +(apl-test + "nub" + (apl-nub (list 1 2 1 3 2)) + (list 1 2 3)) +(apl-test + "union" + (apl-union + (list 1 2 3) + (list 2 3 4)) + (list 1 2 3 4)) +(apl-test + "intersect" + (apl-intersect + (list 1 2 3 4) + (list 2 4 6)) + (list 2 4)) +(apl-test + "without" + (apl-without + (list 1 2 3 4) + (list 2 4)) + (list 1 3)) + +;; --------------------------------------------------------------------------- +;; 9. Format +;; --------------------------------------------------------------------------- + +(apl-test + "format vec" + (apl-format (list 1 2 3)) + "1 2 3") +(apl-test "format scalar" (apl-format 42) "42") +(apl-test "format empty" (apl-format (list)) "") + +;; --------------------------------------------------------------------------- +;; Summary +;; --------------------------------------------------------------------------- + +(list apl-test-pass apl-test-fail) diff --git a/plans/agent-briefings/primitives-loop.md b/plans/agent-briefings/primitives-loop.md index a608a46f..a568dacf 100644 --- a/plans/agent-briefings/primitives-loop.md +++ b/plans/agent-briefings/primitives-loop.md @@ -698,8 +698,9 @@ Brief each language's loop agent (or do inline) after rebasing their branch onto `Set new`; char type for `Character`; string ports + `read`/`write` for `printString`. lib/smalltalk/runtime.sx (72 forms) + tests/runtime.sx (86/86 pass). COMMIT. -- [ ] APL: vectors as core array type; bitwise ops for array masks; sets for APL set ops; +- [x] APL: vectors as core array type; bitwise ops for array masks; sets for APL set ops; sequence protocol for rank-polymorphic operations; format for APL output formatting. + lib/apl/runtime.sx (60 forms) + tests/runtime.sx (73/73 pass). COMMIT. - [ ] Ruby: coroutines for fibers; hash tables for `Hash`; sets for `Set`; regexp for Ruby regex; string ports for `StringIO`; bytevectors for `String` binary encoding. @@ -726,6 +727,7 @@ Brief each language's loop agent (or do inline) after rebasing their branch onto _Newest first._ +- 2026-05-01: Phase 22 APL done — runtime.sx (60 forms): iota/rho/at, rank-polymorphic dyadic/monadic helpers, arithmetic/comparison/boolean/bitwise element-wise, reduce/scan, take/drop/rotate/compress/index, set ops (member/nub/union/intersect/without), format. 73/73 tests. COMMIT. - 2026-05-01: Phase 22 Smalltalk done — runtime.sx (72 forms): numeric helpers, Character (1-indexed Array backed by dict), Dictionary (list-of-pairs any-key map), Set (make-set), WriteStream/ReadStream/printString. set-member? (set item) order. 86/86 tests. COMMIT. - 2026-05-01: Phase 22 JS done — stdlib.sx (36 forms): bitwise (truncate not js-num-to-int; set-member? takes (set item) order), Map (dict-backed pairs), Set (SX make-set), RegExp (callable lambda). 25/25 new tests pass; total 492/585. COMMIT. - 2026-05-01: Phase 22 Haskell done — runtime.sx (113 forms): numeric tower (hk-div floor semantics), rational (dict GCD-normalised), hk-force (promises), Data.Char, Data.Set, Data.List, Maybe/Either, tuples, string helpers, hk-show. 148/148 tests. c02ffcf3.