Compare commits
241 Commits
90bc1208d9
...
loops/apl
| Author | SHA1 | Date | |
|---|---|---|---|
| 40dff449ef | |||
| eeb530eb85 | |||
| 36e1519613 | |||
| d1a491e530 | |||
| 015ecb8bc8 | |||
| a074ea9e98 | |||
| ef53232314 | |||
| 8cdebbe305 | |||
| 58c6ec27f3 | |||
| fa43aa6711 | |||
| 69078a59a9 | |||
| f5d3b1df19 | |||
| bf782d9c49 | |||
| bcdd137d6f | |||
| 0b3610a63a | |||
| 2b8c1a506c | |||
| 203f81004d | |||
| 04b0e61a33 | |||
| 80dac0051d | |||
| b661318a45 | |||
| a677585639 | |||
| c04f38a1ba | |||
| b13819c50c | |||
| d9cf00f287 | |||
| 0c0ed0605a | |||
| 0dd2fa3058 | |||
| 67ff2a3ae8 | |||
| aaabe370d6 | |||
| 637ba4102f | |||
| 7cf8b74d1d | |||
| dec1cf3fbe | |||
| 52df09655d | |||
| d755caeb9a | |||
| 3e77dd4ded | |||
| 0f13052900 | |||
| e37167a58e | |||
| 49eb22243a | |||
| 20a61de693 | |||
| ed0853f4a0 | |||
| ec26b61cbe | |||
| bee4e0846c | |||
| f591ee17c3 | |||
| 1900726fc9 | |||
| 16167c5d9b | |||
| 84d210b6b3 | |||
| 3628a504db | |||
| 4c71c5a75e | |||
| 9eecbde61e | |||
| 4dbd3a0b34 | |||
| 3d2bdc52b5 | |||
| d570da1dea | |||
| d67e04a9ad | |||
| 4332b4032f | |||
| 3489c9f131 | |||
| c56f400403 | |||
| c63c0d26e8 | |||
| c5ceb9c718 | |||
| e42aec8957 | |||
| ce72070d2a | |||
| 32efdfe4aa | |||
| e06e3ad014 | |||
| ad914b413c | |||
| 7dfa092ed2 | |||
| 03e9df3ecf | |||
| e11fbd6140 | |||
| 248dca5b32 | |||
| 71ad7d2d24 | |||
| c03ba9eccb | |||
| 3c83985841 | |||
| 6a6a94e203 | |||
| be26f77410 | |||
| 2314735431 | |||
| 74e020359f | |||
| db52a6d77c | |||
| 679b45e3fc | |||
| 096faf2c40 | |||
| bf190b8fc4 | |||
| 74ce9e7c75 | |||
| bc45b7abf5 | |||
| 2c61be39de | |||
| ea064346e1 | |||
| 23c44cf6cf | |||
| 5e0fcb9316 | |||
| d295ab8463 | |||
| afddc92c70 | |||
| 95f96efb78 | |||
| 95b22a648d | |||
| cffd3bec83 | |||
| eb5babaf99 | |||
| 985671cd76 | |||
| a49b1a9f79 | |||
| 263d9aae68 | |||
| 0dbf9b9f73 | |||
| 7b11f3d44a | |||
| a26be0bfd0 | |||
| 9ed3e4faaf | |||
| ac013c9381 | |||
| f07b6e497e | |||
| 72ccaf4565 | |||
| d8cf74fd28 | |||
| ef736112ef | |||
| e4eab6a309 | |||
| c311d4ebc4 | |||
| 99f8ccb30e | |||
| 4f9da65b3d | |||
| 025ddbebdd | |||
| f449f82fdd | |||
| 0e426cfea8 | |||
| 71c4b5e33f | |||
| 4cd8773766 | |||
| 733b1ebefa | |||
| 85911d7b84 | |||
| ab66b29a74 | |||
| 32a82a2e12 | |||
| 7d6df6fd5f | |||
| fd16776dd2 | |||
| a12a6a11cb | |||
| ce7243a1fb | |||
| 3f8fe41d4d | |||
| 086ad028ce | |||
| 97ccd61f74 | |||
| c8d7fdd59a | |||
| a14fe05632 | |||
| 4f4b735958 | |||
| da8ba104a6 | |||
| 82da16e4bb | |||
| 4da91bb9b4 | |||
| 35aa998fcc | |||
| 6ee052593c | |||
| 81f96df5fa | |||
| 1819156d1e | |||
| cdee007185 | |||
| dbba2fe418 | |||
| c73b696494 | |||
| 1a17d8d232 | |||
| 666e29d5f0 | |||
| bcf6057ac5 | |||
| 8fd55d6aa0 | |||
| 8a9c074141 | |||
| 13d0ebcce8 | |||
| 7e7a9c06e9 | |||
| 75032c5789 | |||
| df62c02a21 | |||
| 5d369daf2b | |||
| 446a0e7d68 | |||
| 00db8b7763 | |||
| 788ac9dd05 | |||
| bf250a24bf | |||
| 537e2cdb5a | |||
| 0ca664b81c | |||
| 0a8b30b7b8 | |||
| 2075db62ba | |||
| fa600442d6 | |||
| 1aca2c7bc5 | |||
| be2000a048 | |||
| 0be5eeafd8 | |||
| 04ed092f88 | |||
| 15da694c0d | |||
| 776ae18a20 | |||
| 5a83f4ef51 | |||
| 47249900f2 | |||
| 73080bb7de | |||
| 8f0af85d01 | |||
| 07a22257f6 | |||
| 8ef05514b5 | |||
| 496447ae36 | |||
| 0823832dcd | |||
| 3be722d5b6 | |||
| 0b5f3c180e | |||
| 8ee0928a3d | |||
| 25a4ce4a05 | |||
| fdd8e18cc3 | |||
| 3e83624317 | |||
| f72868c445 | |||
| 1c4ac47450 | |||
| 4ced16f04e | |||
| c6f58116bf | |||
| 9954a234ae | |||
| 76ee8cc39b | |||
| 44dc32aa54 | |||
| ae94a24de5 | |||
| a8cfd84f18 | |||
| 373d57cbcb | |||
| 5ef07a4d8d | |||
| 3190e770fb | |||
| 7c5c49c529 | |||
| ce8ff8b738 | |||
| a446d31d0d | |||
| e018ba9423 | |||
| 193b0c04be | |||
| e6af4e1449 | |||
| 09683b8a18 | |||
| 8e809614ba | |||
| 8daf33dc53 | |||
| 64e3b3f44e | |||
| c444bbe256 | |||
| 47a59343a1 | |||
| 1302f5a3cc | |||
| c7d0801850 | |||
| 8717094e74 | |||
| 93b31b6c8a | |||
| a7272ad162 | |||
| f09a712666 | |||
| 424b5ca472 | |||
| ffc3716b0e | |||
| c33d03d2a2 | |||
| 7fb4c52159 | |||
| 882205aa70 | |||
| 82bad15b13 | |||
| 072735a6de | |||
| 1a5a2e8982 | |||
| 45147bd8a6 | |||
| 8b7b6ad028 | |||
| 1846be0bd8 | |||
| c363856df6 | |||
| 4e89498664 | |||
| 3adad8e50e | |||
| aa7d691028 | |||
| 52523606a8 | |||
| f019d42727 | |||
| e71154f9c6 | |||
| 089e2569d4 | |||
| 33ce994f23 | |||
| 738f44e47d | |||
| 1516e1f9cd | |||
| 4e7d2183ad | |||
| 51ba2da119 | |||
| 1888c272f9 | |||
| 8a8d0e14bd | |||
| 0962e4231c | |||
| 2a3340f8e1 | |||
| 97513e5b96 | |||
| e2e801e38a | |||
| d191f7cd9e | |||
| 266693a2f6 | |||
| bc1a69925e | |||
| 1dc96c814e | |||
| 7f4fb9c3ed | |||
| 4965be71ca | |||
| efbab24cb2 | |||
| 60b7f0d7bb |
116
lib/apl/conformance.sh
Executable file
116
lib/apl/conformance.sh
Executable file
@@ -0,0 +1,116 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# lib/apl/conformance.sh — run APL test suites, emit scoreboard.json + scoreboard.md.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
SX_SERVER="${SX_SERVER:-/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||||
|
if [ ! -x "$SX_SERVER" ]; then
|
||||||
|
SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$SX_SERVER" ]; then
|
||||||
|
echo "ERROR: sx_server.exe not found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SUITES=(structural operators dfn tradfn valence programs system idioms eval-ops pipeline)
|
||||||
|
|
||||||
|
OUT_JSON="lib/apl/scoreboard.json"
|
||||||
|
OUT_MD="lib/apl/scoreboard.md"
|
||||||
|
|
||||||
|
run_suite() {
|
||||||
|
local suite=$1
|
||||||
|
local file="lib/apl/tests/${suite}.sx"
|
||||||
|
local TMP
|
||||||
|
TMP=$(mktemp)
|
||||||
|
cat > "$TMP" << EPOCHS
|
||||||
|
(epoch 1)
|
||||||
|
(load "spec/stdlib.sx")
|
||||||
|
(load "lib/r7rs.sx")
|
||||||
|
(load "lib/apl/runtime.sx")
|
||||||
|
(load "lib/apl/tokenizer.sx")
|
||||||
|
(load "lib/apl/parser.sx")
|
||||||
|
(load "lib/apl/transpile.sx")
|
||||||
|
(epoch 2)
|
||||||
|
(eval "(define apl-test-pass 0)")
|
||||||
|
(eval "(define apl-test-fail 0)")
|
||||||
|
(eval "(define apl-test (fn (name got expected) (if (= got expected) (set! apl-test-pass (+ apl-test-pass 1)) (set! apl-test-fail (+ apl-test-fail 1)))))")
|
||||||
|
(epoch 3)
|
||||||
|
(load "${file}")
|
||||||
|
(epoch 4)
|
||||||
|
(eval "(list apl-test-pass apl-test-fail)")
|
||||||
|
EPOCHS
|
||||||
|
|
||||||
|
local OUTPUT
|
||||||
|
OUTPUT=$(timeout 300 "$SX_SERVER" < "$TMP" 2>/dev/null)
|
||||||
|
rm -f "$TMP"
|
||||||
|
|
||||||
|
local LINE
|
||||||
|
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 4 / {getline; print; exit}')
|
||||||
|
if [ -z "$LINE" ]; then
|
||||||
|
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 4 \([0-9]+ [0-9]+\)\)' | tail -1 \
|
||||||
|
| sed -E 's/^\(ok 4 //; s/\)$//')
|
||||||
|
fi
|
||||||
|
|
||||||
|
local P F
|
||||||
|
P=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\1/')
|
||||||
|
F=$(echo "$LINE" | sed -E 's/^\(([0-9]+) ([0-9]+)\).*/\2/')
|
||||||
|
P=${P:-0}
|
||||||
|
F=${F:-0}
|
||||||
|
echo "${P} ${F}"
|
||||||
|
}
|
||||||
|
|
||||||
|
declare -A SUITE_PASS
|
||||||
|
declare -A SUITE_FAIL
|
||||||
|
TOTAL_PASS=0
|
||||||
|
TOTAL_FAIL=0
|
||||||
|
|
||||||
|
echo "Running APL conformance suite..." >&2
|
||||||
|
for s in "${SUITES[@]}"; do
|
||||||
|
read -r p f < <(run_suite "$s")
|
||||||
|
SUITE_PASS[$s]=$p
|
||||||
|
SUITE_FAIL[$s]=$f
|
||||||
|
TOTAL_PASS=$((TOTAL_PASS + p))
|
||||||
|
TOTAL_FAIL=$((TOTAL_FAIL + f))
|
||||||
|
printf " %-12s %d/%d\n" "$s" "$p" "$((p+f))" >&2
|
||||||
|
done
|
||||||
|
|
||||||
|
# scoreboard.json
|
||||||
|
{
|
||||||
|
printf '{\n'
|
||||||
|
printf ' "suites": {\n'
|
||||||
|
first=1
|
||||||
|
for s in "${SUITES[@]}"; do
|
||||||
|
if [ $first -eq 0 ]; then printf ',\n'; fi
|
||||||
|
printf ' "%s": {"pass": %d, "fail": %d}' "$s" "${SUITE_PASS[$s]}" "${SUITE_FAIL[$s]}"
|
||||||
|
first=0
|
||||||
|
done
|
||||||
|
printf '\n },\n'
|
||||||
|
printf ' "total_pass": %d,\n' "$TOTAL_PASS"
|
||||||
|
printf ' "total_fail": %d,\n' "$TOTAL_FAIL"
|
||||||
|
printf ' "total": %d\n' "$((TOTAL_PASS + TOTAL_FAIL))"
|
||||||
|
printf '}\n'
|
||||||
|
} > "$OUT_JSON"
|
||||||
|
|
||||||
|
# scoreboard.md
|
||||||
|
{
|
||||||
|
printf '# APL Conformance Scoreboard\n\n'
|
||||||
|
printf '_Generated by `lib/apl/conformance.sh`_\n\n'
|
||||||
|
printf '| Suite | Pass | Fail | Total |\n'
|
||||||
|
printf '|-------|-----:|-----:|------:|\n'
|
||||||
|
for s in "${SUITES[@]}"; do
|
||||||
|
p=${SUITE_PASS[$s]}
|
||||||
|
f=${SUITE_FAIL[$s]}
|
||||||
|
printf '| %s | %d | %d | %d |\n' "$s" "$p" "$f" "$((p+f))"
|
||||||
|
done
|
||||||
|
printf '| **Total** | **%d** | **%d** | **%d** |\n' "$TOTAL_PASS" "$TOTAL_FAIL" "$((TOTAL_PASS + TOTAL_FAIL))"
|
||||||
|
printf '\n'
|
||||||
|
printf '## Notes\n\n'
|
||||||
|
printf '%s\n' '- Suites use the standard `apl-test name got expected` framework loaded against `lib/apl/runtime.sx` + `lib/apl/transpile.sx`.'
|
||||||
|
printf '%s\n' '- `lib/apl/tests/parse.sx` and `lib/apl/tests/scalar.sx` use their own self-contained frameworks and are excluded from this scoreboard.'
|
||||||
|
} > "$OUT_MD"
|
||||||
|
|
||||||
|
echo "Wrote $OUT_JSON and $OUT_MD" >&2
|
||||||
|
echo "Total: $TOTAL_PASS pass, $TOTAL_FAIL fail" >&2
|
||||||
|
|
||||||
|
[ "$TOTAL_FAIL" -eq 0 ]
|
||||||
711
lib/apl/parser.sx
Normal file
711
lib/apl/parser.sx
Normal file
@@ -0,0 +1,711 @@
|
|||||||
|
; APL Parser — right-to-left expression parser
|
||||||
|
;
|
||||||
|
; Takes a token list (output of apl-tokenize) and produces an AST.
|
||||||
|
; APL evaluates right-to-left with no precedence among functions.
|
||||||
|
; Operators bind to the function immediately to their left in the source.
|
||||||
|
;
|
||||||
|
; AST node types:
|
||||||
|
; (:num n) number literal
|
||||||
|
; (:str s) string literal
|
||||||
|
; (:vec n1 n2 ...) strand (juxtaposed literals)
|
||||||
|
; (:name "x") name reference / alpha / omega
|
||||||
|
; (:assign "x" expr) assignment x←expr
|
||||||
|
; (:monad fn arg) monadic function call
|
||||||
|
; (:dyad fn left right) dyadic function call
|
||||||
|
; (:derived-fn op fn) derived function: f/ f¨ f⍨
|
||||||
|
; (:derived-fn2 "." f g) inner product: f.g
|
||||||
|
; (:outer "∘." fn) outer product: ∘.f
|
||||||
|
; (:fn-glyph "⍳") function reference
|
||||||
|
; (:fn-name "foo") named-function reference (dfn variable)
|
||||||
|
; (:dfn stmt...) {⍺+⍵} anonymous function
|
||||||
|
; (:guard cond expr) cond:expr guard inside dfn
|
||||||
|
; (:program stmt...) multi-statement sequence
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Glyph classification sets
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-parse-op-glyphs
|
||||||
|
(list "/" "⌿" "\\" "⍀" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@"))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-parse-fn-glyphs
|
||||||
|
(list
|
||||||
|
"+"
|
||||||
|
"-"
|
||||||
|
"×"
|
||||||
|
"÷"
|
||||||
|
"*"
|
||||||
|
"⍟"
|
||||||
|
"⌈"
|
||||||
|
"⌊"
|
||||||
|
"|"
|
||||||
|
"!"
|
||||||
|
"?"
|
||||||
|
"○"
|
||||||
|
"~"
|
||||||
|
"<"
|
||||||
|
"≤"
|
||||||
|
"="
|
||||||
|
"≥"
|
||||||
|
">"
|
||||||
|
"≠"
|
||||||
|
"≢"
|
||||||
|
"≡"
|
||||||
|
"∊"
|
||||||
|
"∧"
|
||||||
|
"∨"
|
||||||
|
"⍱"
|
||||||
|
"⍲"
|
||||||
|
","
|
||||||
|
"⍪"
|
||||||
|
"⍴"
|
||||||
|
"⌽"
|
||||||
|
"⊖"
|
||||||
|
"⍉"
|
||||||
|
"↑"
|
||||||
|
"↓"
|
||||||
|
"⊂"
|
||||||
|
"⊃"
|
||||||
|
"⊆"
|
||||||
|
"∪"
|
||||||
|
"∩"
|
||||||
|
"⍳"
|
||||||
|
"⍸"
|
||||||
|
"⌷"
|
||||||
|
"⍋"
|
||||||
|
"⍒"
|
||||||
|
"⊥"
|
||||||
|
"⊤"
|
||||||
|
"⊣"
|
||||||
|
"⊢"
|
||||||
|
"⍎"
|
||||||
|
"⍕"))
|
||||||
|
|
||||||
|
(define apl-quad-fn-names (list "⎕FMT" "⎕←"))
|
||||||
|
|
||||||
|
(define apl-known-fn-names (list))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Token accessors
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-collect-fn-bindings
|
||||||
|
(fn
|
||||||
|
(stmt-groups)
|
||||||
|
(set! apl-known-fn-names (list))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(toks)
|
||||||
|
(when
|
||||||
|
(and
|
||||||
|
(>= (len toks) 3)
|
||||||
|
(= (tok-type (nth toks 0)) :name)
|
||||||
|
(= (tok-type (nth toks 1)) :assign)
|
||||||
|
(= (tok-type (nth toks 2)) :lbrace))
|
||||||
|
(set!
|
||||||
|
apl-known-fn-names
|
||||||
|
(cons (tok-val (nth toks 0)) apl-known-fn-names))))
|
||||||
|
stmt-groups)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-parse-op-glyph?
|
||||||
|
(fn (v) (some (fn (g) (= g v)) apl-parse-op-glyphs)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-parse-fn-glyph?
|
||||||
|
(fn (v) (some (fn (g) (= g v)) apl-parse-fn-glyphs)))
|
||||||
|
|
||||||
|
(define tok-type (fn (tok) (get tok :type)))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Collect trailing operators starting at index i
|
||||||
|
; Returns {:ops (op ...) :end new-i}
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define tok-val (fn (tok) (get tok :value)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
is-op-tok?
|
||||||
|
(fn
|
||||||
|
(tok)
|
||||||
|
(and (= (tok-type tok) :glyph) (apl-parse-op-glyph? (tok-val tok)))))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Build a derived-fn node by chaining operators left-to-right
|
||||||
|
; (+/¨ → (:derived-fn "¨" (:derived-fn "/" (:fn-glyph "+"))))
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define
|
||||||
|
is-fn-tok?
|
||||||
|
(fn
|
||||||
|
(tok)
|
||||||
|
(or
|
||||||
|
(and (= (tok-type tok) :glyph) (apl-parse-fn-glyph? (tok-val tok)))
|
||||||
|
(and
|
||||||
|
(= (tok-type tok) :name)
|
||||||
|
(or
|
||||||
|
(some (fn (q) (= q (tok-val tok))) apl-quad-fn-names)
|
||||||
|
(some (fn (q) (= q (tok-val tok))) apl-known-fn-names))))))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Find matching close bracket/paren/brace
|
||||||
|
; Returns the index of the matching close token
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define collect-ops (fn (tokens i) (collect-ops-loop tokens i (list))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
collect-ops-loop
|
||||||
|
(fn
|
||||||
|
(tokens i acc)
|
||||||
|
(if
|
||||||
|
(>= i (len tokens))
|
||||||
|
{:end i :ops acc}
|
||||||
|
(let
|
||||||
|
((tok (nth tokens i)))
|
||||||
|
(if
|
||||||
|
(is-op-tok? tok)
|
||||||
|
(collect-ops-loop tokens (+ i 1) (append acc (tok-val tok)))
|
||||||
|
{:end i :ops acc})))))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Segment collection: scan tokens left-to-right, building
|
||||||
|
; a list of {:kind "val"/"fn" :node ast} segments.
|
||||||
|
; Operators following function glyphs are merged into
|
||||||
|
; derived-fn nodes during this pass.
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define
|
||||||
|
build-derived-fn
|
||||||
|
(fn
|
||||||
|
(fn-node ops)
|
||||||
|
(if
|
||||||
|
(= (len ops) 0)
|
||||||
|
fn-node
|
||||||
|
(build-derived-fn (list :derived-fn (first ops) fn-node) (rest ops)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
find-matching-close
|
||||||
|
(fn
|
||||||
|
(tokens start open-type close-type)
|
||||||
|
(find-matching-close-loop tokens start open-type close-type 1)))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Build tree from segment list
|
||||||
|
;
|
||||||
|
; The segments are in left-to-right order.
|
||||||
|
; APL evaluates right-to-left, so the LEFTMOST function is
|
||||||
|
; the outermost (last-evaluated) node.
|
||||||
|
;
|
||||||
|
; Patterns:
|
||||||
|
; [val] → val node
|
||||||
|
; [fn val ...] → (:monad fn (build-tree rest))
|
||||||
|
; [val fn val ...] → (:dyad fn val (build-tree rest))
|
||||||
|
; [val val ...] → (:vec val1 val2 ...) — strand
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
; Find the index of the first function segment (returns -1 if none)
|
||||||
|
(define
|
||||||
|
find-matching-close-loop
|
||||||
|
(fn
|
||||||
|
(tokens i open-type close-type depth)
|
||||||
|
(if
|
||||||
|
(>= i (len tokens))
|
||||||
|
(len tokens)
|
||||||
|
(let
|
||||||
|
((tt (tok-type (nth tokens i))))
|
||||||
|
(cond
|
||||||
|
((= tt open-type)
|
||||||
|
(find-matching-close-loop
|
||||||
|
tokens
|
||||||
|
(+ i 1)
|
||||||
|
open-type
|
||||||
|
close-type
|
||||||
|
(+ depth 1)))
|
||||||
|
((= tt close-type)
|
||||||
|
(if
|
||||||
|
(= depth 1)
|
||||||
|
i
|
||||||
|
(find-matching-close-loop
|
||||||
|
tokens
|
||||||
|
(+ i 1)
|
||||||
|
open-type
|
||||||
|
close-type
|
||||||
|
(- depth 1))))
|
||||||
|
(true
|
||||||
|
(find-matching-close-loop
|
||||||
|
tokens
|
||||||
|
(+ i 1)
|
||||||
|
open-type
|
||||||
|
close-type
|
||||||
|
depth)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
collect-segments
|
||||||
|
(fn (tokens) (collect-segments-loop tokens 0 (list))))
|
||||||
|
|
||||||
|
; Build an array node from 0..n value segments
|
||||||
|
; If n=1 → return that segment's node
|
||||||
|
; If n>1 → return (:vec node1 node2 ...)
|
||||||
|
(define
|
||||||
|
collect-segments-loop
|
||||||
|
(fn
|
||||||
|
(tokens i acc)
|
||||||
|
(if
|
||||||
|
(>= i (len tokens))
|
||||||
|
acc
|
||||||
|
(let
|
||||||
|
((tok (nth tokens i)) (n (len tokens)))
|
||||||
|
(let
|
||||||
|
((tt (tok-type tok)) (tv (tok-val tok)))
|
||||||
|
(cond
|
||||||
|
((or (= tt :diamond) (= tt :newline) (= tt :semi))
|
||||||
|
(collect-segments-loop tokens (+ i 1) acc))
|
||||||
|
((= tt :num)
|
||||||
|
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :num tv)})))
|
||||||
|
((= tt :str)
|
||||||
|
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)})))
|
||||||
|
((= tt :name)
|
||||||
|
(cond
|
||||||
|
((and (< (+ i 1) (len tokens)) (= (tok-type (nth tokens (+ i 1))) :assign))
|
||||||
|
(let
|
||||||
|
((rhs-tokens (slice tokens (+ i 2) (len tokens))))
|
||||||
|
(let
|
||||||
|
((rhs-expr (parse-apl-expr rhs-tokens)))
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
(len tokens)
|
||||||
|
(append acc {:kind "val" :node (list :assign-expr tv rhs-expr)})))))
|
||||||
|
((some (fn (q) (= q tv)) apl-quad-fn-names)
|
||||||
|
(let
|
||||||
|
((op-result (collect-ops tokens (+ i 1))))
|
||||||
|
(let
|
||||||
|
((ops (get op-result :ops))
|
||||||
|
(ni (get op-result :end)))
|
||||||
|
(let
|
||||||
|
((fn-node (build-derived-fn (list :fn-glyph tv) ops)))
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
ni
|
||||||
|
(append acc {:kind "fn" :node fn-node}))))))
|
||||||
|
((some (fn (q) (= q tv)) apl-known-fn-names)
|
||||||
|
(let
|
||||||
|
((op-result (collect-ops tokens (+ i 1))))
|
||||||
|
(let
|
||||||
|
((ops (get op-result :ops))
|
||||||
|
(ni (get op-result :end)))
|
||||||
|
(let
|
||||||
|
((fn-node (build-derived-fn (list :fn-name tv) ops)))
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
ni
|
||||||
|
(append acc {:kind "fn" :node fn-node}))))))
|
||||||
|
(else
|
||||||
|
(let
|
||||||
|
((br (maybe-bracket (list :name tv) tokens (+ i 1))))
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
(nth br 1)
|
||||||
|
(append acc {:kind "val" :node (nth br 0)}))))))
|
||||||
|
((= tt :lparen)
|
||||||
|
(let
|
||||||
|
((end (find-matching-close tokens (+ i 1) :lparen :rparen)))
|
||||||
|
(let
|
||||||
|
((inner-tokens (slice tokens (+ i 1) end))
|
||||||
|
(after (+ end 1)))
|
||||||
|
(let
|
||||||
|
((inner-segs (collect-segments inner-tokens)))
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(>= (len inner-segs) 2)
|
||||||
|
(every? (fn (s) (= (get s :kind) "fn")) inner-segs))
|
||||||
|
(let
|
||||||
|
((train-node (cons :train (map (fn (s) (get s :node)) inner-segs))))
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
after
|
||||||
|
(append acc {:kind "fn" :node train-node})))
|
||||||
|
(let
|
||||||
|
((br (maybe-bracket (parse-apl-expr inner-tokens) tokens after)))
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
(nth br 1)
|
||||||
|
(append acc {:kind "val" :node (nth br 0)}))))))))
|
||||||
|
((= tt :lbrace)
|
||||||
|
(let
|
||||||
|
((end (find-matching-close tokens (+ i 1) :lbrace :rbrace)))
|
||||||
|
(let
|
||||||
|
((inner-tokens (slice tokens (+ i 1) end))
|
||||||
|
(after (+ end 1)))
|
||||||
|
(collect-segments-loop tokens after (append acc {:kind "fn" :node (parse-dfn inner-tokens)})))))
|
||||||
|
((= tt :glyph)
|
||||||
|
(cond
|
||||||
|
((or (= tv "⍺") (= tv "⍵"))
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(< (+ i 1) (len tokens))
|
||||||
|
(= (tok-type (nth tokens (+ i 1))) :assign))
|
||||||
|
(let
|
||||||
|
((rhs-tokens (slice tokens (+ i 2) (len tokens))))
|
||||||
|
(let
|
||||||
|
((rhs-expr (parse-apl-expr rhs-tokens)))
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
(len tokens)
|
||||||
|
(append acc {:kind "val" :node (list :assign-expr tv rhs-expr)}))))
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
(+ i 1)
|
||||||
|
(append acc {:kind "val" :node (list :name tv)}))))
|
||||||
|
((= tv "∇")
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
(+ i 1)
|
||||||
|
(append acc {:kind "fn" :node (list :fn-glyph "∇")})))
|
||||||
|
((and (= tv "∘") (< (+ i 1) n) (= (tok-val (nth tokens (+ i 1))) "."))
|
||||||
|
(if
|
||||||
|
(and (< (+ i 2) n) (is-fn-tok? (nth tokens (+ i 2))))
|
||||||
|
(let
|
||||||
|
((fn-tv (tok-val (nth tokens (+ i 2)))))
|
||||||
|
(let
|
||||||
|
((op-result (collect-ops tokens (+ i 3))))
|
||||||
|
(let
|
||||||
|
((ops (get op-result :ops))
|
||||||
|
(ni (get op-result :end)))
|
||||||
|
(let
|
||||||
|
((fn-node (build-derived-fn (list :fn-glyph fn-tv) ops)))
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
ni
|
||||||
|
(append acc {:kind "fn" :node (list :outer "∘." fn-node)}))))))
|
||||||
|
(collect-segments-loop tokens (+ i 1) acc)))
|
||||||
|
((apl-parse-fn-glyph? tv)
|
||||||
|
(let
|
||||||
|
((op-result (collect-ops tokens (+ i 1))))
|
||||||
|
(let
|
||||||
|
((ops (get op-result :ops))
|
||||||
|
(ni (get op-result :end)))
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(= (len ops) 1)
|
||||||
|
(= (first ops) ".")
|
||||||
|
(< ni n)
|
||||||
|
(is-fn-tok? (nth tokens ni)))
|
||||||
|
(let
|
||||||
|
((g-tv (tok-val (nth tokens ni))))
|
||||||
|
(let
|
||||||
|
((op-result2 (collect-ops tokens (+ ni 1))))
|
||||||
|
(let
|
||||||
|
((ops2 (get op-result2 :ops))
|
||||||
|
(ni2 (get op-result2 :end)))
|
||||||
|
(let
|
||||||
|
((g-node (build-derived-fn (list :fn-glyph g-tv) ops2)))
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
ni2
|
||||||
|
(append acc {:kind "fn" :node (list :derived-fn2 "." (list :fn-glyph tv) g-node)}))))))
|
||||||
|
(let
|
||||||
|
((fn-node (build-derived-fn (list :fn-glyph tv) ops)))
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
ni
|
||||||
|
(append acc {:kind "fn" :node fn-node})))))))
|
||||||
|
((apl-parse-op-glyph? tv)
|
||||||
|
(if
|
||||||
|
(or (= tv "/") (= tv "⌿") (= tv "\\") (= tv "⍀"))
|
||||||
|
(let
|
||||||
|
((next-i (+ i 1)))
|
||||||
|
(let
|
||||||
|
((next-tok (if (< next-i n) (nth tokens next-i) nil)))
|
||||||
|
(let
|
||||||
|
((mod (if (and next-tok (= (tok-type next-tok) :glyph) (or (= (get next-tok :value) "⍨") (= (get next-tok :value) "¨"))) (get next-tok :value) nil))
|
||||||
|
(base-fn-node (list :fn-glyph tv)))
|
||||||
|
(let
|
||||||
|
((node (if mod (list :derived-fn mod base-fn-node) base-fn-node))
|
||||||
|
(advance (if mod 2 1)))
|
||||||
|
(collect-segments-loop
|
||||||
|
tokens
|
||||||
|
(+ i advance)
|
||||||
|
(append acc {:kind "fn" :node node}))))))
|
||||||
|
(collect-segments-loop tokens (+ i 1) acc)))
|
||||||
|
(true (collect-segments-loop tokens (+ i 1) acc))))
|
||||||
|
(true (collect-segments-loop tokens (+ i 1) acc))))))))
|
||||||
|
|
||||||
|
(define find-first-fn (fn (segs) (find-first-fn-loop segs 0)))
|
||||||
|
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Split token list on statement separators (diamond / newline)
|
||||||
|
; Only splits at depth 0 (ignores separators inside { } or ( ) )
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define
|
||||||
|
find-first-fn-loop
|
||||||
|
(fn
|
||||||
|
(segs i)
|
||||||
|
(if
|
||||||
|
(>= i (len segs))
|
||||||
|
-1
|
||||||
|
(if
|
||||||
|
(= (get (nth segs i) :kind) "fn")
|
||||||
|
i
|
||||||
|
(find-first-fn-loop segs (+ i 1))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
segs-to-array
|
||||||
|
(fn
|
||||||
|
(segs)
|
||||||
|
(if
|
||||||
|
(= (len segs) 1)
|
||||||
|
(get (first segs) :node)
|
||||||
|
(cons :vec (map (fn (s) (get s :node)) segs)))))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Parse a dfn body (tokens between { and })
|
||||||
|
; Handles guard expressions: cond : expr
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define
|
||||||
|
build-tree
|
||||||
|
(fn
|
||||||
|
(segs)
|
||||||
|
(cond
|
||||||
|
((= (len segs) 0) nil)
|
||||||
|
((= (len segs) 1) (get (first segs) :node))
|
||||||
|
((every? (fn (s) (= (get s :kind) "val")) segs)
|
||||||
|
(segs-to-array segs))
|
||||||
|
(true
|
||||||
|
(let
|
||||||
|
((fn-idx (find-first-fn segs)))
|
||||||
|
(cond
|
||||||
|
((= fn-idx -1) (segs-to-array segs))
|
||||||
|
((= fn-idx 0)
|
||||||
|
(list
|
||||||
|
:monad (get (first segs) :node)
|
||||||
|
(build-tree (rest segs))))
|
||||||
|
(true
|
||||||
|
(let
|
||||||
|
((left-segs (slice segs 0 fn-idx))
|
||||||
|
(fn-seg (nth segs fn-idx))
|
||||||
|
(right-segs (slice segs (+ fn-idx 1))))
|
||||||
|
(list
|
||||||
|
:dyad (get fn-seg :node)
|
||||||
|
(segs-to-array left-segs)
|
||||||
|
(build-tree right-segs))))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
split-statements
|
||||||
|
(fn (tokens) (split-statements-loop tokens (list) (list) 0)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
split-statements-loop
|
||||||
|
(fn
|
||||||
|
(tokens current-stmt acc depth)
|
||||||
|
(if
|
||||||
|
(= (len tokens) 0)
|
||||||
|
(if (> (len current-stmt) 0) (append acc (list current-stmt)) acc)
|
||||||
|
(let
|
||||||
|
((tok (first tokens))
|
||||||
|
(rest-toks (rest tokens))
|
||||||
|
(tt (tok-type (first tokens))))
|
||||||
|
(cond
|
||||||
|
((or (= tt :lparen) (= tt :lbrace) (= tt :lbracket))
|
||||||
|
(split-statements-loop
|
||||||
|
rest-toks
|
||||||
|
(append current-stmt tok)
|
||||||
|
acc
|
||||||
|
(+ depth 1)))
|
||||||
|
((or (= tt :rparen) (= tt :rbrace) (= tt :rbracket))
|
||||||
|
(split-statements-loop
|
||||||
|
rest-toks
|
||||||
|
(append current-stmt tok)
|
||||||
|
acc
|
||||||
|
(- depth 1)))
|
||||||
|
((and (> depth 0) (or (= tt :diamond) (= tt :newline)))
|
||||||
|
(split-statements-loop
|
||||||
|
rest-toks
|
||||||
|
(append current-stmt tok)
|
||||||
|
acc
|
||||||
|
depth))
|
||||||
|
((and (= depth 0) (or (= tt :diamond) (= tt :newline)))
|
||||||
|
(if
|
||||||
|
(> (len current-stmt) 0)
|
||||||
|
(split-statements-loop
|
||||||
|
rest-toks
|
||||||
|
(list)
|
||||||
|
(append acc (list current-stmt))
|
||||||
|
depth)
|
||||||
|
(split-statements-loop rest-toks (list) acc depth)))
|
||||||
|
(true
|
||||||
|
(split-statements-loop
|
||||||
|
rest-toks
|
||||||
|
(append current-stmt tok)
|
||||||
|
acc
|
||||||
|
depth)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
parse-dfn
|
||||||
|
(fn
|
||||||
|
(tokens)
|
||||||
|
(let
|
||||||
|
((stmt-groups (split-statements tokens)))
|
||||||
|
(let ((stmts (map parse-dfn-stmt stmt-groups))) (cons :dfn stmts)))))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Parse a single statement (assignment or expression)
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define
|
||||||
|
parse-dfn-stmt
|
||||||
|
(fn
|
||||||
|
(tokens)
|
||||||
|
(let
|
||||||
|
((colon-idx (find-top-level-colon tokens 0)))
|
||||||
|
(if
|
||||||
|
(>= colon-idx 0)
|
||||||
|
(let
|
||||||
|
((cond-tokens (slice tokens 0 colon-idx))
|
||||||
|
(body-tokens (slice tokens (+ colon-idx 1))))
|
||||||
|
(list
|
||||||
|
:guard (parse-apl-expr cond-tokens)
|
||||||
|
(parse-apl-expr body-tokens)))
|
||||||
|
(parse-stmt tokens)))))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Parse an expression from a flat token list
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define
|
||||||
|
find-top-level-colon
|
||||||
|
(fn (tokens i) (find-top-level-colon-loop tokens i 0)))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Main entry point
|
||||||
|
; parse-apl: string → AST
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define
|
||||||
|
find-top-level-colon-loop
|
||||||
|
(fn
|
||||||
|
(tokens i depth)
|
||||||
|
(if
|
||||||
|
(>= i (len tokens))
|
||||||
|
-1
|
||||||
|
(let
|
||||||
|
((tok (nth tokens i)) (tt (tok-type (nth tokens i))))
|
||||||
|
(cond
|
||||||
|
((or (= tt :lparen) (= tt :lbrace) (= tt :lbracket))
|
||||||
|
(find-top-level-colon-loop tokens (+ i 1) (+ depth 1)))
|
||||||
|
((or (= tt :rparen) (= tt :rbrace) (= tt :rbracket))
|
||||||
|
(find-top-level-colon-loop tokens (+ i 1) (- depth 1)))
|
||||||
|
((and (= tt :colon) (= depth 0)) i)
|
||||||
|
(true (find-top-level-colon-loop tokens (+ i 1) depth)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
parse-stmt
|
||||||
|
(fn
|
||||||
|
(tokens)
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(>= (len tokens) 2)
|
||||||
|
(= (tok-type (nth tokens 0)) :name)
|
||||||
|
(= (tok-type (nth tokens 1)) :assign))
|
||||||
|
(list
|
||||||
|
:assign (tok-val (nth tokens 0))
|
||||||
|
(parse-apl-expr (slice tokens 2)))
|
||||||
|
(parse-apl-expr tokens))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
parse-apl-expr
|
||||||
|
(fn
|
||||||
|
(tokens)
|
||||||
|
(let
|
||||||
|
((segs (collect-segments tokens)))
|
||||||
|
(if (= (len segs) 0) nil (build-tree segs)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
parse-apl
|
||||||
|
(fn
|
||||||
|
(src)
|
||||||
|
(let
|
||||||
|
((tokens (apl-tokenize src)))
|
||||||
|
(let
|
||||||
|
((stmt-groups (split-statements tokens)))
|
||||||
|
(begin
|
||||||
|
(apl-collect-fn-bindings stmt-groups)
|
||||||
|
(if
|
||||||
|
(= (len stmt-groups) 0)
|
||||||
|
nil
|
||||||
|
(if
|
||||||
|
(= (len stmt-groups) 1)
|
||||||
|
(parse-stmt (first stmt-groups))
|
||||||
|
(cons :program (map parse-stmt stmt-groups)))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
split-bracket-loop
|
||||||
|
(fn
|
||||||
|
(tokens current acc depth)
|
||||||
|
(if
|
||||||
|
(= (len tokens) 0)
|
||||||
|
(append acc (list current))
|
||||||
|
(let
|
||||||
|
((tok (first tokens)) (more (rest tokens)))
|
||||||
|
(let
|
||||||
|
((tt (tok-type tok)))
|
||||||
|
(cond
|
||||||
|
((or (= tt :lparen) (= tt :lbrace) (= tt :lbracket))
|
||||||
|
(split-bracket-loop
|
||||||
|
more
|
||||||
|
(append current (list tok))
|
||||||
|
acc
|
||||||
|
(+ depth 1)))
|
||||||
|
((or (= tt :rparen) (= tt :rbrace) (= tt :rbracket))
|
||||||
|
(split-bracket-loop
|
||||||
|
more
|
||||||
|
(append current (list tok))
|
||||||
|
acc
|
||||||
|
(- depth 1)))
|
||||||
|
((and (= tt :semi) (= depth 0))
|
||||||
|
(split-bracket-loop
|
||||||
|
more
|
||||||
|
(list)
|
||||||
|
(append acc (list current))
|
||||||
|
depth))
|
||||||
|
(else
|
||||||
|
(split-bracket-loop more (append current (list tok)) acc depth))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
split-bracket-content
|
||||||
|
(fn (tokens) (split-bracket-loop tokens (list) (list) 0)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
maybe-bracket
|
||||||
|
(fn
|
||||||
|
(val-node tokens after)
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(< after (len tokens))
|
||||||
|
(= (tok-type (nth tokens after)) :lbracket))
|
||||||
|
(let
|
||||||
|
((end (find-matching-close tokens (+ after 1) :lbracket :rbracket)))
|
||||||
|
(let
|
||||||
|
((inner-tokens (slice tokens (+ after 1) end))
|
||||||
|
(next-after (+ end 1)))
|
||||||
|
(let
|
||||||
|
((sections (split-bracket-content inner-tokens)))
|
||||||
|
(if
|
||||||
|
(= (len sections) 1)
|
||||||
|
(let
|
||||||
|
((idx-expr (parse-apl-expr inner-tokens)))
|
||||||
|
(let
|
||||||
|
((indexed (list :dyad (list :fn-glyph "⌷") idx-expr val-node)))
|
||||||
|
(maybe-bracket indexed tokens next-after)))
|
||||||
|
(let
|
||||||
|
((axis-exprs (map (fn (toks) (if (= (len toks) 0) :all (parse-apl-expr toks))) sections)))
|
||||||
|
(let
|
||||||
|
((indexed (cons :bracket (cons val-node axis-exprs))))
|
||||||
|
(maybe-bracket indexed tokens next-after)))))))
|
||||||
|
(list val-node after))))
|
||||||
1748
lib/apl/runtime.sx
1748
lib/apl/runtime.sx
File diff suppressed because it is too large
Load Diff
17
lib/apl/scoreboard.json
Normal file
17
lib/apl/scoreboard.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"suites": {
|
||||||
|
"structural": {"pass": 94, "fail": 0},
|
||||||
|
"operators": {"pass": 117, "fail": 0},
|
||||||
|
"dfn": {"pass": 24, "fail": 0},
|
||||||
|
"tradfn": {"pass": 25, "fail": 0},
|
||||||
|
"valence": {"pass": 14, "fail": 0},
|
||||||
|
"programs": {"pass": 45, "fail": 0},
|
||||||
|
"system": {"pass": 13, "fail": 0},
|
||||||
|
"idioms": {"pass": 64, "fail": 0},
|
||||||
|
"eval-ops": {"pass": 14, "fail": 0},
|
||||||
|
"pipeline": {"pass": 40, "fail": 0}
|
||||||
|
},
|
||||||
|
"total_pass": 450,
|
||||||
|
"total_fail": 0,
|
||||||
|
"total": 450
|
||||||
|
}
|
||||||
22
lib/apl/scoreboard.md
Normal file
22
lib/apl/scoreboard.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# APL Conformance Scoreboard
|
||||||
|
|
||||||
|
_Generated by `lib/apl/conformance.sh`_
|
||||||
|
|
||||||
|
| Suite | Pass | Fail | Total |
|
||||||
|
|-------|-----:|-----:|------:|
|
||||||
|
| structural | 94 | 0 | 94 |
|
||||||
|
| operators | 117 | 0 | 117 |
|
||||||
|
| dfn | 24 | 0 | 24 |
|
||||||
|
| tradfn | 25 | 0 | 25 |
|
||||||
|
| valence | 14 | 0 | 14 |
|
||||||
|
| programs | 45 | 0 | 45 |
|
||||||
|
| system | 13 | 0 | 13 |
|
||||||
|
| idioms | 64 | 0 | 64 |
|
||||||
|
| eval-ops | 14 | 0 | 14 |
|
||||||
|
| pipeline | 40 | 0 | 40 |
|
||||||
|
| **Total** | **450** | **0** | **450** |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Suites use the standard `apl-test name got expected` framework loaded against `lib/apl/runtime.sx` + `lib/apl/transpile.sx`.
|
||||||
|
- `lib/apl/tests/parse.sx` and `lib/apl/tests/scalar.sx` use their own self-contained frameworks and are excluded from this scoreboard.
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
cd "$(git rev-parse --show-toplevel)"
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
SX_SERVER="${SX_SERVER:-/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||||
if [ ! -x "$SX_SERVER" ]; then
|
if [ ! -x "$SX_SERVER" ]; then
|
||||||
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
SX_SERVER="hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$SX_SERVER" ]; then
|
if [ ! -x "$SX_SERVER" ]; then
|
||||||
echo "ERROR: sx_server.exe not found."
|
echo "ERROR: sx_server.exe not found."
|
||||||
@@ -18,19 +18,38 @@ TMPFILE=$(mktemp); trap "rm -f $TMPFILE" EXIT
|
|||||||
cat > "$TMPFILE" << 'EPOCHS'
|
cat > "$TMPFILE" << 'EPOCHS'
|
||||||
(epoch 1)
|
(epoch 1)
|
||||||
(load "spec/stdlib.sx")
|
(load "spec/stdlib.sx")
|
||||||
|
(load "lib/r7rs.sx")
|
||||||
(load "lib/apl/runtime.sx")
|
(load "lib/apl/runtime.sx")
|
||||||
|
(load "lib/apl/tokenizer.sx")
|
||||||
|
(load "lib/apl/parser.sx")
|
||||||
|
(load "lib/apl/transpile.sx")
|
||||||
(epoch 2)
|
(epoch 2)
|
||||||
(load "lib/apl/tests/runtime.sx")
|
(eval "(define apl-test-pass 0)")
|
||||||
|
(eval "(define apl-test-fail 0)")
|
||||||
|
(eval "(define apl-test-fails (list))")
|
||||||
|
(eval "(define apl-test (fn (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 {:name name :got got :expected expected})))))))")
|
||||||
(epoch 3)
|
(epoch 3)
|
||||||
|
(load "lib/apl/tests/structural.sx")
|
||||||
|
(load "lib/apl/tests/operators.sx")
|
||||||
|
(load "lib/apl/tests/dfn.sx")
|
||||||
|
(load "lib/apl/tests/tradfn.sx")
|
||||||
|
(load "lib/apl/tests/valence.sx")
|
||||||
|
(load "lib/apl/tests/programs.sx")
|
||||||
|
(load "lib/apl/tests/system.sx")
|
||||||
|
(load "lib/apl/tests/idioms.sx")
|
||||||
|
(load "lib/apl/tests/eval-ops.sx")
|
||||||
|
(load "lib/apl/tests/pipeline.sx")
|
||||||
|
(load "lib/apl/tests/programs-e2e.sx")
|
||||||
|
(epoch 4)
|
||||||
(eval "(list apl-test-pass apl-test-fail)")
|
(eval "(list apl-test-pass apl-test-fail)")
|
||||||
EPOCHS
|
EPOCHS
|
||||||
|
|
||||||
OUTPUT=$(timeout 60 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
OUTPUT=$(timeout 300 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||||
|
|
||||||
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 3 / {getline; print; exit}')
|
LINE=$(echo "$OUTPUT" | awk '/^\(ok-len 4 / {getline; print; exit}')
|
||||||
if [ -z "$LINE" ]; then
|
if [ -z "$LINE" ]; then
|
||||||
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 3 \([0-9]+ [0-9]+\)\)' | tail -1 \
|
LINE=$(echo "$OUTPUT" | grep -E '^\(ok 4 \([0-9]+ [0-9]+\)\)' | tail -1 \
|
||||||
| sed -E 's/^\(ok 3 //; s/\)$//')
|
| sed -E 's/^\(ok 4 //; s/\)$//')
|
||||||
fi
|
fi
|
||||||
if [ -z "$LINE" ]; then
|
if [ -z "$LINE" ]; then
|
||||||
echo "ERROR: could not extract summary"
|
echo "ERROR: could not extract summary"
|
||||||
|
|||||||
227
lib/apl/tests/dfn.sx
Normal file
227
lib/apl/tests/dfn.sx
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
; Tests for apl-eval-ast and apl-call-dfn (manual AST construction).
|
||||||
|
|
||||||
|
(define rv (fn (arr) (get arr :ravel)))
|
||||||
|
(define sh (fn (arr) (get arr :shape)))
|
||||||
|
|
||||||
|
(define mknum (fn (n) (list :num n)))
|
||||||
|
(define mkname (fn (s) (list :name s)))
|
||||||
|
(define mkfg (fn (g) (list :fn-glyph g)))
|
||||||
|
(define mkmon (fn (g a) (list :monad (mkfg g) a)))
|
||||||
|
(define mkdyd (fn (g l r) (list :dyad (mkfg g) l r)))
|
||||||
|
(define mkdfn1 (fn (body) (list :dfn body)))
|
||||||
|
(define mkprog (fn (stmts) (cons :program stmts)))
|
||||||
|
|
||||||
|
(define mkasg (fn (mkname expr) (list :assign mkname expr)))
|
||||||
|
|
||||||
|
(define mkgrd (fn (c e) (list :guard c e)))
|
||||||
|
|
||||||
|
(define mkdfn (fn (stmts) (cons :dfn stmts)))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval :num literal"
|
||||||
|
(rv (apl-eval-ast (mknum 42) {}))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval :num literal shape"
|
||||||
|
(sh (apl-eval-ast (mknum 42) {}))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval :dyad +"
|
||||||
|
(rv (apl-eval-ast (mkdyd "+" (mknum 2) (mknum 3)) {}))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval :dyad ×"
|
||||||
|
(rv (apl-eval-ast (mkdyd "×" (mknum 6) (mknum 7)) {}))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval :monad - (negate)"
|
||||||
|
(rv (apl-eval-ast (mkmon "-" (mknum 7)) {}))
|
||||||
|
(list -7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval :monad ⌊ (floor)"
|
||||||
|
(rv (apl-eval-ast (mkmon "⌊" (mknum 3)) {}))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval :name ⍵ from env"
|
||||||
|
(rv (apl-eval-ast (mkname "⍵") {:omega (apl-scalar 99) :alpha nil}))
|
||||||
|
(list 99))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval :name ⍺ from env"
|
||||||
|
(rv (apl-eval-ast (mkname "⍺") {:omega nil :alpha (apl-scalar 7)}))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn {⍵+1} called monadic"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn-m
|
||||||
|
(mkdfn1 (mkdyd "+" (mkname "⍵") (mknum 1)))
|
||||||
|
(apl-scalar 5)))
|
||||||
|
(list 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn {⍺+⍵} called dyadic"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn
|
||||||
|
(mkdfn1 (mkdyd "+" (mkname "⍺") (mkname "⍵")))
|
||||||
|
(apl-scalar 4)
|
||||||
|
(apl-scalar 9)))
|
||||||
|
(list 13))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn {⍺×⍵} dyadic on vectors"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn
|
||||||
|
(mkdfn1 (mkdyd "×" (mkname "⍺") (mkname "⍵")))
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 10 20 30))))
|
||||||
|
(list 10 40 90))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn {-⍵} monadic negate"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn-m
|
||||||
|
(mkdfn1 (mkmon "-" (mkname "⍵")))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list -1 -2 -3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn {⍺-⍵} dyadic subtract scalar"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn
|
||||||
|
(mkdfn1 (mkdyd "-" (mkname "⍺") (mkname "⍵")))
|
||||||
|
(apl-scalar 10)
|
||||||
|
(apl-scalar 3)))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn {⌈⍺,⍵} not used (just verify : missing) — ceiling of right"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn-m (mkdfn1 (mkmon "⌈" (mkname "⍵"))) (apl-scalar 5)))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn nested dyad"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn
|
||||||
|
(mkdfn1
|
||||||
|
(mkdyd "+" (mkname "⍺") (mkdyd "×" (mkname "⍵") (mknum 2))))
|
||||||
|
(apl-scalar 1)
|
||||||
|
(apl-scalar 3)))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn local assign x←⍵+1; ⍺×x"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn
|
||||||
|
(mkdfn
|
||||||
|
(list
|
||||||
|
(mkasg "x" (mkdyd "+" (mkname "⍵") (mknum 1)))
|
||||||
|
(mkdyd "×" (mkname "⍺") (mkname "x"))))
|
||||||
|
(apl-scalar 3)
|
||||||
|
(apl-scalar 4)))
|
||||||
|
(list 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn guard: 0=⍵:99; ⍵×2 (true branch)"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn-m
|
||||||
|
(mkdfn
|
||||||
|
(list
|
||||||
|
(mkgrd (mkdyd "=" (mknum 0) (mkname "⍵")) (mknum 99))
|
||||||
|
(mkdyd "×" (mkname "⍵") (mknum 2))))
|
||||||
|
(apl-scalar 0)))
|
||||||
|
(list 99))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn guard: 0=⍵:99; ⍵×2 (false branch)"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn-m
|
||||||
|
(mkdfn
|
||||||
|
(list
|
||||||
|
(mkgrd (mkdyd "=" (mknum 0) (mkname "⍵")) (mknum 99))
|
||||||
|
(mkdyd "×" (mkname "⍵") (mknum 2))))
|
||||||
|
(apl-scalar 5)))
|
||||||
|
(list 10))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn default ⍺←10 used (monadic call)"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn-m
|
||||||
|
(mkdfn
|
||||||
|
(list
|
||||||
|
(mkasg "⍺" (mknum 10))
|
||||||
|
(mkdyd "+" (mkname "⍺") (mkname "⍵"))))
|
||||||
|
(apl-scalar 5)))
|
||||||
|
(list 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn default ⍺←10 ignored when ⍺ given (dyadic call)"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn
|
||||||
|
(mkdfn
|
||||||
|
(list
|
||||||
|
(mkasg "⍺" (mknum 10))
|
||||||
|
(mkdyd "+" (mkname "⍺") (mkname "⍵"))))
|
||||||
|
(apl-scalar 100)
|
||||||
|
(apl-scalar 5)))
|
||||||
|
(list 105))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn ∇ recursion: factorial via guard"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn-m
|
||||||
|
(mkdfn
|
||||||
|
(list
|
||||||
|
(mkgrd (mkdyd "=" (mknum 0) (mkname "⍵")) (mknum 1))
|
||||||
|
(mkdyd
|
||||||
|
"×"
|
||||||
|
(mkname "⍵")
|
||||||
|
(mkmon "∇" (mkdyd "-" (mkname "⍵") (mknum 1))))))
|
||||||
|
(apl-scalar 5)))
|
||||||
|
(list 120))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn ∇ recursion: 3 → 6 (factorial)"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn-m
|
||||||
|
(mkdfn
|
||||||
|
(list
|
||||||
|
(mkgrd (mkdyd "=" (mknum 0) (mkname "⍵")) (mknum 1))
|
||||||
|
(mkdyd
|
||||||
|
"×"
|
||||||
|
(mkname "⍵")
|
||||||
|
(mkmon "∇" (mkdyd "-" (mkname "⍵") (mknum 1))))))
|
||||||
|
(apl-scalar 3)))
|
||||||
|
(list 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn local: x←⍵+10; y←x×2; y"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn-m
|
||||||
|
(mkdfn
|
||||||
|
(list
|
||||||
|
(mkasg "x" (mkdyd "+" (mkname "⍵") (mknum 10)))
|
||||||
|
(mkasg "y" (mkdyd "×" (mkname "x") (mknum 2)))
|
||||||
|
(mkname "y")))
|
||||||
|
(apl-scalar 5)))
|
||||||
|
(list 30))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn first guard wins: many guards"
|
||||||
|
(rv
|
||||||
|
(apl-call-dfn-m
|
||||||
|
(mkdfn
|
||||||
|
(list
|
||||||
|
(mkgrd (mkdyd "=" (mknum 1) (mkname "⍵")) (mknum 100))
|
||||||
|
(mkgrd (mkdyd "=" (mknum 2) (mkname "⍵")) (mknum 200))
|
||||||
|
(mkgrd (mkdyd "=" (mknum 3) (mkname "⍵")) (mknum 300))
|
||||||
|
(mknum 0)))
|
||||||
|
(apl-scalar 2)))
|
||||||
|
(list 200))
|
||||||
147
lib/apl/tests/eval-ops.sx
Normal file
147
lib/apl/tests/eval-ops.sx
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
; Tests for operator handling in apl-eval-ast (Phase 7).
|
||||||
|
; Manual AST construction; verifies :derived-fn / :outer / :derived-fn2
|
||||||
|
; route through apl-resolve-monadic / apl-resolve-dyadic correctly.
|
||||||
|
|
||||||
|
(define mkrv (fn (arr) (get arr :ravel)))
|
||||||
|
(define mksh (fn (arr) (get arr :shape)))
|
||||||
|
(define mknum (fn (n) (list :num n)))
|
||||||
|
(define mkfg (fn (g) (list :fn-glyph g)))
|
||||||
|
(define mkmon (fn (g a) (list :monad g a)))
|
||||||
|
(define mkdyd (fn (g l r) (list :dyad g l r)))
|
||||||
|
(define mkder (fn (op f) (list :derived-fn op f)))
|
||||||
|
(define mkdr2 (fn (op f g) (list :derived-fn2 op f g)))
|
||||||
|
(define mkout (fn (f) (list :outer "∘." f)))
|
||||||
|
|
||||||
|
; helper: literal vector AST via :vec (from list of values)
|
||||||
|
(define mkvec (fn (xs) (cons :vec (map (fn (n) (mknum n)) xs))))
|
||||||
|
|
||||||
|
; ---------- monadic operators ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast +/ ⍳5 → 15"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkmon (mkder "/" (mkfg "+")) (mkmon (mkfg "⍳") (mknum 5)))
|
||||||
|
{}))
|
||||||
|
(list 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast ×/ ⍳5 → 120"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkmon (mkder "/" (mkfg "×")) (mkmon (mkfg "⍳") (mknum 5)))
|
||||||
|
{}))
|
||||||
|
(list 120))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast ⌈/ — max reduce"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkmon (mkder "/" (mkfg "⌈")) (mkvec (list 3 1 4 1 5 9 2 6)))
|
||||||
|
{}))
|
||||||
|
(list 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast +\\ scan"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkmon (mkder "\\" (mkfg "+")) (mkvec (list 1 2 3 4 5)))
|
||||||
|
{}))
|
||||||
|
(list 1 3 6 10 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast +⌿ first-axis reduce on vector"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkmon (mkder "⌿" (mkfg "+")) (mkvec (list 1 2 3 4 5)))
|
||||||
|
{}))
|
||||||
|
(list 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast -¨ each-negate"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkmon (mkder "¨" (mkfg "-")) (mkvec (list 1 2 3 4)))
|
||||||
|
{}))
|
||||||
|
(list -1 -2 -3 -4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast +⍨ commute (double via x+x)"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast (mkmon (mkder "⍨" (mkfg "+")) (mknum 7)) {}))
|
||||||
|
(list 14))
|
||||||
|
|
||||||
|
; ---------- dyadic operators ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast outer ∘.× — multiplication table"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkdyd
|
||||||
|
(mkout (mkfg "×"))
|
||||||
|
(mkvec (list 1 2 3))
|
||||||
|
(mkvec (list 1 2 3)))
|
||||||
|
{}))
|
||||||
|
(list 1 2 3 2 4 6 3 6 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast outer ∘.× shape (3 3)"
|
||||||
|
(mksh
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkdyd
|
||||||
|
(mkout (mkfg "×"))
|
||||||
|
(mkvec (list 1 2 3))
|
||||||
|
(mkvec (list 1 2 3)))
|
||||||
|
{}))
|
||||||
|
(list 3 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast inner +.× — dot product"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkdyd
|
||||||
|
(mkdr2 "." (mkfg "+") (mkfg "×"))
|
||||||
|
(mkvec (list 1 2 3))
|
||||||
|
(mkvec (list 4 5 6)))
|
||||||
|
{}))
|
||||||
|
(list 32))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast inner ∧.= equal vectors"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkdyd
|
||||||
|
(mkdr2 "." (mkfg "∧") (mkfg "="))
|
||||||
|
(mkvec (list 1 2 3))
|
||||||
|
(mkvec (list 1 2 3)))
|
||||||
|
{}))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast each-dyadic +¨"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkdyd
|
||||||
|
(mkder "¨" (mkfg "+"))
|
||||||
|
(mkvec (list 1 2 3))
|
||||||
|
(mkvec (list 10 20 30)))
|
||||||
|
{}))
|
||||||
|
(list 11 22 33))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast commute -⍨ (subtract swapped)"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkdyd (mkder "⍨" (mkfg "-")) (mknum 5) (mknum 3))
|
||||||
|
{}))
|
||||||
|
(list -2))
|
||||||
|
|
||||||
|
; ---------- nested operators ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"eval-ast +/¨ — sum of each"
|
||||||
|
(mkrv
|
||||||
|
(apl-eval-ast
|
||||||
|
(mkmon (mkder "/" (mkfg "+")) (mkvec (list 10 20 30)))
|
||||||
|
{}))
|
||||||
|
(list 60))
|
||||||
359
lib/apl/tests/idioms.sx
Normal file
359
lib/apl/tests/idioms.sx
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
; APL idiom corpus — classic Roger Hui / Phil Last idioms expressed
|
||||||
|
; through our runtime primitives. Each test names the APL one-liner
|
||||||
|
; and verifies the equivalent runtime call.
|
||||||
|
|
||||||
|
(define mkrv (fn (arr) (get arr :ravel)))
|
||||||
|
(define mksh (fn (arr) (get arr :shape)))
|
||||||
|
|
||||||
|
; ---------- reductions ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"+/⍵ — sum"
|
||||||
|
(mkrv (apl-reduce apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"(+/⍵)÷⍴⍵ — mean"
|
||||||
|
(mkrv
|
||||||
|
(apl-div
|
||||||
|
(apl-reduce apl-add (make-array (list 5) (list 1 2 3 4 5)))
|
||||||
|
(apl-scalar 5)))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⌈/⍵ — max"
|
||||||
|
(mkrv (apl-reduce apl-max (make-array (list 6) (list 3 1 4 1 5 9))))
|
||||||
|
(list 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⌊/⍵ — min"
|
||||||
|
(mkrv (apl-reduce apl-min (make-array (list 6) (list 3 1 4 1 5 9))))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"(⌈/⍵)-⌊/⍵ — range"
|
||||||
|
(mkrv
|
||||||
|
(apl-sub
|
||||||
|
(apl-reduce apl-max (make-array (list 6) (list 3 1 4 1 5 9)))
|
||||||
|
(apl-reduce apl-min (make-array (list 6) (list 3 1 4 1 5 9)))))
|
||||||
|
(list 8))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"×/⍵ — product"
|
||||||
|
(mkrv (apl-reduce apl-mul (make-array (list 4) (list 1 2 3 4))))
|
||||||
|
(list 24))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"+\\⍵ — running sum"
|
||||||
|
(mkrv (apl-scan apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 3 6 10 15))
|
||||||
|
|
||||||
|
; ---------- sort / order ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⍵[⍋⍵] — sort ascending"
|
||||||
|
(mkrv (apl-quicksort (make-array (list 5) (list 3 1 4 1 5))))
|
||||||
|
(list 1 1 3 4 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⌽⍵ — reverse"
|
||||||
|
(mkrv (apl-reverse (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 5 4 3 2 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⊃⌽⍵ — last element"
|
||||||
|
(mkrv
|
||||||
|
(apl-disclose (apl-reverse (make-array (list 4) (list 10 20 30 40)))))
|
||||||
|
(list 40))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"1↑⍵ — first element"
|
||||||
|
(mkrv
|
||||||
|
(apl-take (apl-scalar 1) (make-array (list 4) (list 10 20 30 40))))
|
||||||
|
(list 10))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"1↓⍵ — drop first"
|
||||||
|
(mkrv
|
||||||
|
(apl-drop (apl-scalar 1) (make-array (list 4) (list 10 20 30 40))))
|
||||||
|
(list 20 30 40))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"¯1↓⍵ — drop last"
|
||||||
|
(mkrv
|
||||||
|
(apl-drop (apl-scalar -1) (make-array (list 4) (list 10 20 30 40))))
|
||||||
|
(list 10 20 30))
|
||||||
|
|
||||||
|
; ---------- counts / membership ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"≢⍵ — tally"
|
||||||
|
(mkrv (apl-tally (make-array (list 7) (list 9 8 7 6 5 4 3))))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"+/⍵=v — count occurrences of v"
|
||||||
|
(mkrv
|
||||||
|
(apl-reduce
|
||||||
|
apl-add
|
||||||
|
(apl-eq (make-array (list 7) (list 1 2 3 2 1 3 2)) (apl-scalar 2))))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"0=N|M — divisibility test"
|
||||||
|
(mkrv (apl-eq (apl-scalar 0) (apl-mod (apl-scalar 3) (apl-scalar 12))))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
; ---------- shape constructors ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"N⍴1 — vector of N ones"
|
||||||
|
(mkrv (apl-reshape (apl-scalar 5) (apl-scalar 1)))
|
||||||
|
(list 1 1 1 1 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"(N N)⍴0 — N×N zero matrix"
|
||||||
|
(mkrv (apl-reshape (make-array (list 2) (list 3 3)) (apl-scalar 0)))
|
||||||
|
(list 0 0 0 0 0 0 0 0 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⍳∘.=⍳ — N×N identity matrix"
|
||||||
|
(mkrv
|
||||||
|
(apl-outer apl-eq (apl-iota (apl-scalar 3)) (apl-iota (apl-scalar 3))))
|
||||||
|
(list 1 0 0 0 1 0 0 0 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⍳∘.×⍳ — multiplication table"
|
||||||
|
(mkrv
|
||||||
|
(apl-outer apl-mul (apl-iota (apl-scalar 3)) (apl-iota (apl-scalar 3))))
|
||||||
|
(list 1 2 3 2 4 6 3 6 9))
|
||||||
|
|
||||||
|
; ---------- numerical idioms ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"+\\⍳N — triangular numbers"
|
||||||
|
(mkrv (apl-scan apl-add (apl-iota (apl-scalar 5))))
|
||||||
|
(list 1 3 6 10 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"+/⍳N=N×(N+1)÷2 — sum of 1..N"
|
||||||
|
(mkrv (apl-reduce apl-add (apl-iota (apl-scalar 10))))
|
||||||
|
(list 55))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"×/⍳N — factorial via iota"
|
||||||
|
(mkrv (apl-reduce apl-mul (apl-iota (apl-scalar 5))))
|
||||||
|
(list 120))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"2|⍵ — parity (1=odd)"
|
||||||
|
(mkrv (apl-mod (apl-scalar 2) (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 0 1 0 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"+/2|⍵ — count odd"
|
||||||
|
(mkrv
|
||||||
|
(apl-reduce
|
||||||
|
apl-add
|
||||||
|
(apl-mod (apl-scalar 2) (make-array (list 5) (list 1 2 3 4 5)))))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
; ---------- boolean idioms ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"∧/⍵ — all-true"
|
||||||
|
(mkrv (apl-reduce apl-and (make-array (list 4) (list 1 1 1 1))))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"∧/⍵ — all-true with zero is false"
|
||||||
|
(mkrv (apl-reduce apl-and (make-array (list 4) (list 1 1 0 1))))
|
||||||
|
(list 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"∨/⍵ — any-true"
|
||||||
|
(mkrv (apl-reduce apl-or (make-array (list 4) (list 0 0 1 0))))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"∨/⍵ — any-true all zero is false"
|
||||||
|
(mkrv (apl-reduce apl-or (make-array (list 4) (list 0 0 0 0))))
|
||||||
|
(list 0))
|
||||||
|
|
||||||
|
; ---------- selection / scaling ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⍵×⍵ — square each"
|
||||||
|
(mkrv
|
||||||
|
(apl-mul
|
||||||
|
(make-array (list 4) (list 1 2 3 4))
|
||||||
|
(make-array (list 4) (list 1 2 3 4))))
|
||||||
|
(list 1 4 9 16))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"+/⍵×⍵ — sum of squares"
|
||||||
|
(mkrv
|
||||||
|
(apl-reduce
|
||||||
|
apl-add
|
||||||
|
(apl-mul
|
||||||
|
(make-array (list 4) (list 1 2 3 4))
|
||||||
|
(make-array (list 4) (list 1 2 3 4)))))
|
||||||
|
(list 30))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⍵-(+/⍵)÷⍴⍵ — mean-centered"
|
||||||
|
(mkrv
|
||||||
|
(apl-sub
|
||||||
|
(make-array (list 5) (list 2 4 6 8 10))
|
||||||
|
(apl-div
|
||||||
|
(apl-reduce apl-add (make-array (list 5) (list 2 4 6 8 10)))
|
||||||
|
(apl-scalar 5))))
|
||||||
|
(list -4 -2 0 2 4))
|
||||||
|
|
||||||
|
; ---------- shape / structure ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
",⍵ — ravel"
|
||||||
|
(mkrv (apl-ravel (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 2 3 4 5 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⍴⍴⍵ — rank"
|
||||||
|
(mkrv
|
||||||
|
(apl-shape (apl-shape (make-array (list 2 3) (list 1 2 3 4 5 6)))))
|
||||||
|
(list 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: +/⍳N → triangular(N)"
|
||||||
|
(mkrv (apl-run "+/⍳100"))
|
||||||
|
(list 5050))
|
||||||
|
|
||||||
|
(apl-test "src: ×/⍳N → N!" (mkrv (apl-run "×/⍳6")) (list 720))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: ⌈/V — max"
|
||||||
|
(mkrv (apl-run "⌈/3 1 4 1 5 9 2 6"))
|
||||||
|
(list 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: ⌊/V — min"
|
||||||
|
(mkrv (apl-run "⌊/3 1 4 1 5 9 2 6"))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: range = (⌈/V) - ⌊/V"
|
||||||
|
(mkrv (apl-run "(⌈/3 1 4 1 5 9 2 6) - ⌊/3 1 4 1 5 9 2 6"))
|
||||||
|
(list 8))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: +\\V — running sum"
|
||||||
|
(mkrv (apl-run "+\\1 2 3 4 5"))
|
||||||
|
(list 1 3 6 10 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: ×\\V — running product"
|
||||||
|
(mkrv (apl-run "×\\1 2 3 4 5"))
|
||||||
|
(list 1 2 6 24 120))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: V × V — squares"
|
||||||
|
(mkrv (apl-run "(⍳5) × ⍳5"))
|
||||||
|
(list 1 4 9 16 25))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: +/V × V — sum of squares"
|
||||||
|
(mkrv (apl-run "+/(⍳5) × ⍳5"))
|
||||||
|
(list 55))
|
||||||
|
|
||||||
|
(apl-test "src: ∧/V — all-true" (mkrv (apl-run "∧/1 1 1 1")) (list 1))
|
||||||
|
|
||||||
|
(apl-test "src: ∨/V — any-true" (mkrv (apl-run "∨/0 0 1 0")) (list 1))
|
||||||
|
|
||||||
|
(apl-test "src: 0 = N|M — divides" (mkrv (apl-run "0 = 3 | 12")) (list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: 2 | V — parity"
|
||||||
|
(mkrv (apl-run "2 | 1 2 3 4 5 6"))
|
||||||
|
(list 1 0 1 0 1 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: +/2|V — count odd"
|
||||||
|
(mkrv (apl-run "+/2 | 1 2 3 4 5 6"))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test "src: ⍴ V" (mkrv (apl-run "⍴ 1 2 3 4 5")) (list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: ⍴⍴ M — rank"
|
||||||
|
(mkrv (apl-run "⍴ ⍴ (2 3) ⍴ ⍳6"))
|
||||||
|
(list 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: N⍴1 — vector of ones"
|
||||||
|
(mkrv (apl-run "5 ⍴ 1"))
|
||||||
|
(list 1 1 1 1 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: ⍳N ∘.= ⍳N — identity matrix"
|
||||||
|
(mkrv (apl-run "(⍳3) ∘.= ⍳3"))
|
||||||
|
(list 1 0 0 0 1 0 0 0 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: ⍳N ∘.× ⍳N — multiplication table"
|
||||||
|
(mkrv (apl-run "(⍳3) ∘.× ⍳3"))
|
||||||
|
(list 1 2 3 2 4 6 3 6 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: V +.× V — dot product"
|
||||||
|
(mkrv (apl-run "1 2 3 +.× 4 5 6"))
|
||||||
|
(list 32))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: ∧.= V — vectors equal?"
|
||||||
|
(mkrv (apl-run "1 2 3 ∧.= 1 2 3"))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: V[1] — first element"
|
||||||
|
(mkrv (apl-run "(10 20 30 40)[1]"))
|
||||||
|
(list 10))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: 1↑V — first via take"
|
||||||
|
(mkrv (apl-run "1 ↑ 10 20 30 40"))
|
||||||
|
(list 10))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: 1↓V — drop first"
|
||||||
|
(mkrv (apl-run "1 ↓ 10 20 30 40"))
|
||||||
|
(list 20 30 40))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: ¯1↓V — drop last"
|
||||||
|
(mkrv (apl-run "¯1 ↓ 10 20 30 40"))
|
||||||
|
(list 10 20 30))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: ⌽V — reverse"
|
||||||
|
(mkrv (apl-run "⌽ 1 2 3 4 5"))
|
||||||
|
(list 5 4 3 2 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: ≢V — tally"
|
||||||
|
(mkrv (apl-run "≢ 9 8 7 6 5 4 3 2 1"))
|
||||||
|
(list 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: ,M — ravel"
|
||||||
|
(mkrv (apl-run ", (2 3) ⍴ ⍳6"))
|
||||||
|
(list 1 2 3 4 5 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: A=V — count occurrences"
|
||||||
|
(mkrv (apl-run "+/2 = 1 2 3 2 1 3 2"))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"src: ⌈/(V × V) — max squared"
|
||||||
|
(mkrv (apl-run "⌈/(1 2 3 4 5) × 1 2 3 4 5"))
|
||||||
|
(list 25))
|
||||||
791
lib/apl/tests/operators.sx
Normal file
791
lib/apl/tests/operators.sx
Normal file
@@ -0,0 +1,791 @@
|
|||||||
|
(define rv (fn (arr) (get arr :ravel)))
|
||||||
|
(define sh (fn (arr) (get arr :shape)))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce +/ vector"
|
||||||
|
(rv (apl-reduce apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce x/ vector"
|
||||||
|
(rv (apl-reduce apl-mul (make-array (list 4) (list 1 2 3 4))))
|
||||||
|
(list 24))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce max/ vector"
|
||||||
|
(rv (apl-reduce apl-max (make-array (list 5) (list 3 1 4 1 5))))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce min/ vector"
|
||||||
|
(rv (apl-reduce apl-min (make-array (list 3) (list 3 1 4))))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce and/ all true"
|
||||||
|
(rv (apl-reduce apl-and (make-array (list 3) (list 1 1 1))))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce or/ with true"
|
||||||
|
(rv (apl-reduce apl-or (make-array (list 3) (list 0 0 1))))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce +/ single element"
|
||||||
|
(rv (apl-reduce apl-add (make-array (list 1) (list 42))))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce +/ scalar no-op"
|
||||||
|
(rv (apl-reduce apl-add (apl-scalar 7)))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce +/ shape is scalar"
|
||||||
|
(sh (apl-reduce apl-add (make-array (list 4) (list 1 2 3 4))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce +/ matrix row sums shape"
|
||||||
|
(sh (apl-reduce apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce +/ matrix row sums values"
|
||||||
|
(rv (apl-reduce apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 6 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce max/ matrix row maxima"
|
||||||
|
(rv (apl-reduce apl-max (make-array (list 2 3) (list 3 1 4 1 5 9))))
|
||||||
|
(list 4 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce-first +/ vector same as reduce"
|
||||||
|
(rv (apl-reduce-first apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce-first +/ matrix col sums shape"
|
||||||
|
(sh
|
||||||
|
(apl-reduce-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce-first +/ matrix col sums values"
|
||||||
|
(rv
|
||||||
|
(apl-reduce-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 5 7 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reduce-first max/ matrix col maxima"
|
||||||
|
(rv
|
||||||
|
(apl-reduce-first apl-max (make-array (list 3 2) (list 1 9 2 8 3 7))))
|
||||||
|
(list 3 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan +\\ vector"
|
||||||
|
(rv (apl-scan apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 3 6 10 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan x\\ vector cumulative product"
|
||||||
|
(rv (apl-scan apl-mul (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 2 6 24 120))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan max\\ vector running max"
|
||||||
|
(rv (apl-scan apl-max (make-array (list 5) (list 3 1 4 1 5))))
|
||||||
|
(list 3 3 4 4 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan min\\ vector running min"
|
||||||
|
(rv (apl-scan apl-min (make-array (list 5) (list 3 1 4 1 5))))
|
||||||
|
(list 3 1 1 1 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan +\\ single element"
|
||||||
|
(rv (apl-scan apl-add (make-array (list 1) (list 42))))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan +\\ scalar no-op"
|
||||||
|
(rv (apl-scan apl-add (apl-scalar 7)))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan +\\ vector preserves shape"
|
||||||
|
(sh (apl-scan apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan +\\ matrix preserves shape"
|
||||||
|
(sh (apl-scan apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan +\\ matrix row-wise"
|
||||||
|
(rv (apl-scan apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 3 6 4 9 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan max\\ matrix row-wise running max"
|
||||||
|
(rv (apl-scan apl-max (make-array (list 2 3) (list 3 1 4 1 5 9))))
|
||||||
|
(list 3 3 4 1 5 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan-first +\\ vector same as scan"
|
||||||
|
(rv (apl-scan-first apl-add (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 3 6 10 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan-first +\\ scalar no-op"
|
||||||
|
(rv (apl-scan-first apl-add (apl-scalar 9)))
|
||||||
|
(list 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan-first +\\ matrix preserves shape"
|
||||||
|
(sh (apl-scan-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan-first +\\ matrix col-wise"
|
||||||
|
(rv (apl-scan-first apl-add (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 2 3 5 7 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"scan-first max\\ matrix col-wise running max"
|
||||||
|
(rv (apl-scan-first apl-max (make-array (list 3 2) (list 3 1 4 1 5 9))))
|
||||||
|
(list 3 1 4 1 5 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each negate vector"
|
||||||
|
(rv (apl-each apl-neg-m (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list -1 -2 -3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each negate vector preserves shape"
|
||||||
|
(sh (apl-each apl-neg-m (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each reciprocal vector"
|
||||||
|
(rv (apl-each apl-recip (make-array (list 3) (list 1 2 4))))
|
||||||
|
(list 1 (/ 1 2) (/ 1 4)))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each abs vector"
|
||||||
|
(rv (apl-each apl-abs (make-array (list 4) (list -1 2 -3 4))))
|
||||||
|
(list 1 2 3 4))
|
||||||
|
|
||||||
|
(apl-test "each scalar" (rv (apl-each apl-neg-m (apl-scalar 5))) (list -5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each scalar shape"
|
||||||
|
(sh (apl-each apl-neg-m (apl-scalar 5)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each negate matrix shape"
|
||||||
|
(sh (apl-each apl-neg-m (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each negate matrix values"
|
||||||
|
(rv (apl-each apl-neg-m (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list -1 -2 -3 -4 -5 -6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each-dyadic scalar+scalar"
|
||||||
|
(rv (apl-each-dyadic apl-add (apl-scalar 3) (apl-scalar 4)))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each-dyadic scalar+vector"
|
||||||
|
(rv
|
||||||
|
(apl-each-dyadic
|
||||||
|
apl-add
|
||||||
|
(apl-scalar 10)
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 11 12 13))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each-dyadic vector+scalar"
|
||||||
|
(rv
|
||||||
|
(apl-each-dyadic
|
||||||
|
apl-add
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(apl-scalar 10)))
|
||||||
|
(list 11 12 13))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each-dyadic vector+vector"
|
||||||
|
(rv
|
||||||
|
(apl-each-dyadic
|
||||||
|
apl-add
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 10 20 30))))
|
||||||
|
(list 11 22 33))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each-dyadic mul matrix+matrix shape"
|
||||||
|
(sh
|
||||||
|
(apl-each-dyadic
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 2 2) (list 1 2 3 4))
|
||||||
|
(make-array (list 2 2) (list 5 6 7 8))))
|
||||||
|
(list 2 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"each-dyadic mul matrix+matrix values"
|
||||||
|
(rv
|
||||||
|
(apl-each-dyadic
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 2 2) (list 1 2 3 4))
|
||||||
|
(make-array (list 2 2) (list 5 6 7 8))))
|
||||||
|
(list 5 12 21 32))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"outer product mult table values"
|
||||||
|
(rv
|
||||||
|
(apl-outer
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 1 2 3 2 4 6 3 6 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"outer product mult table shape"
|
||||||
|
(sh
|
||||||
|
(apl-outer
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 3 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"outer product add table values"
|
||||||
|
(rv
|
||||||
|
(apl-outer
|
||||||
|
apl-add
|
||||||
|
(make-array (list 2) (list 1 2))
|
||||||
|
(make-array (list 3) (list 10 20 30))))
|
||||||
|
(list 11 21 31 12 22 32))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"outer product add table shape"
|
||||||
|
(sh
|
||||||
|
(apl-outer
|
||||||
|
apl-add
|
||||||
|
(make-array (list 2) (list 1 2))
|
||||||
|
(make-array (list 3) (list 10 20 30))))
|
||||||
|
(list 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"outer product scalar+vector shape"
|
||||||
|
(sh
|
||||||
|
(apl-outer apl-mul (apl-scalar 5) (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"outer product scalar+vector values"
|
||||||
|
(rv
|
||||||
|
(apl-outer apl-mul (apl-scalar 5) (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 5 10 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"outer product vector+scalar shape"
|
||||||
|
(sh
|
||||||
|
(apl-outer apl-mul (make-array (list 3) (list 1 2 3)) (apl-scalar 10)))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"outer product scalar+scalar"
|
||||||
|
(rv (apl-outer apl-mul (apl-scalar 6) (apl-scalar 7)))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"outer product scalar+scalar shape"
|
||||||
|
(sh (apl-outer apl-mul (apl-scalar 6) (apl-scalar 7)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"outer product equality identity matrix values"
|
||||||
|
(rv
|
||||||
|
(apl-outer
|
||||||
|
apl-eq
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 1 0 0 0 1 0 0 0 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"outer product matrix+vector rank doubling shape"
|
||||||
|
(sh
|
||||||
|
(apl-outer
|
||||||
|
apl-add
|
||||||
|
(make-array (list 2 2) (list 1 2 3 4))
|
||||||
|
(make-array (list 3) (list 10 20 30))))
|
||||||
|
(list 2 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"outer product matrix+vector rank doubling values"
|
||||||
|
(rv
|
||||||
|
(apl-outer
|
||||||
|
apl-add
|
||||||
|
(make-array (list 2 2) (list 1 2 3 4))
|
||||||
|
(make-array (list 3) (list 10 20 30))))
|
||||||
|
(list 11 21 31 12 22 32 13 23 33 14 24 34))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inner +.× dot product"
|
||||||
|
(rv
|
||||||
|
(apl-inner
|
||||||
|
apl-add
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 4 5 6))))
|
||||||
|
(list 32))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inner +.× dot product shape is scalar"
|
||||||
|
(sh
|
||||||
|
(apl-inner
|
||||||
|
apl-add
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 4 5 6))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inner +.× matrix multiply 2x3 * 3x2 shape"
|
||||||
|
(sh
|
||||||
|
(apl-inner
|
||||||
|
apl-add
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||||
|
(make-array (list 3 2) (list 7 8 9 10 11 12))))
|
||||||
|
(list 2 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inner +.× matrix multiply 2x3 * 3x2 values"
|
||||||
|
(rv
|
||||||
|
(apl-inner
|
||||||
|
apl-add
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||||
|
(make-array (list 3 2) (list 7 8 9 10 11 12))))
|
||||||
|
(list 58 64 139 154))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inner +.× identity matrix 2x2"
|
||||||
|
(rv
|
||||||
|
(apl-inner
|
||||||
|
apl-add
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 2 2) (list 1 0 0 1))
|
||||||
|
(make-array (list 2 2) (list 5 6 7 8))))
|
||||||
|
(list 5 6 7 8))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inner ∧.= equal vectors"
|
||||||
|
(rv
|
||||||
|
(apl-inner
|
||||||
|
apl-and
|
||||||
|
apl-eq
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inner ∧.= unequal vectors"
|
||||||
|
(rv
|
||||||
|
(apl-inner
|
||||||
|
apl-and
|
||||||
|
apl-eq
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 1 9 3))))
|
||||||
|
(list 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inner +.× matrix * vector shape"
|
||||||
|
(sh
|
||||||
|
(apl-inner
|
||||||
|
apl-add
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||||
|
(make-array (list 3) (list 7 8 9))))
|
||||||
|
(list 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inner +.× matrix * vector values"
|
||||||
|
(rv
|
||||||
|
(apl-inner
|
||||||
|
apl-add
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||||
|
(make-array (list 3) (list 7 8 9))))
|
||||||
|
(list 50 122))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inner +.× vector * matrix shape"
|
||||||
|
(sh
|
||||||
|
(apl-inner
|
||||||
|
apl-add
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3 2) (list 4 5 6 7 8 9))))
|
||||||
|
(list 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inner +.× vector * matrix values"
|
||||||
|
(rv
|
||||||
|
(apl-inner
|
||||||
|
apl-add
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3 2) (list 4 5 6 7 8 9))))
|
||||||
|
(list 40 46))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inner +.× single-element vectors"
|
||||||
|
(rv
|
||||||
|
(apl-inner
|
||||||
|
apl-add
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 1) (list 6))
|
||||||
|
(make-array (list 1) (list 7))))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"commute +⍨ scalar doubles"
|
||||||
|
(rv (apl-commute apl-add (apl-scalar 5)))
|
||||||
|
(list 10))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"commute ×⍨ vector squares"
|
||||||
|
(rv (apl-commute apl-mul (make-array (list 4) (list 1 2 3 4))))
|
||||||
|
(list 1 4 9 16))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"commute +⍨ vector doubles"
|
||||||
|
(rv (apl-commute apl-add (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 2 4 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"commute +⍨ shape preserved"
|
||||||
|
(sh (apl-commute apl-add (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"commute ×⍨ matrix shape preserved"
|
||||||
|
(sh (apl-commute apl-mul (make-array (list 2 2) (list 1 2 3 4))))
|
||||||
|
(list 2 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"commute-dyadic -⍨ swaps subtraction"
|
||||||
|
(rv (apl-commute-dyadic apl-sub (apl-scalar 5) (apl-scalar 3)))
|
||||||
|
(list -2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"commute-dyadic ÷⍨ swaps division"
|
||||||
|
(rv (apl-commute-dyadic apl-div (apl-scalar 4) (apl-scalar 12)))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"commute-dyadic -⍨ on vectors"
|
||||||
|
(rv
|
||||||
|
(apl-commute-dyadic
|
||||||
|
apl-sub
|
||||||
|
(make-array (list 3) (list 10 20 30))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list -9 -18 -27))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"commute-dyadic +⍨ commutative same result"
|
||||||
|
(rv
|
||||||
|
(apl-commute-dyadic
|
||||||
|
apl-add
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 10 20 30))))
|
||||||
|
(list 11 22 33))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"commute-dyadic ×⍨ commutative same result"
|
||||||
|
(rv
|
||||||
|
(apl-commute-dyadic
|
||||||
|
apl-mul
|
||||||
|
(make-array (list 3) (list 2 3 4))
|
||||||
|
(make-array (list 3) (list 5 6 7))))
|
||||||
|
(list 10 18 28))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compose -∘| scalar (negative abs)"
|
||||||
|
(rv (apl-compose apl-neg-m apl-abs (apl-scalar -7)))
|
||||||
|
(list -7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compose -∘| vector"
|
||||||
|
(rv
|
||||||
|
(apl-compose apl-neg-m apl-abs (make-array (list 4) (list -1 2 -3 4))))
|
||||||
|
(list -1 -2 -3 -4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compose ⌊∘- (floor of negate)"
|
||||||
|
(rv (apl-compose apl-floor apl-neg-m (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list -1 -2 -3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compose -∘| matrix shape preserved"
|
||||||
|
(sh
|
||||||
|
(apl-compose apl-neg-m apl-abs (make-array (list 2 2) (list -1 2 -3 4))))
|
||||||
|
(list 2 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compose-dyadic +∘- equals subtract scalar"
|
||||||
|
(rv (apl-compose-dyadic apl-add apl-neg-m (apl-scalar 10) (apl-scalar 3)))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compose-dyadic +∘- equals subtract vector"
|
||||||
|
(rv
|
||||||
|
(apl-compose-dyadic
|
||||||
|
apl-add
|
||||||
|
apl-neg-m
|
||||||
|
(make-array (list 3) (list 10 20 30))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 9 18 27))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compose-dyadic -∘| (subtract abs)"
|
||||||
|
(rv (apl-compose-dyadic apl-sub apl-abs (apl-scalar 10) (apl-scalar -3)))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compose-dyadic ×∘- (multiply by negative)"
|
||||||
|
(rv
|
||||||
|
(apl-compose-dyadic
|
||||||
|
apl-mul
|
||||||
|
apl-neg-m
|
||||||
|
(make-array (list 3) (list 2 3 4))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list -2 -6 -12))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compose-dyadic shape preserved"
|
||||||
|
(sh
|
||||||
|
(apl-compose-dyadic
|
||||||
|
apl-add
|
||||||
|
apl-neg-m
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||||
|
(make-array (list 2 3) (list 1 1 1 1 1 1))))
|
||||||
|
(list 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"power n=0 identity"
|
||||||
|
(rv (apl-power (fn (a) (apl-add a (apl-scalar 1))) 0 (apl-scalar 5)))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"power increment by 3"
|
||||||
|
(rv (apl-power (fn (a) (apl-add a (apl-scalar 1))) 3 (apl-scalar 0)))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"power double 4 times = 16"
|
||||||
|
(rv (apl-power (fn (a) (apl-mul a (apl-scalar 2))) 4 (apl-scalar 1)))
|
||||||
|
(list 16))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"power on vector +5"
|
||||||
|
(rv
|
||||||
|
(apl-power
|
||||||
|
(fn (a) (apl-add a (apl-scalar 1)))
|
||||||
|
5
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 6 7 8))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"power on vector preserves shape"
|
||||||
|
(sh
|
||||||
|
(apl-power
|
||||||
|
(fn (a) (apl-add a (apl-scalar 1)))
|
||||||
|
5
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"power on matrix"
|
||||||
|
(rv
|
||||||
|
(apl-power
|
||||||
|
(fn (a) (apl-mul a (apl-scalar 3)))
|
||||||
|
2
|
||||||
|
(make-array (list 2 2) (list 1 2 3 4))))
|
||||||
|
(list 9 18 27 36))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"power-fixed identity stops immediately"
|
||||||
|
(rv (apl-power-fixed (fn (a) a) (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"power-fixed floor half scalar to 0"
|
||||||
|
(rv
|
||||||
|
(apl-power-fixed
|
||||||
|
(fn (a) (apl-floor (apl-div a (apl-scalar 2))))
|
||||||
|
(apl-scalar 100)))
|
||||||
|
(list 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"power-fixed shape preserved"
|
||||||
|
(sh
|
||||||
|
(apl-power-fixed (fn (a) a) (make-array (list 2 2) (list 1 2 3 4))))
|
||||||
|
(list 2 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rank tally⍤1 row tallies"
|
||||||
|
(rv (apl-rank apl-tally 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 3 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rank tally⍤1 row tallies shape"
|
||||||
|
(sh (apl-rank apl-tally 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rank neg⍤0 vector scalar cells"
|
||||||
|
(rv (apl-rank apl-neg-m 0 (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list -1 -2 -3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rank neg⍤0 vector preserves shape"
|
||||||
|
(sh (apl-rank apl-neg-m 0 (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rank neg⍤1 matrix per-row"
|
||||||
|
(rv (apl-rank apl-neg-m 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list -1 -2 -3 -4 -5 -6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rank neg⍤1 matrix preserves shape"
|
||||||
|
(sh (apl-rank apl-neg-m 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rank k>=rank fallthrough"
|
||||||
|
(rv (apl-rank apl-tally 5 (make-array (list 4) (list 1 2 3 4))))
|
||||||
|
(list 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rank tally⍤2 whole matrix tally"
|
||||||
|
(rv
|
||||||
|
(apl-rank
|
||||||
|
apl-tally
|
||||||
|
2
|
||||||
|
(make-array (list 3 5) (list 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15))))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rank reverse⍤1 matrix reverse rows"
|
||||||
|
(rv (apl-rank apl-reverse 1 (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 3 2 1 6 5 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rank tally⍤1 3x4 row tallies"
|
||||||
|
(rv
|
||||||
|
(apl-rank
|
||||||
|
apl-tally
|
||||||
|
1
|
||||||
|
(make-array (list 3 4) (list 1 2 3 4 5 6 7 8 9 10 11 12))))
|
||||||
|
(list 4 4 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"at-replace single index"
|
||||||
|
(rv
|
||||||
|
(apl-at-replace
|
||||||
|
(apl-scalar 99)
|
||||||
|
(make-array (list 1) (list 2))
|
||||||
|
(make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 99 3 4 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"at-replace multiple indices vector vals"
|
||||||
|
(rv
|
||||||
|
(apl-at-replace
|
||||||
|
(make-array (list 2) (list 99 88))
|
||||||
|
(make-array (list 2) (list 2 4))
|
||||||
|
(make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 99 3 88 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"at-replace scalar broadcast"
|
||||||
|
(rv
|
||||||
|
(apl-at-replace
|
||||||
|
(apl-scalar 0)
|
||||||
|
(make-array (list 3) (list 1 3 5))
|
||||||
|
(make-array (list 5) (list 10 20 30 40 50))))
|
||||||
|
(list 0 20 0 40 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"at-replace preserves shape"
|
||||||
|
(sh
|
||||||
|
(apl-at-replace
|
||||||
|
(apl-scalar 99)
|
||||||
|
(make-array (list 1) (list 2))
|
||||||
|
(make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"at-replace last index"
|
||||||
|
(rv
|
||||||
|
(apl-at-replace
|
||||||
|
(apl-scalar 99)
|
||||||
|
(make-array (list 1) (list 5))
|
||||||
|
(make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 2 3 4 99))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"at-replace on matrix linear-index"
|
||||||
|
(rv
|
||||||
|
(apl-at-replace
|
||||||
|
(apl-scalar 99)
|
||||||
|
(make-array (list 1) (list 3))
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 2 99 4 5 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"at-apply negate at indices"
|
||||||
|
(rv
|
||||||
|
(apl-at-apply
|
||||||
|
apl-neg-m
|
||||||
|
(make-array (list 3) (list 1 3 5))
|
||||||
|
(make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list -1 2 -3 4 -5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"at-apply double at index 1"
|
||||||
|
(rv
|
||||||
|
(apl-at-apply
|
||||||
|
(fn (a) (apl-mul a (apl-scalar 2)))
|
||||||
|
(make-array (list 1) (list 1))
|
||||||
|
(make-array (list 2) (list 5 10))))
|
||||||
|
(list 10 10))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"at-apply preserves shape"
|
||||||
|
(sh
|
||||||
|
(apl-at-apply
|
||||||
|
apl-neg-m
|
||||||
|
(make-array (list 2) (list 1 3))
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"at-apply on matrix linear-index"
|
||||||
|
(rv
|
||||||
|
(apl-at-apply
|
||||||
|
apl-neg-m
|
||||||
|
(make-array (list 2) (list 1 6))
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list -1 2 3 4 5 -6))
|
||||||
340
lib/apl/tests/parse.sx
Normal file
340
lib/apl/tests/parse.sx
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
(define apl-test-count 0)
|
||||||
|
(define apl-test-pass 0)
|
||||||
|
(define apl-test-fails (list))
|
||||||
|
|
||||||
|
(define apl-test
|
||||||
|
(fn (name actual expected)
|
||||||
|
(begin
|
||||||
|
(set! apl-test-count (+ apl-test-count 1))
|
||||||
|
(if (= actual expected)
|
||||||
|
(set! apl-test-pass (+ apl-test-pass 1))
|
||||||
|
(append! apl-test-fails {:name name :actual actual :expected expected})))))
|
||||||
|
|
||||||
|
(define tok-types
|
||||||
|
(fn (src)
|
||||||
|
(map (fn (t) (get t :type)) (apl-tokenize src))))
|
||||||
|
|
||||||
|
(define tok-values
|
||||||
|
(fn (src)
|
||||||
|
(map (fn (t) (get t :value)) (apl-tokenize src))))
|
||||||
|
|
||||||
|
(define tok-count
|
||||||
|
(fn (src)
|
||||||
|
(len (apl-tokenize src))))
|
||||||
|
|
||||||
|
(define tok-type-at
|
||||||
|
(fn (src i)
|
||||||
|
(get (nth (apl-tokenize src) i) :type)))
|
||||||
|
|
||||||
|
(define tok-value-at
|
||||||
|
(fn (src i)
|
||||||
|
(get (nth (apl-tokenize src) i) :value)))
|
||||||
|
|
||||||
|
(apl-test "empty: no tokens" (tok-count "") 0)
|
||||||
|
(apl-test "empty: whitespace only" (tok-count " ") 0)
|
||||||
|
(apl-test "num: zero" (tok-values "0") (list 0))
|
||||||
|
(apl-test "num: positive" (tok-values "42") (list 42))
|
||||||
|
(apl-test "num: large" (tok-values "12345") (list 12345))
|
||||||
|
(apl-test "num: negative" (tok-values "¯5") (list -5))
|
||||||
|
(apl-test "num: negative zero" (tok-values "¯0") (list 0))
|
||||||
|
(apl-test "num: strand count" (tok-count "1 2 3") 3)
|
||||||
|
(apl-test "num: strand types" (tok-types "1 2 3") (list :num :num :num))
|
||||||
|
(apl-test "num: strand values" (tok-values "1 2 3") (list 1 2 3))
|
||||||
|
(apl-test "num: neg in strand" (tok-values "1 ¯2 3") (list 1 -2 3))
|
||||||
|
(apl-test "str: empty" (tok-values "''") (list ""))
|
||||||
|
(apl-test "str: single char" (tok-values "'a'") (list "a"))
|
||||||
|
(apl-test "str: word" (tok-values "'hello'") (list "hello"))
|
||||||
|
(apl-test "str: escaped quote" (tok-values "''''") (list "'"))
|
||||||
|
(apl-test "str: type" (tok-types "'abc'") (list :str))
|
||||||
|
(apl-test "name: simple" (tok-values "foo") (list "foo"))
|
||||||
|
(apl-test "name: type" (tok-types "foo") (list :name))
|
||||||
|
(apl-test "name: mixed case" (tok-values "MyVar") (list "MyVar"))
|
||||||
|
(apl-test "name: with digits" (tok-values "x1") (list "x1"))
|
||||||
|
(apl-test "name: system var" (tok-values "⎕IO") (list "⎕IO"))
|
||||||
|
(apl-test "name: system var type" (tok-types "⎕IO") (list :name))
|
||||||
|
(apl-test "glyph: plus" (tok-types "+") (list :glyph))
|
||||||
|
(apl-test "glyph: plus value" (tok-values "+") (list "+"))
|
||||||
|
(apl-test "glyph: iota" (tok-values "⍳") (list "⍳"))
|
||||||
|
(apl-test "glyph: reduce" (tok-values "+/") (list "+" "/"))
|
||||||
|
(apl-test "glyph: floor" (tok-values "⌊") (list "⌊"))
|
||||||
|
(apl-test "glyph: rho" (tok-values "⍴") (list "⍴"))
|
||||||
|
(apl-test "glyph: alpha omega" (tok-types "⍺ ⍵") (list :glyph :glyph))
|
||||||
|
(apl-test "punct: lparen" (tok-types "(") (list :lparen))
|
||||||
|
(apl-test "punct: rparen" (tok-types ")") (list :rparen))
|
||||||
|
(apl-test "punct: brackets" (tok-types "[42]") (list :lbracket :num :rbracket))
|
||||||
|
(apl-test "punct: braces" (tok-types "{}") (list :lbrace :rbrace))
|
||||||
|
(apl-test "punct: semi" (tok-types ";") (list :semi))
|
||||||
|
(apl-test "assign: arrow" (tok-types "x←1") (list :name :assign :num))
|
||||||
|
(apl-test "diamond: separator" (tok-types "1⋄2") (list :num :diamond :num))
|
||||||
|
(apl-test "newline: emitted" (tok-types "1\n2") (list :num :newline :num))
|
||||||
|
(apl-test "comment: skipped" (tok-count "⍝ ignore me") 0)
|
||||||
|
(apl-test "comment: rest ignored" (tok-count "1 ⍝ note") 1)
|
||||||
|
(apl-test "colon: bare" (tok-types ":") (list :colon))
|
||||||
|
(apl-test "keyword: If" (tok-values ":If") (list ":If"))
|
||||||
|
(apl-test "keyword: type" (tok-types ":While") (list :keyword))
|
||||||
|
(apl-test "keyword: EndFor" (tok-values ":EndFor") (list ":EndFor"))
|
||||||
|
(apl-test "expr: +/ ⍳ 5" (tok-types "+/ ⍳ 5") (list :glyph :glyph :glyph :num))
|
||||||
|
(apl-test "expr: x←42" (tok-count "x←42") 3)
|
||||||
|
(apl-test "expr: dfn body" (tok-types "{⍺+⍵}")
|
||||||
|
(list :lbrace :glyph :glyph :glyph :rbrace))
|
||||||
|
|
||||||
|
(define apl-tokenize-test-summary
|
||||||
|
(str "tokenizer " apl-test-pass "/" apl-test-count
|
||||||
|
(if (= (len apl-test-fails) 0) "" (str " FAILS: " apl-test-fails))))
|
||||||
|
|
||||||
|
; ===========================================================================
|
||||||
|
; Parser tests
|
||||||
|
; ===========================================================================
|
||||||
|
|
||||||
|
; Helper: parse an APL source string and return the AST
|
||||||
|
(define parse
|
||||||
|
(fn (src) (parse-apl src)))
|
||||||
|
|
||||||
|
; Helper: build an expected AST node using keyword-tagged lists
|
||||||
|
(define num-node (fn (n) (list :num n)))
|
||||||
|
(define str-node (fn (s) (list :str s)))
|
||||||
|
(define name-node (fn (n) (list :name n)))
|
||||||
|
(define fn-node (fn (g) (list :fn-glyph g)))
|
||||||
|
(define fn-nm (fn (n) (list :fn-name n)))
|
||||||
|
(define assign-node (fn (nm expr) (list :assign nm expr)))
|
||||||
|
(define monad-node (fn (f a) (list :monad f a)))
|
||||||
|
(define dyad-node (fn (f l r) (list :dyad f l r)))
|
||||||
|
(define derived-fn (fn (op f) (list :derived-fn op f)))
|
||||||
|
(define derived-fn2 (fn (op f g) (list :derived-fn2 op f g)))
|
||||||
|
(define outer-node (fn (f) (list :outer "∘." f)))
|
||||||
|
(define guard-node (fn (c e) (list :guard c e)))
|
||||||
|
|
||||||
|
; ---- numeric literals ----
|
||||||
|
|
||||||
|
(apl-test "parse: num literal"
|
||||||
|
(parse "42")
|
||||||
|
(num-node 42))
|
||||||
|
|
||||||
|
(apl-test "parse: negative num"
|
||||||
|
(parse "¯3")
|
||||||
|
(num-node -3))
|
||||||
|
|
||||||
|
(apl-test "parse: zero"
|
||||||
|
(parse "0")
|
||||||
|
(num-node 0))
|
||||||
|
|
||||||
|
; ---- string literals ----
|
||||||
|
|
||||||
|
(apl-test "parse: str literal"
|
||||||
|
(parse "'hello'")
|
||||||
|
(str-node "hello"))
|
||||||
|
|
||||||
|
(apl-test "parse: empty str"
|
||||||
|
(parse "''")
|
||||||
|
(str-node ""))
|
||||||
|
|
||||||
|
; ---- name reference ----
|
||||||
|
|
||||||
|
(apl-test "parse: name"
|
||||||
|
(parse "x")
|
||||||
|
(name-node "x"))
|
||||||
|
|
||||||
|
(apl-test "parse: system name"
|
||||||
|
(parse "⎕IO")
|
||||||
|
(name-node "⎕IO"))
|
||||||
|
|
||||||
|
; ---- strands (vec nodes) ----
|
||||||
|
|
||||||
|
(apl-test "parse: strand 3 nums"
|
||||||
|
(parse "1 2 3")
|
||||||
|
(list :vec (num-node 1) (num-node 2) (num-node 3)))
|
||||||
|
|
||||||
|
(apl-test "parse: strand 2 nums"
|
||||||
|
(parse "1 2")
|
||||||
|
(list :vec (num-node 1) (num-node 2)))
|
||||||
|
|
||||||
|
(apl-test "parse: strand with negatives"
|
||||||
|
(parse "1 ¯2 3")
|
||||||
|
(list :vec (num-node 1) (num-node -2) (num-node 3)))
|
||||||
|
|
||||||
|
; ---- assignment ----
|
||||||
|
|
||||||
|
(apl-test "parse: assignment"
|
||||||
|
(parse "x←42")
|
||||||
|
(assign-node "x" (num-node 42)))
|
||||||
|
|
||||||
|
(apl-test "parse: assignment with spaces"
|
||||||
|
(parse "x ← 42")
|
||||||
|
(assign-node "x" (num-node 42)))
|
||||||
|
|
||||||
|
(apl-test "parse: assignment of expr"
|
||||||
|
(parse "r←2+3")
|
||||||
|
(assign-node "r" (dyad-node (fn-node "+") (num-node 2) (num-node 3))))
|
||||||
|
|
||||||
|
; ---- monadic functions ----
|
||||||
|
|
||||||
|
(apl-test "parse: monadic iota"
|
||||||
|
(parse "⍳5")
|
||||||
|
(monad-node (fn-node "⍳") (num-node 5)))
|
||||||
|
|
||||||
|
(apl-test "parse: monadic iota with space"
|
||||||
|
(parse "⍳ 5")
|
||||||
|
(monad-node (fn-node "⍳") (num-node 5)))
|
||||||
|
|
||||||
|
(apl-test "parse: monadic negate"
|
||||||
|
(parse "-3")
|
||||||
|
(monad-node (fn-node "-") (num-node 3)))
|
||||||
|
|
||||||
|
(apl-test "parse: monadic floor"
|
||||||
|
(parse "⌊2")
|
||||||
|
(monad-node (fn-node "⌊") (num-node 2)))
|
||||||
|
|
||||||
|
(apl-test "parse: monadic of name"
|
||||||
|
(parse "⍴x")
|
||||||
|
(monad-node (fn-node "⍴") (name-node "x")))
|
||||||
|
|
||||||
|
; ---- dyadic functions ----
|
||||||
|
|
||||||
|
(apl-test "parse: dyadic plus"
|
||||||
|
(parse "2+3")
|
||||||
|
(dyad-node (fn-node "+") (num-node 2) (num-node 3)))
|
||||||
|
|
||||||
|
(apl-test "parse: dyadic times"
|
||||||
|
(parse "2×3")
|
||||||
|
(dyad-node (fn-node "×") (num-node 2) (num-node 3)))
|
||||||
|
|
||||||
|
(apl-test "parse: dyadic with names"
|
||||||
|
(parse "x+y")
|
||||||
|
(dyad-node (fn-node "+") (name-node "x") (name-node "y")))
|
||||||
|
|
||||||
|
; ---- right-to-left evaluation ----
|
||||||
|
|
||||||
|
(apl-test "parse: right-to-left 2×3+4"
|
||||||
|
(parse "2×3+4")
|
||||||
|
(dyad-node (fn-node "×") (num-node 2)
|
||||||
|
(dyad-node (fn-node "+") (num-node 3) (num-node 4))))
|
||||||
|
|
||||||
|
(apl-test "parse: right-to-left chain"
|
||||||
|
(parse "1+2×3-4")
|
||||||
|
(dyad-node (fn-node "+") (num-node 1)
|
||||||
|
(dyad-node (fn-node "×") (num-node 2)
|
||||||
|
(dyad-node (fn-node "-") (num-node 3) (num-node 4)))))
|
||||||
|
|
||||||
|
; ---- parenthesized subexpressions ----
|
||||||
|
|
||||||
|
(apl-test "parse: parens override order"
|
||||||
|
(parse "(2+3)×4")
|
||||||
|
(dyad-node (fn-node "×")
|
||||||
|
(dyad-node (fn-node "+") (num-node 2) (num-node 3))
|
||||||
|
(num-node 4)))
|
||||||
|
|
||||||
|
(apl-test "parse: nested parens"
|
||||||
|
(parse "((2+3))")
|
||||||
|
(dyad-node (fn-node "+") (num-node 2) (num-node 3)))
|
||||||
|
|
||||||
|
(apl-test "parse: paren in dyadic right"
|
||||||
|
(parse "2×(3+4)")
|
||||||
|
(dyad-node (fn-node "×") (num-node 2)
|
||||||
|
(dyad-node (fn-node "+") (num-node 3) (num-node 4))))
|
||||||
|
|
||||||
|
; ---- operators → derived functions ----
|
||||||
|
|
||||||
|
(apl-test "parse: reduce +"
|
||||||
|
(parse "+/x")
|
||||||
|
(monad-node (derived-fn "/" (fn-node "+")) (name-node "x")))
|
||||||
|
|
||||||
|
(apl-test "parse: reduce iota"
|
||||||
|
(parse "+/⍳5")
|
||||||
|
(monad-node (derived-fn "/" (fn-node "+"))
|
||||||
|
(monad-node (fn-node "⍳") (num-node 5))))
|
||||||
|
|
||||||
|
(apl-test "parse: scan"
|
||||||
|
(parse "+\\x")
|
||||||
|
(monad-node (derived-fn "\\" (fn-node "+")) (name-node "x")))
|
||||||
|
|
||||||
|
(apl-test "parse: each"
|
||||||
|
(parse "⍳¨x")
|
||||||
|
(monad-node (derived-fn "¨" (fn-node "⍳")) (name-node "x")))
|
||||||
|
|
||||||
|
(apl-test "parse: commute"
|
||||||
|
(parse "-⍨3")
|
||||||
|
(monad-node (derived-fn "⍨" (fn-node "-")) (num-node 3)))
|
||||||
|
|
||||||
|
(apl-test "parse: stacked ops"
|
||||||
|
(parse "+/¨x")
|
||||||
|
(monad-node (derived-fn "¨" (derived-fn "/" (fn-node "+"))) (name-node "x")))
|
||||||
|
|
||||||
|
; ---- outer product ----
|
||||||
|
|
||||||
|
(apl-test "parse: outer product monadic"
|
||||||
|
(parse "∘.×")
|
||||||
|
(outer-node (fn-node "×")))
|
||||||
|
|
||||||
|
(apl-test "parse: outer product dyadic names"
|
||||||
|
(parse "x ∘.× y")
|
||||||
|
(dyad-node (outer-node (fn-node "×")) (name-node "x") (name-node "y")))
|
||||||
|
|
||||||
|
(apl-test "parse: outer product dyadic strands"
|
||||||
|
(parse "1 2 3 ∘.× 4 5 6")
|
||||||
|
(dyad-node (outer-node (fn-node "×"))
|
||||||
|
(list :vec (num-node 1) (num-node 2) (num-node 3))
|
||||||
|
(list :vec (num-node 4) (num-node 5) (num-node 6))))
|
||||||
|
|
||||||
|
; ---- inner product ----
|
||||||
|
|
||||||
|
(apl-test "parse: inner product"
|
||||||
|
(parse "+.×")
|
||||||
|
(derived-fn2 "." (fn-node "+") (fn-node "×")))
|
||||||
|
|
||||||
|
(apl-test "parse: inner product applied"
|
||||||
|
(parse "a +.× b")
|
||||||
|
(dyad-node (derived-fn2 "." (fn-node "+") (fn-node "×"))
|
||||||
|
(name-node "a") (name-node "b")))
|
||||||
|
|
||||||
|
; ---- dfn (anonymous function) ----
|
||||||
|
|
||||||
|
(apl-test "parse: simple dfn"
|
||||||
|
(parse "{⍺+⍵}")
|
||||||
|
(list :dfn (dyad-node (fn-node "+") (name-node "⍺") (name-node "⍵"))))
|
||||||
|
|
||||||
|
(apl-test "parse: monadic dfn"
|
||||||
|
(parse "{⍵×2}")
|
||||||
|
(list :dfn (dyad-node (fn-node "×") (name-node "⍵") (num-node 2))))
|
||||||
|
|
||||||
|
(apl-test "parse: dfn self-ref"
|
||||||
|
(parse "{⍵≤1:1 ⋄ ⍵×∇ ⍵-1}")
|
||||||
|
(list :dfn
|
||||||
|
(guard-node (dyad-node (fn-node "≤") (name-node "⍵") (num-node 1)) (num-node 1))
|
||||||
|
(dyad-node (fn-node "×") (name-node "⍵")
|
||||||
|
(monad-node (fn-node "∇") (dyad-node (fn-node "-") (name-node "⍵") (num-node 1))))))
|
||||||
|
|
||||||
|
; ---- dfn applied ----
|
||||||
|
|
||||||
|
(apl-test "parse: dfn as function"
|
||||||
|
(parse "{⍺+⍵} 3")
|
||||||
|
(monad-node
|
||||||
|
(list :dfn (dyad-node (fn-node "+") (name-node "⍺") (name-node "⍵")))
|
||||||
|
(num-node 3)))
|
||||||
|
|
||||||
|
; ---- multi-statement ----
|
||||||
|
|
||||||
|
(apl-test "parse: diamond separator"
|
||||||
|
(let ((result (parse "x←1 ⋄ x+2")))
|
||||||
|
(= (first result) :program))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(apl-test "parse: diamond first stmt"
|
||||||
|
(let ((result (parse "x←1 ⋄ x+2")))
|
||||||
|
(nth result 1))
|
||||||
|
(assign-node "x" (num-node 1)))
|
||||||
|
|
||||||
|
(apl-test "parse: diamond second stmt"
|
||||||
|
(let ((result (parse "x←1 ⋄ x+2")))
|
||||||
|
(nth result 2))
|
||||||
|
(dyad-node (fn-node "+") (name-node "x") (num-node 2)))
|
||||||
|
|
||||||
|
; ---- combined summary ----
|
||||||
|
|
||||||
|
(define apl-parse-test-count (- apl-test-count 46))
|
||||||
|
(define apl-parse-test-pass (- apl-test-pass 46))
|
||||||
|
|
||||||
|
(define apl-test-summary
|
||||||
|
(str
|
||||||
|
"tokenizer 46/46 | "
|
||||||
|
"parser " apl-parse-test-pass "/" apl-parse-test-count
|
||||||
|
(if (= (len apl-test-fails) 0) "" (str " FAILS: " apl-test-fails))))
|
||||||
687
lib/apl/tests/pipeline.sx
Normal file
687
lib/apl/tests/pipeline.sx
Normal file
@@ -0,0 +1,687 @@
|
|||||||
|
; End-to-end pipeline tests: source string → tokenize → parse → eval-ast → array.
|
||||||
|
; Verifies the full stack as a single function call (apl-run).
|
||||||
|
|
||||||
|
(define mkrv (fn (arr) (get arr :ravel)))
|
||||||
|
(define mksh (fn (arr) (get arr :shape)))
|
||||||
|
|
||||||
|
; ---------- scalars ----------
|
||||||
|
|
||||||
|
(apl-test "apl-run \"42\" → scalar 42" (mkrv (apl-run "42")) (list 42))
|
||||||
|
|
||||||
|
(apl-test "apl-run \"¯7\" → scalar -7" (mkrv (apl-run "¯7")) (list -7))
|
||||||
|
|
||||||
|
; ---------- strands ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"1 2 3\" → vector"
|
||||||
|
(mkrv (apl-run "1 2 3"))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test "apl-run \"1 2 3\" shape" (mksh (apl-run "1 2 3")) (list 3))
|
||||||
|
|
||||||
|
; ---------- dyadic arithmetic ----------
|
||||||
|
|
||||||
|
(apl-test "apl-run \"2 + 3\" → 5" (mkrv (apl-run "2 + 3")) (list 5))
|
||||||
|
|
||||||
|
(apl-run "2 × 3 + 4") ; right-to-left
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"2 × 3 + 4\" → 14 (right-to-left)"
|
||||||
|
(mkrv (apl-run "2 × 3 + 4"))
|
||||||
|
(list 14))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"1 2 3 + 4 5 6\" → 5 7 9"
|
||||||
|
(mkrv (apl-run "1 2 3 + 4 5 6"))
|
||||||
|
(list 5 7 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"3 × 1 2 3 4\" → scalar broadcast"
|
||||||
|
(mkrv (apl-run "3 × 1 2 3 4"))
|
||||||
|
(list 3 6 9 12))
|
||||||
|
|
||||||
|
; ---------- monadic primitives ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"⍳5\" → 1..5"
|
||||||
|
(mkrv (apl-run "⍳5"))
|
||||||
|
(list 1 2 3 4 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"-3\" → -3 (monadic negate)"
|
||||||
|
(mkrv (apl-run "-3"))
|
||||||
|
(list -3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"⌈/ 1 3 9 5 7\" → 9 (max-reduce)"
|
||||||
|
(mkrv (apl-run "⌈/ 1 3 9 5 7"))
|
||||||
|
(list 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"⌊/ 4 7 2 9 1 3\" → 1 (min-reduce)"
|
||||||
|
(mkrv (apl-run "⌊/ 4 7 2 9 1 3"))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
; ---------- operators ----------
|
||||||
|
|
||||||
|
(apl-test "apl-run \"+/⍳5\" → 15" (mkrv (apl-run "+/⍳5")) (list 15))
|
||||||
|
|
||||||
|
(apl-test "apl-run \"×/⍳5\" → 120" (mkrv (apl-run "×/⍳5")) (list 120))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"⌈/3 1 4 1 5 9 2\" → 9"
|
||||||
|
(mkrv (apl-run "⌈/3 1 4 1 5 9 2"))
|
||||||
|
(list 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"+\\\\⍳5\" → triangular numbers"
|
||||||
|
(mkrv (apl-run "+\\⍳5"))
|
||||||
|
(list 1 3 6 10 15))
|
||||||
|
|
||||||
|
; ---------- outer / inner products ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"1 2 3 ∘.× 1 2 3\" → mult table values"
|
||||||
|
(mkrv (apl-run "1 2 3 ∘.× 1 2 3"))
|
||||||
|
(list 1 2 3 2 4 6 3 6 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"1 2 3 +.× 4 5 6\" → dot product 32"
|
||||||
|
(mkrv (apl-run "1 2 3 +.× 4 5 6"))
|
||||||
|
(list 32))
|
||||||
|
|
||||||
|
; ---------- shape ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"⍴ 1 2 3 4 5\" → 5"
|
||||||
|
(mkrv (apl-run "⍴ 1 2 3 4 5"))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test "apl-run \"⍴⍳10\" → 10" (mkrv (apl-run "⍴⍳10")) (list 10))
|
||||||
|
|
||||||
|
; ---------- comparison ----------
|
||||||
|
|
||||||
|
(apl-test "apl-run \"3 < 5\" → 1" (mkrv (apl-run "3 < 5")) (list 1))
|
||||||
|
|
||||||
|
(apl-test "apl-run \"5 = 5\" → 1" (mkrv (apl-run "5 = 5")) (list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"1 2 3 = 1 0 3\" → 1 0 1"
|
||||||
|
(mkrv (apl-run "1 2 3 = 1 0 3"))
|
||||||
|
(list 1 0 1))
|
||||||
|
|
||||||
|
; ---------- famous one-liners ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"+/(⍳10)\" → sum 1..10 = 55"
|
||||||
|
(mkrv (apl-run "+/(⍳10)"))
|
||||||
|
(list 55))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"×/⍳10\" → 10! = 3628800"
|
||||||
|
(mkrv (apl-run "×/⍳10"))
|
||||||
|
(list 3628800))
|
||||||
|
|
||||||
|
(apl-test "apl-run \"⎕IO\" → 1" (mkrv (apl-run "⎕IO")) (list 1))
|
||||||
|
|
||||||
|
(apl-test "apl-run \"⎕ML\" → 1" (mkrv (apl-run "⎕ML")) (list 1))
|
||||||
|
|
||||||
|
(apl-test "apl-run \"⎕FR\" → 1248" (mkrv (apl-run "⎕FR")) (list 1248))
|
||||||
|
|
||||||
|
(apl-test "apl-run \"⎕TS\" shape (7)" (mksh (apl-run "⎕TS")) (list 7))
|
||||||
|
|
||||||
|
(apl-test "apl-run \"⎕FMT 42\" → \"42\"" (apl-run "⎕FMT 42") "42")
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"⎕FMT 1 2 3\" → \"1 2 3\""
|
||||||
|
(apl-run "⎕FMT 1 2 3")
|
||||||
|
"1 2 3")
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"⎕FMT ⍳5\" → \"1 2 3 4 5\""
|
||||||
|
(apl-run "⎕FMT ⍳5")
|
||||||
|
"1 2 3 4 5")
|
||||||
|
|
||||||
|
(apl-test "apl-run \"⎕IO + 4\" → 5" (mkrv (apl-run "⎕IO + 4")) (list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"(10 20 30 40 50)[3]\" → 30"
|
||||||
|
(mkrv (apl-run "(10 20 30 40 50)[3]"))
|
||||||
|
(list 30))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"(⍳10)[5]\" → 5"
|
||||||
|
(mkrv (apl-run "(⍳10)[5]"))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"A ← 100 200 300 ⋄ A[2]\" → 200"
|
||||||
|
(mkrv (apl-run "A ← 100 200 300 ⋄ A[2]"))
|
||||||
|
(list 200))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"V ← ⍳10 ⋄ V[3]\" → 3"
|
||||||
|
(mkrv (apl-run "V ← ⍳10 ⋄ V[3]"))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"(10 20 30)[1]\" → 10 (1-indexed)"
|
||||||
|
(mkrv (apl-run "(10 20 30)[1]"))
|
||||||
|
(list 10))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"V ← 10 20 30 40 50 ⋄ V[3] + 1\" → 31"
|
||||||
|
(mkrv (apl-run "V ← 10 20 30 40 50 ⋄ V[3] + 1"))
|
||||||
|
(list 31))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run \"(⍳5)[3] × 7\" → 21"
|
||||||
|
(mkrv (apl-run "(⍳5)[3] × 7"))
|
||||||
|
(list 21))
|
||||||
|
|
||||||
|
(apl-test "decimal: 3.7 → 3.7" (mkrv (apl-run "3.7")) (list 3.7))
|
||||||
|
|
||||||
|
(apl-test "decimal: ¯2.5 → -2.5" (mkrv (apl-run "¯2.5")) (list -2.5))
|
||||||
|
|
||||||
|
(apl-test "decimal: 1.5 + 2.5 → 4" (mkrv (apl-run "1.5 + 2.5")) (list 4))
|
||||||
|
|
||||||
|
(apl-test "decimal: ⌊3.7 → 3" (mkrv (apl-run "⌊ 3.7")) (list 3))
|
||||||
|
|
||||||
|
(apl-test "decimal: ⌈3.7 → 4" (mkrv (apl-run "⌈ 3.7")) (list 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⎕← scalar passthrough"
|
||||||
|
(mkrv (apl-run "⎕← 42"))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⎕← vector passthrough"
|
||||||
|
(mkrv (apl-run "⎕← 1 2 3"))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"string: 'abc' → 3-char vector"
|
||||||
|
(mkrv (apl-run "'abc'"))
|
||||||
|
(list "a" "b" "c"))
|
||||||
|
|
||||||
|
(apl-test "string: 'a' is rank-0 scalar" (mksh (apl-run "'a'")) (list))
|
||||||
|
|
||||||
|
(apl-test "string: 'hello' shape (5)" (mksh (apl-run "'hello'")) (list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"named-fn: f ← {⍺+⍵} ⋄ 3 f 4 → 7"
|
||||||
|
(mkrv (apl-run "f ← {⍺+⍵} ⋄ 3 f 4"))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"named-fn monadic: sq ← {⍵×⍵} ⋄ sq 7 → 49"
|
||||||
|
(mkrv (apl-run "sq ← {⍵×⍵} ⋄ sq 7"))
|
||||||
|
(list 49))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"named-fn dyadic: hyp ← {((⍺×⍺)+⍵×⍵)} ⋄ 3 hyp 4 → 25"
|
||||||
|
(mkrv (apl-run "hyp ← {((⍺×⍺)+⍵×⍵)} ⋄ 3 hyp 4"))
|
||||||
|
(list 25))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"named-fn: dbl ← {⍵+⍵} ⋄ dbl ⍳5"
|
||||||
|
(mkrv (apl-run "dbl ← {⍵+⍵} ⋄ dbl ⍳5"))
|
||||||
|
(list 2 4 6 8 10))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"named-fn factorial via ∇ recursion"
|
||||||
|
(mkrv (apl-run "fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 5"))
|
||||||
|
(list 120))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"named-fn used twice in expr: dbl ← {⍵+⍵} ⋄ (dbl 3) + dbl 4"
|
||||||
|
(mkrv (apl-run "dbl ← {⍵+⍵} ⋄ (dbl 3) + dbl 4"))
|
||||||
|
(list 14))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"named-fn with vector arg: neg ← {-⍵} ⋄ neg 1 2 3"
|
||||||
|
(mkrv (apl-run "neg ← {-⍵} ⋄ neg 1 2 3"))
|
||||||
|
(list -1 -2 -3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"multi-axis: M[2;2] → center"
|
||||||
|
(mkrv (apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[2;2]"))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"multi-axis: M[1;] → first row"
|
||||||
|
(mkrv (apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[1;]"))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"multi-axis: M[;2] → second column"
|
||||||
|
(mkrv (apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[;2]"))
|
||||||
|
(list 2 5 8))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"multi-axis: M[1 2;1 2] → 2x2 block"
|
||||||
|
(mkrv (apl-run "M ← (2 3) ⍴ ⍳6 ⋄ M[1 2;1 2]"))
|
||||||
|
(list 1 2 4 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"multi-axis: M[1 2;1 2] shape (2 2)"
|
||||||
|
(mksh (apl-run "M ← (2 3) ⍴ ⍳6 ⋄ M[1 2;1 2]"))
|
||||||
|
(list 2 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"multi-axis: M[;] full matrix"
|
||||||
|
(mkrv (apl-run "M ← (2 2) ⍴ 10 20 30 40 ⋄ M[;]"))
|
||||||
|
(list 10 20 30 40))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"multi-axis: M[1;] shape collapsed"
|
||||||
|
(mksh (apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[1;]"))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"multi-axis: select all rows of column 3"
|
||||||
|
(mkrv (apl-run "M ← (4 3) ⍴ 1 2 3 4 5 6 7 8 9 10 11 12 ⋄ M[;3]"))
|
||||||
|
(list 3 6 9 12))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"train: mean = (+/÷≢) on 1..5"
|
||||||
|
(mkrv (apl-run "(+/÷≢) 1 2 3 4 5"))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"train: mean of 2 4 6 8 10"
|
||||||
|
(mkrv (apl-run "(+/÷≢) 2 4 6 8 10"))
|
||||||
|
(list 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"train 2-atop: (- ⌊) 5 → -5"
|
||||||
|
(mkrv (apl-run "(- ⌊) 5"))
|
||||||
|
(list -5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"train 3-fork dyadic: 2(+×-)5 → -21"
|
||||||
|
(mkrv (apl-run "2 (+ × -) 5"))
|
||||||
|
(list -21))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"train: range = (⌈/-⌊/) on vector"
|
||||||
|
(mkrv (apl-run "(⌈/-⌊/) 3 1 4 1 5 9 2 6"))
|
||||||
|
(list 8))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"train: mean of ⍳10 has shape ()"
|
||||||
|
(mksh (apl-run "(+/÷≢) ⍳10"))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compress: 1 0 1 0 1 / 10 20 30 40 50"
|
||||||
|
(mkrv (apl-run "1 0 1 0 1 / 10 20 30 40 50"))
|
||||||
|
(list 10 30 50))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compress: empty mask → empty"
|
||||||
|
(mkrv (apl-run "0 0 0 / 1 2 3"))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"primes via classic idiom (multi-stmt)"
|
||||||
|
(mkrv (apl-run "P ← ⍳ 30 ⋄ (2 = +⌿ 0 = P ∘.| P) / P"))
|
||||||
|
(list 2 3 5 7 11 13 17 19 23 29))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"primes via classic idiom (n=20)"
|
||||||
|
(mkrv (apl-run "P ← ⍳ 20 ⋄ (2 = +⌿ 0 = P ∘.| P) / P"))
|
||||||
|
(list 2 3 5 7 11 13 17 19))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compress: filter even values"
|
||||||
|
(mkrv (apl-run "(0 = 2 | 1 2 3 4 5 6) / 1 2 3 4 5 6"))
|
||||||
|
(list 2 4 6))
|
||||||
|
|
||||||
|
(apl-test "inline-assign: x ← 5" (mkrv (apl-run "x ← 5")) (list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inline-assign: (2×x) + x←10 → 30"
|
||||||
|
(mkrv (apl-run "(2 × x) + x ← 10"))
|
||||||
|
(list 30))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inline-assign primes one-liner: (2=+⌿0=a∘.|a)/a←⍳30"
|
||||||
|
(mkrv (apl-run "(2 = +⌿ 0 = a ∘.| a) / a ← ⍳ 30"))
|
||||||
|
(list 2 3 5 7 11 13 17 19 23 29))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inline-assign: x is reusable — x + x ← 7 → 14"
|
||||||
|
(mkrv (apl-run "x + x ← 7"))
|
||||||
|
(list 14))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"inline-assign in dfn: f ← {x + x ← ⍵} ⋄ f 8 → 16"
|
||||||
|
(mkrv (apl-run "f ← {x + x ← ⍵} ⋄ f 8"))
|
||||||
|
(list 16))
|
||||||
|
|
||||||
|
(begin (apl-rng-seed! 42) nil)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"?10 with seed 42 → 8 (deterministic)"
|
||||||
|
(mkrv (apl-run "?10"))
|
||||||
|
(list 8))
|
||||||
|
|
||||||
|
(apl-test "?10 next call → 5" (mkrv (apl-run "?10")) (list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"?100 stays in range"
|
||||||
|
(let ((v (first (mkrv (apl-run "?100"))))) (and (>= v 1) (<= v 100)))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(begin (apl-rng-seed! 42) nil)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"?10 with re-seed 42 → 8 (reproducible)"
|
||||||
|
(mkrv (apl-run "?10"))
|
||||||
|
(list 8))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run-file: load primes.apl returns dfn AST"
|
||||||
|
(first (apl-run-file "lib/apl/tests/programs/primes.apl"))
|
||||||
|
:dfn)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run-file: life.apl parses without error"
|
||||||
|
(first (apl-run-file "lib/apl/tests/programs/life.apl"))
|
||||||
|
:dfn)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run-file: quicksort.apl parses without error"
|
||||||
|
(first (apl-run-file "lib/apl/tests/programs/quicksort.apl"))
|
||||||
|
:dfn)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-run-file: source-then-call returns primes count"
|
||||||
|
(mksh
|
||||||
|
(apl-run
|
||||||
|
(str (file-read "lib/apl/tests/programs/primes.apl") " ⋄ primes 30")))
|
||||||
|
(list 10))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"primes one-liner with ⍵-rebind: primes 30"
|
||||||
|
(mkrv
|
||||||
|
(apl-run "primes ← {(2=+⌿0=⍵∘.|⍵)/⍵←⍳⍵} ⋄ primes 30"))
|
||||||
|
(list 2 3 5 7 11 13 17 19 23 29))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"primes one-liner: primes 50"
|
||||||
|
(mkrv
|
||||||
|
(apl-run "primes ← {(2=+⌿0=⍵∘.|⍵)/⍵←⍳⍵} ⋄ primes 50"))
|
||||||
|
(list 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"primes.apl loaded + called via apl-run-file"
|
||||||
|
(mkrv
|
||||||
|
(apl-run
|
||||||
|
(str (file-read "lib/apl/tests/programs/primes.apl") " ⋄ primes 20")))
|
||||||
|
(list 2 3 5 7 11 13 17 19))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"primes.apl loaded — count of primes ≤ 100"
|
||||||
|
(first
|
||||||
|
(mksh
|
||||||
|
(apl-run
|
||||||
|
(str
|
||||||
|
(file-read "lib/apl/tests/programs/primes.apl")
|
||||||
|
" ⋄ primes 100"))))
|
||||||
|
25)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⍉ monadic transpose 2x3 → 3x2"
|
||||||
|
(mkrv (apl-run "⍉ (2 3) ⍴ ⍳6"))
|
||||||
|
(list 1 4 2 5 3 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⍉ transpose shape (3 2)"
|
||||||
|
(mksh (apl-run "⍉ (2 3) ⍴ ⍳6"))
|
||||||
|
(list 3 2))
|
||||||
|
|
||||||
|
(apl-test "⊢ monadic identity" (mkrv (apl-run "⊢ 1 2 3")) (list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"5 ⊣ 1 2 3 → 5 (left)"
|
||||||
|
(mkrv (apl-run "5 ⊣ 1 2 3"))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"5 ⊢ 1 2 3 → 1 2 3 (right)"
|
||||||
|
(mkrv (apl-run "5 ⊢ 1 2 3"))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test "⍕ 42 → \"42\" (alias for ⎕FMT)" (apl-run "⍕ 42") "42")
|
||||||
|
|
||||||
|
(begin
|
||||||
|
(apl-test
|
||||||
|
"⍸ where: indices of truthy cells"
|
||||||
|
(mkrv (apl-run "⍸ 0 1 0 1 1"))
|
||||||
|
(list 2 4 5))
|
||||||
|
(apl-test
|
||||||
|
"⍸ where: leading truthy"
|
||||||
|
(mkrv (apl-run "⍸ 1 0 0 1 1"))
|
||||||
|
(list 1 4 5))
|
||||||
|
(apl-test
|
||||||
|
"⍸ where: all-zero → empty"
|
||||||
|
(mkrv (apl-run "⍸ 0 0 0"))
|
||||||
|
(list))
|
||||||
|
(apl-test
|
||||||
|
"⍸ where: all-truthy"
|
||||||
|
(mkrv (apl-run "⍸ 1 1 1"))
|
||||||
|
(list 1 2 3))
|
||||||
|
(apl-test
|
||||||
|
"⍸ where: ⎕IO=1 (1-based)"
|
||||||
|
(mkrv (apl-run "⍸ (⍳5)=3"))
|
||||||
|
(list 3))
|
||||||
|
(apl-test
|
||||||
|
"⍸ interval-index: 2 4 6 ⍸ 5 → 2"
|
||||||
|
(mkrv (apl-run "2 4 6 ⍸ 5"))
|
||||||
|
(list 2))
|
||||||
|
(apl-test
|
||||||
|
"⍸ interval-index: 2 4 6 ⍸ 1 3 5 6 7 → 0 1 2 3 3"
|
||||||
|
(mkrv (apl-run "2 4 6 ⍸ 1 3 5 6 7"))
|
||||||
|
(list 0 1 2 3 3))
|
||||||
|
(apl-test
|
||||||
|
"⍸ interval-index: ⍳5 ⍸ 3 → 3"
|
||||||
|
(mkrv (apl-run "(⍳5) ⍸ 3"))
|
||||||
|
(list 3))
|
||||||
|
(apl-test
|
||||||
|
"⍸ interval-index: y below all → 0"
|
||||||
|
(mkrv (apl-run "10 20 30 ⍸ 5"))
|
||||||
|
(list 0))
|
||||||
|
(apl-test
|
||||||
|
"⍸ interval-index: y above all → len breaks"
|
||||||
|
(mkrv (apl-run "10 20 30 ⍸ 100"))
|
||||||
|
(list 3)))
|
||||||
|
|
||||||
|
(begin
|
||||||
|
(apl-test
|
||||||
|
"∪ unique: dedup keeps first-occurrence order"
|
||||||
|
(mkrv (apl-run "∪ 1 2 1 3 2 1 4"))
|
||||||
|
(list 1 2 3 4))
|
||||||
|
(apl-test
|
||||||
|
"∪ unique: already-unique unchanged"
|
||||||
|
(mkrv (apl-run "∪ 5 4 3 2 1"))
|
||||||
|
(list 5 4 3 2 1))
|
||||||
|
(apl-test "∪ unique: scalar" (mkrv (apl-run "∪ 7")) (list 7))
|
||||||
|
(apl-test
|
||||||
|
"∪ unique: string mississippi → misp"
|
||||||
|
(mkrv (apl-run "∪ 'mississippi'"))
|
||||||
|
(list "m" "i" "s" "p"))
|
||||||
|
(apl-test
|
||||||
|
"∪ union: 1 2 3 ∪ 3 4 5 → 1 2 3 4 5"
|
||||||
|
(mkrv (apl-run "1 2 3 ∪ 3 4 5"))
|
||||||
|
(list 1 2 3 4 5))
|
||||||
|
(apl-test
|
||||||
|
"∪ union: dedups left side too"
|
||||||
|
(mkrv (apl-run "1 2 1 ∪ 1 3 2"))
|
||||||
|
(list 1 2 3))
|
||||||
|
(apl-test
|
||||||
|
"∪ union: disjoint → catenated"
|
||||||
|
(mkrv (apl-run "1 2 ∪ 3 4"))
|
||||||
|
(list 1 2 3 4))
|
||||||
|
(apl-test
|
||||||
|
"∩ intersection: 1 2 3 4 ∩ 2 4 6 → 2 4"
|
||||||
|
(mkrv (apl-run "1 2 3 4 ∩ 2 4 6"))
|
||||||
|
(list 2 4))
|
||||||
|
(apl-test
|
||||||
|
"∩ intersection: disjoint → empty"
|
||||||
|
(mkrv (apl-run "1 2 3 ∩ 4 5 6"))
|
||||||
|
(list))
|
||||||
|
(apl-test
|
||||||
|
"∩ intersection: preserves left order"
|
||||||
|
(mkrv (apl-run "(⍳5) ∩ 5 3 1"))
|
||||||
|
(list 1 3 5))
|
||||||
|
(apl-test
|
||||||
|
"∩ intersection: identical"
|
||||||
|
(mkrv (apl-run "1 2 3 ∩ 1 2 3"))
|
||||||
|
(list 1 2 3))
|
||||||
|
(apl-test
|
||||||
|
"∪/∩ identity: A ∪ A = ∪A"
|
||||||
|
(mkrv (apl-run "1 2 1 ∪ 1 2 1"))
|
||||||
|
(list 1 2)))
|
||||||
|
|
||||||
|
(begin
|
||||||
|
(apl-test
|
||||||
|
"⊥ decode: 2 2 2 ⊥ 1 0 1 → 5"
|
||||||
|
(mkrv (apl-run "2 2 2 ⊥ 1 0 1"))
|
||||||
|
(list 5))
|
||||||
|
(apl-test
|
||||||
|
"⊥ decode: 10 10 10 ⊥ 1 2 3 → 123"
|
||||||
|
(mkrv (apl-run "10 10 10 ⊥ 1 2 3"))
|
||||||
|
(list 123))
|
||||||
|
(apl-test
|
||||||
|
"⊥ decode: 24 60 60 ⊥ 2 3 4 → 7384 (mixed-radix HMS)"
|
||||||
|
(mkrv (apl-run "24 60 60 ⊥ 2 3 4"))
|
||||||
|
(list 7384))
|
||||||
|
(apl-test
|
||||||
|
"⊥ decode: scalar base 2 ⊥ 1 0 1 0 → 10"
|
||||||
|
(mkrv (apl-run "2 ⊥ 1 0 1 0"))
|
||||||
|
(list 10))
|
||||||
|
(apl-test
|
||||||
|
"⊥ decode: 16 16 ⊥ 15 15 → 255"
|
||||||
|
(mkrv (apl-run "16 16 ⊥ 15 15"))
|
||||||
|
(list 255))
|
||||||
|
(apl-test
|
||||||
|
"⊤ encode: 2 2 2 ⊤ 5 → 1 0 1"
|
||||||
|
(mkrv (apl-run "2 2 2 ⊤ 5"))
|
||||||
|
(list 1 0 1))
|
||||||
|
(apl-test
|
||||||
|
"⊤ encode: 24 60 60 ⊤ 7384 → 2 3 4 (HMS)"
|
||||||
|
(mkrv (apl-run "24 60 60 ⊤ 7384"))
|
||||||
|
(list 2 3 4))
|
||||||
|
(apl-test
|
||||||
|
"⊤ encode: 2 2 2 2 ⊤ 13 → 1 1 0 1"
|
||||||
|
(mkrv (apl-run "2 2 2 2 ⊤ 13"))
|
||||||
|
(list 1 1 0 1))
|
||||||
|
(apl-test
|
||||||
|
"⊤ encode: 10 10 ⊤ 42 → 4 2"
|
||||||
|
(mkrv (apl-run "10 10 ⊤ 42"))
|
||||||
|
(list 4 2))
|
||||||
|
(apl-test
|
||||||
|
"⊤ encode: round-trip B⊥(B⊤N) = N"
|
||||||
|
(mkrv (apl-run "24 60 60 ⊥ 24 60 60 ⊤ 7384"))
|
||||||
|
(list 7384))
|
||||||
|
(apl-test
|
||||||
|
"⊥ decode: round-trip B⊤(B⊥V) = V"
|
||||||
|
(mkrv (apl-run "2 2 2 ⊤ 2 2 2 ⊥ 1 0 1"))
|
||||||
|
(list 1 0 1)))
|
||||||
|
|
||||||
|
(begin
|
||||||
|
(define
|
||||||
|
mk-parts
|
||||||
|
(fn (s) (map (fn (p) (get p :ravel)) (get (apl-run s) :ravel))))
|
||||||
|
(apl-test
|
||||||
|
"⊆ partition: 1 1 0 1 1 ⊆ 'abcde' → ('ab' 'de')"
|
||||||
|
(mk-parts "1 1 0 1 1 ⊆ 'abcde'")
|
||||||
|
(list (list "a" "b") (list "d" "e")))
|
||||||
|
(apl-test
|
||||||
|
"⊆ partition: 1 0 0 1 1 ⊆ ⍳5 → ((1) (4 5))"
|
||||||
|
(mk-parts "1 0 0 1 1 ⊆ ⍳5")
|
||||||
|
(list (list 1) (list 4 5)))
|
||||||
|
(apl-test
|
||||||
|
"⊆ partition: all-zero mask → empty"
|
||||||
|
(len (get (apl-run "0 0 0 ⊆ 1 2 3") :ravel))
|
||||||
|
0)
|
||||||
|
(apl-test
|
||||||
|
"⊆ partition: all-one mask → single partition"
|
||||||
|
(mk-parts "1 1 1 ⊆ 7 8 9")
|
||||||
|
(list (list 7 8 9)))
|
||||||
|
(apl-test
|
||||||
|
"⊆ partition: strict increase 1 2 starts new"
|
||||||
|
(mk-parts "1 2 ⊆ 10 20")
|
||||||
|
(list (list 10) (list 20)))
|
||||||
|
(apl-test
|
||||||
|
"⊆ partition: same level continues 2 2 → one partition"
|
||||||
|
(mk-parts "2 2 ⊆ 10 20")
|
||||||
|
(list (list 10 20)))
|
||||||
|
(apl-test
|
||||||
|
"⊆ partition: 0 separates"
|
||||||
|
(mk-parts "1 1 0 0 1 ⊆ 1 2 3 4 5")
|
||||||
|
(list (list 1 2) (list 5)))
|
||||||
|
(apl-test
|
||||||
|
"⊆ partition: outer length matches partition count"
|
||||||
|
(len (get (apl-run "1 0 1 0 1 ⊆ ⍳5") :ravel))
|
||||||
|
3))
|
||||||
|
|
||||||
|
(begin
|
||||||
|
(apl-test
|
||||||
|
"⍎ execute: ⍎ '1 + 2' → 3"
|
||||||
|
(mkrv (apl-run "⍎ '1 + 2'"))
|
||||||
|
(list 3))
|
||||||
|
(apl-test
|
||||||
|
"⍎ execute: ⍎ '+/⍳10' → 55"
|
||||||
|
(mkrv (apl-run "⍎ '+/⍳10'"))
|
||||||
|
(list 55))
|
||||||
|
(apl-test
|
||||||
|
"⍎ execute: ⍎ '⌈/ 1 3 9 5 7' → 9"
|
||||||
|
(mkrv (apl-run "⍎ '⌈/ 1 3 9 5 7'"))
|
||||||
|
(list 9))
|
||||||
|
(apl-test
|
||||||
|
"⍎ execute: ⍎ '⍳5' → 1..5"
|
||||||
|
(mkrv (apl-run "⍎ '⍳5'"))
|
||||||
|
(list 1 2 3 4 5))
|
||||||
|
(apl-test
|
||||||
|
"⍎ execute: ⍎ '×/⍳5' → 120"
|
||||||
|
(mkrv (apl-run "⍎ '×/⍳5'"))
|
||||||
|
(list 120))
|
||||||
|
(apl-test
|
||||||
|
"⍎ execute: round-trip ⍎ ⎕FMT 42 → 42"
|
||||||
|
(mkrv (apl-run "⍎ ⎕FMT 42"))
|
||||||
|
(list 42))
|
||||||
|
(apl-test
|
||||||
|
"⍎ execute: nested ⍎ ⍎"
|
||||||
|
(mkrv (apl-run "⍎ '⍎ ''2 × 3'''"))
|
||||||
|
(list 6))
|
||||||
|
(apl-test
|
||||||
|
"⍎ execute: with assignment side-effect"
|
||||||
|
(mkrv (apl-run "⍎ 'q ← 99 ⋄ q + 1'"))
|
||||||
|
(list 100)))
|
||||||
|
|
||||||
|
(begin
|
||||||
|
(apl-test
|
||||||
|
"het-inner: 1 ⍵ ∨.∧ X — result is enclosed (5 5)"
|
||||||
|
(let
|
||||||
|
((r (apl-run "B ← 5 5 ⍴ 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 ⋄ X ← 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂B ⋄ 1 B ∨.∧ X")))
|
||||||
|
(list
|
||||||
|
(len (get r :shape))
|
||||||
|
(= (type-of (first (get r :ravel))) "dict")))
|
||||||
|
(list 0 true))
|
||||||
|
(apl-test
|
||||||
|
"het-inner: ⊃ unwraps to (5 5) board"
|
||||||
|
(mksh
|
||||||
|
(apl-run
|
||||||
|
"B ← 5 5 ⍴ 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 ⋄ X ← 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂B ⋄ ⊃ 1 B ∨.∧ X"))
|
||||||
|
(list 5 5))
|
||||||
|
(apl-test
|
||||||
|
"het-inner: homogeneous inner product unaffected"
|
||||||
|
(mkrv (apl-run "1 2 3 +.× 4 5 6"))
|
||||||
|
(list 32))
|
||||||
|
(apl-test
|
||||||
|
"het-inner: matrix inner product unaffected"
|
||||||
|
(mkrv (apl-run "(2 2 ⍴ 1 2 3 4) +.× 2 2 ⍴ 5 6 7 8"))
|
||||||
|
(list 19 22 43 50)))
|
||||||
189
lib/apl/tests/programs-e2e.sx
Normal file
189
lib/apl/tests/programs-e2e.sx
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
; End-to-end tests of the classic-program archetypes — running APL
|
||||||
|
; source through the full pipeline (tokenize → parse → eval-ast → runtime).
|
||||||
|
;
|
||||||
|
; These mirror the algorithms documented in lib/apl/tests/programs/*.apl
|
||||||
|
; but use forms our pipeline supports today (named functions instead of
|
||||||
|
; the inline ⍵← rebinding idiom; multi-stmt over single one-liners).
|
||||||
|
|
||||||
|
(define mkrv (fn (arr) (get arr :ravel)))
|
||||||
|
(define mksh (fn (arr) (get arr :shape)))
|
||||||
|
|
||||||
|
; ---------- factorial via ∇ recursion (cf. n-queens style) ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: factorial 5! = 120"
|
||||||
|
(mkrv (apl-run "fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 5"))
|
||||||
|
(list 120))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: factorial 7! = 5040"
|
||||||
|
(mkrv (apl-run "fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 7"))
|
||||||
|
(list 5040))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: factorial via ×/⍳N (no recursion)"
|
||||||
|
(mkrv (apl-run "fact ← {×/⍳⍵} ⋄ fact 6"))
|
||||||
|
(list 720))
|
||||||
|
|
||||||
|
; ---------- sum / triangular numbers (sum-1..N) ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: triangular(10) = 55"
|
||||||
|
(mkrv (apl-run "tri ← {+/⍳⍵} ⋄ tri 10"))
|
||||||
|
(list 55))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: triangular(100) = 5050"
|
||||||
|
(mkrv (apl-run "tri ← {+/⍳⍵} ⋄ tri 100"))
|
||||||
|
(list 5050))
|
||||||
|
|
||||||
|
; ---------- sum of squares ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: sum-of-squares 1..5 = 55"
|
||||||
|
(mkrv (apl-run "ss ← {+/⍵×⍵} ⋄ ss ⍳5"))
|
||||||
|
(list 55))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: sum-of-squares 1..10 = 385"
|
||||||
|
(mkrv (apl-run "ss ← {+/⍵×⍵} ⋄ ss ⍳10"))
|
||||||
|
(list 385))
|
||||||
|
|
||||||
|
; ---------- divisor-counting (prime-sieve building blocks) ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: divisor counts 1..5 via outer mod"
|
||||||
|
(mkrv (apl-run "P ← ⍳ 5 ⋄ +⌿ 0 = P ∘.| P"))
|
||||||
|
(list 1 2 2 3 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: divisor counts 1..10"
|
||||||
|
(mkrv (apl-run "P ← ⍳ 10 ⋄ +⌿ 0 = P ∘.| P"))
|
||||||
|
(list 1 2 2 3 2 4 2 4 3 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: prime-mask 1..10 (count==2)"
|
||||||
|
(mkrv (apl-run "P ← ⍳ 10 ⋄ 2 = +⌿ 0 = P ∘.| P"))
|
||||||
|
(list 0 1 1 0 1 0 1 0 0 0))
|
||||||
|
|
||||||
|
; ---------- monadic primitives chained ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: sum of |abs| = 15"
|
||||||
|
(mkrv (apl-run "+/|¯1 ¯2 ¯3 ¯4 ¯5"))
|
||||||
|
(list 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: max of squares 1..6"
|
||||||
|
(mkrv (apl-run "⌈/(⍳6)×⍳6"))
|
||||||
|
(list 36))
|
||||||
|
|
||||||
|
; ---------- nested named functions ----------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: compose dbl and sq via two named fns"
|
||||||
|
(mkrv (apl-run "dbl ← {⍵+⍵} ⋄ sq ← {⍵×⍵} ⋄ sq dbl 3"))
|
||||||
|
(list 36))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: max-of-two as named dyadic fn"
|
||||||
|
(mkrv (apl-run "mx ← {⍺⌈⍵} ⋄ 5 mx 3"))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"e2e: sqrt-via-newton 1 step from 1 → 2.5"
|
||||||
|
(mkrv (apl-run "step ← {(⍵+⍺÷⍵)÷2} ⋄ 4 step 1"))
|
||||||
|
(list 2.5))
|
||||||
|
|
||||||
|
(begin
|
||||||
|
(apl-test
|
||||||
|
"life.apl: blinker 5×5 → vertical blinker"
|
||||||
|
(mkrv
|
||||||
|
(apl-run
|
||||||
|
"life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵} ⋄ life 5 5 ⍴ 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0"))
|
||||||
|
(list 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0))
|
||||||
|
(apl-test
|
||||||
|
"life.apl: blinker oscillates (period 2)"
|
||||||
|
(mkrv
|
||||||
|
(apl-run
|
||||||
|
"life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵} ⋄ life life 5 5 ⍴ 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0"))
|
||||||
|
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))
|
||||||
|
(apl-test
|
||||||
|
"life.apl: 2×2 block stable"
|
||||||
|
(mkrv
|
||||||
|
(apl-run
|
||||||
|
"life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵} ⋄ life 4 4 ⍴ 0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0"))
|
||||||
|
(list 0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0))
|
||||||
|
(apl-test
|
||||||
|
"life.apl: empty grid stays empty"
|
||||||
|
(mkrv
|
||||||
|
(apl-run
|
||||||
|
"life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵} ⋄ life 5 5 ⍴ 0"))
|
||||||
|
(list 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
|
||||||
|
(apl-test
|
||||||
|
"life.apl: source-file as-written runs"
|
||||||
|
(let
|
||||||
|
((dfn (apl-run-file "lib/apl/tests/programs/life.apl"))
|
||||||
|
(board
|
||||||
|
(apl-run "5 5 ⍴ 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0")))
|
||||||
|
(get (apl-call-dfn-m dfn board) :ravel))
|
||||||
|
(list 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0)))
|
||||||
|
|
||||||
|
(begin
|
||||||
|
(apl-test
|
||||||
|
"quicksort.apl: 11-element with duplicates"
|
||||||
|
(begin
|
||||||
|
(apl-rng-seed! 42)
|
||||||
|
(mkrv
|
||||||
|
(apl-run
|
||||||
|
"quicksort ← {1≥≢⍵:⍵ ⋄ p←⍵⌷⍨?≢⍵ ⋄ (∇⍵⌿⍨⍵<p),(p=⍵)/⍵,∇⍵⌿⍨⍵>p} ⋄ quicksort 3 1 4 1 5 9 2 6 5 3 5")))
|
||||||
|
(list 1 1 2 3 3 4 5 5 5 6 9))
|
||||||
|
(apl-test
|
||||||
|
"quicksort.apl: already sorted"
|
||||||
|
(begin
|
||||||
|
(apl-rng-seed! 42)
|
||||||
|
(mkrv
|
||||||
|
(apl-run
|
||||||
|
"quicksort ← {1≥≢⍵:⍵ ⋄ p←⍵⌷⍨?≢⍵ ⋄ (∇⍵⌿⍨⍵<p),(p=⍵)/⍵,∇⍵⌿⍨⍵>p} ⋄ quicksort 1 2 3 4 5")))
|
||||||
|
(list 1 2 3 4 5))
|
||||||
|
(apl-test
|
||||||
|
"quicksort.apl: reverse sorted"
|
||||||
|
(begin
|
||||||
|
(apl-rng-seed! 42)
|
||||||
|
(mkrv
|
||||||
|
(apl-run
|
||||||
|
"quicksort ← {1≥≢⍵:⍵ ⋄ p←⍵⌷⍨?≢⍵ ⋄ (∇⍵⌿⍨⍵<p),(p=⍵)/⍵,∇⍵⌿⍨⍵>p} ⋄ quicksort 5 4 3 2 1")))
|
||||||
|
(list 1 2 3 4 5))
|
||||||
|
(apl-test
|
||||||
|
"quicksort.apl: all equal"
|
||||||
|
(begin
|
||||||
|
(apl-rng-seed! 42)
|
||||||
|
(mkrv
|
||||||
|
(apl-run
|
||||||
|
"quicksort ← {1≥≢⍵:⍵ ⋄ p←⍵⌷⍨?≢⍵ ⋄ (∇⍵⌿⍨⍵<p),(p=⍵)/⍵,∇⍵⌿⍨⍵>p} ⋄ quicksort 7 7 7 7")))
|
||||||
|
(list 7 7 7 7))
|
||||||
|
(apl-test
|
||||||
|
"quicksort.apl: single element"
|
||||||
|
(begin
|
||||||
|
(apl-rng-seed! 42)
|
||||||
|
(mkrv
|
||||||
|
(apl-run
|
||||||
|
"quicksort ← {1≥≢⍵:⍵ ⋄ p←⍵⌷⍨?≢⍵ ⋄ (∇⍵⌿⍨⍵<p),(p=⍵)/⍵,∇⍵⌿⍨⍵>p} ⋄ quicksort ,42")))
|
||||||
|
(list 42))
|
||||||
|
(apl-test
|
||||||
|
"quicksort.apl: matches grade-up"
|
||||||
|
(begin
|
||||||
|
(apl-rng-seed! 42)
|
||||||
|
(mkrv
|
||||||
|
(apl-run
|
||||||
|
"V ← 8 3 1 9 2 7 5 6 4 ⋄ quicksort ← {1≥≢⍵:⍵ ⋄ p←⍵⌷⍨?≢⍵ ⋄ (∇⍵⌿⍨⍵<p),(p=⍵)/⍵,∇⍵⌿⍨⍵>p} ⋄ quicksort V")))
|
||||||
|
(list 1 2 3 4 5 6 7 8 9))
|
||||||
|
(apl-test
|
||||||
|
"quicksort.apl: source-file as-written runs"
|
||||||
|
(begin
|
||||||
|
(apl-rng-seed! 42)
|
||||||
|
(let
|
||||||
|
((dfn (apl-run-file "lib/apl/tests/programs/quicksort.apl"))
|
||||||
|
(vec (apl-run "5 2 8 1 9 3 7 4 6")))
|
||||||
|
(get (apl-call-dfn-m dfn vec) :ravel)))
|
||||||
|
(list 1 2 3 4 5 6 7 8 9)))
|
||||||
304
lib/apl/tests/programs.sx
Normal file
304
lib/apl/tests/programs.sx
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
; Tests for classic APL programs (lib/apl/tests/programs/*.apl).
|
||||||
|
; Programs are showcase APL source; runtime impl is in lib/apl/runtime.sx.
|
||||||
|
|
||||||
|
(define mkrv (fn (arr) (get arr :ravel)))
|
||||||
|
(define mksh (fn (arr) (get arr :shape)))
|
||||||
|
|
||||||
|
; ===== primes (Sieve of Eratosthenes) =====
|
||||||
|
|
||||||
|
(apl-test "primes 1 → empty" (mkrv (apl-primes 1)) (list))
|
||||||
|
|
||||||
|
(apl-test "primes 2 → just 2" (mkrv (apl-primes 2)) (list 2))
|
||||||
|
|
||||||
|
(apl-test "primes 10 → 2 3 5 7" (mkrv (apl-primes 10)) (list 2 3 5 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"primes 20 → 2 3 5 7 11 13 17 19"
|
||||||
|
(mkrv (apl-primes 20))
|
||||||
|
(list 2 3 5 7 11 13 17 19))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"primes 30"
|
||||||
|
(mkrv (apl-primes 30))
|
||||||
|
(list 2 3 5 7 11 13 17 19 23 29))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"primes 50"
|
||||||
|
(mkrv (apl-primes 50))
|
||||||
|
(list 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47))
|
||||||
|
|
||||||
|
(apl-test "primes 7 length" (first (mksh (apl-primes 7))) 4)
|
||||||
|
|
||||||
|
(apl-test "primes 100 has 25 primes" (first (mksh (apl-primes 100))) 25)
|
||||||
|
|
||||||
|
; ===== compress helper sanity =====
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compress 1 0 1 0 1 / 10 20 30 40 50"
|
||||||
|
(mkrv
|
||||||
|
(apl-compress
|
||||||
|
(make-array (list 5) (list 1 0 1 0 1))
|
||||||
|
(make-array (list 5) (list 10 20 30 40 50))))
|
||||||
|
(list 10 30 50))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compress all-zero mask → empty"
|
||||||
|
(mkrv
|
||||||
|
(apl-compress
|
||||||
|
(make-array (list 3) (list 0 0 0))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"compress all-one mask → full vector"
|
||||||
|
(mkrv
|
||||||
|
(apl-compress
|
||||||
|
(make-array (list 3) (list 1 1 1))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"life: empty 5x5 stays empty"
|
||||||
|
(mkrv
|
||||||
|
(apl-life-step
|
||||||
|
(make-array
|
||||||
|
(list 5 5)
|
||||||
|
(list 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))))
|
||||||
|
(list 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"life: horizontal blinker → vertical blinker"
|
||||||
|
(mkrv
|
||||||
|
(apl-life-step
|
||||||
|
(make-array
|
||||||
|
(list 5 5)
|
||||||
|
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))))
|
||||||
|
(list 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"life: vertical blinker → horizontal blinker"
|
||||||
|
(mkrv
|
||||||
|
(apl-life-step
|
||||||
|
(make-array
|
||||||
|
(list 5 5)
|
||||||
|
(list 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0))))
|
||||||
|
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"life: blinker has period 2"
|
||||||
|
(mkrv
|
||||||
|
(apl-life-step
|
||||||
|
(apl-life-step
|
||||||
|
(make-array
|
||||||
|
(list 5 5)
|
||||||
|
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0)))))
|
||||||
|
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"life: 2x2 block stable on 5x5"
|
||||||
|
(mkrv
|
||||||
|
(apl-life-step
|
||||||
|
(make-array
|
||||||
|
(list 5 5)
|
||||||
|
(list 0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0))))
|
||||||
|
(list 0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"life: shape preserved"
|
||||||
|
(mksh
|
||||||
|
(apl-life-step
|
||||||
|
(make-array
|
||||||
|
(list 5 5)
|
||||||
|
(list 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0))))
|
||||||
|
(list 5 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"life: glider on 6x6 advances"
|
||||||
|
(mkrv
|
||||||
|
(apl-life-step
|
||||||
|
(make-array
|
||||||
|
(list 6 6)
|
||||||
|
(list
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
1
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
1
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
1
|
||||||
|
1
|
||||||
|
1
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0))))
|
||||||
|
(list
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
1
|
||||||
|
0
|
||||||
|
1
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
1
|
||||||
|
1
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
1
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0
|
||||||
|
0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"mandelbrot c=0 stays bounded"
|
||||||
|
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list 0)) 100))
|
||||||
|
(list 100))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"mandelbrot c=-1 cycle bounded"
|
||||||
|
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list -1)) 100))
|
||||||
|
(list 100))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"mandelbrot c=-2 boundary stays bounded"
|
||||||
|
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list -2)) 100))
|
||||||
|
(list 100))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"mandelbrot c=0.25 boundary stays bounded"
|
||||||
|
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list 0.25)) 100))
|
||||||
|
(list 100))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"mandelbrot c=1 escapes at iter 3"
|
||||||
|
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list 1)) 100))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"mandelbrot c=0.5 escapes at iter 5"
|
||||||
|
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list 0.5)) 100))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"mandelbrot batched grid (rank-polymorphic)"
|
||||||
|
(mkrv (apl-mandelbrot-1d (make-array (list 5) (list -2 -1 0 1 2)) 10))
|
||||||
|
(list 10 10 10 3 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"mandelbrot batched preserves shape"
|
||||||
|
(mksh (apl-mandelbrot-1d (make-array (list 5) (list -2 -1 0 1 2)) 10))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"mandelbrot c=-1.5 stays bounded"
|
||||||
|
(mkrv (apl-mandelbrot-1d (make-array (list 1) (list -1.5)) 100))
|
||||||
|
(list 100))
|
||||||
|
|
||||||
|
(apl-test "queens 1 → 1 solution" (mkrv (apl-queens 1)) (list 1))
|
||||||
|
|
||||||
|
(apl-test "queens 2 → 0 solutions" (mkrv (apl-queens 2)) (list 0))
|
||||||
|
|
||||||
|
(apl-test "queens 3 → 0 solutions" (mkrv (apl-queens 3)) (list 0))
|
||||||
|
|
||||||
|
(apl-test "queens 4 → 2 solutions" (mkrv (apl-queens 4)) (list 2))
|
||||||
|
|
||||||
|
(apl-test "queens 5 → 10 solutions" (mkrv (apl-queens 5)) (list 10))
|
||||||
|
|
||||||
|
(apl-test "queens 6 → 4 solutions" (mkrv (apl-queens 6)) (list 4))
|
||||||
|
|
||||||
|
(apl-test "queens 7 → 40 solutions" (mkrv (apl-queens 7)) (list 40))
|
||||||
|
|
||||||
|
(apl-test "permutations of 3 has 6" (len (apl-permutations 3)) 6)
|
||||||
|
|
||||||
|
(apl-test "permutations of 4 has 24" (len (apl-permutations 4)) 24)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"quicksort empty"
|
||||||
|
(mkrv (apl-quicksort (make-array (list 0) (list))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"quicksort single"
|
||||||
|
(mkrv (apl-quicksort (make-array (list 1) (list 42))))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"quicksort already sorted"
|
||||||
|
(mkrv (apl-quicksort (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 2 3 4 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"quicksort reverse sorted"
|
||||||
|
(mkrv (apl-quicksort (make-array (list 5) (list 5 4 3 2 1))))
|
||||||
|
(list 1 2 3 4 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"quicksort with duplicates"
|
||||||
|
(mkrv (apl-quicksort (make-array (list 7) (list 3 1 4 1 5 9 2))))
|
||||||
|
(list 1 1 2 3 4 5 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"quicksort all equal"
|
||||||
|
(mkrv (apl-quicksort (make-array (list 5) (list 7 7 7 7 7))))
|
||||||
|
(list 7 7 7 7 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"quicksort negatives"
|
||||||
|
(mkrv (apl-quicksort (make-array (list 5) (list -3 1 -1 2 0))))
|
||||||
|
(list -3 -1 0 1 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"quicksort 11-element pi"
|
||||||
|
(mkrv
|
||||||
|
(apl-quicksort (make-array (list 11) (list 3 1 4 1 5 9 2 6 5 3 5))))
|
||||||
|
(list 1 1 2 3 3 4 5 5 5 6 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"quicksort preserves length"
|
||||||
|
(first
|
||||||
|
(mksh (apl-quicksort (make-array (list 7) (list 3 1 4 1 5 9 2)))))
|
||||||
|
7)
|
||||||
22
lib/apl/tests/programs/life.apl
Normal file
22
lib/apl/tests/programs/life.apl
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
⍝ Conway's Game of Life — toroidal one-liner
|
||||||
|
⍝
|
||||||
|
⍝ The classic Roger Hui formulation:
|
||||||
|
⍝ life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}
|
||||||
|
⍝
|
||||||
|
⍝ Read right-to-left:
|
||||||
|
⍝ ⊂⍵ : enclose the board (so it's a single scalar item)
|
||||||
|
⍝ ¯1 0 1 ⌽¨ ⊂⍵ : produce 3 horizontally-shifted copies
|
||||||
|
⍝ ¯1 0 1 ∘.⊖ … : outer-product with vertical shifts → 3×3 = 9 shifts
|
||||||
|
⍝ +/ +/ … : sum the 9 boards element-wise → neighbor-count + self
|
||||||
|
⍝ 3 4 = … : leading-axis-extended boolean — count is 3 (born) or 4 (survive)
|
||||||
|
⍝ 1 ⍵ ∨.∧ … : "alive next" iff (count=3) or (alive AND count=4)
|
||||||
|
⍝ ⊃ … : disclose the enclosed result back to a 2D board
|
||||||
|
⍝
|
||||||
|
⍝ Rules in plain language:
|
||||||
|
⍝ - dead cell + 3 live neighbors → born
|
||||||
|
⍝ - live cell + 2 or 3 live neighbors → survives
|
||||||
|
⍝ - all else → dies
|
||||||
|
⍝
|
||||||
|
⍝ Toroidal: edges wrap (rotate is cyclic).
|
||||||
|
|
||||||
|
life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}
|
||||||
29
lib/apl/tests/programs/mandelbrot.apl
Normal file
29
lib/apl/tests/programs/mandelbrot.apl
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
⍝ Mandelbrot — real-axis subset
|
||||||
|
⍝
|
||||||
|
⍝ For complex c, the Mandelbrot set is { c : |z_n| stays bounded } where
|
||||||
|
⍝ z_0 = 0, z_{n+1} = z_n² + c.
|
||||||
|
⍝ Restricting c (and z) to ℝ gives the segment c ∈ [-2, 1/4]
|
||||||
|
⍝ where the iteration stays bounded.
|
||||||
|
⍝
|
||||||
|
⍝ Rank-polymorphic batched-iteration form:
|
||||||
|
⍝ mandelbrot ← {⍵ ⍵⍵ ⍺⍺ +,(⍺⍺ × ⍺⍺) }
|
||||||
|
⍝
|
||||||
|
⍝ Pseudocode (as we don't have ⎕ system fns yet):
|
||||||
|
⍝ z ← 0×c ⍝ start at zero
|
||||||
|
⍝ alive ← 1+0×c ⍝ all "still in"
|
||||||
|
⍝ for k iterations:
|
||||||
|
⍝ alive ← alive ∧ 4 ≥ z×z ⍝ still bounded?
|
||||||
|
⍝ z ← alive × c + z×z ⍝ freeze escaped via mask
|
||||||
|
⍝ count ← count + alive ⍝ tally surviving iters
|
||||||
|
⍝
|
||||||
|
⍝ Examples (count after 100 iterations):
|
||||||
|
⍝ c=0 : 100 (z stays at 0)
|
||||||
|
⍝ c=-1 : 100 (cycles 0,-1,0,-1,...)
|
||||||
|
⍝ c=-2 : 100 (settles at 2 — boundary)
|
||||||
|
⍝ c=0.25 : 100 (boundary — converges to 0.5)
|
||||||
|
⍝ c=0.5 : 5 (escapes by iteration 6)
|
||||||
|
⍝ c=1 : 3 (escapes quickly)
|
||||||
|
⍝
|
||||||
|
⍝ Real-axis Mandelbrot set: bounded for c ∈ [-2, 0.25].
|
||||||
|
|
||||||
|
mandelbrot ← {z←alive←count←0×⍵ ⋄ {alive←alive∧4≥z×z ⋄ z←alive×⍵+z×z ⋄ count+←alive}⍣⍺⊢⍵}
|
||||||
18
lib/apl/tests/programs/n-queens.apl
Normal file
18
lib/apl/tests/programs/n-queens.apl
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
⍝ N-Queens — count solutions to placing N non-attacking queens on N×N
|
||||||
|
⍝
|
||||||
|
⍝ A solution is encoded as a permutation P of 1..N where P[i] is the
|
||||||
|
⍝ column of the queen in row i. Rows and columns are then automatically
|
||||||
|
⍝ unique (it's a permutation). We must additionally rule out queens
|
||||||
|
⍝ sharing a diagonal: |i-j| = |P[i]-P[j]| for any pair.
|
||||||
|
⍝
|
||||||
|
⍝ Backtracking via reduce — the classic Roger Hui style:
|
||||||
|
⍝ queens ← {≢{⍵,¨⍨↓(0=∊(¨⍳⍴⍵)≠.+|⍵)/⍳⍴⍵}/(⍳⍵)⍴⊂⍳⍵}
|
||||||
|
⍝
|
||||||
|
⍝ Plain reading:
|
||||||
|
⍝ permute 1..N, keep those where no two queens share a diagonal.
|
||||||
|
⍝
|
||||||
|
⍝ Known solution counts (OEIS A000170):
|
||||||
|
⍝ N 1 2 3 4 5 6 7 8 9 10
|
||||||
|
⍝ q(N) 1 0 0 2 10 4 40 92 352 724
|
||||||
|
|
||||||
|
queens ← {≢({(i j)←⍺⍵ ⋄ (|i-j)≠|(P[i])-(P[j])}⌿permutations ⍵)}
|
||||||
16
lib/apl/tests/programs/primes.apl
Normal file
16
lib/apl/tests/programs/primes.apl
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
⍝ Sieve of Eratosthenes — the classic APL one-liner
|
||||||
|
⍝ primes ← (2=+⌿0=A∘.|A)/A←⍳N
|
||||||
|
⍝
|
||||||
|
⍝ Read right-to-left:
|
||||||
|
⍝ A ← ⍳N : A is 1..N
|
||||||
|
⍝ A∘.|A : outer-product residue table — M[i,j] = A[j] mod A[i]
|
||||||
|
⍝ 0=... : boolean — true where A[i] divides A[j]
|
||||||
|
⍝ +⌿... : column sums — count of divisors per A[j]
|
||||||
|
⍝ 2=... : true for numbers with exactly 2 divisors (1 and self) → primes
|
||||||
|
⍝ .../A : compress — select A[j] where mask[j] is true
|
||||||
|
⍝
|
||||||
|
⍝ Examples:
|
||||||
|
⍝ primes 10 → 2 3 5 7
|
||||||
|
⍝ primes 30 → 2 3 5 7 11 13 17 19 23 29
|
||||||
|
|
||||||
|
primes ← {(2=+⌿0=⍵∘.|⍵)/⍵←⍳⍵}
|
||||||
25
lib/apl/tests/programs/quicksort.apl
Normal file
25
lib/apl/tests/programs/quicksort.apl
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
⍝ Quicksort — the classic Roger Hui one-liner
|
||||||
|
⍝
|
||||||
|
⍝ Q ← {1≥≢⍵:⍵ ⋄ (∇⍵⌿⍨⍵<p),(p=⍵)/⍵,∇⍵⌿⍨⍵>p←⍵⌷⍨?≢⍵}
|
||||||
|
⍝
|
||||||
|
⍝ Read right-to-left:
|
||||||
|
⍝ ?≢⍵ : pick a random index in 1..length
|
||||||
|
⍝ ⍵⌷⍨… : take that element as pivot p
|
||||||
|
⍝ ⍵>p : boolean — elements greater than pivot
|
||||||
|
⍝ ∇⍵⌿⍨… : recursively sort the > partition
|
||||||
|
⍝ (p=⍵)/⍵ : keep elements equal to pivot
|
||||||
|
⍝ ⍵<p : boolean — elements less than pivot
|
||||||
|
⍝ ∇⍵⌿⍨… : recursively sort the < partition
|
||||||
|
⍝ , : catenate ⟨less⟩ ⟨equal⟩ ⟨greater⟩
|
||||||
|
⍝ 1≥≢⍵:⍵ : guard — base case for length ≤ 1
|
||||||
|
⍝
|
||||||
|
⍝ Stability: not stable on duplicates (but eq-class is preserved as a block).
|
||||||
|
⍝ Worst case O(N²) on already-sorted input with deterministic pivot;
|
||||||
|
⍝ randomized pivot selection gives expected O(N log N).
|
||||||
|
⍝
|
||||||
|
⍝ Examples:
|
||||||
|
⍝ Q 3 1 4 1 5 9 2 6 5 3 5 → 1 1 2 3 3 4 5 5 5 6 9
|
||||||
|
⍝ Q ⍳0 → ⍬ (empty)
|
||||||
|
⍝ Q ,42 → 42
|
||||||
|
|
||||||
|
quicksort ← {1≥≢⍵:⍵ ⋄ p←⍵⌷⍨?≢⍵ ⋄ (∇⍵⌿⍨⍵<p),(p=⍵)/⍵,∇⍵⌿⍨⍵>p}
|
||||||
369
lib/apl/tests/scalar.sx
Normal file
369
lib/apl/tests/scalar.sx
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
; APL scalar primitives test suite
|
||||||
|
; Requires: lib/apl/runtime.sx
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Test framework
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define apl-rt-count 0)
|
||||||
|
(define apl-rt-pass 0)
|
||||||
|
(define apl-rt-fails (list))
|
||||||
|
|
||||||
|
; Element-wise list comparison (handles both List and ListRef)
|
||||||
|
(define
|
||||||
|
lists-eq
|
||||||
|
(fn
|
||||||
|
(a b)
|
||||||
|
(if
|
||||||
|
(and (= (len a) 0) (= (len b) 0))
|
||||||
|
true
|
||||||
|
(if
|
||||||
|
(not (= (len a) (len b)))
|
||||||
|
false
|
||||||
|
(if
|
||||||
|
(not (= (first a) (first b)))
|
||||||
|
false
|
||||||
|
(lists-eq (rest a) (rest b)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-rt-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(begin
|
||||||
|
(set! apl-rt-count (+ apl-rt-count 1))
|
||||||
|
(if
|
||||||
|
(equal? actual expected)
|
||||||
|
(set! apl-rt-pass (+ apl-rt-pass 1))
|
||||||
|
(append! apl-rt-fails {:actual actual :expected expected :name name})))))
|
||||||
|
|
||||||
|
; Test that a ravel equals a plain list (handles ListRef vs List)
|
||||||
|
(define
|
||||||
|
ravel-test
|
||||||
|
(fn
|
||||||
|
(name arr expected-list)
|
||||||
|
(begin
|
||||||
|
(set! apl-rt-count (+ apl-rt-count 1))
|
||||||
|
(let
|
||||||
|
((actual (get arr :ravel)))
|
||||||
|
(if
|
||||||
|
(lists-eq actual expected-list)
|
||||||
|
(set! apl-rt-pass (+ apl-rt-pass 1))
|
||||||
|
(append! apl-rt-fails {:actual actual :expected expected-list :name name}))))))
|
||||||
|
|
||||||
|
; Test a scalar ravel value (single-element list)
|
||||||
|
(define
|
||||||
|
scalar-test
|
||||||
|
(fn (name arr expected-val) (ravel-test name arr (list expected-val))))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Array constructor tests
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
"scalar: shape is empty list"
|
||||||
|
(get (apl-scalar 5) :shape)
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
"scalar: ravel has one element"
|
||||||
|
(get (apl-scalar 5) :ravel)
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-rt-test "scalar: rank 0" (array-rank (apl-scalar 5)) 0)
|
||||||
|
|
||||||
|
(apl-rt-test "scalar? returns true for scalar" (scalar? (apl-scalar 5)) true)
|
||||||
|
|
||||||
|
(apl-rt-test "scalar: zero" (get (apl-scalar 0) :ravel) (list 0))
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
"vector: shape is (3)"
|
||||||
|
(get (apl-vector (list 1 2 3)) :shape)
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
"vector: ravel matches input"
|
||||||
|
(get (apl-vector (list 1 2 3)) :ravel)
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-rt-test "vector: rank 1" (array-rank (apl-vector (list 1 2 3))) 1)
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
"scalar? returns false for vector"
|
||||||
|
(scalar? (apl-vector (list 1 2 3)))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
"make-array: rank 2"
|
||||||
|
(array-rank (make-array (list 2 3) (list 1 2 3 4 5 6)))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
"make-array: shape"
|
||||||
|
(get (make-array (list 2 3) (list 1 2 3 4 5 6)) :shape)
|
||||||
|
(list 2 3))
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
"array-ref: first element"
|
||||||
|
(array-ref (apl-vector (list 10 20 30)) 0)
|
||||||
|
10)
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
"array-ref: last element"
|
||||||
|
(array-ref (apl-vector (list 10 20 30)) 2)
|
||||||
|
30)
|
||||||
|
|
||||||
|
(apl-rt-test "enclose: wraps in rank-0" (scalar? (enclose 42)) true)
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
"enclose: ravel contains value"
|
||||||
|
(get (enclose 42) :ravel)
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-rt-test "disclose: unwraps rank-0" (disclose (enclose 42)) 42)
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Shape primitive tests
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(ravel-test "⍴ scalar: returns empty" (apl-shape (apl-scalar 5)) (list))
|
||||||
|
|
||||||
|
(ravel-test
|
||||||
|
"⍴ vector: returns (3)"
|
||||||
|
(apl-shape (apl-vector (list 1 2 3)))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(ravel-test
|
||||||
|
"⍴ matrix: returns (2 3)"
|
||||||
|
(apl-shape (make-array (list 2 3) (list 1 2 3 4 5 6)))
|
||||||
|
(list 2 3))
|
||||||
|
|
||||||
|
(ravel-test
|
||||||
|
", ravel scalar: vector of 1"
|
||||||
|
(apl-ravel (apl-scalar 5))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
", ravel vector: same elements"
|
||||||
|
(get (apl-ravel (apl-vector (list 1 2 3))) :ravel)
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
", ravel matrix: all elements"
|
||||||
|
(get (apl-ravel (make-array (list 2 3) (list 1 2 3 4 5 6))) :ravel)
|
||||||
|
(list 1 2 3 4 5 6))
|
||||||
|
|
||||||
|
(scalar-test "≢ tally scalar: 1" (apl-tally (apl-scalar 5)) 1)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"≢ tally vector: first dimension"
|
||||||
|
(apl-tally (apl-vector (list 1 2 3)))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"≢ tally matrix: first dimension"
|
||||||
|
(apl-tally (make-array (list 2 3) (list 1 2 3 4 5 6)))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"≡ depth flat vector: 0"
|
||||||
|
(apl-depth (apl-vector (list 1 2 3)))
|
||||||
|
0)
|
||||||
|
|
||||||
|
(scalar-test "≡ depth scalar: 0" (apl-depth (apl-scalar 5)) 0)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"≡ depth nested (enclose in vector): 1"
|
||||||
|
(apl-depth (enclose (apl-vector (list 1 2 3))))
|
||||||
|
1)
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; ⍳ iota tests
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(apl-rt-test
|
||||||
|
"⍳5 shape is (5)"
|
||||||
|
(get (apl-iota (apl-scalar 5)) :shape)
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(ravel-test "⍳5 ravel is 1..5" (apl-iota (apl-scalar 5)) (list 1 2 3 4 5))
|
||||||
|
|
||||||
|
(ravel-test "⍳1 ravel is (1)" (apl-iota (apl-scalar 1)) (list 1))
|
||||||
|
|
||||||
|
(ravel-test "⍳0 ravel is empty" (apl-iota (apl-scalar 0)) (list))
|
||||||
|
|
||||||
|
(apl-rt-test "apl-io is 1" apl-io 1)
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Arithmetic broadcast tests
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"+ scalar scalar: 3+4=7"
|
||||||
|
(apl-add (apl-scalar 3) (apl-scalar 4))
|
||||||
|
7)
|
||||||
|
|
||||||
|
(ravel-test
|
||||||
|
"+ vector scalar: +10"
|
||||||
|
(apl-add (apl-vector (list 1 2 3)) (apl-scalar 10))
|
||||||
|
(list 11 12 13))
|
||||||
|
|
||||||
|
(ravel-test
|
||||||
|
"+ scalar vector: 10+"
|
||||||
|
(apl-add (apl-scalar 10) (apl-vector (list 1 2 3)))
|
||||||
|
(list 11 12 13))
|
||||||
|
|
||||||
|
(ravel-test
|
||||||
|
"+ vector vector"
|
||||||
|
(apl-add (apl-vector (list 1 2 3)) (apl-vector (list 4 5 6)))
|
||||||
|
(list 5 7 9))
|
||||||
|
|
||||||
|
(scalar-test "- negate monadic" (apl-neg-m (apl-scalar 5)) -5)
|
||||||
|
|
||||||
|
(scalar-test "- dyadic 10-3=7" (apl-sub (apl-scalar 10) (apl-scalar 3)) 7)
|
||||||
|
|
||||||
|
(scalar-test "× signum positive" (apl-signum (apl-scalar 7)) 1)
|
||||||
|
|
||||||
|
(scalar-test "× signum negative" (apl-signum (apl-scalar -3)) -1)
|
||||||
|
|
||||||
|
(scalar-test "× signum zero" (apl-signum (apl-scalar 0)) 0)
|
||||||
|
|
||||||
|
(scalar-test "× dyadic 3×4=12" (apl-mul (apl-scalar 3) (apl-scalar 4)) 12)
|
||||||
|
|
||||||
|
(scalar-test "÷ reciprocal 1÷4=0.25" (apl-recip (apl-scalar 4)) 0.25)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"÷ dyadic 10÷4=2.5"
|
||||||
|
(apl-div (apl-scalar 10) (apl-scalar 4))
|
||||||
|
2.5)
|
||||||
|
|
||||||
|
(scalar-test "⌈ ceiling 2.3→3" (apl-ceil (apl-scalar 2.3)) 3)
|
||||||
|
|
||||||
|
(scalar-test "⌈ max 3 5 → 5" (apl-max (apl-scalar 3) (apl-scalar 5)) 5)
|
||||||
|
|
||||||
|
(scalar-test "⌊ floor 2.7→2" (apl-floor (apl-scalar 2.7)) 2)
|
||||||
|
|
||||||
|
(scalar-test "⌊ min 3 5 → 3" (apl-min (apl-scalar 3) (apl-scalar 5)) 3)
|
||||||
|
|
||||||
|
(scalar-test "* exp monadic e^0=1" (apl-exp (apl-scalar 0)) 1)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"* pow dyadic 2^10=1024"
|
||||||
|
(apl-pow (apl-scalar 2) (apl-scalar 10))
|
||||||
|
1024)
|
||||||
|
|
||||||
|
(scalar-test "⍟ ln 1=0" (apl-ln (apl-scalar 1)) 0)
|
||||||
|
|
||||||
|
(scalar-test "| abs positive" (apl-abs (apl-scalar 5)) 5)
|
||||||
|
|
||||||
|
(scalar-test "| abs negative" (apl-abs (apl-scalar -5)) 5)
|
||||||
|
|
||||||
|
(scalar-test "| mod 3|7=1" (apl-mod (apl-scalar 3) (apl-scalar 7)) 1)
|
||||||
|
|
||||||
|
(scalar-test "! factorial 5!=120" (apl-fact (apl-scalar 5)) 120)
|
||||||
|
|
||||||
|
(scalar-test "! factorial 0!=1" (apl-fact (apl-scalar 0)) 1)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"! binomial 4 choose 2 = 6"
|
||||||
|
(apl-binomial (apl-scalar 4) (apl-scalar 2))
|
||||||
|
6)
|
||||||
|
|
||||||
|
(scalar-test "○ pi×0=0" (apl-pi-times (apl-scalar 0)) 0)
|
||||||
|
|
||||||
|
(scalar-test "○ trig sin(0)=0" (apl-trig (apl-scalar 1) (apl-scalar 0)) 0)
|
||||||
|
|
||||||
|
(scalar-test "○ trig cos(0)=1" (apl-trig (apl-scalar 2) (apl-scalar 0)) 1)
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Comparison tests
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(scalar-test "< less: 3<5 → 1" (apl-lt (apl-scalar 3) (apl-scalar 5)) 1)
|
||||||
|
|
||||||
|
(scalar-test "< less: 5<3 → 0" (apl-lt (apl-scalar 5) (apl-scalar 3)) 0)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"≤ le equal: 3≤3 → 1"
|
||||||
|
(apl-le (apl-scalar 3) (apl-scalar 3))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(scalar-test "= eq: 5=5 → 1" (apl-eq (apl-scalar 5) (apl-scalar 5)) 1)
|
||||||
|
|
||||||
|
(scalar-test "= ne: 5=6 → 0" (apl-eq (apl-scalar 5) (apl-scalar 6)) 0)
|
||||||
|
|
||||||
|
(scalar-test "≥ ge: 5≥3 → 1" (apl-ge (apl-scalar 5) (apl-scalar 3)) 1)
|
||||||
|
|
||||||
|
(scalar-test "> gt: 5>3 → 1" (apl-gt (apl-scalar 5) (apl-scalar 3)) 1)
|
||||||
|
|
||||||
|
(scalar-test "≠ ne: 5≠3 → 1" (apl-ne (apl-scalar 5) (apl-scalar 3)) 1)
|
||||||
|
|
||||||
|
(ravel-test
|
||||||
|
"comparison vector broadcast: 1 2 3 < 2 → 1 0 0"
|
||||||
|
(apl-lt (apl-vector (list 1 2 3)) (apl-scalar 2))
|
||||||
|
(list 1 0 0))
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Logical tests
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(scalar-test "~ not 0 → 1" (apl-not (apl-scalar 0)) 1)
|
||||||
|
|
||||||
|
(scalar-test "~ not 1 → 0" (apl-not (apl-scalar 1)) 0)
|
||||||
|
|
||||||
|
(ravel-test
|
||||||
|
"~ not vector: 1 0 1 0 → 0 1 0 1"
|
||||||
|
(apl-not (apl-vector (list 1 0 1 0)))
|
||||||
|
(list 0 1 0 1))
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"∧ and 1∧1 → 1"
|
||||||
|
(apl-and (apl-scalar 1) (apl-scalar 1))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"∧ and 1∧0 → 0"
|
||||||
|
(apl-and (apl-scalar 1) (apl-scalar 0))
|
||||||
|
0)
|
||||||
|
|
||||||
|
(scalar-test "∨ or 0∨1 → 1" (apl-or (apl-scalar 0) (apl-scalar 1)) 1)
|
||||||
|
|
||||||
|
(scalar-test "∨ or 0∨0 → 0" (apl-or (apl-scalar 0) (apl-scalar 0)) 0)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"⍱ nor 0⍱0 → 1"
|
||||||
|
(apl-nor (apl-scalar 0) (apl-scalar 0))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"⍱ nor 1⍱0 → 0"
|
||||||
|
(apl-nor (apl-scalar 1) (apl-scalar 0))
|
||||||
|
0)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"⍲ nand 1⍲1 → 0"
|
||||||
|
(apl-nand (apl-scalar 1) (apl-scalar 1))
|
||||||
|
0)
|
||||||
|
|
||||||
|
(scalar-test
|
||||||
|
"⍲ nand 1⍲0 → 1"
|
||||||
|
(apl-nand (apl-scalar 1) (apl-scalar 0))
|
||||||
|
1)
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; plus-m identity test
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(scalar-test "+ monadic identity: +5 → 5" (apl-plus-m (apl-scalar 5)) 5)
|
||||||
|
|
||||||
|
; ============================================================
|
||||||
|
; Summary
|
||||||
|
; ============================================================
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-scalar-summary
|
||||||
|
(str
|
||||||
|
"scalar "
|
||||||
|
apl-rt-pass
|
||||||
|
"/"
|
||||||
|
apl-rt-count
|
||||||
|
(if (= (len apl-rt-fails) 0) "" (str " FAILS: " apl-rt-fails))))
|
||||||
608
lib/apl/tests/structural.sx
Normal file
608
lib/apl/tests/structural.sx
Normal file
@@ -0,0 +1,608 @@
|
|||||||
|
;; lib/apl/tests/structural.sx — Phase 3: structural primitives
|
||||||
|
;; Tests for: apl-reshape, apl-ravel, apl-transpose, apl-transpose-dyadic
|
||||||
|
;; Loaded after runtime.sx; shares apl-test / apl-test-pass / apl-test-fail.
|
||||||
|
|
||||||
|
(define rv (fn (arr) (get arr :ravel)))
|
||||||
|
(define sh (fn (arr) (get arr :shape)))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 1. Ravel (monadic ,)
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
(apl-test "ravel scalar" (rv (apl-ravel (apl-scalar 5))) (list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"ravel vector"
|
||||||
|
(rv (apl-ravel (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"ravel matrix"
|
||||||
|
(rv (apl-ravel (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 2 3 4 5 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"ravel shape is rank-1"
|
||||||
|
(sh (apl-ravel (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 6))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 2. Reshape (dyadic ⍴)
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reshape 2x3 ravel"
|
||||||
|
(rv
|
||||||
|
(apl-reshape
|
||||||
|
(make-array (list 2) (list 2 3))
|
||||||
|
(make-array (list 6) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 2 3 4 5 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reshape 2x3 shape"
|
||||||
|
(sh
|
||||||
|
(apl-reshape
|
||||||
|
(make-array (list 2) (list 2 3))
|
||||||
|
(make-array (list 6) (list 1 2 3 4 5 6))))
|
||||||
|
(list 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reshape cycle 6 from 1 2"
|
||||||
|
(rv
|
||||||
|
(apl-reshape
|
||||||
|
(make-array (list 1) (list 6))
|
||||||
|
(make-array (list 2) (list 1 2))))
|
||||||
|
(list 1 2 1 2 1 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reshape cycle 2x3 from 1 2"
|
||||||
|
(rv
|
||||||
|
(apl-reshape
|
||||||
|
(make-array (list 2) (list 2 3))
|
||||||
|
(make-array (list 2) (list 1 2))))
|
||||||
|
(list 1 2 1 2 1 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reshape scalar fill"
|
||||||
|
(rv (apl-reshape (make-array (list 1) (list 4)) (apl-scalar 7)))
|
||||||
|
(list 7 7 7 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reshape truncate"
|
||||||
|
(rv
|
||||||
|
(apl-reshape
|
||||||
|
(make-array (list 1) (list 3))
|
||||||
|
(make-array (list 6) (list 10 20 30 40 50 60))))
|
||||||
|
(list 10 20 30))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reshape matrix to vector"
|
||||||
|
(sh
|
||||||
|
(apl-reshape
|
||||||
|
(make-array (list 1) (list 6))
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reshape 2x2x3"
|
||||||
|
(sh
|
||||||
|
(apl-reshape
|
||||||
|
(make-array (list 3) (list 2 2 3))
|
||||||
|
(make-array (list 12) (range 1 13))))
|
||||||
|
(list 2 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reshape to empty"
|
||||||
|
(rv
|
||||||
|
(apl-reshape
|
||||||
|
(make-array (list 1) (list 0))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 3. Monadic transpose (⍉)
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"transpose scalar shape"
|
||||||
|
(sh (apl-transpose (apl-scalar 99)))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"transpose scalar ravel"
|
||||||
|
(rv (apl-transpose (apl-scalar 99)))
|
||||||
|
(list 99))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"transpose vector shape"
|
||||||
|
(sh (apl-transpose (make-array (list 3) (list 3 1 4))))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"transpose vector ravel"
|
||||||
|
(rv (apl-transpose (make-array (list 3) (list 3 1 4))))
|
||||||
|
(list 3 1 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"transpose 2x3 shape"
|
||||||
|
(sh (apl-transpose (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 3 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"transpose 2x3 ravel"
|
||||||
|
(rv (apl-transpose (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 4 2 5 3 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"transpose 3x3"
|
||||||
|
(rv (apl-transpose (make-array (list 3 3) (list 1 2 3 4 5 6 7 8 9))))
|
||||||
|
(list 1 4 7 2 5 8 3 6 9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"transpose 1x4 shape"
|
||||||
|
(sh (apl-transpose (make-array (list 1 4) (list 1 2 3 4))))
|
||||||
|
(list 4 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"transpose twice identity"
|
||||||
|
(rv
|
||||||
|
(apl-transpose
|
||||||
|
(apl-transpose (make-array (list 2 3) (list 1 2 3 4 5 6)))))
|
||||||
|
(list 1 2 3 4 5 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"transpose 3d shape"
|
||||||
|
(sh (apl-transpose (make-array (list 2 3 4) (range 0 24))))
|
||||||
|
(list 4 3 2))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 4. Dyadic transpose (perm⍉arr)
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dyadic-transpose identity"
|
||||||
|
(rv
|
||||||
|
(apl-transpose-dyadic
|
||||||
|
(make-array (list 2) (list 1 2))
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 2 3 4 5 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dyadic-transpose swap 2x3"
|
||||||
|
(rv
|
||||||
|
(apl-transpose-dyadic
|
||||||
|
(make-array (list 2) (list 2 1))
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 4 2 5 3 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dyadic-transpose swap shape"
|
||||||
|
(sh
|
||||||
|
(apl-transpose-dyadic
|
||||||
|
(make-array (list 2) (list 2 1))
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 3 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dyadic-transpose 3d shape"
|
||||||
|
(sh
|
||||||
|
(apl-transpose-dyadic
|
||||||
|
(make-array (list 3) (list 2 1 3))
|
||||||
|
(make-array (list 2 3 4) (range 0 24))))
|
||||||
|
(list 3 2 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"take 3 from front"
|
||||||
|
(rv (apl-take (apl-scalar 3) (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"take 0"
|
||||||
|
(rv (apl-take (apl-scalar 0) (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"take -2 from back"
|
||||||
|
(rv (apl-take (apl-scalar -2) (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 4 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"take over-take pads with 0"
|
||||||
|
(rv (apl-take (apl-scalar 7) (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 2 3 4 5 0 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"take matrix 1 row 2 cols shape"
|
||||||
|
(sh
|
||||||
|
(apl-take
|
||||||
|
(make-array (list 2) (list 1 2))
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"take matrix 1 row 2 cols ravel"
|
||||||
|
(rv
|
||||||
|
(apl-take
|
||||||
|
(make-array (list 2) (list 1 2))
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"take matrix negative row"
|
||||||
|
(rv
|
||||||
|
(apl-take
|
||||||
|
(make-array (list 2) (list -1 3))
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 4 5 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"drop 2 from front"
|
||||||
|
(rv (apl-drop (apl-scalar 2) (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 3 4 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"drop -2 from back"
|
||||||
|
(rv (apl-drop (apl-scalar -2) (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"drop all"
|
||||||
|
(rv (apl-drop (apl-scalar 5) (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"drop 0"
|
||||||
|
(rv (apl-drop (apl-scalar 0) (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 2 3 4 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"drop matrix 1 row shape"
|
||||||
|
(sh
|
||||||
|
(apl-drop
|
||||||
|
(make-array (list 2) (list 1 0))
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"drop matrix 1 row ravel"
|
||||||
|
(rv
|
||||||
|
(apl-drop
|
||||||
|
(make-array (list 2) (list 1 0))
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 4 5 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reverse vector"
|
||||||
|
(rv (apl-reverse (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 5 4 3 2 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reverse scalar identity"
|
||||||
|
(rv (apl-reverse (apl-scalar 42)))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reverse matrix last axis"
|
||||||
|
(rv (apl-reverse (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 3 2 1 6 5 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reverse-first matrix"
|
||||||
|
(rv (apl-reverse-first (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 4 5 6 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"reverse-first vector identity"
|
||||||
|
(rv (apl-reverse-first (make-array (list 4) (list 1 2 3 4))))
|
||||||
|
(list 4 3 2 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rotate vector left by 2"
|
||||||
|
(rv (apl-rotate (apl-scalar 2) (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 3 4 5 1 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rotate vector right by 1 (negative)"
|
||||||
|
(rv (apl-rotate (apl-scalar -1) (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 5 1 2 3 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rotate by 0 is identity"
|
||||||
|
(rv (apl-rotate (apl-scalar 0) (make-array (list 5) (list 1 2 3 4 5))))
|
||||||
|
(list 1 2 3 4 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rotate matrix last axis"
|
||||||
|
(rv
|
||||||
|
(apl-rotate (apl-scalar 1) (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 2 3 1 5 6 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"rotate-first matrix"
|
||||||
|
(rv
|
||||||
|
(apl-rotate-first
|
||||||
|
(apl-scalar 1)
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 4 5 6 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"cat v,v ravel"
|
||||||
|
(rv
|
||||||
|
(apl-catenate
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 2) (list 4 5))))
|
||||||
|
(list 1 2 3 4 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"cat v,v shape"
|
||||||
|
(sh
|
||||||
|
(apl-catenate
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 2) (list 4 5))))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"cat scalar,v"
|
||||||
|
(rv (apl-catenate (apl-scalar 99) (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 99 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"cat v,scalar"
|
||||||
|
(rv (apl-catenate (make-array (list 3) (list 1 2 3)) (apl-scalar 99)))
|
||||||
|
(list 1 2 3 99))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"cat matrix last-axis shape"
|
||||||
|
(sh
|
||||||
|
(apl-catenate
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||||
|
(make-array (list 2 2) (list 7 8 9 10))))
|
||||||
|
(list 2 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"cat matrix last-axis ravel"
|
||||||
|
(rv
|
||||||
|
(apl-catenate
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||||
|
(make-array (list 2 2) (list 7 8 9 10))))
|
||||||
|
(list 1 2 3 7 8 4 5 6 9 10))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"cat-first v,v shape"
|
||||||
|
(sh
|
||||||
|
(apl-catenate-first
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 2) (list 4 5))))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"cat-first matrix shape"
|
||||||
|
(sh
|
||||||
|
(apl-catenate-first
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||||
|
(make-array (list 3 3) (list 11 12 13 14 15 16 17 18 19))))
|
||||||
|
(list 5 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"cat-first matrix ravel"
|
||||||
|
(rv
|
||||||
|
(apl-catenate-first
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||||
|
(make-array (list 3 3) (list 11 12 13 14 15 16 17 18 19))))
|
||||||
|
(list 1 2 3 4 5 6 11 12 13 14 15 16 17 18 19))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"squad scalar into vector"
|
||||||
|
(rv
|
||||||
|
(apl-squad (apl-scalar 2) (make-array (list 5) (list 10 20 30 40 50))))
|
||||||
|
(list 20))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"squad first element"
|
||||||
|
(rv (apl-squad (apl-scalar 1) (make-array (list 3) (list 10 20 30))))
|
||||||
|
(list 10))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"squad last element"
|
||||||
|
(rv
|
||||||
|
(apl-squad (apl-scalar 5) (make-array (list 5) (list 10 20 30 40 50))))
|
||||||
|
(list 50))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"squad fully specified matrix element"
|
||||||
|
(rv
|
||||||
|
(apl-squad
|
||||||
|
(make-array (list 2) (list 2 3))
|
||||||
|
(make-array (list 3 4) (list 1 2 3 4 5 6 7 8 9 10 11 12))))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"squad partial row of matrix shape"
|
||||||
|
(sh
|
||||||
|
(apl-squad
|
||||||
|
(apl-scalar 2)
|
||||||
|
(make-array (list 3 4) (list 1 2 3 4 5 6 7 8 9 10 11 12))))
|
||||||
|
(list 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"squad partial row of matrix ravel"
|
||||||
|
(rv
|
||||||
|
(apl-squad
|
||||||
|
(apl-scalar 2)
|
||||||
|
(make-array (list 3 4) (list 1 2 3 4 5 6 7 8 9 10 11 12))))
|
||||||
|
(list 5 6 7 8))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"squad partial 3d slice shape"
|
||||||
|
(sh (apl-squad (apl-scalar 1) (make-array (list 2 3 4) (range 1 25))))
|
||||||
|
(list 3 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"grade-up basic"
|
||||||
|
(rv (apl-grade-up (make-array (list 5) (list 3 1 4 1 5))))
|
||||||
|
(list 2 4 1 3 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"grade-up shape"
|
||||||
|
(sh (apl-grade-up (make-array (list 4) (list 4 1 3 2))))
|
||||||
|
(list 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"grade-up no duplicates"
|
||||||
|
(rv (apl-grade-up (make-array (list 4) (list 4 1 3 2))))
|
||||||
|
(list 2 4 3 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"grade-up already sorted"
|
||||||
|
(rv (apl-grade-up (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"grade-up reverse sorted"
|
||||||
|
(rv (apl-grade-up (make-array (list 3) (list 3 2 1))))
|
||||||
|
(list 3 2 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"grade-down basic"
|
||||||
|
(rv (apl-grade-down (make-array (list 5) (list 3 1 4 1 5))))
|
||||||
|
(list 5 3 1 2 4))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"grade-down no duplicates"
|
||||||
|
(rv (apl-grade-down (make-array (list 4) (list 4 1 3 2))))
|
||||||
|
(list 1 3 4 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"grade-up single element"
|
||||||
|
(rv (apl-grade-up (make-array (list 1) (list 42))))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"enclose shape is scalar"
|
||||||
|
(sh (apl-enclose (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"enclose ravel length is 1"
|
||||||
|
(len (rv (apl-enclose (make-array (list 3) (list 1 2 3)))))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"enclose inner ravel"
|
||||||
|
(rv (first (rv (apl-enclose (make-array (list 3) (list 1 2 3))))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"disclose of enclose round-trips ravel"
|
||||||
|
(rv (apl-disclose (apl-enclose (make-array (list 3) (list 10 20 30)))))
|
||||||
|
(list 10 20 30))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"disclose of enclose round-trips shape"
|
||||||
|
(sh (apl-disclose (apl-enclose (make-array (list 3) (list 10 20 30)))))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"disclose scalar ravel"
|
||||||
|
(rv (apl-disclose (apl-scalar 42)))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"disclose vector ravel"
|
||||||
|
(rv (apl-disclose (make-array (list 3) (list 5 6 7))))
|
||||||
|
(list 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"disclose matrix returns first row"
|
||||||
|
(rv (apl-disclose (make-array (list 2 3) (list 1 2 3 4 5 6))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"member basic"
|
||||||
|
(rv
|
||||||
|
(apl-member
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 2) (list 2 3))))
|
||||||
|
(list 0 1 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"member all absent"
|
||||||
|
(rv
|
||||||
|
(apl-member
|
||||||
|
(make-array (list 3) (list 4 5 6))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 0 0 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"member scalar"
|
||||||
|
(rv (apl-member (apl-scalar 5) (make-array (list 3) (list 1 5 9))))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"member shape preserved"
|
||||||
|
(sh
|
||||||
|
(apl-member
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||||
|
(make-array (list 3) (list 1 3 5))))
|
||||||
|
(list 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"member matrix ravel"
|
||||||
|
(rv
|
||||||
|
(apl-member
|
||||||
|
(make-array (list 2 3) (list 1 2 3 4 5 6))
|
||||||
|
(make-array (list 3) (list 1 3 5))))
|
||||||
|
(list 1 0 1 0 1 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"index-of basic"
|
||||||
|
(rv
|
||||||
|
(apl-index-of
|
||||||
|
(make-array (list 4) (list 10 20 30 40))
|
||||||
|
(make-array (list 3) (list 20 40 10))))
|
||||||
|
(list 2 4 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"index-of not-found"
|
||||||
|
(rv
|
||||||
|
(apl-index-of
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 2) (list 5 2))))
|
||||||
|
(list 4 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"index-of scalar right"
|
||||||
|
(rv
|
||||||
|
(apl-index-of (make-array (list 3) (list 10 20 30)) (apl-scalar 20)))
|
||||||
|
(list 2))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"without basic"
|
||||||
|
(rv
|
||||||
|
(apl-without
|
||||||
|
(make-array (list 5) (list 1 2 3 4 5))
|
||||||
|
(make-array (list 2) (list 2 4))))
|
||||||
|
(list 1 3 5))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"without shape"
|
||||||
|
(sh
|
||||||
|
(apl-without
|
||||||
|
(make-array (list 5) (list 1 2 3 4 5))
|
||||||
|
(make-array (list 2) (list 2 4))))
|
||||||
|
(list 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"without nothing removed"
|
||||||
|
(rv
|
||||||
|
(apl-without
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 4 5 6))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"without all removed"
|
||||||
|
(rv
|
||||||
|
(apl-without
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 1 2 3))))
|
||||||
|
(list))
|
||||||
48
lib/apl/tests/system.sx
Normal file
48
lib/apl/tests/system.sx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
; Tests for APL ⎕ system functions.
|
||||||
|
|
||||||
|
(define mkrv (fn (arr) (get arr :ravel)))
|
||||||
|
(define mksh (fn (arr) (get arr :shape)))
|
||||||
|
|
||||||
|
(apl-test "⎕IO returns 1" (mkrv (apl-quad-io)) (list 1))
|
||||||
|
|
||||||
|
(apl-test "⎕ML returns 1" (mkrv (apl-quad-ml)) (list 1))
|
||||||
|
|
||||||
|
(apl-test "⎕FR returns 1248" (mkrv (apl-quad-fr)) (list 1248))
|
||||||
|
|
||||||
|
(apl-test "⎕TS shape is 7" (mksh (apl-quad-ts)) (list 7))
|
||||||
|
|
||||||
|
(apl-test "⎕TS year is 1970 default" (first (mkrv (apl-quad-ts))) 1970)
|
||||||
|
|
||||||
|
(apl-test "⎕FMT scalar 42" (apl-quad-fmt (apl-scalar 42)) "42")
|
||||||
|
|
||||||
|
(apl-test "⎕FMT scalar negative" (apl-quad-fmt (apl-scalar -7)) "-7")
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⎕FMT empty vector"
|
||||||
|
(apl-quad-fmt (make-array (list 0) (list)))
|
||||||
|
"")
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⎕FMT singleton vector"
|
||||||
|
(apl-quad-fmt (make-array (list 1) (list 42)))
|
||||||
|
"42")
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⎕FMT vector"
|
||||||
|
(apl-quad-fmt (make-array (list 5) (list 1 2 3 4 5)))
|
||||||
|
"1 2 3 4 5")
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⎕FMT matrix 2x3"
|
||||||
|
(apl-quad-fmt (make-array (list 2 3) (list 1 2 3 4 5 6)))
|
||||||
|
"1 2 3\n4 5 6\n")
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⎕← (print) returns its arg"
|
||||||
|
(mkrv (apl-quad-print (apl-scalar 99)))
|
||||||
|
(list 99))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"⎕← preserves shape"
|
||||||
|
(mksh (apl-quad-print (make-array (list 3) (list 1 2 3))))
|
||||||
|
(list 3))
|
||||||
156
lib/apl/tests/tradfn.sx
Normal file
156
lib/apl/tests/tradfn.sx
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
; Tests for apl-call-tradfn (manual structure construction).
|
||||||
|
|
||||||
|
(define mkrv (fn (arr) (get arr :ravel)))
|
||||||
|
(define mksh (fn (arr) (get arr :shape)))
|
||||||
|
(define mknum (fn (n) (list :num n)))
|
||||||
|
(define mknm (fn (s) (list :name s)))
|
||||||
|
(define mkfg (fn (g) (list :fn-glyph g)))
|
||||||
|
(define mkmon (fn (g a) (list :monad (mkfg g) a)))
|
||||||
|
(define mkdyd (fn (g l r) (list :dyad (mkfg g) l r)))
|
||||||
|
(define mkasg (fn (n e) (list :assign n e)))
|
||||||
|
(define mkbr (fn (e) (list :branch e)))
|
||||||
|
|
||||||
|
(define mkif (fn (c t e) (list :if c t e)))
|
||||||
|
|
||||||
|
(define mkwhile (fn (c b) (list :while c b)))
|
||||||
|
|
||||||
|
(define mkfor (fn (v i b) (list :for v i b)))
|
||||||
|
|
||||||
|
(define mksel (fn (v cs d) (list :select v cs d)))
|
||||||
|
|
||||||
|
(define mktrap (fn (codes t c) (list :trap codes t c)))
|
||||||
|
|
||||||
|
(define mkthr (fn (code msg) (list :throw code msg)))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn R←L+W simple add"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "+" (mknm "L") (mknm "W")))) :alpha "L"} (apl-scalar 5) (apl-scalar 7)))
|
||||||
|
(list 12))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn R←L×W"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "×" (mknm "L") (mknm "W")))) :alpha "L"} (apl-scalar 6) (apl-scalar 7)))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn monadic R←-W"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mkmon "-" (mknm "W")))) :alpha nil} nil (apl-scalar 9)))
|
||||||
|
(list -9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn →0 exits early"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mknm "W")) (mkbr (mknum 0)) (mkasg "R" (mknum 999))) :alpha nil} nil (apl-scalar 7)))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn branch to line 3 skips line 2"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkbr (mknum 3)) (mkasg "R" (mknum 999)) (mkasg "R" (mknum 42))) :alpha nil} nil (apl-scalar 0)))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn local var t←W+1; R←t×2"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "t" (mkdyd "+" (mknm "W") (mknum 1))) (mkasg "R" (mkdyd "×" (mknm "t") (mknum 2)))) :alpha nil} nil (apl-scalar 5)))
|
||||||
|
(list 12))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn vector args"
|
||||||
|
(mkrv
|
||||||
|
(apl-call-tradfn
|
||||||
|
{:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "+" (mknm "L") (mknm "W")))) :alpha "L"}
|
||||||
|
(make-array (list 3) (list 1 2 3))
|
||||||
|
(make-array (list 3) (list 10 20 30))))
|
||||||
|
(list 11 22 33))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn unset result returns nil"
|
||||||
|
(apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkbr (mknum 0))) :alpha nil} nil (apl-scalar 5))
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn run-off end returns result"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "×" (mknm "W") (mknum 3)))) :alpha nil} nil (apl-scalar 7)))
|
||||||
|
(list 21))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn loop sum 1+2+...+5 via branch"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "i" (mknum 1)) (mkasg "R" (mknum 0)) (mkasg "R" (mkdyd "+" (mknm "R") (mknm "i"))) (mkasg "i" (mkdyd "+" (mknm "i") (mknum 1))) (mkbr (mkdyd "×" (mkdyd "≤" (mknm "i") (mknm "W")) (mknum 3)))) :alpha nil} nil (apl-scalar 5)))
|
||||||
|
(list 15))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :If true branch"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkif (mkdyd ">" (mknm "W") (mknum 0)) (list (mkasg "R" (mknum 1))) (list (mkasg "R" (mknum 0))))) :alpha nil} nil (apl-scalar 5)))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :If false branch"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkif (mkdyd ">" (mknm "W") (mknum 100)) (list (mkasg "R" (mknum 1))) (list (mkasg "R" (mknum 0))))) :alpha nil} nil (apl-scalar 5)))
|
||||||
|
(list 0))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :While sum 1..N"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "i" (mknum 1)) (mkasg "R" (mknum 0)) (mkwhile (mkdyd "≤" (mknm "i") (mknm "W")) (list (mkasg "R" (mkdyd "+" (mknm "R") (mknm "i"))) (mkasg "i" (mkdyd "+" (mknm "i") (mknum 1)))))) :alpha nil} nil (apl-scalar 10)))
|
||||||
|
(list 55))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :For sum elements"
|
||||||
|
(mkrv
|
||||||
|
(apl-call-tradfn
|
||||||
|
{:result "R" :omega "W" :stmts (list (mkasg "R" (mknum 0)) (mkfor "x" (mknm "W") (list (mkasg "R" (mkdyd "+" (mknm "R") (mknm "x")))))) :alpha nil}
|
||||||
|
nil
|
||||||
|
(make-array (list 4) (list 10 20 30 40))))
|
||||||
|
(list 100))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :For with empty vector"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mknum 99)) (mkfor "x" (mknm "W") (list (mkasg "R" (mkdyd "+" (mknm "R") (mknm "x")))))) :alpha nil} nil (make-array (list 0) (list))))
|
||||||
|
(list 99))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :Select dispatch hit"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mksel (mknm "W") (list (list (mknum 1) (mkasg "R" (mknum 100))) (list (mknum 2) (mkasg "R" (mknum 200))) (list (mknum 3) (mkasg "R" (mknum 300)))) (list (mkasg "R" (mknum 0))))) :alpha nil} nil (apl-scalar 2)))
|
||||||
|
(list 200))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :Select default block"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mksel (mknm "W") (list (list (mknum 1) (mkasg "R" (mknum 100))) (list (mknum 2) (mkasg "R" (mknum 200)))) (list (mkasg "R" (mknum -1))))) :alpha nil} nil (apl-scalar 99)))
|
||||||
|
(list -1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn nested :If"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkif (mkdyd ">" (mknm "W") (mknum 0)) (list (mkif (mkdyd ">" (mknm "W") (mknum 10)) (list (mkasg "R" (mknum 2))) (list (mkasg "R" (mknum 1))))) (list (mkasg "R" (mknum 0))))) :alpha nil} nil (apl-scalar 5)))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :If assigns persist outside"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mknum 0)) (mkif (mkdyd ">" (mknm "W") (mknum 0)) (list (mkasg "R" (mknum 42))) (list)) (mkasg "R" (mkdyd "+" (mknm "R") (mknum 1)))) :alpha nil} nil (apl-scalar 5)))
|
||||||
|
(list 43))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :For factorial 1..5"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega "W" :stmts (list (mkasg "R" (mknum 1)) (mkfor "x" (mkmon "⍳" (mknm "W")) (list (mkasg "R" (mkdyd "×" (mknm "R") (mknm "x")))))) :alpha nil} nil (apl-scalar 5)))
|
||||||
|
(list 120))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :Trap normal flow (no error)"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 0) (list (mkasg "R" (mknum 99))) (list (mkasg "R" (mknum -1))))) :alpha nil} nil nil))
|
||||||
|
(list 99))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :Trap catches matching code"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 5) (list (mkthr 5 "boom")) (list (mkasg "R" (mknum 42))))) :alpha nil} nil nil))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :Trap catch-all (code 0)"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 0) (list (mkthr 99 "any")) (list (mkasg "R" (mknum 1))))) :alpha nil} nil nil))
|
||||||
|
(list 1))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :Trap catches one of many codes"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 1 2 3) (list (mkthr 2 "two")) (list (mkasg "R" (mknum 22))))) :alpha nil} nil nil))
|
||||||
|
(list 22))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"tradfn :Trap continues to next stmt after catch"
|
||||||
|
(mkrv (apl-call-tradfn {:result "R" :omega nil :stmts (list (mktrap (list 7) (list (mkthr 7 "c")) (list (mkasg "R" (mknum 10)))) (mkasg "R" (mkdyd "+" (mknm "R") (mknum 5)))) :alpha nil} nil nil))
|
||||||
|
(list 15))
|
||||||
81
lib/apl/tests/valence.sx
Normal file
81
lib/apl/tests/valence.sx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
; Tests for valence detection (apl-dfn-valence, apl-tradfn-valence)
|
||||||
|
; and unified dispatch (apl-call).
|
||||||
|
|
||||||
|
(define mkrv (fn (arr) (get arr :ravel)))
|
||||||
|
(define mknum (fn (n) (list :num n)))
|
||||||
|
(define mknm (fn (s) (list :name s)))
|
||||||
|
(define mkfg (fn (g) (list :fn-glyph g)))
|
||||||
|
(define mkmon (fn (g a) (list :monad (mkfg g) a)))
|
||||||
|
(define mkdyd (fn (g l r) (list :dyad (mkfg g) l r)))
|
||||||
|
(define mkasg (fn (n e) (list :assign n e)))
|
||||||
|
(define mkdfn (fn (stmts) (cons :dfn stmts)))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn-valence niladic body=42"
|
||||||
|
(apl-dfn-valence (mkdfn (list (mknum 42))))
|
||||||
|
:niladic)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn-valence monadic body=⍵+1"
|
||||||
|
(apl-dfn-valence (mkdfn (list (mkdyd "+" (mknm "⍵") (mknum 1)))))
|
||||||
|
:monadic)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn-valence dyadic body=⍺+⍵"
|
||||||
|
(apl-dfn-valence (mkdfn (list (mkdyd "+" (mknm "⍺") (mknm "⍵")))))
|
||||||
|
:dyadic)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn-valence dyadic mentions ⍺ via local"
|
||||||
|
(apl-dfn-valence (mkdfn (list (mkasg "x" (mknm "⍺")) (mknm "x"))))
|
||||||
|
:dyadic)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"dfn-valence dyadic deep nest"
|
||||||
|
(apl-dfn-valence
|
||||||
|
(mkdfn (list (mkmon "-" (mkdyd "×" (mknm "⍺") (mknm "⍵"))))))
|
||||||
|
:dyadic)
|
||||||
|
|
||||||
|
(apl-test "tradfn-valence niladic" (apl-tradfn-valence {:result "R" :omega nil :stmts (list) :alpha nil}) :niladic)
|
||||||
|
|
||||||
|
(apl-test "tradfn-valence monadic" (apl-tradfn-valence {:result "R" :omega "W" :stmts (list) :alpha nil}) :monadic)
|
||||||
|
|
||||||
|
(apl-test "tradfn-valence dyadic" (apl-tradfn-valence {:result "R" :omega "W" :stmts (list) :alpha "L"}) :dyadic)
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-call dfn niladic"
|
||||||
|
(mkrv (apl-call (mkdfn (list (mknum 42))) nil nil))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-call dfn monadic"
|
||||||
|
(mkrv
|
||||||
|
(apl-call
|
||||||
|
(mkdfn (list (mkdyd "+" (mknm "⍵") (mknum 1))))
|
||||||
|
nil
|
||||||
|
(apl-scalar 5)))
|
||||||
|
(list 6))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-call dfn dyadic"
|
||||||
|
(mkrv
|
||||||
|
(apl-call
|
||||||
|
(mkdfn (list (mkdyd "+" (mknm "⍺") (mknm "⍵"))))
|
||||||
|
(apl-scalar 3)
|
||||||
|
(apl-scalar 4)))
|
||||||
|
(list 7))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-call tradfn dyadic"
|
||||||
|
(mkrv (apl-call {:result "R" :omega "W" :stmts (list (mkasg "R" (mkdyd "×" (mknm "L") (mknm "W")))) :alpha "L"} (apl-scalar 6) (apl-scalar 7)))
|
||||||
|
(list 42))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-call tradfn monadic"
|
||||||
|
(mkrv (apl-call {:result "R" :omega "W" :stmts (list (mkasg "R" (mkmon "-" (mknm "W")))) :alpha nil} nil (apl-scalar 9)))
|
||||||
|
(list -9))
|
||||||
|
|
||||||
|
(apl-test
|
||||||
|
"apl-call tradfn niladic returns nil result"
|
||||||
|
(apl-call {:result "R" :omega nil :stmts (list) :alpha nil} nil nil)
|
||||||
|
nil)
|
||||||
198
lib/apl/tokenizer.sx
Normal file
198
lib/apl/tokenizer.sx
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
(define apl-glyph-set
|
||||||
|
(list "+" "-" "×" "÷" "*" "⍟" "⌈" "⌊" "|" "!" "?" "○" "~" "<" "≤" "=" "≥" ">" "≠"
|
||||||
|
"≢" "≡" "∊" "∧" "∨" "⍱" "⍲" "," "⍪" "⍴" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆"
|
||||||
|
"∪" "∩" "⍳" "⍸" "⌷" "⍋" "⍒" "⊥" "⊤" "⊣" "⊢" "⍎" "⍕"
|
||||||
|
"⍺" "⍵" "∇" "/" "⌿" "\\" "⍀" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@" "¯"))
|
||||||
|
|
||||||
|
(define apl-glyph?
|
||||||
|
(fn (ch)
|
||||||
|
(some (fn (g) (= g ch)) apl-glyph-set)))
|
||||||
|
|
||||||
|
(define apl-digit?
|
||||||
|
(fn (ch)
|
||||||
|
(and (string? ch) (>= ch "0") (<= ch "9"))))
|
||||||
|
|
||||||
|
(define apl-alpha?
|
||||||
|
(fn (ch)
|
||||||
|
(and (string? ch)
|
||||||
|
(or (and (>= ch "a") (<= ch "z"))
|
||||||
|
(and (>= ch "A") (<= ch "Z"))
|
||||||
|
(= ch "_")))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-tokenize
|
||||||
|
(fn
|
||||||
|
(source)
|
||||||
|
(let
|
||||||
|
((pos 0) (src-len (len source)) (tokens (list)))
|
||||||
|
(define tok-push! (fn (type value) (append! tokens {:value value :type type})))
|
||||||
|
(define
|
||||||
|
cur-sw?
|
||||||
|
(fn
|
||||||
|
(ch)
|
||||||
|
(and (< pos src-len) (starts-with? (slice source pos) ch))))
|
||||||
|
(define cur-byte (fn () (if (< pos src-len) (nth source pos) nil)))
|
||||||
|
(define advance! (fn () (set! pos (+ pos 1))))
|
||||||
|
(define consume! (fn (ch) (set! pos (+ pos (len ch)))))
|
||||||
|
(define
|
||||||
|
find-glyph
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(let
|
||||||
|
((rem (slice source pos)))
|
||||||
|
(let
|
||||||
|
((matches (filter (fn (g) (starts-with? rem g)) apl-glyph-set)))
|
||||||
|
(if (> (len matches) 0) (first matches) nil)))))
|
||||||
|
(define
|
||||||
|
read-digits!
|
||||||
|
(fn
|
||||||
|
(acc)
|
||||||
|
(if
|
||||||
|
(and (< pos src-len) (apl-digit? (cur-byte)))
|
||||||
|
(let
|
||||||
|
((ch (cur-byte)))
|
||||||
|
(begin (advance!) (read-digits! (str acc ch))))
|
||||||
|
acc)))
|
||||||
|
(define
|
||||||
|
read-ident-cont!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(and
|
||||||
|
(< pos src-len)
|
||||||
|
(let
|
||||||
|
((ch (cur-byte)))
|
||||||
|
(or (apl-alpha? ch) (apl-digit? ch))))
|
||||||
|
(begin (advance!) (read-ident-cont!)))))
|
||||||
|
(define
|
||||||
|
read-string!
|
||||||
|
(fn
|
||||||
|
(acc)
|
||||||
|
(cond
|
||||||
|
((>= pos src-len) acc)
|
||||||
|
((cur-sw? "'")
|
||||||
|
(if
|
||||||
|
(and (< (+ pos 1) src-len) (cur-sw? "'"))
|
||||||
|
(begin (advance!) (advance!) (read-string! (str acc "'")))
|
||||||
|
(begin (advance!) acc)))
|
||||||
|
(true
|
||||||
|
(let
|
||||||
|
((ch (cur-byte)))
|
||||||
|
(begin (advance!) (read-string! (str acc ch))))))))
|
||||||
|
(define
|
||||||
|
skip-line!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(and (< pos src-len) (not (cur-sw? "\n")))
|
||||||
|
(begin (advance!) (skip-line!)))))
|
||||||
|
(define
|
||||||
|
scan!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(< pos src-len)
|
||||||
|
(let
|
||||||
|
((ch (cur-byte)))
|
||||||
|
(cond
|
||||||
|
((or (= ch " ") (= ch "\t") (= ch "\r"))
|
||||||
|
(begin (advance!) (scan!)))
|
||||||
|
((= ch "\n")
|
||||||
|
(begin (advance!) (tok-push! :newline nil) (scan!)))
|
||||||
|
((cur-sw? "⍝") (begin (skip-line!) (scan!)))
|
||||||
|
((cur-sw? "⋄")
|
||||||
|
(begin (consume! "⋄") (tok-push! :diamond nil) (scan!)))
|
||||||
|
((= ch "(")
|
||||||
|
(begin (advance!) (tok-push! :lparen nil) (scan!)))
|
||||||
|
((= ch ")")
|
||||||
|
(begin (advance!) (tok-push! :rparen nil) (scan!)))
|
||||||
|
((= ch "[")
|
||||||
|
(begin (advance!) (tok-push! :lbracket nil) (scan!)))
|
||||||
|
((= ch "]")
|
||||||
|
(begin (advance!) (tok-push! :rbracket nil) (scan!)))
|
||||||
|
((= ch "{")
|
||||||
|
(begin (advance!) (tok-push! :lbrace nil) (scan!)))
|
||||||
|
((= ch "}")
|
||||||
|
(begin (advance!) (tok-push! :rbrace nil) (scan!)))
|
||||||
|
((= ch ";")
|
||||||
|
(begin (advance!) (tok-push! :semi nil) (scan!)))
|
||||||
|
((cur-sw? "←")
|
||||||
|
(begin (consume! "←") (tok-push! :assign nil) (scan!)))
|
||||||
|
((= ch ":")
|
||||||
|
(let
|
||||||
|
((start pos))
|
||||||
|
(begin
|
||||||
|
(advance!)
|
||||||
|
(if
|
||||||
|
(and (< pos src-len) (apl-alpha? (cur-byte)))
|
||||||
|
(begin
|
||||||
|
(read-ident-cont!)
|
||||||
|
(tok-push! :keyword (slice source start pos)))
|
||||||
|
(tok-push! :colon nil))
|
||||||
|
(scan!))))
|
||||||
|
((and (cur-sw? "¯") (< (+ pos (len "¯")) src-len) (apl-digit? (nth source (+ pos (len "¯")))))
|
||||||
|
(begin
|
||||||
|
(consume! "¯")
|
||||||
|
(let
|
||||||
|
((digits (read-digits! "")))
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(< pos src-len)
|
||||||
|
(= (cur-byte) ".")
|
||||||
|
(< (+ pos 1) src-len)
|
||||||
|
(apl-digit? (nth source (+ pos 1))))
|
||||||
|
(begin
|
||||||
|
(advance!)
|
||||||
|
(let
|
||||||
|
((frac (read-digits! "")))
|
||||||
|
(tok-push!
|
||||||
|
:num (- 0 (string->number (str digits "." frac))))))
|
||||||
|
(tok-push! :num (- 0 (parse-int digits 0)))))
|
||||||
|
(scan!)))
|
||||||
|
((apl-digit? ch)
|
||||||
|
(begin
|
||||||
|
(let
|
||||||
|
((digits (read-digits! "")))
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(< pos src-len)
|
||||||
|
(= (cur-byte) ".")
|
||||||
|
(< (+ pos 1) src-len)
|
||||||
|
(apl-digit? (nth source (+ pos 1))))
|
||||||
|
(begin
|
||||||
|
(advance!)
|
||||||
|
(let
|
||||||
|
((frac (read-digits! "")))
|
||||||
|
(tok-push!
|
||||||
|
:num (string->number (str digits "." frac)))))
|
||||||
|
(tok-push! :num (parse-int digits 0))))
|
||||||
|
(scan!)))
|
||||||
|
((= ch "'")
|
||||||
|
(begin
|
||||||
|
(advance!)
|
||||||
|
(let ((s (read-string! ""))) (tok-push! :str s))
|
||||||
|
(scan!)))
|
||||||
|
((or (apl-alpha? ch) (cur-sw? "⎕"))
|
||||||
|
(let
|
||||||
|
((start pos))
|
||||||
|
(begin
|
||||||
|
(if
|
||||||
|
(cur-sw? "⎕")
|
||||||
|
(begin
|
||||||
|
(consume! "⎕")
|
||||||
|
(if
|
||||||
|
(and (< pos src-len) (cur-sw? "←"))
|
||||||
|
(consume! "←")
|
||||||
|
(read-ident-cont!)))
|
||||||
|
(begin (advance!) (read-ident-cont!)))
|
||||||
|
(tok-push! :name (slice source start pos))
|
||||||
|
(scan!))))
|
||||||
|
(true
|
||||||
|
(let
|
||||||
|
((g (find-glyph)))
|
||||||
|
(if
|
||||||
|
g
|
||||||
|
(begin (consume! g) (tok-push! :glyph g) (scan!))
|
||||||
|
(begin (advance!) (scan!))))))))))
|
||||||
|
(scan!)
|
||||||
|
tokens)))
|
||||||
592
lib/apl/transpile.sx
Normal file
592
lib/apl/transpile.sx
Normal file
@@ -0,0 +1,592 @@
|
|||||||
|
; APL transpile / AST evaluator
|
||||||
|
;
|
||||||
|
; Walks parsed AST nodes and evaluates against the runtime.
|
||||||
|
; Entry points:
|
||||||
|
; apl-eval-ast : node × env → value
|
||||||
|
; apl-eval-stmts : stmt-list × env → value (handles guards, locals, ⍺← default)
|
||||||
|
; apl-call-dfn : dfn-ast × ⍺ × ⍵ → value (dyadic)
|
||||||
|
; apl-call-dfn-m : dfn-ast × ⍵ → value (monadic)
|
||||||
|
;
|
||||||
|
; Env is a dict; ⍺ stored under "alpha", ⍵ under "omega",
|
||||||
|
; the dfn-ast itself under "nabla" (for ∇ recursion),
|
||||||
|
; user names under their literal name.
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-monadic-fn
|
||||||
|
(fn
|
||||||
|
(g)
|
||||||
|
(cond
|
||||||
|
((= g "+") apl-plus-m)
|
||||||
|
((= g "-") apl-neg-m)
|
||||||
|
((= g "×") apl-signum)
|
||||||
|
((= g "÷") apl-recip)
|
||||||
|
((= g "⌈") apl-ceil)
|
||||||
|
((= g "⌊") apl-floor)
|
||||||
|
((= g "⍳") apl-iota)
|
||||||
|
((= g "|") apl-abs)
|
||||||
|
((= g "*") apl-exp)
|
||||||
|
((= g "⍟") apl-ln)
|
||||||
|
((= g "!") apl-fact)
|
||||||
|
((= g "○") apl-pi-times)
|
||||||
|
((= g "~") apl-not)
|
||||||
|
((= g "≢") apl-tally)
|
||||||
|
((= g "⍴") apl-shape)
|
||||||
|
((= g "≡") apl-depth)
|
||||||
|
((= g "⊂") apl-enclose)
|
||||||
|
((= g "⊃") apl-disclose)
|
||||||
|
((= g ",") apl-ravel)
|
||||||
|
((= g "⌽") apl-reverse)
|
||||||
|
((= g "⊖") apl-reverse-first)
|
||||||
|
((= g "⍋") apl-grade-up)
|
||||||
|
((= g "⍒") apl-grade-down)
|
||||||
|
((= g "?") apl-roll)
|
||||||
|
((= g "⍉") apl-transpose)
|
||||||
|
((= g "⊢") (fn (a) a))
|
||||||
|
((= g "⊣") (fn (a) a))
|
||||||
|
((= g "⍕") apl-quad-fmt)
|
||||||
|
((= g "⎕FMT") apl-quad-fmt)
|
||||||
|
((= g "⎕←") apl-quad-print)
|
||||||
|
((= g "⍸") apl-where)
|
||||||
|
((= g "∪") apl-unique)
|
||||||
|
((= g "⍎") apl-execute)
|
||||||
|
(else (error "no monadic fn for glyph")))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-dyadic-fn
|
||||||
|
(fn
|
||||||
|
(g)
|
||||||
|
(cond
|
||||||
|
((= g "+") apl-add)
|
||||||
|
((= g "-") apl-sub)
|
||||||
|
((= g "×") apl-mul)
|
||||||
|
((= g "÷") apl-div)
|
||||||
|
((= g "⌈") apl-max)
|
||||||
|
((= g "⌊") apl-min)
|
||||||
|
((= g "*") apl-pow)
|
||||||
|
((= g "⍟") apl-log)
|
||||||
|
((= g "|") apl-mod)
|
||||||
|
((= g "!") apl-binomial)
|
||||||
|
((= g "○") apl-trig)
|
||||||
|
((= g "<") apl-lt)
|
||||||
|
((= g "≤") apl-le)
|
||||||
|
((= g "=") apl-eq)
|
||||||
|
((= g "≥") apl-ge)
|
||||||
|
((= g ">") apl-gt)
|
||||||
|
((= g "≠") apl-ne)
|
||||||
|
((= g "∧") apl-and)
|
||||||
|
((= g "∨") apl-or)
|
||||||
|
((= g "⍱") apl-nor)
|
||||||
|
((= g "⍲") apl-nand)
|
||||||
|
((= g ",") apl-catenate)
|
||||||
|
((= g "⍪") apl-catenate-first)
|
||||||
|
((= g "⍴") apl-reshape)
|
||||||
|
((= g "↑") apl-take)
|
||||||
|
((= g "↓") apl-drop)
|
||||||
|
((= g "⌷") apl-squad)
|
||||||
|
((= g "⌽") apl-rotate)
|
||||||
|
((= g "⊖") apl-rotate-first)
|
||||||
|
((= g "∊") apl-member)
|
||||||
|
((= g "⍳") apl-index-of)
|
||||||
|
((= g "~") apl-without)
|
||||||
|
((= g "/") apl-compress)
|
||||||
|
((= g "⌿") apl-compress-first)
|
||||||
|
((= g "⍉") apl-transpose-dyadic)
|
||||||
|
((= g "⊢") (fn (a b) b))
|
||||||
|
((= g "⊣") (fn (a b) a))
|
||||||
|
((= g "⍸") apl-interval-index)
|
||||||
|
((= g "∪") apl-union)
|
||||||
|
((= g "∩") apl-intersect)
|
||||||
|
((= g "⊥") apl-decode)
|
||||||
|
((= g "⊤") apl-encode)
|
||||||
|
((= g "⊆") apl-partition)
|
||||||
|
(else (error "no dyadic fn for glyph")))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-truthy?
|
||||||
|
(fn
|
||||||
|
(v)
|
||||||
|
(let
|
||||||
|
((rv (get v :ravel)))
|
||||||
|
(if (and (= (len rv) 1) (= (first rv) 0)) false true))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-eval-ast
|
||||||
|
(fn
|
||||||
|
(node env)
|
||||||
|
(let
|
||||||
|
((tag (first node)))
|
||||||
|
(cond
|
||||||
|
((= tag :num) (apl-scalar (nth node 1)))
|
||||||
|
((= tag :str)
|
||||||
|
(let
|
||||||
|
((s (nth node 1)))
|
||||||
|
(if
|
||||||
|
(= (len s) 1)
|
||||||
|
(apl-scalar s)
|
||||||
|
(make-array
|
||||||
|
(list (len s))
|
||||||
|
(map (fn (i) (slice s i (+ i 1))) (range 0 (len s)))))))
|
||||||
|
((= tag :vec)
|
||||||
|
(let
|
||||||
|
((items (rest node)))
|
||||||
|
(let
|
||||||
|
((vals (map (fn (n) (apl-eval-ast n env)) items)))
|
||||||
|
(make-array
|
||||||
|
(list (len vals))
|
||||||
|
(map
|
||||||
|
(fn
|
||||||
|
(v)
|
||||||
|
(if
|
||||||
|
(= (len (get v :shape)) 0)
|
||||||
|
(first (get v :ravel))
|
||||||
|
v))
|
||||||
|
vals)))))
|
||||||
|
((= tag :name)
|
||||||
|
(let
|
||||||
|
((nm (nth node 1)))
|
||||||
|
(cond
|
||||||
|
((= nm "⍺")
|
||||||
|
(let
|
||||||
|
((v (get env "⍺")))
|
||||||
|
(if (= v nil) (get env "alpha") v)))
|
||||||
|
((= nm "⍵")
|
||||||
|
(let
|
||||||
|
((v (get env "⍵")))
|
||||||
|
(if (= v nil) (get env "omega") v)))
|
||||||
|
((= nm "⎕IO") (apl-quad-io))
|
||||||
|
((= nm "⎕ML") (apl-quad-ml))
|
||||||
|
((= nm "⎕FR") (apl-quad-fr))
|
||||||
|
((= nm "⎕TS") (apl-quad-ts))
|
||||||
|
(else (get env nm)))))
|
||||||
|
((= tag :monad)
|
||||||
|
(let
|
||||||
|
((fn-node (nth node 1)) (arg (nth node 2)))
|
||||||
|
(if
|
||||||
|
(and (= (first fn-node) :fn-glyph) (= (nth fn-node 1) "∇"))
|
||||||
|
(apl-call-dfn-m (get env "nabla") (apl-eval-ast arg env))
|
||||||
|
(let
|
||||||
|
((arg-val (apl-eval-ast arg env)))
|
||||||
|
(let
|
||||||
|
((new-env (if (and (list? arg) (> (len arg) 0) (= (first arg) :assign-expr)) (assoc env (nth arg 1) arg-val) env)))
|
||||||
|
((apl-resolve-monadic fn-node new-env) arg-val))))))
|
||||||
|
((= tag :dyad)
|
||||||
|
(let
|
||||||
|
((fn-node (nth node 1))
|
||||||
|
(lhs (nth node 2))
|
||||||
|
(rhs (nth node 3)))
|
||||||
|
(if
|
||||||
|
(and (= (first fn-node) :fn-glyph) (= (nth fn-node 1) "∇"))
|
||||||
|
(apl-call-dfn
|
||||||
|
(get env "nabla")
|
||||||
|
(apl-eval-ast lhs env)
|
||||||
|
(apl-eval-ast rhs env))
|
||||||
|
(let
|
||||||
|
((rhs-val (apl-eval-ast rhs env)))
|
||||||
|
(let
|
||||||
|
((new-env (if (and (list? rhs) (> (len rhs) 0) (= (first rhs) :assign-expr)) (assoc env (nth rhs 1) rhs-val) env)))
|
||||||
|
((apl-resolve-dyadic fn-node new-env)
|
||||||
|
(apl-eval-ast lhs new-env)
|
||||||
|
rhs-val))))))
|
||||||
|
((= tag :program) (apl-eval-stmts (rest node) env))
|
||||||
|
((= tag :dfn) node)
|
||||||
|
((= tag :bracket)
|
||||||
|
(let
|
||||||
|
((arr-expr (nth node 1)) (axis-exprs (rest (rest node))))
|
||||||
|
(let
|
||||||
|
((arr (apl-eval-ast arr-expr env))
|
||||||
|
(axes
|
||||||
|
(map
|
||||||
|
(fn (a) (if (= a :all) nil (apl-eval-ast a env)))
|
||||||
|
axis-exprs)))
|
||||||
|
(apl-bracket-multi axes arr))))
|
||||||
|
((= tag :assign-expr) (apl-eval-ast (nth node 2) env))
|
||||||
|
((= tag :assign) (apl-eval-ast (nth node 2) env))
|
||||||
|
(else (error (list "apl-eval-ast: unknown node tag" tag node)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-eval-stmts
|
||||||
|
(fn
|
||||||
|
(stmts env)
|
||||||
|
(if
|
||||||
|
(= (len stmts) 0)
|
||||||
|
nil
|
||||||
|
(let
|
||||||
|
((stmt (first stmts)) (more (rest stmts)))
|
||||||
|
(let
|
||||||
|
((tag (first stmt)))
|
||||||
|
(cond
|
||||||
|
((= tag :guard)
|
||||||
|
(let
|
||||||
|
((cond-val (apl-eval-ast (nth stmt 1) env)))
|
||||||
|
(if
|
||||||
|
(apl-truthy? cond-val)
|
||||||
|
(apl-eval-ast (nth stmt 2) env)
|
||||||
|
(apl-eval-stmts more env))))
|
||||||
|
((and (= tag :assign) (= (nth stmt 1) "⍺"))
|
||||||
|
(if
|
||||||
|
(get env "alpha")
|
||||||
|
(apl-eval-stmts more env)
|
||||||
|
(let
|
||||||
|
((v (apl-eval-ast (nth stmt 2) env)))
|
||||||
|
(apl-eval-stmts more (assoc env "alpha" v)))))
|
||||||
|
((= tag :assign)
|
||||||
|
(let
|
||||||
|
((v (apl-eval-ast (nth stmt 2) env)))
|
||||||
|
(apl-eval-stmts more (assoc env (nth stmt 1) v))))
|
||||||
|
((= (len more) 0) (apl-eval-ast stmt env))
|
||||||
|
(else (begin (apl-eval-ast stmt env) (apl-eval-stmts more env)))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-call-dfn
|
||||||
|
(fn
|
||||||
|
(dfn-ast alpha omega)
|
||||||
|
(let
|
||||||
|
((stmts (rest dfn-ast)) (env {:omega omega :nabla dfn-ast :alpha alpha}))
|
||||||
|
(apl-eval-stmts stmts env))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-call-dfn-m
|
||||||
|
(fn
|
||||||
|
(dfn-ast omega)
|
||||||
|
(let
|
||||||
|
((stmts (rest dfn-ast)) (env {:omega omega :nabla dfn-ast :alpha nil}))
|
||||||
|
(apl-eval-stmts stmts env))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-tradfn-eval-block
|
||||||
|
(fn
|
||||||
|
(stmts env)
|
||||||
|
(if
|
||||||
|
(= (len stmts) 0)
|
||||||
|
env
|
||||||
|
(let
|
||||||
|
((stmt (first stmts)))
|
||||||
|
(apl-tradfn-eval-block (rest stmts) (apl-tradfn-eval-stmt stmt env))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-tradfn-eval-while
|
||||||
|
(fn
|
||||||
|
(cond-expr body env)
|
||||||
|
(let
|
||||||
|
((cond-val (apl-eval-ast cond-expr env)))
|
||||||
|
(if
|
||||||
|
(apl-truthy? cond-val)
|
||||||
|
(apl-tradfn-eval-while
|
||||||
|
cond-expr
|
||||||
|
body
|
||||||
|
(apl-tradfn-eval-block body env))
|
||||||
|
env))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-tradfn-eval-for
|
||||||
|
(fn
|
||||||
|
(var-name items body env)
|
||||||
|
(if
|
||||||
|
(= (len items) 0)
|
||||||
|
env
|
||||||
|
(let
|
||||||
|
((env-with-var (assoc env var-name (apl-scalar (first items)))))
|
||||||
|
(apl-tradfn-eval-for
|
||||||
|
var-name
|
||||||
|
(rest items)
|
||||||
|
body
|
||||||
|
(apl-tradfn-eval-block body env-with-var))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-tradfn-eval-select
|
||||||
|
(fn
|
||||||
|
(val cases default-block env)
|
||||||
|
(if
|
||||||
|
(= (len cases) 0)
|
||||||
|
(apl-tradfn-eval-block default-block env)
|
||||||
|
(let
|
||||||
|
((c (first cases)))
|
||||||
|
(let
|
||||||
|
((case-val (apl-eval-ast (first c) env)))
|
||||||
|
(if
|
||||||
|
(= (first (get val :ravel)) (first (get case-val :ravel)))
|
||||||
|
(apl-tradfn-eval-block (rest c) env)
|
||||||
|
(apl-tradfn-eval-select val (rest cases) default-block env)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-tradfn-eval-stmt
|
||||||
|
(fn
|
||||||
|
(stmt env)
|
||||||
|
(let
|
||||||
|
((tag (first stmt)))
|
||||||
|
(cond
|
||||||
|
((= tag :assign)
|
||||||
|
(assoc env (nth stmt 1) (apl-eval-ast (nth stmt 2) env)))
|
||||||
|
((= tag :if)
|
||||||
|
(let
|
||||||
|
((cond-val (apl-eval-ast (nth stmt 1) env)))
|
||||||
|
(if
|
||||||
|
(apl-truthy? cond-val)
|
||||||
|
(apl-tradfn-eval-block (nth stmt 2) env)
|
||||||
|
(apl-tradfn-eval-block (nth stmt 3) env))))
|
||||||
|
((= tag :while)
|
||||||
|
(apl-tradfn-eval-while (nth stmt 1) (nth stmt 2) env))
|
||||||
|
((= tag :for)
|
||||||
|
(let
|
||||||
|
((iter-val (apl-eval-ast (nth stmt 2) env)))
|
||||||
|
(apl-tradfn-eval-for
|
||||||
|
(nth stmt 1)
|
||||||
|
(get iter-val :ravel)
|
||||||
|
(nth stmt 3)
|
||||||
|
env)))
|
||||||
|
((= tag :select)
|
||||||
|
(let
|
||||||
|
((val (apl-eval-ast (nth stmt 1) env)))
|
||||||
|
(apl-tradfn-eval-select val (nth stmt 2) (nth stmt 3) env)))
|
||||||
|
((= tag :trap)
|
||||||
|
(let
|
||||||
|
((codes (nth stmt 1))
|
||||||
|
(try-block (nth stmt 2))
|
||||||
|
(catch-block (nth stmt 3)))
|
||||||
|
(guard
|
||||||
|
(e
|
||||||
|
((apl-trap-matches? codes e)
|
||||||
|
(apl-tradfn-eval-block catch-block env)))
|
||||||
|
(apl-tradfn-eval-block try-block env))))
|
||||||
|
((= tag :throw) (apl-throw (nth stmt 1) (nth stmt 2)))
|
||||||
|
(else (begin (apl-eval-ast stmt env) env))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-tradfn-loop
|
||||||
|
(fn
|
||||||
|
(stmts line env result-name)
|
||||||
|
(cond
|
||||||
|
((= line 0) (get env result-name))
|
||||||
|
((> line (len stmts)) (get env result-name))
|
||||||
|
(else
|
||||||
|
(let
|
||||||
|
((stmt (nth stmts (- line 1))))
|
||||||
|
(let
|
||||||
|
((tag (first stmt)))
|
||||||
|
(cond
|
||||||
|
((= tag :branch)
|
||||||
|
(let
|
||||||
|
((target (apl-eval-ast (nth stmt 1) env)))
|
||||||
|
(let
|
||||||
|
((target-num (first (get target :ravel))))
|
||||||
|
(apl-tradfn-loop stmts target-num env result-name))))
|
||||||
|
(else
|
||||||
|
(apl-tradfn-loop
|
||||||
|
stmts
|
||||||
|
(+ line 1)
|
||||||
|
(apl-tradfn-eval-stmt stmt env)
|
||||||
|
result-name)))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-call-tradfn
|
||||||
|
(fn
|
||||||
|
(tradfn alpha omega)
|
||||||
|
(let
|
||||||
|
((stmts (get tradfn :stmts))
|
||||||
|
(result-name (get tradfn :result))
|
||||||
|
(alpha-name (get tradfn :alpha))
|
||||||
|
(omega-name (get tradfn :omega)))
|
||||||
|
(let
|
||||||
|
((env-a (if alpha-name (assoc {} alpha-name alpha) {})))
|
||||||
|
(let
|
||||||
|
((env-ao (if omega-name (assoc env-a omega-name omega) env-a)))
|
||||||
|
(apl-tradfn-loop stmts 1 env-ao result-name))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-ast-mentions-list?
|
||||||
|
(fn
|
||||||
|
(lst target)
|
||||||
|
(if
|
||||||
|
(= (len lst) 0)
|
||||||
|
false
|
||||||
|
(if
|
||||||
|
(apl-ast-mentions? (first lst) target)
|
||||||
|
true
|
||||||
|
(apl-ast-mentions-list? (rest lst) target)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-ast-mentions?
|
||||||
|
(fn
|
||||||
|
(node target)
|
||||||
|
(cond
|
||||||
|
((not (list? node)) false)
|
||||||
|
((= (len node) 0) false)
|
||||||
|
((and (= (first node) :name) (= (nth node 1) target)) true)
|
||||||
|
(else (apl-ast-mentions-list? (rest node) target)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-dfn-valence
|
||||||
|
(fn
|
||||||
|
(dfn-ast)
|
||||||
|
(let
|
||||||
|
((body (rest dfn-ast)))
|
||||||
|
(cond
|
||||||
|
((apl-ast-mentions-list? body "⍺") :dyadic)
|
||||||
|
((apl-ast-mentions-list? body "⍵") :monadic)
|
||||||
|
(else :niladic)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-tradfn-valence
|
||||||
|
(fn
|
||||||
|
(tradfn)
|
||||||
|
(cond
|
||||||
|
((get tradfn :alpha) :dyadic)
|
||||||
|
((get tradfn :omega) :monadic)
|
||||||
|
(else :niladic))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-call
|
||||||
|
(fn
|
||||||
|
(f alpha omega)
|
||||||
|
(cond
|
||||||
|
((and (list? f) (> (len f) 0) (= (first f) :dfn))
|
||||||
|
(if alpha (apl-call-dfn f alpha omega) (apl-call-dfn-m f omega)))
|
||||||
|
((dict? f) (apl-call-tradfn f alpha omega))
|
||||||
|
(else (error "apl-call: not a function")))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-resolve-monadic
|
||||||
|
(fn
|
||||||
|
(fn-node env)
|
||||||
|
(let
|
||||||
|
((tag (first fn-node)))
|
||||||
|
(cond
|
||||||
|
((= tag :fn-glyph) (apl-monadic-fn (nth fn-node 1)))
|
||||||
|
((= tag :derived-fn)
|
||||||
|
(let
|
||||||
|
((op (nth fn-node 1)) (inner (nth fn-node 2)))
|
||||||
|
(cond
|
||||||
|
((= op "/")
|
||||||
|
(let
|
||||||
|
((f (apl-resolve-dyadic inner env)))
|
||||||
|
(fn (arr) (apl-reduce f arr))))
|
||||||
|
((= op "⌿")
|
||||||
|
(let
|
||||||
|
((f (apl-resolve-dyadic inner env)))
|
||||||
|
(fn (arr) (apl-reduce-first f arr))))
|
||||||
|
((= op "\\")
|
||||||
|
(let
|
||||||
|
((f (apl-resolve-dyadic inner env)))
|
||||||
|
(fn (arr) (apl-scan f arr))))
|
||||||
|
((= op "⍀")
|
||||||
|
(let
|
||||||
|
((f (apl-resolve-dyadic inner env)))
|
||||||
|
(fn (arr) (apl-scan-first f arr))))
|
||||||
|
((= op "¨")
|
||||||
|
(let
|
||||||
|
((f (apl-resolve-monadic inner env)))
|
||||||
|
(fn (arr) (apl-each f arr))))
|
||||||
|
((= op "⍨")
|
||||||
|
(let
|
||||||
|
((f (apl-resolve-dyadic inner env)))
|
||||||
|
(fn (arr) (apl-commute f arr))))
|
||||||
|
(else (error "apl-resolve-monadic: unsupported op")))))
|
||||||
|
((= tag :fn-name)
|
||||||
|
(let
|
||||||
|
((nm (nth fn-node 1)))
|
||||||
|
(let
|
||||||
|
((bound (get env nm)))
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(list? bound)
|
||||||
|
(> (len bound) 0)
|
||||||
|
(= (first bound) :dfn))
|
||||||
|
(fn (arg) (apl-call-dfn-m bound arg))
|
||||||
|
(error "apl-resolve-monadic: name not bound to dfn")))))
|
||||||
|
((= tag :train)
|
||||||
|
(let
|
||||||
|
((fns (rest fn-node)))
|
||||||
|
(let
|
||||||
|
((n (len fns)))
|
||||||
|
(cond
|
||||||
|
((= n 2)
|
||||||
|
(let
|
||||||
|
((g (apl-resolve-monadic (nth fns 0) env))
|
||||||
|
(h (apl-resolve-monadic (nth fns 1) env)))
|
||||||
|
(fn (arg) (g (h arg)))))
|
||||||
|
((= n 3)
|
||||||
|
(let
|
||||||
|
((f (apl-resolve-monadic (nth fns 0) env))
|
||||||
|
(g (apl-resolve-dyadic (nth fns 1) env))
|
||||||
|
(h (apl-resolve-monadic (nth fns 2) env)))
|
||||||
|
(fn (arg) (g (f arg) (h arg)))))
|
||||||
|
(else (error "monadic train arity not 2 or 3"))))))
|
||||||
|
(else (error "apl-resolve-monadic: unknown fn-node tag"))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-resolve-dyadic
|
||||||
|
(fn
|
||||||
|
(fn-node env)
|
||||||
|
(let
|
||||||
|
((tag (first fn-node)))
|
||||||
|
(cond
|
||||||
|
((= tag :fn-glyph) (apl-dyadic-fn (nth fn-node 1)))
|
||||||
|
((= tag :derived-fn)
|
||||||
|
(let
|
||||||
|
((op (nth fn-node 1)) (inner (nth fn-node 2)))
|
||||||
|
(cond
|
||||||
|
((= op "¨")
|
||||||
|
(let
|
||||||
|
((f (apl-resolve-dyadic inner env)))
|
||||||
|
(fn (a b) (apl-each-dyadic f a b))))
|
||||||
|
((= op "⍨")
|
||||||
|
(let
|
||||||
|
((f (apl-resolve-dyadic inner env)))
|
||||||
|
(fn (a b) (apl-commute-dyadic f a b))))
|
||||||
|
(else (error "apl-resolve-dyadic: unsupported op")))))
|
||||||
|
((= tag :fn-name)
|
||||||
|
(let
|
||||||
|
((nm (nth fn-node 1)))
|
||||||
|
(let
|
||||||
|
((bound (get env nm)))
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(list? bound)
|
||||||
|
(> (len bound) 0)
|
||||||
|
(= (first bound) :dfn))
|
||||||
|
(fn (a b) (apl-call-dfn bound a b))
|
||||||
|
(error "apl-resolve-dyadic: name not bound to dfn")))))
|
||||||
|
((= tag :outer)
|
||||||
|
(let
|
||||||
|
((inner (nth fn-node 2)))
|
||||||
|
(let
|
||||||
|
((f (apl-resolve-dyadic inner env)))
|
||||||
|
(fn (a b) (apl-outer f a b)))))
|
||||||
|
((= tag :derived-fn2)
|
||||||
|
(let
|
||||||
|
((f-node (nth fn-node 2)) (g-node (nth fn-node 3)))
|
||||||
|
(let
|
||||||
|
((f (apl-resolve-dyadic f-node env))
|
||||||
|
(g (apl-resolve-dyadic g-node env)))
|
||||||
|
(fn (a b) (apl-inner f g a b)))))
|
||||||
|
((= tag :train)
|
||||||
|
(let
|
||||||
|
((fns (rest fn-node)))
|
||||||
|
(let
|
||||||
|
((n (len fns)))
|
||||||
|
(cond
|
||||||
|
((= n 2)
|
||||||
|
(let
|
||||||
|
((g (apl-resolve-monadic (nth fns 0) env))
|
||||||
|
(h (apl-resolve-dyadic (nth fns 1) env)))
|
||||||
|
(fn (a b) (g (h a b)))))
|
||||||
|
((= n 3)
|
||||||
|
(let
|
||||||
|
((f (apl-resolve-dyadic (nth fns 0) env))
|
||||||
|
(g (apl-resolve-dyadic (nth fns 1) env))
|
||||||
|
(h (apl-resolve-dyadic (nth fns 2) env)))
|
||||||
|
(fn (a b) (g (f a b) (h a b)))))
|
||||||
|
(else (error "dyadic train arity not 2 or 3"))))))
|
||||||
|
(else (error "apl-resolve-dyadic: unknown fn-node tag"))))))
|
||||||
|
|
||||||
|
(define apl-run (fn (src) (apl-eval-ast (parse-apl src) {})))
|
||||||
|
|
||||||
|
(define apl-run-file (fn (path) (apl-run (file-read path))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
apl-execute
|
||||||
|
(fn
|
||||||
|
(arr)
|
||||||
|
(let
|
||||||
|
((src (cond ((string? arr) arr) ((scalar? arr) (disclose arr)) (else (reduce str "" (get arr :ravel))))))
|
||||||
|
(apl-run src))))
|
||||||
500
lib/common-lisp/clos.sx
Normal file
500
lib/common-lisp/clos.sx
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
;; lib/common-lisp/clos.sx — CLOS: classes, instances, generic functions
|
||||||
|
;;
|
||||||
|
;; Class records: {:clos-type "class" :name "NAME" :slots {...} :parents [...] :methods [...]}
|
||||||
|
;; Instance: {:clos-type "instance" :class "NAME" :slots {slot: val ...}}
|
||||||
|
;; Method: {:qualifiers [...] :specializers [...] :fn (fn (args next-fn) ...)}
|
||||||
|
;;
|
||||||
|
;; SX primitive notes:
|
||||||
|
;; dict->list: use (map (fn (k) (list k (get d k))) (keys d))
|
||||||
|
;; dict-set (pure): use assoc
|
||||||
|
;; fn?/callable?: use callable?
|
||||||
|
|
||||||
|
;; ── dict helpers ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-dict->list
|
||||||
|
(fn (d) (map (fn (k) (list k (get d k))) (keys d))))
|
||||||
|
|
||||||
|
;; ── class registry ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-class-registry
|
||||||
|
(dict
|
||||||
|
"t"
|
||||||
|
{:parents (list) :clos-type "class" :slots (dict) :methods (list) :name "t"}
|
||||||
|
"null"
|
||||||
|
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "null"}
|
||||||
|
"integer"
|
||||||
|
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "integer"}
|
||||||
|
"float"
|
||||||
|
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "float"}
|
||||||
|
"string"
|
||||||
|
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "string"}
|
||||||
|
"symbol"
|
||||||
|
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "symbol"}
|
||||||
|
"cons"
|
||||||
|
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "cons"}
|
||||||
|
"list"
|
||||||
|
{:parents (list "t") :clos-type "class" :slots (dict) :methods (list) :name "list"}))
|
||||||
|
|
||||||
|
;; ── clos-generic-registry ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define clos-generic-registry (dict))
|
||||||
|
|
||||||
|
;; ── class-of ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-class-of
|
||||||
|
(fn
|
||||||
|
(x)
|
||||||
|
(cond
|
||||||
|
((nil? x) "null")
|
||||||
|
((integer? x) "integer")
|
||||||
|
((float? x) "float")
|
||||||
|
((string? x) "string")
|
||||||
|
((symbol? x) "symbol")
|
||||||
|
((and (list? x) (> (len x) 0)) "cons")
|
||||||
|
((and (list? x) (= (len x) 0)) "null")
|
||||||
|
((and (dict? x) (= (get x "clos-type") "instance")) (get x "class"))
|
||||||
|
(:else "t"))))
|
||||||
|
|
||||||
|
;; ── subclass-of? ──────────────────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; Captures clos-class-registry at define time to avoid free-variable issues.
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-subclass-of?
|
||||||
|
(let
|
||||||
|
((registry clos-class-registry))
|
||||||
|
(fn
|
||||||
|
(class-name super-name)
|
||||||
|
(if
|
||||||
|
(= class-name super-name)
|
||||||
|
true
|
||||||
|
(let
|
||||||
|
((rec (get registry class-name)))
|
||||||
|
(if
|
||||||
|
(nil? rec)
|
||||||
|
false
|
||||||
|
(some
|
||||||
|
(fn (p) (clos-subclass-of? p super-name))
|
||||||
|
(get rec "parents"))))))))
|
||||||
|
|
||||||
|
;; ── instance-of? ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-instance-of?
|
||||||
|
(fn (obj class-name) (clos-subclass-of? (clos-class-of obj) class-name)))
|
||||||
|
|
||||||
|
;; ── defclass ──────────────────────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; slot-specs: list of dicts with keys: name initarg initform accessor reader writer
|
||||||
|
;; Each missing key defaults to nil.
|
||||||
|
|
||||||
|
(define clos-slot-spec (fn (spec) (if (string? spec) {:initform nil :initarg nil :reader nil :writer nil :accessor nil :name spec} spec)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-defclass
|
||||||
|
(fn
|
||||||
|
(name parents slot-specs)
|
||||||
|
(let
|
||||||
|
((slots (dict)))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(pname)
|
||||||
|
(let
|
||||||
|
((prec (get clos-class-registry pname)))
|
||||||
|
(when
|
||||||
|
(not (nil? prec))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(k)
|
||||||
|
(when
|
||||||
|
(nil? (get slots k))
|
||||||
|
(dict-set! slots k (get (get prec "slots") k))))
|
||||||
|
(keys (get prec "slots"))))))
|
||||||
|
parents)
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(let
|
||||||
|
((spec (clos-slot-spec s)))
|
||||||
|
(dict-set! slots (get spec "name") spec)))
|
||||||
|
slot-specs)
|
||||||
|
(let
|
||||||
|
((class-rec {:parents parents :clos-type "class" :slots slots :methods (list) :name name}))
|
||||||
|
(dict-set! clos-class-registry name class-rec)
|
||||||
|
(clos-install-accessors-for name slots)
|
||||||
|
name))))
|
||||||
|
|
||||||
|
;; ── accessor installation (forward-declared, defined after defmethod) ──────
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-install-accessors-for
|
||||||
|
(fn
|
||||||
|
(class-name slots)
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(k)
|
||||||
|
(let
|
||||||
|
((spec (get slots k)))
|
||||||
|
(let
|
||||||
|
((reader (get spec "reader")))
|
||||||
|
(when
|
||||||
|
(not (nil? reader))
|
||||||
|
(clos-add-reader-method reader class-name k)))
|
||||||
|
(let
|
||||||
|
((accessor (get spec "accessor")))
|
||||||
|
(when
|
||||||
|
(not (nil? accessor))
|
||||||
|
(clos-add-reader-method accessor class-name k)))))
|
||||||
|
(keys slots))))
|
||||||
|
|
||||||
|
;; placeholder — real impl filled in after defmethod is defined
|
||||||
|
(define clos-add-reader-method (fn (method-name class-name slot-name) nil))
|
||||||
|
|
||||||
|
;; ── make-instance ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-make-instance
|
||||||
|
(fn
|
||||||
|
(class-name &rest initargs)
|
||||||
|
(let
|
||||||
|
((class-rec (get clos-class-registry class-name)))
|
||||||
|
(if
|
||||||
|
(nil? class-rec)
|
||||||
|
(error (str "No class named: " class-name))
|
||||||
|
(let
|
||||||
|
((slots (dict)))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(k)
|
||||||
|
(let
|
||||||
|
((spec (get (get class-rec "slots") k)))
|
||||||
|
(let
|
||||||
|
((initform (get spec "initform")))
|
||||||
|
(when
|
||||||
|
(not (nil? initform))
|
||||||
|
(dict-set!
|
||||||
|
slots
|
||||||
|
k
|
||||||
|
(if (callable? initform) (initform) initform))))))
|
||||||
|
(keys (get class-rec "slots")))
|
||||||
|
(define
|
||||||
|
apply-args
|
||||||
|
(fn
|
||||||
|
(args)
|
||||||
|
(when
|
||||||
|
(>= (len args) 2)
|
||||||
|
(let
|
||||||
|
((key (str (first args))) (val (first (rest args))))
|
||||||
|
(let
|
||||||
|
((skey (if (= (slice key 0 1) ":") (slice key 1 (len key)) key)))
|
||||||
|
(let
|
||||||
|
((matched false))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(sk)
|
||||||
|
(let
|
||||||
|
((spec (get (get class-rec "slots") sk)))
|
||||||
|
(let
|
||||||
|
((ia (get spec "initarg")))
|
||||||
|
(when
|
||||||
|
(or
|
||||||
|
(= ia key)
|
||||||
|
(= ia (str ":" skey))
|
||||||
|
(= sk skey))
|
||||||
|
(dict-set! slots sk val)
|
||||||
|
(set! matched true)))))
|
||||||
|
(keys (get class-rec "slots")))))
|
||||||
|
(apply-args (rest (rest args)))))))
|
||||||
|
(apply-args initargs)
|
||||||
|
{:clos-type "instance" :slots slots :class class-name})))))
|
||||||
|
|
||||||
|
;; ── slot-value ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-slot-value
|
||||||
|
(fn
|
||||||
|
(instance slot-name)
|
||||||
|
(if
|
||||||
|
(and (dict? instance) (= (get instance "clos-type") "instance"))
|
||||||
|
(get (get instance "slots") slot-name)
|
||||||
|
(error (str "Not a CLOS instance: " (inspect instance))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-set-slot-value!
|
||||||
|
(fn
|
||||||
|
(instance slot-name value)
|
||||||
|
(if
|
||||||
|
(and (dict? instance) (= (get instance "clos-type") "instance"))
|
||||||
|
(dict-set! (get instance "slots") slot-name value)
|
||||||
|
(error (str "Not a CLOS instance: " (inspect instance))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-slot-boundp
|
||||||
|
(fn
|
||||||
|
(instance slot-name)
|
||||||
|
(and
|
||||||
|
(dict? instance)
|
||||||
|
(= (get instance "clos-type") "instance")
|
||||||
|
(not (nil? (get (get instance "slots") slot-name))))))
|
||||||
|
|
||||||
|
;; ── find-class / change-class ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define clos-find-class (fn (name) (get clos-class-registry name)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-change-class!
|
||||||
|
(fn
|
||||||
|
(instance new-class-name)
|
||||||
|
(if
|
||||||
|
(and (dict? instance) (= (get instance "clos-type") "instance"))
|
||||||
|
(dict-set! instance "class" new-class-name)
|
||||||
|
(error (str "Not a CLOS instance: " (inspect instance))))))
|
||||||
|
|
||||||
|
;; ── defgeneric ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-defgeneric
|
||||||
|
(fn
|
||||||
|
(name options)
|
||||||
|
(let
|
||||||
|
((combination (or (get options "method-combination") "standard")))
|
||||||
|
(when
|
||||||
|
(nil? (get clos-generic-registry name))
|
||||||
|
(dict-set! clos-generic-registry name {:methods (list) :combination combination :name name}))
|
||||||
|
name)))
|
||||||
|
|
||||||
|
;; ── defmethod ─────────────────────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; method-fn: (fn (args next-fn) body)
|
||||||
|
;; args = list of all call arguments
|
||||||
|
;; next-fn = (fn () next-method-result) or nil
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-defmethod
|
||||||
|
(fn
|
||||||
|
(generic-name qualifiers specializers method-fn)
|
||||||
|
(when
|
||||||
|
(nil? (get clos-generic-registry generic-name))
|
||||||
|
(clos-defgeneric generic-name {}))
|
||||||
|
(let
|
||||||
|
((grec (get clos-generic-registry generic-name))
|
||||||
|
(new-method {:fn method-fn :qualifiers qualifiers :specializers specializers}))
|
||||||
|
(let
|
||||||
|
((kept (filter (fn (m) (not (and (= (get m "qualifiers") qualifiers) (= (get m "specializers") specializers)))) (get grec "methods"))))
|
||||||
|
(dict-set!
|
||||||
|
clos-generic-registry
|
||||||
|
generic-name
|
||||||
|
(assoc grec "methods" (append kept (list new-method))))
|
||||||
|
generic-name))))
|
||||||
|
|
||||||
|
;; Now install the real accessor-method installer
|
||||||
|
(set!
|
||||||
|
clos-add-reader-method
|
||||||
|
(fn
|
||||||
|
(method-name class-name slot-name)
|
||||||
|
(clos-defmethod
|
||||||
|
method-name
|
||||||
|
(list)
|
||||||
|
(list class-name)
|
||||||
|
(fn (args next-fn) (clos-slot-value (first args) slot-name)))))
|
||||||
|
|
||||||
|
;; ── method specificity ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-method-matches?
|
||||||
|
(fn
|
||||||
|
(method args)
|
||||||
|
(let
|
||||||
|
((specs (get method "specializers")))
|
||||||
|
(if
|
||||||
|
(> (len specs) (len args))
|
||||||
|
false
|
||||||
|
(define
|
||||||
|
check-all
|
||||||
|
(fn
|
||||||
|
(i)
|
||||||
|
(if
|
||||||
|
(>= i (len specs))
|
||||||
|
true
|
||||||
|
(let
|
||||||
|
((spec (nth specs i)) (arg (nth args i)))
|
||||||
|
(if
|
||||||
|
(= spec "t")
|
||||||
|
(check-all (+ i 1))
|
||||||
|
(if
|
||||||
|
(clos-instance-of? arg spec)
|
||||||
|
(check-all (+ i 1))
|
||||||
|
false))))))
|
||||||
|
(check-all 0)))))
|
||||||
|
|
||||||
|
;; Precedence distance: how far class-name is from spec-name up the hierarchy.
|
||||||
|
(define
|
||||||
|
clos-specificity
|
||||||
|
(let
|
||||||
|
((registry clos-class-registry))
|
||||||
|
(fn
|
||||||
|
(class-name spec-name)
|
||||||
|
(define
|
||||||
|
walk
|
||||||
|
(fn
|
||||||
|
(cn depth)
|
||||||
|
(if
|
||||||
|
(= cn spec-name)
|
||||||
|
depth
|
||||||
|
(let
|
||||||
|
((rec (get registry cn)))
|
||||||
|
(if
|
||||||
|
(nil? rec)
|
||||||
|
nil
|
||||||
|
(let
|
||||||
|
((results (map (fn (p) (walk p (+ depth 1))) (get rec "parents"))))
|
||||||
|
(let
|
||||||
|
((non-nil (filter (fn (x) (not (nil? x))) results)))
|
||||||
|
(if
|
||||||
|
(empty? non-nil)
|
||||||
|
nil
|
||||||
|
(reduce
|
||||||
|
(fn (a b) (if (< a b) a b))
|
||||||
|
(first non-nil)
|
||||||
|
(rest non-nil))))))))))
|
||||||
|
(walk class-name 0))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-method-more-specific?
|
||||||
|
(fn
|
||||||
|
(m1 m2 args)
|
||||||
|
(let
|
||||||
|
((s1 (get m1 "specializers")) (s2 (get m2 "specializers")))
|
||||||
|
(define
|
||||||
|
cmp
|
||||||
|
(fn
|
||||||
|
(i)
|
||||||
|
(if
|
||||||
|
(>= i (len s1))
|
||||||
|
false
|
||||||
|
(let
|
||||||
|
((c1 (clos-specificity (clos-class-of (nth args i)) (nth s1 i)))
|
||||||
|
(c2
|
||||||
|
(clos-specificity (clos-class-of (nth args i)) (nth s2 i))))
|
||||||
|
(cond
|
||||||
|
((and (nil? c1) (nil? c2)) (cmp (+ i 1)))
|
||||||
|
((nil? c1) false)
|
||||||
|
((nil? c2) true)
|
||||||
|
((< c1 c2) true)
|
||||||
|
((> c1 c2) false)
|
||||||
|
(:else (cmp (+ i 1))))))))
|
||||||
|
(cmp 0))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-sort-methods
|
||||||
|
(fn
|
||||||
|
(methods args)
|
||||||
|
(define
|
||||||
|
insert
|
||||||
|
(fn
|
||||||
|
(m sorted)
|
||||||
|
(if
|
||||||
|
(empty? sorted)
|
||||||
|
(list m)
|
||||||
|
(if
|
||||||
|
(clos-method-more-specific? m (first sorted) args)
|
||||||
|
(cons m sorted)
|
||||||
|
(cons (first sorted) (insert m (rest sorted)))))))
|
||||||
|
(reduce (fn (acc m) (insert m acc)) (list) methods)))
|
||||||
|
|
||||||
|
;; ── call-generic (standard method combination) ─────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-call-generic
|
||||||
|
(fn
|
||||||
|
(generic-name args)
|
||||||
|
(let
|
||||||
|
((grec (get clos-generic-registry generic-name)))
|
||||||
|
(if
|
||||||
|
(nil? grec)
|
||||||
|
(error (str "No generic function: " generic-name))
|
||||||
|
(let
|
||||||
|
((applicable (filter (fn (m) (clos-method-matches? m args)) (get grec "methods"))))
|
||||||
|
(if
|
||||||
|
(empty? applicable)
|
||||||
|
(error
|
||||||
|
(str
|
||||||
|
"No applicable method for "
|
||||||
|
generic-name
|
||||||
|
" with classes "
|
||||||
|
(inspect (map clos-class-of args))))
|
||||||
|
(let
|
||||||
|
((primary (filter (fn (m) (empty? (get m "qualifiers"))) applicable))
|
||||||
|
(before
|
||||||
|
(filter
|
||||||
|
(fn (m) (= (get m "qualifiers") (list "before")))
|
||||||
|
applicable))
|
||||||
|
(after
|
||||||
|
(filter
|
||||||
|
(fn (m) (= (get m "qualifiers") (list "after")))
|
||||||
|
applicable))
|
||||||
|
(around
|
||||||
|
(filter
|
||||||
|
(fn (m) (= (get m "qualifiers") (list "around")))
|
||||||
|
applicable)))
|
||||||
|
(let
|
||||||
|
((sp (clos-sort-methods primary args))
|
||||||
|
(sb (clos-sort-methods before args))
|
||||||
|
(sa (clos-sort-methods after args))
|
||||||
|
(sw (clos-sort-methods around args)))
|
||||||
|
(define
|
||||||
|
make-primary-chain
|
||||||
|
(fn
|
||||||
|
(methods)
|
||||||
|
(if
|
||||||
|
(empty? methods)
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(error (str "No next primary method: " generic-name)))
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
((get (first methods) "fn")
|
||||||
|
args
|
||||||
|
(make-primary-chain (rest methods)))))))
|
||||||
|
(define
|
||||||
|
make-around-chain
|
||||||
|
(fn
|
||||||
|
(around-methods inner-thunk)
|
||||||
|
(if
|
||||||
|
(empty? around-methods)
|
||||||
|
inner-thunk
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
((get (first around-methods) "fn")
|
||||||
|
args
|
||||||
|
(make-around-chain
|
||||||
|
(rest around-methods)
|
||||||
|
inner-thunk))))))
|
||||||
|
(for-each (fn (m) ((get m "fn") args (fn () nil))) sb)
|
||||||
|
(let
|
||||||
|
((primary-thunk (make-primary-chain sp)))
|
||||||
|
(let
|
||||||
|
((result (if (empty? sw) (primary-thunk) ((make-around-chain sw primary-thunk)))))
|
||||||
|
(for-each
|
||||||
|
(fn (m) ((get m "fn") args (fn () nil)))
|
||||||
|
(reverse sa))
|
||||||
|
result))))))))))
|
||||||
|
|
||||||
|
;; ── call-next-method / next-method-p ──────────────────────────────────────
|
||||||
|
|
||||||
|
(define clos-call-next-method (fn (next-fn) (next-fn)))
|
||||||
|
|
||||||
|
(define clos-next-method-p (fn (next-fn) (not (nil? next-fn))))
|
||||||
|
|
||||||
|
;; ── with-slots ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
clos-with-slots
|
||||||
|
(fn
|
||||||
|
(instance slot-names body-fn)
|
||||||
|
(let
|
||||||
|
((vals (map (fn (s) (clos-slot-value instance s)) slot-names)))
|
||||||
|
(apply body-fn vals))))
|
||||||
161
lib/common-lisp/conformance.sh
Executable file
161
lib/common-lisp/conformance.sh
Executable file
@@ -0,0 +1,161 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# lib/common-lisp/conformance.sh — CL-on-SX conformance test runner
|
||||||
|
#
|
||||||
|
# Runs all Common Lisp test suites and writes scoreboard.json + scoreboard.md.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# bash lib/common-lisp/conformance.sh
|
||||||
|
# bash lib/common-lisp/conformance.sh -v
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
VERBOSE="${1:-}"
|
||||||
|
TOTAL_PASS=0; TOTAL_FAIL=0
|
||||||
|
SUITE_NAMES=()
|
||||||
|
SUITE_PASS=()
|
||||||
|
SUITE_FAIL=()
|
||||||
|
|
||||||
|
# run_suite NAME "file1 file2 ..." PASS_VAR FAIL_VAR FAILURES_VAR
|
||||||
|
run_suite() {
|
||||||
|
local name="$1" load_files="$2" pass_var="$3" fail_var="$4" failures_var="$5"
|
||||||
|
local TMP; TMP=$(mktemp)
|
||||||
|
{
|
||||||
|
printf '(epoch 1)\n(load "spec/stdlib.sx")\n'
|
||||||
|
local i=2
|
||||||
|
for f in $load_files; do
|
||||||
|
printf '(epoch %d)\n(load "%s")\n' "$i" "$f"
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
printf '(epoch 100)\n(eval "%s")\n' "$pass_var"
|
||||||
|
printf '(epoch 101)\n(eval "%s")\n' "$fail_var"
|
||||||
|
} > "$TMP"
|
||||||
|
local OUT; OUT=$(timeout 30 "$SX_SERVER" < "$TMP" 2>/dev/null)
|
||||||
|
rm -f "$TMP"
|
||||||
|
local P F
|
||||||
|
P=$(echo "$OUT" | grep -A1 "^(ok-len 100 " | tail -1 | tr -d ' ()' || true)
|
||||||
|
F=$(echo "$OUT" | grep -A1 "^(ok-len 101 " | tail -1 | tr -d ' ()' || true)
|
||||||
|
# Also try plain (ok 100 N) format
|
||||||
|
[ -z "$P" ] && P=$(echo "$OUT" | grep "^(ok 100 " | awk '{print $3}' | tr -d ')' || true)
|
||||||
|
[ -z "$F" ] && F=$(echo "$OUT" | grep "^(ok 101 " | awk '{print $3}' | tr -d ')' || true)
|
||||||
|
[ -z "$P" ] && P=0; [ -z "$F" ] && F=0
|
||||||
|
SUITE_NAMES+=("$name")
|
||||||
|
SUITE_PASS+=("$P")
|
||||||
|
SUITE_FAIL+=("$F")
|
||||||
|
TOTAL_PASS=$((TOTAL_PASS + P))
|
||||||
|
TOTAL_FAIL=$((TOTAL_FAIL + F))
|
||||||
|
if [ "$F" = "0" ] && [ "${P:-0}" -gt 0 ] 2>/dev/null; then
|
||||||
|
echo " PASS $name ($P tests)"
|
||||||
|
else
|
||||||
|
echo " FAIL $name ($P passed, $F failed)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=== Common Lisp on SX — Conformance Run ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
run_suite "Phase 1: tokenizer/reader" \
|
||||||
|
"lib/common-lisp/reader.sx lib/common-lisp/tests/read.sx" \
|
||||||
|
"cl-test-pass" "cl-test-fail" "cl-test-fails"
|
||||||
|
|
||||||
|
run_suite "Phase 1: parser/lambda-lists" \
|
||||||
|
"lib/common-lisp/reader.sx lib/common-lisp/parser.sx lib/common-lisp/tests/lambda.sx" \
|
||||||
|
"cl-test-pass" "cl-test-fail" "cl-test-fails"
|
||||||
|
|
||||||
|
run_suite "Phase 2: evaluator" \
|
||||||
|
"lib/common-lisp/reader.sx lib/common-lisp/parser.sx lib/common-lisp/eval.sx lib/common-lisp/tests/eval.sx" \
|
||||||
|
"cl-test-pass" "cl-test-fail" "cl-test-fails"
|
||||||
|
|
||||||
|
run_suite "Phase 3: condition system" \
|
||||||
|
"lib/common-lisp/runtime.sx lib/common-lisp/tests/conditions.sx" \
|
||||||
|
"passed" "failed" "failures"
|
||||||
|
|
||||||
|
run_suite "Phase 3: restart-demo" \
|
||||||
|
"lib/common-lisp/runtime.sx lib/common-lisp/tests/programs/restart-demo.sx" \
|
||||||
|
"demo-passed" "demo-failed" "demo-failures"
|
||||||
|
|
||||||
|
run_suite "Phase 3: parse-recover" \
|
||||||
|
"lib/common-lisp/runtime.sx lib/common-lisp/tests/programs/parse-recover.sx" \
|
||||||
|
"parse-passed" "parse-failed" "parse-failures"
|
||||||
|
|
||||||
|
run_suite "Phase 3: interactive-debugger" \
|
||||||
|
"lib/common-lisp/runtime.sx lib/common-lisp/tests/programs/interactive-debugger.sx" \
|
||||||
|
"debugger-passed" "debugger-failed" "debugger-failures"
|
||||||
|
|
||||||
|
run_suite "Phase 4: CLOS" \
|
||||||
|
"lib/common-lisp/runtime.sx lib/common-lisp/clos.sx lib/common-lisp/tests/clos.sx" \
|
||||||
|
"passed" "failed" "failures"
|
||||||
|
|
||||||
|
run_suite "Phase 4: geometry" \
|
||||||
|
"lib/common-lisp/runtime.sx lib/common-lisp/clos.sx lib/common-lisp/tests/programs/geometry.sx" \
|
||||||
|
"geo-passed" "geo-failed" "geo-failures"
|
||||||
|
|
||||||
|
run_suite "Phase 4: mop-trace" \
|
||||||
|
"lib/common-lisp/runtime.sx lib/common-lisp/clos.sx lib/common-lisp/tests/programs/mop-trace.sx" \
|
||||||
|
"mop-passed" "mop-failed" "mop-failures"
|
||||||
|
|
||||||
|
run_suite "Phase 5: macros+LOOP" \
|
||||||
|
"lib/common-lisp/reader.sx lib/common-lisp/parser.sx lib/common-lisp/eval.sx lib/common-lisp/loop.sx lib/common-lisp/tests/macros.sx" \
|
||||||
|
"macro-passed" "macro-failed" "macro-failures"
|
||||||
|
|
||||||
|
run_suite "Phase 6: stdlib" \
|
||||||
|
"lib/common-lisp/reader.sx lib/common-lisp/parser.sx lib/common-lisp/eval.sx lib/common-lisp/tests/stdlib.sx" \
|
||||||
|
"stdlib-passed" "stdlib-failed" "stdlib-failures"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Total: $TOTAL_PASS passed, $TOTAL_FAIL failed ==="
|
||||||
|
|
||||||
|
# ── write scoreboard.json ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
SCORE_DIR="lib/common-lisp"
|
||||||
|
JSON="$SCORE_DIR/scoreboard.json"
|
||||||
|
{
|
||||||
|
printf '{\n'
|
||||||
|
printf ' "generated": "%s",\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
printf ' "total_pass": %d,\n' "$TOTAL_PASS"
|
||||||
|
printf ' "total_fail": %d,\n' "$TOTAL_FAIL"
|
||||||
|
printf ' "suites": [\n'
|
||||||
|
first=true
|
||||||
|
for i in "${!SUITE_NAMES[@]}"; do
|
||||||
|
if [ "$first" = "true" ]; then first=false; else printf ',\n'; fi
|
||||||
|
printf ' {"name": "%s", "pass": %d, "fail": %d}' \
|
||||||
|
"${SUITE_NAMES[$i]}" "${SUITE_PASS[$i]}" "${SUITE_FAIL[$i]}"
|
||||||
|
done
|
||||||
|
printf '\n ]\n'
|
||||||
|
printf '}\n'
|
||||||
|
} > "$JSON"
|
||||||
|
|
||||||
|
# ── write scoreboard.md ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
MD="$SCORE_DIR/scoreboard.md"
|
||||||
|
{
|
||||||
|
printf '# Common Lisp on SX — Scoreboard\n\n'
|
||||||
|
printf '_Generated: %s_\n\n' "$(date -u '+%Y-%m-%d %H:%M UTC')"
|
||||||
|
printf '| Suite | Pass | Fail | Status |\n'
|
||||||
|
printf '|-------|------|------|--------|\n'
|
||||||
|
for i in "${!SUITE_NAMES[@]}"; do
|
||||||
|
p="${SUITE_PASS[$i]}" f="${SUITE_FAIL[$i]}"
|
||||||
|
status=""
|
||||||
|
if [ "$f" = "0" ] && [ "${p:-0}" -gt 0 ] 2>/dev/null; then
|
||||||
|
status="pass"
|
||||||
|
else
|
||||||
|
status="FAIL"
|
||||||
|
fi
|
||||||
|
printf '| %s | %s | %s | %s |\n' "${SUITE_NAMES[$i]}" "$p" "$f" "$status"
|
||||||
|
done
|
||||||
|
printf '\n**Total: %d passed, %d failed**\n' "$TOTAL_PASS" "$TOTAL_FAIL"
|
||||||
|
} > "$MD"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Scoreboard written to $JSON and $MD"
|
||||||
|
|
||||||
|
[ "$TOTAL_FAIL" -eq 0 ]
|
||||||
1391
lib/common-lisp/eval.sx
Normal file
1391
lib/common-lisp/eval.sx
Normal file
File diff suppressed because it is too large
Load Diff
623
lib/common-lisp/loop.sx
Normal file
623
lib/common-lisp/loop.sx
Normal file
@@ -0,0 +1,623 @@
|
|||||||
|
;; lib/common-lisp/loop.sx — The LOOP macro for CL-on-SX
|
||||||
|
;;
|
||||||
|
;; Supported clauses:
|
||||||
|
;; for VAR in LIST — iterate over list
|
||||||
|
;; for VAR across VECTOR — alias for 'in'
|
||||||
|
;; for VAR from N — numeric iteration (to/upto/below/downto/above/by)
|
||||||
|
;; for VAR = EXPR [then EXPR] — general iteration
|
||||||
|
;; while COND — stop when false
|
||||||
|
;; until COND — stop when true
|
||||||
|
;; repeat N — repeat N times
|
||||||
|
;; collect EXPR [into VAR]
|
||||||
|
;; append EXPR [into VAR]
|
||||||
|
;; nconc EXPR [into VAR]
|
||||||
|
;; sum EXPR [into VAR]
|
||||||
|
;; count EXPR [into VAR]
|
||||||
|
;; maximize EXPR [into VAR]
|
||||||
|
;; minimize EXPR [into VAR]
|
||||||
|
;; do FORM...
|
||||||
|
;; when/if COND clause...
|
||||||
|
;; unless COND clause...
|
||||||
|
;; finally FORM...
|
||||||
|
;; always COND
|
||||||
|
;; never COND
|
||||||
|
;; thereis COND
|
||||||
|
;; named BLOCK-NAME
|
||||||
|
;;
|
||||||
|
;; Depends on: lib/common-lisp/runtime.sx, lib/common-lisp/eval.sx already loaded.
|
||||||
|
;; Uses defmacro in the CL evaluator.
|
||||||
|
|
||||||
|
;; ── LOOP expansion driver ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; cl-loop-parse: analyse the flat LOOP clause list and build a Lisp form.
|
||||||
|
;; Returns a (block NAME (let (...) (tagbody ...))) form.
|
||||||
|
(define
|
||||||
|
cl-loop-parse
|
||||||
|
(fn
|
||||||
|
(clauses)
|
||||||
|
(define block-name nil)
|
||||||
|
(define with-bindings (list))
|
||||||
|
(define for-bindings (list))
|
||||||
|
(define test-forms (list))
|
||||||
|
(define repeat-var nil)
|
||||||
|
(define repeat-count nil)
|
||||||
|
(define body-forms (list))
|
||||||
|
(define accum-vars (dict))
|
||||||
|
(define accum-clauses (dict))
|
||||||
|
(define result-var nil)
|
||||||
|
(define finally-forms (list))
|
||||||
|
(define return-expr nil)
|
||||||
|
(define termination nil)
|
||||||
|
(define idx 0)
|
||||||
|
(define (lp-peek) (if (< idx (len clauses)) (nth clauses idx) nil))
|
||||||
|
(define
|
||||||
|
(next!)
|
||||||
|
(let ((v (lp-peek))) (do (set! idx (+ idx 1)) v)))
|
||||||
|
(define
|
||||||
|
(skip-if pred)
|
||||||
|
(if (and (not (nil? (lp-peek))) (pred (lp-peek))) (next!) nil))
|
||||||
|
(define (upcase-str s) (if (string? s) (upcase s) s))
|
||||||
|
(define (kw? s k) (= (upcase-str s) k))
|
||||||
|
(define
|
||||||
|
(make-accum-var!)
|
||||||
|
(if
|
||||||
|
(nil? result-var)
|
||||||
|
(do (set! result-var "#LOOP-RESULT") result-var)
|
||||||
|
result-var))
|
||||||
|
(define
|
||||||
|
(add-accum! type expr into-var)
|
||||||
|
(let
|
||||||
|
((v (if (nil? into-var) (make-accum-var!) into-var)))
|
||||||
|
(if
|
||||||
|
(not (has-key? accum-vars v))
|
||||||
|
(do
|
||||||
|
(set!
|
||||||
|
accum-vars
|
||||||
|
(assoc
|
||||||
|
accum-vars
|
||||||
|
v
|
||||||
|
(cond
|
||||||
|
((= type ":sum") 0)
|
||||||
|
((= type ":count") 0)
|
||||||
|
((= type ":maximize") nil)
|
||||||
|
((= type ":minimize") nil)
|
||||||
|
(:else (list)))))
|
||||||
|
(set! accum-clauses (assoc accum-clauses v type))))
|
||||||
|
(let
|
||||||
|
((update (cond ((= type ":collect") (list "SETQ" v (list "APPEND" v (list "LIST" expr)))) ((= type ":append") (list "SETQ" v (list "APPEND" v expr))) ((= type ":nconc") (list "SETQ" v (list "NCONC" v expr))) ((= type ":sum") (list "SETQ" v (list "+" v expr))) ((= type ":count") (list "SETQ" v (list "+" v (list "IF" expr 1 0)))) ((= type ":maximize") (list "SETQ" v (list "IF" (list "OR" (list "NULL" v) (list ">" expr v)) expr v))) ((= type ":minimize") (list "SETQ" v (list "IF" (list "OR" (list "NULL" v) (list "<" expr v)) expr v))) (:else (list "SETQ" v (list "APPEND" v (list "LIST" expr)))))))
|
||||||
|
(set! body-forms (append body-forms (list update))))))
|
||||||
|
(define
|
||||||
|
(parse-clause!)
|
||||||
|
(let
|
||||||
|
((tok (lp-peek)))
|
||||||
|
(if
|
||||||
|
(nil? tok)
|
||||||
|
nil
|
||||||
|
(do
|
||||||
|
(let
|
||||||
|
((u (upcase-str tok)))
|
||||||
|
(cond
|
||||||
|
((= u "NAMED")
|
||||||
|
(do (next!) (set! block-name (next!)) (parse-clause!)))
|
||||||
|
((= u "WITH")
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((var (next!)))
|
||||||
|
(skip-if (fn (s) (kw? s "=")))
|
||||||
|
(let
|
||||||
|
((init (next!)))
|
||||||
|
(set!
|
||||||
|
with-bindings
|
||||||
|
(append with-bindings (list (list var init))))
|
||||||
|
(parse-clause!)))))
|
||||||
|
((= u "FOR")
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((var (next!)))
|
||||||
|
(let
|
||||||
|
((kw2 (upcase-str (lp-peek))))
|
||||||
|
(cond
|
||||||
|
((or (= kw2 "IN") (= kw2 "ACROSS"))
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((lst-expr (next!))
|
||||||
|
(tail-var (str "#TAIL-" var)))
|
||||||
|
(set!
|
||||||
|
for-bindings
|
||||||
|
(append for-bindings (list {:list lst-expr :tail tail-var :type ":list" :var var})))
|
||||||
|
(parse-clause!))))
|
||||||
|
((= kw2 "=")
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((init-expr (next!)))
|
||||||
|
(let
|
||||||
|
((then-expr (if (kw? (lp-peek) "THEN") (do (next!) (next!)) init-expr)))
|
||||||
|
(set!
|
||||||
|
for-bindings
|
||||||
|
(append for-bindings (list {:type ":general" :then then-expr :init init-expr :var var})))
|
||||||
|
(parse-clause!)))))
|
||||||
|
((or (= kw2 "FROM") (= kw2 "DOWNFROM") (= kw2 "UPFROM"))
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((from-expr (next!))
|
||||||
|
(dir (if (= kw2 "DOWNFROM") ":down" ":up"))
|
||||||
|
(limit-expr nil)
|
||||||
|
(limit-type nil)
|
||||||
|
(step-expr 1))
|
||||||
|
(let
|
||||||
|
((lkw (upcase-str (lp-peek))))
|
||||||
|
(when
|
||||||
|
(or
|
||||||
|
(= lkw "TO")
|
||||||
|
(= lkw "UPTO")
|
||||||
|
(= lkw "BELOW")
|
||||||
|
(= lkw "DOWNTO")
|
||||||
|
(= lkw "ABOVE"))
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(set! limit-type lkw)
|
||||||
|
(set! limit-expr (next!)))))
|
||||||
|
(when
|
||||||
|
(kw? (lp-peek) "BY")
|
||||||
|
(do (next!) (set! step-expr (next!))))
|
||||||
|
(set!
|
||||||
|
for-bindings
|
||||||
|
(append for-bindings (list {:dir dir :step step-expr :from from-expr :type ":numeric" :limit-type limit-type :var var :limit limit-expr})))
|
||||||
|
(parse-clause!))))
|
||||||
|
((or (= kw2 "TO") (= kw2 "UPTO") (= kw2 "BELOW"))
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((limit-expr (next!))
|
||||||
|
(step-expr 1))
|
||||||
|
(when
|
||||||
|
(kw? (lp-peek) "BY")
|
||||||
|
(do (next!) (set! step-expr (next!))))
|
||||||
|
(set!
|
||||||
|
for-bindings
|
||||||
|
(append for-bindings (list {:dir ":up" :step step-expr :from 0 :type ":numeric" :limit-type kw2 :var var :limit limit-expr})))
|
||||||
|
(parse-clause!))))
|
||||||
|
(:else (do (parse-clause!))))))))
|
||||||
|
((= u "WHILE")
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(set! test-forms (append test-forms (list {:expr (next!) :type ":while"})))
|
||||||
|
(parse-clause!)))
|
||||||
|
((= u "UNTIL")
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(set! test-forms (append test-forms (list {:expr (next!) :type ":until"})))
|
||||||
|
(parse-clause!)))
|
||||||
|
((= u "REPEAT")
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(set! repeat-count (next!))
|
||||||
|
(set! repeat-var "#REPEAT-COUNT")
|
||||||
|
(parse-clause!)))
|
||||||
|
((or (= u "COLLECT") (= u "COLLECTING"))
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((expr (next!)) (into-var nil))
|
||||||
|
(when
|
||||||
|
(kw? (lp-peek) "INTO")
|
||||||
|
(do (next!) (set! into-var (next!))))
|
||||||
|
(add-accum! ":collect" expr into-var)
|
||||||
|
(parse-clause!))))
|
||||||
|
((or (= u "APPEND") (= u "APPENDING"))
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((expr (next!)) (into-var nil))
|
||||||
|
(when
|
||||||
|
(kw? (lp-peek) "INTO")
|
||||||
|
(do (next!) (set! into-var (next!))))
|
||||||
|
(add-accum! ":append" expr into-var)
|
||||||
|
(parse-clause!))))
|
||||||
|
((or (= u "NCONC") (= u "NCONCING"))
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((expr (next!)) (into-var nil))
|
||||||
|
(when
|
||||||
|
(kw? (lp-peek) "INTO")
|
||||||
|
(do (next!) (set! into-var (next!))))
|
||||||
|
(add-accum! ":nconc" expr into-var)
|
||||||
|
(parse-clause!))))
|
||||||
|
((or (= u "SUM") (= u "SUMMING"))
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((expr (next!)) (into-var nil))
|
||||||
|
(when
|
||||||
|
(kw? (lp-peek) "INTO")
|
||||||
|
(do (next!) (set! into-var (next!))))
|
||||||
|
(add-accum! ":sum" expr into-var)
|
||||||
|
(parse-clause!))))
|
||||||
|
((or (= u "COUNT") (= u "COUNTING"))
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((expr (next!)) (into-var nil))
|
||||||
|
(when
|
||||||
|
(kw? (lp-peek) "INTO")
|
||||||
|
(do (next!) (set! into-var (next!))))
|
||||||
|
(add-accum! ":count" expr into-var)
|
||||||
|
(parse-clause!))))
|
||||||
|
((or (= u "MAXIMIZE") (= u "MAXIMIZING"))
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((expr (next!)) (into-var nil))
|
||||||
|
(when
|
||||||
|
(kw? (lp-peek) "INTO")
|
||||||
|
(do (next!) (set! into-var (next!))))
|
||||||
|
(add-accum! ":maximize" expr into-var)
|
||||||
|
(parse-clause!))))
|
||||||
|
((or (= u "MINIMIZE") (= u "MINIMIZING"))
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((expr (next!)) (into-var nil))
|
||||||
|
(when
|
||||||
|
(kw? (lp-peek) "INTO")
|
||||||
|
(do (next!) (set! into-var (next!))))
|
||||||
|
(add-accum! ":minimize" expr into-var)
|
||||||
|
(parse-clause!))))
|
||||||
|
((= u "DO")
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(define
|
||||||
|
(loop-kw? s)
|
||||||
|
(let
|
||||||
|
((us (upcase-str s)))
|
||||||
|
(some
|
||||||
|
(fn (k) (= us k))
|
||||||
|
(list
|
||||||
|
"FOR"
|
||||||
|
"WITH"
|
||||||
|
"WHILE"
|
||||||
|
"UNTIL"
|
||||||
|
"REPEAT"
|
||||||
|
"COLLECT"
|
||||||
|
"COLLECTING"
|
||||||
|
"APPEND"
|
||||||
|
"APPENDING"
|
||||||
|
"NCONC"
|
||||||
|
"NCONCING"
|
||||||
|
"SUM"
|
||||||
|
"SUMMING"
|
||||||
|
"COUNT"
|
||||||
|
"COUNTING"
|
||||||
|
"MAXIMIZE"
|
||||||
|
"MAXIMIZING"
|
||||||
|
"MINIMIZE"
|
||||||
|
"MINIMIZING"
|
||||||
|
"DO"
|
||||||
|
"WHEN"
|
||||||
|
"IF"
|
||||||
|
"UNLESS"
|
||||||
|
"FINALLY"
|
||||||
|
"ALWAYS"
|
||||||
|
"NEVER"
|
||||||
|
"THEREIS"
|
||||||
|
"RETURN"
|
||||||
|
"NAMED"))))
|
||||||
|
(define
|
||||||
|
(collect-do-forms!)
|
||||||
|
(if
|
||||||
|
(or (nil? (lp-peek)) (loop-kw? (lp-peek)))
|
||||||
|
nil
|
||||||
|
(do
|
||||||
|
(set!
|
||||||
|
body-forms
|
||||||
|
(append body-forms (list (next!))))
|
||||||
|
(collect-do-forms!))))
|
||||||
|
(collect-do-forms!)
|
||||||
|
(parse-clause!)))
|
||||||
|
((or (= u "WHEN") (= u "IF"))
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((cond-expr (next!))
|
||||||
|
(body-start (len body-forms)))
|
||||||
|
(parse-clause!)
|
||||||
|
;; wrap forms added since body-start in (WHEN cond ...)
|
||||||
|
(when (> (len body-forms) body-start)
|
||||||
|
(let ((added (list (nth body-forms body-start))))
|
||||||
|
(set! body-forms
|
||||||
|
(append
|
||||||
|
(if (> body-start 0)
|
||||||
|
(list (nth body-forms (- body-start 1)))
|
||||||
|
(list))
|
||||||
|
(list (list "WHEN" cond-expr (first added)))))
|
||||||
|
nil)))))
|
||||||
|
((= u "UNLESS")
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(let
|
||||||
|
((cond-expr (next!))
|
||||||
|
(body-start (len body-forms)))
|
||||||
|
(parse-clause!)
|
||||||
|
(when (> (len body-forms) body-start)
|
||||||
|
(let ((added (list (nth body-forms body-start))))
|
||||||
|
(set! body-forms
|
||||||
|
(append
|
||||||
|
(if (> body-start 0)
|
||||||
|
(list (nth body-forms (- body-start 1)))
|
||||||
|
(list))
|
||||||
|
(list (list "UNLESS" cond-expr (first added)))))
|
||||||
|
nil)))))
|
||||||
|
((= u "ALWAYS")
|
||||||
|
(do (next!) (set! termination {:expr (next!) :type ":always"}) (parse-clause!)))
|
||||||
|
((= u "NEVER")
|
||||||
|
(do (next!) (set! termination {:expr (next!) :type ":never"}) (parse-clause!)))
|
||||||
|
((= u "THEREIS")
|
||||||
|
(do (next!) (set! termination {:expr (next!) :type ":thereis"}) (parse-clause!)))
|
||||||
|
((= u "RETURN")
|
||||||
|
(do (next!) (set! return-expr (next!)) (parse-clause!)))
|
||||||
|
((= u "FINALLY")
|
||||||
|
(do
|
||||||
|
(next!)
|
||||||
|
(define
|
||||||
|
(collect-finally!)
|
||||||
|
(if
|
||||||
|
(nil? (lp-peek))
|
||||||
|
nil
|
||||||
|
(do
|
||||||
|
(set!
|
||||||
|
finally-forms
|
||||||
|
(append finally-forms (list (next!))))
|
||||||
|
(collect-finally!))))
|
||||||
|
(collect-finally!)
|
||||||
|
(parse-clause!)))
|
||||||
|
(:else
|
||||||
|
(do
|
||||||
|
(set! body-forms (append body-forms (list (next!))))
|
||||||
|
(parse-clause!)))))))))
|
||||||
|
(parse-clause!)
|
||||||
|
(define let-bindings (list))
|
||||||
|
(for-each
|
||||||
|
(fn (wb) (set! let-bindings (append let-bindings (list wb))))
|
||||||
|
with-bindings)
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(v)
|
||||||
|
(set!
|
||||||
|
let-bindings
|
||||||
|
(append let-bindings (list (list v (get accum-vars v))))))
|
||||||
|
(keys accum-vars))
|
||||||
|
(when
|
||||||
|
(not (nil? repeat-var))
|
||||||
|
(set!
|
||||||
|
let-bindings
|
||||||
|
(append let-bindings (list (list repeat-var repeat-count)))))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(fb)
|
||||||
|
(let
|
||||||
|
((type (get fb "type")))
|
||||||
|
(cond
|
||||||
|
((= type ":list")
|
||||||
|
(do
|
||||||
|
(set!
|
||||||
|
let-bindings
|
||||||
|
(append
|
||||||
|
let-bindings
|
||||||
|
(list (list (get fb "tail") (get fb "list")))
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
(get fb "var")
|
||||||
|
(list
|
||||||
|
"IF"
|
||||||
|
(list "CONSP" (get fb "tail"))
|
||||||
|
(list "CAR" (get fb "tail"))
|
||||||
|
nil)))))
|
||||||
|
nil))
|
||||||
|
((= type ":numeric")
|
||||||
|
(set!
|
||||||
|
let-bindings
|
||||||
|
(append
|
||||||
|
let-bindings
|
||||||
|
(list (list (get fb "var") (get fb "from"))))))
|
||||||
|
((= type ":general")
|
||||||
|
(set!
|
||||||
|
let-bindings
|
||||||
|
(append
|
||||||
|
let-bindings
|
||||||
|
(list (list (get fb "var") (get fb "init"))))))
|
||||||
|
(:else nil))))
|
||||||
|
for-bindings)
|
||||||
|
(define all-tests (list))
|
||||||
|
(when
|
||||||
|
(not (nil? repeat-var))
|
||||||
|
(set!
|
||||||
|
all-tests
|
||||||
|
(append
|
||||||
|
all-tests
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
"WHEN"
|
||||||
|
(list "<=" repeat-var 0)
|
||||||
|
(list "RETURN-FROM" block-name (if (nil? result-var) nil result-var))))))
|
||||||
|
(set!
|
||||||
|
body-forms
|
||||||
|
(append
|
||||||
|
(list (list "SETQ" repeat-var (list "-" repeat-var 1)))
|
||||||
|
body-forms)))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(fb)
|
||||||
|
(when
|
||||||
|
(= (get fb "type") ":list")
|
||||||
|
(let
|
||||||
|
((tvar (get fb "tail")) (var (get fb "var")))
|
||||||
|
(set!
|
||||||
|
all-tests
|
||||||
|
(append
|
||||||
|
all-tests
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
"WHEN"
|
||||||
|
(list "NULL" tvar)
|
||||||
|
(list
|
||||||
|
"RETURN-FROM"
|
||||||
|
block-name
|
||||||
|
(if (nil? result-var) nil result-var))))))
|
||||||
|
(set!
|
||||||
|
body-forms
|
||||||
|
(append
|
||||||
|
body-forms
|
||||||
|
(list
|
||||||
|
(list "SETQ" tvar (list "CDR" tvar))
|
||||||
|
(list
|
||||||
|
"SETQ"
|
||||||
|
var
|
||||||
|
(list "IF" (list "CONSP" tvar) (list "CAR" tvar) nil))))))))
|
||||||
|
for-bindings)
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(fb)
|
||||||
|
(when
|
||||||
|
(= (get fb "type") ":numeric")
|
||||||
|
(let
|
||||||
|
((var (get fb "var"))
|
||||||
|
(dir (get fb "dir"))
|
||||||
|
(lim (get fb "limit"))
|
||||||
|
(ltype (get fb "limit-type"))
|
||||||
|
(step (get fb "step")))
|
||||||
|
(when
|
||||||
|
(not (nil? lim))
|
||||||
|
(let
|
||||||
|
((test-op (cond ((or (= ltype "BELOW") (= ltype "ABOVE")) (if (= dir ":up") ">=" "<=")) ((or (= ltype "TO") (= ltype "UPTO")) ">") ((= ltype "DOWNTO") "<") (:else (if (= dir ":up") ">" "<")))))
|
||||||
|
(set!
|
||||||
|
all-tests
|
||||||
|
(append
|
||||||
|
all-tests
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
"WHEN"
|
||||||
|
(list test-op var lim)
|
||||||
|
(list
|
||||||
|
"RETURN-FROM"
|
||||||
|
block-name
|
||||||
|
(if (nil? result-var) nil result-var))))))))
|
||||||
|
(let
|
||||||
|
((step-op (if (or (= dir ":down") (= ltype "DOWNTO") (= ltype "ABOVE")) "-" "+")))
|
||||||
|
(set!
|
||||||
|
body-forms
|
||||||
|
(append
|
||||||
|
body-forms
|
||||||
|
(list (list "SETQ" var (list step-op var step)))))))))
|
||||||
|
for-bindings)
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(fb)
|
||||||
|
(when
|
||||||
|
(= (get fb "type") ":general")
|
||||||
|
(set!
|
||||||
|
body-forms
|
||||||
|
(append
|
||||||
|
body-forms
|
||||||
|
(list (list "SETQ" (get fb "var") (get fb "then")))))))
|
||||||
|
for-bindings)
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(t)
|
||||||
|
(let
|
||||||
|
((type (get t "type")) (expr (get t "expr")))
|
||||||
|
(if
|
||||||
|
(= type ":while")
|
||||||
|
(set!
|
||||||
|
all-tests
|
||||||
|
(append
|
||||||
|
all-tests
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
"WHEN"
|
||||||
|
(list "NOT" expr)
|
||||||
|
(list
|
||||||
|
"RETURN-FROM"
|
||||||
|
block-name
|
||||||
|
(if (nil? result-var) nil result-var))))))
|
||||||
|
(set!
|
||||||
|
all-tests
|
||||||
|
(append
|
||||||
|
all-tests
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
"WHEN"
|
||||||
|
expr
|
||||||
|
(list
|
||||||
|
"RETURN-FROM"
|
||||||
|
block-name
|
||||||
|
(if (nil? result-var) nil result-var)))))))))
|
||||||
|
test-forms)
|
||||||
|
(when
|
||||||
|
(not (nil? termination))
|
||||||
|
(let
|
||||||
|
((type (get termination "type")) (expr (get termination "expr")))
|
||||||
|
(cond
|
||||||
|
((= type ":always")
|
||||||
|
(set!
|
||||||
|
body-forms
|
||||||
|
(append
|
||||||
|
body-forms
|
||||||
|
(list
|
||||||
|
(list "UNLESS" expr (list "RETURN-FROM" block-name false)))))
|
||||||
|
(set! return-expr true))
|
||||||
|
((= type ":never")
|
||||||
|
(set!
|
||||||
|
body-forms
|
||||||
|
(append
|
||||||
|
body-forms
|
||||||
|
(list
|
||||||
|
(list "WHEN" expr (list "RETURN-FROM" block-name false)))))
|
||||||
|
(set! return-expr true))
|
||||||
|
((= type ":thereis")
|
||||||
|
(set!
|
||||||
|
body-forms
|
||||||
|
(append
|
||||||
|
body-forms
|
||||||
|
(list
|
||||||
|
(list "WHEN" expr (list "RETURN-FROM" block-name expr)))))))))
|
||||||
|
(define tag "#LOOP-START")
|
||||||
|
(define
|
||||||
|
inner-body
|
||||||
|
(append (list tag) all-tests body-forms (list (list "GO" tag))))
|
||||||
|
(define
|
||||||
|
result-form
|
||||||
|
(cond
|
||||||
|
((not (nil? return-expr)) return-expr)
|
||||||
|
((not (nil? result-var)) result-var)
|
||||||
|
(:else nil)))
|
||||||
|
(define
|
||||||
|
full-body
|
||||||
|
(if
|
||||||
|
(= (len let-bindings) 0)
|
||||||
|
(append
|
||||||
|
(list "PROGN")
|
||||||
|
(list (append (list "TAGBODY") inner-body))
|
||||||
|
finally-forms
|
||||||
|
(list result-form))
|
||||||
|
(list
|
||||||
|
"LET*"
|
||||||
|
let-bindings
|
||||||
|
(append (list "TAGBODY") inner-body)
|
||||||
|
(append (list "PROGN") finally-forms (list result-form)))))
|
||||||
|
(list "BLOCK" block-name full-body)))
|
||||||
|
|
||||||
|
;; ── Install LOOP as a CL macro ────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; (loop ...) — the form arrives with head "LOOP" and rest = clauses.
|
||||||
|
;; The macro fn receives the full form.
|
||||||
|
|
||||||
|
(dict-set!
|
||||||
|
cl-macro-registry
|
||||||
|
"LOOP"
|
||||||
|
(fn (form env) (cl-loop-parse (rest form))))
|
||||||
377
lib/common-lisp/parser.sx
Normal file
377
lib/common-lisp/parser.sx
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
;; Common Lisp reader — converts token stream to CL AST forms.
|
||||||
|
;;
|
||||||
|
;; Depends on: lib/common-lisp/reader.sx (cl-tokenize)
|
||||||
|
;;
|
||||||
|
;; AST representation:
|
||||||
|
;; integer/float → SX number (or {:cl-type "float"/:ratio ...})
|
||||||
|
;; string "hello" → {:cl-type "string" :value "hello"}
|
||||||
|
;; symbol FOO → SX string "FOO" (upcase)
|
||||||
|
;; symbol NIL → nil
|
||||||
|
;; symbol T → true
|
||||||
|
;; :keyword → {:cl-type "keyword" :name "FOO"}
|
||||||
|
;; #\char → {:cl-type "char" :value "a"}
|
||||||
|
;; #:uninterned → {:cl-type "uninterned" :name "FOO"}
|
||||||
|
;; ratio 1/3 → {:cl-type "ratio" :value "1/3"}
|
||||||
|
;; float 3.14 → {:cl-type "float" :value "3.14"}
|
||||||
|
;; proper list (a b c) → SX list (a b c)
|
||||||
|
;; dotted pair (a . b) → {:cl-type "cons" :car a :cdr b}
|
||||||
|
;; vector #(a b) → {:cl-type "vector" :elements (list a b)}
|
||||||
|
;; 'x → ("QUOTE" x)
|
||||||
|
;; `x → ("QUASIQUOTE" x)
|
||||||
|
;; ,x → ("UNQUOTE" x)
|
||||||
|
;; ,@x → ("UNQUOTE-SPLICING" x)
|
||||||
|
;; #'x → ("FUNCTION" x)
|
||||||
|
;;
|
||||||
|
;; Public API:
|
||||||
|
;; (cl-read src) — parse first form from string, return form
|
||||||
|
;; (cl-read-all src) — parse all top-level forms, return list
|
||||||
|
|
||||||
|
;; ── number conversion ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-hex-val
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(let
|
||||||
|
((o (cl-ord c)))
|
||||||
|
(cond
|
||||||
|
((and (>= o 48) (<= o 57)) (- o 48))
|
||||||
|
((and (>= o 65) (<= o 70)) (+ 10 (- o 65)))
|
||||||
|
((and (>= o 97) (<= o 102)) (+ 10 (- o 97)))
|
||||||
|
(:else 0)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-parse-radix-str
|
||||||
|
(fn
|
||||||
|
(s radix start)
|
||||||
|
(let
|
||||||
|
((n (string-length s)) (i start) (acc 0))
|
||||||
|
(define
|
||||||
|
loop
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(< i n)
|
||||||
|
(do
|
||||||
|
(set! acc (+ (* acc radix) (cl-hex-val (substring s i (+ i 1)))))
|
||||||
|
(set! i (+ i 1))
|
||||||
|
(loop)))))
|
||||||
|
(loop)
|
||||||
|
acc)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-convert-integer
|
||||||
|
(fn
|
||||||
|
(s)
|
||||||
|
(let
|
||||||
|
((n (string-length s)) (neg false))
|
||||||
|
(cond
|
||||||
|
((and (> n 2) (= (substring s 0 1) "#"))
|
||||||
|
(let
|
||||||
|
((letter (downcase (substring s 1 2))))
|
||||||
|
(cond
|
||||||
|
((= letter "x") (cl-parse-radix-str s 16 2))
|
||||||
|
((= letter "b") (cl-parse-radix-str s 2 2))
|
||||||
|
((= letter "o") (cl-parse-radix-str s 8 2))
|
||||||
|
(:else (parse-int s 0)))))
|
||||||
|
(:else (parse-int s 0))))))
|
||||||
|
|
||||||
|
;; ── reader ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Read one form from token list.
|
||||||
|
;; Returns {:form F :rest remaining-toks} or {:form nil :rest toks :eof true}
|
||||||
|
(define
|
||||||
|
cl-read-form
|
||||||
|
(fn
|
||||||
|
(toks)
|
||||||
|
(if
|
||||||
|
(not toks)
|
||||||
|
{:form nil :rest toks :eof true}
|
||||||
|
(let
|
||||||
|
((tok (nth toks 0)) (nxt (rest toks)))
|
||||||
|
(let
|
||||||
|
((type (get tok "type")) (val (get tok "value")))
|
||||||
|
(cond
|
||||||
|
((= type "eof") {:form nil :rest toks :eof true})
|
||||||
|
((= type "integer") {:form (cl-convert-integer val) :rest nxt})
|
||||||
|
((= type "float") {:form {:cl-type "float" :value val} :rest nxt})
|
||||||
|
((= type "ratio") {:form {:cl-type "ratio" :value val} :rest nxt})
|
||||||
|
((= type "string") {:form {:cl-type "string" :value val} :rest nxt})
|
||||||
|
((= type "char") {:form {:cl-type "char" :value val} :rest nxt})
|
||||||
|
((= type "keyword") {:form {:cl-type "keyword" :name val} :rest nxt})
|
||||||
|
((= type "uninterned") {:form {:cl-type "uninterned" :name val} :rest nxt})
|
||||||
|
((= type "symbol")
|
||||||
|
(cond
|
||||||
|
((= val "NIL") {:form nil :rest nxt})
|
||||||
|
((= val "T") {:form true :rest nxt})
|
||||||
|
(:else {:form val :rest nxt})))
|
||||||
|
;; list forms
|
||||||
|
((= type "lparen") (cl-read-list nxt))
|
||||||
|
((= type "hash-paren") (cl-read-vector nxt))
|
||||||
|
;; reader macros that wrap the next form
|
||||||
|
((= type "quote") (cl-read-wrap "QUOTE" nxt))
|
||||||
|
((= type "backquote") (cl-read-wrap "QUASIQUOTE" nxt))
|
||||||
|
((= type "comma") (cl-read-wrap "UNQUOTE" nxt))
|
||||||
|
((= type "comma-at") (cl-read-wrap "UNQUOTE-SPLICING" nxt))
|
||||||
|
((= type "hash-quote") (cl-read-wrap "FUNCTION" nxt))
|
||||||
|
;; skip unrecognised tokens
|
||||||
|
(:else (cl-read-form nxt))))))))
|
||||||
|
|
||||||
|
;; Wrap next form in a list: (name form)
|
||||||
|
(define
|
||||||
|
cl-read-wrap
|
||||||
|
(fn
|
||||||
|
(name toks)
|
||||||
|
(let
|
||||||
|
((inner (cl-read-form toks)))
|
||||||
|
{:form (list name (get inner "form")) :rest (get inner "rest")})))
|
||||||
|
|
||||||
|
;; Read list forms until ')'; handles dotted pair (a . b)
|
||||||
|
;; Called after consuming '('
|
||||||
|
(define
|
||||||
|
cl-read-list
|
||||||
|
(fn
|
||||||
|
(toks)
|
||||||
|
(let
|
||||||
|
((result (cl-read-list-items toks (list))))
|
||||||
|
{:form (get result "items") :rest (get result "rest")})))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-read-list-items
|
||||||
|
(fn
|
||||||
|
(toks acc)
|
||||||
|
(if
|
||||||
|
(not toks)
|
||||||
|
{:items acc :rest toks}
|
||||||
|
(let
|
||||||
|
((tok (nth toks 0)))
|
||||||
|
(let
|
||||||
|
((type (get tok "type")))
|
||||||
|
(cond
|
||||||
|
((= type "eof") {:items acc :rest toks})
|
||||||
|
((= type "rparen") {:items acc :rest (rest toks)})
|
||||||
|
;; dotted pair: read one more form then expect ')'
|
||||||
|
((= type "dot")
|
||||||
|
(let
|
||||||
|
((cdr-result (cl-read-form (rest toks))))
|
||||||
|
(let
|
||||||
|
((cdr-form (get cdr-result "form"))
|
||||||
|
(after-cdr (get cdr-result "rest")))
|
||||||
|
;; skip the closing ')'
|
||||||
|
(let
|
||||||
|
((close (if after-cdr (nth after-cdr 0) nil)))
|
||||||
|
(let
|
||||||
|
((remaining
|
||||||
|
(if
|
||||||
|
(and close (= (get close "type") "rparen"))
|
||||||
|
(rest after-cdr)
|
||||||
|
after-cdr)))
|
||||||
|
;; build dotted structure
|
||||||
|
(let
|
||||||
|
((dotted (cl-build-dotted acc cdr-form)))
|
||||||
|
{:items dotted :rest remaining}))))))
|
||||||
|
(:else
|
||||||
|
(let
|
||||||
|
((item (cl-read-form toks)))
|
||||||
|
(cl-read-list-items
|
||||||
|
(get item "rest")
|
||||||
|
(concat acc (list (get item "form"))))))))))))
|
||||||
|
|
||||||
|
;; Build dotted form: (a b . c) → ((DOTTED a b) . c) style
|
||||||
|
;; In CL (a b c . d) means a proper dotted structure.
|
||||||
|
;; We represent it as {:cl-type "cons" :car a :cdr (list->dotted b c d)}
|
||||||
|
(define
|
||||||
|
cl-build-dotted
|
||||||
|
(fn
|
||||||
|
(head-items tail)
|
||||||
|
(if
|
||||||
|
(= (len head-items) 0)
|
||||||
|
tail
|
||||||
|
(if
|
||||||
|
(= (len head-items) 1)
|
||||||
|
{:cl-type "cons" :car (nth head-items 0) :cdr tail}
|
||||||
|
(let
|
||||||
|
((last-item (nth head-items (- (len head-items) 1)))
|
||||||
|
(but-last (slice head-items 0 (- (len head-items) 1))))
|
||||||
|
{:cl-type "cons"
|
||||||
|
:car (cl-build-dotted but-last (list last-item))
|
||||||
|
:cdr tail})))))
|
||||||
|
|
||||||
|
;; Read vector #(…) elements until ')'
|
||||||
|
(define
|
||||||
|
cl-read-vector
|
||||||
|
(fn
|
||||||
|
(toks)
|
||||||
|
(let
|
||||||
|
((result (cl-read-vector-items toks (list))))
|
||||||
|
{:form {:cl-type "vector" :elements (get result "items")} :rest (get result "rest")})))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-read-vector-items
|
||||||
|
(fn
|
||||||
|
(toks acc)
|
||||||
|
(if
|
||||||
|
(not toks)
|
||||||
|
{:items acc :rest toks}
|
||||||
|
(let
|
||||||
|
((tok (nth toks 0)))
|
||||||
|
(let
|
||||||
|
((type (get tok "type")))
|
||||||
|
(cond
|
||||||
|
((= type "eof") {:items acc :rest toks})
|
||||||
|
((= type "rparen") {:items acc :rest (rest toks)})
|
||||||
|
(:else
|
||||||
|
(let
|
||||||
|
((item (cl-read-form toks)))
|
||||||
|
(cl-read-vector-items
|
||||||
|
(get item "rest")
|
||||||
|
(concat acc (list (get item "form"))))))))))))
|
||||||
|
|
||||||
|
;; ── lambda-list parser ───────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; (cl-parse-lambda-list forms) — parse a list of CL forms (already read)
|
||||||
|
;; into a structured dict:
|
||||||
|
;; {:required (list sym ...)
|
||||||
|
;; :optional (list {:name N :default D :supplied S} ...)
|
||||||
|
;; :rest nil | "SYM"
|
||||||
|
;; :key (list {:name N :keyword K :default D :supplied S} ...)
|
||||||
|
;; :allow-other-keys false | true
|
||||||
|
;; :aux (list {:name N :init I} ...)}
|
||||||
|
;;
|
||||||
|
;; Symbols arrive as SX strings (upcase). &-markers are strings like "&OPTIONAL".
|
||||||
|
;; Key params: keyword is the upcase name string; caller uses it as :keyword.
|
||||||
|
;; Supplied-p: nil when absent.
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-parse-opt-spec
|
||||||
|
(fn
|
||||||
|
(spec)
|
||||||
|
(if
|
||||||
|
(list? spec)
|
||||||
|
{:name (nth spec 0)
|
||||||
|
:default (if (> (len spec) 1) (nth spec 1) nil)
|
||||||
|
:supplied (if (> (len spec) 2) (nth spec 2) nil)}
|
||||||
|
{:name spec :default nil :supplied nil})))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-parse-key-spec
|
||||||
|
(fn
|
||||||
|
(spec)
|
||||||
|
(if
|
||||||
|
(list? spec)
|
||||||
|
(let
|
||||||
|
((first (nth spec 0)))
|
||||||
|
(if
|
||||||
|
(list? first)
|
||||||
|
;; ((:keyword var) default supplied-p)
|
||||||
|
{:name (nth first 1)
|
||||||
|
:keyword (get first "name")
|
||||||
|
:default (if (> (len spec) 1) (nth spec 1) nil)
|
||||||
|
:supplied (if (> (len spec) 2) (nth spec 2) nil)}
|
||||||
|
;; (var default supplied-p)
|
||||||
|
{:name first
|
||||||
|
:keyword first
|
||||||
|
:default (if (> (len spec) 1) (nth spec 1) nil)
|
||||||
|
:supplied (if (> (len spec) 2) (nth spec 2) nil)}))
|
||||||
|
{:name spec :keyword spec :default nil :supplied nil})))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-parse-aux-spec
|
||||||
|
(fn
|
||||||
|
(spec)
|
||||||
|
(if
|
||||||
|
(list? spec)
|
||||||
|
{:name (nth spec 0) :init (if (> (len spec) 1) (nth spec 1) nil)}
|
||||||
|
{:name spec :init nil})))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-parse-lambda-list
|
||||||
|
(fn
|
||||||
|
(forms)
|
||||||
|
(let
|
||||||
|
((state "required")
|
||||||
|
(required (list))
|
||||||
|
(optional (list))
|
||||||
|
(rest-name nil)
|
||||||
|
(key (list))
|
||||||
|
(allow-other-keys false)
|
||||||
|
(aux (list)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
scan
|
||||||
|
(fn
|
||||||
|
(items)
|
||||||
|
(when
|
||||||
|
(> (len items) 0)
|
||||||
|
(let
|
||||||
|
((item (nth items 0)) (tail (rest items)))
|
||||||
|
(cond
|
||||||
|
((= item "&OPTIONAL")
|
||||||
|
(do (set! state "optional") (scan tail)))
|
||||||
|
((= item "&REST")
|
||||||
|
(do (set! state "rest") (scan tail)))
|
||||||
|
((= item "&BODY")
|
||||||
|
(do (set! state "rest") (scan tail)))
|
||||||
|
((= item "&KEY")
|
||||||
|
(do (set! state "key") (scan tail)))
|
||||||
|
((= item "&AUX")
|
||||||
|
(do (set! state "aux") (scan tail)))
|
||||||
|
((= item "&ALLOW-OTHER-KEYS")
|
||||||
|
(do (set! allow-other-keys true) (scan tail)))
|
||||||
|
((= state "required")
|
||||||
|
(do (append! required item) (scan tail)))
|
||||||
|
((= state "optional")
|
||||||
|
(do (append! optional (cl-parse-opt-spec item)) (scan tail)))
|
||||||
|
((= state "rest")
|
||||||
|
(do (set! rest-name item) (set! state "done") (scan tail)))
|
||||||
|
((= state "key")
|
||||||
|
(do (append! key (cl-parse-key-spec item)) (scan tail)))
|
||||||
|
((= state "aux")
|
||||||
|
(do (append! aux (cl-parse-aux-spec item)) (scan tail)))
|
||||||
|
(:else (scan tail)))))))
|
||||||
|
|
||||||
|
(scan forms)
|
||||||
|
{:required required
|
||||||
|
:optional optional
|
||||||
|
:rest rest-name
|
||||||
|
:key key
|
||||||
|
:allow-other-keys allow-other-keys
|
||||||
|
:aux aux})))
|
||||||
|
|
||||||
|
;; Convenience: parse lambda list from a CL source string
|
||||||
|
(define
|
||||||
|
cl-parse-lambda-list-str
|
||||||
|
(fn
|
||||||
|
(src)
|
||||||
|
(cl-parse-lambda-list (cl-read src))))
|
||||||
|
|
||||||
|
;; ── public API ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-read
|
||||||
|
(fn
|
||||||
|
(src)
|
||||||
|
(let
|
||||||
|
((toks (cl-tokenize src)))
|
||||||
|
(get (cl-read-form toks) "form"))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-read-all
|
||||||
|
(fn
|
||||||
|
(src)
|
||||||
|
(let
|
||||||
|
((toks (cl-tokenize src)))
|
||||||
|
(define
|
||||||
|
loop
|
||||||
|
(fn
|
||||||
|
(toks acc)
|
||||||
|
(if
|
||||||
|
(or (not toks) (= (get (nth toks 0) "type") "eof"))
|
||||||
|
acc
|
||||||
|
(let
|
||||||
|
((result (cl-read-form toks)))
|
||||||
|
(if
|
||||||
|
(get result "eof")
|
||||||
|
acc
|
||||||
|
(loop (get result "rest") (concat acc (list (get result "form")))))))))
|
||||||
|
(loop toks (list)))))
|
||||||
381
lib/common-lisp/reader.sx
Normal file
381
lib/common-lisp/reader.sx
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
;; Common Lisp tokenizer
|
||||||
|
;;
|
||||||
|
;; Tokens: {:type T :value V :pos P}
|
||||||
|
;;
|
||||||
|
;; Types:
|
||||||
|
;; "symbol" — FOO, PKG:SYM, PKG::SYM, T, NIL (upcase)
|
||||||
|
;; "keyword" — :foo (value is upcase name without colon)
|
||||||
|
;; "integer" — 42, -5, #xFF, #b1010, #o17 (string)
|
||||||
|
;; "float" — 3.14, 1.0e10 (string)
|
||||||
|
;; "ratio" — 1/3 (string "N/D")
|
||||||
|
;; "string" — unescaped content
|
||||||
|
;; "char" — single-character string
|
||||||
|
;; "lparen" "rparen" "quote" "backquote" "comma" "comma-at"
|
||||||
|
;; "hash-quote" — #'
|
||||||
|
;; "hash-paren" — #(
|
||||||
|
;; "uninterned" — #:foo (upcase name)
|
||||||
|
;; "dot" — standalone . (dotted pair separator)
|
||||||
|
;; "eof"
|
||||||
|
|
||||||
|
(define cl-make-tok (fn (type value pos) {:type type :value value :pos pos}))
|
||||||
|
|
||||||
|
;; ── char ordinal table ────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-ord-table
|
||||||
|
(let
|
||||||
|
((t (dict)) (i 0))
|
||||||
|
(define
|
||||||
|
cl-fill
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(< i 128)
|
||||||
|
(do
|
||||||
|
(dict-set! t (char-from-code i) i)
|
||||||
|
(set! i (+ i 1))
|
||||||
|
(cl-fill)))))
|
||||||
|
(cl-fill)
|
||||||
|
t))
|
||||||
|
|
||||||
|
(define cl-ord (fn (c) (or (get cl-ord-table c) 0)))
|
||||||
|
|
||||||
|
;; ── character predicates ──────────────────────────────────────────
|
||||||
|
|
||||||
|
(define cl-digit? (fn (c) (and (>= (cl-ord c) 48) (<= (cl-ord c) 57))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-hex?
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(or
|
||||||
|
(cl-digit? c)
|
||||||
|
(and (>= (cl-ord c) 65) (<= (cl-ord c) 70))
|
||||||
|
(and (>= (cl-ord c) 97) (<= (cl-ord c) 102)))))
|
||||||
|
|
||||||
|
(define cl-octal? (fn (c) (and (>= (cl-ord c) 48) (<= (cl-ord c) 55))))
|
||||||
|
|
||||||
|
(define cl-binary? (fn (c) (or (= c "0") (= c "1"))))
|
||||||
|
|
||||||
|
(define cl-ws? (fn (c) (or (= c " ") (= c "\t") (= c "\n") (= c "\r"))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-alpha?
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(or
|
||||||
|
(and (>= (cl-ord c) 65) (<= (cl-ord c) 90))
|
||||||
|
(and (>= (cl-ord c) 97) (<= (cl-ord c) 122)))))
|
||||||
|
|
||||||
|
;; Characters that end a token (whitespace + terminating macro chars)
|
||||||
|
(define
|
||||||
|
cl-terminating?
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(or
|
||||||
|
(cl-ws? c)
|
||||||
|
(= c "(")
|
||||||
|
(= c ")")
|
||||||
|
(= c "\"")
|
||||||
|
(= c ";")
|
||||||
|
(= c "`")
|
||||||
|
(= c ","))))
|
||||||
|
|
||||||
|
;; Symbol constituent: not terminating, not reader-special
|
||||||
|
(define
|
||||||
|
cl-sym-char?
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(not
|
||||||
|
(or
|
||||||
|
(cl-terminating? c)
|
||||||
|
(= c "#")
|
||||||
|
(= c "|")
|
||||||
|
(= c "\\")
|
||||||
|
(= c "'")))))
|
||||||
|
|
||||||
|
;; ── named character table ─────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-named-chars
|
||||||
|
{:space " "
|
||||||
|
:newline "\n"
|
||||||
|
:tab "\t"
|
||||||
|
:return "\r"
|
||||||
|
:backspace (char-from-code 8)
|
||||||
|
:rubout (char-from-code 127)
|
||||||
|
:delete (char-from-code 127)
|
||||||
|
:escape (char-from-code 27)
|
||||||
|
:altmode (char-from-code 27)
|
||||||
|
:null (char-from-code 0)
|
||||||
|
:nul (char-from-code 0)
|
||||||
|
:page (char-from-code 12)
|
||||||
|
:formfeed (char-from-code 12)})
|
||||||
|
|
||||||
|
;; ── main tokenizer ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-tokenize
|
||||||
|
(fn
|
||||||
|
(src)
|
||||||
|
(let
|
||||||
|
((pos 0) (n (string-length src)) (toks (list)))
|
||||||
|
|
||||||
|
(define at (fn () (if (< pos n) (substring src pos (+ pos 1)) nil)))
|
||||||
|
(define peek1 (fn () (if (< (+ pos 1) n) (substring src (+ pos 1) (+ pos 2)) nil)))
|
||||||
|
(define adv (fn () (set! pos (+ pos 1))))
|
||||||
|
|
||||||
|
;; Advance while predicate holds; return substring from start to end
|
||||||
|
(define
|
||||||
|
read-while
|
||||||
|
(fn
|
||||||
|
(pred)
|
||||||
|
(let
|
||||||
|
((start pos))
|
||||||
|
(define
|
||||||
|
rw-loop
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(and (at) (pred (at)))
|
||||||
|
(do (adv) (rw-loop)))))
|
||||||
|
(rw-loop)
|
||||||
|
(substring src start pos))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
skip-line
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(and (at) (not (= (at) "\n")))
|
||||||
|
(do (adv) (skip-line)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
skip-block
|
||||||
|
(fn
|
||||||
|
(depth)
|
||||||
|
(when
|
||||||
|
(at)
|
||||||
|
(cond
|
||||||
|
((and (= (at) "#") (= (peek1) "|"))
|
||||||
|
(do (adv) (adv) (skip-block (+ depth 1))))
|
||||||
|
((and (= (at) "|") (= (peek1) "#"))
|
||||||
|
(do
|
||||||
|
(adv)
|
||||||
|
(adv)
|
||||||
|
(when (> depth 1) (skip-block (- depth 1)))))
|
||||||
|
(:else (do (adv) (skip-block depth)))))))
|
||||||
|
|
||||||
|
;; Read string literal — called with pos just past opening "
|
||||||
|
(define
|
||||||
|
read-str
|
||||||
|
(fn
|
||||||
|
(acc)
|
||||||
|
(if
|
||||||
|
(not (at))
|
||||||
|
acc
|
||||||
|
(cond
|
||||||
|
((= (at) "\"") (do (adv) acc))
|
||||||
|
((= (at) "\\")
|
||||||
|
(do
|
||||||
|
(adv)
|
||||||
|
(let
|
||||||
|
((e (at)))
|
||||||
|
(adv)
|
||||||
|
(read-str
|
||||||
|
(str
|
||||||
|
acc
|
||||||
|
(cond
|
||||||
|
((= e "n") "\n")
|
||||||
|
((= e "t") "\t")
|
||||||
|
((= e "r") "\r")
|
||||||
|
((= e "\"") "\"")
|
||||||
|
((= e "\\") "\\")
|
||||||
|
(:else e)))))))
|
||||||
|
(:else
|
||||||
|
(let
|
||||||
|
((c (at)))
|
||||||
|
(adv)
|
||||||
|
(read-str (str acc c))))))))
|
||||||
|
|
||||||
|
;; Read #\ char literal — called with pos just past the backslash
|
||||||
|
(define
|
||||||
|
read-char-lit
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(let
|
||||||
|
((first (at)))
|
||||||
|
(adv)
|
||||||
|
(let
|
||||||
|
((rest (if (and (at) (cl-alpha? (at))) (read-while cl-alpha?) "")))
|
||||||
|
(if
|
||||||
|
(= rest "")
|
||||||
|
first
|
||||||
|
(let
|
||||||
|
((name (downcase (str first rest))))
|
||||||
|
(or (get cl-named-chars name) first)))))))
|
||||||
|
|
||||||
|
;; Number scanner — called with pos just past first digit(s).
|
||||||
|
;; acc holds what was already consumed (first digit or sign+digit).
|
||||||
|
(define
|
||||||
|
scan-num
|
||||||
|
(fn
|
||||||
|
(p acc)
|
||||||
|
(let
|
||||||
|
((more (read-while cl-digit?)))
|
||||||
|
(set! acc (str acc more))
|
||||||
|
(cond
|
||||||
|
;; ratio N/D
|
||||||
|
((and (at) (= (at) "/") (peek1) (cl-digit? (peek1)))
|
||||||
|
(do
|
||||||
|
(adv)
|
||||||
|
(let
|
||||||
|
((denom (read-while cl-digit?)))
|
||||||
|
{:type "ratio" :value (str acc "/" denom) :pos p})))
|
||||||
|
;; float: decimal point N.M[eE]
|
||||||
|
((and (at) (= (at) ".") (peek1) (cl-digit? (peek1)))
|
||||||
|
(do
|
||||||
|
(adv)
|
||||||
|
(let
|
||||||
|
((frac (read-while cl-digit?)))
|
||||||
|
(set! acc (str acc "." frac))
|
||||||
|
(when
|
||||||
|
(and (at) (or (= (at) "e") (= (at) "E")))
|
||||||
|
(do
|
||||||
|
(set! acc (str acc (at)))
|
||||||
|
(adv)
|
||||||
|
(when
|
||||||
|
(and (at) (or (= (at) "+") (= (at) "-")))
|
||||||
|
(do (set! acc (str acc (at))) (adv)))
|
||||||
|
(set! acc (str acc (read-while cl-digit?)))))
|
||||||
|
{:type "float" :value acc :pos p})))
|
||||||
|
;; float: exponent only NeE
|
||||||
|
((and (at) (or (= (at) "e") (= (at) "E")))
|
||||||
|
(do
|
||||||
|
(set! acc (str acc (at)))
|
||||||
|
(adv)
|
||||||
|
(when
|
||||||
|
(and (at) (or (= (at) "+") (= (at) "-")))
|
||||||
|
(do (set! acc (str acc (at))) (adv)))
|
||||||
|
(set! acc (str acc (read-while cl-digit?)))
|
||||||
|
{:type "float" :value acc :pos p}))
|
||||||
|
(:else {:type "integer" :value acc :pos p})))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
read-radix
|
||||||
|
(fn
|
||||||
|
(letter p)
|
||||||
|
(let
|
||||||
|
((pred
|
||||||
|
(cond
|
||||||
|
((or (= letter "x") (= letter "X")) cl-hex?)
|
||||||
|
((or (= letter "b") (= letter "B")) cl-binary?)
|
||||||
|
((or (= letter "o") (= letter "O")) cl-octal?)
|
||||||
|
(:else cl-digit?))))
|
||||||
|
{:type "integer"
|
||||||
|
:value (str "#" letter (read-while pred))
|
||||||
|
:pos p})))
|
||||||
|
|
||||||
|
(define emit (fn (tok) (append! toks tok)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
scan
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(< pos n)
|
||||||
|
(let
|
||||||
|
((c (at)) (p pos))
|
||||||
|
(cond
|
||||||
|
((cl-ws? c) (do (adv) (scan)))
|
||||||
|
((= c ";") (do (adv) (skip-line) (scan)))
|
||||||
|
((= c "(") (do (adv) (emit (cl-make-tok "lparen" "(" p)) (scan)))
|
||||||
|
((= c ")") (do (adv) (emit (cl-make-tok "rparen" ")" p)) (scan)))
|
||||||
|
((= c "'") (do (adv) (emit (cl-make-tok "quote" "'" p)) (scan)))
|
||||||
|
((= c "`") (do (adv) (emit (cl-make-tok "backquote" "`" p)) (scan)))
|
||||||
|
((= c ",")
|
||||||
|
(do
|
||||||
|
(adv)
|
||||||
|
(if
|
||||||
|
(= (at) "@")
|
||||||
|
(do (adv) (emit (cl-make-tok "comma-at" ",@" p)))
|
||||||
|
(emit (cl-make-tok "comma" "," p)))
|
||||||
|
(scan)))
|
||||||
|
((= c "\"")
|
||||||
|
(do
|
||||||
|
(adv)
|
||||||
|
(emit (cl-make-tok "string" (read-str "") p))
|
||||||
|
(scan)))
|
||||||
|
;; :keyword
|
||||||
|
((= c ":")
|
||||||
|
(do
|
||||||
|
(adv)
|
||||||
|
(emit (cl-make-tok "keyword" (upcase (read-while cl-sym-char?)) p))
|
||||||
|
(scan)))
|
||||||
|
;; dispatch macro #
|
||||||
|
((= c "#")
|
||||||
|
(do
|
||||||
|
(adv)
|
||||||
|
(let
|
||||||
|
((d (at)))
|
||||||
|
(cond
|
||||||
|
((= d "'") (do (adv) (emit (cl-make-tok "hash-quote" "#'" p)) (scan)))
|
||||||
|
((= d "(") (do (adv) (emit (cl-make-tok "hash-paren" "#(" p)) (scan)))
|
||||||
|
((= d ":")
|
||||||
|
(do
|
||||||
|
(adv)
|
||||||
|
(emit
|
||||||
|
(cl-make-tok "uninterned" (upcase (read-while cl-sym-char?)) p))
|
||||||
|
(scan)))
|
||||||
|
((= d "|") (do (adv) (skip-block 1) (scan)))
|
||||||
|
((= d "\\")
|
||||||
|
(do (adv) (emit (cl-make-tok "char" (read-char-lit) p)) (scan)))
|
||||||
|
((or (= d "x") (= d "X"))
|
||||||
|
(do (adv) (emit (read-radix d p)) (scan)))
|
||||||
|
((or (= d "b") (= d "B"))
|
||||||
|
(do (adv) (emit (read-radix d p)) (scan)))
|
||||||
|
((or (= d "o") (= d "O"))
|
||||||
|
(do (adv) (emit (read-radix d p)) (scan)))
|
||||||
|
(:else (scan))))))
|
||||||
|
;; standalone dot, float .5, or symbol starting with dots
|
||||||
|
((= c ".")
|
||||||
|
(do
|
||||||
|
(adv)
|
||||||
|
(cond
|
||||||
|
((or (not (at)) (cl-terminating? (at)))
|
||||||
|
(do (emit (cl-make-tok "dot" "." p)) (scan)))
|
||||||
|
((cl-digit? (at))
|
||||||
|
(do
|
||||||
|
(emit
|
||||||
|
(cl-make-tok "float" (str "0." (read-while cl-digit?)) p))
|
||||||
|
(scan)))
|
||||||
|
(:else
|
||||||
|
(do
|
||||||
|
(emit
|
||||||
|
(cl-make-tok "symbol" (upcase (str "." (read-while cl-sym-char?))) p))
|
||||||
|
(scan))))))
|
||||||
|
;; sign followed by digit → number
|
||||||
|
((and (or (= c "+") (= c "-")) (peek1) (cl-digit? (peek1)))
|
||||||
|
(do
|
||||||
|
(adv)
|
||||||
|
(let
|
||||||
|
((first-d (at)))
|
||||||
|
(adv)
|
||||||
|
(emit (scan-num p (str c first-d))))
|
||||||
|
(scan)))
|
||||||
|
;; decimal digit → number
|
||||||
|
((cl-digit? c)
|
||||||
|
(do
|
||||||
|
(adv)
|
||||||
|
(emit (scan-num p c))
|
||||||
|
(scan)))
|
||||||
|
;; symbol constituent (includes bare +, -, etc.)
|
||||||
|
((cl-sym-char? c)
|
||||||
|
(do
|
||||||
|
(emit (cl-make-tok "symbol" (upcase (read-while cl-sym-char?)) p))
|
||||||
|
(scan)))
|
||||||
|
(:else (do (adv) (scan))))))))
|
||||||
|
|
||||||
|
(scan)
|
||||||
|
(append! toks (cl-make-tok "eof" nil n))
|
||||||
|
toks)))
|
||||||
@@ -1,18 +1,14 @@
|
|||||||
;; lib/common-lisp/runtime.sx — CL built-ins using SX spec primitives
|
;; lib/common-lisp/runtime.sx — CL built-ins + condition system on SX
|
||||||
;;
|
;;
|
||||||
;; Provides CL-specific wrappers and helpers. Deliberately thin: wherever
|
;; Section 1-9: Type predicates, arithmetic, characters, strings, gensym,
|
||||||
;; an SX spec primitive already does the job, we alias it rather than
|
;; multiple values, sets, radix formatting, list utilities.
|
||||||
;; reinventing it.
|
;; Section 10: Condition system (define-condition, signal/error/warn,
|
||||||
|
;; handler-bind, handler-case, restart-case, invoke-restart).
|
||||||
;;
|
;;
|
||||||
;; Primitives used from spec:
|
;; Primitives used from spec:
|
||||||
;; char/char->integer/integer->char/char-upcase/char-downcase
|
;; char/char->integer/integer->char/char-upcase/char-downcase
|
||||||
;; format (Phase 21 — must be loaded before this file)
|
;; format gensym rational/rational? make-set/set-member?/etc
|
||||||
;; gensym (Phase 12)
|
;; modulo/remainder/quotient/gcd/lcm/expt number->string
|
||||||
;; rational/rational? (Phase 16)
|
|
||||||
;; make-set/set-member?/set-union/etc (Phase 18)
|
|
||||||
;; open-input-string/read-char/etc (Phase 14)
|
|
||||||
;; modulo/remainder/quotient/gcd/lcm/expt (Phase 2 / Phase 15)
|
|
||||||
;; number->string with radix (Phase 15)
|
|
||||||
|
|
||||||
;; ---------------------------------------------------------------------------
|
;; ---------------------------------------------------------------------------
|
||||||
;; 1. Type predicates
|
;; 1. Type predicates
|
||||||
@@ -304,3 +300,425 @@
|
|||||||
((or (cl-empty? plist) (cl-empty? (rest plist))) nil)
|
((or (cl-empty? plist) (cl-empty? (rest plist))) nil)
|
||||||
((equal? (first plist) key) (first (rest plist)))
|
((equal? (first plist) key) (first (rest plist)))
|
||||||
(else (cl-getf (rest (rest plist)) key))))
|
(else (cl-getf (rest (rest plist)) key))))
|
||||||
|
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
;; 10. Condition system (Phase 3)
|
||||||
|
;;
|
||||||
|
;; Condition objects:
|
||||||
|
;; {:cl-type "cl-condition" :class "NAME" :slots {slot-name val ...}}
|
||||||
|
;;
|
||||||
|
;; The built-in handler-bind / restart-case expect LITERAL handler specs in
|
||||||
|
;; source (they operate on the raw AST), so we implement our own handler and
|
||||||
|
;; restart stacks as mutable SX globals.
|
||||||
|
;; ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
;; ── condition class registry ───────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; Populated at load time with all ANSI standard condition types.
|
||||||
|
;; Also mutated by cl-define-condition.
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-condition-classes
|
||||||
|
(dict
|
||||||
|
"condition"
|
||||||
|
{:parents (list) :slots (list) :name "condition"}
|
||||||
|
"serious-condition"
|
||||||
|
{:parents (list "condition") :slots (list) :name "serious-condition"}
|
||||||
|
"error"
|
||||||
|
{:parents (list "serious-condition") :slots (list) :name "error"}
|
||||||
|
"warning"
|
||||||
|
{:parents (list "condition") :slots (list) :name "warning"}
|
||||||
|
"simple-condition"
|
||||||
|
{:parents (list "condition") :slots (list "format-control" "format-arguments") :name "simple-condition"}
|
||||||
|
"simple-error"
|
||||||
|
{:parents (list "error" "simple-condition") :slots (list "format-control" "format-arguments") :name "simple-error"}
|
||||||
|
"simple-warning"
|
||||||
|
{:parents (list "warning" "simple-condition") :slots (list "format-control" "format-arguments") :name "simple-warning"}
|
||||||
|
"type-error"
|
||||||
|
{:parents (list "error") :slots (list "datum" "expected-type") :name "type-error"}
|
||||||
|
"arithmetic-error"
|
||||||
|
{:parents (list "error") :slots (list "operation" "operands") :name "arithmetic-error"}
|
||||||
|
"division-by-zero"
|
||||||
|
{:parents (list "arithmetic-error") :slots (list) :name "division-by-zero"}
|
||||||
|
"cell-error"
|
||||||
|
{:parents (list "error") :slots (list "name") :name "cell-error"}
|
||||||
|
"unbound-variable"
|
||||||
|
{:parents (list "cell-error") :slots (list) :name "unbound-variable"}
|
||||||
|
"undefined-function"
|
||||||
|
{:parents (list "cell-error") :slots (list) :name "undefined-function"}
|
||||||
|
"program-error"
|
||||||
|
{:parents (list "error") :slots (list) :name "program-error"}
|
||||||
|
"storage-condition"
|
||||||
|
{:parents (list "serious-condition") :slots (list) :name "storage-condition"}))
|
||||||
|
|
||||||
|
;; ── condition predicates ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-condition?
|
||||||
|
(fn (x) (and (dict? x) (= (get x "cl-type") "cl-condition"))))
|
||||||
|
|
||||||
|
;; cl-condition-of-type? walks the class hierarchy.
|
||||||
|
;; We capture cl-condition-classes at define time via let to avoid
|
||||||
|
;; free-variable scoping issues at call time.
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-condition-of-type?
|
||||||
|
(let
|
||||||
|
((classes cl-condition-classes))
|
||||||
|
(fn
|
||||||
|
(c type-name)
|
||||||
|
(if
|
||||||
|
(not (cl-condition? c))
|
||||||
|
false
|
||||||
|
(let
|
||||||
|
((class-name (get c "class")))
|
||||||
|
(define
|
||||||
|
check
|
||||||
|
(fn
|
||||||
|
(n)
|
||||||
|
(if
|
||||||
|
(= n type-name)
|
||||||
|
true
|
||||||
|
(let
|
||||||
|
((entry (get classes n)))
|
||||||
|
(if
|
||||||
|
(nil? entry)
|
||||||
|
false
|
||||||
|
(some (fn (p) (check p)) (get entry "parents")))))))
|
||||||
|
(check class-name))))))
|
||||||
|
|
||||||
|
;; ── condition constructors ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; cl-define-condition registers a new condition class.
|
||||||
|
;; name: string (condition class name)
|
||||||
|
;; parents: list of strings (parent class names)
|
||||||
|
;; slot-names: list of strings
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-define-condition
|
||||||
|
(fn
|
||||||
|
(name parents slot-names)
|
||||||
|
(begin (dict-set! cl-condition-classes name {:parents parents :slots slot-names :name name}) name)))
|
||||||
|
|
||||||
|
;; cl-make-condition constructs a condition object.
|
||||||
|
;; Keyword args (alternating slot-name/value pairs) populate the slots dict.
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-make-condition
|
||||||
|
(fn
|
||||||
|
(name &rest kw-args)
|
||||||
|
(let
|
||||||
|
((slots (dict)))
|
||||||
|
(define
|
||||||
|
fill
|
||||||
|
(fn
|
||||||
|
(args)
|
||||||
|
(when
|
||||||
|
(>= (len args) 2)
|
||||||
|
(begin
|
||||||
|
(dict-set! slots (first args) (first (rest args)))
|
||||||
|
(fill (rest (rest args)))))))
|
||||||
|
(fill kw-args)
|
||||||
|
{:cl-type "cl-condition" :slots slots :class name})))
|
||||||
|
|
||||||
|
;; ── condition accessors ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-condition-slot
|
||||||
|
(fn
|
||||||
|
(c slot-name)
|
||||||
|
(if (cl-condition? c) (get (get c "slots") slot-name) nil)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-condition-message
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(if
|
||||||
|
(not (cl-condition? c))
|
||||||
|
(str c)
|
||||||
|
(let
|
||||||
|
((slots (get c "slots")))
|
||||||
|
(or
|
||||||
|
(get slots "message")
|
||||||
|
(get slots "format-control")
|
||||||
|
(str "Condition: " (get c "class")))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-simple-condition-format-control
|
||||||
|
(fn (c) (cl-condition-slot c "format-control")))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-simple-condition-format-arguments
|
||||||
|
(fn (c) (cl-condition-slot c "format-arguments")))
|
||||||
|
|
||||||
|
(define cl-type-error-datum (fn (c) (cl-condition-slot c "datum")))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-type-error-expected-type
|
||||||
|
(fn (c) (cl-condition-slot c "expected-type")))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-arithmetic-error-operation
|
||||||
|
(fn (c) (cl-condition-slot c "operation")))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-arithmetic-error-operands
|
||||||
|
(fn (c) (cl-condition-slot c "operands")))
|
||||||
|
|
||||||
|
;; ── mutable handler + restart stacks ──────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; Handler entry: {:type "type-name" :fn (fn (condition) result)}
|
||||||
|
;; Restart entry: {:name "restart-name" :fn (fn (&optional arg) result) :escape k}
|
||||||
|
;;
|
||||||
|
;; New handlers are prepended (checked first = most recent handler wins).
|
||||||
|
|
||||||
|
(define cl-handler-stack (list))
|
||||||
|
(define cl-restart-stack (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-push-handlers
|
||||||
|
(fn (entries) (set! cl-handler-stack (append entries cl-handler-stack))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-pop-handlers
|
||||||
|
(fn
|
||||||
|
(n)
|
||||||
|
(set! cl-handler-stack (slice cl-handler-stack n (len cl-handler-stack)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-push-restarts
|
||||||
|
(fn (entries) (set! cl-restart-stack (append entries cl-restart-stack))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-pop-restarts
|
||||||
|
(fn
|
||||||
|
(n)
|
||||||
|
(set! cl-restart-stack (slice cl-restart-stack n (len cl-restart-stack)))))
|
||||||
|
|
||||||
|
;; ── *debugger-hook* + invoke-debugger ────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; cl-debugger-hook: called when an error propagates with no handler.
|
||||||
|
;; Signature: (fn (condition hook) result). The hook arg is itself
|
||||||
|
;; (so the hook can rebind it to nil to prevent recursion).
|
||||||
|
;; nil = use default (re-raise as host error).
|
||||||
|
|
||||||
|
(define cl-debugger-hook nil)
|
||||||
|
|
||||||
|
(define cl-invoke-debugger
|
||||||
|
(fn (c)
|
||||||
|
(if (nil? cl-debugger-hook)
|
||||||
|
(error (str "Debugger: " (cl-condition-message c)))
|
||||||
|
(let ((hook cl-debugger-hook))
|
||||||
|
(set! cl-debugger-hook nil)
|
||||||
|
(let ((result (hook c hook)))
|
||||||
|
(set! cl-debugger-hook hook)
|
||||||
|
result)))))
|
||||||
|
|
||||||
|
;; ── *break-on-signals* ────────────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; When set to a type name string, cl-signal invokes the debugger hook
|
||||||
|
;; before walking handlers if the condition is of that type.
|
||||||
|
;; nil = disabled (ANSI default).
|
||||||
|
|
||||||
|
(define cl-break-on-signals nil)
|
||||||
|
|
||||||
|
;; ── invoke-restart-interactively ──────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; Like invoke-restart but calls the restart's fn with no arguments
|
||||||
|
;; (real CL would prompt the user for each arg via :interactive).
|
||||||
|
|
||||||
|
(define cl-invoke-restart-interactively
|
||||||
|
(fn (name)
|
||||||
|
(let ((entry (cl-find-restart-entry name cl-restart-stack)))
|
||||||
|
(if (nil? entry)
|
||||||
|
(error (str "No active restart: " name))
|
||||||
|
(let ((restart-fn (get entry "fn"))
|
||||||
|
(escape (get entry "escape")))
|
||||||
|
(escape (restart-fn)))))))
|
||||||
|
|
||||||
|
;; ── cl-signal (non-unwinding) ─────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; Walks cl-handler-stack; for each matching entry, calls the handler fn.
|
||||||
|
;; Handlers return normally — signal continues to the next matching handler.
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-signal-obj
|
||||||
|
(fn
|
||||||
|
(obj stack)
|
||||||
|
(if
|
||||||
|
(empty? stack)
|
||||||
|
nil
|
||||||
|
(let
|
||||||
|
((entry (first stack)))
|
||||||
|
(if
|
||||||
|
(cl-condition-of-type? obj (get entry "type"))
|
||||||
|
(begin ((get entry "fn") obj) (cl-signal-obj obj (rest stack)))
|
||||||
|
(cl-signal-obj obj (rest stack)))))))
|
||||||
|
|
||||||
|
(define cl-signal
|
||||||
|
(fn (c)
|
||||||
|
(let ((obj (if (cl-condition? c)
|
||||||
|
c
|
||||||
|
(cl-make-condition "simple-condition"
|
||||||
|
"format-control" (str c)))))
|
||||||
|
;; *break-on-signals*: invoke debugger hook when type matches
|
||||||
|
(when (and (not (nil? cl-break-on-signals))
|
||||||
|
(cl-condition-of-type? obj cl-break-on-signals))
|
||||||
|
(cl-invoke-debugger obj))
|
||||||
|
(cl-signal-obj obj cl-handler-stack))))
|
||||||
|
|
||||||
|
;; ── cl-error ───────────────────────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; Signals an error. If no handler catches it, raises a host-level error.
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-error
|
||||||
|
(fn
|
||||||
|
(c &rest args)
|
||||||
|
(let
|
||||||
|
((obj (cond ((cl-condition? c) c) ((string? c) (cl-make-condition "simple-error" "format-control" c "format-arguments" args)) (:else (cl-make-condition "simple-error" "format-control" (str c))))))
|
||||||
|
(cl-signal-obj obj cl-handler-stack)
|
||||||
|
(cl-invoke-debugger obj))))
|
||||||
|
|
||||||
|
;; ── cl-warn ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-warn
|
||||||
|
(fn
|
||||||
|
(c &rest args)
|
||||||
|
(let
|
||||||
|
((obj (cond ((cl-condition? c) c) ((string? c) (cl-make-condition "simple-warning" "format-control" c "format-arguments" args)) (:else (cl-make-condition "simple-warning" "format-control" (str c))))))
|
||||||
|
(cl-signal-obj obj cl-handler-stack))))
|
||||||
|
|
||||||
|
;; ── cl-handler-bind (non-unwinding) ───────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; bindings: list of (type-name handler-fn) pairs
|
||||||
|
;; thunk: (fn () body)
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-handler-bind
|
||||||
|
(fn
|
||||||
|
(bindings thunk)
|
||||||
|
(let
|
||||||
|
((entries (map (fn (b) {:fn (first (rest b)) :type (first b)}) bindings)))
|
||||||
|
(begin
|
||||||
|
(cl-push-handlers entries)
|
||||||
|
(let
|
||||||
|
((result (thunk)))
|
||||||
|
(begin (cl-pop-handlers (len entries)) result))))))
|
||||||
|
|
||||||
|
;; ── cl-handler-case (unwinding) ───────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; thunk: (fn () body)
|
||||||
|
;; cases: list of (type-name handler-fn) pairs
|
||||||
|
;;
|
||||||
|
;; Uses call/cc for the escape continuation.
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-handler-case
|
||||||
|
(fn
|
||||||
|
(thunk &rest cases)
|
||||||
|
(call/cc
|
||||||
|
(fn
|
||||||
|
(escape)
|
||||||
|
(let
|
||||||
|
((entries (map (fn (c) {:fn (fn (x) (escape ((first (rest c)) x))) :type (first c)}) cases)))
|
||||||
|
(begin
|
||||||
|
(cl-push-handlers entries)
|
||||||
|
(let
|
||||||
|
((result (thunk)))
|
||||||
|
(begin (cl-pop-handlers (len entries)) result))))))))
|
||||||
|
|
||||||
|
;; ── cl-restart-case ────────────────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; thunk: (fn () body)
|
||||||
|
;; restarts: list of (name params body-fn) triples
|
||||||
|
;; body-fn is (fn () val) or (fn (arg) val)
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-restart-case
|
||||||
|
(fn
|
||||||
|
(thunk &rest restarts)
|
||||||
|
(call/cc
|
||||||
|
(fn
|
||||||
|
(escape)
|
||||||
|
(let
|
||||||
|
((entries (map (fn (r) {:fn (first (rest (rest r))) :escape escape :name (first r)}) restarts)))
|
||||||
|
(begin
|
||||||
|
(cl-push-restarts entries)
|
||||||
|
(let
|
||||||
|
((result (thunk)))
|
||||||
|
(begin (cl-pop-restarts (len entries)) result))))))))
|
||||||
|
|
||||||
|
;; ── cl-with-simple-restart ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-with-simple-restart
|
||||||
|
(fn
|
||||||
|
(name description thunk)
|
||||||
|
(cl-restart-case thunk (list name (list) (fn () nil)))))
|
||||||
|
|
||||||
|
;; ── find-restart / invoke-restart / compute-restarts ──────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-find-restart-entry
|
||||||
|
(fn
|
||||||
|
(name stack)
|
||||||
|
(if
|
||||||
|
(empty? stack)
|
||||||
|
nil
|
||||||
|
(let
|
||||||
|
((entry (first stack)))
|
||||||
|
(if
|
||||||
|
(= (get entry "name") name)
|
||||||
|
entry
|
||||||
|
(cl-find-restart-entry name (rest stack)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-find-restart
|
||||||
|
(fn (name) (cl-find-restart-entry name cl-restart-stack)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-invoke-restart
|
||||||
|
(fn
|
||||||
|
(name &rest args)
|
||||||
|
(let
|
||||||
|
((entry (cl-find-restart-entry name cl-restart-stack)))
|
||||||
|
(if
|
||||||
|
(nil? entry)
|
||||||
|
(error (str "No active restart: " name))
|
||||||
|
(let
|
||||||
|
((restart-fn (get entry "fn")) (escape (get entry "escape")))
|
||||||
|
(escape
|
||||||
|
(if (empty? args) (restart-fn) (restart-fn (first args)))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-compute-restarts
|
||||||
|
(fn () (map (fn (e) (get e "name")) cl-restart-stack)))
|
||||||
|
|
||||||
|
;; ── with-condition-restarts (stub — association is advisory) ──────────────
|
||||||
|
|
||||||
|
(define cl-with-condition-restarts (fn (c restarts thunk) (thunk)))
|
||||||
|
|
||||||
|
;; ── cl-cerror ──────────────────────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; Signals a continuable error. The "continue" restart is established;
|
||||||
|
;; invoke-restart "continue" to proceed past the error.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; ── cl-cerror ──────────────────────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; Signals a continuable error. The "continue" restart is established;
|
||||||
|
;; invoke-restart "continue" to proceed past the error.
|
||||||
|
|
||||||
|
(define cl-cerror
|
||||||
|
(fn (continue-string c &rest args)
|
||||||
|
(let ((obj (if (cl-condition? c)
|
||||||
|
c
|
||||||
|
(cl-make-condition "simple-error"
|
||||||
|
"format-control" (str c)
|
||||||
|
"format-arguments" args))))
|
||||||
|
(cl-restart-case
|
||||||
|
(fn () (cl-signal-obj obj cl-handler-stack))
|
||||||
|
(list "continue" (list) (fn () nil))))))
|
||||||
19
lib/common-lisp/scoreboard.json
Normal file
19
lib/common-lisp/scoreboard.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"generated": "2026-05-05T12:35:09Z",
|
||||||
|
"total_pass": 518,
|
||||||
|
"total_fail": 0,
|
||||||
|
"suites": [
|
||||||
|
{"name": "Phase 1: tokenizer/reader", "pass": 79, "fail": 0},
|
||||||
|
{"name": "Phase 1: parser/lambda-lists", "pass": 31, "fail": 0},
|
||||||
|
{"name": "Phase 2: evaluator", "pass": 182, "fail": 0},
|
||||||
|
{"name": "Phase 3: condition system", "pass": 59, "fail": 0},
|
||||||
|
{"name": "Phase 3: restart-demo", "pass": 7, "fail": 0},
|
||||||
|
{"name": "Phase 3: parse-recover", "pass": 6, "fail": 0},
|
||||||
|
{"name": "Phase 3: interactive-debugger", "pass": 7, "fail": 0},
|
||||||
|
{"name": "Phase 4: CLOS", "pass": 41, "fail": 0},
|
||||||
|
{"name": "Phase 4: geometry", "pass": 12, "fail": 0},
|
||||||
|
{"name": "Phase 4: mop-trace", "pass": 13, "fail": 0},
|
||||||
|
{"name": "Phase 5: macros+LOOP", "pass": 27, "fail": 0},
|
||||||
|
{"name": "Phase 6: stdlib", "pass": 54, "fail": 0}
|
||||||
|
]
|
||||||
|
}
|
||||||
20
lib/common-lisp/scoreboard.md
Normal file
20
lib/common-lisp/scoreboard.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Common Lisp on SX — Scoreboard
|
||||||
|
|
||||||
|
_Generated: 2026-05-05 12:35 UTC_
|
||||||
|
|
||||||
|
| Suite | Pass | Fail | Status |
|
||||||
|
|-------|------|------|--------|
|
||||||
|
| Phase 1: tokenizer/reader | 79 | 0 | pass |
|
||||||
|
| Phase 1: parser/lambda-lists | 31 | 0 | pass |
|
||||||
|
| Phase 2: evaluator | 182 | 0 | pass |
|
||||||
|
| Phase 3: condition system | 59 | 0 | pass |
|
||||||
|
| Phase 3: restart-demo | 7 | 0 | pass |
|
||||||
|
| Phase 3: parse-recover | 6 | 0 | pass |
|
||||||
|
| Phase 3: interactive-debugger | 7 | 0 | pass |
|
||||||
|
| Phase 4: CLOS | 41 | 0 | pass |
|
||||||
|
| Phase 4: geometry | 12 | 0 | pass |
|
||||||
|
| Phase 4: mop-trace | 13 | 0 | pass |
|
||||||
|
| Phase 5: macros+LOOP | 27 | 0 | pass |
|
||||||
|
| Phase 6: stdlib | 54 | 0 | pass |
|
||||||
|
|
||||||
|
**Total: 518 passed, 0 failed**
|
||||||
@@ -292,6 +292,147 @@ check 113 "cl-format-decimal 42" '"42"'
|
|||||||
check 114 "n->s base 16" '"1f"'
|
check 114 "n->s base 16" '"1f"'
|
||||||
check 115 "s->n base 16" "31"
|
check 115 "s->n base 16" "31"
|
||||||
|
|
||||||
|
# ── Phase 2: condition system unit tests ─────────────────────────────────────
|
||||||
|
# Load runtime.sx then conditions.sx; query the passed/failed/failures globals.
|
||||||
|
UNIT_FILE=$(mktemp); trap "rm -f $UNIT_FILE" EXIT
|
||||||
|
cat > "$UNIT_FILE" << 'UNIT'
|
||||||
|
(epoch 1)
|
||||||
|
(load "spec/stdlib.sx")
|
||||||
|
(epoch 2)
|
||||||
|
(load "lib/common-lisp/runtime.sx")
|
||||||
|
(epoch 3)
|
||||||
|
(load "lib/common-lisp/tests/conditions.sx")
|
||||||
|
(epoch 4)
|
||||||
|
(eval "passed")
|
||||||
|
(epoch 5)
|
||||||
|
(eval "failed")
|
||||||
|
(epoch 6)
|
||||||
|
(eval "failures")
|
||||||
|
UNIT
|
||||||
|
|
||||||
|
UNIT_OUT=$(timeout 30 "$SX_SERVER" < "$UNIT_FILE" 2>/dev/null)
|
||||||
|
|
||||||
|
# extract passed/failed counts from ok-len lines
|
||||||
|
UNIT_PASSED=$(echo "$UNIT_OUT" | grep -A1 "^(ok-len 4 " | tail -1 || true)
|
||||||
|
UNIT_FAILED=$(echo "$UNIT_OUT" | grep -A1 "^(ok-len 5 " | tail -1 || true)
|
||||||
|
UNIT_ERRS=$(echo "$UNIT_OUT" | grep -A1 "^(ok-len 6 " | tail -1 || true)
|
||||||
|
# fallback: try plain ok lines
|
||||||
|
[ -z "$UNIT_PASSED" ] && UNIT_PASSED=$(echo "$UNIT_OUT" | grep "^(ok 4 " | awk '{print $3}' | tr -d ')' || true)
|
||||||
|
[ -z "$UNIT_FAILED" ] && UNIT_FAILED=$(echo "$UNIT_OUT" | grep "^(ok 5 " | awk '{print $3}' | tr -d ')' || true)
|
||||||
|
[ -z "$UNIT_PASSED" ] && UNIT_PASSED=0
|
||||||
|
[ -z "$UNIT_FAILED" ] && UNIT_FAILED=0
|
||||||
|
|
||||||
|
if [ "$UNIT_FAILED" = "0" ] && [ "$UNIT_PASSED" -gt 0 ] 2>/dev/null; then
|
||||||
|
PASS=$((PASS + UNIT_PASSED))
|
||||||
|
[ "$VERBOSE" = "-v" ] && echo " ok condition tests ($UNIT_PASSED)"
|
||||||
|
else
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
ERRORS+=" FAIL [condition tests] (${UNIT_PASSED} passed, ${UNIT_FAILED} failed) ${UNIT_ERRS}
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Phase 3: classic program tests ───────────────────────────────────────────
|
||||||
|
run_program_suite() {
|
||||||
|
local prog="$1" pass_var="$2" fail_var="$3" failures_var="$4"
|
||||||
|
local PROG_FILE=$(mktemp)
|
||||||
|
printf '(epoch 1)\n(load "spec/stdlib.sx")\n(epoch 2)\n(load "lib/common-lisp/runtime.sx")\n(epoch 3)\n(load "%s")\n(epoch 4)\n(eval "%s")\n(epoch 5)\n(eval "%s")\n(epoch 6)\n(eval "%s")\n' \
|
||||||
|
"$prog" "$pass_var" "$fail_var" "$failures_var" > "$PROG_FILE"
|
||||||
|
local OUT; OUT=$(timeout 20 "$SX_SERVER" < "$PROG_FILE" 2>/dev/null)
|
||||||
|
rm -f "$PROG_FILE"
|
||||||
|
local P F
|
||||||
|
P=$(echo "$OUT" | grep -A1 "^(ok-len 4 " | tail -1 || true)
|
||||||
|
F=$(echo "$OUT" | grep -A1 "^(ok-len 5 " | tail -1 || true)
|
||||||
|
local ERRS; ERRS=$(echo "$OUT" | grep -A1 "^(ok-len 6 " | tail -1 || true)
|
||||||
|
[ -z "$P" ] && P=0; [ -z "$F" ] && F=0
|
||||||
|
if [ "$F" = "0" ] && [ "$P" -gt 0 ] 2>/dev/null; then
|
||||||
|
PASS=$((PASS + P))
|
||||||
|
[ "$VERBOSE" = "-v" ] && echo " ok $prog ($P)"
|
||||||
|
else
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
ERRORS+=" FAIL [$prog] (${P} passed, ${F} failed) ${ERRS}
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_program_suite \
|
||||||
|
"lib/common-lisp/tests/programs/restart-demo.sx" \
|
||||||
|
"demo-passed" "demo-failed" "demo-failures"
|
||||||
|
|
||||||
|
run_program_suite \
|
||||||
|
"lib/common-lisp/tests/programs/parse-recover.sx" \
|
||||||
|
"parse-passed" "parse-failed" "parse-failures"
|
||||||
|
|
||||||
|
run_program_suite \
|
||||||
|
"lib/common-lisp/tests/programs/interactive-debugger.sx" \
|
||||||
|
"debugger-passed" "debugger-failed" "debugger-failures"
|
||||||
|
|
||||||
|
# ── Phase 4: CLOS unit tests ─────────────────────────────────────────────────
|
||||||
|
CLOS_FILE=$(mktemp); trap "rm -f $CLOS_FILE" EXIT
|
||||||
|
printf '(epoch 1)\n(load "spec/stdlib.sx")\n(epoch 2)\n(load "lib/common-lisp/runtime.sx")\n(epoch 3)\n(load "lib/common-lisp/clos.sx")\n(epoch 4)\n(load "lib/common-lisp/tests/clos.sx")\n(epoch 5)\n(eval "passed")\n(epoch 6)\n(eval "failed")\n(epoch 7)\n(eval "failures")\n' > "$CLOS_FILE"
|
||||||
|
CLOS_OUT=$(timeout 30 "$SX_SERVER" < "$CLOS_FILE" 2>/dev/null)
|
||||||
|
rm -f "$CLOS_FILE"
|
||||||
|
CLOS_PASSED=$(echo "$CLOS_OUT" | grep -A1 "^(ok-len 5 " | tail -1 || true)
|
||||||
|
CLOS_FAILED=$(echo "$CLOS_OUT" | grep -A1 "^(ok-len 6 " | tail -1 || true)
|
||||||
|
[ -z "$CLOS_PASSED" ] && CLOS_PASSED=$(echo "$CLOS_OUT" | grep "^(ok 5 " | awk '{print $3}' | tr -d ')' || true)
|
||||||
|
[ -z "$CLOS_FAILED" ] && CLOS_FAILED=$(echo "$CLOS_OUT" | grep "^(ok 6 " | awk '{print $3}' | tr -d ')' || true)
|
||||||
|
[ -z "$CLOS_PASSED" ] && CLOS_PASSED=0; [ -z "$CLOS_FAILED" ] && CLOS_FAILED=0
|
||||||
|
if [ "$CLOS_FAILED" = "0" ] && [ "$CLOS_PASSED" -gt 0 ] 2>/dev/null; then
|
||||||
|
PASS=$((PASS + CLOS_PASSED))
|
||||||
|
[ "$VERBOSE" = "-v" ] && echo " ok CLOS unit tests ($CLOS_PASSED)"
|
||||||
|
else
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
ERRORS+=" FAIL [CLOS unit tests] (${CLOS_PASSED} passed, ${CLOS_FAILED} failed)
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Phase 4: CLOS classic programs ───────────────────────────────────────────
|
||||||
|
run_clos_suite() {
|
||||||
|
local prog="$1" pass_var="$2" fail_var="$3" failures_var="$4"
|
||||||
|
local PROG_FILE=$(mktemp)
|
||||||
|
printf '(epoch 1)\n(load "spec/stdlib.sx")\n(epoch 2)\n(load "lib/common-lisp/runtime.sx")\n(epoch 3)\n(load "lib/common-lisp/clos.sx")\n(epoch 4)\n(load "%s")\n(epoch 5)\n(eval "%s")\n(epoch 6)\n(eval "%s")\n(epoch 7)\n(eval "%s")\n' \
|
||||||
|
"$prog" "$pass_var" "$fail_var" "$failures_var" > "$PROG_FILE"
|
||||||
|
local OUT; OUT=$(timeout 20 "$SX_SERVER" < "$PROG_FILE" 2>/dev/null)
|
||||||
|
rm -f "$PROG_FILE"
|
||||||
|
local P F
|
||||||
|
P=$(echo "$OUT" | grep -A1 "^(ok-len 5 " | tail -1 || true)
|
||||||
|
F=$(echo "$OUT" | grep -A1 "^(ok-len 6 " | tail -1 || true)
|
||||||
|
local ERRS; ERRS=$(echo "$OUT" | grep -A1 "^(ok-len 7 " | tail -1 || true)
|
||||||
|
[ -z "$P" ] && P=0; [ -z "$F" ] && F=0
|
||||||
|
if [ "$F" = "0" ] && [ "$P" -gt 0 ] 2>/dev/null; then
|
||||||
|
PASS=$((PASS + P))
|
||||||
|
[ "$VERBOSE" = "-v" ] && echo " ok $prog ($P)"
|
||||||
|
else
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
ERRORS+=" FAIL [$prog] (${P} passed, ${F} failed) ${ERRS}
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_clos_suite \
|
||||||
|
"lib/common-lisp/tests/programs/geometry.sx" \
|
||||||
|
"geo-passed" "geo-failed" "geo-failures"
|
||||||
|
|
||||||
|
run_clos_suite \
|
||||||
|
"lib/common-lisp/tests/programs/mop-trace.sx" \
|
||||||
|
"mop-passed" "mop-failed" "mop-failures"
|
||||||
|
|
||||||
|
# ── Phase 5: macros + LOOP ───────────────────────────────────────────────────
|
||||||
|
MACRO_FILE=$(mktemp); trap "rm -f $MACRO_FILE" EXIT
|
||||||
|
printf '(epoch 1)\n(load "spec/stdlib.sx")\n(epoch 2)\n(load "lib/common-lisp/reader.sx")\n(epoch 3)\n(load "lib/common-lisp/parser.sx")\n(epoch 4)\n(load "lib/common-lisp/eval.sx")\n(epoch 5)\n(load "lib/common-lisp/loop.sx")\n(epoch 6)\n(load "lib/common-lisp/tests/macros.sx")\n(epoch 7)\n(eval "macro-passed")\n(epoch 8)\n(eval "macro-failed")\n(epoch 9)\n(eval "macro-failures")\n' > "$MACRO_FILE"
|
||||||
|
MACRO_OUT=$(timeout 60 "$SX_SERVER" < "$MACRO_FILE" 2>/dev/null)
|
||||||
|
rm -f "$MACRO_FILE"
|
||||||
|
MACRO_PASSED=$(echo "$MACRO_OUT" | grep -A1 "^(ok-len 7 " | tail -1 || true)
|
||||||
|
MACRO_FAILED=$(echo "$MACRO_OUT" | grep -A1 "^(ok-len 8 " | tail -1 || true)
|
||||||
|
[ -z "$MACRO_PASSED" ] && MACRO_PASSED=0; [ -z "$MACRO_FAILED" ] && MACRO_FAILED=0
|
||||||
|
if [ "$MACRO_FAILED" = "0" ] && [ "$MACRO_PASSED" -gt 0 ] 2>/dev/null; then
|
||||||
|
PASS=$((PASS + MACRO_PASSED))
|
||||||
|
[ "$VERBOSE" = "-v" ] && echo " ok Phase 5 macros+LOOP ($MACRO_PASSED)"
|
||||||
|
else
|
||||||
|
FAIL=$((FAIL + 1))
|
||||||
|
ERRORS+=" FAIL [Phase 5 macros+LOOP] (${MACRO_PASSED} passed, ${MACRO_FAILED} failed)
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
TOTAL=$((PASS+FAIL))
|
TOTAL=$((PASS+FAIL))
|
||||||
if [ $FAIL -eq 0 ]; then
|
if [ $FAIL -eq 0 ]; then
|
||||||
echo "ok $PASS/$TOTAL lib/common-lisp tests passed"
|
echo "ok $PASS/$TOTAL lib/common-lisp tests passed"
|
||||||
|
|||||||
334
lib/common-lisp/tests/clos.sx
Normal file
334
lib/common-lisp/tests/clos.sx
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
;; lib/common-lisp/tests/clos.sx — CLOS test suite
|
||||||
|
;;
|
||||||
|
;; Loaded after: spec/stdlib.sx, lib/common-lisp/runtime.sx, lib/common-lisp/clos.sx
|
||||||
|
|
||||||
|
(define passed 0)
|
||||||
|
(define failed 0)
|
||||||
|
(define failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
assert-equal
|
||||||
|
(fn
|
||||||
|
(label got expected)
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list
|
||||||
|
(str
|
||||||
|
"FAIL ["
|
||||||
|
label
|
||||||
|
"]: got="
|
||||||
|
(inspect got)
|
||||||
|
" expected="
|
||||||
|
(inspect expected)))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
assert-true
|
||||||
|
(fn
|
||||||
|
(label got)
|
||||||
|
(if
|
||||||
|
got
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list
|
||||||
|
(str "FAIL [" label "]: expected true, got " (inspect got)))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
assert-nil
|
||||||
|
(fn
|
||||||
|
(label got)
|
||||||
|
(if
|
||||||
|
(nil? got)
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list (str "FAIL [" label "]: expected nil, got " (inspect got)))))))))
|
||||||
|
|
||||||
|
;; ── 1. class-of for built-in types ────────────────────────────────────────
|
||||||
|
|
||||||
|
(assert-equal "class-of integer" (clos-class-of 42) "integer")
|
||||||
|
(assert-equal "class-of float" (clos-class-of 3.14) "float")
|
||||||
|
(assert-equal "class-of string" (clos-class-of "hi") "string")
|
||||||
|
(assert-equal "class-of nil" (clos-class-of nil) "null")
|
||||||
|
(assert-equal "class-of list" (clos-class-of (list 1)) "cons")
|
||||||
|
(assert-equal "class-of empty" (clos-class-of (list)) "null")
|
||||||
|
|
||||||
|
;; ── 2. subclass-of? ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(assert-true "integer subclass-of t" (clos-subclass-of? "integer" "t"))
|
||||||
|
(assert-true "float subclass-of t" (clos-subclass-of? "float" "t"))
|
||||||
|
(assert-true "t subclass-of t" (clos-subclass-of? "t" "t"))
|
||||||
|
(assert-equal
|
||||||
|
"integer not subclass-of float"
|
||||||
|
(clos-subclass-of? "integer" "float")
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── 3. defclass + make-instance ───────────────────────────────────────────
|
||||||
|
|
||||||
|
(clos-defclass "point" (list "t") (list {:initform 0 :initarg ":x" :reader nil :writer nil :accessor "point-x" :name "x"} {:initform 0 :initarg ":y" :reader nil :writer nil :accessor "point-y" :name "y"}))
|
||||||
|
|
||||||
|
(let
|
||||||
|
((p (clos-make-instance "point" ":x" 3 ":y" 4)))
|
||||||
|
(begin
|
||||||
|
(assert-equal "make-instance slot x" (clos-slot-value p "x") 3)
|
||||||
|
(assert-equal "make-instance slot y" (clos-slot-value p "y") 4)
|
||||||
|
(assert-equal "class-of instance" (clos-class-of p) "point")
|
||||||
|
(assert-true "instance-of? point" (clos-instance-of? p "point"))
|
||||||
|
(assert-true "instance-of? t" (clos-instance-of? p "t"))
|
||||||
|
(assert-equal "instance-of? string" (clos-instance-of? p "string") false)))
|
||||||
|
|
||||||
|
;; initform defaults
|
||||||
|
(let
|
||||||
|
((p0 (clos-make-instance "point")))
|
||||||
|
(begin
|
||||||
|
(assert-equal "initform default x=0" (clos-slot-value p0 "x") 0)
|
||||||
|
(assert-equal "initform default y=0" (clos-slot-value p0 "y") 0)))
|
||||||
|
|
||||||
|
;; ── 4. slot-value / set-slot-value! ──────────────────────────────────────
|
||||||
|
|
||||||
|
(let
|
||||||
|
((p (clos-make-instance "point" ":x" 10 ":y" 20)))
|
||||||
|
(begin
|
||||||
|
(clos-set-slot-value! p "x" 99)
|
||||||
|
(assert-equal "set-slot-value! x" (clos-slot-value p "x") 99)
|
||||||
|
(assert-equal "slot-value y unchanged" (clos-slot-value p "y") 20)))
|
||||||
|
|
||||||
|
;; ── 5. slot-boundp ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(let
|
||||||
|
((p (clos-make-instance "point" ":x" 5)))
|
||||||
|
(begin
|
||||||
|
(assert-true "slot-boundp x" (clos-slot-boundp p "x"))
|
||||||
|
(assert-true "slot-boundp y (initform 0)" (clos-slot-boundp p "y"))))
|
||||||
|
|
||||||
|
;; ── 6. find-class ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(assert-equal
|
||||||
|
"find-class point"
|
||||||
|
(get (clos-find-class "point") "name")
|
||||||
|
"point")
|
||||||
|
(assert-nil "find-class missing" (clos-find-class "no-such-class"))
|
||||||
|
|
||||||
|
;; ── 7. inheritance ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(clos-defclass "colored-point" (list "point") (list {:initform "white" :initarg ":color" :reader nil :writer nil :accessor nil :name "color"}))
|
||||||
|
|
||||||
|
(let
|
||||||
|
((cp (clos-make-instance "colored-point" ":x" 1 ":y" 2 ":color" "red")))
|
||||||
|
(begin
|
||||||
|
(assert-equal "inherited slot x" (clos-slot-value cp "x") 1)
|
||||||
|
(assert-equal "inherited slot y" (clos-slot-value cp "y") 2)
|
||||||
|
(assert-equal "own slot color" (clos-slot-value cp "color") "red")
|
||||||
|
(assert-true
|
||||||
|
"instance-of? colored-point"
|
||||||
|
(clos-instance-of? cp "colored-point"))
|
||||||
|
(assert-true "instance-of? point (parent)" (clos-instance-of? cp "point"))
|
||||||
|
(assert-true "instance-of? t (root)" (clos-instance-of? cp "t"))))
|
||||||
|
|
||||||
|
;; ── 8. defgeneric + primary method ───────────────────────────────────────
|
||||||
|
|
||||||
|
(clos-defgeneric "describe-obj" {})
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"describe-obj"
|
||||||
|
(list)
|
||||||
|
(list "point")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((p (first args)))
|
||||||
|
(str "(" (clos-slot-value p "x") "," (clos-slot-value p "y") ")"))))
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"describe-obj"
|
||||||
|
(list)
|
||||||
|
(list "t")
|
||||||
|
(fn (args next-fn) (str "object:" (inspect (first args)))))
|
||||||
|
|
||||||
|
(let
|
||||||
|
((p (clos-make-instance "point" ":x" 3 ":y" 4)))
|
||||||
|
(begin
|
||||||
|
(assert-equal
|
||||||
|
"primary method for point"
|
||||||
|
(clos-call-generic "describe-obj" (list p))
|
||||||
|
"(3,4)")
|
||||||
|
(assert-equal
|
||||||
|
"fallback t method"
|
||||||
|
(clos-call-generic "describe-obj" (list 42))
|
||||||
|
"object:42")))
|
||||||
|
|
||||||
|
;; ── 9. method inheritance + specificity ───────────────────────────────────
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"describe-obj"
|
||||||
|
(list)
|
||||||
|
(list "colored-point")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((cp (first args)))
|
||||||
|
(str
|
||||||
|
(clos-slot-value cp "color")
|
||||||
|
"@("
|
||||||
|
(clos-slot-value cp "x")
|
||||||
|
","
|
||||||
|
(clos-slot-value cp "y")
|
||||||
|
")"))))
|
||||||
|
|
||||||
|
(let
|
||||||
|
((cp (clos-make-instance "colored-point" ":x" 5 ":y" 6 ":color" "blue")))
|
||||||
|
(assert-equal
|
||||||
|
"most specific method wins"
|
||||||
|
(clos-call-generic "describe-obj" (list cp))
|
||||||
|
"blue@(5,6)"))
|
||||||
|
|
||||||
|
;; ── 10. :before / :after / :around qualifiers ─────────────────────────────
|
||||||
|
|
||||||
|
(clos-defgeneric "logged-action" {})
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"logged-action"
|
||||||
|
(list "before")
|
||||||
|
(list "t")
|
||||||
|
(fn (args next-fn) (set! action-log (append action-log (list "before")))))
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"logged-action"
|
||||||
|
(list)
|
||||||
|
(list "t")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(set! action-log (append action-log (list "primary")))
|
||||||
|
"result"))
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"logged-action"
|
||||||
|
(list "after")
|
||||||
|
(list "t")
|
||||||
|
(fn (args next-fn) (set! action-log (append action-log (list "after")))))
|
||||||
|
|
||||||
|
(define action-log (list))
|
||||||
|
(clos-call-generic "logged-action" (list 1))
|
||||||
|
(assert-equal
|
||||||
|
":before/:after order"
|
||||||
|
action-log
|
||||||
|
(list "before" "primary" "after"))
|
||||||
|
|
||||||
|
;; :around
|
||||||
|
(define around-log (list))
|
||||||
|
|
||||||
|
(clos-defgeneric "wrapped-action" {})
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"wrapped-action"
|
||||||
|
(list "around")
|
||||||
|
(list "t")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(set! around-log (append around-log (list "around-enter")))
|
||||||
|
(let
|
||||||
|
((r (next-fn)))
|
||||||
|
(set! around-log (append around-log (list "around-exit")))
|
||||||
|
r)))
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"wrapped-action"
|
||||||
|
(list)
|
||||||
|
(list "t")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(set! around-log (append around-log (list "primary")))
|
||||||
|
42))
|
||||||
|
|
||||||
|
(let
|
||||||
|
((r (clos-call-generic "wrapped-action" (list nil))))
|
||||||
|
(begin
|
||||||
|
(assert-equal ":around result" r 42)
|
||||||
|
(assert-equal
|
||||||
|
":around log"
|
||||||
|
around-log
|
||||||
|
(list "around-enter" "primary" "around-exit"))))
|
||||||
|
|
||||||
|
;; ── 11. call-next-method ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(clos-defgeneric "chain-test" {})
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"chain-test"
|
||||||
|
(list)
|
||||||
|
(list "colored-point")
|
||||||
|
(fn (args next-fn) (str "colored:" (clos-call-next-method next-fn))))
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"chain-test"
|
||||||
|
(list)
|
||||||
|
(list "point")
|
||||||
|
(fn (args next-fn) "point-base"))
|
||||||
|
|
||||||
|
(let
|
||||||
|
((cp (clos-make-instance "colored-point" ":x" 0 ":y" 0 ":color" "green")))
|
||||||
|
(assert-equal
|
||||||
|
"call-next-method chains"
|
||||||
|
(clos-call-generic "chain-test" (list cp))
|
||||||
|
"colored:point-base"))
|
||||||
|
|
||||||
|
;; ── 12. accessor methods ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(let
|
||||||
|
((p (clos-make-instance "point" ":x" 7 ":y" 8)))
|
||||||
|
(begin
|
||||||
|
(assert-equal
|
||||||
|
"accessor point-x"
|
||||||
|
(clos-call-generic "point-x" (list p))
|
||||||
|
7)
|
||||||
|
(assert-equal
|
||||||
|
"accessor point-y"
|
||||||
|
(clos-call-generic "point-y" (list p))
|
||||||
|
8)))
|
||||||
|
|
||||||
|
;; ── 13. with-slots ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(let
|
||||||
|
((p (clos-make-instance "point" ":x" 3 ":y" 4)))
|
||||||
|
(assert-equal
|
||||||
|
"with-slots"
|
||||||
|
(clos-with-slots p (list "x" "y") (fn (x y) (* x y)))
|
||||||
|
12))
|
||||||
|
|
||||||
|
;; ── 14. change-class ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(clos-defclass "special-point" (list "point") (list {:initform "" :initarg ":label" :reader nil :writer nil :accessor nil :name "label"}))
|
||||||
|
|
||||||
|
(let
|
||||||
|
((p (clos-make-instance "point" ":x" 1 ":y" 2)))
|
||||||
|
(begin
|
||||||
|
(clos-change-class! p "special-point")
|
||||||
|
(assert-equal
|
||||||
|
"change-class updates class"
|
||||||
|
(clos-class-of p)
|
||||||
|
"special-point")))
|
||||||
|
|
||||||
|
;; ── summary ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(if
|
||||||
|
(= failed 0)
|
||||||
|
(print (str "ok " passed "/" (+ passed failed) " CLOS tests passed"))
|
||||||
|
(begin
|
||||||
|
(for-each (fn (f) (print f)) failures)
|
||||||
|
(print
|
||||||
|
(str "FAIL " passed "/" (+ passed failed) " passed, " failed " failed"))))
|
||||||
478
lib/common-lisp/tests/conditions.sx
Normal file
478
lib/common-lisp/tests/conditions.sx
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
;; lib/common-lisp/tests/conditions.sx — Phase 3 condition system tests
|
||||||
|
;;
|
||||||
|
;; Loaded by lib/common-lisp/test.sh after:
|
||||||
|
;; (load "spec/stdlib.sx")
|
||||||
|
;; (load "lib/common-lisp/runtime.sx")
|
||||||
|
;;
|
||||||
|
;; Each test resets the handler/restart stacks to ensure isolation.
|
||||||
|
|
||||||
|
(define
|
||||||
|
reset-stacks!
|
||||||
|
(fn () (set! cl-handler-stack (list)) (set! cl-restart-stack (list))))
|
||||||
|
|
||||||
|
;; ── helpers ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define passed 0)
|
||||||
|
(define failed 0)
|
||||||
|
(define failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
assert-equal
|
||||||
|
(fn
|
||||||
|
(label got expected)
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list
|
||||||
|
(str
|
||||||
|
"FAIL ["
|
||||||
|
label
|
||||||
|
"]: got="
|
||||||
|
(inspect got)
|
||||||
|
" expected="
|
||||||
|
(inspect expected)))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
assert-true
|
||||||
|
(fn
|
||||||
|
(label got)
|
||||||
|
(if
|
||||||
|
got
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list
|
||||||
|
(str "FAIL [" label "]: expected true, got " (inspect got)))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
assert-nil
|
||||||
|
(fn
|
||||||
|
(label got)
|
||||||
|
(if
|
||||||
|
(nil? got)
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list (str "FAIL [" label "]: expected nil, got " (inspect got)))))))))
|
||||||
|
|
||||||
|
;; ── 1. condition predicates ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
(let
|
||||||
|
((c (cl-make-condition "simple-error" "format-control" "oops")))
|
||||||
|
(begin
|
||||||
|
(assert-true "cl-condition? on condition" (cl-condition? c))
|
||||||
|
(assert-equal "cl-condition? on string" (cl-condition? "hello") false)
|
||||||
|
(assert-equal "cl-condition? on number" (cl-condition? 42) false)
|
||||||
|
(assert-equal "cl-condition? on nil" (cl-condition? nil) false)))
|
||||||
|
|
||||||
|
;; ── 2. cl-make-condition + slot access ────────────────────────────────────
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
(let
|
||||||
|
((c (cl-make-condition "simple-error" "format-control" "msg" "format-arguments" (list 1 2))))
|
||||||
|
(begin
|
||||||
|
(assert-equal "class field" (get c "class") "simple-error")
|
||||||
|
(assert-equal "cl-type field" (get c "cl-type") "cl-condition")
|
||||||
|
(assert-equal
|
||||||
|
"format-control slot"
|
||||||
|
(cl-condition-slot c "format-control")
|
||||||
|
"msg")
|
||||||
|
(assert-equal
|
||||||
|
"format-arguments slot"
|
||||||
|
(cl-condition-slot c "format-arguments")
|
||||||
|
(list 1 2))
|
||||||
|
(assert-nil "missing slot is nil" (cl-condition-slot c "no-such-slot"))
|
||||||
|
(assert-equal "condition-message" (cl-condition-message c) "msg")))
|
||||||
|
|
||||||
|
;; ── 3. cl-condition-of-type? — hierarchy walking ─────────────────────────
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
(let
|
||||||
|
((se (cl-make-condition "simple-error" "format-control" "x"))
|
||||||
|
(w (cl-make-condition "simple-warning" "format-control" "y"))
|
||||||
|
(te
|
||||||
|
(cl-make-condition
|
||||||
|
"type-error"
|
||||||
|
"datum"
|
||||||
|
5
|
||||||
|
"expected-type"
|
||||||
|
"string"))
|
||||||
|
(dz (cl-make-condition "division-by-zero")))
|
||||||
|
(begin
|
||||||
|
(assert-true
|
||||||
|
"se isa simple-error"
|
||||||
|
(cl-condition-of-type? se "simple-error"))
|
||||||
|
(assert-true "se isa error" (cl-condition-of-type? se "error"))
|
||||||
|
(assert-true
|
||||||
|
"se isa serious-condition"
|
||||||
|
(cl-condition-of-type? se "serious-condition"))
|
||||||
|
(assert-true "se isa condition" (cl-condition-of-type? se "condition"))
|
||||||
|
(assert-equal
|
||||||
|
"se not isa warning"
|
||||||
|
(cl-condition-of-type? se "warning")
|
||||||
|
false)
|
||||||
|
(assert-true
|
||||||
|
"w isa simple-warning"
|
||||||
|
(cl-condition-of-type? w "simple-warning"))
|
||||||
|
(assert-true "w isa warning" (cl-condition-of-type? w "warning"))
|
||||||
|
(assert-true "w isa condition" (cl-condition-of-type? w "condition"))
|
||||||
|
(assert-equal "w not isa error" (cl-condition-of-type? w "error") false)
|
||||||
|
(assert-true "te isa type-error" (cl-condition-of-type? te "type-error"))
|
||||||
|
(assert-true "te isa error" (cl-condition-of-type? te "error"))
|
||||||
|
(assert-true
|
||||||
|
"dz isa division-by-zero"
|
||||||
|
(cl-condition-of-type? dz "division-by-zero"))
|
||||||
|
(assert-true
|
||||||
|
"dz isa arithmetic-error"
|
||||||
|
(cl-condition-of-type? dz "arithmetic-error"))
|
||||||
|
(assert-true "dz isa error" (cl-condition-of-type? dz "error"))
|
||||||
|
(assert-equal
|
||||||
|
"non-condition not isa anything"
|
||||||
|
(cl-condition-of-type? 42 "error")
|
||||||
|
false)))
|
||||||
|
|
||||||
|
;; ── 4. cl-define-condition ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
(begin
|
||||||
|
(cl-define-condition "my-app-error" (list "error") (list "code" "detail"))
|
||||||
|
(let
|
||||||
|
((c (cl-make-condition "my-app-error" "code" 404 "detail" "not found")))
|
||||||
|
(begin
|
||||||
|
(assert-true "user condition: cl-condition?" (cl-condition? c))
|
||||||
|
(assert-true
|
||||||
|
"user condition isa my-app-error"
|
||||||
|
(cl-condition-of-type? c "my-app-error"))
|
||||||
|
(assert-true
|
||||||
|
"user condition isa error"
|
||||||
|
(cl-condition-of-type? c "error"))
|
||||||
|
(assert-true
|
||||||
|
"user condition isa condition"
|
||||||
|
(cl-condition-of-type? c "condition"))
|
||||||
|
(assert-equal
|
||||||
|
"user condition slot code"
|
||||||
|
(cl-condition-slot c "code")
|
||||||
|
404)
|
||||||
|
(assert-equal
|
||||||
|
"user condition slot detail"
|
||||||
|
(cl-condition-slot c "detail")
|
||||||
|
"not found"))))
|
||||||
|
|
||||||
|
;; ── 5. cl-handler-bind (non-unwinding) ───────────────────────────────────
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
(let
|
||||||
|
((log (list)))
|
||||||
|
(begin
|
||||||
|
(cl-handler-bind
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
"error"
|
||||||
|
(fn (c) (set! log (append log (list (cl-condition-message c)))))))
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(cl-signal (cl-make-condition "simple-error" "format-control" "oops"))))
|
||||||
|
(assert-equal "handler-bind: handler fired" log (list "oops"))))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; Non-unwinding: body continues after signal
|
||||||
|
(let
|
||||||
|
((body-ran false))
|
||||||
|
(begin
|
||||||
|
(cl-handler-bind
|
||||||
|
(list (list "error" (fn (c) nil)))
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(cl-signal (cl-make-condition "simple-error" "format-control" "x"))
|
||||||
|
(set! body-ran true)))
|
||||||
|
(assert-true "handler-bind: body continues after signal" body-ran)))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; Type filtering: warning handler does not fire for error
|
||||||
|
(let
|
||||||
|
((w-fired false))
|
||||||
|
(begin
|
||||||
|
(cl-handler-bind
|
||||||
|
(list (list "warning" (fn (c) (set! w-fired true))))
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(cl-signal (cl-make-condition "simple-error" "format-control" "e"))))
|
||||||
|
(assert-equal
|
||||||
|
"handler-bind: type filter (warning ignores error)"
|
||||||
|
w-fired
|
||||||
|
false)))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; Multiple handlers: both matching handlers fire
|
||||||
|
(let
|
||||||
|
((log (list)))
|
||||||
|
(begin
|
||||||
|
(cl-handler-bind
|
||||||
|
(list
|
||||||
|
(list "error" (fn (c) (set! log (append log (list "e1")))))
|
||||||
|
(list "condition" (fn (c) (set! log (append log (list "e2"))))))
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(cl-signal (cl-make-condition "simple-error" "format-control" "x"))))
|
||||||
|
(assert-equal "handler-bind: both handlers fire" log (list "e1" "e2"))))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; ── 6. cl-handler-case (unwinding) ───────────────────────────────────────
|
||||||
|
|
||||||
|
;; Catches error, returns handler result
|
||||||
|
(let
|
||||||
|
((result (cl-handler-case (fn () (cl-error "boom") 99) (list "error" (fn (c) (str "caught: " (cl-condition-message c)))))))
|
||||||
|
(assert-equal "handler-case: catches error" result "caught: boom"))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; Returns body result when no signal
|
||||||
|
(let
|
||||||
|
((result (cl-handler-case (fn () 42) (list "error" (fn (c) -1)))))
|
||||||
|
(assert-equal "handler-case: body result" result 42))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; Only first matching handler runs (unwinding)
|
||||||
|
(let
|
||||||
|
((result (cl-handler-case (fn () (cl-error "x")) (list "simple-error" (fn (c) "simple")) (list "error" (fn (c) "error")))))
|
||||||
|
(assert-equal "handler-case: most specific wins" result "simple"))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; ── 7. cl-warn ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(let
|
||||||
|
((warned false))
|
||||||
|
(begin
|
||||||
|
(cl-handler-bind
|
||||||
|
(list (list "warning" (fn (c) (set! warned true))))
|
||||||
|
(fn () (cl-warn "be careful")))
|
||||||
|
(assert-true "cl-warn: fires warning handler" warned)))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; Warn with condition object
|
||||||
|
(let
|
||||||
|
((msg ""))
|
||||||
|
(begin
|
||||||
|
(cl-handler-bind
|
||||||
|
(list (list "warning" (fn (c) (set! msg (cl-condition-message c)))))
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(cl-warn
|
||||||
|
(cl-make-condition "simple-warning" "format-control" "take care"))))
|
||||||
|
(assert-equal "cl-warn: condition object" msg "take care")))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; ── 8. cl-restart-case + cl-invoke-restart ───────────────────────────────
|
||||||
|
|
||||||
|
;; Basic restart invocation
|
||||||
|
(let
|
||||||
|
((result (cl-restart-case (fn () (cl-invoke-restart "use-zero")) (list "use-zero" (list) (fn () 0)))))
|
||||||
|
(assert-equal "restart-case: invoke-restart use-zero" result 0))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; Restart with argument
|
||||||
|
(let
|
||||||
|
((result (cl-restart-case (fn () (cl-invoke-restart "use-value" 77)) (list "use-value" (list "v") (fn (v) v)))))
|
||||||
|
(assert-equal "restart-case: invoke-restart with arg" result 77))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; Body returns normally when restart not invoked
|
||||||
|
(let
|
||||||
|
((result (cl-restart-case (fn () 42) (list "never-used" (list) (fn () -1)))))
|
||||||
|
(assert-equal "restart-case: body result" result 42))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; ── 9. cl-with-simple-restart ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
(let
|
||||||
|
((result (cl-with-simple-restart "skip" "Skip this step" (fn () (cl-invoke-restart "skip") 99))))
|
||||||
|
(assert-nil "with-simple-restart: invoke returns nil" result))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; ── 10. cl-find-restart ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(let
|
||||||
|
((found (cl-restart-case (fn () (cl-find-restart "retry")) (list "retry" (list) (fn () nil)))))
|
||||||
|
(assert-true "find-restart: finds active restart" (not (nil? found))))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
(let
|
||||||
|
((not-found (cl-restart-case (fn () (cl-find-restart "nonexistent")) (list "retry" (list) (fn () nil)))))
|
||||||
|
(assert-nil "find-restart: nil for inactive restart" not-found))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; ── 11. cl-compute-restarts ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
(let
|
||||||
|
((names (cl-restart-case (fn () (cl-restart-case (fn () (cl-compute-restarts)) (list "inner" (list) (fn () nil)))) (list "outer" (list) (fn () nil)))))
|
||||||
|
(assert-equal
|
||||||
|
"compute-restarts: both restarts"
|
||||||
|
names
|
||||||
|
(list "inner" "outer")))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; ── 12. handler-bind + restart-case interop ───────────────────────────────
|
||||||
|
|
||||||
|
;; Classic CL pattern: error handler invokes a restart
|
||||||
|
(let
|
||||||
|
((result (cl-restart-case (fn () (cl-handler-bind (list (list "error" (fn (c) (cl-invoke-restart "use-zero")))) (fn () (cl-error "divide by zero")))) (list "use-zero" (list) (fn () 0)))))
|
||||||
|
(assert-equal "interop: handler invokes restart" result 0))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; ── 13. cl-cerror ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; When "continue" restart is invoked, cerror returns nil
|
||||||
|
(let
|
||||||
|
((result (cl-restart-case (fn () (cl-cerror "continue anyway" "something bad") 42) (list "continue" (list) (fn () "resumed")))))
|
||||||
|
(assert-true
|
||||||
|
"cerror: returns"
|
||||||
|
(or (nil? result) (= result 42) (= result "resumed"))))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; ── 14. slot accessor helpers ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
(let
|
||||||
|
((c (cl-make-condition "simple-error" "format-control" "msg" "format-arguments" (list 1 2))))
|
||||||
|
(begin
|
||||||
|
(assert-equal
|
||||||
|
"simple-condition-format-control"
|
||||||
|
(cl-simple-condition-format-control c)
|
||||||
|
"msg")
|
||||||
|
(assert-equal
|
||||||
|
"simple-condition-format-arguments"
|
||||||
|
(cl-simple-condition-format-arguments c)
|
||||||
|
(list 1 2))))
|
||||||
|
|
||||||
|
(let
|
||||||
|
((c (cl-make-condition "type-error" "datum" 42 "expected-type" "string")))
|
||||||
|
(begin
|
||||||
|
(assert-equal "type-error-datum" (cl-type-error-datum c) 42)
|
||||||
|
(assert-equal
|
||||||
|
"type-error-expected-type"
|
||||||
|
(cl-type-error-expected-type c)
|
||||||
|
"string")))
|
||||||
|
|
||||||
|
(let
|
||||||
|
((c (cl-make-condition "arithmetic-error" "operation" "/" "operands" (list 1 0))))
|
||||||
|
(begin
|
||||||
|
(assert-equal
|
||||||
|
"arithmetic-error-operation"
|
||||||
|
(cl-arithmetic-error-operation c)
|
||||||
|
"/")
|
||||||
|
(assert-equal
|
||||||
|
"arithmetic-error-operands"
|
||||||
|
(cl-arithmetic-error-operands c)
|
||||||
|
(list 1 0))))
|
||||||
|
|
||||||
|
|
||||||
|
;; ── 15. *debugger-hook* ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
(let ((received nil))
|
||||||
|
(begin
|
||||||
|
(set! cl-debugger-hook
|
||||||
|
(fn (c h)
|
||||||
|
(set! received (cl-condition-message c))
|
||||||
|
(cl-invoke-restart "escape")))
|
||||||
|
(cl-restart-case
|
||||||
|
(fn () (cl-error "debugger test"))
|
||||||
|
(list "escape" (list) (fn () nil)))
|
||||||
|
(set! cl-debugger-hook nil)
|
||||||
|
(assert-equal "debugger-hook receives condition" received "debugger test")))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; ── 16. *break-on-signals* ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
(let ((triggered false))
|
||||||
|
(begin
|
||||||
|
(set! cl-break-on-signals "error")
|
||||||
|
(set! cl-debugger-hook
|
||||||
|
(fn (c h)
|
||||||
|
(set! triggered true)
|
||||||
|
(cl-invoke-restart "abort")))
|
||||||
|
(cl-restart-case
|
||||||
|
(fn ()
|
||||||
|
(cl-signal (cl-make-condition "simple-error" "format-control" "x")))
|
||||||
|
(list "abort" (list) (fn () nil)))
|
||||||
|
(set! cl-break-on-signals nil)
|
||||||
|
(set! cl-debugger-hook nil)
|
||||||
|
(assert-true "break-on-signals fires hook" triggered)))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; break-on-signals: non-matching type does NOT fire hook
|
||||||
|
(let ((triggered false))
|
||||||
|
(begin
|
||||||
|
(set! cl-break-on-signals "error")
|
||||||
|
(set! cl-debugger-hook
|
||||||
|
(fn (c h) (set! triggered true) nil))
|
||||||
|
(cl-handler-bind
|
||||||
|
(list (list "warning" (fn (c) nil)))
|
||||||
|
(fn ()
|
||||||
|
(cl-signal (cl-make-condition "simple-warning" "format-control" "w"))))
|
||||||
|
(set! cl-break-on-signals nil)
|
||||||
|
(set! cl-debugger-hook nil)
|
||||||
|
(assert-equal "break-on-signals: type mismatch not triggered" triggered false)))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; ── 17. cl-invoke-restart-interactively ──────────────────────────────────
|
||||||
|
|
||||||
|
(let ((result
|
||||||
|
(cl-restart-case
|
||||||
|
(fn () (cl-invoke-restart-interactively "use-default"))
|
||||||
|
(list "use-default" (list) (fn () 99)))))
|
||||||
|
(assert-equal "invoke-restart-interactively: returns restart value" result 99))
|
||||||
|
|
||||||
|
(reset-stacks!)
|
||||||
|
|
||||||
|
;; ── summary ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(if
|
||||||
|
(= failed 0)
|
||||||
|
(print (str "ok " passed "/" (+ passed failed) " condition tests passed"))
|
||||||
|
(begin
|
||||||
|
(for-each (fn (f) (print f)) failures)
|
||||||
|
(print
|
||||||
|
(str "FAIL " passed "/" (+ passed failed) " passed, " failed " failed"))))
|
||||||
466
lib/common-lisp/tests/eval.sx
Normal file
466
lib/common-lisp/tests/eval.sx
Normal file
@@ -0,0 +1,466 @@
|
|||||||
|
;; CL evaluator tests
|
||||||
|
|
||||||
|
(define cl-test-pass 0)
|
||||||
|
(define cl-test-fail 0)
|
||||||
|
(define cl-test-fails (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-deep=
|
||||||
|
(fn
|
||||||
|
(a b)
|
||||||
|
(cond
|
||||||
|
((= a b) true)
|
||||||
|
((and (dict? a) (dict? b))
|
||||||
|
(let
|
||||||
|
((ak (keys a)) (bk (keys b)))
|
||||||
|
(if
|
||||||
|
(not (= (len ak) (len bk)))
|
||||||
|
false
|
||||||
|
(every?
|
||||||
|
(fn (k) (and (has-key? b k) (cl-deep= (get a k) (get b k))))
|
||||||
|
ak))))
|
||||||
|
((and (list? a) (list? b))
|
||||||
|
(if
|
||||||
|
(not (= (len a) (len b)))
|
||||||
|
false
|
||||||
|
(let
|
||||||
|
((i 0) (ok true))
|
||||||
|
(define
|
||||||
|
chk
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(and ok (< i (len a)))
|
||||||
|
(do
|
||||||
|
(when
|
||||||
|
(not (cl-deep= (nth a i) (nth b i)))
|
||||||
|
(set! ok false))
|
||||||
|
(set! i (+ i 1))
|
||||||
|
(chk)))))
|
||||||
|
(chk)
|
||||||
|
ok)))
|
||||||
|
(:else false))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(if
|
||||||
|
(cl-deep= actual expected)
|
||||||
|
(set! cl-test-pass (+ cl-test-pass 1))
|
||||||
|
(do
|
||||||
|
(set! cl-test-fail (+ cl-test-fail 1))
|
||||||
|
(append! cl-test-fails {:name name :expected expected :actual actual})))))
|
||||||
|
|
||||||
|
;; Convenience: evaluate CL string with fresh env each time
|
||||||
|
(define ev (fn (src) (cl-eval-str src (cl-make-env))))
|
||||||
|
(define evall (fn (src) (cl-eval-all-str src (cl-make-env))))
|
||||||
|
|
||||||
|
;; ── self-evaluating literals ──────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "lit: nil" (ev "nil") nil)
|
||||||
|
(cl-test "lit: t" (ev "t") true)
|
||||||
|
(cl-test "lit: integer" (ev "42") 42)
|
||||||
|
(cl-test "lit: negative" (ev "-7") -7)
|
||||||
|
(cl-test "lit: zero" (ev "0") 0)
|
||||||
|
(cl-test "lit: string" (ev "\"hello\"") "hello")
|
||||||
|
(cl-test "lit: empty string" (ev "\"\"") "")
|
||||||
|
(cl-test "lit: keyword type" (get (ev ":foo") "cl-type") "keyword")
|
||||||
|
(cl-test "lit: keyword name" (get (ev ":foo") "name") "FOO")
|
||||||
|
(cl-test "lit: float type" (get (ev "3.14") "cl-type") "float")
|
||||||
|
|
||||||
|
;; ── QUOTE ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "quote: symbol" (ev "'x") "X")
|
||||||
|
(cl-test "quote: list" (ev "'(a b c)") (list "A" "B" "C"))
|
||||||
|
(cl-test "quote: nil" (ev "'nil") nil)
|
||||||
|
(cl-test "quote: integer" (ev "'42") 42)
|
||||||
|
(cl-test "quote: nested" (ev "'(a (b c))") (list "A" (list "B" "C")))
|
||||||
|
|
||||||
|
;; ── IF ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "if: true branch" (ev "(if t 1 2)") 1)
|
||||||
|
(cl-test "if: false branch" (ev "(if nil 1 2)") 2)
|
||||||
|
(cl-test "if: no else nil" (ev "(if nil 99)") nil)
|
||||||
|
(cl-test "if: number truthy" (ev "(if 0 'yes 'no)") "YES")
|
||||||
|
(cl-test "if: empty string truthy" (ev "(if \"\" 'yes 'no)") "YES")
|
||||||
|
(cl-test "if: nested" (ev "(if t (if nil 1 2) 3)") 2)
|
||||||
|
|
||||||
|
;; ── PROGN ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "progn: single" (ev "(progn 42)") 42)
|
||||||
|
(cl-test "progn: multiple" (ev "(progn 1 2 3)") 3)
|
||||||
|
(cl-test "progn: nil last" (ev "(progn 1 nil)") nil)
|
||||||
|
|
||||||
|
;; ── AND / OR ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "and: empty" (ev "(and)") true)
|
||||||
|
(cl-test "and: all true" (ev "(and 1 2 3)") 3)
|
||||||
|
(cl-test "and: short-circuit" (ev "(and nil 99)") nil)
|
||||||
|
(cl-test "and: returns last" (ev "(and 1 2)") 2)
|
||||||
|
(cl-test "or: empty" (ev "(or)") nil)
|
||||||
|
(cl-test "or: first truthy" (ev "(or 1 2)") 1)
|
||||||
|
(cl-test "or: all nil" (ev "(or nil nil)") nil)
|
||||||
|
(cl-test "or: short-circuit" (ev "(or nil 42)") 42)
|
||||||
|
|
||||||
|
;; ── COND ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "cond: first match" (ev "(cond (t 1) (t 2))") 1)
|
||||||
|
(cl-test "cond: second match" (ev "(cond (nil 1) (t 2))") 2)
|
||||||
|
(cl-test "cond: no match" (ev "(cond (nil 1) (nil 2))") nil)
|
||||||
|
(cl-test "cond: returns test value" (ev "(cond (42))") 42)
|
||||||
|
|
||||||
|
;; ── WHEN / UNLESS ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "when: true" (ev "(when t 1 2 3)") 3)
|
||||||
|
(cl-test "when: nil" (ev "(when nil 99)") nil)
|
||||||
|
(cl-test "unless: nil runs" (ev "(unless nil 42)") 42)
|
||||||
|
(cl-test "unless: true skips" (ev "(unless t 99)") nil)
|
||||||
|
|
||||||
|
;; ── LET ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "let: empty bindings" (ev "(let () 42)") 42)
|
||||||
|
(cl-test "let: single binding" (ev "(let ((x 5)) x)") 5)
|
||||||
|
(cl-test "let: two bindings" (ev "(let ((x 3) (y 4)) (+ x y))") 7)
|
||||||
|
(cl-test "let: parallel" (ev "(let ((x 1)) (let ((x 2) (y x)) y))") 1)
|
||||||
|
(cl-test "let: nested" (ev "(let ((x 1)) (let ((y 2)) (+ x y)))") 3)
|
||||||
|
(cl-test "let: progn body" (ev "(let ((x 5)) (+ x 1) (* x 2))") 10)
|
||||||
|
(cl-test "let: bare name nil" (ev "(let (x) x)") nil)
|
||||||
|
|
||||||
|
;; ── LET* ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "let*: sequential" (ev "(let* ((x 1) (y (+ x 1))) y)") 2)
|
||||||
|
(cl-test "let*: chain" (ev "(let* ((a 2) (b (* a 3)) (c (+ b 1))) c)") 7)
|
||||||
|
(cl-test "let*: shadow" (ev "(let ((x 1)) (let* ((x 2) (y x)) y))") 2)
|
||||||
|
|
||||||
|
;; ── SETQ / SETF ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "setq: basic" (ev "(let ((x 0)) (setq x 5) x)") 5)
|
||||||
|
(cl-test "setq: returns value" (ev "(let ((x 0)) (setq x 99))") 99)
|
||||||
|
(cl-test "setf: basic" (ev "(let ((x 0)) (setf x 7) x)") 7)
|
||||||
|
|
||||||
|
;; ── LAMBDA ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "lambda: call" (ev "((lambda (x) x) 42)") 42)
|
||||||
|
(cl-test "lambda: multi-arg" (ev "((lambda (x y) (+ x y)) 3 4)") 7)
|
||||||
|
(cl-test "lambda: closure" (ev "(let ((n 10)) ((lambda (x) (+ x n)) 5))") 15)
|
||||||
|
(cl-test "lambda: rest arg"
|
||||||
|
(ev "((lambda (x &rest xs) (cons x xs)) 1 2 3)")
|
||||||
|
{:cl-type "cons" :car 1 :cdr (list 2 3)})
|
||||||
|
(cl-test "lambda: optional no default"
|
||||||
|
(ev "((lambda (&optional x) x))")
|
||||||
|
nil)
|
||||||
|
(cl-test "lambda: optional with arg"
|
||||||
|
(ev "((lambda (&optional (x 99)) x) 42)")
|
||||||
|
42)
|
||||||
|
(cl-test "lambda: optional default used"
|
||||||
|
(ev "((lambda (&optional (x 7)) x))")
|
||||||
|
7)
|
||||||
|
|
||||||
|
;; ── FUNCTION ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "function: lambda" (get (ev "(function (lambda (x) x))") "cl-type") "function")
|
||||||
|
|
||||||
|
;; ── DEFUN ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "defun: returns name" (evall "(defun sq (x) (* x x))") "SQ")
|
||||||
|
(cl-test "defun: call" (evall "(defun sq (x) (* x x)) (sq 5)") 25)
|
||||||
|
(cl-test "defun: multi-arg" (evall "(defun add (x y) (+ x y)) (add 3 4)") 7)
|
||||||
|
(cl-test "defun: recursive factorial"
|
||||||
|
(evall "(defun fact (n) (if (<= n 1) 1 (* n (fact (- n 1))))) (fact 5)")
|
||||||
|
120)
|
||||||
|
(cl-test "defun: multiple calls"
|
||||||
|
(evall "(defun double (x) (* x 2)) (+ (double 3) (double 5))")
|
||||||
|
16)
|
||||||
|
|
||||||
|
;; ── FLET ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "flet: basic"
|
||||||
|
(ev "(flet ((double (x) (* x 2))) (double 5))")
|
||||||
|
10)
|
||||||
|
(cl-test "flet: sees outer vars"
|
||||||
|
(ev "(let ((n 3)) (flet ((add-n (x) (+ x n))) (add-n 7)))")
|
||||||
|
10)
|
||||||
|
(cl-test "flet: non-recursive"
|
||||||
|
(ev "(flet ((f (x) (+ x 1))) (flet ((f (x) (f (f x)))) (f 5)))")
|
||||||
|
7)
|
||||||
|
|
||||||
|
;; ── LABELS ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "labels: basic"
|
||||||
|
(ev "(labels ((greet (x) x)) (greet 42))")
|
||||||
|
42)
|
||||||
|
(cl-test "labels: recursive"
|
||||||
|
(ev "(labels ((count (n) (if (<= n 0) 0 (+ 1 (count (- n 1)))))) (count 5))")
|
||||||
|
5)
|
||||||
|
(cl-test "labels: mutual recursion"
|
||||||
|
(ev "(labels
|
||||||
|
((even? (n) (if (= n 0) t (odd? (- n 1))))
|
||||||
|
(odd? (n) (if (= n 0) nil (even? (- n 1)))))
|
||||||
|
(list (even? 4) (odd? 3)))")
|
||||||
|
(list true true))
|
||||||
|
|
||||||
|
;; ── THE / LOCALLY / EVAL-WHEN ────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "the: passthrough" (ev "(the integer 42)") 42)
|
||||||
|
(cl-test "the: string" (ev "(the string \"hi\")") "hi")
|
||||||
|
(cl-test "locally: body" (ev "(locally 1 2 3)") 3)
|
||||||
|
(cl-test "eval-when: execute" (ev "(eval-when (:execute) 99)") 99)
|
||||||
|
(cl-test "eval-when: no execute" (ev "(eval-when (:compile-toplevel) 99)") nil)
|
||||||
|
|
||||||
|
;; ── DEFVAR / DEFPARAMETER ────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "defvar: returns name" (evall "(defvar *x* 10)") "*X*")
|
||||||
|
(cl-test "defparameter: sets value" (evall "(defparameter *y* 42) *y*") 42)
|
||||||
|
(cl-test "defvar: no reinit" (evall "(defvar *z* 1) (defvar *z* 99) *z*") 1)
|
||||||
|
|
||||||
|
;; ── built-in arithmetic ───────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "arith: +" (ev "(+ 1 2 3)") 6)
|
||||||
|
(cl-test "arith: + zero" (ev "(+)") 0)
|
||||||
|
(cl-test "arith: -" (ev "(- 10 3 2)") 5)
|
||||||
|
(cl-test "arith: - negate" (ev "(- 5)") -5)
|
||||||
|
(cl-test "arith: *" (ev "(* 2 3 4)") 24)
|
||||||
|
(cl-test "arith: * one" (ev "(*)") 1)
|
||||||
|
(cl-test "arith: /" (ev "(/ 12 3)") 4)
|
||||||
|
(cl-test "arith: max" (ev "(max 3 1 4 1 5)") 5)
|
||||||
|
(cl-test "arith: min" (ev "(min 3 1 4 1 5)") 1)
|
||||||
|
(cl-test "arith: abs neg" (ev "(abs -7)") 7)
|
||||||
|
(cl-test "arith: abs pos" (ev "(abs 7)") 7)
|
||||||
|
|
||||||
|
;; ── built-in comparisons ──────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "cmp: = true" (ev "(= 3 3)") true)
|
||||||
|
(cl-test "cmp: = false" (ev "(= 3 4)") nil)
|
||||||
|
(cl-test "cmp: /=" (ev "(/= 3 4)") true)
|
||||||
|
(cl-test "cmp: <" (ev "(< 1 2)") true)
|
||||||
|
(cl-test "cmp: > false" (ev "(> 1 2)") nil)
|
||||||
|
(cl-test "cmp: <=" (ev "(<= 2 2)") true)
|
||||||
|
|
||||||
|
;; ── built-in predicates ───────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "pred: null nil" (ev "(null nil)") true)
|
||||||
|
(cl-test "pred: null non-nil" (ev "(null 5)") nil)
|
||||||
|
(cl-test "pred: not nil" (ev "(not nil)") true)
|
||||||
|
(cl-test "pred: not truthy" (ev "(not 5)") nil)
|
||||||
|
(cl-test "pred: numberp" (ev "(numberp 5)") true)
|
||||||
|
(cl-test "pred: numberp str" (ev "(numberp \"x\")") nil)
|
||||||
|
(cl-test "pred: stringp" (ev "(stringp \"hello\")") true)
|
||||||
|
(cl-test "pred: listp list" (ev "(listp '(1))") true)
|
||||||
|
(cl-test "pred: listp nil" (ev "(listp nil)") true)
|
||||||
|
(cl-test "pred: zerop" (ev "(zerop 0)") true)
|
||||||
|
(cl-test "pred: plusp" (ev "(plusp 3)") true)
|
||||||
|
(cl-test "pred: evenp" (ev "(evenp 4)") true)
|
||||||
|
(cl-test "pred: oddp" (ev "(oddp 3)") true)
|
||||||
|
|
||||||
|
;; ── built-in list ops ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "list: car" (ev "(car '(1 2 3))") 1)
|
||||||
|
(cl-test "list: cdr" (ev "(cdr '(1 2 3))") (list 2 3))
|
||||||
|
(cl-test "list: cons" (get (ev "(cons 1 2)") "car") 1)
|
||||||
|
(cl-test "list: list fn" (ev "(list 1 2 3)") (list 1 2 3))
|
||||||
|
(cl-test "list: length" (ev "(length '(a b c))") 3)
|
||||||
|
(cl-test "list: length nil" (ev "(length nil)") 0)
|
||||||
|
(cl-test "list: append" (ev "(append '(1 2) '(3 4))") (list 1 2 3 4))
|
||||||
|
(cl-test "list: first" (ev "(first '(10 20 30))") 10)
|
||||||
|
(cl-test "list: second" (ev "(second '(10 20 30))") 20)
|
||||||
|
(cl-test "list: third" (ev "(third '(10 20 30))") 30)
|
||||||
|
(cl-test "list: rest" (ev "(rest '(1 2 3))") (list 2 3))
|
||||||
|
(cl-test "list: nth" (ev "(nth 1 '(a b c))") "B")
|
||||||
|
(cl-test "list: reverse" (ev "(reverse '(1 2 3))") (list 3 2 1))
|
||||||
|
|
||||||
|
;; ── FUNCALL / APPLY / MAPCAR ─────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "funcall: lambda"
|
||||||
|
(ev "(funcall (lambda (x) (* x x)) 5)")
|
||||||
|
25)
|
||||||
|
(cl-test "apply: basic"
|
||||||
|
(ev "(apply #'+ '(1 2 3))")
|
||||||
|
6)
|
||||||
|
(cl-test "apply: leading args"
|
||||||
|
(ev "(apply #'+ 1 2 '(3 4))")
|
||||||
|
10)
|
||||||
|
(cl-test "mapcar: basic"
|
||||||
|
(ev "(mapcar (lambda (x) (* x 2)) '(1 2 3))")
|
||||||
|
(list 2 4 6))
|
||||||
|
|
||||||
|
;; ── BLOCK / RETURN-FROM / RETURN ─────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "block: last form value"
|
||||||
|
(ev "(block done 1 2 3)")
|
||||||
|
3)
|
||||||
|
(cl-test "block: empty body"
|
||||||
|
(ev "(block done)")
|
||||||
|
nil)
|
||||||
|
(cl-test "block: single form"
|
||||||
|
(ev "(block foo 42)")
|
||||||
|
42)
|
||||||
|
(cl-test "block: return-from"
|
||||||
|
(ev "(block done 1 (return-from done 99) 2)")
|
||||||
|
99)
|
||||||
|
(cl-test "block: return-from nil block"
|
||||||
|
(ev "(block nil 1 (return-from nil 42) 3)")
|
||||||
|
42)
|
||||||
|
(cl-test "block: return-from no value"
|
||||||
|
(ev "(block done (return-from done))")
|
||||||
|
nil)
|
||||||
|
(cl-test "block: nested inner return stays inner"
|
||||||
|
(ev "(block outer (block inner (return-from inner 1) 2) 3)")
|
||||||
|
3)
|
||||||
|
(cl-test "block: nested outer return"
|
||||||
|
(ev "(block outer (block inner 1 2) (return-from outer 99) 3)")
|
||||||
|
99)
|
||||||
|
(cl-test "return: shorthand for nil block"
|
||||||
|
(ev "(block nil (return 77))")
|
||||||
|
77)
|
||||||
|
(cl-test "return: no value"
|
||||||
|
(ev "(block nil 1 (return) 2)")
|
||||||
|
nil)
|
||||||
|
(cl-test "block: return-from inside let"
|
||||||
|
(ev "(block done (let ((x 5)) (when (> x 3) (return-from done x))) 0)")
|
||||||
|
5)
|
||||||
|
(cl-test "block: return-from inside progn"
|
||||||
|
(ev "(block done (progn (return-from done 7) 99))")
|
||||||
|
7)
|
||||||
|
(cl-test "block: return-from through function"
|
||||||
|
(ev "(block done (flet ((f () (return-from done 42))) (f)) nil)")
|
||||||
|
42)
|
||||||
|
|
||||||
|
;; ── TAGBODY / GO ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "tagbody: empty returns nil"
|
||||||
|
(ev "(tagbody)")
|
||||||
|
nil)
|
||||||
|
(cl-test "tagbody: forms only, returns nil"
|
||||||
|
(ev "(let ((x 0)) (tagbody (setq x 1) (setq x 2)) x)")
|
||||||
|
2)
|
||||||
|
(cl-test "tagbody: tag only, returns nil"
|
||||||
|
(ev "(tagbody done)")
|
||||||
|
nil)
|
||||||
|
(cl-test "tagbody: go skips forms"
|
||||||
|
(ev "(let ((x 0)) (tagbody (go done) (setq x 99) done) x)")
|
||||||
|
0)
|
||||||
|
(cl-test "tagbody: go to later tag"
|
||||||
|
(ev "(let ((x 0)) (tagbody start (setq x (+ x 1)) (go done) (setq x 99) done) x)")
|
||||||
|
1)
|
||||||
|
(cl-test "tagbody: loop with counter"
|
||||||
|
(ev "(let ((n 0)) (tagbody loop (when (>= n 3) (go done)) (setq n (+ n 1)) (go loop) done) n)")
|
||||||
|
3)
|
||||||
|
(cl-test "tagbody: go inside when"
|
||||||
|
(ev "(let ((x 0)) (tagbody (setq x 1) (when t (go done)) (setq x 99) done) x)")
|
||||||
|
1)
|
||||||
|
(cl-test "tagbody: go inside progn"
|
||||||
|
(ev "(let ((x 0)) (tagbody (progn (setq x 1) (go done)) (setq x 99) done) x)")
|
||||||
|
1)
|
||||||
|
(cl-test "tagbody: go inside let"
|
||||||
|
(ev "(let ((acc 0)) (tagbody (let ((y 5)) (when (> y 3) (go done))) (setq acc 99) done) acc)")
|
||||||
|
0)
|
||||||
|
(cl-test "tagbody: integer tags"
|
||||||
|
(ev "(let ((x 0)) (tagbody (go 2) 1 (setq x 1) (go 3) 2 (setq x 2) (go 3) 3) x)")
|
||||||
|
2)
|
||||||
|
(cl-test "tagbody: block-return propagates out"
|
||||||
|
(ev "(block done (tagbody (return-from done 42)) nil)")
|
||||||
|
42)
|
||||||
|
|
||||||
|
;; ── UNWIND-PROTECT ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "unwind-protect: normal returns protected"
|
||||||
|
(ev "(unwind-protect 42 nil)")
|
||||||
|
42)
|
||||||
|
(cl-test "unwind-protect: cleanup runs"
|
||||||
|
(ev "(let ((x 0)) (unwind-protect 1 (setq x 99)) x)")
|
||||||
|
99)
|
||||||
|
(cl-test "unwind-protect: cleanup result ignored"
|
||||||
|
(ev "(unwind-protect 42 777)")
|
||||||
|
42)
|
||||||
|
(cl-test "unwind-protect: multiple cleanup forms"
|
||||||
|
(ev "(let ((x 0)) (unwind-protect 1 (setq x (+ x 1)) (setq x (+ x 1))) x)")
|
||||||
|
2)
|
||||||
|
(cl-test "unwind-protect: cleanup on return-from"
|
||||||
|
(ev "(let ((x 0)) (block done (unwind-protect (return-from done 7) (setq x 99))) x)")
|
||||||
|
99)
|
||||||
|
(cl-test "unwind-protect: return-from still propagates"
|
||||||
|
(ev "(block done (unwind-protect (return-from done 42) nil))")
|
||||||
|
42)
|
||||||
|
(cl-test "unwind-protect: cleanup on go"
|
||||||
|
(ev "(let ((x 0)) (tagbody (unwind-protect (go done) (setq x 1)) done) x)")
|
||||||
|
1)
|
||||||
|
(cl-test "unwind-protect: nested, inner cleanup first"
|
||||||
|
(ev "(let ((n 0)) (unwind-protect (unwind-protect 1 (setq n (+ n 10))) (setq n (+ n 1))) n)")
|
||||||
|
11)
|
||||||
|
|
||||||
|
;; ── VALUES / MULTIPLE-VALUE-BIND / NTH-VALUE ────────────────────
|
||||||
|
|
||||||
|
(cl-test "values: single returns plain"
|
||||||
|
(ev "(values 42)")
|
||||||
|
42)
|
||||||
|
(cl-test "values: zero returns nil"
|
||||||
|
(ev "(values)")
|
||||||
|
nil)
|
||||||
|
(cl-test "values: multi — primary via funcall"
|
||||||
|
(ev "(car (list (values 1 2)))")
|
||||||
|
1)
|
||||||
|
(cl-test "multiple-value-bind: basic"
|
||||||
|
(ev "(multiple-value-bind (a b) (values 1 2) (+ a b))")
|
||||||
|
3)
|
||||||
|
(cl-test "multiple-value-bind: extra vars get nil"
|
||||||
|
(ev "(multiple-value-bind (a b c) (values 10 20) (list a b c))")
|
||||||
|
(list 10 20 nil))
|
||||||
|
(cl-test "multiple-value-bind: extra values ignored"
|
||||||
|
(ev "(multiple-value-bind (a) (values 1 2 3) a)")
|
||||||
|
1)
|
||||||
|
(cl-test "multiple-value-bind: single value source"
|
||||||
|
(ev "(multiple-value-bind (a b) 42 (list a b))")
|
||||||
|
(list 42 nil))
|
||||||
|
(cl-test "nth-value: 0"
|
||||||
|
(ev "(nth-value 0 (values 10 20 30))")
|
||||||
|
10)
|
||||||
|
(cl-test "nth-value: 1"
|
||||||
|
(ev "(nth-value 1 (values 10 20 30))")
|
||||||
|
20)
|
||||||
|
(cl-test "nth-value: out of range"
|
||||||
|
(ev "(nth-value 5 (values 10 20))")
|
||||||
|
nil)
|
||||||
|
(cl-test "multiple-value-call: basic"
|
||||||
|
(ev "(multiple-value-call #'+ (values 1 2) (values 3 4))")
|
||||||
|
10)
|
||||||
|
(cl-test "multiple-value-prog1: returns first"
|
||||||
|
(ev "(multiple-value-prog1 1 2 3)")
|
||||||
|
1)
|
||||||
|
(cl-test "multiple-value-prog1: side effects run"
|
||||||
|
(ev "(let ((x 0)) (multiple-value-prog1 99 (setq x 7)) x)")
|
||||||
|
7)
|
||||||
|
(cl-test "values: nil primary in if"
|
||||||
|
(ev "(if (values nil t) 'yes 'no)")
|
||||||
|
"NO")
|
||||||
|
(cl-test "values: truthy primary in if"
|
||||||
|
(ev "(if (values 42 nil) 'yes 'no)")
|
||||||
|
"YES")
|
||||||
|
|
||||||
|
;; --- Dynamic variables ---
|
||||||
|
(cl-test "defvar marks special"
|
||||||
|
(do (ev "(defvar *dv* 10)")
|
||||||
|
(cl-special? "*DV*"))
|
||||||
|
true)
|
||||||
|
(cl-test "defvar: let rebinds dynamically"
|
||||||
|
(ev "(progn (defvar *x* 1) (defun get-x () *x*) (let ((*x* 99)) (get-x)))")
|
||||||
|
99)
|
||||||
|
(cl-test "defvar: binding restores after let"
|
||||||
|
(ev "(progn (defvar *yrst* 5) (let ((*yrst* 42)) *yrst*) *yrst*)")
|
||||||
|
5)
|
||||||
|
(cl-test "defparameter marks special"
|
||||||
|
(do (ev "(defparameter *dp* 0)")
|
||||||
|
(cl-special? "*DP*"))
|
||||||
|
true)
|
||||||
|
(cl-test "defparameter: let rebinds dynamically"
|
||||||
|
(ev "(progn (defparameter *z* 10) (defun get-z () *z*) (let ((*z* 77)) (get-z)))")
|
||||||
|
77)
|
||||||
|
(cl-test "defparameter: always assigns"
|
||||||
|
(ev "(progn (defparameter *p* 1) (defparameter *p* 2) *p*)")
|
||||||
|
2)
|
||||||
|
(cl-test "dynamic binding: nested lets"
|
||||||
|
(ev "(progn (defvar *n* 0) (let ((*n* 1)) (let ((*n* 2)) *n*)))")
|
||||||
|
2)
|
||||||
|
(cl-test "dynamic binding: restores across nesting"
|
||||||
|
(ev "(progn (defvar *m* 10) (let ((*m* 20)) (let ((*m* 30)) nil)) *m*)")
|
||||||
|
10)
|
||||||
204
lib/common-lisp/tests/lambda.sx
Normal file
204
lib/common-lisp/tests/lambda.sx
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
;; Lambda list parser tests
|
||||||
|
|
||||||
|
(define cl-test-pass 0)
|
||||||
|
(define cl-test-fail 0)
|
||||||
|
(define cl-test-fails (list))
|
||||||
|
|
||||||
|
;; Deep structural equality for dicts and lists
|
||||||
|
(define
|
||||||
|
cl-deep=
|
||||||
|
(fn
|
||||||
|
(a b)
|
||||||
|
(cond
|
||||||
|
((= a b) true)
|
||||||
|
((and (dict? a) (dict? b))
|
||||||
|
(let
|
||||||
|
((ak (keys a)) (bk (keys b)))
|
||||||
|
(if
|
||||||
|
(not (= (len ak) (len bk)))
|
||||||
|
false
|
||||||
|
(every?
|
||||||
|
(fn (k) (and (has-key? b k) (cl-deep= (get a k) (get b k))))
|
||||||
|
ak))))
|
||||||
|
((and (list? a) (list? b))
|
||||||
|
(if
|
||||||
|
(not (= (len a) (len b)))
|
||||||
|
false
|
||||||
|
(let
|
||||||
|
((i 0) (ok true))
|
||||||
|
(define
|
||||||
|
chk
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(and ok (< i (len a)))
|
||||||
|
(do
|
||||||
|
(when
|
||||||
|
(not (cl-deep= (nth a i) (nth b i)))
|
||||||
|
(set! ok false))
|
||||||
|
(set! i (+ i 1))
|
||||||
|
(chk)))))
|
||||||
|
(chk)
|
||||||
|
ok)))
|
||||||
|
(:else false))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(if
|
||||||
|
(cl-deep= actual expected)
|
||||||
|
(set! cl-test-pass (+ cl-test-pass 1))
|
||||||
|
(do
|
||||||
|
(set! cl-test-fail (+ cl-test-fail 1))
|
||||||
|
(append! cl-test-fails {:name name :expected expected :actual actual})))))
|
||||||
|
|
||||||
|
;; Helper: parse lambda list from string "(x y ...)"
|
||||||
|
(define ll (fn (src) (cl-parse-lambda-list-str src)))
|
||||||
|
(define ll-req (fn (src) (get (ll src) "required")))
|
||||||
|
(define ll-opt (fn (src) (get (ll src) "optional")))
|
||||||
|
(define ll-rest (fn (src) (get (ll src) "rest")))
|
||||||
|
(define ll-key (fn (src) (get (ll src) "key")))
|
||||||
|
(define ll-aok (fn (src) (get (ll src) "allow-other-keys")))
|
||||||
|
(define ll-aux (fn (src) (get (ll src) "aux")))
|
||||||
|
|
||||||
|
;; ── required parameters ───────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "required: empty" (ll-req "()") (list))
|
||||||
|
(cl-test "required: one" (ll-req "(x)") (list "X"))
|
||||||
|
(cl-test "required: two" (ll-req "(x y)") (list "X" "Y"))
|
||||||
|
(cl-test "required: three" (ll-req "(a b c)") (list "A" "B" "C"))
|
||||||
|
(cl-test "required: upcased" (ll-req "(foo bar)") (list "FOO" "BAR"))
|
||||||
|
|
||||||
|
;; ── &optional ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "optional: none" (ll-opt "(x)") (list))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"optional: bare symbol"
|
||||||
|
(ll-opt "(x &optional z)")
|
||||||
|
(list {:name "Z" :default nil :supplied nil}))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"optional: with default"
|
||||||
|
(ll-opt "(x &optional (z 0))")
|
||||||
|
(list {:name "Z" :default 0 :supplied nil}))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"optional: with supplied-p"
|
||||||
|
(ll-opt "(x &optional (z 0 z-p))")
|
||||||
|
(list {:name "Z" :default 0 :supplied "Z-P"}))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"optional: two params"
|
||||||
|
(ll-opt "(&optional a (b 1))")
|
||||||
|
(list {:name "A" :default nil :supplied nil} {:name "B" :default 1 :supplied nil}))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"optional: string default"
|
||||||
|
(ll-opt "(&optional (name \"world\"))")
|
||||||
|
(list {:name "NAME" :default {:cl-type "string" :value "world"} :supplied nil}))
|
||||||
|
|
||||||
|
;; ── &rest ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "rest: none" (ll-rest "(x)") nil)
|
||||||
|
(cl-test "rest: present" (ll-rest "(x &rest args)") "ARGS")
|
||||||
|
(cl-test "rest: with required" (ll-rest "(a b &rest tail)") "TAIL")
|
||||||
|
|
||||||
|
;; &body is an alias for &rest
|
||||||
|
(cl-test "body: alias for rest" (ll-rest "(&body forms)") "FORMS")
|
||||||
|
|
||||||
|
;; rest doesn't consume required params
|
||||||
|
(cl-test "rest: required still there" (ll-req "(a b &rest rest)") (list "A" "B"))
|
||||||
|
|
||||||
|
;; ── &key ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "key: none" (ll-key "(x)") (list))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"key: bare symbol"
|
||||||
|
(ll-key "(&key x)")
|
||||||
|
(list {:name "X" :keyword "X" :default nil :supplied nil}))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"key: with default"
|
||||||
|
(ll-key "(&key (x 42))")
|
||||||
|
(list {:name "X" :keyword "X" :default 42 :supplied nil}))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"key: with supplied-p"
|
||||||
|
(ll-key "(&key (x 42 x-p))")
|
||||||
|
(list {:name "X" :keyword "X" :default 42 :supplied "X-P"}))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"key: two params"
|
||||||
|
(ll-key "(&key a b)")
|
||||||
|
(list
|
||||||
|
{:name "A" :keyword "A" :default nil :supplied nil}
|
||||||
|
{:name "B" :keyword "B" :default nil :supplied nil}))
|
||||||
|
|
||||||
|
;; ── &allow-other-keys ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "aok: absent" (ll-aok "(x)") false)
|
||||||
|
(cl-test "aok: present" (ll-aok "(&key x &allow-other-keys)") true)
|
||||||
|
|
||||||
|
;; ── &aux ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "aux: none" (ll-aux "(x)") (list))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"aux: bare symbol"
|
||||||
|
(ll-aux "(&aux temp)")
|
||||||
|
(list {:name "TEMP" :init nil}))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"aux: with init"
|
||||||
|
(ll-aux "(&aux (count 0))")
|
||||||
|
(list {:name "COUNT" :init 0}))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"aux: two vars"
|
||||||
|
(ll-aux "(&aux a (b 1))")
|
||||||
|
(list {:name "A" :init nil} {:name "B" :init 1}))
|
||||||
|
|
||||||
|
;; ── combined ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"combined: full lambda list"
|
||||||
|
(let
|
||||||
|
((parsed (ll "(x y &optional (z 0 z-p) &rest args &key a (b nil b-p) &aux temp)")))
|
||||||
|
(list
|
||||||
|
(get parsed "required")
|
||||||
|
(get (nth (get parsed "optional") 0) "name")
|
||||||
|
(get (nth (get parsed "optional") 0) "default")
|
||||||
|
(get (nth (get parsed "optional") 0) "supplied")
|
||||||
|
(get parsed "rest")
|
||||||
|
(get (nth (get parsed "key") 0) "name")
|
||||||
|
(get (nth (get parsed "key") 1) "supplied")
|
||||||
|
(get (nth (get parsed "aux") 0) "name")))
|
||||||
|
(list
|
||||||
|
(list "X" "Y")
|
||||||
|
"Z"
|
||||||
|
0
|
||||||
|
"Z-P"
|
||||||
|
"ARGS"
|
||||||
|
"A"
|
||||||
|
"B-P"
|
||||||
|
"TEMP"))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"combined: required only stops before &"
|
||||||
|
(ll-req "(a b &optional c)")
|
||||||
|
(list "A" "B"))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"combined: required only with &key"
|
||||||
|
(ll-req "(x &key y)")
|
||||||
|
(list "X"))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"combined: &rest and &key together"
|
||||||
|
(let
|
||||||
|
((parsed (ll "(&rest args &key verbose)")))
|
||||||
|
(list (get parsed "rest") (get (nth (get parsed "key") 0) "name")))
|
||||||
|
(list "ARGS" "VERBOSE"))
|
||||||
204
lib/common-lisp/tests/macros.sx
Normal file
204
lib/common-lisp/tests/macros.sx
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
;; lib/common-lisp/tests/macros.sx — Phase 5: defmacro, gensym, LOOP tests
|
||||||
|
;;
|
||||||
|
;; Depends on: runtime.sx, eval.sx, loop.sx already loaded.
|
||||||
|
;; Tests via (ev "...") using the CL evaluator.
|
||||||
|
|
||||||
|
(define ev (fn (src) (cl-eval-str src (cl-make-env))))
|
||||||
|
(define evall (fn (src) (cl-eval-all-str src (cl-make-env))))
|
||||||
|
|
||||||
|
(define passed 0)
|
||||||
|
(define failed 0)
|
||||||
|
(define failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
check
|
||||||
|
(fn
|
||||||
|
(label got expected)
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list
|
||||||
|
(str
|
||||||
|
"FAIL ["
|
||||||
|
label
|
||||||
|
"]: got="
|
||||||
|
(inspect got)
|
||||||
|
" expected="
|
||||||
|
(inspect expected)))))))))
|
||||||
|
|
||||||
|
;; ── defmacro basics ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check
|
||||||
|
"defmacro returns name"
|
||||||
|
(ev "(defmacro my-or (a b) (list 'if a a b))")
|
||||||
|
"MY-OR")
|
||||||
|
|
||||||
|
(check
|
||||||
|
"defmacro expansion works"
|
||||||
|
(ev "(progn (defmacro my-inc (x) (list '+ x 1)) (my-inc 5))")
|
||||||
|
6)
|
||||||
|
|
||||||
|
(check
|
||||||
|
"defmacro with &rest"
|
||||||
|
(ev "(progn (defmacro my-list (&rest xs) (cons 'list xs)) (my-list 1 2 3))")
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(check
|
||||||
|
"nested macro expansion"
|
||||||
|
(ev "(progn (defmacro sq (x) (list '* x x)) (sq 7))")
|
||||||
|
49)
|
||||||
|
|
||||||
|
(check
|
||||||
|
"macro in conditional"
|
||||||
|
(ev
|
||||||
|
"(progn (defmacro my-when (c &rest body) (list 'if c (cons 'progn body) nil)) (my-when t 10 20))")
|
||||||
|
20)
|
||||||
|
|
||||||
|
(check
|
||||||
|
"macro returns nil branch"
|
||||||
|
(ev
|
||||||
|
"(progn (defmacro my-when (c &rest body) (list 'if c (cons 'progn body) nil)) (my-when nil 42))")
|
||||||
|
nil)
|
||||||
|
|
||||||
|
;; ── macroexpand ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check
|
||||||
|
"macroexpand returns expanded form"
|
||||||
|
(ev "(progn (defmacro double (x) (list '+ x x)) (macroexpand '(double 5)))")
|
||||||
|
(list "+" 5 5))
|
||||||
|
|
||||||
|
;; ── gensym ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "gensym returns string" (ev "(stringp (gensym))") true)
|
||||||
|
|
||||||
|
(check
|
||||||
|
"gensym prefix"
|
||||||
|
(ev "(let ((g (gensym \"MY\"))) (not (= g nil)))")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(check "gensyms are unique" (ev "(not (= (gensym) (gensym)))") true)
|
||||||
|
|
||||||
|
;; ── swap! macro with gensym ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check
|
||||||
|
"swap! macro"
|
||||||
|
(evall
|
||||||
|
"(defmacro swap! (a b) (let ((tmp (gensym))) (list 'let (list (list tmp a)) (list 'setq a b) (list 'setq b tmp)))) (defvar *a* 10) (defvar *b* 20) (swap! *a* *b*) (list *a* *b*)")
|
||||||
|
(list 20 10))
|
||||||
|
|
||||||
|
;; ── LOOP: basic repeat and collect ────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop repeat collect"
|
||||||
|
(ev "(loop repeat 3 collect 99)")
|
||||||
|
(list 99 99 99))
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop for-in collect"
|
||||||
|
(ev "(loop for x in '(1 2 3) collect (* x x))")
|
||||||
|
(list 1 4 9))
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop for-from-to collect"
|
||||||
|
(ev "(loop for i from 1 to 5 collect i)")
|
||||||
|
(list 1 2 3 4 5))
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop for-from-below collect"
|
||||||
|
(ev "(loop for i from 0 below 4 collect i)")
|
||||||
|
(list 0 1 2 3))
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop for-downto collect"
|
||||||
|
(ev "(loop for i from 5 downto 1 collect i)")
|
||||||
|
(list 5 4 3 2 1))
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop for-by collect"
|
||||||
|
(ev "(loop for i from 0 to 10 by 2 collect i)")
|
||||||
|
(list 0 2 4 6 8 10))
|
||||||
|
|
||||||
|
;; ── LOOP: sum, count, maximize, minimize ─────────────────────────────────────
|
||||||
|
|
||||||
|
(check "loop sum" (ev "(loop for i from 1 to 5 sum i)") 15)
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop count"
|
||||||
|
(ev "(loop for x in '(1 2 3 4 5) count (> x 3))")
|
||||||
|
2)
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop maximize"
|
||||||
|
(ev "(loop for x in '(3 1 4 1 5 9 2 6) maximize x)")
|
||||||
|
9)
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop minimize"
|
||||||
|
(ev "(loop for x in '(3 1 4 1 5 9 2 6) minimize x)")
|
||||||
|
1)
|
||||||
|
|
||||||
|
;; ── LOOP: while and until ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop while"
|
||||||
|
(ev "(loop for i from 1 to 10 while (< i 5) collect i)")
|
||||||
|
(list 1 2 3 4))
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop until"
|
||||||
|
(ev "(loop for i from 1 to 10 until (= i 5) collect i)")
|
||||||
|
(list 1 2 3 4))
|
||||||
|
|
||||||
|
;; ── LOOP: when / unless ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop when filter"
|
||||||
|
(ev "(loop for i from 0 below 8 when (evenp i) collect i)")
|
||||||
|
(list 0 2 4 6))
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop unless filter"
|
||||||
|
(ev "(loop for i from 0 below 8 unless (evenp i) collect i)")
|
||||||
|
(list 1 3 5 7))
|
||||||
|
|
||||||
|
;; ── LOOP: append ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop append"
|
||||||
|
(ev "(loop for x in '((1 2) (3 4) (5 6)) append x)")
|
||||||
|
(list 1 2 3 4 5 6))
|
||||||
|
|
||||||
|
;; ── LOOP: always, never, thereis ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop always true"
|
||||||
|
(ev "(loop for x in '(2 4 6) always (evenp x))")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop always false"
|
||||||
|
(ev "(loop for x in '(2 3 6) always (evenp x))")
|
||||||
|
false)
|
||||||
|
|
||||||
|
(check "loop never" (ev "(loop for x in '(1 3 5) never (evenp x))") true)
|
||||||
|
|
||||||
|
(check "loop thereis" (ev "(loop for x in '(1 2 3) thereis (> x 2))") true)
|
||||||
|
|
||||||
|
;; ── LOOP: for = then (general iteration) ─────────────────────────────────────
|
||||||
|
|
||||||
|
(check
|
||||||
|
"loop for = then doubling"
|
||||||
|
(ev "(loop repeat 5 for x = 1 then (* x 2) collect x)")
|
||||||
|
(list 1 2 4 8 16))
|
||||||
|
|
||||||
|
;; ── summary ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define macro-passed passed)
|
||||||
|
(define macro-failed failed)
|
||||||
|
(define macro-failures failures)
|
||||||
160
lib/common-lisp/tests/parse.sx
Normal file
160
lib/common-lisp/tests/parse.sx
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
;; Common Lisp reader/parser tests
|
||||||
|
|
||||||
|
(define cl-test-pass 0)
|
||||||
|
(define cl-test-fail 0)
|
||||||
|
(define cl-test-fails (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-deep=
|
||||||
|
(fn
|
||||||
|
(a b)
|
||||||
|
(cond
|
||||||
|
((= a b) true)
|
||||||
|
((and (dict? a) (dict? b))
|
||||||
|
(let
|
||||||
|
((ak (keys a)) (bk (keys b)))
|
||||||
|
(if
|
||||||
|
(not (= (len ak) (len bk)))
|
||||||
|
false
|
||||||
|
(every?
|
||||||
|
(fn (k) (and (has-key? b k) (cl-deep= (get a k) (get b k))))
|
||||||
|
ak))))
|
||||||
|
((and (list? a) (list? b))
|
||||||
|
(if
|
||||||
|
(not (= (len a) (len b)))
|
||||||
|
false
|
||||||
|
(let
|
||||||
|
((i 0) (ok true))
|
||||||
|
(define
|
||||||
|
chk
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(and ok (< i (len a)))
|
||||||
|
(do
|
||||||
|
(when
|
||||||
|
(not (cl-deep= (nth a i) (nth b i)))
|
||||||
|
(set! ok false))
|
||||||
|
(set! i (+ i 1))
|
||||||
|
(chk)))))
|
||||||
|
(chk)
|
||||||
|
ok)))
|
||||||
|
(:else false))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(if
|
||||||
|
(cl-deep= actual expected)
|
||||||
|
(set! cl-test-pass (+ cl-test-pass 1))
|
||||||
|
(do
|
||||||
|
(set! cl-test-fail (+ cl-test-fail 1))
|
||||||
|
(append! cl-test-fails {:name name :expected expected :actual actual})))))
|
||||||
|
|
||||||
|
;; ── atoms ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "integer: 42" (cl-read "42") 42)
|
||||||
|
(cl-test "integer: 0" (cl-read "0") 0)
|
||||||
|
(cl-test "integer: negative" (cl-read "-5") -5)
|
||||||
|
(cl-test "integer: positive sign" (cl-read "+3") 3)
|
||||||
|
(cl-test "integer: hex #xFF" (cl-read "#xFF") 255)
|
||||||
|
(cl-test "integer: hex #xAB" (cl-read "#xAB") 171)
|
||||||
|
(cl-test "integer: binary #b1010" (cl-read "#b1010") 10)
|
||||||
|
(cl-test "integer: octal #o17" (cl-read "#o17") 15)
|
||||||
|
|
||||||
|
(cl-test "float: type" (get (cl-read "3.14") "cl-type") "float")
|
||||||
|
(cl-test "float: value" (get (cl-read "3.14") "value") "3.14")
|
||||||
|
(cl-test "float: neg" (get (cl-read "-2.5") "value") "-2.5")
|
||||||
|
(cl-test "float: exp" (get (cl-read "1.0e10") "value") "1.0e10")
|
||||||
|
|
||||||
|
(cl-test "ratio: type" (get (cl-read "1/3") "cl-type") "ratio")
|
||||||
|
(cl-test "ratio: value" (get (cl-read "1/3") "value") "1/3")
|
||||||
|
(cl-test "ratio: 22/7" (get (cl-read "22/7") "value") "22/7")
|
||||||
|
|
||||||
|
(cl-test "string: basic" (cl-read "\"hello\"") {:cl-type "string" :value "hello"})
|
||||||
|
(cl-test "string: empty" (cl-read "\"\"") {:cl-type "string" :value ""})
|
||||||
|
(cl-test "string: with escape" (cl-read "\"a\\nb\"") {:cl-type "string" :value "a\nb"})
|
||||||
|
|
||||||
|
(cl-test "symbol: foo" (cl-read "foo") "FOO")
|
||||||
|
(cl-test "symbol: BAR" (cl-read "BAR") "BAR")
|
||||||
|
(cl-test "symbol: pkg:sym" (cl-read "cl:car") "CL:CAR")
|
||||||
|
(cl-test "symbol: pkg::sym" (cl-read "pkg::foo") "PKG::FOO")
|
||||||
|
|
||||||
|
(cl-test "nil: symbol" (cl-read "nil") nil)
|
||||||
|
(cl-test "nil: uppercase" (cl-read "NIL") nil)
|
||||||
|
(cl-test "t: symbol" (cl-read "t") true)
|
||||||
|
(cl-test "t: uppercase" (cl-read "T") true)
|
||||||
|
|
||||||
|
(cl-test "keyword: type" (get (cl-read ":foo") "cl-type") "keyword")
|
||||||
|
(cl-test "keyword: name" (get (cl-read ":foo") "name") "FOO")
|
||||||
|
(cl-test "keyword: :test" (get (cl-read ":test") "name") "TEST")
|
||||||
|
|
||||||
|
(cl-test "char: type" (get (cl-read "#\\a") "cl-type") "char")
|
||||||
|
(cl-test "char: value" (get (cl-read "#\\a") "value") "a")
|
||||||
|
(cl-test "char: Space" (get (cl-read "#\\Space") "value") " ")
|
||||||
|
(cl-test "char: Newline" (get (cl-read "#\\Newline") "value") "\n")
|
||||||
|
|
||||||
|
(cl-test "uninterned: type" (get (cl-read "#:foo") "cl-type") "uninterned")
|
||||||
|
(cl-test "uninterned: name" (get (cl-read "#:foo") "name") "FOO")
|
||||||
|
|
||||||
|
;; ── lists ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "list: empty" (cl-read "()") (list))
|
||||||
|
(cl-test "list: one element" (cl-read "(foo)") (list "FOO"))
|
||||||
|
(cl-test "list: two elements" (cl-read "(foo bar)") (list "FOO" "BAR"))
|
||||||
|
(cl-test "list: nested" (cl-read "((a b) c)") (list (list "A" "B") "C"))
|
||||||
|
(cl-test "list: with integer" (cl-read "(+ 1 2)") (list "+" 1 2))
|
||||||
|
(cl-test "list: with string" (cl-read "(print \"hi\")") (list "PRINT" {:cl-type "string" :value "hi"}))
|
||||||
|
(cl-test "list: nil element" (cl-read "(a nil b)") (list "A" nil "B"))
|
||||||
|
(cl-test "list: t element" (cl-read "(a t b)") (list "A" true "B"))
|
||||||
|
|
||||||
|
;; ── dotted pairs ──────────────────────────────────────────────<E29480><E29480>──
|
||||||
|
|
||||||
|
(cl-test "dotted: type" (get (cl-read "(a . b)") "cl-type") "cons")
|
||||||
|
(cl-test "dotted: car" (get (cl-read "(a . b)") "car") "A")
|
||||||
|
(cl-test "dotted: cdr" (get (cl-read "(a . b)") "cdr") "B")
|
||||||
|
(cl-test "dotted: number cdr" (get (cl-read "(x . 42)") "cdr") 42)
|
||||||
|
|
||||||
|
;; ── reader macros ────────────────────────────────────────────────<E29480><E29480>
|
||||||
|
|
||||||
|
(cl-test "quote: form" (cl-read "'x") (list "QUOTE" "X"))
|
||||||
|
(cl-test "quote: list" (cl-read "'(a b)") (list "QUOTE" (list "A" "B")))
|
||||||
|
(cl-test "backquote: form" (cl-read "`x") (list "QUASIQUOTE" "X"))
|
||||||
|
(cl-test "unquote: form" (cl-read ",x") (list "UNQUOTE" "X"))
|
||||||
|
(cl-test "comma-at: form" (cl-read ",@x") (list "UNQUOTE-SPLICING" "X"))
|
||||||
|
(cl-test "function: form" (cl-read "#'foo") (list "FUNCTION" "FOO"))
|
||||||
|
|
||||||
|
;; ── vector ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "vector: type" (get (cl-read "#(1 2 3)") "cl-type") "vector")
|
||||||
|
(cl-test "vector: elements" (get (cl-read "#(1 2 3)") "elements") (list 1 2 3))
|
||||||
|
(cl-test "vector: empty" (get (cl-read "#()") "elements") (list))
|
||||||
|
(cl-test "vector: mixed" (get (cl-read "#(a 1 \"s\")") "elements") (list "A" 1 {:cl-type "string" :value "s"}))
|
||||||
|
|
||||||
|
;; ── cl-read-all ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"read-all: empty"
|
||||||
|
(cl-read-all "")
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"read-all: two forms"
|
||||||
|
(cl-read-all "42 foo")
|
||||||
|
(list 42 "FOO"))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"read-all: three forms"
|
||||||
|
(cl-read-all "(+ 1 2) (+ 3 4) hello")
|
||||||
|
(list (list "+" 1 2) (list "+" 3 4) "HELLO"))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"read-all: with comments"
|
||||||
|
(cl-read-all "; this is a comment\n42 ; inline\nfoo")
|
||||||
|
(list 42 "FOO"))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"read-all: defun form"
|
||||||
|
(nth (cl-read-all "(defun square (x) (* x x))") 0)
|
||||||
|
(list "DEFUN" "SQUARE" (list "X") (list "*" "X" "X")))
|
||||||
291
lib/common-lisp/tests/programs/geometry.sx
Normal file
291
lib/common-lisp/tests/programs/geometry.sx
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
;; geometry.sx — Multiple dispatch with CLOS
|
||||||
|
;;
|
||||||
|
;; Demonstrates generic functions dispatching on combinations of
|
||||||
|
;; geometric types: point, line, plane.
|
||||||
|
;;
|
||||||
|
;; Depends on: lib/common-lisp/runtime.sx, lib/common-lisp/clos.sx
|
||||||
|
|
||||||
|
;; ── geometric classes ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(clos-defclass "geo-point" (list "t") (list {:initform 0 :initarg ":px" :reader nil :writer nil :accessor nil :name "px"} {:initform 0 :initarg ":py" :reader nil :writer nil :accessor nil :name "py"}))
|
||||||
|
|
||||||
|
(clos-defclass "geo-line" (list "t") (list {:initform nil :initarg ":p1" :reader nil :writer nil :accessor nil :name "p1"} {:initform nil :initarg ":p2" :reader nil :writer nil :accessor nil :name "p2"}))
|
||||||
|
|
||||||
|
(clos-defclass "geo-plane" (list "t") (list {:initform nil :initarg ":normal" :reader nil :writer nil :accessor nil :name "normal"} {:initform 0 :initarg ":d" :reader nil :writer nil :accessor nil :name "d"}))
|
||||||
|
|
||||||
|
;; ── helpers ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define geo-point-x (fn (p) (clos-slot-value p "px")))
|
||||||
|
(define geo-point-y (fn (p) (clos-slot-value p "py")))
|
||||||
|
|
||||||
|
(define
|
||||||
|
geo-make-point
|
||||||
|
(fn (x y) (clos-make-instance "geo-point" ":px" x ":py" y)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
geo-make-line
|
||||||
|
(fn (p1 p2) (clos-make-instance "geo-line" ":p1" p1 ":p2" p2)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
geo-make-plane
|
||||||
|
(fn
|
||||||
|
(nx ny d)
|
||||||
|
(clos-make-instance "geo-plane" ":normal" (list nx ny) ":d" d)))
|
||||||
|
|
||||||
|
;; ── describe generic ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(clos-defgeneric "geo-describe" {})
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"geo-describe"
|
||||||
|
(list)
|
||||||
|
(list "geo-point")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((p (first args)))
|
||||||
|
(str "P(" (geo-point-x p) "," (geo-point-y p) ")"))))
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"geo-describe"
|
||||||
|
(list)
|
||||||
|
(list "geo-line")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((l (first args)))
|
||||||
|
(str
|
||||||
|
"L["
|
||||||
|
(clos-call-generic "geo-describe" (list (clos-slot-value l "p1")))
|
||||||
|
"-"
|
||||||
|
(clos-call-generic "geo-describe" (list (clos-slot-value l "p2")))
|
||||||
|
"]"))))
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"geo-describe"
|
||||||
|
(list)
|
||||||
|
(list "geo-plane")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((pl (first args)))
|
||||||
|
(str "Plane(d=" (clos-slot-value pl "d") ")"))))
|
||||||
|
|
||||||
|
;; ── intersect: multi-dispatch generic ─────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; Returns a string description of the intersection result.
|
||||||
|
|
||||||
|
(clos-defgeneric "intersect" {})
|
||||||
|
|
||||||
|
;; point ∩ point: same if coordinates match
|
||||||
|
(clos-defmethod
|
||||||
|
"intersect"
|
||||||
|
(list)
|
||||||
|
(list "geo-point" "geo-point")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((p1 (first args)) (p2 (first (rest args))))
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(= (geo-point-x p1) (geo-point-x p2))
|
||||||
|
(= (geo-point-y p1) (geo-point-y p2)))
|
||||||
|
"point"
|
||||||
|
"empty"))))
|
||||||
|
|
||||||
|
;; point ∩ line: check if point lies on line (cross product = 0)
|
||||||
|
(clos-defmethod
|
||||||
|
"intersect"
|
||||||
|
(list)
|
||||||
|
(list "geo-point" "geo-line")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((pt (first args)) (ln (first (rest args))))
|
||||||
|
(let
|
||||||
|
((lp1 (clos-slot-value ln "p1")) (lp2 (clos-slot-value ln "p2")))
|
||||||
|
(let
|
||||||
|
((dx (- (geo-point-x lp2) (geo-point-x lp1)))
|
||||||
|
(dy (- (geo-point-y lp2) (geo-point-y lp1)))
|
||||||
|
(ex (- (geo-point-x pt) (geo-point-x lp1)))
|
||||||
|
(ey (- (geo-point-y pt) (geo-point-y lp1))))
|
||||||
|
(if (= (- (* dx ey) (* dy ex)) 0) "point" "empty"))))))
|
||||||
|
|
||||||
|
;; line ∩ line: parallel (same slope = empty) or point
|
||||||
|
(clos-defmethod
|
||||||
|
"intersect"
|
||||||
|
(list)
|
||||||
|
(list "geo-line" "geo-line")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((l1 (first args)) (l2 (first (rest args))))
|
||||||
|
(let
|
||||||
|
((p1 (clos-slot-value l1 "p1"))
|
||||||
|
(p2 (clos-slot-value l1 "p2"))
|
||||||
|
(p3 (clos-slot-value l2 "p1"))
|
||||||
|
(p4 (clos-slot-value l2 "p2")))
|
||||||
|
(let
|
||||||
|
((dx1 (- (geo-point-x p2) (geo-point-x p1)))
|
||||||
|
(dy1 (- (geo-point-y p2) (geo-point-y p1)))
|
||||||
|
(dx2 (- (geo-point-x p4) (geo-point-x p3)))
|
||||||
|
(dy2 (- (geo-point-y p4) (geo-point-y p3))))
|
||||||
|
(let
|
||||||
|
((cross (- (* dx1 dy2) (* dy1 dx2))))
|
||||||
|
(if (= cross 0) "parallel" "point")))))))
|
||||||
|
|
||||||
|
;; line ∩ plane: general case = point (or parallel if line ⊥ normal)
|
||||||
|
(clos-defmethod
|
||||||
|
"intersect"
|
||||||
|
(list)
|
||||||
|
(list "geo-line" "geo-plane")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((ln (first args)) (pl (first (rest args))))
|
||||||
|
(let
|
||||||
|
((p1 (clos-slot-value ln "p1"))
|
||||||
|
(p2 (clos-slot-value ln "p2"))
|
||||||
|
(n (clos-slot-value pl "normal")))
|
||||||
|
(let
|
||||||
|
((dx (- (geo-point-x p2) (geo-point-x p1)))
|
||||||
|
(dy (- (geo-point-y p2) (geo-point-y p1)))
|
||||||
|
(nx (first n))
|
||||||
|
(ny (first (rest n))))
|
||||||
|
(let
|
||||||
|
((dot (+ (* dx nx) (* dy ny))))
|
||||||
|
(if (= dot 0) "parallel" "point")))))))
|
||||||
|
|
||||||
|
;; ── tests ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define passed 0)
|
||||||
|
(define failed 0)
|
||||||
|
(define failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
check
|
||||||
|
(fn
|
||||||
|
(label got expected)
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list
|
||||||
|
(str
|
||||||
|
"FAIL ["
|
||||||
|
label
|
||||||
|
"]: got="
|
||||||
|
(inspect got)
|
||||||
|
" expected="
|
||||||
|
(inspect expected)))))))))
|
||||||
|
|
||||||
|
;; describe
|
||||||
|
(check
|
||||||
|
"describe point"
|
||||||
|
(clos-call-generic
|
||||||
|
"geo-describe"
|
||||||
|
(list (geo-make-point 3 4)))
|
||||||
|
"P(3,4)")
|
||||||
|
(check
|
||||||
|
"describe line"
|
||||||
|
(clos-call-generic
|
||||||
|
"geo-describe"
|
||||||
|
(list
|
||||||
|
(geo-make-line
|
||||||
|
(geo-make-point 0 0)
|
||||||
|
(geo-make-point 1 1))))
|
||||||
|
"L[P(0,0)-P(1,1)]")
|
||||||
|
(check
|
||||||
|
"describe plane"
|
||||||
|
(clos-call-generic
|
||||||
|
"geo-describe"
|
||||||
|
(list (geo-make-plane 0 1 5)))
|
||||||
|
"Plane(d=5)")
|
||||||
|
|
||||||
|
;; intersect point×point
|
||||||
|
(check
|
||||||
|
"P∩P same"
|
||||||
|
(clos-call-generic
|
||||||
|
"intersect"
|
||||||
|
(list
|
||||||
|
(geo-make-point 2 3)
|
||||||
|
(geo-make-point 2 3)))
|
||||||
|
"point")
|
||||||
|
(check
|
||||||
|
"P∩P diff"
|
||||||
|
(clos-call-generic
|
||||||
|
"intersect"
|
||||||
|
(list
|
||||||
|
(geo-make-point 1 2)
|
||||||
|
(geo-make-point 3 4)))
|
||||||
|
"empty")
|
||||||
|
|
||||||
|
;; intersect point×line
|
||||||
|
(let
|
||||||
|
((origin (geo-make-point 0 0))
|
||||||
|
(p10 (geo-make-point 10 0))
|
||||||
|
(p55 (geo-make-point 5 5))
|
||||||
|
(l-x
|
||||||
|
(geo-make-line
|
||||||
|
(geo-make-point 0 0)
|
||||||
|
(geo-make-point 10 0))))
|
||||||
|
(begin
|
||||||
|
(check
|
||||||
|
"P∩L on line"
|
||||||
|
(clos-call-generic "intersect" (list p10 l-x))
|
||||||
|
"point")
|
||||||
|
(check
|
||||||
|
"P∩L on x-axis"
|
||||||
|
(clos-call-generic "intersect" (list origin l-x))
|
||||||
|
"point")
|
||||||
|
(check
|
||||||
|
"P∩L off line"
|
||||||
|
(clos-call-generic "intersect" (list p55 l-x))
|
||||||
|
"empty")))
|
||||||
|
|
||||||
|
;; intersect line×line
|
||||||
|
(let
|
||||||
|
((horiz (geo-make-line (geo-make-point 0 0) (geo-make-point 10 0)))
|
||||||
|
(vert
|
||||||
|
(geo-make-line
|
||||||
|
(geo-make-point 5 -5)
|
||||||
|
(geo-make-point 5 5)))
|
||||||
|
(horiz2
|
||||||
|
(geo-make-line
|
||||||
|
(geo-make-point 0 3)
|
||||||
|
(geo-make-point 10 3))))
|
||||||
|
(begin
|
||||||
|
(check
|
||||||
|
"L∩L crossing"
|
||||||
|
(clos-call-generic "intersect" (list horiz vert))
|
||||||
|
"point")
|
||||||
|
(check
|
||||||
|
"L∩L parallel"
|
||||||
|
(clos-call-generic "intersect" (list horiz horiz2))
|
||||||
|
"parallel")))
|
||||||
|
|
||||||
|
;; intersect line×plane
|
||||||
|
(let
|
||||||
|
((diag (geo-make-line (geo-make-point 0 0) (geo-make-point 1 1)))
|
||||||
|
(vert-plane (geo-make-plane 1 0 5))
|
||||||
|
(diag-plane (geo-make-plane -1 1 0)))
|
||||||
|
(begin
|
||||||
|
(check
|
||||||
|
"L∩Plane cross"
|
||||||
|
(clos-call-generic "intersect" (list diag vert-plane))
|
||||||
|
"point")
|
||||||
|
(check
|
||||||
|
"L∩Plane parallel"
|
||||||
|
(clos-call-generic "intersect" (list diag diag-plane))
|
||||||
|
"parallel")))
|
||||||
|
|
||||||
|
;; ── summary ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define geo-passed passed)
|
||||||
|
(define geo-failed failed)
|
||||||
|
(define geo-failures failures)
|
||||||
196
lib/common-lisp/tests/programs/interactive-debugger.sx
Normal file
196
lib/common-lisp/tests/programs/interactive-debugger.sx
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
;; interactive-debugger.sx — Condition debugger using *debugger-hook*
|
||||||
|
;;
|
||||||
|
;; Demonstrates the classic CL debugger pattern:
|
||||||
|
;; - *debugger-hook* is invoked when an unhandled error reaches the top level
|
||||||
|
;; - The hook receives the condition and a reference to itself
|
||||||
|
;; - It can offer restarts interactively (here simulated with a policy fn)
|
||||||
|
;;
|
||||||
|
;; In real CL the debugger reads from the terminal. Here we simulate
|
||||||
|
;; the "user input" via a policy function passed in at call time.
|
||||||
|
;;
|
||||||
|
;; Depends on: lib/common-lisp/runtime.sx already loaded.
|
||||||
|
|
||||||
|
;; ── *debugger-hook* global ────────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; CL: when error is unhandled, invoke *debugger-hook* with (condition hook).
|
||||||
|
;; A nil hook means use the system default (which we simulate as re-raise).
|
||||||
|
|
||||||
|
(define cl-debugger-hook nil)
|
||||||
|
|
||||||
|
;; ── invoke-debugger ────────────────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; Called when cl-error finds no handler. Tries cl-debugger-hook first;
|
||||||
|
;; falls back to a simple error report.
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-invoke-debugger
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(if
|
||||||
|
(nil? cl-debugger-hook)
|
||||||
|
(error (str "Debugger: " (cl-condition-message c)))
|
||||||
|
(begin
|
||||||
|
(let
|
||||||
|
((hook cl-debugger-hook))
|
||||||
|
(set! cl-debugger-hook nil)
|
||||||
|
(let
|
||||||
|
((result (hook c hook)))
|
||||||
|
(set! cl-debugger-hook hook)
|
||||||
|
result))))))
|
||||||
|
|
||||||
|
;; ── cl-error/debugger — error that routes through invoke-debugger ─────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-error-with-debugger
|
||||||
|
(fn
|
||||||
|
(c &rest args)
|
||||||
|
(let
|
||||||
|
((obj (cond ((cl-condition? c) c) ((string? c) (cl-make-condition "simple-error" "format-control" c "format-arguments" args)) (:else (cl-make-condition "simple-error" "format-control" (str c))))))
|
||||||
|
(cl-signal-obj obj cl-handler-stack)
|
||||||
|
(cl-invoke-debugger obj))))
|
||||||
|
|
||||||
|
;; ── simulated debugger session ────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; A debugger hook takes (condition hook) and "reads" user commands.
|
||||||
|
;; We simulate this with a policy function: (fn (c restarts) restart-name)
|
||||||
|
;; that picks a restart given the condition and available restarts.
|
||||||
|
|
||||||
|
(define
|
||||||
|
make-policy-debugger
|
||||||
|
(fn
|
||||||
|
(policy)
|
||||||
|
(fn
|
||||||
|
(c hook)
|
||||||
|
(let
|
||||||
|
((available (cl-compute-restarts)))
|
||||||
|
(let
|
||||||
|
((choice (policy c available)))
|
||||||
|
(if
|
||||||
|
(and choice (not (nil? (cl-find-restart choice))))
|
||||||
|
(cl-invoke-restart choice)
|
||||||
|
(error
|
||||||
|
(str
|
||||||
|
"Debugger: no restart chosen for: "
|
||||||
|
(cl-condition-message c)))))))))
|
||||||
|
|
||||||
|
;; ── tests ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define passed 0)
|
||||||
|
(define failed 0)
|
||||||
|
(define failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
check
|
||||||
|
(fn
|
||||||
|
(label got expected)
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list
|
||||||
|
(str
|
||||||
|
"FAIL ["
|
||||||
|
label
|
||||||
|
"]: got="
|
||||||
|
(inspect got)
|
||||||
|
" expected="
|
||||||
|
(inspect expected)))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
reset-stacks!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(set! cl-handler-stack (list))
|
||||||
|
(set! cl-restart-stack (list))
|
||||||
|
(set! cl-debugger-hook nil)))
|
||||||
|
|
||||||
|
;; Test 1: debugger hook receives condition
|
||||||
|
(reset-stacks!)
|
||||||
|
(let
|
||||||
|
((received-msg ""))
|
||||||
|
(begin
|
||||||
|
(set!
|
||||||
|
cl-debugger-hook
|
||||||
|
(fn (c hook) (set! received-msg (cl-condition-message c)) nil))
|
||||||
|
(cl-restart-case
|
||||||
|
(fn () (cl-error-with-debugger "something broke"))
|
||||||
|
(list "abort" (list) (fn () nil)))
|
||||||
|
(check "debugger hook receives condition" received-msg "something broke")))
|
||||||
|
|
||||||
|
;; Test 2: policy-driven restart selection (use-zero)
|
||||||
|
(reset-stacks!)
|
||||||
|
(let
|
||||||
|
((result (begin (set! cl-debugger-hook (make-policy-debugger (fn (c restarts) "use-zero"))) (cl-restart-case (fn () (cl-error-with-debugger (cl-make-condition "division-by-zero")) 999) (list "use-zero" (list) (fn () 0))))))
|
||||||
|
(check "policy debugger: use-zero restart" result 0))
|
||||||
|
|
||||||
|
;; Test 3: policy selects abort
|
||||||
|
(reset-stacks!)
|
||||||
|
(let
|
||||||
|
((result (begin (set! cl-debugger-hook (make-policy-debugger (fn (c restarts) "abort"))) (cl-restart-case (fn () (cl-error-with-debugger "aborting error") 999) (list "abort" (list) (fn () "aborted"))))))
|
||||||
|
(check "policy debugger: abort restart" result "aborted"))
|
||||||
|
|
||||||
|
;; Test 4: compute-restarts inside debugger hook
|
||||||
|
(reset-stacks!)
|
||||||
|
(let
|
||||||
|
((seen-restarts (list)))
|
||||||
|
(begin
|
||||||
|
(set!
|
||||||
|
cl-debugger-hook
|
||||||
|
(fn
|
||||||
|
(c hook)
|
||||||
|
(set! seen-restarts (cl-compute-restarts))
|
||||||
|
(cl-invoke-restart "continue")))
|
||||||
|
(cl-restart-case
|
||||||
|
(fn () (cl-error-with-debugger "test") 42)
|
||||||
|
(list "continue" (list) (fn () "ok"))
|
||||||
|
(list "abort" (list) (fn () "no")))
|
||||||
|
(check
|
||||||
|
"debugger: compute-restarts visible"
|
||||||
|
(= (len seen-restarts) 2)
|
||||||
|
true)))
|
||||||
|
|
||||||
|
;; Test 5: hook not invoked when handler catches first
|
||||||
|
(reset-stacks!)
|
||||||
|
(let
|
||||||
|
((hook-called false)
|
||||||
|
(result
|
||||||
|
(begin
|
||||||
|
(set! cl-debugger-hook (fn (c hook) (set! hook-called true) nil))
|
||||||
|
(cl-handler-case
|
||||||
|
(fn () (cl-error-with-debugger "handled"))
|
||||||
|
(list "error" (fn (c) "handler-won"))))))
|
||||||
|
(check "handler wins; hook not called" hook-called false)
|
||||||
|
(check "handler result returned" result "handler-won"))
|
||||||
|
|
||||||
|
;; Test 6: debugger-hook nil after re-raise guard
|
||||||
|
(reset-stacks!)
|
||||||
|
(let
|
||||||
|
((hook-calls 0))
|
||||||
|
(begin
|
||||||
|
(set!
|
||||||
|
cl-debugger-hook
|
||||||
|
(fn
|
||||||
|
(c hook)
|
||||||
|
(set! hook-calls (+ hook-calls 1))
|
||||||
|
(if
|
||||||
|
(> hook-calls 1)
|
||||||
|
(error "infinite loop guard")
|
||||||
|
(cl-invoke-restart "escape"))))
|
||||||
|
(cl-restart-case
|
||||||
|
(fn () (cl-error-with-debugger "once"))
|
||||||
|
(list "escape" (list) (fn () nil)))
|
||||||
|
(check
|
||||||
|
"hook called exactly once (no infinite recursion)"
|
||||||
|
hook-calls
|
||||||
|
1)))
|
||||||
|
|
||||||
|
;; ── summary ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define debugger-passed passed)
|
||||||
|
(define debugger-failed failed)
|
||||||
|
(define debugger-failures failures)
|
||||||
228
lib/common-lisp/tests/programs/mop-trace.sx
Normal file
228
lib/common-lisp/tests/programs/mop-trace.sx
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
;; mop-trace.sx — :before/:after method tracing with CLOS
|
||||||
|
;;
|
||||||
|
;; Classic CLOS pattern: instrument generic functions with :before and :after
|
||||||
|
;; qualifiers to print call/return traces without modifying the primary method.
|
||||||
|
;;
|
||||||
|
;; Depends on: lib/common-lisp/runtime.sx, lib/common-lisp/clos.sx
|
||||||
|
|
||||||
|
;; ── trace log (mutable accumulator) ───────────────────────────────────────
|
||||||
|
|
||||||
|
(define trace-log (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
trace-push
|
||||||
|
(fn (msg) (set! trace-log (append trace-log (list msg)))))
|
||||||
|
|
||||||
|
(define trace-clear (fn () (set! trace-log (list))))
|
||||||
|
|
||||||
|
;; ── domain classes ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(clos-defclass "shape" (list "t") (list {:initform "white" :initarg ":color" :reader nil :writer nil :accessor nil :name "color"}))
|
||||||
|
|
||||||
|
(clos-defclass "circle" (list "shape") (list {:initform 1 :initarg ":radius" :reader nil :writer nil :accessor nil :name "radius"}))
|
||||||
|
|
||||||
|
(clos-defclass "rect" (list "shape") (list {:initform 1 :initarg ":width" :reader nil :writer nil :accessor nil :name "width"} {:initform 1 :initarg ":height" :reader nil :writer nil :accessor nil :name "height"}))
|
||||||
|
|
||||||
|
;; ── generic function: area ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(clos-defgeneric "area" {})
|
||||||
|
|
||||||
|
;; primary methods
|
||||||
|
(clos-defmethod
|
||||||
|
"area"
|
||||||
|
(list)
|
||||||
|
(list "circle")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((c (first args)))
|
||||||
|
(let ((r (clos-slot-value c "radius"))) (* r r)))))
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"area"
|
||||||
|
(list)
|
||||||
|
(list "rect")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((r (first args)))
|
||||||
|
(* (clos-slot-value r "width") (clos-slot-value r "height")))))
|
||||||
|
|
||||||
|
;; :before tracing
|
||||||
|
(clos-defmethod
|
||||||
|
"area"
|
||||||
|
(list "before")
|
||||||
|
(list "shape")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(trace-push (str "BEFORE area(" (clos-class-of (first args)) ")"))))
|
||||||
|
|
||||||
|
;; :after tracing
|
||||||
|
(clos-defmethod
|
||||||
|
"area"
|
||||||
|
(list "after")
|
||||||
|
(list "shape")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(trace-push (str "AFTER area(" (clos-class-of (first args)) ")"))))
|
||||||
|
|
||||||
|
;; ── generic function: describe-shape ──────────────────────────────────────
|
||||||
|
|
||||||
|
(clos-defgeneric "describe-shape" {})
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"describe-shape"
|
||||||
|
(list)
|
||||||
|
(list "shape")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((s (first args)))
|
||||||
|
(str "shape[" (clos-slot-value s "color") "]"))))
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"describe-shape"
|
||||||
|
(list)
|
||||||
|
(list "circle")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((c (first args)))
|
||||||
|
(str
|
||||||
|
"circle[r="
|
||||||
|
(clos-slot-value c "radius")
|
||||||
|
" "
|
||||||
|
(clos-call-next-method next-fn)
|
||||||
|
"]"))))
|
||||||
|
|
||||||
|
(clos-defmethod
|
||||||
|
"describe-shape"
|
||||||
|
(list)
|
||||||
|
(list "rect")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(let
|
||||||
|
((r (first args)))
|
||||||
|
(str
|
||||||
|
"rect["
|
||||||
|
(clos-slot-value r "width")
|
||||||
|
"x"
|
||||||
|
(clos-slot-value r "height")
|
||||||
|
" "
|
||||||
|
(clos-call-next-method next-fn)
|
||||||
|
"]"))))
|
||||||
|
|
||||||
|
;; :before on base shape (fires for all subclasses too)
|
||||||
|
(clos-defmethod
|
||||||
|
"describe-shape"
|
||||||
|
(list "before")
|
||||||
|
(list "shape")
|
||||||
|
(fn
|
||||||
|
(args next-fn)
|
||||||
|
(trace-push
|
||||||
|
(str "BEFORE describe-shape(" (clos-class-of (first args)) ")"))))
|
||||||
|
|
||||||
|
;; ── tests ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define passed 0)
|
||||||
|
(define failed 0)
|
||||||
|
(define failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
check
|
||||||
|
(fn
|
||||||
|
(label got expected)
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list
|
||||||
|
(str
|
||||||
|
"FAIL ["
|
||||||
|
label
|
||||||
|
"]: got="
|
||||||
|
(inspect got)
|
||||||
|
" expected="
|
||||||
|
(inspect expected)))))))))
|
||||||
|
|
||||||
|
;; ── area tests ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; circle area = r*r (no pi — integer arithmetic for predictability)
|
||||||
|
(let
|
||||||
|
((c (clos-make-instance "circle" ":radius" 5 ":color" "red")))
|
||||||
|
(do
|
||||||
|
(trace-clear)
|
||||||
|
(check "circle area" (clos-call-generic "area" (list c)) 25)
|
||||||
|
(check
|
||||||
|
":before fired for circle"
|
||||||
|
(= (first trace-log) "BEFORE area(circle)")
|
||||||
|
true)
|
||||||
|
(check
|
||||||
|
":after fired for circle"
|
||||||
|
(= (first (rest trace-log)) "AFTER area(circle)")
|
||||||
|
true)
|
||||||
|
(check "trace length 2" (len trace-log) 2)))
|
||||||
|
|
||||||
|
;; rect area = w*h
|
||||||
|
(let
|
||||||
|
((r (clos-make-instance "rect" ":width" 4 ":height" 6 ":color" "blue")))
|
||||||
|
(do
|
||||||
|
(trace-clear)
|
||||||
|
(check "rect area" (clos-call-generic "area" (list r)) 24)
|
||||||
|
(check
|
||||||
|
":before fired for rect"
|
||||||
|
(= (first trace-log) "BEFORE area(rect)")
|
||||||
|
true)
|
||||||
|
(check
|
||||||
|
":after fired for rect"
|
||||||
|
(= (first (rest trace-log)) "AFTER area(rect)")
|
||||||
|
true)
|
||||||
|
(check "trace length 2 (rect)" (len trace-log) 2)))
|
||||||
|
|
||||||
|
;; ── describe-shape tests ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(let
|
||||||
|
((c (clos-make-instance "circle" ":radius" 3 ":color" "green")))
|
||||||
|
(do
|
||||||
|
(trace-clear)
|
||||||
|
(check
|
||||||
|
"circle describe"
|
||||||
|
(clos-call-generic "describe-shape" (list c))
|
||||||
|
"circle[r=3 shape[green]]")
|
||||||
|
(check
|
||||||
|
":before fired for describe circle"
|
||||||
|
(= (first trace-log) "BEFORE describe-shape(circle)")
|
||||||
|
true)))
|
||||||
|
|
||||||
|
(let
|
||||||
|
((r (clos-make-instance "rect" ":width" 2 ":height" 7 ":color" "black")))
|
||||||
|
(do
|
||||||
|
(trace-clear)
|
||||||
|
(check
|
||||||
|
"rect describe"
|
||||||
|
(clos-call-generic "describe-shape" (list r))
|
||||||
|
"rect[2x7 shape[black]]")
|
||||||
|
(check
|
||||||
|
":before fired for describe rect"
|
||||||
|
(= (first trace-log) "BEFORE describe-shape(rect)")
|
||||||
|
true)))
|
||||||
|
|
||||||
|
;; ── call-next-method: circle -> shape ─────────────────────────────────────
|
||||||
|
|
||||||
|
(let
|
||||||
|
((c (clos-make-instance "circle" ":radius" 1 ":color" "purple")))
|
||||||
|
(check
|
||||||
|
"call-next-method result in describe"
|
||||||
|
(clos-call-generic "describe-shape" (list c))
|
||||||
|
"circle[r=1 shape[purple]]"))
|
||||||
|
|
||||||
|
;; ── summary ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define mop-passed passed)
|
||||||
|
(define mop-failed failed)
|
||||||
|
(define mop-failures failures)
|
||||||
163
lib/common-lisp/tests/programs/parse-recover.sx
Normal file
163
lib/common-lisp/tests/programs/parse-recover.sx
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
;; parse-recover.sx — Parser with skipped-token restart
|
||||||
|
;;
|
||||||
|
;; Classic CL pattern: a simple token parser that signals a condition
|
||||||
|
;; when it encounters an unexpected token. The :skip-token restart
|
||||||
|
;; allows the parser to continue past the offending token.
|
||||||
|
;;
|
||||||
|
;; Depends on: lib/common-lisp/runtime.sx already loaded.
|
||||||
|
|
||||||
|
;; ── condition type ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-define-condition "parse-error" (list "error") (list "token" "position"))
|
||||||
|
|
||||||
|
;; ── simple token parser ────────────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; parse-numbers: given a list of tokens (strings), parse integers.
|
||||||
|
;; Non-integer tokens signal parse-error with two restarts:
|
||||||
|
;; skip-token — skip the bad token and continue
|
||||||
|
;; use-zero — use 0 in place of the bad token
|
||||||
|
|
||||||
|
(define
|
||||||
|
parse-numbers
|
||||||
|
(fn
|
||||||
|
(tokens)
|
||||||
|
(define result (list))
|
||||||
|
(define
|
||||||
|
process
|
||||||
|
(fn
|
||||||
|
(toks)
|
||||||
|
(if
|
||||||
|
(empty? toks)
|
||||||
|
result
|
||||||
|
(let
|
||||||
|
((tok (first toks)) (rest-toks (rest toks)))
|
||||||
|
(let
|
||||||
|
((n (string->number tok 10)))
|
||||||
|
(if
|
||||||
|
n
|
||||||
|
(begin
|
||||||
|
(set! result (append result (list n)))
|
||||||
|
(process rest-toks))
|
||||||
|
(cl-restart-case
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(cl-signal
|
||||||
|
(cl-make-condition
|
||||||
|
"parse-error"
|
||||||
|
"token"
|
||||||
|
tok
|
||||||
|
"position"
|
||||||
|
(len result)))
|
||||||
|
(set! result (append result (list 0)))
|
||||||
|
(process rest-toks))
|
||||||
|
(list "skip-token" (list) (fn () (process rest-toks)))
|
||||||
|
(list
|
||||||
|
"use-zero"
|
||||||
|
(list)
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(begin
|
||||||
|
(set! result (append result (list 0)))
|
||||||
|
(process rest-toks)))))))))))
|
||||||
|
(process tokens)
|
||||||
|
result))
|
||||||
|
|
||||||
|
;; ── tests ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define passed 0)
|
||||||
|
(define failed 0)
|
||||||
|
(define failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
check
|
||||||
|
(fn
|
||||||
|
(label got expected)
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list
|
||||||
|
(str
|
||||||
|
"FAIL ["
|
||||||
|
label
|
||||||
|
"]: got="
|
||||||
|
(inspect got)
|
||||||
|
" expected="
|
||||||
|
(inspect expected)))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
reset-stacks!
|
||||||
|
(fn () (set! cl-handler-stack (list)) (set! cl-restart-stack (list))))
|
||||||
|
|
||||||
|
;; All valid tokens
|
||||||
|
(reset-stacks!)
|
||||||
|
(check
|
||||||
|
"all valid: 1 2 3"
|
||||||
|
(cl-handler-bind
|
||||||
|
(list (list "parse-error" (fn (c) (cl-invoke-restart "skip-token"))))
|
||||||
|
(fn () (parse-numbers (list "1" "2" "3"))))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
;; Skip bad token
|
||||||
|
(reset-stacks!)
|
||||||
|
(check
|
||||||
|
"skip bad token: 1 x 3 -> (1 3)"
|
||||||
|
(cl-handler-bind
|
||||||
|
(list (list "parse-error" (fn (c) (cl-invoke-restart "skip-token"))))
|
||||||
|
(fn () (parse-numbers (list "1" "x" "3"))))
|
||||||
|
(list 1 3))
|
||||||
|
|
||||||
|
;; Use zero for bad token
|
||||||
|
(reset-stacks!)
|
||||||
|
(check
|
||||||
|
"use-zero for bad: 1 x 3 -> (1 0 3)"
|
||||||
|
(cl-handler-bind
|
||||||
|
(list (list "parse-error" (fn (c) (cl-invoke-restart "use-zero"))))
|
||||||
|
(fn () (parse-numbers (list "1" "x" "3"))))
|
||||||
|
(list 1 0 3))
|
||||||
|
|
||||||
|
;; Multiple bad tokens, all skipped
|
||||||
|
(reset-stacks!)
|
||||||
|
(check
|
||||||
|
"skip multiple bad: a 2 b 4 -> (2 4)"
|
||||||
|
(cl-handler-bind
|
||||||
|
(list (list "parse-error" (fn (c) (cl-invoke-restart "skip-token"))))
|
||||||
|
(fn () (parse-numbers (list "a" "2" "b" "4"))))
|
||||||
|
(list 2 4))
|
||||||
|
|
||||||
|
;; handler-case: abort on first bad token
|
||||||
|
(reset-stacks!)
|
||||||
|
(check
|
||||||
|
"handler-case: abort on first bad"
|
||||||
|
(cl-handler-case
|
||||||
|
(fn () (parse-numbers (list "1" "bad" "3")))
|
||||||
|
(list
|
||||||
|
"parse-error"
|
||||||
|
(fn
|
||||||
|
(c)
|
||||||
|
(str
|
||||||
|
"parse error at position "
|
||||||
|
(cl-condition-slot c "position")
|
||||||
|
": "
|
||||||
|
(cl-condition-slot c "token")))))
|
||||||
|
"parse error at position 1: bad")
|
||||||
|
|
||||||
|
;; Verify condition type hierarchy
|
||||||
|
(reset-stacks!)
|
||||||
|
(check
|
||||||
|
"parse-error isa error"
|
||||||
|
(cl-condition-of-type?
|
||||||
|
(cl-make-condition "parse-error" "token" "x" "position" 0)
|
||||||
|
"error")
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── summary ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define parse-passed passed)
|
||||||
|
(define parse-failed failed)
|
||||||
|
(define parse-failures failures)
|
||||||
141
lib/common-lisp/tests/programs/restart-demo.sx
Normal file
141
lib/common-lisp/tests/programs/restart-demo.sx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
;; restart-demo.sx — Classic CL condition system demo
|
||||||
|
;;
|
||||||
|
;; Demonstrates resumable exceptions via restarts.
|
||||||
|
;; The `safe-divide` function signals a division-by-zero condition
|
||||||
|
;; and offers two restarts:
|
||||||
|
;; :use-zero — return 0 as the result
|
||||||
|
;; :retry — call safe-divide again with a corrected divisor
|
||||||
|
;;
|
||||||
|
;; Depends on: lib/common-lisp/runtime.sx already loaded.
|
||||||
|
|
||||||
|
;; ── safe-divide ────────────────────────────────────────────────────────────
|
||||||
|
;;
|
||||||
|
;; Divides numerator by denominator.
|
||||||
|
;; When denominator is 0, signals division-by-zero with two restarts.
|
||||||
|
|
||||||
|
(define
|
||||||
|
safe-divide
|
||||||
|
(fn
|
||||||
|
(n d)
|
||||||
|
(if
|
||||||
|
(= d 0)
|
||||||
|
(cl-restart-case
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(cl-signal
|
||||||
|
(cl-make-condition
|
||||||
|
"division-by-zero"
|
||||||
|
"operation"
|
||||||
|
"/"
|
||||||
|
"operands"
|
||||||
|
(list n d)))
|
||||||
|
(error "division by zero — no restart invoked"))
|
||||||
|
(list "use-zero" (list) (fn () 0))
|
||||||
|
(list "retry" (list "d") (fn (d2) (safe-divide n d2))))
|
||||||
|
(/ n d))))
|
||||||
|
|
||||||
|
;; ── tests ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define passed 0)
|
||||||
|
(define failed 0)
|
||||||
|
(define failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
check
|
||||||
|
(fn
|
||||||
|
(label got expected)
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list
|
||||||
|
(str
|
||||||
|
"FAIL ["
|
||||||
|
label
|
||||||
|
"]: got="
|
||||||
|
(inspect got)
|
||||||
|
" expected="
|
||||||
|
(inspect expected)))))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
reset-stacks!
|
||||||
|
(fn () (set! cl-handler-stack (list)) (set! cl-restart-stack (list))))
|
||||||
|
|
||||||
|
;; Normal division
|
||||||
|
(reset-stacks!)
|
||||||
|
(check "10 / 2 = 5" (safe-divide 10 2) 5)
|
||||||
|
|
||||||
|
;; Invoke use-zero restart
|
||||||
|
(reset-stacks!)
|
||||||
|
(check
|
||||||
|
"10 / 0 -> use-zero"
|
||||||
|
(cl-handler-bind
|
||||||
|
(list
|
||||||
|
(list "division-by-zero" (fn (c) (cl-invoke-restart "use-zero"))))
|
||||||
|
(fn () (safe-divide 10 0)))
|
||||||
|
0)
|
||||||
|
|
||||||
|
;; Invoke retry restart with a corrected denominator
|
||||||
|
(reset-stacks!)
|
||||||
|
(check
|
||||||
|
"10 / 0 -> retry with 2"
|
||||||
|
(cl-handler-bind
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
"division-by-zero"
|
||||||
|
(fn (c) (cl-invoke-restart "retry" 2))))
|
||||||
|
(fn () (safe-divide 10 0)))
|
||||||
|
5)
|
||||||
|
|
||||||
|
;; Nested calls: outer handles the inner divide-by-zero
|
||||||
|
(reset-stacks!)
|
||||||
|
(check
|
||||||
|
"nested: 20 / (0->4) = 5"
|
||||||
|
(cl-handler-bind
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
"division-by-zero"
|
||||||
|
(fn (c) (cl-invoke-restart "retry" 4))))
|
||||||
|
(fn () (let ((r1 (safe-divide 20 0))) r1)))
|
||||||
|
5)
|
||||||
|
|
||||||
|
;; handler-case — unwinding version
|
||||||
|
(reset-stacks!)
|
||||||
|
(check
|
||||||
|
"handler-case: catches division-by-zero"
|
||||||
|
(cl-handler-case
|
||||||
|
(fn () (safe-divide 9 0))
|
||||||
|
(list "division-by-zero" (fn (c) "caught!")))
|
||||||
|
"caught!")
|
||||||
|
|
||||||
|
;; Verify use-zero is idempotent (two uses)
|
||||||
|
(reset-stacks!)
|
||||||
|
(check
|
||||||
|
"two use-zero invocations"
|
||||||
|
(cl-handler-bind
|
||||||
|
(list
|
||||||
|
(list "division-by-zero" (fn (c) (cl-invoke-restart "use-zero"))))
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(+
|
||||||
|
(safe-divide 10 0)
|
||||||
|
(safe-divide 3 0))))
|
||||||
|
0)
|
||||||
|
|
||||||
|
;; No restart needed for normal division
|
||||||
|
(reset-stacks!)
|
||||||
|
(check
|
||||||
|
"no restart needed for 8/4"
|
||||||
|
(safe-divide 8 4)
|
||||||
|
2)
|
||||||
|
|
||||||
|
;; ── summary ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define demo-passed passed)
|
||||||
|
(define demo-failed failed)
|
||||||
|
(define demo-failures failures)
|
||||||
180
lib/common-lisp/tests/read.sx
Normal file
180
lib/common-lisp/tests/read.sx
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
;; Common Lisp tokenizer tests
|
||||||
|
|
||||||
|
(define cl-test-pass 0)
|
||||||
|
(define cl-test-fail 0)
|
||||||
|
(define cl-test-fails (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(if
|
||||||
|
(= actual expected)
|
||||||
|
(set! cl-test-pass (+ cl-test-pass 1))
|
||||||
|
(do
|
||||||
|
(set! cl-test-fail (+ cl-test-fail 1))
|
||||||
|
(append! cl-test-fails {:name name :expected expected :actual actual})))))
|
||||||
|
|
||||||
|
;; Helpers: extract types and values from token stream (drops eof)
|
||||||
|
(define
|
||||||
|
cl-tok-types
|
||||||
|
(fn
|
||||||
|
(src)
|
||||||
|
(map
|
||||||
|
(fn (t) (get t "type"))
|
||||||
|
(filter (fn (t) (not (= (get t "type") "eof"))) (cl-tokenize src)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-tok-values
|
||||||
|
(fn
|
||||||
|
(src)
|
||||||
|
(map
|
||||||
|
(fn (t) (get t "value"))
|
||||||
|
(filter (fn (t) (not (= (get t "type") "eof"))) (cl-tokenize src)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
cl-tok-first
|
||||||
|
(fn (src) (nth (cl-tokenize src) 0)))
|
||||||
|
|
||||||
|
;; ── symbols ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "symbol: bare lowercase" (cl-tok-values "foo") (list "FOO"))
|
||||||
|
(cl-test "symbol: uppercase" (cl-tok-values "BAR") (list "BAR"))
|
||||||
|
(cl-test "symbol: mixed case folded" (cl-tok-values "FooBar") (list "FOOBAR"))
|
||||||
|
(cl-test "symbol: with hyphen" (cl-tok-values "foo-bar") (list "FOO-BAR"))
|
||||||
|
(cl-test "symbol: with star" (cl-tok-values "*special*") (list "*SPECIAL*"))
|
||||||
|
(cl-test "symbol: with question" (cl-tok-values "null?") (list "NULL?"))
|
||||||
|
(cl-test "symbol: with exclamation" (cl-tok-values "set!") (list "SET!"))
|
||||||
|
(cl-test "symbol: plus sign alone" (cl-tok-values "+") (list "+"))
|
||||||
|
(cl-test "symbol: minus sign alone" (cl-tok-values "-") (list "-"))
|
||||||
|
(cl-test "symbol: type is symbol" (cl-tok-types "foo") (list "symbol"))
|
||||||
|
|
||||||
|
;; ── package-qualified symbols ─────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "symbol: pkg:sym external" (cl-tok-values "cl:car") (list "CL:CAR"))
|
||||||
|
(cl-test "symbol: pkg::sym internal" (cl-tok-values "pkg::foo") (list "PKG::FOO"))
|
||||||
|
(cl-test "symbol: cl:car type" (cl-tok-types "cl:car") (list "symbol"))
|
||||||
|
|
||||||
|
;; ── keywords ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "keyword: basic" (cl-tok-values ":foo") (list "FOO"))
|
||||||
|
(cl-test "keyword: type" (cl-tok-types ":foo") (list "keyword"))
|
||||||
|
(cl-test "keyword: upcase" (cl-tok-values ":hello-world") (list "HELLO-WORLD"))
|
||||||
|
(cl-test "keyword: multiple" (cl-tok-types ":a :b :c") (list "keyword" "keyword" "keyword"))
|
||||||
|
|
||||||
|
;; ── integers ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "integer: zero" (cl-tok-values "0") (list "0"))
|
||||||
|
(cl-test "integer: positive" (cl-tok-values "42") (list "42"))
|
||||||
|
(cl-test "integer: negative" (cl-tok-values "-5") (list "-5"))
|
||||||
|
(cl-test "integer: positive-sign" (cl-tok-values "+3") (list "+3"))
|
||||||
|
(cl-test "integer: type" (cl-tok-types "42") (list "integer"))
|
||||||
|
(cl-test "integer: multi-digit" (cl-tok-values "12345678") (list "12345678"))
|
||||||
|
|
||||||
|
;; ── hex, binary, octal ───────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "hex: lowercase x" (cl-tok-values "#xFF") (list "#xFF"))
|
||||||
|
(cl-test "hex: uppercase X" (cl-tok-values "#XFF") (list "#XFF"))
|
||||||
|
(cl-test "hex: type" (cl-tok-types "#xFF") (list "integer"))
|
||||||
|
(cl-test "hex: zero" (cl-tok-values "#x0") (list "#x0"))
|
||||||
|
(cl-test "binary: #b" (cl-tok-values "#b1010") (list "#b1010"))
|
||||||
|
(cl-test "binary: type" (cl-tok-types "#b1010") (list "integer"))
|
||||||
|
(cl-test "octal: #o" (cl-tok-values "#o17") (list "#o17"))
|
||||||
|
(cl-test "octal: type" (cl-tok-types "#o17") (list "integer"))
|
||||||
|
|
||||||
|
;; ── floats ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "float: basic" (cl-tok-values "3.14") (list "3.14"))
|
||||||
|
(cl-test "float: type" (cl-tok-types "3.14") (list "float"))
|
||||||
|
(cl-test "float: negative" (cl-tok-values "-2.5") (list "-2.5"))
|
||||||
|
(cl-test "float: exponent" (cl-tok-values "1.0e10") (list "1.0e10"))
|
||||||
|
(cl-test "float: neg exponent" (cl-tok-values "1.5e-3") (list "1.5e-3"))
|
||||||
|
(cl-test "float: leading dot" (cl-tok-values ".5") (list "0.5"))
|
||||||
|
(cl-test "float: exp only" (cl-tok-values "1e5") (list "1e5"))
|
||||||
|
|
||||||
|
;; ── ratios ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "ratio: 1/3" (cl-tok-values "1/3") (list "1/3"))
|
||||||
|
(cl-test "ratio: type" (cl-tok-types "1/3") (list "ratio"))
|
||||||
|
(cl-test "ratio: 22/7" (cl-tok-values "22/7") (list "22/7"))
|
||||||
|
(cl-test "ratio: negative" (cl-tok-values "-1/2") (list "-1/2"))
|
||||||
|
|
||||||
|
;; ── strings ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "string: empty" (cl-tok-values "\"\"") (list ""))
|
||||||
|
(cl-test "string: basic" (cl-tok-values "\"hello\"") (list "hello"))
|
||||||
|
(cl-test "string: type" (cl-tok-types "\"hello\"") (list "string"))
|
||||||
|
(cl-test "string: with space" (cl-tok-values "\"hello world\"") (list "hello world"))
|
||||||
|
(cl-test "string: escaped quote" (cl-tok-values "\"say \\\"hi\\\"\"") (list "say \"hi\""))
|
||||||
|
(cl-test "string: escaped backslash" (cl-tok-values "\"a\\\\b\"") (list "a\\b"))
|
||||||
|
(cl-test "string: newline escape" (cl-tok-values "\"a\\nb\"") (list "a\nb"))
|
||||||
|
(cl-test "string: tab escape" (cl-tok-values "\"a\\tb\"") (list "a\tb"))
|
||||||
|
|
||||||
|
;; ── characters ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "char: lowercase a" (cl-tok-values "#\\a") (list "a"))
|
||||||
|
(cl-test "char: uppercase A" (cl-tok-values "#\\A") (list "A"))
|
||||||
|
(cl-test "char: digit" (cl-tok-values "#\\1") (list "1"))
|
||||||
|
(cl-test "char: type" (cl-tok-types "#\\a") (list "char"))
|
||||||
|
(cl-test "char: Space" (cl-tok-values "#\\Space") (list " "))
|
||||||
|
(cl-test "char: Newline" (cl-tok-values "#\\Newline") (list "\n"))
|
||||||
|
(cl-test "char: Tab" (cl-tok-values "#\\Tab") (list "\t"))
|
||||||
|
(cl-test "char: Return" (cl-tok-values "#\\Return") (list "\r"))
|
||||||
|
|
||||||
|
;; ── reader macros ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "quote: type" (cl-tok-types "'x") (list "quote" "symbol"))
|
||||||
|
(cl-test "backquote: type" (cl-tok-types "`x") (list "backquote" "symbol"))
|
||||||
|
(cl-test "comma: type" (cl-tok-types ",x") (list "comma" "symbol"))
|
||||||
|
(cl-test "comma-at: type" (cl-tok-types ",@x") (list "comma-at" "symbol"))
|
||||||
|
(cl-test "hash-quote: type" (cl-tok-types "#'foo") (list "hash-quote" "symbol"))
|
||||||
|
(cl-test "hash-paren: type" (cl-tok-types "#(1 2)") (list "hash-paren" "integer" "integer" "rparen"))
|
||||||
|
|
||||||
|
;; ── uninterned ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "uninterned: type" (cl-tok-types "#:foo") (list "uninterned"))
|
||||||
|
(cl-test "uninterned: value upcase" (cl-tok-values "#:foo") (list "FOO"))
|
||||||
|
(cl-test "uninterned: compound" (cl-tok-values "#:my-sym") (list "MY-SYM"))
|
||||||
|
|
||||||
|
;; ── parens and structure ──────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "paren: empty list" (cl-tok-types "()") (list "lparen" "rparen"))
|
||||||
|
(cl-test "paren: nested" (cl-tok-types "((a))") (list "lparen" "lparen" "symbol" "rparen" "rparen"))
|
||||||
|
(cl-test "dot: standalone" (cl-tok-types "(a . b)") (list "lparen" "symbol" "dot" "symbol" "rparen"))
|
||||||
|
|
||||||
|
;; ── comments ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test "comment: line" (cl-tok-types "; comment\nfoo") (list "symbol"))
|
||||||
|
(cl-test "comment: inline" (cl-tok-values "foo ; bar\nbaz") (list "FOO" "BAZ"))
|
||||||
|
(cl-test "block-comment: basic" (cl-tok-types "#| hello |# foo") (list "symbol"))
|
||||||
|
(cl-test "block-comment: nested" (cl-tok-types "#| a #| b |# c |# x") (list "symbol"))
|
||||||
|
|
||||||
|
;; ── combined ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"combined: defun skeleton"
|
||||||
|
(cl-tok-types "(defun foo (x) x)")
|
||||||
|
(list "lparen" "symbol" "symbol" "lparen" "symbol" "rparen" "symbol" "rparen"))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"combined: let form"
|
||||||
|
(cl-tok-types "(let ((x 1)) x)")
|
||||||
|
(list
|
||||||
|
"lparen"
|
||||||
|
"symbol"
|
||||||
|
"lparen"
|
||||||
|
"lparen"
|
||||||
|
"symbol"
|
||||||
|
"integer"
|
||||||
|
"rparen"
|
||||||
|
"rparen"
|
||||||
|
"symbol"
|
||||||
|
"rparen"))
|
||||||
|
|
||||||
|
(cl-test
|
||||||
|
"combined: whitespace skip"
|
||||||
|
(cl-tok-values " foo bar baz ")
|
||||||
|
(list "FOO" "BAR" "BAZ"))
|
||||||
|
|
||||||
|
(cl-test "eof: present" (get (nth (cl-tokenize "") 0) "type") "eof")
|
||||||
|
(cl-test "eof: at end of tokens" (get (nth (cl-tokenize "x") 1) "type") "eof")
|
||||||
207
lib/common-lisp/tests/runtime.sx
Normal file
207
lib/common-lisp/tests/runtime.sx
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
;; lib/common-lisp/tests/runtime.sx — tests for CL runtime layer
|
||||||
|
|
||||||
|
(load "lib/common-lisp/runtime.sx")
|
||||||
|
|
||||||
|
(defsuite
|
||||||
|
"cl-types"
|
||||||
|
(deftest "cl-null? nil" (assert= true (cl-null? nil)))
|
||||||
|
(deftest "cl-null? false" (assert= false (cl-null? false)))
|
||||||
|
(deftest
|
||||||
|
"cl-consp? pair"
|
||||||
|
(assert= true (cl-consp? (list 1 2))))
|
||||||
|
(deftest "cl-consp? nil" (assert= false (cl-consp? nil)))
|
||||||
|
(deftest "cl-listp? nil" (assert= true (cl-listp? nil)))
|
||||||
|
(deftest
|
||||||
|
"cl-listp? list"
|
||||||
|
(assert= true (cl-listp? (list 1 2))))
|
||||||
|
(deftest "cl-atom? nil" (assert= true (cl-atom? nil)))
|
||||||
|
(deftest "cl-atom? pair" (assert= false (cl-atom? (list 1))))
|
||||||
|
(deftest "cl-integerp?" (assert= true (cl-integerp? 42)))
|
||||||
|
(deftest "cl-floatp?" (assert= true (cl-floatp? 3.14)))
|
||||||
|
(deftest
|
||||||
|
"cl-characterp?"
|
||||||
|
(assert= true (cl-characterp? (integer->char 65))))
|
||||||
|
(deftest "cl-stringp?" (assert= true (cl-stringp? "hello")))
|
||||||
|
(deftest "cl-symbolp?" (assert= true (cl-symbolp? (quote foo)))))
|
||||||
|
|
||||||
|
(defsuite
|
||||||
|
"cl-arithmetic"
|
||||||
|
(deftest "cl-mod" (assert= 1 (cl-mod 10 3)))
|
||||||
|
(deftest "cl-rem" (assert= 1 (cl-rem 10 3)))
|
||||||
|
(deftest
|
||||||
|
"cl-quotient"
|
||||||
|
(assert= 3 (cl-quotient 10 3)))
|
||||||
|
(deftest "cl-gcd" (assert= 4 (cl-gcd 12 8)))
|
||||||
|
(deftest "cl-lcm" (assert= 12 (cl-lcm 4 6)))
|
||||||
|
(deftest "cl-abs pos" (assert= 5 (cl-abs 5)))
|
||||||
|
(deftest "cl-abs neg" (assert= 5 (cl-abs -5)))
|
||||||
|
(deftest "cl-min" (assert= 2 (cl-min 2 7)))
|
||||||
|
(deftest "cl-max" (assert= 7 (cl-max 2 7)))
|
||||||
|
(deftest "cl-evenp? t" (assert= true (cl-evenp? 4)))
|
||||||
|
(deftest "cl-evenp? f" (assert= false (cl-evenp? 3)))
|
||||||
|
(deftest "cl-oddp? t" (assert= true (cl-oddp? 7)))
|
||||||
|
(deftest "cl-zerop?" (assert= true (cl-zerop? 0)))
|
||||||
|
(deftest "cl-plusp?" (assert= true (cl-plusp? 1)))
|
||||||
|
(deftest "cl-minusp?" (assert= true (cl-minusp? -1)))
|
||||||
|
(deftest "cl-signum pos" (assert= 1 (cl-signum 42)))
|
||||||
|
(deftest "cl-signum neg" (assert= -1 (cl-signum -7)))
|
||||||
|
(deftest "cl-signum zero" (assert= 0 (cl-signum 0))))
|
||||||
|
|
||||||
|
(defsuite
|
||||||
|
"cl-chars"
|
||||||
|
(deftest
|
||||||
|
"cl-char-code"
|
||||||
|
(assert= 65 (cl-char-code (integer->char 65))))
|
||||||
|
(deftest "cl-code-char" (assert= true (char? (cl-code-char 65))))
|
||||||
|
(deftest
|
||||||
|
"cl-char-upcase"
|
||||||
|
(assert=
|
||||||
|
(integer->char 65)
|
||||||
|
(cl-char-upcase (integer->char 97))))
|
||||||
|
(deftest
|
||||||
|
"cl-char-downcase"
|
||||||
|
(assert=
|
||||||
|
(integer->char 97)
|
||||||
|
(cl-char-downcase (integer->char 65))))
|
||||||
|
(deftest
|
||||||
|
"cl-alpha-char-p"
|
||||||
|
(assert= true (cl-alpha-char-p (integer->char 65))))
|
||||||
|
(deftest
|
||||||
|
"cl-digit-char-p"
|
||||||
|
(assert= true (cl-digit-char-p (integer->char 48))))
|
||||||
|
(deftest
|
||||||
|
"cl-char=?"
|
||||||
|
(assert=
|
||||||
|
true
|
||||||
|
(cl-char=? (integer->char 65) (integer->char 65))))
|
||||||
|
(deftest
|
||||||
|
"cl-char<?"
|
||||||
|
(assert=
|
||||||
|
true
|
||||||
|
(cl-char<? (integer->char 65) (integer->char 90))))
|
||||||
|
(deftest
|
||||||
|
"cl-char space"
|
||||||
|
(assert= (integer->char 32) cl-char-space))
|
||||||
|
(deftest
|
||||||
|
"cl-char newline"
|
||||||
|
(assert= (integer->char 10) cl-char-newline)))
|
||||||
|
|
||||||
|
(defsuite
|
||||||
|
"cl-format"
|
||||||
|
(deftest
|
||||||
|
"cl-format nil basic"
|
||||||
|
(assert= "hello" (cl-format nil "~a" "hello")))
|
||||||
|
(deftest
|
||||||
|
"cl-format nil number"
|
||||||
|
(assert= "42" (cl-format nil "~d" 42)))
|
||||||
|
(deftest
|
||||||
|
"cl-format nil hex"
|
||||||
|
(assert= "ff" (cl-format nil "~x" 255)))
|
||||||
|
(deftest
|
||||||
|
"cl-format nil template"
|
||||||
|
(assert= "x=3 y=4" (cl-format nil "x=~d y=~d" 3 4)))
|
||||||
|
(deftest "cl-format nil tilde" (assert= "a~b" (cl-format nil "a~~b"))))
|
||||||
|
|
||||||
|
(defsuite
|
||||||
|
"cl-gensym"
|
||||||
|
(deftest
|
||||||
|
"cl-gensym returns symbol"
|
||||||
|
(assert= "symbol" (type-of (cl-gensym))))
|
||||||
|
(deftest "cl-gensym unique" (assert= false (= (cl-gensym) (cl-gensym)))))
|
||||||
|
|
||||||
|
(defsuite
|
||||||
|
"cl-sets"
|
||||||
|
(deftest "cl-make-set empty" (assert= true (cl-set? (cl-make-set))))
|
||||||
|
(deftest
|
||||||
|
"cl-set-add/member"
|
||||||
|
(let
|
||||||
|
((s (cl-make-set)))
|
||||||
|
(do
|
||||||
|
(cl-set-add s 1)
|
||||||
|
(assert= true (cl-set-memberp s 1)))))
|
||||||
|
(deftest
|
||||||
|
"cl-set-memberp false"
|
||||||
|
(assert= false (cl-set-memberp (cl-make-set) 42)))
|
||||||
|
(deftest
|
||||||
|
"cl-list->set"
|
||||||
|
(let
|
||||||
|
((s (cl-list->set (list 1 2 3))))
|
||||||
|
(assert= true (cl-set-memberp s 2)))))
|
||||||
|
|
||||||
|
(defsuite
|
||||||
|
"cl-lists"
|
||||||
|
(deftest
|
||||||
|
"cl-nth 0"
|
||||||
|
(assert=
|
||||||
|
1
|
||||||
|
(cl-nth 0 (list 1 2 3))))
|
||||||
|
(deftest
|
||||||
|
"cl-nth 2"
|
||||||
|
(assert=
|
||||||
|
3
|
||||||
|
(cl-nth 2 (list 1 2 3))))
|
||||||
|
(deftest
|
||||||
|
"cl-last"
|
||||||
|
(assert=
|
||||||
|
(list 3)
|
||||||
|
(cl-last (list 1 2 3))))
|
||||||
|
(deftest
|
||||||
|
"cl-butlast"
|
||||||
|
(assert=
|
||||||
|
(list 1 2)
|
||||||
|
(cl-butlast (list 1 2 3))))
|
||||||
|
(deftest
|
||||||
|
"cl-nthcdr 1"
|
||||||
|
(assert=
|
||||||
|
(list 2 3)
|
||||||
|
(cl-nthcdr 1 (list 1 2 3))))
|
||||||
|
(deftest
|
||||||
|
"cl-assoc hit"
|
||||||
|
(assert=
|
||||||
|
(list "b" 2)
|
||||||
|
(cl-assoc "b" (list (list "a" 1) (list "b" 2)))))
|
||||||
|
(deftest
|
||||||
|
"cl-assoc miss"
|
||||||
|
(assert= nil (cl-assoc "z" (list (list "a" 1)))))
|
||||||
|
(deftest
|
||||||
|
"cl-getf hit"
|
||||||
|
(assert= 42 (cl-getf (list "x" 42 "y" 99) "x")))
|
||||||
|
(deftest "cl-getf miss" (assert= nil (cl-getf (list "x" 42) "z")))
|
||||||
|
(deftest
|
||||||
|
"cl-adjoin new"
|
||||||
|
(assert=
|
||||||
|
(list 0 1 2)
|
||||||
|
(cl-adjoin 0 (list 1 2))))
|
||||||
|
(deftest
|
||||||
|
"cl-adjoin dup"
|
||||||
|
(assert=
|
||||||
|
(list 1 2)
|
||||||
|
(cl-adjoin 1 (list 1 2))))
|
||||||
|
(deftest
|
||||||
|
"cl-flatten"
|
||||||
|
(assert=
|
||||||
|
(list 1 2 3 4)
|
||||||
|
(cl-flatten (list 1 (list 2 3) 4))))
|
||||||
|
(deftest
|
||||||
|
"cl-member hit"
|
||||||
|
(assert=
|
||||||
|
(list 2 3)
|
||||||
|
(cl-member 2 (list 1 2 3))))
|
||||||
|
(deftest
|
||||||
|
"cl-member miss"
|
||||||
|
(assert=
|
||||||
|
nil
|
||||||
|
(cl-member 9 (list 1 2 3)))))
|
||||||
|
|
||||||
|
(defsuite
|
||||||
|
"cl-radix"
|
||||||
|
(deftest "binary" (assert= "1010" (cl-format-binary 10)))
|
||||||
|
(deftest "octal" (assert= "17" (cl-format-octal 15)))
|
||||||
|
(deftest "hex" (assert= "ff" (cl-format-hex 255)))
|
||||||
|
(deftest "decimal" (assert= "42" (cl-format-decimal 42)))
|
||||||
|
(deftest
|
||||||
|
"n->s r16"
|
||||||
|
(assert= "1f" (cl-integer-to-string 31 16)))
|
||||||
|
(deftest
|
||||||
|
"s->n r16"
|
||||||
|
(assert= 31 (cl-string-to-integer "1f" 16))))
|
||||||
285
lib/common-lisp/tests/stdlib.sx
Normal file
285
lib/common-lisp/tests/stdlib.sx
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
;; lib/common-lisp/tests/stdlib.sx — Phase 6: sequence, list, string functions
|
||||||
|
|
||||||
|
(define ev (fn (src) (cl-eval-str src (cl-make-env))))
|
||||||
|
|
||||||
|
(define passed 0)
|
||||||
|
(define failed 0)
|
||||||
|
(define failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
check
|
||||||
|
(fn
|
||||||
|
(label got expected)
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! passed (+ passed 1))
|
||||||
|
(begin
|
||||||
|
(set! failed (+ failed 1))
|
||||||
|
(set!
|
||||||
|
failures
|
||||||
|
(append
|
||||||
|
failures
|
||||||
|
(list
|
||||||
|
(str
|
||||||
|
"FAIL ["
|
||||||
|
label
|
||||||
|
"]: got="
|
||||||
|
(inspect got)
|
||||||
|
" expected="
|
||||||
|
(inspect expected)))))))))
|
||||||
|
|
||||||
|
;; ── mapc ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "mapc returns list"
|
||||||
|
(ev "(mapc #'1+ '(1 2 3))")
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
;; ── mapcan ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "mapcan basic"
|
||||||
|
(ev "(mapcan (lambda (x) (list x (* x x))) '(1 2 3))")
|
||||||
|
(list 1 1 2 4 3 9))
|
||||||
|
|
||||||
|
(check "mapcan filter-like"
|
||||||
|
(ev "(mapcan (lambda (x) (if (evenp x) (list x) nil)) '(1 2 3 4 5 6))")
|
||||||
|
(list 2 4 6))
|
||||||
|
|
||||||
|
;; ── reduce ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "reduce sum"
|
||||||
|
(ev "(reduce #'+ '(1 2 3 4 5))")
|
||||||
|
15)
|
||||||
|
|
||||||
|
(check "reduce with initial-value"
|
||||||
|
(ev "(reduce #'+ '(1 2 3) :initial-value 10)")
|
||||||
|
16)
|
||||||
|
|
||||||
|
(check "reduce max"
|
||||||
|
(ev "(reduce (lambda (a b) (if (> a b) a b)) '(3 1 4 1 5 9 2 6))")
|
||||||
|
9)
|
||||||
|
|
||||||
|
;; ── find ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "find present"
|
||||||
|
(ev "(find 3 '(1 2 3 4 5))")
|
||||||
|
3)
|
||||||
|
|
||||||
|
(check "find absent"
|
||||||
|
(ev "(find 9 '(1 2 3))")
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(check "find-if present"
|
||||||
|
(ev "(find-if #'evenp '(1 3 4 7))")
|
||||||
|
4)
|
||||||
|
|
||||||
|
(check "find-if absent"
|
||||||
|
(ev "(find-if #'evenp '(1 3 5))")
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(check "find-if-not"
|
||||||
|
(ev "(find-if-not #'evenp '(2 4 5 6))")
|
||||||
|
5)
|
||||||
|
|
||||||
|
;; ── position ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "position found"
|
||||||
|
(ev "(position 3 '(1 2 3 4 5))")
|
||||||
|
2)
|
||||||
|
|
||||||
|
(check "position not found"
|
||||||
|
(ev "(position 9 '(1 2 3))")
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(check "position-if"
|
||||||
|
(ev "(position-if #'evenp '(1 3 4 8))")
|
||||||
|
2)
|
||||||
|
|
||||||
|
;; ── count ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "count"
|
||||||
|
(ev "(count 2 '(1 2 3 2 4 2))")
|
||||||
|
3)
|
||||||
|
|
||||||
|
(check "count-if"
|
||||||
|
(ev "(count-if #'evenp '(1 2 3 4 5 6))")
|
||||||
|
3)
|
||||||
|
|
||||||
|
;; ── every / some / notany / notevery ─────────────────────────────
|
||||||
|
|
||||||
|
(check "every true"
|
||||||
|
(ev "(every #'evenp '(2 4 6))")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(check "every false"
|
||||||
|
(ev "(every #'evenp '(2 3 6))")
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(check "every empty"
|
||||||
|
(ev "(every #'evenp '())")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(check "some truthy"
|
||||||
|
(ev "(some #'evenp '(1 3 4))")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(check "some nil"
|
||||||
|
(ev "(some #'evenp '(1 3 5))")
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(check "notany true"
|
||||||
|
(ev "(notany #'evenp '(1 3 5))")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(check "notany false"
|
||||||
|
(ev "(notany #'evenp '(1 2 5))")
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(check "notevery false"
|
||||||
|
(ev "(notevery #'evenp '(2 4 6))")
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(check "notevery true"
|
||||||
|
(ev "(notevery #'evenp '(2 3 6))")
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── remove ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "remove"
|
||||||
|
(ev "(remove 3 '(1 2 3 4 3 5))")
|
||||||
|
(list 1 2 4 5))
|
||||||
|
|
||||||
|
(check "remove-if"
|
||||||
|
(ev "(remove-if #'evenp '(1 2 3 4 5 6))")
|
||||||
|
(list 1 3 5))
|
||||||
|
|
||||||
|
(check "remove-if-not"
|
||||||
|
(ev "(remove-if-not #'evenp '(1 2 3 4 5 6))")
|
||||||
|
(list 2 4 6))
|
||||||
|
|
||||||
|
;; ── member ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "member found"
|
||||||
|
(ev "(member 3 '(1 2 3 4 5))")
|
||||||
|
(list 3 4 5))
|
||||||
|
|
||||||
|
(check "member not found"
|
||||||
|
(ev "(member 9 '(1 2 3))")
|
||||||
|
nil)
|
||||||
|
|
||||||
|
;; ── subst ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "subst flat"
|
||||||
|
(ev "(subst 'b 'a '(a b c a))")
|
||||||
|
(list "B" "B" "C" "B"))
|
||||||
|
|
||||||
|
(check "subst nested"
|
||||||
|
(ev "(subst 99 1 '(1 (2 1) 3))")
|
||||||
|
(list 99 (list 2 99) 3))
|
||||||
|
|
||||||
|
;; ── assoc ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "assoc found"
|
||||||
|
(ev "(assoc 'b '((a 1) (b 2) (c 3)))")
|
||||||
|
(list "B" 2))
|
||||||
|
|
||||||
|
(check "assoc not found"
|
||||||
|
(ev "(assoc 'z '((a 1) (b 2)))")
|
||||||
|
nil)
|
||||||
|
|
||||||
|
;; ── list ops ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "last"
|
||||||
|
(ev "(last '(1 2 3 4))")
|
||||||
|
(list 4))
|
||||||
|
|
||||||
|
(check "butlast"
|
||||||
|
(ev "(butlast '(1 2 3 4))")
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(check "nthcdr"
|
||||||
|
(ev "(nthcdr 2 '(a b c d))")
|
||||||
|
(list "C" "D"))
|
||||||
|
|
||||||
|
(check "list*"
|
||||||
|
(ev "(list* 1 2 '(3 4))")
|
||||||
|
(list 1 2 3 4))
|
||||||
|
|
||||||
|
(check "cadr"
|
||||||
|
(ev "(cadr '(1 2 3))")
|
||||||
|
2)
|
||||||
|
|
||||||
|
(check "caddr"
|
||||||
|
(ev "(caddr '(1 2 3))")
|
||||||
|
3)
|
||||||
|
|
||||||
|
(check "cadddr"
|
||||||
|
(ev "(cadddr '(1 2 3 4))")
|
||||||
|
4)
|
||||||
|
|
||||||
|
(check "cddr"
|
||||||
|
(ev "(cddr '(1 2 3 4))")
|
||||||
|
(list 3 4))
|
||||||
|
|
||||||
|
;; ── subseq ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "subseq string"
|
||||||
|
(ev "(subseq \"hello\" 1 3)")
|
||||||
|
"el")
|
||||||
|
|
||||||
|
(check "subseq list"
|
||||||
|
(ev "(subseq '(a b c d) 1 3)")
|
||||||
|
(list "B" "C"))
|
||||||
|
|
||||||
|
(check "subseq no end"
|
||||||
|
(ev "(subseq \"hello\" 2)")
|
||||||
|
"llo")
|
||||||
|
|
||||||
|
;; ── FORMAT ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "format ~A"
|
||||||
|
(ev "(format nil \"hello ~A\" \"world\")")
|
||||||
|
"hello world")
|
||||||
|
|
||||||
|
(check "format ~D"
|
||||||
|
(ev "(format nil \"~D items\" 42)")
|
||||||
|
"42 items")
|
||||||
|
|
||||||
|
(check "format two args"
|
||||||
|
(ev "(format nil \"~A ~A\" 1 2)")
|
||||||
|
"1 2")
|
||||||
|
|
||||||
|
(check "format ~A+~A=~A"
|
||||||
|
(ev "(format nil \"~A + ~A = ~A\" 1 2 3)")
|
||||||
|
"1 + 2 = 3")
|
||||||
|
|
||||||
|
(check "format iterate"
|
||||||
|
(ev "(format nil \"~{~A~}\" (quote (1 2 3)))")
|
||||||
|
"123")
|
||||||
|
|
||||||
|
(check "format iterate with space"
|
||||||
|
(ev "(format nil \"(~{~A ~})\" (quote (1 2 3)))")
|
||||||
|
"(1 2 3 )")
|
||||||
|
|
||||||
|
;; ── packages ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(check "defpackage returns name"
|
||||||
|
(ev "(defpackage :my-pkg (:use :cl))")
|
||||||
|
"MY-PKG")
|
||||||
|
|
||||||
|
(check "in-package"
|
||||||
|
(ev "(progn (defpackage :test-pkg) (in-package :test-pkg) (package-name))")
|
||||||
|
"TEST-PKG")
|
||||||
|
|
||||||
|
(check "package-qualified function"
|
||||||
|
(ev "(cl:car (quote (1 2 3)))")
|
||||||
|
1)
|
||||||
|
|
||||||
|
(check "package-qualified function 2"
|
||||||
|
(ev "(cl:mapcar (function evenp) (quote (2 3 4)))")
|
||||||
|
(list true nil true))
|
||||||
|
|
||||||
|
;; ── summary ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define stdlib-passed passed)
|
||||||
|
(define stdlib-failed failed)
|
||||||
|
(define stdlib-failures failures)
|
||||||
86
lib/erlang/bench_ring.sh
Executable file
86
lib/erlang/bench_ring.sh
Executable file
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Erlang-on-SX ring benchmark.
|
||||||
|
#
|
||||||
|
# Spawns N processes in a ring, passes a token N hops (one full round),
|
||||||
|
# and reports wall-clock time + throughput. Aspirational target from
|
||||||
|
# the plan is 1M processes; current sync-scheduler architecture caps out
|
||||||
|
# orders of magnitude lower — this script measures honestly across a
|
||||||
|
# range of N so the result/scaling is recorded.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# bash lib/erlang/bench_ring.sh # default ladder
|
||||||
|
# bash lib/erlang/bench_ring.sh 100 1000 5000 # custom Ns
|
||||||
|
|
||||||
|
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." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$#" -gt 0 ]; then
|
||||||
|
NS=("$@")
|
||||||
|
else
|
||||||
|
NS=(10 100 500 1000)
|
||||||
|
fi
|
||||||
|
|
||||||
|
TMPFILE=$(mktemp)
|
||||||
|
trap "rm -f $TMPFILE" EXIT
|
||||||
|
|
||||||
|
# One-line Erlang program. Replaces __N__ with the size for each run.
|
||||||
|
PROGRAM='Me = self(), N = __N__, Spawner = fun () -> receive {setup, Next} -> Loop = fun () -> receive {token, 0, Parent} -> Parent ! done; {token, K, Parent} -> Next ! {token, K-1, Parent}, Loop() end end, Loop() end end, BuildRing = fun (K, Acc) -> if K =:= 0 -> Acc; true -> BuildRing(K-1, [spawn(Spawner) | Acc]) end end, Pids = BuildRing(N, []), Wire = fun (Ps) -> case Ps of [P, Q | _] -> P ! {setup, Q}, Wire(tl(Ps)); [Last] -> Last ! {setup, hd(Pids)} end end, Wire(Pids), hd(Pids) ! {token, N, Me}, receive done -> done end'
|
||||||
|
|
||||||
|
run_n() {
|
||||||
|
local n="$1"
|
||||||
|
local prog="${PROGRAM//__N__/$n}"
|
||||||
|
cat > "$TMPFILE" <<EPOCHS
|
||||||
|
(epoch 1)
|
||||||
|
(load "lib/erlang/tokenizer.sx")
|
||||||
|
(load "lib/erlang/parser.sx")
|
||||||
|
(load "lib/erlang/parser-core.sx")
|
||||||
|
(load "lib/erlang/parser-expr.sx")
|
||||||
|
(load "lib/erlang/parser-module.sx")
|
||||||
|
(load "lib/erlang/transpile.sx")
|
||||||
|
(load "lib/erlang/runtime.sx")
|
||||||
|
(epoch 2)
|
||||||
|
(eval "(erlang-eval-ast \"${prog//\"/\\\"}\")")
|
||||||
|
EPOCHS
|
||||||
|
|
||||||
|
local start_s start_ns end_s end_ns elapsed_ms
|
||||||
|
start_s=$(date +%s)
|
||||||
|
start_ns=$(date +%N)
|
||||||
|
out=$(timeout 300 "$SX_SERVER" < "$TMPFILE" 2>&1)
|
||||||
|
end_s=$(date +%s)
|
||||||
|
end_ns=$(date +%N)
|
||||||
|
|
||||||
|
local ok="false"
|
||||||
|
if echo "$out" | grep -q ':name "done"'; then ok="true"; fi
|
||||||
|
|
||||||
|
# ms = (end_s - start_s)*1000 + (end_ns - start_ns)/1e6
|
||||||
|
elapsed_ms=$(awk -v s1="$start_s" -v n1="$start_ns" -v s2="$end_s" -v n2="$end_ns" \
|
||||||
|
'BEGIN { printf "%d", (s2 - s1) * 1000 + (n2 - n1) / 1000000 }')
|
||||||
|
|
||||||
|
if [ "$ok" = "true" ]; then
|
||||||
|
local hops_per_s
|
||||||
|
hops_per_s=$(awk -v n="$n" -v ms="$elapsed_ms" \
|
||||||
|
'BEGIN { if (ms == 0) ms = 1; printf "%.0f", n * 1000 / ms }')
|
||||||
|
printf " N=%-8s hops=%-8s %sms (%s hops/s)\n" "$n" "$n" "$elapsed_ms" "$hops_per_s"
|
||||||
|
else
|
||||||
|
printf " N=%-8s FAILED %sms\n" "$n" "$elapsed_ms"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Ring benchmark — sx_server.exe (synchronous scheduler)"
|
||||||
|
echo
|
||||||
|
for n in "${NS[@]}"; do
|
||||||
|
run_n "$n"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
echo "Note: 1M-process target from the plan is aspirational; the synchronous"
|
||||||
|
echo "scheduler with shift-based suspension and dict-based env copies is not"
|
||||||
|
echo "engineered for that scale. Numbers above are honest baselines."
|
||||||
35
lib/erlang/bench_ring_results.md
Normal file
35
lib/erlang/bench_ring_results.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Ring Benchmark Results
|
||||||
|
|
||||||
|
Generated by `lib/erlang/bench_ring.sh` against `sx_server.exe` on the
|
||||||
|
synchronous Erlang-on-SX scheduler.
|
||||||
|
|
||||||
|
| N (processes) | Hops | Wall-clock | Throughput |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 10 | 10 | 907ms | 11 hops/s |
|
||||||
|
| 50 | 50 | 2107ms | 24 hops/s |
|
||||||
|
| 100 | 100 | 3827ms | 26 hops/s |
|
||||||
|
| 500 | 500 | 17004ms | 29 hops/s |
|
||||||
|
| 1000 | 1000 | 29832ms | 34 hops/s |
|
||||||
|
|
||||||
|
(Each `Nm` row spawns N processes connected in a ring and passes a
|
||||||
|
single token N hops total — i.e. the token completes one full lap.)
|
||||||
|
|
||||||
|
## Status of the 1M-process target
|
||||||
|
|
||||||
|
Phase 3's stretch goal in `plans/erlang-on-sx.md` is a million-process
|
||||||
|
ring benchmark. **That target is not met** in the current synchronous
|
||||||
|
scheduler; extrapolating from the table above, 1M hops would take
|
||||||
|
~30 000 s. Correctness is fine — the program runs at every measured
|
||||||
|
size — but throughput is bound by per-hop overhead.
|
||||||
|
|
||||||
|
Per-hop cost is dominated by:
|
||||||
|
- `er-env-copy` per fun clause attempt (whole-dict copy each time)
|
||||||
|
- `call/cc` capture + `raise`/`guard` unwind on every `receive`
|
||||||
|
- `er-q-delete-at!` rebuilds the mailbox backing list on every match
|
||||||
|
- `dict-set!`/`dict-has?` lookups in the global processes table
|
||||||
|
|
||||||
|
To reach 1M-process throughput in this architecture would need at
|
||||||
|
least: persistent (path-copying) envs, an inline scheduler that
|
||||||
|
doesn't call/cc on the common path (msg-already-in-mailbox), and a
|
||||||
|
linked-list mailbox. None of those are in scope for the Phase 3
|
||||||
|
checkbox — captured here as the floor we're starting from.
|
||||||
153
lib/erlang/conformance.sh
Executable file
153
lib/erlang/conformance.sh
Executable file
@@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Erlang-on-SX conformance runner.
|
||||||
|
#
|
||||||
|
# Loads every erlang test suite via the epoch protocol, collects
|
||||||
|
# pass/fail counts, and writes lib/erlang/scoreboard.json + .md.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# bash lib/erlang/conformance.sh # run all suites
|
||||||
|
# bash lib/erlang/conformance.sh -v # verbose per-suite
|
||||||
|
|
||||||
|
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." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERBOSE="${1:-}"
|
||||||
|
TMPFILE=$(mktemp)
|
||||||
|
OUTFILE=$(mktemp)
|
||||||
|
trap "rm -f $TMPFILE $OUTFILE" EXIT
|
||||||
|
|
||||||
|
# Each suite: name | counter pass | counter total
|
||||||
|
SUITES=(
|
||||||
|
"tokenize|er-test-pass|er-test-count"
|
||||||
|
"parse|er-parse-test-pass|er-parse-test-count"
|
||||||
|
"eval|er-eval-test-pass|er-eval-test-count"
|
||||||
|
"runtime|er-rt-test-pass|er-rt-test-count"
|
||||||
|
"ring|er-ring-test-pass|er-ring-test-count"
|
||||||
|
"ping-pong|er-pp-test-pass|er-pp-test-count"
|
||||||
|
"bank|er-bank-test-pass|er-bank-test-count"
|
||||||
|
"echo|er-echo-test-pass|er-echo-test-count"
|
||||||
|
"fib|er-fib-test-pass|er-fib-test-count"
|
||||||
|
)
|
||||||
|
|
||||||
|
cat > "$TMPFILE" << 'EPOCHS'
|
||||||
|
(epoch 1)
|
||||||
|
(load "lib/erlang/tokenizer.sx")
|
||||||
|
(load "lib/erlang/parser.sx")
|
||||||
|
(load "lib/erlang/parser-core.sx")
|
||||||
|
(load "lib/erlang/parser-expr.sx")
|
||||||
|
(load "lib/erlang/parser-module.sx")
|
||||||
|
(load "lib/erlang/transpile.sx")
|
||||||
|
(load "lib/erlang/runtime.sx")
|
||||||
|
(load "lib/erlang/tests/tokenize.sx")
|
||||||
|
(load "lib/erlang/tests/parse.sx")
|
||||||
|
(load "lib/erlang/tests/eval.sx")
|
||||||
|
(load "lib/erlang/tests/runtime.sx")
|
||||||
|
(load "lib/erlang/tests/programs/ring.sx")
|
||||||
|
(load "lib/erlang/tests/programs/ping_pong.sx")
|
||||||
|
(load "lib/erlang/tests/programs/bank.sx")
|
||||||
|
(load "lib/erlang/tests/programs/echo.sx")
|
||||||
|
(load "lib/erlang/tests/programs/fib_server.sx")
|
||||||
|
(epoch 100)
|
||||||
|
(eval "(list er-test-pass er-test-count)")
|
||||||
|
(epoch 101)
|
||||||
|
(eval "(list er-parse-test-pass er-parse-test-count)")
|
||||||
|
(epoch 102)
|
||||||
|
(eval "(list er-eval-test-pass er-eval-test-count)")
|
||||||
|
(epoch 103)
|
||||||
|
(eval "(list er-rt-test-pass er-rt-test-count)")
|
||||||
|
(epoch 104)
|
||||||
|
(eval "(list er-ring-test-pass er-ring-test-count)")
|
||||||
|
(epoch 105)
|
||||||
|
(eval "(list er-pp-test-pass er-pp-test-count)")
|
||||||
|
(epoch 106)
|
||||||
|
(eval "(list er-bank-test-pass er-bank-test-count)")
|
||||||
|
(epoch 107)
|
||||||
|
(eval "(list er-echo-test-pass er-echo-test-count)")
|
||||||
|
(epoch 108)
|
||||||
|
(eval "(list er-fib-test-pass er-fib-test-count)")
|
||||||
|
EPOCHS
|
||||||
|
|
||||||
|
timeout 120 "$SX_SERVER" < "$TMPFILE" > "$OUTFILE" 2>&1
|
||||||
|
|
||||||
|
# Parse "(N M)" from the line after each "(ok-len <epoch> ...)" marker.
|
||||||
|
parse_pair() {
|
||||||
|
local epoch="$1"
|
||||||
|
local line
|
||||||
|
line=$(grep -A1 "^(ok-len $epoch " "$OUTFILE" | tail -1)
|
||||||
|
echo "$line" | sed -E 's/[()]//g'
|
||||||
|
}
|
||||||
|
|
||||||
|
TOTAL_PASS=0
|
||||||
|
TOTAL_COUNT=0
|
||||||
|
JSON_SUITES=""
|
||||||
|
MD_ROWS=""
|
||||||
|
|
||||||
|
idx=0
|
||||||
|
for entry in "${SUITES[@]}"; do
|
||||||
|
name="${entry%%|*}"
|
||||||
|
epoch=$((100 + idx))
|
||||||
|
pair=$(parse_pair "$epoch")
|
||||||
|
pass=$(echo "$pair" | awk '{print $1}')
|
||||||
|
count=$(echo "$pair" | awk '{print $2}')
|
||||||
|
if [ -z "$pass" ] || [ -z "$count" ]; then
|
||||||
|
pass=0
|
||||||
|
count=0
|
||||||
|
fi
|
||||||
|
TOTAL_PASS=$((TOTAL_PASS + pass))
|
||||||
|
TOTAL_COUNT=$((TOTAL_COUNT + count))
|
||||||
|
status="ok"
|
||||||
|
marker="✅"
|
||||||
|
if [ "$pass" != "$count" ]; then
|
||||||
|
status="fail"
|
||||||
|
marker="❌"
|
||||||
|
fi
|
||||||
|
if [ "$VERBOSE" = "-v" ]; then
|
||||||
|
printf " %-12s %s/%s\n" "$name" "$pass" "$count"
|
||||||
|
fi
|
||||||
|
if [ -n "$JSON_SUITES" ]; then JSON_SUITES+=","; fi
|
||||||
|
JSON_SUITES+=$'\n '
|
||||||
|
JSON_SUITES+="{\"name\":\"$name\",\"pass\":$pass,\"total\":$count,\"status\":\"$status\"}"
|
||||||
|
MD_ROWS+="| $marker | $name | $pass | $count |"$'\n'
|
||||||
|
idx=$((idx + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '\nErlang-on-SX conformance: %d / %d\n' "$TOTAL_PASS" "$TOTAL_COUNT"
|
||||||
|
|
||||||
|
# scoreboard.json
|
||||||
|
cat > lib/erlang/scoreboard.json <<JSON
|
||||||
|
{
|
||||||
|
"language": "erlang",
|
||||||
|
"total_pass": $TOTAL_PASS,
|
||||||
|
"total": $TOTAL_COUNT,
|
||||||
|
"suites": [$JSON_SUITES
|
||||||
|
]
|
||||||
|
}
|
||||||
|
JSON
|
||||||
|
|
||||||
|
# scoreboard.md
|
||||||
|
cat > lib/erlang/scoreboard.md <<MD
|
||||||
|
# Erlang-on-SX Scoreboard
|
||||||
|
|
||||||
|
**Total: ${TOTAL_PASS} / ${TOTAL_COUNT} tests passing**
|
||||||
|
|
||||||
|
| | Suite | Pass | Total |
|
||||||
|
|---|---|---|---|
|
||||||
|
$MD_ROWS
|
||||||
|
|
||||||
|
Generated by \`lib/erlang/conformance.sh\`.
|
||||||
|
MD
|
||||||
|
|
||||||
|
if [ "$TOTAL_PASS" -eq "$TOTAL_COUNT" ]; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -237,6 +237,8 @@
|
|||||||
(er-parse-fun-expr st)
|
(er-parse-fun-expr st)
|
||||||
(er-is? st "keyword" "try")
|
(er-is? st "keyword" "try")
|
||||||
(er-parse-try st)
|
(er-parse-try st)
|
||||||
|
(er-is? st "punct" "<<")
|
||||||
|
(er-parse-binary st)
|
||||||
:else (error
|
:else (error
|
||||||
(str
|
(str
|
||||||
"Erlang parse: unexpected "
|
"Erlang parse: unexpected "
|
||||||
@@ -281,12 +283,56 @@
|
|||||||
(fn
|
(fn
|
||||||
(st)
|
(st)
|
||||||
(er-expect! st "punct" "[")
|
(er-expect! st "punct" "[")
|
||||||
(if
|
(cond
|
||||||
(er-is? st "punct" "]")
|
(er-is? st "punct" "]")
|
||||||
(do (er-advance! st) {:type "nil"})
|
(do (er-advance! st) {:type "nil"})
|
||||||
|
:else (let
|
||||||
|
((first (er-parse-expr-prec st 0)))
|
||||||
|
(cond
|
||||||
|
(er-is? st "punct" "||") (er-parse-list-comp st first)
|
||||||
|
:else (er-parse-list-tail st (list first)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-parse-list-comp
|
||||||
|
(fn
|
||||||
|
(st head)
|
||||||
|
(er-advance! st)
|
||||||
(let
|
(let
|
||||||
((elems (list (er-parse-expr-prec st 0))))
|
((quals (list (er-parse-lc-qualifier st))))
|
||||||
(er-parse-list-tail st elems)))))
|
(er-parse-list-comp-tail st head quals))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-parse-list-comp-tail
|
||||||
|
(fn
|
||||||
|
(st head quals)
|
||||||
|
(cond
|
||||||
|
(er-is? st "punct" ",")
|
||||||
|
(do
|
||||||
|
(er-advance! st)
|
||||||
|
(append! quals (er-parse-lc-qualifier st))
|
||||||
|
(er-parse-list-comp-tail st head quals))
|
||||||
|
(er-is? st "punct" "]")
|
||||||
|
(do (er-advance! st) {:head head :qualifiers quals :type "lc"})
|
||||||
|
:else (error
|
||||||
|
(str
|
||||||
|
"Erlang parse: expected ',' or ']' in list comprehension, got '"
|
||||||
|
(er-cur-value st)
|
||||||
|
"'")))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-parse-lc-qualifier
|
||||||
|
(fn
|
||||||
|
(st)
|
||||||
|
(let
|
||||||
|
((e (er-parse-expr-prec st 0)))
|
||||||
|
(cond
|
||||||
|
(er-is? st "punct" "<-")
|
||||||
|
(do
|
||||||
|
(er-advance! st)
|
||||||
|
(let
|
||||||
|
((source (er-parse-expr-prec st 0)))
|
||||||
|
{:kind "gen" :pattern e :source source}))
|
||||||
|
:else {:kind "filter" :expr e}))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
er-parse-list-tail
|
er-parse-list-tail
|
||||||
@@ -532,3 +578,63 @@
|
|||||||
((guards (if (er-is? st "keyword" "when") (do (er-advance! st) (er-parse-guards st)) (list))))
|
((guards (if (er-is? st "keyword" "when") (do (er-advance! st) (er-parse-guards st)) (list))))
|
||||||
(er-expect! st "punct" "->")
|
(er-expect! st "punct" "->")
|
||||||
(let ((body (er-parse-body st))) {:pattern pat :body body :class klass :guards guards}))))))
|
(let ((body (er-parse-body st))) {:pattern pat :body body :class klass :guards guards}))))))
|
||||||
|
|
||||||
|
;; ── binary literals / patterns ────────────────────────────────
|
||||||
|
;; `<< [Seg {, Seg}] >>` where Seg = Value [: Size] [/ Spec]. Size is
|
||||||
|
;; a literal integer (multiple of 8 supported); Spec is `integer`
|
||||||
|
;; (default) or `binary` (rest-of-binary tail). Sufficient for the
|
||||||
|
;; common `<<A:8, B:16, Rest/binary>>` patterns.
|
||||||
|
(define
|
||||||
|
er-parse-binary
|
||||||
|
(fn
|
||||||
|
(st)
|
||||||
|
(er-expect! st "punct" "<<")
|
||||||
|
(cond
|
||||||
|
(er-is? st "punct" ">>")
|
||||||
|
(do (er-advance! st) {:segments (list) :type "binary"})
|
||||||
|
:else (let
|
||||||
|
((segs (list (er-parse-binary-segment st))))
|
||||||
|
(er-parse-binary-tail st segs)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-parse-binary-tail
|
||||||
|
(fn
|
||||||
|
(st segs)
|
||||||
|
(cond
|
||||||
|
(er-is? st "punct" ",")
|
||||||
|
(do
|
||||||
|
(er-advance! st)
|
||||||
|
(append! segs (er-parse-binary-segment st))
|
||||||
|
(er-parse-binary-tail st segs))
|
||||||
|
(er-is? st "punct" ">>")
|
||||||
|
(do (er-advance! st) {:segments segs :type "binary"})
|
||||||
|
:else (error
|
||||||
|
(str
|
||||||
|
"Erlang parse: expected ',' or '>>' in binary, got '"
|
||||||
|
(er-cur-value st)
|
||||||
|
"'")))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-parse-binary-segment
|
||||||
|
(fn
|
||||||
|
(st)
|
||||||
|
;; Use `er-parse-primary` for the value so a leading `:` falls
|
||||||
|
;; through to the segment's size suffix instead of being eaten
|
||||||
|
;; by `er-parse-postfix-loop` as a `Mod:Fun` remote call.
|
||||||
|
(let
|
||||||
|
((v (er-parse-primary st)))
|
||||||
|
(let
|
||||||
|
((size (cond
|
||||||
|
(er-is? st "punct" ":")
|
||||||
|
(do (er-advance! st) (er-parse-primary st))
|
||||||
|
:else nil))
|
||||||
|
(spec (cond
|
||||||
|
(er-is? st "op" "/")
|
||||||
|
(do
|
||||||
|
(er-advance! st)
|
||||||
|
(let
|
||||||
|
((tok (er-cur st)))
|
||||||
|
(er-advance! st)
|
||||||
|
(get tok :value)))
|
||||||
|
:else "integer")))
|
||||||
|
{:size size :spec spec :value v}))))
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
16
lib/erlang/scoreboard.json
Normal file
16
lib/erlang/scoreboard.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"language": "erlang",
|
||||||
|
"total_pass": 530,
|
||||||
|
"total": 530,
|
||||||
|
"suites": [
|
||||||
|
{"name":"tokenize","pass":62,"total":62,"status":"ok"},
|
||||||
|
{"name":"parse","pass":52,"total":52,"status":"ok"},
|
||||||
|
{"name":"eval","pass":346,"total":346,"status":"ok"},
|
||||||
|
{"name":"runtime","pass":39,"total":39,"status":"ok"},
|
||||||
|
{"name":"ring","pass":4,"total":4,"status":"ok"},
|
||||||
|
{"name":"ping-pong","pass":4,"total":4,"status":"ok"},
|
||||||
|
{"name":"bank","pass":8,"total":8,"status":"ok"},
|
||||||
|
{"name":"echo","pass":7,"total":7,"status":"ok"},
|
||||||
|
{"name":"fib","pass":8,"total":8,"status":"ok"}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
lib/erlang/scoreboard.md
Normal file
18
lib/erlang/scoreboard.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Erlang-on-SX Scoreboard
|
||||||
|
|
||||||
|
**Total: 530 / 530 tests passing**
|
||||||
|
|
||||||
|
| | Suite | Pass | Total |
|
||||||
|
|---|---|---|---|
|
||||||
|
| ✅ | tokenize | 62 | 62 |
|
||||||
|
| ✅ | parse | 52 | 52 |
|
||||||
|
| ✅ | eval | 346 | 346 |
|
||||||
|
| ✅ | runtime | 39 | 39 |
|
||||||
|
| ✅ | ring | 4 | 4 |
|
||||||
|
| ✅ | ping-pong | 4 | 4 |
|
||||||
|
| ✅ | bank | 8 | 8 |
|
||||||
|
| ✅ | echo | 7 | 7 |
|
||||||
|
| ✅ | fib | 8 | 8 |
|
||||||
|
|
||||||
|
|
||||||
|
Generated by `lib/erlang/conformance.sh`.
|
||||||
1130
lib/erlang/tests/eval.sx
Normal file
1130
lib/erlang/tests/eval.sx
Normal file
File diff suppressed because it is too large
Load Diff
159
lib/erlang/tests/programs/bank.sx
Normal file
159
lib/erlang/tests/programs/bank.sx
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
;; Bank account server — stateful process, balance threaded through
|
||||||
|
;; recursive loop. Handles {deposit, Amt, From}, {withdraw, Amt, From},
|
||||||
|
;; {balance, From}, stop. Tests stateful process patterns.
|
||||||
|
|
||||||
|
(define er-bank-test-count 0)
|
||||||
|
(define er-bank-test-pass 0)
|
||||||
|
(define er-bank-test-fails (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-bank-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(set! er-bank-test-count (+ er-bank-test-count 1))
|
||||||
|
(if
|
||||||
|
(= actual expected)
|
||||||
|
(set! er-bank-test-pass (+ er-bank-test-pass 1))
|
||||||
|
(append! er-bank-test-fails {:actual actual :expected expected :name name}))))
|
||||||
|
|
||||||
|
(define bank-ev erlang-eval-ast)
|
||||||
|
|
||||||
|
;; Server fun shared by all tests — threaded via the program string.
|
||||||
|
(define
|
||||||
|
er-bank-server-src
|
||||||
|
"Server = fun (Balance) ->
|
||||||
|
receive
|
||||||
|
{deposit, Amt, From} -> From ! ok, Server(Balance + Amt);
|
||||||
|
{withdraw, Amt, From} ->
|
||||||
|
if Amt > Balance -> From ! insufficient, Server(Balance);
|
||||||
|
true -> From ! ok, Server(Balance - Amt)
|
||||||
|
end;
|
||||||
|
{balance, From} -> From ! Balance, Server(Balance);
|
||||||
|
stop -> ok
|
||||||
|
end
|
||||||
|
end")
|
||||||
|
|
||||||
|
;; Open account, deposit, check balance.
|
||||||
|
(er-bank-test
|
||||||
|
"deposit 100 -> balance 100"
|
||||||
|
(bank-ev
|
||||||
|
(str
|
||||||
|
er-bank-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Bank = spawn(fun () -> Server(0) end),
|
||||||
|
Bank ! {deposit, 100, Me},
|
||||||
|
receive ok -> ok end,
|
||||||
|
Bank ! {balance, Me},
|
||||||
|
receive B -> Bank ! stop, B end"))
|
||||||
|
100)
|
||||||
|
|
||||||
|
;; Multiple deposits accumulate.
|
||||||
|
(er-bank-test
|
||||||
|
"deposits accumulate"
|
||||||
|
(bank-ev
|
||||||
|
(str
|
||||||
|
er-bank-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Bank = spawn(fun () -> Server(0) end),
|
||||||
|
Bank ! {deposit, 50, Me}, receive ok -> ok end,
|
||||||
|
Bank ! {deposit, 25, Me}, receive ok -> ok end,
|
||||||
|
Bank ! {deposit, 10, Me}, receive ok -> ok end,
|
||||||
|
Bank ! {balance, Me},
|
||||||
|
receive B -> Bank ! stop, B end"))
|
||||||
|
85)
|
||||||
|
|
||||||
|
;; Withdraw within balance succeeds; insufficient gets rejected.
|
||||||
|
(er-bank-test
|
||||||
|
"withdraw within balance"
|
||||||
|
(bank-ev
|
||||||
|
(str
|
||||||
|
er-bank-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Bank = spawn(fun () -> Server(100) end),
|
||||||
|
Bank ! {withdraw, 30, Me}, receive ok -> ok end,
|
||||||
|
Bank ! {balance, Me},
|
||||||
|
receive B -> Bank ! stop, B end"))
|
||||||
|
70)
|
||||||
|
|
||||||
|
(er-bank-test
|
||||||
|
"withdraw insufficient"
|
||||||
|
(get
|
||||||
|
(bank-ev
|
||||||
|
(str
|
||||||
|
er-bank-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Bank = spawn(fun () -> Server(20) end),
|
||||||
|
Bank ! {withdraw, 100, Me},
|
||||||
|
receive R -> Bank ! stop, R end"))
|
||||||
|
:name)
|
||||||
|
"insufficient")
|
||||||
|
|
||||||
|
;; State preserved across an insufficient withdrawal.
|
||||||
|
(er-bank-test
|
||||||
|
"state preserved on rejection"
|
||||||
|
(bank-ev
|
||||||
|
(str
|
||||||
|
er-bank-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Bank = spawn(fun () -> Server(50) end),
|
||||||
|
Bank ! {withdraw, 1000, Me}, receive _ -> ok end,
|
||||||
|
Bank ! {balance, Me},
|
||||||
|
receive B -> Bank ! stop, B end"))
|
||||||
|
50)
|
||||||
|
|
||||||
|
;; Mixed deposits and withdrawals.
|
||||||
|
(er-bank-test
|
||||||
|
"mixed transactions"
|
||||||
|
(bank-ev
|
||||||
|
(str
|
||||||
|
er-bank-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Bank = spawn(fun () -> Server(100) end),
|
||||||
|
Bank ! {deposit, 50, Me}, receive ok -> ok end,
|
||||||
|
Bank ! {withdraw, 30, Me}, receive ok -> ok end,
|
||||||
|
Bank ! {deposit, 10, Me}, receive ok -> ok end,
|
||||||
|
Bank ! {withdraw, 5, Me}, receive ok -> ok end,
|
||||||
|
Bank ! {balance, Me},
|
||||||
|
receive B -> Bank ! stop, B end"))
|
||||||
|
125)
|
||||||
|
|
||||||
|
;; Server.stop terminates the bank cleanly — main can verify by
|
||||||
|
;; sending stop and then exiting normally.
|
||||||
|
(er-bank-test
|
||||||
|
"server stops cleanly"
|
||||||
|
(get
|
||||||
|
(bank-ev
|
||||||
|
(str
|
||||||
|
er-bank-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Bank = spawn(fun () -> Server(0) end),
|
||||||
|
Bank ! stop,
|
||||||
|
done"))
|
||||||
|
:name)
|
||||||
|
"done")
|
||||||
|
|
||||||
|
;; Two clients sharing one bank — interleaved transactions.
|
||||||
|
(er-bank-test
|
||||||
|
"two clients share bank"
|
||||||
|
(bank-ev
|
||||||
|
(str
|
||||||
|
er-bank-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Bank = spawn(fun () -> Server(0) end),
|
||||||
|
Client = fun (Amt) ->
|
||||||
|
spawn(fun () ->
|
||||||
|
Bank ! {deposit, Amt, self()},
|
||||||
|
receive ok -> Me ! deposited end
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
Client(40),
|
||||||
|
Client(60),
|
||||||
|
receive deposited -> ok end,
|
||||||
|
receive deposited -> ok end,
|
||||||
|
Bank ! {balance, Me},
|
||||||
|
receive B -> Bank ! stop, B end"))
|
||||||
|
100)
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-bank-test-summary
|
||||||
|
(str "bank " er-bank-test-pass "/" er-bank-test-count))
|
||||||
140
lib/erlang/tests/programs/echo.sx
Normal file
140
lib/erlang/tests/programs/echo.sx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
;; Echo server — minimal classic Erlang server. Receives {From, Msg}
|
||||||
|
;; and sends Msg back to From, then loops. `stop` ends the server.
|
||||||
|
|
||||||
|
(define er-echo-test-count 0)
|
||||||
|
(define er-echo-test-pass 0)
|
||||||
|
(define er-echo-test-fails (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-echo-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(set! er-echo-test-count (+ er-echo-test-count 1))
|
||||||
|
(if
|
||||||
|
(= actual expected)
|
||||||
|
(set! er-echo-test-pass (+ er-echo-test-pass 1))
|
||||||
|
(append! er-echo-test-fails {:actual actual :expected expected :name name}))))
|
||||||
|
|
||||||
|
(define echo-ev erlang-eval-ast)
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-echo-server-src
|
||||||
|
"EchoSrv = fun () ->
|
||||||
|
Loop = fun () ->
|
||||||
|
receive
|
||||||
|
{From, Msg} -> From ! Msg, Loop();
|
||||||
|
stop -> ok
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Loop()
|
||||||
|
end")
|
||||||
|
|
||||||
|
;; Single round-trip with an atom.
|
||||||
|
(er-echo-test
|
||||||
|
"atom round-trip"
|
||||||
|
(get
|
||||||
|
(echo-ev
|
||||||
|
(str
|
||||||
|
er-echo-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Echo = spawn(EchoSrv),
|
||||||
|
Echo ! {Me, hello},
|
||||||
|
receive R -> Echo ! stop, R end"))
|
||||||
|
:name)
|
||||||
|
"hello")
|
||||||
|
|
||||||
|
;; Number round-trip.
|
||||||
|
(er-echo-test
|
||||||
|
"number round-trip"
|
||||||
|
(echo-ev
|
||||||
|
(str
|
||||||
|
er-echo-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Echo = spawn(EchoSrv),
|
||||||
|
Echo ! {Me, 42},
|
||||||
|
receive R -> Echo ! stop, R end"))
|
||||||
|
42)
|
||||||
|
|
||||||
|
;; Tuple round-trip — pattern-match the reply to extract V.
|
||||||
|
(er-echo-test
|
||||||
|
"tuple round-trip"
|
||||||
|
(echo-ev
|
||||||
|
(str
|
||||||
|
er-echo-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Echo = spawn(EchoSrv),
|
||||||
|
Echo ! {Me, {ok, 7}},
|
||||||
|
receive {ok, V} -> Echo ! stop, V end"))
|
||||||
|
7)
|
||||||
|
|
||||||
|
;; List round-trip.
|
||||||
|
(er-echo-test
|
||||||
|
"list round-trip"
|
||||||
|
(echo-ev
|
||||||
|
(str
|
||||||
|
er-echo-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Echo = spawn(EchoSrv),
|
||||||
|
Echo ! {Me, [1, 2, 3]},
|
||||||
|
receive [H | _] -> Echo ! stop, H end"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
;; Multiple sequential round-trips.
|
||||||
|
(er-echo-test
|
||||||
|
"three round-trips"
|
||||||
|
(echo-ev
|
||||||
|
(str
|
||||||
|
er-echo-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Echo = spawn(EchoSrv),
|
||||||
|
Echo ! {Me, 10}, A = receive Ra -> Ra end,
|
||||||
|
Echo ! {Me, 20}, B = receive Rb -> Rb end,
|
||||||
|
Echo ! {Me, 30}, C = receive Rc -> Rc end,
|
||||||
|
Echo ! stop,
|
||||||
|
A + B + C"))
|
||||||
|
60)
|
||||||
|
|
||||||
|
;; Two clients sharing one echo server. Each gets its own reply.
|
||||||
|
(er-echo-test
|
||||||
|
"two clients"
|
||||||
|
(get
|
||||||
|
(echo-ev
|
||||||
|
(str
|
||||||
|
er-echo-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Echo = spawn(EchoSrv),
|
||||||
|
Client = fun (Tag) ->
|
||||||
|
spawn(fun () ->
|
||||||
|
Echo ! {self(), Tag},
|
||||||
|
receive R -> Me ! {got, R} end
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
Client(a),
|
||||||
|
Client(b),
|
||||||
|
receive {got, _} -> ok end,
|
||||||
|
receive {got, _} -> ok end,
|
||||||
|
Echo ! stop,
|
||||||
|
finished"))
|
||||||
|
:name)
|
||||||
|
"finished")
|
||||||
|
|
||||||
|
;; Echo via io trace — verify each message round-trips through.
|
||||||
|
(er-echo-test
|
||||||
|
"trace 4 messages"
|
||||||
|
(do
|
||||||
|
(er-io-flush!)
|
||||||
|
(echo-ev
|
||||||
|
(str
|
||||||
|
er-echo-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Echo = spawn(EchoSrv),
|
||||||
|
Send = fun (V) -> Echo ! {Me, V}, receive R -> io:format(\"~p \", [R]) end end,
|
||||||
|
Send(1), Send(2), Send(3), Send(4),
|
||||||
|
Echo ! stop,
|
||||||
|
done"))
|
||||||
|
(er-io-buffer-content))
|
||||||
|
"1 2 3 4 ")
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-echo-test-summary
|
||||||
|
(str "echo " er-echo-test-pass "/" er-echo-test-count))
|
||||||
152
lib/erlang/tests/programs/fib_server.sx
Normal file
152
lib/erlang/tests/programs/fib_server.sx
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
;; Fib server — long-lived process that computes fibonacci numbers on
|
||||||
|
;; request. Tests recursive function evaluation inside a server loop.
|
||||||
|
|
||||||
|
(define er-fib-test-count 0)
|
||||||
|
(define er-fib-test-pass 0)
|
||||||
|
(define er-fib-test-fails (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-fib-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(set! er-fib-test-count (+ er-fib-test-count 1))
|
||||||
|
(if
|
||||||
|
(= actual expected)
|
||||||
|
(set! er-fib-test-pass (+ er-fib-test-pass 1))
|
||||||
|
(append! er-fib-test-fails {:actual actual :expected expected :name name}))))
|
||||||
|
|
||||||
|
(define fib-ev erlang-eval-ast)
|
||||||
|
|
||||||
|
;; Fib + server-loop source. Standalone so each test can chain queries.
|
||||||
|
(define
|
||||||
|
er-fib-server-src
|
||||||
|
"Fib = fun (0) -> 0; (1) -> 1; (N) -> Fib(N-1) + Fib(N-2) end,
|
||||||
|
FibSrv = fun () ->
|
||||||
|
Loop = fun () ->
|
||||||
|
receive
|
||||||
|
{fib, N, From} -> From ! Fib(N), Loop();
|
||||||
|
stop -> ok
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Loop()
|
||||||
|
end")
|
||||||
|
|
||||||
|
;; Base cases.
|
||||||
|
(er-fib-test
|
||||||
|
"fib(0)"
|
||||||
|
(fib-ev
|
||||||
|
(str
|
||||||
|
er-fib-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Srv = spawn(FibSrv),
|
||||||
|
Srv ! {fib, 0, Me},
|
||||||
|
receive R -> Srv ! stop, R end"))
|
||||||
|
0)
|
||||||
|
|
||||||
|
(er-fib-test
|
||||||
|
"fib(1)"
|
||||||
|
(fib-ev
|
||||||
|
(str
|
||||||
|
er-fib-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Srv = spawn(FibSrv),
|
||||||
|
Srv ! {fib, 1, Me},
|
||||||
|
receive R -> Srv ! stop, R end"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
;; Larger values.
|
||||||
|
(er-fib-test
|
||||||
|
"fib(10) = 55"
|
||||||
|
(fib-ev
|
||||||
|
(str
|
||||||
|
er-fib-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Srv = spawn(FibSrv),
|
||||||
|
Srv ! {fib, 10, Me},
|
||||||
|
receive R -> Srv ! stop, R end"))
|
||||||
|
55)
|
||||||
|
|
||||||
|
(er-fib-test
|
||||||
|
"fib(15) = 610"
|
||||||
|
(fib-ev
|
||||||
|
(str
|
||||||
|
er-fib-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Srv = spawn(FibSrv),
|
||||||
|
Srv ! {fib, 15, Me},
|
||||||
|
receive R -> Srv ! stop, R end"))
|
||||||
|
610)
|
||||||
|
|
||||||
|
;; Multiple sequential queries to one server. Sum to avoid dict-equality.
|
||||||
|
(er-fib-test
|
||||||
|
"sequential fib(5..8) sum"
|
||||||
|
(fib-ev
|
||||||
|
(str
|
||||||
|
er-fib-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Srv = spawn(FibSrv),
|
||||||
|
Srv ! {fib, 5, Me}, A = receive Ra -> Ra end,
|
||||||
|
Srv ! {fib, 6, Me}, B = receive Rb -> Rb end,
|
||||||
|
Srv ! {fib, 7, Me}, C = receive Rc -> Rc end,
|
||||||
|
Srv ! {fib, 8, Me}, D = receive Rd -> Rd end,
|
||||||
|
Srv ! stop,
|
||||||
|
A + B + C + D"))
|
||||||
|
47)
|
||||||
|
|
||||||
|
;; Verify Fib obeys the recurrence — fib(n) = fib(n-1) + fib(n-2).
|
||||||
|
(er-fib-test
|
||||||
|
"fib recurrence at n=12"
|
||||||
|
(fib-ev
|
||||||
|
(str
|
||||||
|
er-fib-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Srv = spawn(FibSrv),
|
||||||
|
Srv ! {fib, 10, Me}, A = receive Ra -> Ra end,
|
||||||
|
Srv ! {fib, 11, Me}, B = receive Rb -> Rb end,
|
||||||
|
Srv ! {fib, 12, Me}, C = receive Rc -> Rc end,
|
||||||
|
Srv ! stop,
|
||||||
|
C - (A + B)"))
|
||||||
|
0)
|
||||||
|
|
||||||
|
;; Two clients each get their own answer; main sums the results.
|
||||||
|
(er-fib-test
|
||||||
|
"two clients sum"
|
||||||
|
(fib-ev
|
||||||
|
(str
|
||||||
|
er-fib-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Srv = spawn(FibSrv),
|
||||||
|
Client = fun (N) ->
|
||||||
|
spawn(fun () ->
|
||||||
|
Srv ! {fib, N, self()},
|
||||||
|
receive R -> Me ! {result, R} end
|
||||||
|
end)
|
||||||
|
end,
|
||||||
|
Client(7),
|
||||||
|
Client(9),
|
||||||
|
{result, A} = receive M1 -> M1 end,
|
||||||
|
{result, B} = receive M2 -> M2 end,
|
||||||
|
Srv ! stop,
|
||||||
|
A + B"))
|
||||||
|
47)
|
||||||
|
|
||||||
|
;; Trace queries via io-buffer.
|
||||||
|
(er-fib-test
|
||||||
|
"trace fib 0..6"
|
||||||
|
(do
|
||||||
|
(er-io-flush!)
|
||||||
|
(fib-ev
|
||||||
|
(str
|
||||||
|
er-fib-server-src
|
||||||
|
", Me = self(),
|
||||||
|
Srv = spawn(FibSrv),
|
||||||
|
Ask = fun (N) -> Srv ! {fib, N, Me}, receive R -> io:format(\"~p \", [R]) end end,
|
||||||
|
Ask(0), Ask(1), Ask(2), Ask(3), Ask(4), Ask(5), Ask(6),
|
||||||
|
Srv ! stop,
|
||||||
|
done"))
|
||||||
|
(er-io-buffer-content))
|
||||||
|
"0 1 1 2 3 5 8 ")
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-fib-test-summary
|
||||||
|
(str "fib " er-fib-test-pass "/" er-fib-test-count))
|
||||||
127
lib/erlang/tests/programs/ping_pong.sx
Normal file
127
lib/erlang/tests/programs/ping_pong.sx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
;; Ping-pong program — two processes exchange N messages, then signal
|
||||||
|
;; main via separate `ping_done` / `pong_done` notifications.
|
||||||
|
|
||||||
|
(define er-pp-test-count 0)
|
||||||
|
(define er-pp-test-pass 0)
|
||||||
|
(define er-pp-test-fails (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-pp-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(set! er-pp-test-count (+ er-pp-test-count 1))
|
||||||
|
(if
|
||||||
|
(= actual expected)
|
||||||
|
(set! er-pp-test-pass (+ er-pp-test-pass 1))
|
||||||
|
(append! er-pp-test-fails {:actual actual :expected expected :name name}))))
|
||||||
|
|
||||||
|
(define pp-ev erlang-eval-ast)
|
||||||
|
|
||||||
|
;; Three rounds of ping-pong, then stop. Main receives ping_done and
|
||||||
|
;; pong_done in arrival order (Ping finishes first because Pong exits
|
||||||
|
;; only after receiving stop).
|
||||||
|
(define
|
||||||
|
er-pp-program
|
||||||
|
"Me = self(),
|
||||||
|
Pong = spawn(fun () ->
|
||||||
|
Loop = fun () ->
|
||||||
|
receive
|
||||||
|
{ping, From} -> From ! pong, Loop();
|
||||||
|
stop -> Me ! pong_done
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Loop()
|
||||||
|
end),
|
||||||
|
Ping = fun (Target, K) ->
|
||||||
|
if K =:= 0 -> Target ! stop, Me ! ping_done;
|
||||||
|
true -> Target ! {ping, self()}, receive pong -> Ping(Target, K - 1) end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
spawn(fun () -> Ping(Pong, 3) end),
|
||||||
|
receive ping_done -> ok end,
|
||||||
|
receive pong_done -> both_done end")
|
||||||
|
|
||||||
|
(er-pp-test
|
||||||
|
"ping-pong 3 rounds"
|
||||||
|
(get (pp-ev er-pp-program) :name)
|
||||||
|
"both_done")
|
||||||
|
|
||||||
|
;; Count exchanges via io-buffer — each pong trip prints "p".
|
||||||
|
(er-pp-test
|
||||||
|
"ping-pong 5 rounds trace"
|
||||||
|
(do
|
||||||
|
(er-io-flush!)
|
||||||
|
(pp-ev
|
||||||
|
"Me = self(),
|
||||||
|
Pong = spawn(fun () ->
|
||||||
|
Loop = fun () ->
|
||||||
|
receive
|
||||||
|
{ping, From} -> io:format(\"p\"), From ! pong, Loop();
|
||||||
|
stop -> Me ! pong_done
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Loop()
|
||||||
|
end),
|
||||||
|
Ping = fun (Target, K) ->
|
||||||
|
if K =:= 0 -> Target ! stop, Me ! ping_done;
|
||||||
|
true -> Target ! {ping, self()}, receive pong -> Ping(Target, K - 1) end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
spawn(fun () -> Ping(Pong, 5) end),
|
||||||
|
receive ping_done -> ok end,
|
||||||
|
receive pong_done -> ok end")
|
||||||
|
(er-io-buffer-content))
|
||||||
|
"ppppp")
|
||||||
|
|
||||||
|
;; Main → Pong directly (no Ping process). Main plays the ping role.
|
||||||
|
(er-pp-test
|
||||||
|
"main-as-pinger 4 rounds"
|
||||||
|
(pp-ev
|
||||||
|
"Me = self(),
|
||||||
|
Pong = spawn(fun () ->
|
||||||
|
Loop = fun () ->
|
||||||
|
receive
|
||||||
|
{ping, From} -> From ! pong, Loop();
|
||||||
|
stop -> ok
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Loop()
|
||||||
|
end),
|
||||||
|
Go = fun (K) ->
|
||||||
|
if K =:= 0 -> Pong ! stop, K;
|
||||||
|
true -> Pong ! {ping, Me}, receive pong -> Go(K - 1) end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Go(4)")
|
||||||
|
0)
|
||||||
|
|
||||||
|
;; Ensure the processes really interleave — inject an id into each
|
||||||
|
;; ping and check we get them all back via trace (the order is
|
||||||
|
;; deterministic under our sync scheduler).
|
||||||
|
(er-pp-test
|
||||||
|
"ids round-trip"
|
||||||
|
(do
|
||||||
|
(er-io-flush!)
|
||||||
|
(pp-ev
|
||||||
|
"Me = self(),
|
||||||
|
Pong = spawn(fun () ->
|
||||||
|
Loop = fun () ->
|
||||||
|
receive
|
||||||
|
{ping, From, Id} -> From ! {pong, Id}, Loop();
|
||||||
|
stop -> ok
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Loop()
|
||||||
|
end),
|
||||||
|
Go = fun (K) ->
|
||||||
|
if K =:= 0 -> Pong ! stop, done;
|
||||||
|
true -> Pong ! {ping, Me, K}, receive {pong, RId} -> io:format(\"~p \", [RId]), Go(K - 1) end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Go(4)")
|
||||||
|
(er-io-buffer-content))
|
||||||
|
"4 3 2 1 ")
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-pp-test-summary
|
||||||
|
(str "ping-pong " er-pp-test-pass "/" er-pp-test-count))
|
||||||
132
lib/erlang/tests/programs/ring.sx
Normal file
132
lib/erlang/tests/programs/ring.sx
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
;; Ring program — N processes in a ring, token passes M times.
|
||||||
|
;;
|
||||||
|
;; Each process waits for {setup, Next} so main can tie the knot
|
||||||
|
;; (can't reference a pid before spawning it). Once wired, main
|
||||||
|
;; injects the first token; each process forwards decrementing K
|
||||||
|
;; until it hits 0, at which point it signals `done` to main.
|
||||||
|
|
||||||
|
(define er-ring-test-count 0)
|
||||||
|
(define er-ring-test-pass 0)
|
||||||
|
(define er-ring-test-fails (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-ring-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(set! er-ring-test-count (+ er-ring-test-count 1))
|
||||||
|
(if
|
||||||
|
(= actual expected)
|
||||||
|
(set! er-ring-test-pass (+ er-ring-test-pass 1))
|
||||||
|
(append! er-ring-test-fails {:actual actual :expected expected :name name}))))
|
||||||
|
|
||||||
|
(define ring-ev erlang-eval-ast)
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-ring-program-3-6
|
||||||
|
"Me = self(),
|
||||||
|
Spawner = fun () ->
|
||||||
|
receive {setup, Next} ->
|
||||||
|
Loop = fun () ->
|
||||||
|
receive
|
||||||
|
{token, 0, Parent} -> Parent ! done;
|
||||||
|
{token, K, Parent} -> Next ! {token, K-1, Parent}, Loop()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Loop()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
P1 = spawn(Spawner),
|
||||||
|
P2 = spawn(Spawner),
|
||||||
|
P3 = spawn(Spawner),
|
||||||
|
P1 ! {setup, P2},
|
||||||
|
P2 ! {setup, P3},
|
||||||
|
P3 ! {setup, P1},
|
||||||
|
P1 ! {token, 5, Me},
|
||||||
|
receive done -> finished end")
|
||||||
|
|
||||||
|
(er-ring-test
|
||||||
|
"ring N=3 M=6"
|
||||||
|
(get (ring-ev er-ring-program-3-6) :name)
|
||||||
|
"finished")
|
||||||
|
|
||||||
|
;; Two-node ring — token bounces twice between P1 and P2.
|
||||||
|
(er-ring-test
|
||||||
|
"ring N=2 M=4"
|
||||||
|
(get (ring-ev
|
||||||
|
"Me = self(),
|
||||||
|
Spawner = fun () ->
|
||||||
|
receive {setup, Next} ->
|
||||||
|
Loop = fun () ->
|
||||||
|
receive
|
||||||
|
{token, 0, Parent} -> Parent ! done;
|
||||||
|
{token, K, Parent} -> Next ! {token, K-1, Parent}, Loop()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Loop()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
P1 = spawn(Spawner),
|
||||||
|
P2 = spawn(Spawner),
|
||||||
|
P1 ! {setup, P2},
|
||||||
|
P2 ! {setup, P1},
|
||||||
|
P1 ! {token, 3, Me},
|
||||||
|
receive done -> done end") :name)
|
||||||
|
"done")
|
||||||
|
|
||||||
|
;; Single-node "ring" — P sends to itself M times.
|
||||||
|
(er-ring-test
|
||||||
|
"ring N=1 M=5"
|
||||||
|
(get (ring-ev
|
||||||
|
"Me = self(),
|
||||||
|
Spawner = fun () ->
|
||||||
|
receive {setup, Next} ->
|
||||||
|
Loop = fun () ->
|
||||||
|
receive
|
||||||
|
{token, 0, Parent} -> Parent ! finished_loop;
|
||||||
|
{token, K, Parent} -> Next ! {token, K-1, Parent}, Loop()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Loop()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
P = spawn(Spawner),
|
||||||
|
P ! {setup, P},
|
||||||
|
P ! {token, 4, Me},
|
||||||
|
receive finished_loop -> ok end") :name)
|
||||||
|
"ok")
|
||||||
|
|
||||||
|
;; Confirm the token really went around — count hops via io-buffer.
|
||||||
|
(er-ring-test
|
||||||
|
"ring N=3 M=9 hop count"
|
||||||
|
(do
|
||||||
|
(er-io-flush!)
|
||||||
|
(ring-ev
|
||||||
|
"Me = self(),
|
||||||
|
Spawner = fun () ->
|
||||||
|
receive {setup, Next} ->
|
||||||
|
Loop = fun () ->
|
||||||
|
receive
|
||||||
|
{token, 0, Parent} -> Parent ! done;
|
||||||
|
{token, K, Parent} ->
|
||||||
|
io:format(\"~p \", [K]),
|
||||||
|
Next ! {token, K-1, Parent},
|
||||||
|
Loop()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
Loop()
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
P1 = spawn(Spawner),
|
||||||
|
P2 = spawn(Spawner),
|
||||||
|
P3 = spawn(Spawner),
|
||||||
|
P1 ! {setup, P2},
|
||||||
|
P2 ! {setup, P3},
|
||||||
|
P3 ! {setup, P1},
|
||||||
|
P1 ! {token, 8, Me},
|
||||||
|
receive done -> done end")
|
||||||
|
(er-io-buffer-content))
|
||||||
|
"8 7 6 5 4 3 2 1 ")
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-ring-test-summary
|
||||||
|
(str "ring " er-ring-test-pass "/" er-ring-test-count))
|
||||||
139
lib/erlang/tests/runtime.sx
Normal file
139
lib/erlang/tests/runtime.sx
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
;; Erlang runtime tests — scheduler + process-record primitives.
|
||||||
|
|
||||||
|
(define er-rt-test-count 0)
|
||||||
|
(define er-rt-test-pass 0)
|
||||||
|
(define er-rt-test-fails (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-rt-test
|
||||||
|
(fn
|
||||||
|
(name actual expected)
|
||||||
|
(set! er-rt-test-count (+ er-rt-test-count 1))
|
||||||
|
(if
|
||||||
|
(= actual expected)
|
||||||
|
(set! er-rt-test-pass (+ er-rt-test-pass 1))
|
||||||
|
(append! er-rt-test-fails {:actual actual :expected expected :name name}))))
|
||||||
|
|
||||||
|
;; ── queue ─────────────────────────────────────────────────────────
|
||||||
|
(er-rt-test "queue empty len" (er-q-len (er-q-new)) 0)
|
||||||
|
(er-rt-test "queue empty?" (er-q-empty? (er-q-new)) true)
|
||||||
|
|
||||||
|
(define q1 (er-q-new))
|
||||||
|
(er-q-push! q1 "a")
|
||||||
|
(er-q-push! q1 "b")
|
||||||
|
(er-q-push! q1 "c")
|
||||||
|
(er-rt-test "queue push len" (er-q-len q1) 3)
|
||||||
|
(er-rt-test "queue empty? after push" (er-q-empty? q1) false)
|
||||||
|
(er-rt-test "queue peek" (er-q-peek q1) "a")
|
||||||
|
(er-rt-test "queue pop 1" (er-q-pop! q1) "a")
|
||||||
|
(er-rt-test "queue pop 2" (er-q-pop! q1) "b")
|
||||||
|
(er-rt-test "queue len after pops" (er-q-len q1) 1)
|
||||||
|
(er-rt-test "queue pop 3" (er-q-pop! q1) "c")
|
||||||
|
(er-rt-test "queue empty again" (er-q-empty? q1) true)
|
||||||
|
(er-rt-test "queue pop empty" (er-q-pop! q1) nil)
|
||||||
|
|
||||||
|
;; Queue FIFO under interleaved push/pop
|
||||||
|
(define q2 (er-q-new))
|
||||||
|
(er-q-push! q2 1)
|
||||||
|
(er-q-push! q2 2)
|
||||||
|
(er-q-pop! q2)
|
||||||
|
(er-q-push! q2 3)
|
||||||
|
(er-rt-test "queue interleave peek" (er-q-peek q2) 2)
|
||||||
|
(er-rt-test "queue to-list" (er-q-to-list q2) (list 2 3))
|
||||||
|
|
||||||
|
;; ── scheduler init ─────────────────────────────────────────────
|
||||||
|
(er-sched-init!)
|
||||||
|
(er-rt-test "sched process count 0" (er-sched-process-count) 0)
|
||||||
|
(er-rt-test "sched runnable count 0" (er-sched-runnable-count) 0)
|
||||||
|
(er-rt-test "sched current nil" (er-sched-current-pid) nil)
|
||||||
|
|
||||||
|
;; ── pid allocation ─────────────────────────────────────────────
|
||||||
|
(define pa (er-pid-new!))
|
||||||
|
(define pb (er-pid-new!))
|
||||||
|
(er-rt-test "pid tag" (get pa :tag) "pid")
|
||||||
|
(er-rt-test "pid ids distinct" (= (er-pid-id pa) (er-pid-id pb)) false)
|
||||||
|
(er-rt-test "pid? true" (er-pid? pa) true)
|
||||||
|
(er-rt-test "pid? false" (er-pid? 42) false)
|
||||||
|
(er-rt-test
|
||||||
|
"pid-equal same"
|
||||||
|
(er-pid-equal? pa (er-mk-pid (er-pid-id pa)))
|
||||||
|
true)
|
||||||
|
(er-rt-test "pid-equal diff" (er-pid-equal? pa pb) false)
|
||||||
|
|
||||||
|
;; ── process lifecycle ──────────────────────────────────────────
|
||||||
|
(er-sched-init!)
|
||||||
|
(define p1 (er-proc-new! {}))
|
||||||
|
(define p2 (er-proc-new! {}))
|
||||||
|
(er-rt-test "proc count 2" (er-sched-process-count) 2)
|
||||||
|
(er-rt-test "runnable count 2" (er-sched-runnable-count) 2)
|
||||||
|
(er-rt-test
|
||||||
|
"proc state runnable"
|
||||||
|
(er-proc-field (get p1 :pid) :state)
|
||||||
|
"runnable")
|
||||||
|
(er-rt-test
|
||||||
|
"proc mailbox empty"
|
||||||
|
(er-proc-mailbox-size (get p1 :pid))
|
||||||
|
0)
|
||||||
|
(er-rt-test
|
||||||
|
"proc lookup"
|
||||||
|
(er-pid-equal? (get (er-proc-get (get p1 :pid)) :pid) (get p1 :pid))
|
||||||
|
true)
|
||||||
|
(er-rt-test "proc exists" (er-proc-exists? (get p1 :pid)) true)
|
||||||
|
(er-rt-test
|
||||||
|
"proc no-such-pid"
|
||||||
|
(er-proc-exists? (er-mk-pid 9999))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; runnable queue dequeue order
|
||||||
|
(er-rt-test
|
||||||
|
"dequeue first"
|
||||||
|
(er-pid-equal? (er-sched-next-runnable!) (get p1 :pid))
|
||||||
|
true)
|
||||||
|
(er-rt-test
|
||||||
|
"dequeue second"
|
||||||
|
(er-pid-equal? (er-sched-next-runnable!) (get p2 :pid))
|
||||||
|
true)
|
||||||
|
(er-rt-test "dequeue empty" (er-sched-next-runnable!) nil)
|
||||||
|
|
||||||
|
;; current-pid get/set
|
||||||
|
(er-sched-set-current! (get p1 :pid))
|
||||||
|
(er-rt-test
|
||||||
|
"current pid set"
|
||||||
|
(er-pid-equal? (er-sched-current-pid) (get p1 :pid))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── mailbox push ──────────────────────────────────────────────
|
||||||
|
(er-proc-mailbox-push! (get p1 :pid) {:tag "atom" :name "ping"})
|
||||||
|
(er-proc-mailbox-push! (get p1 :pid) 42)
|
||||||
|
(er-rt-test "mailbox size 2" (er-proc-mailbox-size (get p1 :pid)) 2)
|
||||||
|
|
||||||
|
;; ── field update ──────────────────────────────────────────────
|
||||||
|
(er-proc-set! (get p1 :pid) :state "waiting")
|
||||||
|
(er-rt-test
|
||||||
|
"proc state waiting"
|
||||||
|
(er-proc-field (get p1 :pid) :state)
|
||||||
|
"waiting")
|
||||||
|
(er-proc-set! (get p1 :pid) :trap-exit true)
|
||||||
|
(er-rt-test
|
||||||
|
"proc trap-exit"
|
||||||
|
(er-proc-field (get p1 :pid) :trap-exit)
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── fresh scheduler ends in clean state ───────────────────────
|
||||||
|
(er-sched-init!)
|
||||||
|
(er-rt-test
|
||||||
|
"sched init resets count"
|
||||||
|
(er-sched-process-count)
|
||||||
|
0)
|
||||||
|
(er-rt-test
|
||||||
|
"sched init resets queue"
|
||||||
|
(er-sched-runnable-count)
|
||||||
|
0)
|
||||||
|
(er-rt-test
|
||||||
|
"sched init resets current"
|
||||||
|
(er-sched-current-pid)
|
||||||
|
nil)
|
||||||
|
|
||||||
|
(define
|
||||||
|
er-rt-test-summary
|
||||||
|
(str "runtime " er-rt-test-pass "/" er-rt-test-count))
|
||||||
1913
lib/erlang/transpile.sx
Normal file
1913
lib/erlang/transpile.sx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -48,6 +48,15 @@
|
|||||||
prop
|
prop
|
||||||
value))
|
value))
|
||||||
(list (quote hs-query-all) (nth base-ast 1))))
|
(list (quote hs-query-all) (nth base-ast 1))))
|
||||||
|
((and (list? base-ast) (= (first base-ast) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote dom-set-prop)
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth base-ast 1)
|
||||||
|
(list (quote hs-query-first) (nth base-ast 1)))
|
||||||
|
prop
|
||||||
|
value))
|
||||||
((and (list? base-ast) (= (first base-ast) dot-sym) (let ((inner (nth base-ast 1))) (and (list? inner) (= (first inner) (quote query)) (let ((s (nth inner 1))) (and (string? s) (> (len s) 0) (= (substring s 0 1) "."))))))
|
((and (list? base-ast) (= (first base-ast) dot-sym) (let ((inner (nth base-ast 1))) (and (list? inner) (= (first inner) (quote query)) (let ((s (nth inner 1))) (and (string? s) (> (len s) 0) (= (substring s 0 1) "."))))))
|
||||||
(let
|
(let
|
||||||
((inner (nth base-ast 1))
|
((inner (nth base-ast 1))
|
||||||
@@ -146,6 +155,14 @@
|
|||||||
(nth prop-ast 1)
|
(nth prop-ast 1)
|
||||||
value)
|
value)
|
||||||
(list (quote set!) (hs-to-sx target) value))))))
|
(list (quote set!) (hs-to-sx target) value))))))
|
||||||
|
((= th (quote query))
|
||||||
|
(list
|
||||||
|
(quote hs-set-inner-html!)
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth target 1)
|
||||||
|
(list (quote hs-query-first) (nth target 1)))
|
||||||
|
value))
|
||||||
(true (list (quote set!) (hs-to-sx target) value)))))))
|
(true (list (quote set!) (hs-to-sx target) value)))))))
|
||||||
(define
|
(define
|
||||||
emit-on
|
emit-on
|
||||||
@@ -274,17 +291,33 @@
|
|||||||
((name (nth ast 1)) (rest-parts (rest (rest ast))))
|
((name (nth ast 1)) (rest-parts (rest (rest ast))))
|
||||||
(cond
|
(cond
|
||||||
((and (= (len ast) 4) (list? (nth ast 2)) (= (first (nth ast 2)) (quote dict)))
|
((and (= (len ast) 4) (list? (nth ast 2)) (= (first (nth ast 2)) (quote dict)))
|
||||||
|
(let
|
||||||
|
((tgt-ast (nth ast 3)))
|
||||||
(list
|
(list
|
||||||
(quote dom-dispatch)
|
(quote dom-dispatch)
|
||||||
(hs-to-sx (nth ast 3))
|
(if
|
||||||
|
(and (list? tgt-ast) (= (first tgt-ast) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth tgt-ast 1)
|
||||||
|
(list (quote hs-query-first) (nth tgt-ast 1)))
|
||||||
|
(hs-to-sx tgt-ast))
|
||||||
name
|
name
|
||||||
(hs-to-sx (nth ast 2))))
|
(hs-to-sx (nth ast 2)))))
|
||||||
((= (len ast) 3)
|
((= (len ast) 3)
|
||||||
|
(let
|
||||||
|
((tgt-ast (nth ast 2)))
|
||||||
(list
|
(list
|
||||||
(quote dom-dispatch)
|
(quote dom-dispatch)
|
||||||
(hs-to-sx (nth ast 2))
|
(if
|
||||||
|
(and (list? tgt-ast) (= (first tgt-ast) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth tgt-ast 1)
|
||||||
|
(list (quote hs-query-first) (nth tgt-ast 1)))
|
||||||
|
(hs-to-sx tgt-ast))
|
||||||
name
|
name
|
||||||
(list (quote dict) "sender" (quote me))))
|
(list (quote dict) "sender" (quote me)))))
|
||||||
(true
|
(true
|
||||||
(list
|
(list
|
||||||
(quote dom-dispatch)
|
(quote dom-dispatch)
|
||||||
@@ -706,6 +739,33 @@
|
|||||||
(quote fn)
|
(quote fn)
|
||||||
(cons (quote me) (map make-symbol params))
|
(cons (quote me) (map make-symbol params))
|
||||||
(cons (quote do) (map hs-to-sx body)))))))
|
(cons (quote do) (map hs-to-sx body)))))))
|
||||||
|
(define
|
||||||
|
hs-safe-obj
|
||||||
|
(fn
|
||||||
|
(obj-ast)
|
||||||
|
(if
|
||||||
|
(and (list? obj-ast) (= (first obj-ast) (quote ref)))
|
||||||
|
(list (quote host-global) (nth obj-ast 1))
|
||||||
|
(if
|
||||||
|
(and (list? obj-ast) (= (first obj-ast) dot-sym))
|
||||||
|
(let
|
||||||
|
((inner (nth obj-ast 1)) (prop (nth obj-ast 2)))
|
||||||
|
(list (quote host-get) (hs-safe-obj inner) prop))
|
||||||
|
(hs-to-sx obj-ast)))))
|
||||||
|
(define
|
||||||
|
hs-chain-name
|
||||||
|
(fn
|
||||||
|
(obj-ast)
|
||||||
|
(if
|
||||||
|
(and (list? obj-ast) (= (first obj-ast) (quote ref)))
|
||||||
|
(nth obj-ast 1)
|
||||||
|
(if
|
||||||
|
(and (list? obj-ast) (= (first obj-ast) dot-sym))
|
||||||
|
(str (hs-chain-name (nth obj-ast 1)) "." (nth obj-ast 2))
|
||||||
|
(if
|
||||||
|
(and (list? obj-ast) (= (first obj-ast) (quote query)))
|
||||||
|
(nth obj-ast 1)
|
||||||
|
nil)))))
|
||||||
(fn
|
(fn
|
||||||
(ast)
|
(ast)
|
||||||
(cond
|
(cond
|
||||||
@@ -1225,13 +1285,22 @@
|
|||||||
((raw-tgt (nth ast 2)))
|
((raw-tgt (nth ast 2)))
|
||||||
(if
|
(if
|
||||||
(and (list? raw-tgt) (= (first raw-tgt) (quote query)))
|
(and (list? raw-tgt) (= (first raw-tgt) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote let)
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
(quote _tgt)
|
||||||
|
(list (quote hs-query-named-all) (nth raw-tgt 1))))
|
||||||
(list
|
(list
|
||||||
(quote for-each)
|
(quote for-each)
|
||||||
(list
|
(list
|
||||||
(quote fn)
|
(quote fn)
|
||||||
(list (quote _el))
|
(list (quote _el))
|
||||||
(list (quote dom-add-class) (quote _el) (nth ast 1)))
|
(list
|
||||||
(list (quote hs-query-all) (nth raw-tgt 1)))
|
(quote dom-add-class)
|
||||||
|
(quote _el)
|
||||||
|
(nth ast 1)))
|
||||||
|
(quote _tgt)))
|
||||||
(list
|
(list
|
||||||
(quote dom-add-class)
|
(quote dom-add-class)
|
||||||
(hs-to-sx raw-tgt)
|
(hs-to-sx raw-tgt)
|
||||||
@@ -1244,14 +1313,20 @@
|
|||||||
(nth ast 2)))
|
(nth ast 2)))
|
||||||
((= head (quote set-styles))
|
((= head (quote set-styles))
|
||||||
(let
|
(let
|
||||||
((pairs (nth ast 1)) (tgt (hs-to-sx (nth ast 2))))
|
((pairs (nth ast 1)) (tgt-ast (nth ast 2)))
|
||||||
|
(let
|
||||||
|
((tgt (if (and (list? tgt-ast) (= (first tgt-ast) (quote query))) (list (quote hs-named-target) (nth tgt-ast 1) (list (quote hs-query-first) (nth tgt-ast 1))) (hs-to-sx tgt-ast))))
|
||||||
(cons
|
(cons
|
||||||
(quote do)
|
(quote do)
|
||||||
(map
|
(map
|
||||||
(fn
|
(fn
|
||||||
(p)
|
(p)
|
||||||
(list (quote dom-set-style) tgt (first p) (nth p 1)))
|
(list
|
||||||
pairs))))
|
(quote dom-set-style)
|
||||||
|
tgt
|
||||||
|
(first p)
|
||||||
|
(nth p 1)))
|
||||||
|
pairs)))))
|
||||||
((= head (quote multi-add-class))
|
((= head (quote multi-add-class))
|
||||||
(let
|
(let
|
||||||
((target (hs-to-sx (nth ast 1)))
|
((target (hs-to-sx (nth ast 1)))
|
||||||
@@ -1348,6 +1423,12 @@
|
|||||||
((raw-tgt (nth ast 2)))
|
((raw-tgt (nth ast 2)))
|
||||||
(if
|
(if
|
||||||
(and (list? raw-tgt) (= (first raw-tgt) (quote query)))
|
(and (list? raw-tgt) (= (first raw-tgt) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote let)
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
(quote _tgt)
|
||||||
|
(list (quote hs-query-named-all) (nth raw-tgt 1))))
|
||||||
(list
|
(list
|
||||||
(quote for-each)
|
(quote for-each)
|
||||||
(list
|
(list
|
||||||
@@ -1357,7 +1438,7 @@
|
|||||||
(quote dom-remove-class)
|
(quote dom-remove-class)
|
||||||
(quote _el)
|
(quote _el)
|
||||||
(nth ast 1)))
|
(nth ast 1)))
|
||||||
(list (quote hs-query-all) (nth raw-tgt 1)))
|
(quote _tgt)))
|
||||||
(list
|
(list
|
||||||
(quote dom-remove-class)
|
(quote dom-remove-class)
|
||||||
(if (nil? raw-tgt) (quote me) (hs-to-sx raw-tgt))
|
(if (nil? raw-tgt) (quote me) (hs-to-sx raw-tgt))
|
||||||
@@ -1401,15 +1482,32 @@
|
|||||||
((tgt (nth ast 3)))
|
((tgt (nth ast 3)))
|
||||||
(list
|
(list
|
||||||
(quote hs-set-attr!)
|
(quote hs-set-attr!)
|
||||||
(hs-to-sx tgt)
|
(if
|
||||||
|
(and (list? tgt) (= (first tgt) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth tgt 1)
|
||||||
|
(list (quote hs-query-first) (nth tgt 1)))
|
||||||
|
(hs-to-sx tgt))
|
||||||
(nth ast 1)
|
(nth ast 1)
|
||||||
(hs-to-sx (nth ast 2)))))
|
(hs-to-sx (nth ast 2)))))
|
||||||
((= head (quote remove-value))
|
((= head (quote remove-value))
|
||||||
(let
|
(let
|
||||||
((val (hs-to-sx (nth ast 1))) (tgt (nth ast 2)))
|
((val (hs-to-sx (nth ast 1))) (raw-tgt (nth ast 2)))
|
||||||
(emit-set
|
(emit-set
|
||||||
tgt
|
raw-tgt
|
||||||
(list (quote hs-remove-from!) val (hs-to-sx tgt)))))
|
(list
|
||||||
|
(quote hs-remove-from!)
|
||||||
|
val
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(list? raw-tgt)
|
||||||
|
(= (first raw-tgt) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth raw-tgt 1)
|
||||||
|
(list (quote hs-query-first) (nth raw-tgt 1)))
|
||||||
|
(hs-to-sx raw-tgt))))))
|
||||||
((= head (quote empty-target))
|
((= head (quote empty-target))
|
||||||
(let
|
(let
|
||||||
((tgt (nth ast 1)))
|
((tgt (nth ast 1)))
|
||||||
@@ -1440,8 +1538,19 @@
|
|||||||
(hs-to-sx (nth ast 2))))
|
(hs-to-sx (nth ast 2))))
|
||||||
((= head (quote remove-attr))
|
((= head (quote remove-attr))
|
||||||
(let
|
(let
|
||||||
((tgt (if (nil? (nth ast 2)) (quote me) (hs-to-sx (nth ast 2)))))
|
((raw-tgt (nth ast 2)))
|
||||||
(list (quote dom-remove-attr) tgt (nth ast 1))))
|
(list
|
||||||
|
(quote dom-remove-attr)
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(list? raw-tgt)
|
||||||
|
(= (first raw-tgt) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth raw-tgt 1)
|
||||||
|
(list (quote hs-query-first) (nth raw-tgt 1)))
|
||||||
|
(if (nil? raw-tgt) (quote me) (hs-to-sx raw-tgt)))
|
||||||
|
(nth ast 1))))
|
||||||
((= head (quote remove-css))
|
((= head (quote remove-css))
|
||||||
(let
|
(let
|
||||||
((tgt (if (nil? (nth ast 2)) (quote me) (hs-to-sx (nth ast 2))))
|
((tgt (if (nil? (nth ast 2)) (quote me) (hs-to-sx (nth ast 2))))
|
||||||
@@ -1452,10 +1561,20 @@
|
|||||||
(fn (p) (list (quote dom-set-style) tgt p ""))
|
(fn (p) (list (quote dom-set-style) tgt p ""))
|
||||||
props))))
|
props))))
|
||||||
((= head (quote toggle-class))
|
((= head (quote toggle-class))
|
||||||
|
(let
|
||||||
|
((tgt-ast (nth ast 2)))
|
||||||
(list
|
(list
|
||||||
(quote hs-toggle-class!)
|
(quote hs-toggle-class!)
|
||||||
(hs-to-sx (nth ast 2))
|
(if
|
||||||
(nth ast 1)))
|
(and
|
||||||
|
(list? tgt-ast)
|
||||||
|
(= (first tgt-ast) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth tgt-ast 1)
|
||||||
|
(list (quote hs-query-first) (nth tgt-ast 1)))
|
||||||
|
(hs-to-sx tgt-ast))
|
||||||
|
(nth ast 1))))
|
||||||
((= head (quote toggle-class-for))
|
((= head (quote toggle-class-for))
|
||||||
(list
|
(list
|
||||||
(quote do)
|
(quote do)
|
||||||
@@ -1510,11 +1629,21 @@
|
|||||||
(hs-to-sx tgt-ast)
|
(hs-to-sx tgt-ast)
|
||||||
(hs-to-sx val-ast)))))
|
(hs-to-sx val-ast)))))
|
||||||
((= head (quote toggle-between))
|
((= head (quote toggle-between))
|
||||||
|
(let
|
||||||
|
((tgt-ast (nth ast 3)))
|
||||||
(list
|
(list
|
||||||
(quote hs-toggle-between!)
|
(quote hs-toggle-between!)
|
||||||
(hs-to-sx (nth ast 3))
|
(if
|
||||||
|
(and
|
||||||
|
(list? tgt-ast)
|
||||||
|
(= (first tgt-ast) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth tgt-ast 1)
|
||||||
|
(list (quote hs-query-first) (nth tgt-ast 1)))
|
||||||
|
(hs-to-sx tgt-ast))
|
||||||
(nth ast 1)
|
(nth ast 1)
|
||||||
(nth ast 2)))
|
(nth ast 2))))
|
||||||
((= head (quote toggle-style))
|
((= head (quote toggle-style))
|
||||||
(let
|
(let
|
||||||
((raw-tgt (nth ast 2)))
|
((raw-tgt (nth ast 2)))
|
||||||
@@ -1538,10 +1667,20 @@
|
|||||||
(quote list)
|
(quote list)
|
||||||
(map hs-to-sx (slice ast 3 (len ast))))))
|
(map hs-to-sx (slice ast 3 (len ast))))))
|
||||||
((= head (quote toggle-attr))
|
((= head (quote toggle-attr))
|
||||||
|
(let
|
||||||
|
((tgt-ast (nth ast 2)))
|
||||||
(list
|
(list
|
||||||
(quote hs-toggle-attr!)
|
(quote hs-toggle-attr!)
|
||||||
(hs-to-sx (nth ast 2))
|
(if
|
||||||
(nth ast 1)))
|
(and
|
||||||
|
(list? tgt-ast)
|
||||||
|
(= (first tgt-ast) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth tgt-ast 1)
|
||||||
|
(list (quote hs-query-first) (nth tgt-ast 1)))
|
||||||
|
(hs-to-sx tgt-ast))
|
||||||
|
(nth ast 1))))
|
||||||
((= head (quote toggle-attr-between))
|
((= head (quote toggle-attr-between))
|
||||||
(list
|
(list
|
||||||
(quote hs-toggle-attr-between!)
|
(quote hs-toggle-attr-between!)
|
||||||
@@ -1575,7 +1714,22 @@
|
|||||||
(emit-set
|
(emit-set
|
||||||
raw-tgt
|
raw-tgt
|
||||||
(list (quote hs-put-at!) val pos (hs-to-sx raw-tgt))))
|
(list (quote hs-put-at!) val pos (hs-to-sx raw-tgt))))
|
||||||
(true (list (quote hs-put!) val pos (hs-to-sx raw-tgt))))))
|
(true
|
||||||
|
(let
|
||||||
|
((tgt-ast raw-tgt))
|
||||||
|
(list
|
||||||
|
(quote hs-put!)
|
||||||
|
val
|
||||||
|
pos
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(list? tgt-ast)
|
||||||
|
(= (first tgt-ast) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth tgt-ast 1)
|
||||||
|
(list (quote hs-query-first) (nth tgt-ast 1)))
|
||||||
|
(hs-to-sx tgt-ast))))))))
|
||||||
((= head (quote if))
|
((= head (quote if))
|
||||||
(if
|
(if
|
||||||
(> (len ast) 3)
|
(> (len ast) 3)
|
||||||
@@ -1651,12 +1805,22 @@
|
|||||||
(detail (if (= (len ast) 4) (nth ast 2) nil)))
|
(detail (if (= (len ast) 4) (nth ast 2) nil)))
|
||||||
(list
|
(list
|
||||||
(quote dom-dispatch)
|
(quote dom-dispatch)
|
||||||
(hs-to-sx tgt)
|
(let
|
||||||
|
((tgt-ast tgt))
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(list? tgt-ast)
|
||||||
|
(= (first tgt-ast) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth tgt-ast 1)
|
||||||
|
(list (quote hs-query-first) (nth tgt-ast 1)))
|
||||||
|
(hs-to-sx tgt-ast)))
|
||||||
name
|
name
|
||||||
(if has-detail (hs-to-sx detail) nil))))
|
(if has-detail (hs-to-sx detail) nil))))
|
||||||
((= head (quote hide))
|
((= head (quote hide))
|
||||||
(let
|
(let
|
||||||
((tgt (let ((raw-tgt (nth ast 1))) (if (and (list? raw-tgt) (= (first raw-tgt) (quote query))) (list (quote hs-query-all) (nth raw-tgt 1)) (hs-to-sx raw-tgt))))
|
((tgt (let ((raw-tgt (nth ast 1))) (if (and (list? raw-tgt) (= (first raw-tgt) (quote query))) (list (quote hs-query-named-all) (nth raw-tgt 1)) (hs-to-sx raw-tgt))))
|
||||||
(strategy (if (> (len ast) 2) (nth ast 2) "display"))
|
(strategy (if (> (len ast) 2) (nth ast 2) "display"))
|
||||||
(when-cond (if (> (len ast) 3) (nth ast 3) nil)))
|
(when-cond (if (> (len ast) 3) (nth ast 3) nil)))
|
||||||
(if
|
(if
|
||||||
@@ -1672,7 +1836,7 @@
|
|||||||
(hs-to-sx when-cond))))))
|
(hs-to-sx when-cond))))))
|
||||||
((= head (quote show))
|
((= head (quote show))
|
||||||
(let
|
(let
|
||||||
((tgt (let ((raw-tgt (nth ast 1))) (if (and (list? raw-tgt) (= (first raw-tgt) (quote query))) (list (quote hs-query-all) (nth raw-tgt 1)) (hs-to-sx raw-tgt))))
|
((tgt (let ((raw-tgt (nth ast 1))) (if (and (list? raw-tgt) (= (first raw-tgt) (quote query))) (list (quote hs-query-named-all) (nth raw-tgt 1)) (hs-to-sx raw-tgt))))
|
||||||
(strategy (if (> (len ast) 2) (nth ast 2) "display"))
|
(strategy (if (> (len ast) 2) (nth ast 2) "display"))
|
||||||
(when-cond (if (> (len ast) 3) (nth ast 3) nil)))
|
(when-cond (if (> (len ast) 3) (nth ast 3) nil)))
|
||||||
(if
|
(if
|
||||||
@@ -1735,13 +1899,28 @@
|
|||||||
((= head (quote call))
|
((= head (quote call))
|
||||||
(let
|
(let
|
||||||
((raw-fn (nth ast 1))
|
((raw-fn (nth ast 1))
|
||||||
(fn-expr
|
|
||||||
(if
|
|
||||||
(string? raw-fn)
|
|
||||||
(make-symbol raw-fn)
|
|
||||||
(hs-to-sx raw-fn)))
|
|
||||||
(args (map hs-to-sx (rest (rest ast)))))
|
(args (map hs-to-sx (rest (rest ast)))))
|
||||||
(cons fn-expr args)))
|
(if
|
||||||
|
(and (list? raw-fn) (= (first raw-fn) (quote ref)))
|
||||||
|
(let
|
||||||
|
((name (nth raw-fn 1)))
|
||||||
|
(list
|
||||||
|
(quote let)
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
(quote __hs-fn)
|
||||||
|
(list (quote host-global) name)))
|
||||||
|
(cons
|
||||||
|
(quote do)
|
||||||
|
(list
|
||||||
|
(list
|
||||||
|
(quote if)
|
||||||
|
(list (quote nil?) (quote __hs-fn))
|
||||||
|
(list (quote raise) (str "'" name "' is null"))
|
||||||
|
(cons (quote __hs-fn) args))))))
|
||||||
|
(let
|
||||||
|
((fn-expr (if (string? raw-fn) (make-symbol raw-fn) (hs-to-sx raw-fn))))
|
||||||
|
(cons fn-expr args)))))
|
||||||
((= head (quote return))
|
((= head (quote return))
|
||||||
(let
|
(let
|
||||||
((val (nth ast 1)))
|
((val (nth ast 1)))
|
||||||
@@ -1754,7 +1933,22 @@
|
|||||||
((= head (quote throw))
|
((= head (quote throw))
|
||||||
(list (quote raise) (hs-to-sx (nth ast 1))))
|
(list (quote raise) (hs-to-sx (nth ast 1))))
|
||||||
((= head (quote settle))
|
((= head (quote settle))
|
||||||
(list (quote hs-settle) (quote me)))
|
(let
|
||||||
|
((raw-tgt (nth ast 1)))
|
||||||
|
(list
|
||||||
|
(quote hs-settle)
|
||||||
|
(if
|
||||||
|
(nil? raw-tgt)
|
||||||
|
(quote me)
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(list? raw-tgt)
|
||||||
|
(= (first raw-tgt) (quote query)))
|
||||||
|
(list
|
||||||
|
(quote hs-named-target)
|
||||||
|
(nth raw-tgt 1)
|
||||||
|
(list (quote hs-query-first) (nth raw-tgt 1)))
|
||||||
|
(hs-to-sx raw-tgt))))))
|
||||||
((= head (quote go))
|
((= head (quote go))
|
||||||
(list (quote hs-navigate!) (hs-to-sx (nth ast 1))))
|
(list (quote hs-navigate!) (hs-to-sx (nth ast 1))))
|
||||||
((= head (quote ask))
|
((= head (quote ask))
|
||||||
@@ -1874,7 +2068,11 @@
|
|||||||
((= head (quote install))
|
((= head (quote install))
|
||||||
(cons (quote hs-install) (map hs-to-sx (rest ast))))
|
(cons (quote hs-install) (map hs-to-sx (rest ast))))
|
||||||
((= head (quote measure))
|
((= head (quote measure))
|
||||||
(list (quote hs-measure) (hs-to-sx (nth ast 1))))
|
(let
|
||||||
|
((raw-tgt (nth ast 1)))
|
||||||
|
(let
|
||||||
|
((compiled-tgt (if (and (list? raw-tgt) (= (first raw-tgt) (quote query))) (list (quote hs-named-target) (nth raw-tgt 1) (list (quote hs-query-first) (nth raw-tgt 1))) (hs-to-sx raw-tgt))))
|
||||||
|
(list (quote hs-measure) compiled-tgt))))
|
||||||
((= head (quote increment!))
|
((= head (quote increment!))
|
||||||
(if
|
(if
|
||||||
(= (len ast) 3)
|
(= (len ast) 3)
|
||||||
|
|||||||
@@ -2455,7 +2455,16 @@
|
|||||||
((and (= typ "keyword") (= val "answer"))
|
((and (= typ "keyword") (= val "answer"))
|
||||||
(do (adv!) (parse-answer-cmd)))
|
(do (adv!) (parse-answer-cmd)))
|
||||||
((and (= typ "keyword") (= val "settle"))
|
((and (= typ "keyword") (= val "settle"))
|
||||||
(do (adv!) (list (quote settle))))
|
(do
|
||||||
|
(adv!)
|
||||||
|
(if
|
||||||
|
(or
|
||||||
|
(at-end?)
|
||||||
|
(and
|
||||||
|
(= (tp-type) "keyword")
|
||||||
|
(or (= (tp-val) "then") (= (tp-val) "end"))))
|
||||||
|
(list (quote settle))
|
||||||
|
(list (quote settle) (parse-expr)))))
|
||||||
((and (= typ "keyword") (= val "go"))
|
((and (= typ "keyword") (= val "go"))
|
||||||
(do (adv!) (parse-go-cmd)))
|
(do (adv!) (parse-go-cmd)))
|
||||||
((and (= typ "keyword") (= val "return"))
|
((and (= typ "keyword") (= val "return"))
|
||||||
|
|||||||
@@ -12,37 +12,14 @@
|
|||||||
|
|
||||||
;; Register an event listener. Returns unlisten function.
|
;; Register an event listener. Returns unlisten function.
|
||||||
;; (hs-on target event-name handler) → unlisten-fn
|
;; (hs-on target event-name handler) → unlisten-fn
|
||||||
(begin
|
|
||||||
(define _hs-config-log-all false)
|
|
||||||
(define _hs-log-captured (list))
|
|
||||||
(define
|
|
||||||
hs-set-log-all!
|
|
||||||
(fn (flag) (set! _hs-config-log-all (if flag true false))))
|
|
||||||
(define hs-get-log-captured (fn () _hs-log-captured))
|
|
||||||
(define
|
|
||||||
hs-clear-log-captured!
|
|
||||||
(fn () (begin (set! _hs-log-captured (list)) nil)))
|
|
||||||
(define
|
|
||||||
hs-log-event!
|
|
||||||
(fn
|
|
||||||
(msg)
|
|
||||||
(when
|
|
||||||
_hs-config-log-all
|
|
||||||
(begin
|
|
||||||
(set! _hs-log-captured (append _hs-log-captured (list msg)))
|
|
||||||
(host-call (host-global "console") "log" msg)
|
|
||||||
nil)))))
|
|
||||||
|
|
||||||
;; Register for every occurrence (no queuing — each fires independently).
|
|
||||||
;; Stock hyperscript queues by default; "every" disables queuing.
|
|
||||||
(define
|
(define
|
||||||
hs-each
|
hs-each
|
||||||
(fn
|
(fn
|
||||||
(target action)
|
(target action)
|
||||||
(if (list? target) (for-each action target) (action target))))
|
(if (list? target) (for-each action target) (action target))))
|
||||||
|
|
||||||
;; Run an initializer function immediately.
|
;; Register for every occurrence (no queuing — each fires independently).
|
||||||
;; (hs-init thunk) — called at element boot time
|
;; Stock hyperscript queues by default; "every" disables queuing.
|
||||||
(define
|
(define
|
||||||
hs-on
|
hs-on
|
||||||
(fn
|
(fn
|
||||||
@@ -55,17 +32,17 @@
|
|||||||
(dom-set-data target "hs-unlisteners" (append prev (list unlisten)))
|
(dom-set-data target "hs-unlisteners" (append prev (list unlisten)))
|
||||||
unlisten))))
|
unlisten))))
|
||||||
|
|
||||||
|
;; Run an initializer function immediately.
|
||||||
|
;; (hs-init thunk) — called at element boot time
|
||||||
|
(define
|
||||||
|
hs-on-every
|
||||||
|
(fn (target event-name handler) (dom-listen target event-name handler)))
|
||||||
|
|
||||||
;; ── Async / timing ──────────────────────────────────────────────
|
;; ── Async / timing ──────────────────────────────────────────────
|
||||||
|
|
||||||
;; Wait for a duration in milliseconds.
|
;; Wait for a duration in milliseconds.
|
||||||
;; In hyperscript, wait is async-transparent — execution pauses.
|
;; In hyperscript, wait is async-transparent — execution pauses.
|
||||||
;; Here we use perform/IO suspension for true pause semantics.
|
;; Here we use perform/IO suspension for true pause semantics.
|
||||||
(define
|
|
||||||
hs-on-every
|
|
||||||
(fn (target event-name handler) (dom-listen target event-name handler)))
|
|
||||||
|
|
||||||
;; Wait for a DOM event on a target.
|
|
||||||
;; (hs-wait-for target event-name) — suspends until event fires
|
|
||||||
(define
|
(define
|
||||||
hs-on-intersection-attach!
|
hs-on-intersection-attach!
|
||||||
(fn
|
(fn
|
||||||
@@ -81,15 +58,16 @@
|
|||||||
(host-call observer "observe" target)
|
(host-call observer "observe" target)
|
||||||
observer)))))
|
observer)))))
|
||||||
|
|
||||||
;; Wait for CSS transitions/animations to settle on an element.
|
;; Wait for a DOM event on a target.
|
||||||
|
;; (hs-wait-for target event-name) — suspends until event fires
|
||||||
(define hs-init (fn (thunk) (thunk)))
|
(define hs-init (fn (thunk) (thunk)))
|
||||||
|
|
||||||
|
;; Wait for CSS transitions/animations to settle on an element.
|
||||||
|
(define hs-wait (fn (ms) (perform (list (quote io-sleep) ms))))
|
||||||
|
|
||||||
;; ── Class manipulation ──────────────────────────────────────────
|
;; ── Class manipulation ──────────────────────────────────────────
|
||||||
|
|
||||||
;; Toggle a single class on an element.
|
;; Toggle a single class on an element.
|
||||||
(define hs-wait (fn (ms) (perform (list (quote io-sleep) ms))))
|
|
||||||
|
|
||||||
;; Toggle between two classes — exactly one is active at a time.
|
|
||||||
(begin
|
(begin
|
||||||
(define
|
(define
|
||||||
hs-wait-for
|
hs-wait-for
|
||||||
@@ -102,21 +80,19 @@
|
|||||||
(target event-name timeout-ms)
|
(target event-name timeout-ms)
|
||||||
(perform (list (quote io-wait-event) target event-name timeout-ms)))))
|
(perform (list (quote io-wait-event) target event-name timeout-ms)))))
|
||||||
|
|
||||||
|
;; Toggle between two classes — exactly one is active at a time.
|
||||||
|
(define hs-settle (fn (target) (perform (list (quote io-settle) target))))
|
||||||
|
|
||||||
;; Take a class from siblings — add to target, remove from others.
|
;; Take a class from siblings — add to target, remove from others.
|
||||||
;; (hs-take! target cls) — like radio button class behavior
|
;; (hs-take! target cls) — like radio button class behavior
|
||||||
(define hs-settle (fn (target) (perform (list (quote io-settle) target))))
|
(define
|
||||||
|
hs-toggle-class!
|
||||||
|
(fn (target cls) (host-call (host-get target "classList") "toggle" cls)))
|
||||||
|
|
||||||
;; ── DOM insertion ───────────────────────────────────────────────
|
;; ── DOM insertion ───────────────────────────────────────────────
|
||||||
|
|
||||||
;; Put content at a position relative to a target.
|
;; Put content at a position relative to a target.
|
||||||
;; pos: "into" | "before" | "after"
|
;; pos: "into" | "before" | "after"
|
||||||
(define
|
|
||||||
hs-toggle-class!
|
|
||||||
(fn (target cls) (host-call (host-get target "classList") "toggle" cls)))
|
|
||||||
|
|
||||||
;; ── Navigation / traversal ──────────────────────────────────────
|
|
||||||
|
|
||||||
;; Navigate to a URL.
|
|
||||||
(define
|
(define
|
||||||
hs-toggle-between!
|
hs-toggle-between!
|
||||||
(fn
|
(fn
|
||||||
@@ -126,7 +102,9 @@
|
|||||||
(do (dom-remove-class target cls1) (dom-add-class target cls2))
|
(do (dom-remove-class target cls1) (dom-add-class target cls2))
|
||||||
(do (dom-remove-class target cls2) (dom-add-class target cls1)))))
|
(do (dom-remove-class target cls2) (dom-add-class target cls1)))))
|
||||||
|
|
||||||
;; Find next sibling matching a selector (or any sibling).
|
;; ── Navigation / traversal ──────────────────────────────────────
|
||||||
|
|
||||||
|
;; Navigate to a URL.
|
||||||
(define
|
(define
|
||||||
hs-toggle-style!
|
hs-toggle-style!
|
||||||
(fn
|
(fn
|
||||||
@@ -150,7 +128,7 @@
|
|||||||
(dom-set-style target prop "hidden")
|
(dom-set-style target prop "hidden")
|
||||||
(dom-set-style target prop "")))))))
|
(dom-set-style target prop "")))))))
|
||||||
|
|
||||||
;; Find previous sibling matching a selector.
|
;; Find next sibling matching a selector (or any sibling).
|
||||||
(define
|
(define
|
||||||
hs-toggle-style-between!
|
hs-toggle-style-between!
|
||||||
(fn
|
(fn
|
||||||
@@ -162,7 +140,7 @@
|
|||||||
(dom-set-style target prop val2)
|
(dom-set-style target prop val2)
|
||||||
(dom-set-style target prop val1)))))
|
(dom-set-style target prop val1)))))
|
||||||
|
|
||||||
;; First element matching selector within a scope.
|
;; Find previous sibling matching a selector.
|
||||||
(define
|
(define
|
||||||
hs-toggle-style-cycle!
|
hs-toggle-style-cycle!
|
||||||
(fn
|
(fn
|
||||||
@@ -183,7 +161,7 @@
|
|||||||
(true (find-next (rest remaining))))))
|
(true (find-next (rest remaining))))))
|
||||||
(dom-set-style target prop (find-next vals)))))
|
(dom-set-style target prop (find-next vals)))))
|
||||||
|
|
||||||
;; Last element matching selector.
|
;; First element matching selector within a scope.
|
||||||
(define
|
(define
|
||||||
hs-take!
|
hs-take!
|
||||||
(fn
|
(fn
|
||||||
@@ -206,7 +184,8 @@
|
|||||||
(when with-cls (dom-remove-class target with-cls))))
|
(when with-cls (dom-remove-class target with-cls))))
|
||||||
(let
|
(let
|
||||||
((attr-val (if (> (len extra) 0) (first extra) nil))
|
((attr-val (if (> (len extra) 0) (first extra) nil))
|
||||||
(with-val (if (> (len extra) 1) (nth extra 1) nil)))
|
(with-val
|
||||||
|
(if (> (len extra) 1) (nth extra 1) nil)))
|
||||||
(do
|
(do
|
||||||
(for-each
|
(for-each
|
||||||
(fn
|
(fn
|
||||||
@@ -223,7 +202,7 @@
|
|||||||
(dom-set-attr target name attr-val)
|
(dom-set-attr target name attr-val)
|
||||||
(dom-set-attr target name ""))))))))
|
(dom-set-attr target name ""))))))))
|
||||||
|
|
||||||
;; First/last within a specific scope.
|
;; Last element matching selector.
|
||||||
(begin
|
(begin
|
||||||
(define
|
(define
|
||||||
hs-element?
|
hs-element?
|
||||||
@@ -335,6 +314,7 @@
|
|||||||
(dom-insert-adjacent-html target "beforeend" value)
|
(dom-insert-adjacent-html target "beforeend" value)
|
||||||
(hs-boot-subtree! target)))))))))
|
(hs-boot-subtree! target)))))))))
|
||||||
|
|
||||||
|
;; First/last within a specific scope.
|
||||||
(define
|
(define
|
||||||
hs-add-to!
|
hs-add-to!
|
||||||
(fn
|
(fn
|
||||||
@@ -347,9 +327,6 @@
|
|||||||
(append target (list value))))
|
(append target (list value))))
|
||||||
(true (do (host-call target "push" value) target)))))
|
(true (do (host-call target "push" value) target)))))
|
||||||
|
|
||||||
;; ── Iteration ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
;; Repeat a thunk N times.
|
|
||||||
(define
|
(define
|
||||||
hs-remove-from!
|
hs-remove-from!
|
||||||
(fn
|
(fn
|
||||||
@@ -357,9 +334,15 @@
|
|||||||
(if
|
(if
|
||||||
(list? target)
|
(list? target)
|
||||||
(filter (fn (x) (not (= x value))) target)
|
(filter (fn (x) (not (= x value))) target)
|
||||||
(host-call target "splice" (host-call target "indexOf" value) 1))))
|
(host-call
|
||||||
|
target
|
||||||
|
"splice"
|
||||||
|
(host-call target "indexOf" value)
|
||||||
|
1))))
|
||||||
|
|
||||||
;; Repeat forever (until break — relies on exception/continuation).
|
;; ── Iteration ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Repeat a thunk N times.
|
||||||
(define
|
(define
|
||||||
hs-splice-at!
|
hs-splice-at!
|
||||||
(fn
|
(fn
|
||||||
@@ -372,7 +355,10 @@
|
|||||||
((i (if (< idx 0) (+ n idx) idx)))
|
((i (if (< idx 0) (+ n idx) idx)))
|
||||||
(cond
|
(cond
|
||||||
((or (< i 0) (>= i n)) target)
|
((or (< i 0) (>= i n)) target)
|
||||||
(true (concat (slice target 0 i) (slice target (+ i 1) n))))))
|
(true
|
||||||
|
(concat
|
||||||
|
(slice target 0 i)
|
||||||
|
(slice target (+ i 1) n))))))
|
||||||
(do
|
(do
|
||||||
(when
|
(when
|
||||||
target
|
target
|
||||||
@@ -383,10 +369,7 @@
|
|||||||
(host-call target "splice" i 1))))
|
(host-call target "splice" i 1))))
|
||||||
target))))
|
target))))
|
||||||
|
|
||||||
;; ── Fetch ───────────────────────────────────────────────────────
|
;; Repeat forever (until break — relies on exception/continuation).
|
||||||
|
|
||||||
;; Fetch a URL, parse response according to format.
|
|
||||||
;; (hs-fetch url format) — format is "json" | "text" | "html"
|
|
||||||
(define
|
(define
|
||||||
hs-index
|
hs-index
|
||||||
(fn
|
(fn
|
||||||
@@ -398,10 +381,10 @@
|
|||||||
((string? obj) (nth obj key))
|
((string? obj) (nth obj key))
|
||||||
(true (host-get obj key)))))
|
(true (host-get obj key)))))
|
||||||
|
|
||||||
;; ── Type coercion ───────────────────────────────────────────────
|
;; ── Fetch ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
;; Coerce a value to a type by name.
|
;; Fetch a URL, parse response according to format.
|
||||||
;; (hs-coerce value type-name) — type-name is "Int", "Float", "String", etc.
|
;; (hs-fetch url format) — format is "json" | "text" | "html"
|
||||||
(define
|
(define
|
||||||
hs-put-at!
|
hs-put-at!
|
||||||
(fn
|
(fn
|
||||||
@@ -423,10 +406,10 @@
|
|||||||
((= pos "start") (host-call target "unshift" value)))
|
((= pos "start") (host-call target "unshift" value)))
|
||||||
target)))))))
|
target)))))))
|
||||||
|
|
||||||
;; ── Object creation ─────────────────────────────────────────────
|
;; ── Type coercion ───────────────────────────────────────────────
|
||||||
|
|
||||||
;; Make a new object of a given type.
|
;; Coerce a value to a type by name.
|
||||||
;; (hs-make type-name) — creates empty object/collection
|
;; (hs-coerce value type-name) — type-name is "Int", "Float", "String", etc.
|
||||||
(define
|
(define
|
||||||
hs-dict-without
|
hs-dict-without
|
||||||
(fn
|
(fn
|
||||||
@@ -447,27 +430,27 @@
|
|||||||
(host-call (host-global "Reflect") "deleteProperty" out key)
|
(host-call (host-global "Reflect") "deleteProperty" out key)
|
||||||
out)))))
|
out)))))
|
||||||
|
|
||||||
;; ── Behavior installation ───────────────────────────────────────
|
;; ── Object creation ─────────────────────────────────────────────
|
||||||
|
|
||||||
;; Install a behavior on an element.
|
;; Make a new object of a given type.
|
||||||
;; A behavior is a function that takes (me ...params) and sets up features.
|
;; (hs-make type-name) — creates empty object/collection
|
||||||
;; (hs-install behavior-fn me ...args)
|
|
||||||
(define
|
(define
|
||||||
hs-set-on!
|
hs-set-on!
|
||||||
(fn
|
(fn
|
||||||
(props target)
|
(props target)
|
||||||
(for-each (fn (k) (host-set! target k (get props k))) (keys props))))
|
(for-each (fn (k) (host-set! target k (get props k))) (keys props))))
|
||||||
|
|
||||||
|
;; ── Behavior installation ───────────────────────────────────────
|
||||||
|
|
||||||
|
;; Install a behavior on an element.
|
||||||
|
;; A behavior is a function that takes (me ...params) and sets up features.
|
||||||
|
;; (hs-install behavior-fn me ...args)
|
||||||
|
(define hs-navigate! (fn (url) (perform (list (quote io-navigate) url))))
|
||||||
|
|
||||||
;; ── Measurement ─────────────────────────────────────────────────
|
;; ── Measurement ─────────────────────────────────────────────────
|
||||||
|
|
||||||
;; Measure an element's bounding rect, store as local variables.
|
;; Measure an element's bounding rect, store as local variables.
|
||||||
;; Returns a dict with x, y, width, height, top, left, right, bottom.
|
;; Returns a dict with x, y, width, height, top, left, right, bottom.
|
||||||
(define hs-navigate! (fn (url) (perform (list (quote io-navigate) url))))
|
|
||||||
|
|
||||||
;; Return the current text selection as a string. In the browser this is
|
|
||||||
;; `window.getSelection().toString()`. In the mock test runner, a test
|
|
||||||
;; setup stashes the desired selection text at `window.__test_selection`
|
|
||||||
;; and the fallback path returns that so tests can assert on the result.
|
|
||||||
(define
|
(define
|
||||||
hs-ask
|
hs-ask
|
||||||
(fn
|
(fn
|
||||||
@@ -476,11 +459,10 @@
|
|||||||
((w (host-global "window")))
|
((w (host-global "window")))
|
||||||
(if w (host-call w "prompt" msg) nil))))
|
(if w (host-call w "prompt" msg) nil))))
|
||||||
|
|
||||||
|
;; Return the current text selection as a string. In the browser this is
|
||||||
;; ── Transition ──────────────────────────────────────────────────
|
;; `window.getSelection().toString()`. In the mock test runner, a test
|
||||||
|
;; setup stashes the desired selection text at `window.__test_selection`
|
||||||
;; Transition a CSS property to a value, optionally with duration.
|
;; and the fallback path returns that so tests can assert on the result.
|
||||||
;; (hs-transition target prop value duration)
|
|
||||||
(define
|
(define
|
||||||
hs-answer
|
hs-answer
|
||||||
(fn
|
(fn
|
||||||
@@ -489,6 +471,11 @@
|
|||||||
((w (host-global "window")))
|
((w (host-global "window")))
|
||||||
(if w (if (host-call w "confirm" msg) yes-val no-val) no-val))))
|
(if w (if (host-call w "confirm" msg) yes-val no-val) no-val))))
|
||||||
|
|
||||||
|
|
||||||
|
;; ── Transition ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
;; Transition a CSS property to a value, optionally with duration.
|
||||||
|
;; (hs-transition target prop value duration)
|
||||||
(define
|
(define
|
||||||
hs-answer-alert
|
hs-answer-alert
|
||||||
(fn
|
(fn
|
||||||
@@ -643,25 +630,25 @@
|
|||||||
(hs-query-all sel)
|
(hs-query-all sel)
|
||||||
(host-call target "querySelectorAll" sel))))
|
(host-call target "querySelectorAll" sel))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-list-set
|
hs-list-set
|
||||||
(fn
|
(fn
|
||||||
(lst idx val)
|
(lst idx val)
|
||||||
(append (take lst idx) (cons val (drop lst (+ idx 1))))))
|
(append (take lst idx) (cons val (drop lst (+ idx 1))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-to-number
|
hs-to-number
|
||||||
(fn (v) (if (number? v) v (or (parse-number (str v)) 0))))
|
(fn (v) (if (number? v) v (or (parse-number (str v)) 0))))
|
||||||
;; ── Sandbox/test runtime additions ──────────────────────────────
|
|
||||||
;; Property access — dot notation and .length
|
|
||||||
(define
|
(define
|
||||||
hs-query-first
|
hs-query-first
|
||||||
(fn (sel) (host-call (host-global "document") "querySelector" sel)))
|
(fn (sel) (host-call (host-global "document") "querySelector" sel)))
|
||||||
;; DOM query stub — sandbox returns empty list
|
;; ── Sandbox/test runtime additions ──────────────────────────────
|
||||||
|
;; Property access — dot notation and .length
|
||||||
(define
|
(define
|
||||||
hs-query-last
|
hs-query-last
|
||||||
(fn
|
(fn
|
||||||
@@ -669,11 +656,9 @@
|
|||||||
(let
|
(let
|
||||||
((all (dom-query-all (dom-body) sel)))
|
((all (dom-query-all (dom-body) sel)))
|
||||||
(if (> (len all) 0) (nth all (- (len all) 1)) nil))))
|
(if (> (len all) 0) (nth all (- (len all) 1)) nil))))
|
||||||
;; Method dispatch — obj.method(args)
|
;; DOM query stub — sandbox returns empty list
|
||||||
(define hs-first (fn (scope sel) (dom-query-all scope sel)))
|
(define hs-first (fn (scope sel) (dom-query-all scope sel)))
|
||||||
|
;; Method dispatch — obj.method(args)
|
||||||
;; ── 0.9.90 features ─────────────────────────────────────────────
|
|
||||||
;; beep! — debug logging, returns value unchanged
|
|
||||||
(define
|
(define
|
||||||
hs-last
|
hs-last
|
||||||
(fn
|
(fn
|
||||||
@@ -681,7 +666,9 @@
|
|||||||
(let
|
(let
|
||||||
((all (dom-query-all scope sel)))
|
((all (dom-query-all scope sel)))
|
||||||
(if (> (len all) 0) (nth all (- (len all) 1)) nil))))
|
(if (> (len all) 0) (nth all (- (len all) 1)) nil))))
|
||||||
;; Property-based is — check obj.key truthiness
|
|
||||||
|
;; ── 0.9.90 features ─────────────────────────────────────────────
|
||||||
|
;; beep! — debug logging, returns value unchanged
|
||||||
(define
|
(define
|
||||||
hs-repeat-times
|
hs-repeat-times
|
||||||
(fn
|
(fn
|
||||||
@@ -699,7 +686,7 @@
|
|||||||
((= signal "hs-continue") (do-repeat (+ i 1)))
|
((= signal "hs-continue") (do-repeat (+ i 1)))
|
||||||
(true (do-repeat (+ i 1))))))))
|
(true (do-repeat (+ i 1))))))))
|
||||||
(do-repeat 0)))
|
(do-repeat 0)))
|
||||||
;; Array slicing (inclusive both ends)
|
;; Property-based is — check obj.key truthiness
|
||||||
(define
|
(define
|
||||||
hs-repeat-forever
|
hs-repeat-forever
|
||||||
(fn
|
(fn
|
||||||
@@ -715,7 +702,7 @@
|
|||||||
((= signal "hs-continue") (do-forever))
|
((= signal "hs-continue") (do-forever))
|
||||||
(true (do-forever))))))
|
(true (do-forever))))))
|
||||||
(do-forever)))
|
(do-forever)))
|
||||||
;; Collection: sorted by
|
;; Array slicing (inclusive both ends)
|
||||||
(define
|
(define
|
||||||
hs-repeat-while
|
hs-repeat-while
|
||||||
(fn
|
(fn
|
||||||
@@ -728,7 +715,7 @@
|
|||||||
((= signal "hs-break") nil)
|
((= signal "hs-break") nil)
|
||||||
((= signal "hs-continue") (hs-repeat-while cond-fn thunk))
|
((= signal "hs-continue") (hs-repeat-while cond-fn thunk))
|
||||||
(true (hs-repeat-while cond-fn thunk)))))))
|
(true (hs-repeat-while cond-fn thunk)))))))
|
||||||
;; Collection: sorted by descending
|
;; Collection: sorted by
|
||||||
(define
|
(define
|
||||||
hs-repeat-until
|
hs-repeat-until
|
||||||
(fn
|
(fn
|
||||||
@@ -740,7 +727,7 @@
|
|||||||
((= signal "hs-continue")
|
((= signal "hs-continue")
|
||||||
(if (cond-fn) nil (hs-repeat-until cond-fn thunk)))
|
(if (cond-fn) nil (hs-repeat-until cond-fn thunk)))
|
||||||
(true (if (cond-fn) nil (hs-repeat-until cond-fn thunk)))))))
|
(true (if (cond-fn) nil (hs-repeat-until cond-fn thunk)))))))
|
||||||
;; Collection: split by
|
;; Collection: sorted by descending
|
||||||
(define
|
(define
|
||||||
hs-for-each
|
hs-for-each
|
||||||
(fn
|
(fn
|
||||||
@@ -760,7 +747,7 @@
|
|||||||
((= signal "hs-continue") (do-loop (rest remaining)))
|
((= signal "hs-continue") (do-loop (rest remaining)))
|
||||||
(true (do-loop (rest remaining))))))))
|
(true (do-loop (rest remaining))))))))
|
||||||
(do-loop items))))
|
(do-loop items))))
|
||||||
;; Collection: joined by
|
;; Collection: split by
|
||||||
(begin
|
(begin
|
||||||
(define
|
(define
|
||||||
hs-append
|
hs-append
|
||||||
@@ -788,7 +775,7 @@
|
|||||||
((hs-element? target)
|
((hs-element? target)
|
||||||
(dom-insert-adjacent-html target "beforeend" (str value)))
|
(dom-insert-adjacent-html target "beforeend" (str value)))
|
||||||
(true nil)))))
|
(true nil)))))
|
||||||
|
;; Collection: joined by
|
||||||
(define
|
(define
|
||||||
hs-sender
|
hs-sender
|
||||||
(fn
|
(fn
|
||||||
@@ -1310,10 +1297,14 @@
|
|||||||
((ch (substring sel i (+ i 1))))
|
((ch (substring sel i (+ i 1))))
|
||||||
(cond
|
(cond
|
||||||
((= ch ".")
|
((= ch ".")
|
||||||
(do (flush!) (set! mode "class") (walk (+ i 1))))
|
(do
|
||||||
|
(flush!)
|
||||||
|
(set! mode "class")
|
||||||
|
(walk (+ i 1))))
|
||||||
((= ch "#")
|
((= ch "#")
|
||||||
(do (flush!) (set! mode "id") (walk (+ i 1))))
|
(do (flush!) (set! mode "id") (walk (+ i 1))))
|
||||||
(true (do (set! cur (str cur ch)) (walk (+ i 1)))))))))
|
(true
|
||||||
|
(do (set! cur (str cur ch)) (walk (+ i 1)))))))))
|
||||||
(walk 0)
|
(walk 0)
|
||||||
(flush!)
|
(flush!)
|
||||||
{:tag tag :classes classes :id id}))))
|
{:tag tag :classes classes :id id}))))
|
||||||
@@ -1398,6 +1389,7 @@
|
|||||||
hs-strict-eq
|
hs-strict-eq
|
||||||
(fn (a b) (and (= (type-of a) (type-of b)) (= a b))))
|
(fn (a b) (and (= (type-of a) (type-of b)) (= a b))))
|
||||||
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-eq-ignore-case
|
hs-eq-ignore-case
|
||||||
(fn (a b) (= (downcase (str a)) (downcase (str b)))))
|
(fn (a b) (= (downcase (str a)) (downcase (str b)))))
|
||||||
@@ -1438,7 +1430,10 @@
|
|||||||
((and (dict? a) (dict? b))
|
((and (dict? a) (dict? b))
|
||||||
(let
|
(let
|
||||||
((pos (host-call a "compareDocumentPosition" b)))
|
((pos (host-call a "compareDocumentPosition" b)))
|
||||||
(if (number? pos) (not (= 0 (mod (/ pos 4) 2))) false)))
|
(if
|
||||||
|
(number? pos)
|
||||||
|
(not (= 0 (mod (/ pos 4) 2)))
|
||||||
|
false)))
|
||||||
(true (< (str a) (str b))))))
|
(true (< (str a) (str b))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -1540,7 +1535,10 @@
|
|||||||
((and (dict? a) (dict? b))
|
((and (dict? a) (dict? b))
|
||||||
(let
|
(let
|
||||||
((pos (host-call a "compareDocumentPosition" b)))
|
((pos (host-call a "compareDocumentPosition" b)))
|
||||||
(if (number? pos) (not (= 0 (mod (/ pos 4) 2))) false)))
|
(if
|
||||||
|
(number? pos)
|
||||||
|
(not (= 0 (mod (/ pos 4) 2)))
|
||||||
|
false)))
|
||||||
(true (< (str a) (str b))))))
|
(true (< (str a) (str b))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -1591,7 +1589,9 @@
|
|||||||
|
|
||||||
(define
|
(define
|
||||||
hs-morph-char
|
hs-morph-char
|
||||||
(fn (s p) (if (or (< p 0) (>= p (string-length s))) nil (nth s p))))
|
(fn
|
||||||
|
(s p)
|
||||||
|
(if (or (< p 0) (>= p (string-length s))) nil (nth s p))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-morph-index-from
|
hs-morph-index-from
|
||||||
@@ -1619,7 +1619,10 @@
|
|||||||
(q)
|
(q)
|
||||||
(let
|
(let
|
||||||
((c (hs-morph-char s q)))
|
((c (hs-morph-char s q)))
|
||||||
(if (and c (< (index-of stop c) 0)) (loop (+ q 1)) q))))
|
(if
|
||||||
|
(and c (< (index-of stop c) 0))
|
||||||
|
(loop (+ q 1))
|
||||||
|
q))))
|
||||||
(let ((e (loop p))) (list (substring s p e) e))))
|
(let ((e (loop p))) (list (substring s p e) e))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -1661,7 +1664,9 @@
|
|||||||
(append
|
(append
|
||||||
acc
|
acc
|
||||||
(list
|
(list
|
||||||
(list name (substring s (+ p4 1) close)))))))
|
(list
|
||||||
|
name
|
||||||
|
(substring s (+ p4 1) close)))))))
|
||||||
((= c2 "'")
|
((= c2 "'")
|
||||||
(let
|
(let
|
||||||
((close (hs-morph-index-from s "'" (+ p4 1))))
|
((close (hs-morph-index-from s "'" (+ p4 1))))
|
||||||
@@ -1671,7 +1676,9 @@
|
|||||||
(append
|
(append
|
||||||
acc
|
acc
|
||||||
(list
|
(list
|
||||||
(list name (substring s (+ p4 1) close)))))))
|
(list
|
||||||
|
name
|
||||||
|
(substring s (+ p4 1) close)))))))
|
||||||
(true
|
(true
|
||||||
(let
|
(let
|
||||||
((r2 (hs-morph-read-until s p4 " \t\n/>")))
|
((r2 (hs-morph-read-until s p4 " \t\n/>")))
|
||||||
@@ -1755,7 +1762,9 @@
|
|||||||
(for-each
|
(for-each
|
||||||
(fn
|
(fn
|
||||||
(c)
|
(c)
|
||||||
(when (> (string-length c) 0) (dom-add-class el c)))
|
(when
|
||||||
|
(> (string-length c) 0)
|
||||||
|
(dom-add-class el c)))
|
||||||
(split v " ")))
|
(split v " ")))
|
||||||
((and keep-id (= n "id")) nil)
|
((and keep-id (= n "id")) nil)
|
||||||
(true (dom-set-attr el n v)))))
|
(true (dom-set-attr el n v)))))
|
||||||
@@ -1856,7 +1865,8 @@
|
|||||||
((parts (split resolved ":")))
|
((parts (split resolved ":")))
|
||||||
(let
|
(let
|
||||||
((prop (first parts))
|
((prop (first parts))
|
||||||
(val (if (> (len parts) 1) (nth parts 1) nil)))
|
(val
|
||||||
|
(if (> (len parts) 1) (nth parts 1) nil)))
|
||||||
(cond
|
(cond
|
||||||
((and (not (= prop "display")) (not (= prop "opacity")) (not (= prop "visibility")) (not (= prop "hidden")) (not (= prop "class-hidden")) (not (= prop "class-invisible")) (not (= prop "class-opacity")) (not (= prop "details")) (not (= prop "dialog")) (dict-has? _hs-hide-strategies prop))
|
((and (not (= prop "display")) (not (= prop "opacity")) (not (= prop "visibility")) (not (= prop "hidden")) (not (= prop "class-hidden")) (not (= prop "class-invisible")) (not (= prop "class-opacity")) (not (= prop "details")) (not (= prop "dialog")) (dict-has? _hs-hide-strategies prop))
|
||||||
(let
|
(let
|
||||||
@@ -1895,7 +1905,8 @@
|
|||||||
((parts (split resolved ":")))
|
((parts (split resolved ":")))
|
||||||
(let
|
(let
|
||||||
((prop (first parts))
|
((prop (first parts))
|
||||||
(val (if (> (len parts) 1) (nth parts 1) nil)))
|
(val
|
||||||
|
(if (> (len parts) 1) (nth parts 1) nil)))
|
||||||
(cond
|
(cond
|
||||||
((and (not (= prop "display")) (not (= prop "opacity")) (not (= prop "visibility")) (not (= prop "hidden")) (not (= prop "class-hidden")) (not (= prop "class-invisible")) (not (= prop "class-opacity")) (not (= prop "details")) (not (= prop "dialog")) (dict-has? _hs-hide-strategies prop))
|
((and (not (= prop "display")) (not (= prop "opacity")) (not (= prop "visibility")) (not (= prop "hidden")) (not (= prop "class-hidden")) (not (= prop "class-invisible")) (not (= prop "class-opacity")) (not (= prop "details")) (not (= prop "dialog")) (dict-has? _hs-hide-strategies prop))
|
||||||
(let
|
(let
|
||||||
@@ -1999,10 +2010,14 @@
|
|||||||
(if
|
(if
|
||||||
(= depth 1)
|
(= depth 1)
|
||||||
j
|
j
|
||||||
(find-close (+ j 1) (- depth 1)))
|
(find-close
|
||||||
|
(+ j 1)
|
||||||
|
(- depth 1)))
|
||||||
(if
|
(if
|
||||||
(= (nth raw j) "{")
|
(= (nth raw j) "{")
|
||||||
(find-close (+ j 1) (+ depth 1))
|
(find-close
|
||||||
|
(+ j 1)
|
||||||
|
(+ depth 1))
|
||||||
(find-close (+ j 1) depth))))))
|
(find-close (+ j 1) depth))))))
|
||||||
(let
|
(let
|
||||||
((close (find-close start 1)))
|
((close (find-close start 1)))
|
||||||
@@ -2093,7 +2108,10 @@
|
|||||||
(if
|
(if
|
||||||
(= (len lst) 0)
|
(= (len lst) 0)
|
||||||
-1
|
-1
|
||||||
(if (= (first lst) item) i (idx-loop (rest lst) (+ i 1))))))
|
(if
|
||||||
|
(= (first lst) item)
|
||||||
|
i
|
||||||
|
(idx-loop (rest lst) (+ i 1))))))
|
||||||
(idx-loop obj 0)))
|
(idx-loop obj 0)))
|
||||||
(true nil))))
|
(true nil))))
|
||||||
|
|
||||||
@@ -2179,7 +2197,8 @@
|
|||||||
(cond
|
(cond
|
||||||
((= end "hs-pick-end") n)
|
((= end "hs-pick-end") n)
|
||||||
((= end "hs-pick-start") 0)
|
((= end "hs-pick-start") 0)
|
||||||
((and (number? end) (< end 0)) (max 0 (+ n end)))
|
((and (number? end) (< end 0))
|
||||||
|
(max 0 (+ n end)))
|
||||||
(true end))))
|
(true end))))
|
||||||
(cond
|
(cond
|
||||||
((string? col) (slice col s e))
|
((string? col) (slice col s e))
|
||||||
@@ -2466,6 +2485,50 @@
|
|||||||
((nth entry 2) val)))
|
((nth entry 2) val)))
|
||||||
_hs-dom-watchers)))
|
_hs-dom-watchers)))
|
||||||
|
|
||||||
|
(define hs-prolog-hook nil)
|
||||||
|
|
||||||
|
(define hs-set-prolog-hook! (fn (f) (set! hs-prolog-hook f)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
prolog
|
||||||
|
(fn
|
||||||
|
(db goal)
|
||||||
|
(if
|
||||||
|
(nil? hs-prolog-hook)
|
||||||
|
(raise "prolog hook not installed")
|
||||||
|
(hs-prolog-hook db goal))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-null-error!
|
||||||
|
(fn (selector) (raise (str "'" selector "' is null"))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-named-target
|
||||||
|
(fn (selector value) (if (nil? value) (hs-null-error! selector) value)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-named-target-list
|
||||||
|
(fn
|
||||||
|
(selector values)
|
||||||
|
(if (nil? values) (hs-null-error! selector) values)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
hs-query-named-all
|
||||||
|
(fn
|
||||||
|
(selector)
|
||||||
|
(let
|
||||||
|
((results (hs-query-all selector)))
|
||||||
|
(if
|
||||||
|
(and
|
||||||
|
(or
|
||||||
|
(nil? results)
|
||||||
|
(and (list? results) (= (len results) 0)))
|
||||||
|
(string? selector)
|
||||||
|
(> (len selector) 0)
|
||||||
|
(= (substring selector 0 1) "#"))
|
||||||
|
(hs-null-error! selector)
|
||||||
|
results))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
hs-dom-is-ancestor?
|
hs-dom-is-ancestor?
|
||||||
(fn
|
(fn
|
||||||
|
|||||||
176
lib/prolog/compiler.sx
Normal file
176
lib/prolog/compiler.sx
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
;; lib/prolog/compiler.sx — clause compiler: parse-AST clauses → SX closures
|
||||||
|
;;
|
||||||
|
;; Each compiled clause is a lambda (fn (goal trail db cut-box k) bool)
|
||||||
|
;; that creates fresh vars, builds the instantiated head/body, and calls
|
||||||
|
;; pl-unify! + pl-solve! directly — no AST walk at solve time.
|
||||||
|
;;
|
||||||
|
;; Usage:
|
||||||
|
;; (pl-db-load! db (pl-parse src))
|
||||||
|
;; (pl-compile-db! db)
|
||||||
|
;; ; pl-solve-user! in runtime.sx automatically prefers compiled clauses
|
||||||
|
;; (pl-solve-once! db goal trail)
|
||||||
|
|
||||||
|
;; Collect unique variable names from a parse-AST clause into a dict.
|
||||||
|
(define
|
||||||
|
pl-cmp-vars-into!
|
||||||
|
(fn
|
||||||
|
(ast seen)
|
||||||
|
(cond
|
||||||
|
((not (list? ast)) nil)
|
||||||
|
((empty? ast) nil)
|
||||||
|
((= (first ast) "var")
|
||||||
|
(let
|
||||||
|
((name (nth ast 1)))
|
||||||
|
(when
|
||||||
|
(and (not (= name "_")) (not (dict-has? seen name)))
|
||||||
|
(dict-set! seen name true))))
|
||||||
|
((= (first ast) "compound")
|
||||||
|
(for-each (fn (a) (pl-cmp-vars-into! a seen)) (nth ast 2)))
|
||||||
|
((= (first ast) "clause")
|
||||||
|
(begin
|
||||||
|
(pl-cmp-vars-into! (nth ast 1) seen)
|
||||||
|
(pl-cmp-vars-into! (nth ast 2) seen))))))
|
||||||
|
|
||||||
|
;; Return list of unique var names in a clause (head + body, excluding _).
|
||||||
|
(define
|
||||||
|
pl-cmp-collect-vars
|
||||||
|
(fn
|
||||||
|
(clause)
|
||||||
|
(let ((seen {})) (pl-cmp-vars-into! clause seen) (keys seen))))
|
||||||
|
|
||||||
|
;; Create a fresh runtime var for each name in the list; return name->var dict.
|
||||||
|
(define
|
||||||
|
pl-cmp-make-var-map
|
||||||
|
(fn
|
||||||
|
(var-names)
|
||||||
|
(let
|
||||||
|
((m {}))
|
||||||
|
(for-each
|
||||||
|
(fn (name) (dict-set! m name (pl-mk-rt-var name)))
|
||||||
|
var-names)
|
||||||
|
m)))
|
||||||
|
|
||||||
|
;; Instantiate a parse-AST term using a pre-built var-map.
|
||||||
|
;; ("var" "_") always gets a fresh anonymous var.
|
||||||
|
(define
|
||||||
|
pl-cmp-build-term
|
||||||
|
(fn
|
||||||
|
(ast var-map)
|
||||||
|
(cond
|
||||||
|
((pl-var? ast) ast)
|
||||||
|
((not (list? ast)) ast)
|
||||||
|
((empty? ast) ast)
|
||||||
|
((= (first ast) "var")
|
||||||
|
(let
|
||||||
|
((name (nth ast 1)))
|
||||||
|
(if (= name "_") (pl-mk-rt-var "_") (dict-get var-map name))))
|
||||||
|
((or (= (first ast) "atom") (= (first ast) "num") (= (first ast) "str"))
|
||||||
|
ast)
|
||||||
|
((= (first ast) "compound")
|
||||||
|
(list
|
||||||
|
"compound"
|
||||||
|
(nth ast 1)
|
||||||
|
(map (fn (a) (pl-cmp-build-term a var-map)) (nth ast 2))))
|
||||||
|
((= (first ast) "clause")
|
||||||
|
(list
|
||||||
|
"clause"
|
||||||
|
(pl-cmp-build-term (nth ast 1) var-map)
|
||||||
|
(pl-cmp-build-term (nth ast 2) var-map)))
|
||||||
|
(true ast))))
|
||||||
|
|
||||||
|
;; Compile one parse-AST clause to a lambda.
|
||||||
|
;; Pre-computes var names at compile time; creates fresh vars per call.
|
||||||
|
(define
|
||||||
|
pl-compile-clause
|
||||||
|
(fn
|
||||||
|
(clause)
|
||||||
|
(let
|
||||||
|
((var-names (pl-cmp-collect-vars clause))
|
||||||
|
(head-ast (nth clause 1))
|
||||||
|
(body-ast (nth clause 2)))
|
||||||
|
(fn
|
||||||
|
(goal trail db cut-box k)
|
||||||
|
(let
|
||||||
|
((var-map (pl-cmp-make-var-map var-names)))
|
||||||
|
(let
|
||||||
|
((fresh-head (pl-cmp-build-term head-ast var-map))
|
||||||
|
(fresh-body (pl-cmp-build-term body-ast var-map)))
|
||||||
|
(let
|
||||||
|
((mark (pl-trail-mark trail)))
|
||||||
|
(if
|
||||||
|
(pl-unify! goal fresh-head trail)
|
||||||
|
(let
|
||||||
|
((r (pl-solve! db fresh-body trail cut-box k)))
|
||||||
|
(if r true (begin (pl-trail-undo-to! trail mark) false)))
|
||||||
|
(begin (pl-trail-undo-to! trail mark) false)))))))))
|
||||||
|
|
||||||
|
;; Try a list of compiled clause lambdas — same cut semantics as pl-try-clauses!.
|
||||||
|
(define
|
||||||
|
pl-try-compiled-clauses!
|
||||||
|
(fn
|
||||||
|
(db
|
||||||
|
goal
|
||||||
|
trail
|
||||||
|
compiled-clauses
|
||||||
|
outer-cut-box
|
||||||
|
outer-was-cut
|
||||||
|
inner-cut-box
|
||||||
|
k)
|
||||||
|
(cond
|
||||||
|
((empty? compiled-clauses) false)
|
||||||
|
(true
|
||||||
|
(let
|
||||||
|
((r ((first compiled-clauses) goal trail db inner-cut-box k)))
|
||||||
|
(cond
|
||||||
|
(r true)
|
||||||
|
((dict-get inner-cut-box :cut) false)
|
||||||
|
((and (not outer-was-cut) (dict-get outer-cut-box :cut)) false)
|
||||||
|
(true
|
||||||
|
(pl-try-compiled-clauses!
|
||||||
|
db
|
||||||
|
goal
|
||||||
|
trail
|
||||||
|
(rest compiled-clauses)
|
||||||
|
outer-cut-box
|
||||||
|
outer-was-cut
|
||||||
|
inner-cut-box
|
||||||
|
k))))))))
|
||||||
|
|
||||||
|
;; Compile all clauses in DB and store in :compiled table.
|
||||||
|
;; After this call, pl-solve-user! will dispatch via compiled lambdas.
|
||||||
|
;; Note: clauses assert!-ed after this call are not compiled.
|
||||||
|
(define
|
||||||
|
pl-compile-db!
|
||||||
|
(fn
|
||||||
|
(db)
|
||||||
|
(let
|
||||||
|
((src-table (dict-get db :clauses)) (compiled-table {}))
|
||||||
|
(for-each
|
||||||
|
(fn
|
||||||
|
(key)
|
||||||
|
(dict-set!
|
||||||
|
compiled-table
|
||||||
|
key
|
||||||
|
(map pl-compile-clause (dict-get src-table key))))
|
||||||
|
(keys src-table))
|
||||||
|
(dict-set! db :compiled compiled-table)
|
||||||
|
db)))
|
||||||
|
|
||||||
|
;; Cross-validate: load src into both a plain and a compiled DB,
|
||||||
|
;; run goal-str through each, return true iff solution counts match.
|
||||||
|
;; Use this to keep the interpreter as the reference implementation.
|
||||||
|
(define
|
||||||
|
pl-compiled-matches-interp?
|
||||||
|
(fn
|
||||||
|
(src goal-str)
|
||||||
|
(let
|
||||||
|
((db-interp (pl-mk-db)) (db-comp (pl-mk-db)))
|
||||||
|
(pl-db-load! db-interp (pl-parse src))
|
||||||
|
(pl-db-load! db-comp (pl-parse src))
|
||||||
|
(pl-compile-db! db-comp)
|
||||||
|
(let
|
||||||
|
((gi (pl-instantiate (pl-parse-goal goal-str) {}))
|
||||||
|
(gc (pl-instantiate (pl-parse-goal goal-str) {})))
|
||||||
|
(=
|
||||||
|
(pl-solve-count! db-interp gi (pl-mk-trail))
|
||||||
|
(pl-solve-count! db-comp gc (pl-mk-trail)))))))
|
||||||
129
lib/prolog/conformance.sh
Executable file
129
lib/prolog/conformance.sh
Executable file
@@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Run every Prolog test suite via sx_server and refresh scoreboard.{json,md}.
|
||||||
|
# Exit 0 if all green, 1 if any failures.
|
||||||
|
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."
|
||||||
84
lib/prolog/hs-bridge.sx
Normal file
84
lib/prolog/hs-bridge.sx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
;; lib/prolog/hs-bridge.sx — Prolog ↔ Hyperscript bridge
|
||||||
|
;;
|
||||||
|
;; Two complementary integration styles:
|
||||||
|
;;
|
||||||
|
;; 1. Hook style — for `prolog(db, "goal(args)")` call syntax in Hyperscript:
|
||||||
|
;; (pl-install-hs-hook!) ;; call once at startup
|
||||||
|
;; Requires lib/hyperscript/runtime.sx (provides hs-set-prolog-hook!)
|
||||||
|
;;
|
||||||
|
;; 2. Factory style — for named conditions like `when allowed(user, action)`:
|
||||||
|
;; (define allowed (pl-hs-predicate/2 pl-db "allowed"))
|
||||||
|
;; No parser/compiler changes needed: Hyperscript compiles
|
||||||
|
;; `allowed(user, action)` to `(allowed user action)` — a plain SX call.
|
||||||
|
;;
|
||||||
|
;; Requires tokenizer.sx, parser.sx, runtime.sx, query.sx loaded first.
|
||||||
|
|
||||||
|
;; --- Hook style ---
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-install-hs-hook!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(hs-set-prolog-hook!
|
||||||
|
(fn (db goal) (not (= nil (pl-query-one db goal)))))))
|
||||||
|
|
||||||
|
;; --- Factory style ---
|
||||||
|
|
||||||
|
;; Test whether a ground Prolog goal succeeds against db.
|
||||||
|
;; Returns true/false (not a solution dict).
|
||||||
|
(define
|
||||||
|
pl-hs-query
|
||||||
|
(fn (db goal-str) (not (nil? (pl-query-one db goal-str)))))
|
||||||
|
|
||||||
|
;; Build a Prolog goal string from a predicate name and arg list.
|
||||||
|
;; SX values: strings/keywords pass through; numbers are stringified via str.
|
||||||
|
(define
|
||||||
|
pl-hs-build-goal
|
||||||
|
(fn
|
||||||
|
(pred-name args)
|
||||||
|
(str pred-name "(" (join ", " (map (fn (a) (str a)) args)) ")")))
|
||||||
|
|
||||||
|
;; Return a 1-arg SX function that succeeds iff pred(a) holds in db.
|
||||||
|
(define
|
||||||
|
pl-hs-predicate/1
|
||||||
|
(fn
|
||||||
|
(db pred-name)
|
||||||
|
(fn (a) (pl-hs-query db (pl-hs-build-goal pred-name (list a))))))
|
||||||
|
|
||||||
|
;; Return a 2-arg SX function that succeeds iff pred(a, b) holds in db.
|
||||||
|
(define
|
||||||
|
pl-hs-predicate/2
|
||||||
|
(fn
|
||||||
|
(db pred-name)
|
||||||
|
(fn (a b) (pl-hs-query db (pl-hs-build-goal pred-name (list a b))))))
|
||||||
|
|
||||||
|
;; Return a 3-arg SX function that succeeds iff pred(a, b, c) holds in db.
|
||||||
|
(define
|
||||||
|
pl-hs-predicate/3
|
||||||
|
(fn
|
||||||
|
(db pred-name)
|
||||||
|
(fn (a b c) (pl-hs-query db (pl-hs-build-goal pred-name (list a b c))))))
|
||||||
|
|
||||||
|
;; Install every predicate in install-list as a named SX function backed by db.
|
||||||
|
;; install-list: list of (name arity) pairs.
|
||||||
|
;; Returns a dict {name → fn} for the caller to destructure.
|
||||||
|
(define
|
||||||
|
pl-hs-install
|
||||||
|
(fn
|
||||||
|
(db install-list)
|
||||||
|
(reduce
|
||||||
|
(fn
|
||||||
|
(acc entry)
|
||||||
|
(let
|
||||||
|
((pred-name (first entry)) (arity (nth entry 1)))
|
||||||
|
(dict-set!
|
||||||
|
acc
|
||||||
|
pred-name
|
||||||
|
(cond
|
||||||
|
((= arity 1) (pl-hs-predicate/1 db pred-name))
|
||||||
|
((= arity 2) (pl-hs-predicate/2 db pred-name))
|
||||||
|
((= arity 3) (pl-hs-predicate/3 db pred-name))
|
||||||
|
(true (fn (a b) false))))
|
||||||
|
acc))
|
||||||
|
{}
|
||||||
|
install-list)))
|
||||||
@@ -1,28 +1,20 @@
|
|||||||
;; lib/prolog/parser.sx — tokens → Prolog AST
|
;; lib/prolog/parser.sx — tokens → Prolog AST
|
||||||
;;
|
;;
|
||||||
;; Phase 1 grammar (NO operator table yet):
|
;; Phase 4 grammar (with operator table):
|
||||||
;; Program := Clause* EOF
|
;; Program := Clause* EOF
|
||||||
;; Clause := Term "." | Term ":-" Term "."
|
;; Clause := Term[999] "." | Term[999] ":-" Term[1200] "."
|
||||||
;; Term := Atom | Var | Number | String | Compound | List
|
;; Term[Pmax] uses precedence climbing on the operator table:
|
||||||
;; Compound := atom "(" ArgList ")"
|
;; primary = Atom | Var | Number | String | Compound | List | "(" Term[1200] ")"
|
||||||
;; ArgList := Term ("," Term)*
|
;; while next token is infix op `op` with prec(op) ≤ Pmax:
|
||||||
;; List := "[" "]" | "[" Term ("," Term)* ("|" Term)? "]"
|
;; consume op; parse rhs at right-prec(op); fold into compound(op-name,[lhs,rhs])
|
||||||
;;
|
;;
|
||||||
;; Term AST shapes (all tagged lists for uniform dispatch):
|
;; Op type → right-prec for op at precedence P:
|
||||||
;; ("atom" name) — atom
|
;; xfx → P-1 strict-both
|
||||||
;; ("var" name) — variable template (parser-time only)
|
;; xfy → P right-associative
|
||||||
;; ("num" value) — integer or float
|
;; yfx → P-1 left-associative
|
||||||
;; ("str" value) — string literal
|
|
||||||
;; ("compound" functor args) — compound term, args is list of term-ASTs
|
|
||||||
;; ("cut") — the cut atom !
|
|
||||||
;;
|
;;
|
||||||
;; A clause is (list "clause" head body). A fact is head with body = ("atom" "true").
|
;; AST shapes are unchanged — operators just become compound terms.
|
||||||
;;
|
|
||||||
;; The empty list is (atom "[]"). Cons is compound "." with two args:
|
|
||||||
;; [1, 2, 3] → .(1, .(2, .(3, [])))
|
|
||||||
;; [H|T] → .(H, T)
|
|
||||||
|
|
||||||
;; ── Parser state helpers ────────────────────────────────────────────
|
|
||||||
(define
|
(define
|
||||||
pp-peek
|
pp-peek
|
||||||
(fn
|
(fn
|
||||||
@@ -66,7 +58,6 @@
|
|||||||
(if (= (get t :value) nil) "" (get t :value))
|
(if (= (get t :value) nil) "" (get t :value))
|
||||||
"'"))))))
|
"'"))))))
|
||||||
|
|
||||||
;; ── AST constructors ────────────────────────────────────────────────
|
|
||||||
(define pl-mk-atom (fn (name) (list "atom" name)))
|
(define pl-mk-atom (fn (name) (list "atom" name)))
|
||||||
(define pl-mk-var (fn (name) (list "var" name)))
|
(define pl-mk-var (fn (name) (list "var" name)))
|
||||||
(define pl-mk-num (fn (n) (list "num" n)))
|
(define pl-mk-num (fn (n) (list "num" n)))
|
||||||
@@ -74,18 +65,14 @@
|
|||||||
(define pl-mk-compound (fn (f args) (list "compound" f args)))
|
(define pl-mk-compound (fn (f args) (list "compound" f args)))
|
||||||
(define pl-mk-cut (fn () (list "cut")))
|
(define pl-mk-cut (fn () (list "cut")))
|
||||||
|
|
||||||
;; Term tag extractors
|
|
||||||
(define pl-term-tag (fn (t) (if (list? t) (first t) nil)))
|
(define pl-term-tag (fn (t) (if (list? t) (first t) nil)))
|
||||||
(define pl-term-val (fn (t) (nth t 1)))
|
(define pl-term-val (fn (t) (nth t 1)))
|
||||||
(define pl-compound-functor (fn (t) (nth t 1)))
|
(define pl-compound-functor (fn (t) (nth t 1)))
|
||||||
(define pl-compound-args (fn (t) (nth t 2)))
|
(define pl-compound-args (fn (t) (nth t 2)))
|
||||||
|
|
||||||
;; Empty-list atom and cons helpers
|
|
||||||
(define pl-nil-term (fn () (pl-mk-atom "[]")))
|
(define pl-nil-term (fn () (pl-mk-atom "[]")))
|
||||||
|
|
||||||
(define pl-mk-cons (fn (h t) (pl-mk-compound "." (list h t))))
|
(define pl-mk-cons (fn (h t) (pl-mk-compound "." (list h t))))
|
||||||
|
|
||||||
;; Build cons list from a list of terms + optional tail
|
|
||||||
(define
|
(define
|
||||||
pl-mk-list-term
|
pl-mk-list-term
|
||||||
(fn
|
(fn
|
||||||
@@ -95,9 +82,61 @@
|
|||||||
tail
|
tail
|
||||||
(pl-mk-cons (first items) (pl-mk-list-term (rest items) tail)))))
|
(pl-mk-cons (first items) (pl-mk-list-term (rest items) tail)))))
|
||||||
|
|
||||||
;; ── Term parser ─────────────────────────────────────────────────────
|
;; ── Operator table (Phase 4) ──────────────────────────────────────
|
||||||
|
;; Each entry: (name precedence type). Type ∈ "xfx" "xfy" "yfx".
|
||||||
(define
|
(define
|
||||||
pp-parse-term
|
pl-op-table
|
||||||
|
(list
|
||||||
|
(list "," 1000 "xfy")
|
||||||
|
(list ";" 1100 "xfy")
|
||||||
|
(list "->" 1050 "xfy")
|
||||||
|
(list "=" 700 "xfx")
|
||||||
|
(list "\\=" 700 "xfx")
|
||||||
|
(list "is" 700 "xfx")
|
||||||
|
(list "<" 700 "xfx")
|
||||||
|
(list ">" 700 "xfx")
|
||||||
|
(list "=<" 700 "xfx")
|
||||||
|
(list ">=" 700 "xfx")
|
||||||
|
(list "+" 500 "yfx")
|
||||||
|
(list "-" 500 "yfx")
|
||||||
|
(list "*" 400 "yfx")
|
||||||
|
(list "/" 400 "yfx")
|
||||||
|
(list ":-" 1200 "xfx")
|
||||||
|
(list "mod" 400 "yfx")))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-op-find
|
||||||
|
(fn
|
||||||
|
(name table)
|
||||||
|
(cond
|
||||||
|
((empty? table) nil)
|
||||||
|
((= (first (first table)) name) (rest (first table)))
|
||||||
|
(true (pl-op-find name (rest table))))))
|
||||||
|
|
||||||
|
(define pl-op-lookup (fn (name) (pl-op-find name pl-op-table)))
|
||||||
|
|
||||||
|
;; Token → (name prec type) for known infix ops, else nil.
|
||||||
|
(define
|
||||||
|
pl-token-op
|
||||||
|
(fn
|
||||||
|
(t)
|
||||||
|
(let
|
||||||
|
((ty (get t :type)) (vv (get t :value)))
|
||||||
|
(cond
|
||||||
|
((and (= ty "punct") (= vv ","))
|
||||||
|
(let
|
||||||
|
((info (pl-op-lookup ",")))
|
||||||
|
(if (nil? info) nil (cons "," info))))
|
||||||
|
((or (= ty "atom") (= ty "op"))
|
||||||
|
(let
|
||||||
|
((info (pl-op-lookup vv)))
|
||||||
|
(if (nil? info) nil (cons vv info))))
|
||||||
|
(true nil)))))
|
||||||
|
|
||||||
|
;; ── Term parser ─────────────────────────────────────────────────────
|
||||||
|
;; Primary term: atom, var, num, str, compound (atom + paren), list, cut, parens.
|
||||||
|
(define
|
||||||
|
pp-parse-primary
|
||||||
(fn
|
(fn
|
||||||
(st)
|
(st)
|
||||||
(let
|
(let
|
||||||
@@ -111,6 +150,12 @@
|
|||||||
((and (= ty "op") (= vv "!"))
|
((and (= ty "op") (= vv "!"))
|
||||||
(do (pp-advance! st) (pl-mk-cut)))
|
(do (pp-advance! st) (pl-mk-cut)))
|
||||||
((and (= ty "punct") (= vv "[")) (pp-parse-list st))
|
((and (= ty "punct") (= vv "[")) (pp-parse-list st))
|
||||||
|
((and (= ty "punct") (= vv "("))
|
||||||
|
(do
|
||||||
|
(pp-advance! st)
|
||||||
|
(let
|
||||||
|
((inner (pp-parse-term-prec st 1200)))
|
||||||
|
(do (pp-expect! st "punct" ")") inner))))
|
||||||
((= ty "atom")
|
((= ty "atom")
|
||||||
(do
|
(do
|
||||||
(pp-advance! st)
|
(pp-advance! st)
|
||||||
@@ -133,13 +178,51 @@
|
|||||||
(if (= vv nil) "" vv)
|
(if (= vv nil) "" vv)
|
||||||
"'"))))))))
|
"'"))))))))
|
||||||
|
|
||||||
;; Parse one or more comma-separated terms (arguments).
|
;; Operator-aware term parser: precedence climbing.
|
||||||
|
(define
|
||||||
|
pp-parse-term-prec
|
||||||
|
(fn
|
||||||
|
(st max-prec)
|
||||||
|
(let ((left (pp-parse-primary st))) (pp-parse-op-rhs st left max-prec))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pp-parse-op-rhs
|
||||||
|
(fn
|
||||||
|
(st left max-prec)
|
||||||
|
(let
|
||||||
|
((op-info (pl-token-op (pp-peek st))))
|
||||||
|
(cond
|
||||||
|
((nil? op-info) left)
|
||||||
|
(true
|
||||||
|
(let
|
||||||
|
((name (first op-info))
|
||||||
|
(prec (nth op-info 1))
|
||||||
|
(ty (nth op-info 2)))
|
||||||
|
(cond
|
||||||
|
((> prec max-prec) left)
|
||||||
|
(true
|
||||||
|
(let
|
||||||
|
((right-prec (if (= ty "xfy") prec (- prec 1))))
|
||||||
|
(do
|
||||||
|
(pp-advance! st)
|
||||||
|
(let
|
||||||
|
((right (pp-parse-term-prec st right-prec)))
|
||||||
|
(pp-parse-op-rhs
|
||||||
|
st
|
||||||
|
(pl-mk-compound name (list left right))
|
||||||
|
max-prec))))))))))))
|
||||||
|
|
||||||
|
;; Backwards-compat alias.
|
||||||
|
(define pp-parse-term (fn (st) (pp-parse-term-prec st 999)))
|
||||||
|
|
||||||
|
;; Args inside parens: parse at prec 999 so comma-as-operator (1000)
|
||||||
|
;; is not consumed; the explicit comma loop handles separation.
|
||||||
(define
|
(define
|
||||||
pp-parse-arg-list
|
pp-parse-arg-list
|
||||||
(fn
|
(fn
|
||||||
(st)
|
(st)
|
||||||
(let
|
(let
|
||||||
((first-arg (pp-parse-term st)) (args (list)))
|
((first-arg (pp-parse-term-prec st 999)) (args (list)))
|
||||||
(do
|
(do
|
||||||
(append! args first-arg)
|
(append! args first-arg)
|
||||||
(define
|
(define
|
||||||
@@ -150,12 +233,12 @@
|
|||||||
(pp-at? st "punct" ",")
|
(pp-at? st "punct" ",")
|
||||||
(do
|
(do
|
||||||
(pp-advance! st)
|
(pp-advance! st)
|
||||||
(append! args (pp-parse-term st))
|
(append! args (pp-parse-term-prec st 999))
|
||||||
(loop)))))
|
(loop)))))
|
||||||
(loop)
|
(loop)
|
||||||
args))))
|
args))))
|
||||||
|
|
||||||
;; Parse a [ ... ] list literal. Consumes the "[".
|
;; List literal.
|
||||||
(define
|
(define
|
||||||
pp-parse-list
|
pp-parse-list
|
||||||
(fn
|
(fn
|
||||||
@@ -168,7 +251,7 @@
|
|||||||
(let
|
(let
|
||||||
((items (list)))
|
((items (list)))
|
||||||
(do
|
(do
|
||||||
(append! items (pp-parse-term st))
|
(append! items (pp-parse-term-prec st 999))
|
||||||
(define
|
(define
|
||||||
comma-loop
|
comma-loop
|
||||||
(fn
|
(fn
|
||||||
@@ -177,52 +260,17 @@
|
|||||||
(pp-at? st "punct" ",")
|
(pp-at? st "punct" ",")
|
||||||
(do
|
(do
|
||||||
(pp-advance! st)
|
(pp-advance! st)
|
||||||
(append! items (pp-parse-term st))
|
(append! items (pp-parse-term-prec st 999))
|
||||||
(comma-loop)))))
|
(comma-loop)))))
|
||||||
(comma-loop)
|
(comma-loop)
|
||||||
(let
|
(let
|
||||||
((tail (if (pp-at? st "punct" "|") (do (pp-advance! st) (pp-parse-term st)) (pl-nil-term))))
|
((tail (if (pp-at? st "punct" "|") (do (pp-advance! st) (pp-parse-term-prec st 999)) (pl-nil-term))))
|
||||||
(do (pp-expect! st "punct" "]") (pl-mk-list-term items tail)))))))))
|
(do (pp-expect! st "punct" "]") (pl-mk-list-term items tail)))))))))
|
||||||
|
|
||||||
;; ── Body parsing ────────────────────────────────────────────────────
|
;; ── Body parsing ────────────────────────────────────────────────────
|
||||||
;; A clause body is a comma-separated list of goals. We flatten into a
|
;; A body is a single term parsed at prec 1200 — operator parser folds
|
||||||
;; right-associative `,` compound: (A, B, C) → ','(A, ','(B, C))
|
;; `,`, `;`, `->` automatically into right-associative compounds.
|
||||||
;; If only one goal, it's that goal directly.
|
(define pp-parse-body (fn (st) (pp-parse-term-prec st 1200)))
|
||||||
(define
|
|
||||||
pp-parse-body
|
|
||||||
(fn
|
|
||||||
(st)
|
|
||||||
(let
|
|
||||||
((first-goal (pp-parse-term st)) (rest-goals (list)))
|
|
||||||
(do
|
|
||||||
(define
|
|
||||||
gloop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(pp-at? st "punct" ",")
|
|
||||||
(do
|
|
||||||
(pp-advance! st)
|
|
||||||
(append! rest-goals (pp-parse-term st))
|
|
||||||
(gloop)))))
|
|
||||||
(gloop)
|
|
||||||
(if
|
|
||||||
(= (len rest-goals) 0)
|
|
||||||
first-goal
|
|
||||||
(pp-build-conj first-goal rest-goals))))))
|
|
||||||
|
|
||||||
(define
|
|
||||||
pp-build-conj
|
|
||||||
(fn
|
|
||||||
(first-goal rest-goals)
|
|
||||||
(if
|
|
||||||
(= (len rest-goals) 0)
|
|
||||||
first-goal
|
|
||||||
(pl-mk-compound
|
|
||||||
","
|
|
||||||
(list
|
|
||||||
first-goal
|
|
||||||
(pp-build-conj (first rest-goals) (rest rest-goals)))))))
|
|
||||||
|
|
||||||
;; ── Clause parsing ──────────────────────────────────────────────────
|
;; ── Clause parsing ──────────────────────────────────────────────────
|
||||||
(define
|
(define
|
||||||
@@ -230,12 +278,11 @@
|
|||||||
(fn
|
(fn
|
||||||
(st)
|
(st)
|
||||||
(let
|
(let
|
||||||
((head (pp-parse-term st)))
|
((head (pp-parse-term-prec st 999)))
|
||||||
(let
|
(let
|
||||||
((body (if (pp-at? st "op" ":-") (do (pp-advance! st) (pp-parse-body st)) (pl-mk-atom "true"))))
|
((body (if (pp-at? st "op" ":-") (do (pp-advance! st) (pp-parse-body st)) (pl-mk-atom "true"))))
|
||||||
(do (pp-expect! st "punct" ".") (list "clause" head body))))))
|
(do (pp-expect! st "punct" ".") (list "clause" head body))))))
|
||||||
|
|
||||||
;; Parse an entire program — returns list of clauses.
|
|
||||||
(define
|
(define
|
||||||
pl-parse-program
|
pl-parse-program
|
||||||
(fn
|
(fn
|
||||||
@@ -253,13 +300,9 @@
|
|||||||
(ploop)
|
(ploop)
|
||||||
clauses))))
|
clauses))))
|
||||||
|
|
||||||
;; Parse a single query term (no trailing "."). Returns the term.
|
|
||||||
(define
|
(define
|
||||||
pl-parse-query
|
pl-parse-query
|
||||||
(fn (tokens) (let ((st {:idx 0 :tokens tokens})) (pp-parse-body st))))
|
(fn (tokens) (let ((st {:idx 0 :tokens tokens})) (pp-parse-body st))))
|
||||||
|
|
||||||
;; Convenience: source → clauses
|
|
||||||
(define pl-parse (fn (src) (pl-parse-program (pl-tokenize src))))
|
(define pl-parse (fn (src) (pl-parse-program (pl-tokenize src))))
|
||||||
|
|
||||||
;; Convenience: source → query term
|
|
||||||
(define pl-parse-goal (fn (src) (pl-parse-query (pl-tokenize src))))
|
(define pl-parse-goal (fn (src) (pl-parse-query (pl-tokenize src))))
|
||||||
|
|||||||
114
lib/prolog/query.sx
Normal file
114
lib/prolog/query.sx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
;; lib/prolog/query.sx — high-level Prolog query API for SX/Hyperscript callers.
|
||||||
|
;;
|
||||||
|
;; Requires tokenizer.sx, parser.sx, runtime.sx to be loaded first.
|
||||||
|
;;
|
||||||
|
;; Public API:
|
||||||
|
;; (pl-load source-str) → db
|
||||||
|
;; (pl-query-all db query-str) → list of solution dicts {var-name → term-string}
|
||||||
|
;; (pl-query-one db query-str) → first solution dict or nil
|
||||||
|
;; (pl-query source-str query-str) → list of solution dicts (convenience)
|
||||||
|
|
||||||
|
;; Collect variable name strings from a parse-time AST (pre-instantiation).
|
||||||
|
;; Returns list of unique strings, excluding anonymous "_".
|
||||||
|
(define
|
||||||
|
pl-query-extract-vars
|
||||||
|
(fn
|
||||||
|
(ast)
|
||||||
|
(let
|
||||||
|
((seen {}))
|
||||||
|
(let
|
||||||
|
((collect!
|
||||||
|
(fn
|
||||||
|
(t)
|
||||||
|
(cond
|
||||||
|
((not (list? t)) nil)
|
||||||
|
((empty? t) nil)
|
||||||
|
((= (first t) "var")
|
||||||
|
(if
|
||||||
|
(not (= (nth t 1) "_"))
|
||||||
|
(dict-set! seen (nth t 1) true)
|
||||||
|
nil))
|
||||||
|
((= (first t) "compound")
|
||||||
|
(for-each collect! (nth t 2)))
|
||||||
|
(true nil)))))
|
||||||
|
(collect! ast)
|
||||||
|
(keys seen)))))
|
||||||
|
|
||||||
|
;; Build a solution dict from a var-env after a successful solve.
|
||||||
|
;; Maps each variable name string to its formatted term value.
|
||||||
|
(define
|
||||||
|
pl-query-solution-dict
|
||||||
|
(fn
|
||||||
|
(var-names var-env)
|
||||||
|
(let
|
||||||
|
((d {}))
|
||||||
|
(for-each
|
||||||
|
(fn (name) (dict-set! d name (pl-format-term (dict-get var-env name))))
|
||||||
|
var-names)
|
||||||
|
d)))
|
||||||
|
|
||||||
|
;; Parse source-str and load clauses into a fresh DB.
|
||||||
|
;; Returns the DB for reuse across multiple queries.
|
||||||
|
(define
|
||||||
|
pl-load
|
||||||
|
(fn
|
||||||
|
(source-str)
|
||||||
|
(let
|
||||||
|
((db (pl-mk-db)))
|
||||||
|
(if
|
||||||
|
(and (string? source-str) (not (= source-str "")))
|
||||||
|
(pl-db-load! db (pl-parse source-str))
|
||||||
|
nil)
|
||||||
|
db)))
|
||||||
|
|
||||||
|
;; Run query-str against db, returning a list of solution dicts.
|
||||||
|
;; Each dict maps variable name strings to their formatted term values.
|
||||||
|
;; Returns an empty list if no solutions.
|
||||||
|
(define
|
||||||
|
pl-query-all
|
||||||
|
(fn
|
||||||
|
(db query-str)
|
||||||
|
(let
|
||||||
|
((parsed (pl-parse (str "q_ :- " query-str "."))))
|
||||||
|
(let
|
||||||
|
((body-ast (nth (first parsed) 2)))
|
||||||
|
(let
|
||||||
|
((var-names (pl-query-extract-vars body-ast))
|
||||||
|
(var-env {}))
|
||||||
|
(let
|
||||||
|
((goal (pl-instantiate body-ast var-env))
|
||||||
|
(trail (pl-mk-trail))
|
||||||
|
(solutions (list)))
|
||||||
|
(let
|
||||||
|
((mark (pl-trail-mark trail)))
|
||||||
|
(pl-solve!
|
||||||
|
db
|
||||||
|
goal
|
||||||
|
trail
|
||||||
|
{:cut false}
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(begin
|
||||||
|
(append!
|
||||||
|
solutions
|
||||||
|
(pl-query-solution-dict var-names var-env))
|
||||||
|
false)))
|
||||||
|
(pl-trail-undo-to! trail mark)
|
||||||
|
solutions)))))))
|
||||||
|
|
||||||
|
;; Return the first solution dict, or nil if no solutions.
|
||||||
|
(define
|
||||||
|
pl-query-one
|
||||||
|
(fn
|
||||||
|
(db query-str)
|
||||||
|
(let
|
||||||
|
((all (pl-query-all db query-str)))
|
||||||
|
(if (empty? all) nil (first all)))))
|
||||||
|
|
||||||
|
;; Convenience: parse source-str, then run query-str against it.
|
||||||
|
;; Returns a list of solution dicts. Creates a fresh DB each call.
|
||||||
|
(define
|
||||||
|
pl-query
|
||||||
|
(fn
|
||||||
|
(source-str query-str)
|
||||||
|
(pl-query-all (pl-load source-str) query-str)))
|
||||||
File diff suppressed because it is too large
Load Diff
7
lib/prolog/scoreboard.json
Normal file
7
lib/prolog/scoreboard.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"total_passed": 590,
|
||||||
|
"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-06T08:29:09+00:00"
|
||||||
|
}
|
||||||
39
lib/prolog/scoreboard.md
Normal file
39
lib/prolog/scoreboard.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Prolog scoreboard
|
||||||
|
|
||||||
|
**590 / 590 passing** (0 failure(s)).
|
||||||
|
Generated 2026-05-06T08:29:09+00:00.
|
||||||
|
|
||||||
|
| Suite | Passed | Total | Status |
|
||||||
|
|-------|--------|-------|--------|
|
||||||
|
| parse | 25 | 25 | ok |
|
||||||
|
| unify | 47 | 47 | ok |
|
||||||
|
| clausedb | 14 | 14 | ok |
|
||||||
|
| solve | 62 | 62 | ok |
|
||||||
|
| operators | 19 | 19 | ok |
|
||||||
|
| dynamic | 11 | 11 | ok |
|
||||||
|
| findall | 11 | 11 | ok |
|
||||||
|
| term_inspect | 14 | 14 | ok |
|
||||||
|
| append | 6 | 6 | ok |
|
||||||
|
| reverse | 6 | 6 | ok |
|
||||||
|
| member | 7 | 7 | ok |
|
||||||
|
| nqueens | 6 | 6 | ok |
|
||||||
|
| family | 10 | 10 | ok |
|
||||||
|
| atoms | 34 | 34 | ok |
|
||||||
|
| query_api | 16 | 16 | ok |
|
||||||
|
| iso_predicates | 29 | 29 | ok |
|
||||||
|
| meta_predicates | 25 | 25 | ok |
|
||||||
|
| list_predicates | 33 | 33 | ok |
|
||||||
|
| meta_call | 15 | 15 | ok |
|
||||||
|
| set_predicates | 15 | 15 | ok |
|
||||||
|
| char_predicates | 27 | 27 | ok |
|
||||||
|
| io_predicates | 24 | 24 | ok |
|
||||||
|
| assert_rules | 15 | 15 | ok |
|
||||||
|
| string_agg | 25 | 25 | ok |
|
||||||
|
| advanced | 21 | 21 | ok |
|
||||||
|
| compiler | 17 | 17 | ok |
|
||||||
|
| cross_validate | 17 | 17 | ok |
|
||||||
|
| integration | 20 | 20 | ok |
|
||||||
|
| hs_bridge | 19 | 19 | ok |
|
||||||
|
|
||||||
|
Run `bash lib/prolog/conformance.sh` to refresh. Override the binary
|
||||||
|
with `SX_SERVER=path/to/sx_server.exe bash …`.
|
||||||
254
lib/prolog/tests/advanced.sx
Normal file
254
lib/prolog/tests/advanced.sx
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
;; lib/prolog/tests/advanced.sx — predsort/3, term_variables/2, arith extensions
|
||||||
|
|
||||||
|
(define pl-adv-test-count 0)
|
||||||
|
(define pl-adv-test-pass 0)
|
||||||
|
(define pl-adv-test-fail 0)
|
||||||
|
(define pl-adv-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-adv-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-adv-test-count (+ pl-adv-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-adv-test-pass (+ pl-adv-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-adv-test-fail (+ pl-adv-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-adv-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-adv-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
(define pl-adv-db (pl-mk-db))
|
||||||
|
;; Load a numeric comparator for predsort tests
|
||||||
|
(pl-db-load!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-parse
|
||||||
|
"cmp_num(Order, X, Y) :- (X < Y -> Order = '<' ; (X > Y -> Order = '>' ; Order = '='))."))
|
||||||
|
|
||||||
|
;; ── Arithmetic extensions ──────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-adv-arith-env-1 {:X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "X is floor(3.7)" pl-adv-arith-env-1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"floor(3.7) = 3"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-adv-arith-env-1 "X")))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(define pl-adv-arith-env-2 {:X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "X is ceiling(3.2)" pl-adv-arith-env-2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"ceiling(3.2) = 4"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-adv-arith-env-2 "X")))
|
||||||
|
4)
|
||||||
|
|
||||||
|
(define pl-adv-arith-env-3 {:X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "X is truncate(3.9)" pl-adv-arith-env-3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"truncate(3.9) = 3"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-adv-arith-env-3 "X")))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(define pl-adv-arith-env-4 {:X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "X is truncate(0 - 3.9)" pl-adv-arith-env-4)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"truncate(0-3.9) = -3"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-adv-arith-env-4 "X")))
|
||||||
|
-3)
|
||||||
|
|
||||||
|
(define pl-adv-arith-env-5 {:X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "X is round(3.5)" pl-adv-arith-env-5)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"round(3.5) = 4"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-adv-arith-env-5 "X")))
|
||||||
|
4)
|
||||||
|
|
||||||
|
(define pl-adv-arith-env-6 {:X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "X is sqrt(4.0)" pl-adv-arith-env-6)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"sqrt(4.0) = 2"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-adv-arith-env-6 "X")))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(define pl-adv-arith-env-7 {:X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "X is sign(0 - 5)" pl-adv-arith-env-7)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"sign(0-5) = -1"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-adv-arith-env-7 "X")))
|
||||||
|
-1)
|
||||||
|
|
||||||
|
(define pl-adv-arith-env-8 {:X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "X is sign(0)" pl-adv-arith-env-8)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"sign(0) = 0"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-adv-arith-env-8 "X")))
|
||||||
|
0)
|
||||||
|
|
||||||
|
(define pl-adv-arith-env-9 {:X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "X is sign(3)" pl-adv-arith-env-9)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"sign(3) = 1"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-adv-arith-env-9 "X")))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(define pl-adv-arith-env-10 {:X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "X is pow(2, 3)" pl-adv-arith-env-10)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"pow(2,3) = 8"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-adv-arith-env-10 "X")))
|
||||||
|
8)
|
||||||
|
|
||||||
|
(define pl-adv-arith-env-11 {:X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "X is floor(0 - 3.7)" pl-adv-arith-env-11)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"floor(0-3.7) = -4"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-adv-arith-env-11 "X")))
|
||||||
|
-4)
|
||||||
|
|
||||||
|
(define pl-adv-arith-env-12 {:X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "X is ceiling(0 - 3.2)" pl-adv-arith-env-12)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"ceiling(0-3.2) = -3"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-adv-arith-env-12 "X")))
|
||||||
|
-3)
|
||||||
|
|
||||||
|
;; ── term_variables/2 ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-adv-tv-env-1 {:Vs (pl-mk-rt-var "Vs")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "term_variables(hello, Vs)" pl-adv-tv-env-1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"term_variables(hello,Vs) -> []"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-adv-tv-env-1 "Vs")))
|
||||||
|
"[]")
|
||||||
|
|
||||||
|
(define pl-adv-tv-env-2 {:Vs (pl-mk-rt-var "Vs")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "term_variables(f(a, g(b)), Vs)" pl-adv-tv-env-2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"term_variables(f(a,g(b)),Vs) -> []"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-adv-tv-env-2 "Vs")))
|
||||||
|
"[]")
|
||||||
|
|
||||||
|
(define pl-adv-tv-env-3 {:Y (pl-mk-rt-var "Y") :Vs (pl-mk-rt-var "Vs") :X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "term_variables(f(X, Y), Vs)" pl-adv-tv-env-3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"term_variables(f(X,Y),Vs) has 2 vars"
|
||||||
|
(pl-list-length (pl-walk-deep (dict-get pl-adv-tv-env-3 "Vs")))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(define pl-adv-tv-env-4 {:Vs (pl-mk-rt-var "Vs") :X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "term_variables(X, Vs)" pl-adv-tv-env-4)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"term_variables(X,Vs) has 1 var"
|
||||||
|
(pl-list-length (pl-walk-deep (dict-get pl-adv-tv-env-4 "Vs")))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(define pl-adv-tv-env-5 {:Y (pl-mk-rt-var "Y") :Vs (pl-mk-rt-var "Vs") :X (pl-mk-rt-var "X")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "term_variables(foo(X, Y, X), Vs)" pl-adv-tv-env-5)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"term_variables(foo(X,Y,X),Vs) deduplicates X -> 2 vars"
|
||||||
|
(pl-list-length (pl-walk-deep (dict-get pl-adv-tv-env-5 "Vs")))
|
||||||
|
2)
|
||||||
|
|
||||||
|
;; ── predsort/3 ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-adv-ps-env-1 {:R (pl-mk-rt-var "R")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "predsort(cmp_num, [], R)" pl-adv-ps-env-1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"predsort([]) -> []"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-adv-ps-env-1 "R")))
|
||||||
|
"[]")
|
||||||
|
|
||||||
|
(define pl-adv-ps-env-2 {:R (pl-mk-rt-var "R")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "predsort(cmp_num, [1], R)" pl-adv-ps-env-2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"predsort([1]) -> [1]"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-adv-ps-env-2 "R")))
|
||||||
|
".(1, [])")
|
||||||
|
|
||||||
|
(define pl-adv-ps-env-3 {:R (pl-mk-rt-var "R")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "predsort(cmp_num, [3,1,2], R)" pl-adv-ps-env-3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"predsort([3,1,2]) -> [1,2,3]"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-adv-ps-env-3 "R")))
|
||||||
|
".(1, .(2, .(3, [])))")
|
||||||
|
|
||||||
|
(define pl-adv-ps-env-4 {:R (pl-mk-rt-var "R")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-adv-db
|
||||||
|
(pl-adv-goal "predsort(cmp_num, [3,1,2,1,3], R)" pl-adv-ps-env-4)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-adv-test!
|
||||||
|
"predsort([3,1,2,1,3]) dedup -> [1,2,3]"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-adv-ps-env-4 "R")))
|
||||||
|
".(1, .(2, .(3, [])))")
|
||||||
|
|
||||||
|
;; ── Runner ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-advanced-tests-run! (fn () {:failed pl-adv-test-fail :passed pl-adv-test-pass :total pl-adv-test-count :failures pl-adv-test-failures}))
|
||||||
215
lib/prolog/tests/assert_rules.sx
Normal file
215
lib/prolog/tests/assert_rules.sx
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
;; lib/prolog/tests/assert_rules.sx — assert/assertz/asserta with rule terms (head :- body)
|
||||||
|
;; Tests that :- is in the op table (prec 1200 xfx) and pl-build-clause handles rule form.
|
||||||
|
|
||||||
|
(define pl-ar-test-count 0)
|
||||||
|
(define pl-ar-test-pass 0)
|
||||||
|
(define pl-ar-test-fail 0)
|
||||||
|
(define pl-ar-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-ar-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-ar-test-count (+ pl-ar-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-ar-test-pass (+ pl-ar-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-ar-test-fail (+ pl-ar-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-ar-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-ar-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
;; ── DB1: assertz a simple rule then query ──────────────────────────
|
||||||
|
(define pl-ar-db1 (pl-mk-db))
|
||||||
|
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db1
|
||||||
|
(pl-ar-goal "assertz((double(X, Y) :- Y is X * 2))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"assertz rule: double(3, Y) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db1
|
||||||
|
(pl-ar-goal "double(3, Y)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(define pl-ar-env1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db1
|
||||||
|
(pl-ar-goal "double(3, Y)" pl-ar-env1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"assertz rule: double(3, Y) binds Y to 6"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ar-env1 "Y")))
|
||||||
|
6)
|
||||||
|
|
||||||
|
(define pl-ar-env1b {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db1
|
||||||
|
(pl-ar-goal "double(10, Y)" pl-ar-env1b)
|
||||||
|
(pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"assertz rule: double(10, Y) yields 20"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ar-env1b "Y")))
|
||||||
|
20)
|
||||||
|
|
||||||
|
;; ── DB2: assert a rule with multiple facts, count solutions ─────────
|
||||||
|
(define pl-ar-db2 (pl-mk-db))
|
||||||
|
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db2
|
||||||
|
(pl-ar-goal "assert(fact(a))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db2
|
||||||
|
(pl-ar-goal "assert(fact(b))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db2
|
||||||
|
(pl-ar-goal "assertz((copy(X) :- fact(X)))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"rule copy/1 using fact/1: 2 solutions"
|
||||||
|
(pl-solve-count! pl-ar-db2 (pl-ar-goal "copy(X)" {}) (pl-mk-trail))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(define pl-ar-env2a {})
|
||||||
|
(pl-solve-once! pl-ar-db2 (pl-ar-goal "copy(X)" pl-ar-env2a) (pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"rule copy/1: first solution is a"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-ar-env2a "X")))
|
||||||
|
"a")
|
||||||
|
|
||||||
|
;; ── DB3: asserta rule is tried before existing clauses ─────────────
|
||||||
|
(define pl-ar-db3 (pl-mk-db))
|
||||||
|
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db3
|
||||||
|
(pl-ar-goal "assert(ord(a))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db3
|
||||||
|
(pl-ar-goal "asserta((ord(b) :- true))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
|
||||||
|
(define pl-ar-env3 {})
|
||||||
|
(pl-solve-once! pl-ar-db3 (pl-ar-goal "ord(X)" pl-ar-env3) (pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"asserta rule ord(b) is tried before ord(a)"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-ar-env3 "X")))
|
||||||
|
"b")
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"asserta: total solutions for ord/1 is 2"
|
||||||
|
(pl-solve-count! pl-ar-db3 (pl-ar-goal "ord(X)" {}) (pl-mk-trail))
|
||||||
|
2)
|
||||||
|
|
||||||
|
;; ── DB4: rule with conjunction in body ─────────────────────────────
|
||||||
|
(define pl-ar-db4 (pl-mk-db))
|
||||||
|
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db4
|
||||||
|
(pl-ar-goal "assert(num(1))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db4
|
||||||
|
(pl-ar-goal "assert(num(2))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db4
|
||||||
|
(pl-ar-goal "assertz((big(X) :- num(X), X > 1))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"conjunction in rule body: big(1) fails"
|
||||||
|
(pl-solve-once! pl-ar-db4 (pl-ar-goal "big(1)" {}) (pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"conjunction in rule body: big(2) succeeds"
|
||||||
|
(pl-solve-once! pl-ar-db4 (pl-ar-goal "big(2)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── DB5: recursive rule ─────────────────────────────────────────────
|
||||||
|
(define pl-ar-db5 (pl-mk-db))
|
||||||
|
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db5
|
||||||
|
(pl-ar-goal "assert((nat(0) :- true))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db5
|
||||||
|
(pl-ar-goal "assertz((nat(s(X)) :- nat(X)))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"recursive rule: nat(0) succeeds"
|
||||||
|
(pl-solve-once! pl-ar-db5 (pl-ar-goal "nat(0)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"recursive rule: nat(s(0)) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db5
|
||||||
|
(pl-ar-goal "nat(s(0))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"recursive rule: nat(s(s(0))) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db5
|
||||||
|
(pl-ar-goal "nat(s(s(0)))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"recursive rule: nat(bad) fails"
|
||||||
|
(pl-solve-once! pl-ar-db5 (pl-ar-goal "nat(bad)" {}) (pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── DB6: rule with true body (explicit) ────────────────────────────
|
||||||
|
(define pl-ar-db6 (pl-mk-db))
|
||||||
|
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db6
|
||||||
|
(pl-ar-goal "assertz((always(X) :- true))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db6
|
||||||
|
(pl-ar-goal "assert(always(extra))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"rule body=true: always(foo) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ar-db6
|
||||||
|
(pl-ar-goal "always(foo)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-ar-test!
|
||||||
|
"rule body=true: always/1 has 2 clauses (1 rule + 1 fact)"
|
||||||
|
(pl-solve-count!
|
||||||
|
pl-ar-db6
|
||||||
|
(pl-ar-goal "always(X)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
2)
|
||||||
|
|
||||||
|
;; ── Runner ──────────────────────────────────────────────────────────
|
||||||
|
(define pl-assert-rules-tests-run! (fn () {:failed pl-ar-test-fail :passed pl-ar-test-pass :total pl-ar-test-count :failures pl-ar-test-failures}))
|
||||||
305
lib/prolog/tests/atoms.sx
Normal file
305
lib/prolog/tests/atoms.sx
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
;; lib/prolog/tests/atoms.sx — type predicates + string/atom built-ins
|
||||||
|
|
||||||
|
(define pl-at-test-count 0)
|
||||||
|
(define pl-at-test-pass 0)
|
||||||
|
(define pl-at-test-fail 0)
|
||||||
|
(define pl-at-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-at-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-at-test-count (+ pl-at-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-at-test-pass (+ pl-at-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-at-test-fail (+ pl-at-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-at-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-at-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
(define pl-at-db (pl-mk-db))
|
||||||
|
|
||||||
|
;; ── var/1 + nonvar/1 ──
|
||||||
|
|
||||||
|
(pl-at-test!
|
||||||
|
"var(X) for unbound var"
|
||||||
|
(pl-solve-once! pl-at-db (pl-at-goal "var(X)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"var(foo) fails"
|
||||||
|
(pl-solve-once! pl-at-db (pl-at-goal "var(foo)" {}) (pl-mk-trail))
|
||||||
|
false)
|
||||||
|
(pl-at-test!
|
||||||
|
"nonvar(foo) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "nonvar(foo)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"nonvar(X) for unbound var fails"
|
||||||
|
(pl-solve-once! pl-at-db (pl-at-goal "nonvar(X)" {}) (pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── atom/1 ──
|
||||||
|
|
||||||
|
(pl-at-test!
|
||||||
|
"atom(foo) succeeds"
|
||||||
|
(pl-solve-once! pl-at-db (pl-at-goal "atom(foo)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"atom([]) succeeds"
|
||||||
|
(pl-solve-once! pl-at-db (pl-at-goal "atom([])" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"atom(42) fails"
|
||||||
|
(pl-solve-once! pl-at-db (pl-at-goal "atom(42)" {}) (pl-mk-trail))
|
||||||
|
false)
|
||||||
|
(pl-at-test!
|
||||||
|
"atom(f(x)) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atom(f(x))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── number/1 + integer/1 ──
|
||||||
|
|
||||||
|
(pl-at-test!
|
||||||
|
"number(42) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "number(42)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"number(foo) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "number(foo)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
(pl-at-test!
|
||||||
|
"integer(7) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "integer(7)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── compound/1 + callable/1 + atomic/1 ──
|
||||||
|
|
||||||
|
(pl-at-test!
|
||||||
|
"compound(f(x)) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "compound(f(x))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"compound(foo) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "compound(foo)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
(pl-at-test!
|
||||||
|
"callable(foo) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "callable(foo)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"callable(f(x)) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "callable(f(x))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"callable(42) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "callable(42)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
(pl-at-test!
|
||||||
|
"atomic(foo) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atomic(foo)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"atomic(42) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atomic(42)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"atomic(f(x)) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atomic(f(x))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── is_list/1 ──
|
||||||
|
|
||||||
|
(pl-at-test!
|
||||||
|
"is_list([]) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "is_list([])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"is_list([1,2,3]) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "is_list([1,2,3])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"is_list(foo) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "is_list(foo)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── atom_length/2 ──
|
||||||
|
|
||||||
|
(define pl-at-env-al {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atom_length(hello, N)" pl-at-env-al)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-at-test!
|
||||||
|
"atom_length(hello, N) -> N=5"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-at-env-al "N")))
|
||||||
|
5)
|
||||||
|
(pl-at-test!
|
||||||
|
"atom_length empty atom"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atom_length('', 0)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── atom_concat/3 ──
|
||||||
|
|
||||||
|
(define pl-at-env-ac {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atom_concat(foo, bar, X)" pl-at-env-ac)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-at-test!
|
||||||
|
"atom_concat(foo, bar, X) -> X=foobar"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-at-env-ac "X")))
|
||||||
|
"foobar")
|
||||||
|
|
||||||
|
(pl-at-test!
|
||||||
|
"atom_concat(foo, bar, foobar) check"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atom_concat(foo, bar, foobar)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
(pl-at-test!
|
||||||
|
"atom_concat(foo, bar, foobaz) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atom_concat(foo, bar, foobaz)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(define pl-at-env-ac2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atom_concat(foo, Y, foobar)" pl-at-env-ac2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-at-test!
|
||||||
|
"atom_concat(foo, Y, foobar) -> Y=bar"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-at-env-ac2 "Y")))
|
||||||
|
"bar")
|
||||||
|
|
||||||
|
;; ── atom_chars/2 ──
|
||||||
|
|
||||||
|
(define pl-at-env-ach {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atom_chars(cat, Cs)" pl-at-env-ach)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-at-test!
|
||||||
|
"atom_chars(cat, Cs) -> Cs=[c,a,t]"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atom_chars(cat, [c,a,t])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(define pl-at-env-ach2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "atom_chars(A, [h,i])" pl-at-env-ach2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-at-test!
|
||||||
|
"atom_chars(A, [h,i]) -> A=hi"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-at-env-ach2 "A")))
|
||||||
|
"hi")
|
||||||
|
|
||||||
|
;; ── char_code/2 ──
|
||||||
|
|
||||||
|
(define pl-at-env-cc {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "char_code(a, N)" pl-at-env-cc)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-at-test!
|
||||||
|
"char_code(a, N) -> N=97"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-at-env-cc "N")))
|
||||||
|
97)
|
||||||
|
|
||||||
|
(define pl-at-env-cc2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "char_code(C, 65)" pl-at-env-cc2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-at-test!
|
||||||
|
"char_code(C, 65) -> C='A'"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-at-env-cc2 "C")))
|
||||||
|
"A")
|
||||||
|
|
||||||
|
;; ── number_codes/2 ──
|
||||||
|
|
||||||
|
(pl-at-test!
|
||||||
|
"number_codes(42, [52,50])"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "number_codes(42, [52,50])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── number_chars/2 ──
|
||||||
|
|
||||||
|
(pl-at-test!
|
||||||
|
"number_chars(42, ['4','2'])"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-at-db
|
||||||
|
(pl-at-goal "number_chars(42, ['4','2'])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(define pl-atom-tests-run! (fn () {:failed pl-at-test-fail :passed pl-at-test-pass :total pl-at-test-count :failures pl-at-test-failures}))
|
||||||
290
lib/prolog/tests/char_predicates.sx
Normal file
290
lib/prolog/tests/char_predicates.sx
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
;; lib/prolog/tests/char_predicates.sx — char_type/2, upcase_atom/2, downcase_atom/2,
|
||||||
|
;; string_upper/2, string_lower/2
|
||||||
|
|
||||||
|
(define pl-cp-test-count 0)
|
||||||
|
(define pl-cp-test-pass 0)
|
||||||
|
(define pl-cp-test-fail 0)
|
||||||
|
(define pl-cp-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-cp-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-cp-test-count (+ pl-cp-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-cp-test-pass (+ pl-cp-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-cp-test-fail (+ pl-cp-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-cp-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-cp-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
(define pl-cp-db (pl-mk-db))
|
||||||
|
|
||||||
|
;; ─── char_type/2 — alpha ──────────────────────────────────────────
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type(a, alpha) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type(a, alpha)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type('1', alpha) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type('1', alpha)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type('A', alpha) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type('A', alpha)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ─── char_type/2 — alnum ─────────────────────────────────────────
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type('5', alnum) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type('5', alnum)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type(a, alnum) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type(a, alnum)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type(' ', alnum) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type(' ', alnum)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ─── char_type/2 — digit ─────────────────────────────────────────
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type('5', digit) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type('5', digit)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type(a, digit) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type(a, digit)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ─── char_type/2 — digit(Weight) ─────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-cp-env-dw {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type('5', digit(N))" pl-cp-env-dw)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type('5', digit(N)) -> N=5"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-cp-env-dw "N")))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(define pl-cp-env-dw0 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type('0', digit(N))" pl-cp-env-dw0)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type('0', digit(N)) -> N=0"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-cp-env-dw0 "N")))
|
||||||
|
0)
|
||||||
|
|
||||||
|
;; ─── char_type/2 — space/white ───────────────────────────────────
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type(' ', space) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type(' ', space)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type(a, space) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type(a, space)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ─── char_type/2 — upper(Lower) ──────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-cp-env-ul {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type('A', upper(L))" pl-cp-env-ul)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type('A', upper(L)) -> L=a"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-cp-env-ul "L")))
|
||||||
|
"a")
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type(a, upper(L)) fails — not uppercase"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type(a, upper(_))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ─── char_type/2 — lower(Upper) ──────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-cp-env-lu {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type(a, lower(U))" pl-cp-env-lu)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type(a, lower(U)) -> U='A'"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-cp-env-lu "U")))
|
||||||
|
"A")
|
||||||
|
|
||||||
|
;; ─── char_type/2 — ascii(Code) ───────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-cp-env-as {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type(a, ascii(C))" pl-cp-env-as)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type(a, ascii(C)) -> C=97"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-cp-env-as "C")))
|
||||||
|
97)
|
||||||
|
|
||||||
|
;; ─── char_type/2 — punct ─────────────────────────────────────────
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type('.', punct) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type('.', punct)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"char_type(a, punct) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "char_type(a, punct)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ─── upcase_atom/2 ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-cp-env-ua {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "upcase_atom(hello, X)" pl-cp-env-ua)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-cp-test!
|
||||||
|
"upcase_atom(hello, X) -> X='HELLO'"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-cp-env-ua "X")))
|
||||||
|
"HELLO")
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"upcase_atom(hello, 'HELLO') succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "upcase_atom(hello, 'HELLO')" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"upcase_atom('Hello World', 'HELLO WORLD') succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "upcase_atom('Hello World', 'HELLO WORLD')" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"upcase_atom('', '') succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "upcase_atom('', '')" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ─── downcase_atom/2 ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-cp-env-da {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "downcase_atom('HELLO', X)" pl-cp-env-da)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-cp-test!
|
||||||
|
"downcase_atom('HELLO', X) -> X=hello"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-cp-env-da "X")))
|
||||||
|
"hello")
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"downcase_atom('HELLO', hello) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "downcase_atom('HELLO', hello)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-cp-test!
|
||||||
|
"downcase_atom(hello, hello) succeeds — already lowercase"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "downcase_atom(hello, hello)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ─── string_upper/2 + string_lower/2 (aliases) ───────────────────
|
||||||
|
|
||||||
|
(define pl-cp-env-su {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "string_upper(hello, X)" pl-cp-env-su)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-cp-test!
|
||||||
|
"string_upper(hello, X) -> X='HELLO'"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-cp-env-su "X")))
|
||||||
|
"HELLO")
|
||||||
|
|
||||||
|
(define pl-cp-env-sl {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-cp-db
|
||||||
|
(pl-cp-goal "string_lower('WORLD', X)" pl-cp-env-sl)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-cp-test!
|
||||||
|
"string_lower('WORLD', X) -> X=world"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-cp-env-sl "X")))
|
||||||
|
"world")
|
||||||
|
|
||||||
|
(define pl-char-predicates-tests-run! (fn () {:failed pl-cp-test-fail :passed pl-cp-test-pass :total pl-cp-test-count :failures pl-cp-test-failures}))
|
||||||
99
lib/prolog/tests/clausedb.sx
Normal file
99
lib/prolog/tests/clausedb.sx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
;; lib/prolog/tests/clausedb.sx — Clause DB unit tests
|
||||||
|
|
||||||
|
(define pl-db-test-count 0)
|
||||||
|
(define pl-db-test-pass 0)
|
||||||
|
(define pl-db-test-fail 0)
|
||||||
|
(define pl-db-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-db-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-db-test-count (+ pl-db-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-db-test-pass (+ pl-db-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-db-test-fail (+ pl-db-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-db-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(pl-db-test!
|
||||||
|
"head-key atom arity 0"
|
||||||
|
(pl-head-key (nth (first (pl-parse "foo.")) 1))
|
||||||
|
"foo/0")
|
||||||
|
|
||||||
|
(pl-db-test!
|
||||||
|
"head-key compound arity 2"
|
||||||
|
(pl-head-key (nth (first (pl-parse "bar(a, b).")) 1))
|
||||||
|
"bar/2")
|
||||||
|
|
||||||
|
(pl-db-test!
|
||||||
|
"clause-key of :- clause"
|
||||||
|
(pl-clause-key (first (pl-parse "likes(mary, X) :- friendly(X).")))
|
||||||
|
"likes/2")
|
||||||
|
|
||||||
|
(pl-db-test!
|
||||||
|
"empty db lookup returns empty list"
|
||||||
|
(len (pl-db-lookup (pl-mk-db) "parent/2"))
|
||||||
|
0)
|
||||||
|
|
||||||
|
(define pl-db-t1 (pl-mk-db))
|
||||||
|
(pl-db-load! pl-db-t1 (pl-parse "foo(a). foo(b). foo(c)."))
|
||||||
|
|
||||||
|
(pl-db-test!
|
||||||
|
"three facts same functor"
|
||||||
|
(len (pl-db-lookup pl-db-t1 "foo/1"))
|
||||||
|
3)
|
||||||
|
(pl-db-test!
|
||||||
|
"mismatching key returns empty"
|
||||||
|
(len (pl-db-lookup pl-db-t1 "foo/2"))
|
||||||
|
0)
|
||||||
|
|
||||||
|
(pl-db-test!
|
||||||
|
"first clause has arg a"
|
||||||
|
(pl-atom-name
|
||||||
|
(first (pl-args (nth (first (pl-db-lookup pl-db-t1 "foo/1")) 1))))
|
||||||
|
"a")
|
||||||
|
|
||||||
|
(pl-db-test!
|
||||||
|
"third clause has arg c"
|
||||||
|
(pl-atom-name
|
||||||
|
(first (pl-args (nth (nth (pl-db-lookup pl-db-t1 "foo/1") 2) 1))))
|
||||||
|
"c")
|
||||||
|
|
||||||
|
(define pl-db-t2 (pl-mk-db))
|
||||||
|
(pl-db-load! pl-db-t2 (pl-parse "foo. bar. foo. parent(a, b). parent(c, d)."))
|
||||||
|
|
||||||
|
(pl-db-test!
|
||||||
|
"atom heads keyed as foo/0"
|
||||||
|
(len (pl-db-lookup pl-db-t2 "foo/0"))
|
||||||
|
2)
|
||||||
|
(pl-db-test!
|
||||||
|
"atom heads keyed as bar/0"
|
||||||
|
(len (pl-db-lookup pl-db-t2 "bar/0"))
|
||||||
|
1)
|
||||||
|
(pl-db-test!
|
||||||
|
"compound heads keyed as parent/2"
|
||||||
|
(len (pl-db-lookup pl-db-t2 "parent/2"))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(pl-db-test!
|
||||||
|
"lookup-goal extracts functor/arity"
|
||||||
|
(len
|
||||||
|
(pl-db-lookup-goal pl-db-t2 (nth (first (pl-parse "parent(X, Y).")) 1)))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(pl-db-test!
|
||||||
|
"lookup-goal on atom goal"
|
||||||
|
(len (pl-db-lookup-goal pl-db-t2 (nth (first (pl-parse "foo.")) 1)))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(pl-db-test!
|
||||||
|
"stored clause is clause form"
|
||||||
|
(first (first (pl-db-lookup pl-db-t2 "parent/2")))
|
||||||
|
"clause")
|
||||||
|
|
||||||
|
(define pl-clausedb-tests-run! (fn () {:failed pl-db-test-fail :passed pl-db-test-pass :total pl-db-test-count :failures pl-db-test-failures}))
|
||||||
185
lib/prolog/tests/compiler.sx
Normal file
185
lib/prolog/tests/compiler.sx
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
;; lib/prolog/tests/compiler.sx — compiled clause dispatch tests
|
||||||
|
|
||||||
|
(define pl-cmp-test-count 0)
|
||||||
|
(define pl-cmp-test-pass 0)
|
||||||
|
(define pl-cmp-test-fail 0)
|
||||||
|
(define pl-cmp-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-cmp-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(set! pl-cmp-test-count (+ pl-cmp-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-cmp-test-pass (+ pl-cmp-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-cmp-test-fail (+ pl-cmp-test-fail 1))
|
||||||
|
(append! pl-cmp-test-failures name)))))
|
||||||
|
|
||||||
|
;; Load src, compile, return DB.
|
||||||
|
(define
|
||||||
|
pl-cmp-mk
|
||||||
|
(fn
|
||||||
|
(src)
|
||||||
|
(let
|
||||||
|
((db (pl-mk-db)))
|
||||||
|
(pl-db-load! db (pl-parse src))
|
||||||
|
(pl-compile-db! db)
|
||||||
|
db)))
|
||||||
|
|
||||||
|
;; Run goal string against compiled DB; return bool (instantiates vars).
|
||||||
|
(define
|
||||||
|
pl-cmp-once
|
||||||
|
(fn
|
||||||
|
(db src)
|
||||||
|
(pl-solve-once!
|
||||||
|
db
|
||||||
|
(pl-instantiate (pl-parse-goal src) {})
|
||||||
|
(pl-mk-trail))))
|
||||||
|
|
||||||
|
;; Count solutions for goal string against compiled DB.
|
||||||
|
(define
|
||||||
|
pl-cmp-count
|
||||||
|
(fn
|
||||||
|
(db src)
|
||||||
|
(pl-solve-count!
|
||||||
|
db
|
||||||
|
(pl-instantiate (pl-parse-goal src) {})
|
||||||
|
(pl-mk-trail))))
|
||||||
|
|
||||||
|
;; ── 1. Simple facts ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-cmp-db1 (pl-cmp-mk "color(red). color(green). color(blue)."))
|
||||||
|
|
||||||
|
(pl-cmp-test! "compiled fact hit" (pl-cmp-once pl-cmp-db1 "color(red)") true)
|
||||||
|
(pl-cmp-test!
|
||||||
|
"compiled fact miss"
|
||||||
|
(pl-cmp-once pl-cmp-db1 "color(yellow)")
|
||||||
|
false)
|
||||||
|
(pl-cmp-test! "compiled fact count" (pl-cmp-count pl-cmp-db1 "color(X)") 3)
|
||||||
|
|
||||||
|
;; ── 2. Recursive rule: append ────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-cmp-db2
|
||||||
|
(pl-cmp-mk "append([], L, L). append([H|T], L, [H|R]) :- append(T, L, R)."))
|
||||||
|
|
||||||
|
(pl-cmp-test!
|
||||||
|
"compiled append build"
|
||||||
|
(pl-cmp-once pl-cmp-db2 "append([1,2],[3],[1,2,3])")
|
||||||
|
true)
|
||||||
|
(pl-cmp-test!
|
||||||
|
"compiled append fail"
|
||||||
|
(pl-cmp-once pl-cmp-db2 "append([1,2],[3],[1,2])")
|
||||||
|
false)
|
||||||
|
(pl-cmp-test!
|
||||||
|
"compiled append split count"
|
||||||
|
(pl-cmp-count pl-cmp-db2 "append(X, Y, [a,b])")
|
||||||
|
3)
|
||||||
|
|
||||||
|
;; ── 3. Cut ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-cmp-db3
|
||||||
|
(pl-cmp-mk "first(X, [X|_]) :- !. first(X, [_|T]) :- first(X, T)."))
|
||||||
|
|
||||||
|
(pl-cmp-test!
|
||||||
|
"compiled cut: only one solution"
|
||||||
|
(pl-cmp-count pl-cmp-db3 "first(X, [a,b,c])")
|
||||||
|
1)
|
||||||
|
|
||||||
|
(let
|
||||||
|
((db pl-cmp-db3) (trail (pl-mk-trail)) (env {}))
|
||||||
|
(let
|
||||||
|
((x (pl-mk-rt-var "X")))
|
||||||
|
(dict-set! env "X" x)
|
||||||
|
(pl-solve-once!
|
||||||
|
db
|
||||||
|
(pl-instantiate (pl-parse-goal "first(X, [a,b,c])") env)
|
||||||
|
trail)
|
||||||
|
(pl-cmp-test!
|
||||||
|
"compiled cut: correct binding"
|
||||||
|
(pl-atom-name (pl-walk x))
|
||||||
|
"a")))
|
||||||
|
|
||||||
|
;; ── 4. member ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-cmp-db4
|
||||||
|
(pl-cmp-mk "member(X, [X|_]). member(X, [_|T]) :- member(X, T)."))
|
||||||
|
|
||||||
|
(pl-cmp-test!
|
||||||
|
"compiled member hit"
|
||||||
|
(pl-cmp-once pl-cmp-db4 "member(b, [a,b,c])")
|
||||||
|
true)
|
||||||
|
(pl-cmp-test!
|
||||||
|
"compiled member miss"
|
||||||
|
(pl-cmp-once pl-cmp-db4 "member(d, [a,b,c])")
|
||||||
|
false)
|
||||||
|
(pl-cmp-test!
|
||||||
|
"compiled member count"
|
||||||
|
(pl-cmp-count pl-cmp-db4 "member(X, [a,b,c])")
|
||||||
|
3)
|
||||||
|
|
||||||
|
;; ── 5. Arithmetic in body ────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-cmp-db5 (pl-cmp-mk "double(X, Y) :- Y is X * 2."))
|
||||||
|
|
||||||
|
(let
|
||||||
|
((db pl-cmp-db5) (trail (pl-mk-trail)) (env {}))
|
||||||
|
(let
|
||||||
|
((y (pl-mk-rt-var "Y")))
|
||||||
|
(dict-set! env "Y" y)
|
||||||
|
(pl-solve-once!
|
||||||
|
db
|
||||||
|
(pl-instantiate (pl-parse-goal "double(5, Y)") env)
|
||||||
|
trail)
|
||||||
|
(pl-cmp-test! "compiled arithmetic in body" (pl-num-val (pl-walk y)) 10)))
|
||||||
|
|
||||||
|
;; ── 6. Transitive ancestor ───────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-cmp-db6
|
||||||
|
(pl-cmp-mk
|
||||||
|
(str
|
||||||
|
"parent(a,b). parent(b,c). parent(c,d)."
|
||||||
|
"ancestor(X,Y) :- parent(X,Y)."
|
||||||
|
"ancestor(X,Y) :- parent(X,Z), ancestor(Z,Y).")))
|
||||||
|
|
||||||
|
(pl-cmp-test!
|
||||||
|
"compiled ancestor direct"
|
||||||
|
(pl-cmp-once pl-cmp-db6 "ancestor(a,b)")
|
||||||
|
true)
|
||||||
|
(pl-cmp-test!
|
||||||
|
"compiled ancestor 3-step"
|
||||||
|
(pl-cmp-once pl-cmp-db6 "ancestor(a,d)")
|
||||||
|
true)
|
||||||
|
(pl-cmp-test!
|
||||||
|
"compiled ancestor fail"
|
||||||
|
(pl-cmp-once pl-cmp-db6 "ancestor(d,a)")
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── 7. Fallback: uncompiled predicate calls compiled sub-predicate
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-cmp-db7
|
||||||
|
(let
|
||||||
|
((db (pl-mk-db)))
|
||||||
|
(pl-db-load! db (pl-parse "q(1). q(2)."))
|
||||||
|
(pl-compile-db! db)
|
||||||
|
(pl-db-load! db (pl-parse "r(X) :- q(X)."))
|
||||||
|
db))
|
||||||
|
|
||||||
|
(pl-cmp-test!
|
||||||
|
"uncompiled predicate resolves"
|
||||||
|
(pl-cmp-once pl-cmp-db7 "r(1)")
|
||||||
|
true)
|
||||||
|
(pl-cmp-test!
|
||||||
|
"uncompiled calls compiled sub-pred count"
|
||||||
|
(pl-cmp-count pl-cmp-db7 "r(X)")
|
||||||
|
2)
|
||||||
|
|
||||||
|
;; ── Runner ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-compiler-tests-run! (fn () {:failed pl-cmp-test-fail :passed pl-cmp-test-pass :total pl-cmp-test-count :failures pl-cmp-test-failures}))
|
||||||
86
lib/prolog/tests/cross_validate.sx
Normal file
86
lib/prolog/tests/cross_validate.sx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
;; lib/prolog/tests/cross_validate.sx
|
||||||
|
;; Verifies that the compiled solver produces the same solution counts as the
|
||||||
|
;; interpreter for each classic program + built-in exercise.
|
||||||
|
;; Interpreter is the reference: if they disagree, the compiler is wrong.
|
||||||
|
|
||||||
|
(define pl-xv-test-count 0)
|
||||||
|
(define pl-xv-test-pass 0)
|
||||||
|
(define pl-xv-test-fail 0)
|
||||||
|
(define pl-xv-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-xv-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(set! pl-xv-test-count (+ pl-xv-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-xv-test-pass (+ pl-xv-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-xv-test-fail (+ pl-xv-test-fail 1))
|
||||||
|
(append! pl-xv-test-failures name)))))
|
||||||
|
|
||||||
|
;; Shorthand: assert compiled result matches interpreter.
|
||||||
|
(define
|
||||||
|
pl-xv-match!
|
||||||
|
(fn
|
||||||
|
(name src goal)
|
||||||
|
(pl-xv-test! name (pl-compiled-matches-interp? src goal) true)))
|
||||||
|
|
||||||
|
;; ── 1. append/3 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-xv-append
|
||||||
|
"append([], L, L). append([H|T], L, [H|R]) :- append(T, L, R).")
|
||||||
|
|
||||||
|
(pl-xv-match! "append build 2+2" pl-xv-append "append([1,2],[3,4],X)")
|
||||||
|
(pl-xv-match! "append split [a,b,c]" pl-xv-append "append(X, Y, [a,b,c])")
|
||||||
|
(pl-xv-match! "append member-mode" pl-xv-append "append(_, [3], [1,2,3])")
|
||||||
|
|
||||||
|
;; ── 2. member/2 ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-xv-member "member(X, [X|_]). member(X, [_|T]) :- member(X, T).")
|
||||||
|
|
||||||
|
(pl-xv-match! "member check hit" pl-xv-member "member(b, [a,b,c])")
|
||||||
|
(pl-xv-match! "member count" pl-xv-member "member(X, [a,b,c])")
|
||||||
|
(pl-xv-match! "member empty" pl-xv-member "member(X, [])")
|
||||||
|
|
||||||
|
;; ── 3. facts + transitive rules ─────────────────────────────────
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-xv-ancestor
|
||||||
|
(str
|
||||||
|
"parent(a,b). parent(b,c). parent(c,d). parent(a,c)."
|
||||||
|
"ancestor(X,Y) :- parent(X,Y)."
|
||||||
|
"ancestor(X,Y) :- parent(X,Z), ancestor(Z,Y)."))
|
||||||
|
|
||||||
|
(pl-xv-match! "ancestor direct" pl-xv-ancestor "ancestor(a,b)")
|
||||||
|
(pl-xv-match! "ancestor transitive" pl-xv-ancestor "ancestor(a,d)")
|
||||||
|
(pl-xv-match! "ancestor all from a" pl-xv-ancestor "ancestor(a,Y)")
|
||||||
|
|
||||||
|
;; ── 4. cut semantics ────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-xv-cut "first(X,[X|_]) :- !. first(X,[_|T]) :- first(X,T).")
|
||||||
|
|
||||||
|
(pl-xv-match! "cut one solution" pl-xv-cut "first(X,[a,b,c])")
|
||||||
|
(pl-xv-match! "cut empty list" pl-xv-cut "first(X,[])")
|
||||||
|
|
||||||
|
;; ── 5. arithmetic ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-xv-arith "sq(X,Y) :- Y is X * X. even(X) :- 0 is X mod 2.")
|
||||||
|
|
||||||
|
(pl-xv-match! "sq(3,Y) count" pl-xv-arith "sq(3,Y)")
|
||||||
|
(pl-xv-match! "sq(3,9) check" pl-xv-arith "sq(3,9)")
|
||||||
|
(pl-xv-match! "even(4) check" pl-xv-arith "even(4)")
|
||||||
|
(pl-xv-match! "even(3) check" pl-xv-arith "even(3)")
|
||||||
|
|
||||||
|
;; ── 6. if-then-else ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-xv-ite "classify(X, pos) :- X > 0, !. classify(_, nonpos).")
|
||||||
|
|
||||||
|
(pl-xv-match! "classify positive" pl-xv-ite "classify(5, C)")
|
||||||
|
(pl-xv-match! "classify zero" pl-xv-ite "classify(0, C)")
|
||||||
|
|
||||||
|
;; ── Runner ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-cross-validate-tests-run! (fn () {:failed pl-xv-test-fail :passed pl-xv-test-pass :total pl-xv-test-count :failures pl-xv-test-failures}))
|
||||||
158
lib/prolog/tests/dynamic.sx
Normal file
158
lib/prolog/tests/dynamic.sx
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
;; lib/prolog/tests/dynamic.sx — assert/asserta/assertz/retract.
|
||||||
|
|
||||||
|
(define pl-dy-test-count 0)
|
||||||
|
(define pl-dy-test-pass 0)
|
||||||
|
(define pl-dy-test-fail 0)
|
||||||
|
(define pl-dy-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-dy-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-dy-test-count (+ pl-dy-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-dy-test-pass (+ pl-dy-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-dy-test-fail (+ pl-dy-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-dy-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-dy-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
;; assertz then query
|
||||||
|
(define pl-dy-db1 (pl-mk-db))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db1
|
||||||
|
(pl-dy-goal "assertz(foo(1))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-dy-test!
|
||||||
|
"assertz(foo(1)) + foo(1)"
|
||||||
|
(pl-solve-once! pl-dy-db1 (pl-dy-goal "foo(1)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-dy-test!
|
||||||
|
"after one assertz, foo/1 has 1 clause"
|
||||||
|
(pl-solve-count! pl-dy-db1 (pl-dy-goal "foo(X)" {}) (pl-mk-trail))
|
||||||
|
1)
|
||||||
|
|
||||||
|
;; assertz appends — order preserved
|
||||||
|
(define pl-dy-db2 (pl-mk-db))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db2
|
||||||
|
(pl-dy-goal "assertz(p(1))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db2
|
||||||
|
(pl-dy-goal "assertz(p(2))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-dy-test!
|
||||||
|
"assertz twice — count 2"
|
||||||
|
(pl-solve-count! pl-dy-db2 (pl-dy-goal "p(X)" {}) (pl-mk-trail))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(define pl-dy-env-a {})
|
||||||
|
(pl-solve-once! pl-dy-db2 (pl-dy-goal "p(X)" pl-dy-env-a) (pl-mk-trail))
|
||||||
|
(pl-dy-test!
|
||||||
|
"assertz: first solution is the first asserted (1)"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-dy-env-a "X")))
|
||||||
|
1)
|
||||||
|
|
||||||
|
;; asserta prepends
|
||||||
|
(define pl-dy-db3 (pl-mk-db))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db3
|
||||||
|
(pl-dy-goal "assertz(p(1))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db3
|
||||||
|
(pl-dy-goal "asserta(p(99))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(define pl-dy-env-b {})
|
||||||
|
(pl-solve-once! pl-dy-db3 (pl-dy-goal "p(X)" pl-dy-env-b) (pl-mk-trail))
|
||||||
|
(pl-dy-test!
|
||||||
|
"asserta: prepended clause is first solution"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-dy-env-b "X")))
|
||||||
|
99)
|
||||||
|
|
||||||
|
;; assert/1 = assertz/1
|
||||||
|
(define pl-dy-db4 (pl-mk-db))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db4
|
||||||
|
(pl-dy-goal "assert(g(7))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-dy-test!
|
||||||
|
"assert/1 alias"
|
||||||
|
(pl-solve-once! pl-dy-db4 (pl-dy-goal "g(7)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; retract removes a fact
|
||||||
|
(define pl-dy-db5 (pl-mk-db))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db5
|
||||||
|
(pl-dy-goal "assertz(q(1))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db5
|
||||||
|
(pl-dy-goal "assertz(q(2))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db5
|
||||||
|
(pl-dy-goal "assertz(q(3))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-dy-test!
|
||||||
|
"before retract: 3 clauses"
|
||||||
|
(pl-solve-count! pl-dy-db5 (pl-dy-goal "q(X)" {}) (pl-mk-trail))
|
||||||
|
3)
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db5
|
||||||
|
(pl-dy-goal "retract(q(2))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-dy-test!
|
||||||
|
"after retract(q(2)): 2 clauses left"
|
||||||
|
(pl-solve-count! pl-dy-db5 (pl-dy-goal "q(X)" {}) (pl-mk-trail))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(define pl-dy-env-c {})
|
||||||
|
(pl-solve-once! pl-dy-db5 (pl-dy-goal "q(X)" pl-dy-env-c) (pl-mk-trail))
|
||||||
|
(pl-dy-test!
|
||||||
|
"after retract(q(2)): first remaining is 1"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-dy-env-c "X")))
|
||||||
|
1)
|
||||||
|
|
||||||
|
;; retract of non-existent
|
||||||
|
(pl-dy-test!
|
||||||
|
"retract(missing(0)) on empty db fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
(pl-mk-db)
|
||||||
|
(pl-dy-goal "retract(missing(0))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; retract with unbound var matches first
|
||||||
|
(define pl-dy-db6 (pl-mk-db))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db6
|
||||||
|
(pl-dy-goal "assertz(r(11))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db6
|
||||||
|
(pl-dy-goal "assertz(r(22))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
(define pl-dy-env-d {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-dy-db6
|
||||||
|
(pl-dy-goal "retract(r(X))" pl-dy-env-d)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-dy-test!
|
||||||
|
"retract(r(X)) binds X to first match"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-dy-env-d "X")))
|
||||||
|
11)
|
||||||
|
|
||||||
|
(define pl-dynamic-tests-run! (fn () {:failed pl-dy-test-fail :passed pl-dy-test-pass :total pl-dy-test-count :failures pl-dy-test-failures}))
|
||||||
167
lib/prolog/tests/findall.sx
Normal file
167
lib/prolog/tests/findall.sx
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
;; lib/prolog/tests/findall.sx — findall/3, bagof/3, setof/3.
|
||||||
|
|
||||||
|
(define pl-fb-test-count 0)
|
||||||
|
(define pl-fb-test-pass 0)
|
||||||
|
(define pl-fb-test-fail 0)
|
||||||
|
(define pl-fb-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-fb-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-fb-test-count (+ pl-fb-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-fb-test-pass (+ pl-fb-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-fb-test-fail (+ pl-fb-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-fb-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-fb-term-to-sx
|
||||||
|
(fn
|
||||||
|
(t)
|
||||||
|
(cond
|
||||||
|
((pl-num? t) (pl-num-val t))
|
||||||
|
((pl-atom? t) (pl-atom-name t))
|
||||||
|
(true (list :complex)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-fb-list-walked
|
||||||
|
(fn
|
||||||
|
(w)
|
||||||
|
(cond
|
||||||
|
((and (pl-atom? w) (= (pl-atom-name w) "[]")) (list))
|
||||||
|
((and (pl-compound? w) (= (pl-fun w) ".") (= (len (pl-args w)) 2))
|
||||||
|
(cons
|
||||||
|
(pl-fb-term-to-sx (first (pl-args w)))
|
||||||
|
(pl-fb-list-walked (nth (pl-args w) 1))))
|
||||||
|
(true (list :not-list)))))
|
||||||
|
|
||||||
|
(define pl-fb-list-to-sx (fn (t) (pl-fb-list-walked (pl-walk-deep t))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-fb-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
(define pl-fb-prog-src "member(X, [X|_]). member(X, [_|T]) :- member(X, T).")
|
||||||
|
|
||||||
|
(define pl-fb-db (pl-mk-db))
|
||||||
|
(pl-db-load! pl-fb-db (pl-parse pl-fb-prog-src))
|
||||||
|
|
||||||
|
;; ── findall ──
|
||||||
|
|
||||||
|
(define pl-fb-env-1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fb-db
|
||||||
|
(pl-fb-goal "findall(X, member(X, [a, b, c]), L)" pl-fb-env-1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-fb-test!
|
||||||
|
"findall member [a, b, c]"
|
||||||
|
(pl-fb-list-to-sx (dict-get pl-fb-env-1 "L"))
|
||||||
|
(list "a" "b" "c"))
|
||||||
|
|
||||||
|
(define pl-fb-env-2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fb-db
|
||||||
|
(pl-fb-goal "findall(X, (member(X, [1, 2, 3]), X >= 2), L)" pl-fb-env-2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-fb-test!
|
||||||
|
"findall with comparison filter"
|
||||||
|
(pl-fb-list-to-sx (dict-get pl-fb-env-2 "L"))
|
||||||
|
(list 2 3))
|
||||||
|
|
||||||
|
(define pl-fb-env-3 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fb-db
|
||||||
|
(pl-fb-goal "findall(X, fail, L)" pl-fb-env-3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-fb-test!
|
||||||
|
"findall on fail succeeds with empty list"
|
||||||
|
(pl-fb-list-to-sx (dict-get pl-fb-env-3 "L"))
|
||||||
|
(list))
|
||||||
|
|
||||||
|
(pl-fb-test!
|
||||||
|
"findall(X, fail, L) the goal succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fb-db
|
||||||
|
(pl-fb-goal "findall(X, fail, L)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(define pl-fb-env-4 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fb-db
|
||||||
|
(pl-fb-goal
|
||||||
|
"findall(p(X, Y), (member(X, [1, 2]), member(Y, [a, b])), L)"
|
||||||
|
pl-fb-env-4)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-fb-test!
|
||||||
|
"findall over compound template — count = 4"
|
||||||
|
(len (pl-fb-list-to-sx (dict-get pl-fb-env-4 "L")))
|
||||||
|
4)
|
||||||
|
|
||||||
|
;; ── bagof ──
|
||||||
|
|
||||||
|
(pl-fb-test!
|
||||||
|
"bagof succeeds when results exist"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fb-db
|
||||||
|
(pl-fb-goal "bagof(X, member(X, [1, 2, 3]), L)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-fb-test!
|
||||||
|
"bagof fails on empty"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fb-db
|
||||||
|
(pl-fb-goal "bagof(X, fail, L)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(define pl-fb-env-5 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fb-db
|
||||||
|
(pl-fb-goal "bagof(X, member(X, [c, a, b]), L)" pl-fb-env-5)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-fb-test!
|
||||||
|
"bagof preserves order"
|
||||||
|
(pl-fb-list-to-sx (dict-get pl-fb-env-5 "L"))
|
||||||
|
(list "c" "a" "b"))
|
||||||
|
|
||||||
|
;; ── setof ──
|
||||||
|
|
||||||
|
(define pl-fb-env-6 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fb-db
|
||||||
|
(pl-fb-goal "setof(X, member(X, [c, a, b, a, c]), L)" pl-fb-env-6)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-fb-test!
|
||||||
|
"setof sorts + dedupes atoms"
|
||||||
|
(pl-fb-list-to-sx (dict-get pl-fb-env-6 "L"))
|
||||||
|
(list "a" "b" "c"))
|
||||||
|
|
||||||
|
(pl-fb-test!
|
||||||
|
"setof fails on empty"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fb-db
|
||||||
|
(pl-fb-goal "setof(X, fail, L)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(define pl-fb-env-7 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fb-db
|
||||||
|
(pl-fb-goal "setof(X, member(X, [3, 1, 2, 1, 3]), L)" pl-fb-env-7)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-fb-test!
|
||||||
|
"setof sorts + dedupes nums"
|
||||||
|
(pl-fb-list-to-sx (dict-get pl-fb-env-7 "L"))
|
||||||
|
(list 1 2 3))
|
||||||
|
|
||||||
|
(define pl-findall-tests-run! (fn () {:failed pl-fb-test-fail :passed pl-fb-test-pass :total pl-fb-test-count :failures pl-fb-test-failures}))
|
||||||
165
lib/prolog/tests/hs_bridge.sx
Normal file
165
lib/prolog/tests/hs_bridge.sx
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
;; lib/prolog/tests/hs_bridge.sx — tests for Prolog↔Hyperscript bridge
|
||||||
|
;;
|
||||||
|
;; Verifies pl-hs-query, pl-hs-predicate/N, and pl-hs-install.
|
||||||
|
;; Also demonstrates the end-to-end DSL pattern:
|
||||||
|
;; (define allowed (pl-hs-predicate/2 db "allowed"))
|
||||||
|
;; → (allowed "alice" "edit") is what Hyperscript compiles
|
||||||
|
;; `when allowed(alice, edit)` to.
|
||||||
|
|
||||||
|
(define pl-hsb-test-count 0)
|
||||||
|
(define pl-hsb-test-pass 0)
|
||||||
|
(define pl-hsb-test-fail 0)
|
||||||
|
(define pl-hsb-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-hsb-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-hsb-test-count (+ pl-hsb-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-hsb-test-pass (+ pl-hsb-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-hsb-test-fail (+ pl-hsb-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-hsb-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
;; ── shared KB ──
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-hsb-perm-src
|
||||||
|
"role(alice, admin). role(bob, editor). role(charlie, viewer). permission(admin, read). permission(admin, write). permission(admin, delete). permission(editor, read). permission(editor, write). permission(viewer, read). allowed(U, A) :- role(U, R), permission(R, A).")
|
||||||
|
|
||||||
|
(define pl-hsb-db (pl-load pl-hsb-perm-src))
|
||||||
|
|
||||||
|
;; ── pl-hs-query ──
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"pl-hs-query: ground fact succeeds"
|
||||||
|
(pl-hs-query pl-hsb-db "role(alice, admin)")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"pl-hs-query: absent fact fails"
|
||||||
|
(pl-hs-query pl-hsb-db "role(alice, viewer)")
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"pl-hs-query: rule derivation succeeds"
|
||||||
|
(pl-hs-query pl-hsb-db "allowed(alice, delete)")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"pl-hs-query: rule derivation fails"
|
||||||
|
(pl-hs-query pl-hsb-db "allowed(charlie, delete)")
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"pl-hs-query: arithmetic goal"
|
||||||
|
(pl-hs-query pl-hsb-db "X is 3 + 4, X = 7")
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── pl-hs-predicate/2 ──
|
||||||
|
|
||||||
|
(define pl-hsb-allowed (pl-hs-predicate/2 pl-hsb-db "allowed"))
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"predicate/2: alice can read"
|
||||||
|
(pl-hsb-allowed "alice" "read")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"predicate/2: alice can delete"
|
||||||
|
(pl-hsb-allowed "alice" "delete")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"predicate/2: charlie cannot write"
|
||||||
|
(pl-hsb-allowed "charlie" "write")
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"predicate/2: bob can write"
|
||||||
|
(pl-hsb-allowed "bob" "write")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"predicate/2: unknown user fails"
|
||||||
|
(pl-hsb-allowed "eve" "read")
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── DSL simulation ──
|
||||||
|
;; Hyperscript compiles `when allowed(user, action) then …`
|
||||||
|
;; to `(allowed user action)` — a direct SX function call.
|
||||||
|
;; Here we verify that pattern works end-to-end.
|
||||||
|
|
||||||
|
(define pl-hsb-user "alice")
|
||||||
|
(define pl-hsb-action "write")
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"DSL simulation: (allowed user action) true path"
|
||||||
|
(pl-hsb-allowed pl-hsb-user pl-hsb-action)
|
||||||
|
true)
|
||||||
|
|
||||||
|
(define pl-hsb-user2 "charlie")
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"DSL simulation: (allowed user action) false path"
|
||||||
|
(pl-hsb-allowed pl-hsb-user2 pl-hsb-action)
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── pl-hs-predicate/1 ──
|
||||||
|
|
||||||
|
(define pl-hsb-viewer-src "color(red). color(green). color(blue).")
|
||||||
|
(define pl-hsb-color-db (pl-load pl-hsb-viewer-src))
|
||||||
|
(define pl-hsb-color? (pl-hs-predicate/1 pl-hsb-color-db "color"))
|
||||||
|
|
||||||
|
(pl-hsb-test! "predicate/1: color(red) succeeds" (pl-hsb-color? "red") true)
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"predicate/1: color(purple) fails"
|
||||||
|
(pl-hsb-color? "purple")
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── pl-hs-predicate/3 ──
|
||||||
|
|
||||||
|
(define pl-hsb-3ary-src "between_vals(X, Lo, Hi) :- X >= Lo, X =< Hi.")
|
||||||
|
(define pl-hsb-3ary-db (pl-load pl-hsb-3ary-src))
|
||||||
|
(define pl-hsb-in-range? (pl-hs-predicate/3 pl-hsb-3ary-db "between_vals"))
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"predicate/3: 5 in range [1,10]"
|
||||||
|
(pl-hsb-in-range? "5" "1" "10")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"predicate/3: 15 not in range [1,10]"
|
||||||
|
(pl-hsb-in-range? "15" "1" "10")
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── pl-hs-install ──
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-hsb-installed
|
||||||
|
(pl-hs-install
|
||||||
|
pl-hsb-db
|
||||||
|
(list (list "allowed" 2) (list "role" 2) (list "permission" 2))))
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"pl-hs-install: returns dict with allowed key"
|
||||||
|
(not (nil? (dict-get pl-hsb-installed "allowed")))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"pl-hs-install: installed allowed fn works"
|
||||||
|
((dict-get pl-hsb-installed "allowed") "alice" "delete")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-hsb-test!
|
||||||
|
"pl-hs-install: installed role fn works"
|
||||||
|
((dict-get pl-hsb-installed "role") "bob" "editor")
|
||||||
|
true)
|
||||||
|
|
||||||
|
(define pl-hs-bridge-tests-run! (fn () {:failed pl-hsb-test-fail :passed pl-hsb-test-pass :total pl-hsb-test-count :failures pl-hsb-test-failures}))
|
||||||
172
lib/prolog/tests/integration.sx
Normal file
172
lib/prolog/tests/integration.sx
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
;; lib/prolog/tests/integration.sx — end-to-end integration tests via pl-query-* API
|
||||||
|
;;
|
||||||
|
;; Tests the full source→parse→load→solve pipeline with real programs.
|
||||||
|
;; Covers: permission system, graph reachability, quicksort, fibonacci, dynamic KB.
|
||||||
|
|
||||||
|
(define pl-int-test-count 0)
|
||||||
|
(define pl-int-test-pass 0)
|
||||||
|
(define pl-int-test-fail 0)
|
||||||
|
(define pl-int-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-int-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-int-test-count (+ pl-int-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-int-test-pass (+ pl-int-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-int-test-fail (+ pl-int-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-int-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
;; ── Permission system ──
|
||||||
|
;; role/2 + permission/2 facts, allowed/2 rule
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-int-perm-src
|
||||||
|
"role(alice, admin). role(bob, editor). role(charlie, viewer). permission(admin, read). permission(admin, write). permission(admin, delete). permission(editor, read). permission(editor, write). permission(viewer, read). allowed(U, A) :- role(U, R), permission(R, A).")
|
||||||
|
|
||||||
|
(define pl-int-perm-db (pl-load pl-int-perm-src))
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"alice can read"
|
||||||
|
(len (pl-query-all pl-int-perm-db "allowed(alice, read)"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"alice can delete"
|
||||||
|
(len (pl-query-all pl-int-perm-db "allowed(alice, delete)"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"charlie cannot write"
|
||||||
|
(len (pl-query-all pl-int-perm-db "allowed(charlie, write)"))
|
||||||
|
0)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"alice has 3 permissions"
|
||||||
|
(len (pl-query-all pl-int-perm-db "allowed(alice, A)"))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"only one user can delete"
|
||||||
|
(len (pl-query-all pl-int-perm-db "allowed(U, delete)"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"the deleter is alice"
|
||||||
|
(dict-get (first (pl-query-all pl-int-perm-db "allowed(U, delete)")) "U")
|
||||||
|
"alice")
|
||||||
|
|
||||||
|
;; ── Graph reachability ──
|
||||||
|
;; Directed edges; path/2 transitive closure via two clauses
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-int-graph-src
|
||||||
|
"edge(a, b). edge(b, c). edge(c, d). edge(b, d). path(X, Y) :- edge(X, Y). path(X, Y) :- edge(X, Z), path(Z, Y).")
|
||||||
|
|
||||||
|
(define pl-int-graph-db (pl-load pl-int-graph-src))
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"direct edge a→b is a path"
|
||||||
|
(len (pl-query-all pl-int-graph-db "path(a, b)"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"transitive path a→c"
|
||||||
|
(len (pl-query-all pl-int-graph-db "path(a, c)"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"no path d→a (no back-edges)"
|
||||||
|
(len (pl-query-all pl-int-graph-db "path(d, a)"))
|
||||||
|
0)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"4 derivations from a (b,c,d via two routes to d)"
|
||||||
|
(len (pl-query-all pl-int-graph-db "path(a, Y)"))
|
||||||
|
4)
|
||||||
|
|
||||||
|
;; ── Quicksort ──
|
||||||
|
;; Partition-and-recurse; uses its own append/3 to avoid DB pollution
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-int-qs-src
|
||||||
|
"partition(_, [], [], []). partition(Piv, [H|T], [H|Less], Greater) :- H =< Piv, !, partition(Piv, T, Less, Greater). partition(Piv, [H|T], Less, [H|Greater]) :- partition(Piv, T, Less, Greater). append([], L, L). append([H|T], L, [H|R]) :- append(T, L, R). quicksort([], []). quicksort([H|T], Sorted) :- partition(H, T, Less, Greater), quicksort(Less, SL), quicksort(Greater, SG), append(SL, [H|SG], Sorted).")
|
||||||
|
|
||||||
|
(define pl-int-qs-db (pl-load pl-int-qs-src))
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"quicksort([]) = [] (ground check)"
|
||||||
|
(len (pl-query-all pl-int-qs-db "quicksort([], [])"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"quicksort([3,1,2]) = [1,2,3] (ground check)"
|
||||||
|
(len (pl-query-all pl-int-qs-db "quicksort([3,1,2], [1,2,3])"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"quicksort([5,3,1,4,2]) = [1,2,3,4,5] (ground check)"
|
||||||
|
(len (pl-query-all pl-int-qs-db "quicksort([5,3,1,4,2], [1,2,3,4,5])"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"quicksort([3,1,2], [3,1,2]) fails — unsorted order rejected"
|
||||||
|
(len (pl-query-all pl-int-qs-db "quicksort([3,1,2], [3,1,2])"))
|
||||||
|
0)
|
||||||
|
|
||||||
|
;; ── Fibonacci ──
|
||||||
|
;; Naive recursive; ground checks avoid list-format uncertainty
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-int-fib-src
|
||||||
|
"fib(0, 0). fib(1, 1). fib(N, F) :- N > 1, N1 is N - 1, N2 is N - 2, fib(N1, F1), fib(N2, F2), F is F1 + F2.")
|
||||||
|
|
||||||
|
(define pl-int-fib-db (pl-load pl-int-fib-src))
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"fib(0, 0) succeeds"
|
||||||
|
(len (pl-query-all pl-int-fib-db "fib(0, 0)"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"fib(5, 5) succeeds"
|
||||||
|
(len (pl-query-all pl-int-fib-db "fib(5, 5)"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"fib(7, 13) succeeds"
|
||||||
|
(len (pl-query-all pl-int-fib-db "fib(7, 13)"))
|
||||||
|
1)
|
||||||
|
|
||||||
|
;; ── Dynamic knowledge base ──
|
||||||
|
;; Assert and retract facts; the DB dict is mutable so mutations persist
|
||||||
|
|
||||||
|
(define pl-int-dyn-src "color(red). color(green). color(blue).")
|
||||||
|
(define pl-int-dyn-db (pl-load pl-int-dyn-src))
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"initial KB: 3 colors"
|
||||||
|
(len (pl-query-all pl-int-dyn-db "color(X)"))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"after assert(color(yellow)): 4 colors"
|
||||||
|
(begin
|
||||||
|
(pl-query-all pl-int-dyn-db "assert(color(yellow))")
|
||||||
|
(len (pl-query-all pl-int-dyn-db "color(X)")))
|
||||||
|
4)
|
||||||
|
|
||||||
|
(pl-int-test!
|
||||||
|
"after retract(color(red)): back to 3 colors"
|
||||||
|
(begin
|
||||||
|
(pl-query-all pl-int-dyn-db "retract(color(red))")
|
||||||
|
(len (pl-query-all pl-int-dyn-db "color(X)")))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(define pl-integration-tests-run! (fn () {:failed pl-int-test-fail :passed pl-int-test-pass :total pl-int-test-count :failures pl-int-test-failures}))
|
||||||
326
lib/prolog/tests/io_predicates.sx
Normal file
326
lib/prolog/tests/io_predicates.sx
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
;; lib/prolog/tests/io_predicates.sx — term_to_atom/2, term_string/2,
|
||||||
|
;; with_output_to/2, writeln/1, format/1, format/2
|
||||||
|
|
||||||
|
(define pl-io-test-count 0)
|
||||||
|
(define pl-io-test-pass 0)
|
||||||
|
(define pl-io-test-fail 0)
|
||||||
|
(define pl-io-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-io-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-io-test-count (+ pl-io-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-io-test-pass (+ pl-io-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-io-test-fail (+ pl-io-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-io-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-io-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
(define pl-io-db (pl-mk-db))
|
||||||
|
|
||||||
|
;; helper: get output buffer after running a goal
|
||||||
|
(define
|
||||||
|
pl-io-capture!
|
||||||
|
(fn
|
||||||
|
(goal)
|
||||||
|
(do
|
||||||
|
(pl-output-clear!)
|
||||||
|
(pl-solve-once! pl-io-db goal (pl-mk-trail))
|
||||||
|
pl-output-buffer)))
|
||||||
|
|
||||||
|
;; ─── term_to_atom/2 — bound Term direction ─────────────────────────────────
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"term_to_atom(foo(a,b), A) — compound"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "term_to_atom(foo(a,b), A)" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "A"))))
|
||||||
|
"foo(a, b)")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"term_to_atom(hello, A) — atom"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "term_to_atom(hello, A)" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "A"))))
|
||||||
|
"hello")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"term_to_atom(42, A) — number"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "term_to_atom(42, A)" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "A"))))
|
||||||
|
"42")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"term_to_atom(foo(a,b), 'foo(a, b)') — succeeds when Atom matches"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "term_to_atom(foo(a,b), 'foo(a, b)')" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"term_to_atom(hello, world) — fails on mismatch"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "term_to_atom(hello, world)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ─── term_to_atom/2 — parse direction (Atom bound, Term unbound) ───────────
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"term_to_atom(T, 'foo(a)') — parse direction gives compound"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "term_to_atom(T, 'foo(a)')" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(let
|
||||||
|
((t (pl-walk-deep (dict-get env "T"))))
|
||||||
|
(and (pl-compound? t) (= (pl-fun t) "foo"))))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"term_to_atom(T, hello) — parse direction gives atom"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "term_to_atom(T, hello)" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(let
|
||||||
|
((t (pl-walk-deep (dict-get env "T"))))
|
||||||
|
(and (pl-atom? t) (= (pl-atom-name t) "hello"))))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ─── term_string/2 — alias ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"term_string(bar(x), A) — same as term_to_atom"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "term_string(bar(x), A)" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "A"))))
|
||||||
|
"bar(x)")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"term_string(42, A) — number to string"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "term_string(42, A)" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "A"))))
|
||||||
|
"42")
|
||||||
|
|
||||||
|
;; ─── writeln/1 ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"writeln(hello) writes 'hello\n'"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), writeln(hello))" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"hello
|
||||||
|
")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"writeln(42) writes '42\n'"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), writeln(42))" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"42
|
||||||
|
")
|
||||||
|
|
||||||
|
;; ─── with_output_to/2 ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"with_output_to(atom(X), write(foo)) — captures write output"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), write(foo))" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"foo")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"with_output_to(atom(X), (write(a), write(b))) — concat output"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), (write(a), write(b)))" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"ab")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"with_output_to(atom(X), nl) — captures newline"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), nl)" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"
|
||||||
|
")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"with_output_to(atom(X), true) — captures empty string"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), true)" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"with_output_to(string(X), write(hello)) — string sink works"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(string(X), write(hello))" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"hello")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"with_output_to(atom(X), fail) — fails when goal fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), fail)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ─── format/1 ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"format('hello~n') — tilde-n becomes newline"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), format('hello~n'))" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"hello
|
||||||
|
")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"format('~~') — double tilde becomes single tilde"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), format('~~'))" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"~")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"format('abc') — plain text passes through"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), format(abc))" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"abc")
|
||||||
|
|
||||||
|
;; ─── format/2 ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"format('~w+~w', [1,2]) — two ~w args"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), format('~w+~w', [1,2]))" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"1+2")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"format('hello ~a!', [world]) — ~a with atom arg"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), format('hello ~a!', [world]))" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"hello world!")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"format('n=~d', [42]) — ~d with integer arg"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), format('n=~d', [42]))" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"n=42")
|
||||||
|
|
||||||
|
(pl-io-test!
|
||||||
|
"format('~w', [foo(a)]) — ~w with compound"
|
||||||
|
(let
|
||||||
|
((env {}))
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-io-db
|
||||||
|
(pl-io-goal "with_output_to(atom(X), format('~w', [foo(a)]))" env)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get env "X"))))
|
||||||
|
"foo(a)")
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-io-predicates-tests-run!
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
{:failed pl-io-test-fail
|
||||||
|
:passed pl-io-test-pass
|
||||||
|
:total pl-io-test-count
|
||||||
|
:failures pl-io-test-failures}))
|
||||||
320
lib/prolog/tests/iso_predicates.sx
Normal file
320
lib/prolog/tests/iso_predicates.sx
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
;; lib/prolog/tests/iso_predicates.sx — succ/2, plus/3, between/3, length/2, last/2, nth0/3, nth1/3, max/min arith
|
||||||
|
|
||||||
|
(define pl-ip-test-count 0)
|
||||||
|
(define pl-ip-test-pass 0)
|
||||||
|
(define pl-ip-test-fail 0)
|
||||||
|
(define pl-ip-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-ip-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-ip-test-count (+ pl-ip-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-ip-test-pass (+ pl-ip-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-ip-test-fail (+ pl-ip-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-ip-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-ip-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
(define pl-ip-db (pl-mk-db))
|
||||||
|
|
||||||
|
;; ── succ/2 ──
|
||||||
|
|
||||||
|
(define pl-ip-env-s1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "succ(3, X)" pl-ip-env-s1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"succ(3, X) → X=4"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-s1 "X")))
|
||||||
|
4)
|
||||||
|
|
||||||
|
(define pl-ip-env-s2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "succ(0, X)" pl-ip-env-s2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"succ(0, X) → X=1"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-s2 "X")))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(define pl-ip-env-s3 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "succ(X, 5)" pl-ip-env-s3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"succ(X, 5) → X=4"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-s3 "X")))
|
||||||
|
4)
|
||||||
|
|
||||||
|
(pl-ip-test!
|
||||||
|
"succ(X, 0) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "succ(X, 0)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── plus/3 ──
|
||||||
|
|
||||||
|
(define pl-ip-env-p1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "plus(2, 3, X)" pl-ip-env-p1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"plus(2, 3, X) → X=5"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-p1 "X")))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(define pl-ip-env-p2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "plus(2, X, 7)" pl-ip-env-p2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"plus(2, X, 7) → X=5"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-p2 "X")))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(define pl-ip-env-p3 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "plus(X, 3, 7)" pl-ip-env-p3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"plus(X, 3, 7) → X=4"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-p3 "X")))
|
||||||
|
4)
|
||||||
|
|
||||||
|
(pl-ip-test!
|
||||||
|
"plus(0, 0, 0) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "plus(0, 0, 0)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── between/3 ──
|
||||||
|
|
||||||
|
(pl-ip-test!
|
||||||
|
"between(1, 3, X): 3 solutions"
|
||||||
|
(pl-solve-count!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "between(1, 3, X)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(pl-ip-test!
|
||||||
|
"between(1, 3, 2) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "between(1, 3, 2)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-ip-test!
|
||||||
|
"between(1, 3, 5) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "between(1, 3, 5)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-ip-test!
|
||||||
|
"between(5, 3, X): 0 solutions (empty range)"
|
||||||
|
(pl-solve-count!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "between(5, 3, X)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
0)
|
||||||
|
|
||||||
|
(define pl-ip-env-b1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "between(1, 5, X)" pl-ip-env-b1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"between(1, 5, X): first solution X=1"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-b1 "X")))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(pl-ip-test!
|
||||||
|
"between + condition: between(1,5,X), X > 3 → 2 solutions"
|
||||||
|
(pl-solve-count!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "between(1, 5, X), X > 3" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
2)
|
||||||
|
|
||||||
|
;; ── length/2 ──
|
||||||
|
|
||||||
|
(define pl-ip-env-l1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "length([1,2,3], N)" pl-ip-env-l1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"length([1,2,3], N) → N=3"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-l1 "N")))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(define pl-ip-env-l2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "length([], N)" pl-ip-env-l2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"length([], N) → N=0"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-l2 "N")))
|
||||||
|
0)
|
||||||
|
|
||||||
|
(pl-ip-test!
|
||||||
|
"length([a,b], 2) check succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "length([a,b], 2)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(define pl-ip-env-l3 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "length(L, 3)" pl-ip-env-l3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"length(L, 3): L is a list of length 3"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "length(L, 3), is_list(L)" pl-ip-env-l3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── last/2 ──
|
||||||
|
|
||||||
|
(define pl-ip-env-la1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "last([1,2,3], X)" pl-ip-env-la1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"last([1,2,3], X) → X=3"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-la1 "X")))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(define pl-ip-env-la2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "last([a], X)" pl-ip-env-la2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"last([a], X) → X=a"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-ip-env-la2 "X")))
|
||||||
|
"a")
|
||||||
|
|
||||||
|
(pl-ip-test!
|
||||||
|
"last([], X) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "last([], X)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── nth0/3 ──
|
||||||
|
|
||||||
|
(define pl-ip-env-n0 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "nth0(0, [a,b,c], X)" pl-ip-env-n0)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"nth0(0, [a,b,c], X) → X=a"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-ip-env-n0 "X")))
|
||||||
|
"a")
|
||||||
|
|
||||||
|
(define pl-ip-env-n1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "nth0(2, [a,b,c], X)" pl-ip-env-n1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"nth0(2, [a,b,c], X) → X=c"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-ip-env-n1 "X")))
|
||||||
|
"c")
|
||||||
|
|
||||||
|
(pl-ip-test!
|
||||||
|
"nth0(5, [a,b,c], X) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "nth0(5, [a,b,c], X)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── nth1/3 ──
|
||||||
|
|
||||||
|
(define pl-ip-env-n1a {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "nth1(1, [a,b,c], X)" pl-ip-env-n1a)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"nth1(1, [a,b,c], X) → X=a"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-ip-env-n1a "X")))
|
||||||
|
"a")
|
||||||
|
|
||||||
|
(define pl-ip-env-n1b {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "nth1(3, [a,b,c], X)" pl-ip-env-n1b)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"nth1(3, [a,b,c], X) → X=c"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-ip-env-n1b "X")))
|
||||||
|
"c")
|
||||||
|
|
||||||
|
;; ── max/min in arithmetic ──
|
||||||
|
|
||||||
|
(define pl-ip-env-m1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "X is max(3, 5)" pl-ip-env-m1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"X is max(3, 5) → X=5"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-m1 "X")))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(define pl-ip-env-m2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "X is min(3, 5)" pl-ip-env-m2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"X is min(3, 5) → X=3"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-m2 "X")))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(define pl-ip-env-m3 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ip-db
|
||||||
|
(pl-ip-goal "X is max(7, 2) + min(1, 4)" pl-ip-env-m3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-ip-test!
|
||||||
|
"X is max(7,2) + min(1,4) → X=8"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-ip-env-m3 "X")))
|
||||||
|
8)
|
||||||
|
|
||||||
|
(define pl-iso-predicates-tests-run! (fn () {:failed pl-ip-test-fail :passed pl-ip-test-pass :total pl-ip-test-count :failures pl-ip-test-failures}))
|
||||||
335
lib/prolog/tests/list_predicates.sx
Normal file
335
lib/prolog/tests/list_predicates.sx
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
;; lib/prolog/tests/list_predicates.sx — ==/2, \==/2, flatten/2, numlist/3,
|
||||||
|
;; atomic_list_concat/2,3, sum_list/2, max_list/2, min_list/2, delete/3
|
||||||
|
|
||||||
|
(define pl-lp-test-count 0)
|
||||||
|
(define pl-lp-test-pass 0)
|
||||||
|
(define pl-lp-test-fail 0)
|
||||||
|
(define pl-lp-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-lp-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-lp-test-count (+ pl-lp-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-lp-test-pass (+ pl-lp-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-lp-test-fail (+ pl-lp-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-lp-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-lp-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
(define pl-lp-db (pl-mk-db))
|
||||||
|
|
||||||
|
;; ── ==/2 ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(pl-lp-test!
|
||||||
|
"==(a, a) succeeds"
|
||||||
|
(pl-solve-once! pl-lp-db (pl-lp-goal "==(a, a)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-lp-test!
|
||||||
|
"==(a, b) fails"
|
||||||
|
(pl-solve-once! pl-lp-db (pl-lp-goal "==(a, b)" {}) (pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-lp-test!
|
||||||
|
"==(1, 1) succeeds"
|
||||||
|
(pl-solve-once! pl-lp-db (pl-lp-goal "==(1, 1)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-lp-test!
|
||||||
|
"==(1, 2) fails"
|
||||||
|
(pl-solve-once! pl-lp-db (pl-lp-goal "==(1, 2)" {}) (pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-lp-test!
|
||||||
|
"==(f(a,b), f(a,b)) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "==(f(a,b), f(a,b))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-lp-test!
|
||||||
|
"==(f(a,b), f(a,c)) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "==(f(a,b), f(a,c))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; unbound var vs atom: fails (different tags)
|
||||||
|
(pl-lp-test!
|
||||||
|
"==(X, a) fails (unbound var vs atom)"
|
||||||
|
(pl-solve-once! pl-lp-db (pl-lp-goal "==(X, a)" {}) (pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; two unbound vars with SAME name in same env share the same runtime var
|
||||||
|
(define pl-lp-env-same-var {})
|
||||||
|
(pl-lp-goal "==(X, X)" pl-lp-env-same-var)
|
||||||
|
(pl-lp-test!
|
||||||
|
"==(X, X) succeeds (same runtime var)"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-instantiate
|
||||||
|
(nth (first (pl-parse "g :- ==(X, X).")) 2)
|
||||||
|
pl-lp-env-same-var)
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── \==/2 ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(pl-lp-test!
|
||||||
|
"\\==(a, b) succeeds"
|
||||||
|
(pl-solve-once! pl-lp-db (pl-lp-goal "\\==(a, b)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-lp-test!
|
||||||
|
"\\==(a, a) fails"
|
||||||
|
(pl-solve-once! pl-lp-db (pl-lp-goal "\\==(a, a)" {}) (pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-lp-test!
|
||||||
|
"\\==(X, a) succeeds (unbound var differs from atom)"
|
||||||
|
(pl-solve-once! pl-lp-db (pl-lp-goal "\\==(X, a)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-lp-test!
|
||||||
|
"\\==(1, 2) succeeds"
|
||||||
|
(pl-solve-once! pl-lp-db (pl-lp-goal "\\==(1, 2)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; ── flatten/2 ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-lp-env-fl1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "flatten([], F)" pl-lp-env-fl1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"flatten([], []) -> empty"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-lp-env-fl1 "F")))
|
||||||
|
"[]")
|
||||||
|
|
||||||
|
(define pl-lp-env-fl2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "flatten([1,2,3], F)" pl-lp-env-fl2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"flatten([1,2,3], F) -> [1,2,3]"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-lp-env-fl2 "F")))
|
||||||
|
".(1, .(2, .(3, [])))")
|
||||||
|
|
||||||
|
(define pl-lp-env-fl3 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "flatten([1,[2,[3]],4], F)" pl-lp-env-fl3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"flatten([1,[2,[3]],4], F) -> [1,2,3,4]"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-lp-env-fl3 "F")))
|
||||||
|
".(1, .(2, .(3, .(4, []))))")
|
||||||
|
|
||||||
|
(define pl-lp-env-fl4 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "flatten([[a,b],[c]], F)" pl-lp-env-fl4)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"flatten([[a,b],[c]], F) -> [a,b,c]"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-lp-env-fl4 "F")))
|
||||||
|
".(a, .(b, .(c, [])))")
|
||||||
|
|
||||||
|
;; ── numlist/3 ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-lp-env-nl1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "numlist(1, 5, L)" pl-lp-env-nl1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"numlist(1,5,L) -> [1,2,3,4,5]"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-lp-env-nl1 "L")))
|
||||||
|
".(1, .(2, .(3, .(4, .(5, [])))))")
|
||||||
|
|
||||||
|
(define pl-lp-env-nl2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "numlist(3, 3, L)" pl-lp-env-nl2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"numlist(3,3,L) -> [3]"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-lp-env-nl2 "L")))
|
||||||
|
".(3, [])")
|
||||||
|
|
||||||
|
(pl-lp-test!
|
||||||
|
"numlist(5, 3, L) fails (Low > High)"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "numlist(5, 3, L)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; ── atomic_list_concat/2 ───────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-lp-env-alc1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "atomic_list_concat([a, b, c], R)" pl-lp-env-alc1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"atomic_list_concat([a,b,c], R) -> abc"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-lp-env-alc1 "R")))
|
||||||
|
"abc")
|
||||||
|
|
||||||
|
(define pl-lp-env-alc2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "atomic_list_concat([hello, world], R)" pl-lp-env-alc2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"atomic_list_concat([hello,world], R) -> helloworld"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-lp-env-alc2 "R")))
|
||||||
|
"helloworld")
|
||||||
|
|
||||||
|
;; ── atomic_list_concat/3 ───────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-lp-env-alcs1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "atomic_list_concat([a, b, c], '-', R)" pl-lp-env-alcs1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"atomic_list_concat([a,b,c], '-', R) -> a-b-c"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-lp-env-alcs1 "R")))
|
||||||
|
"a-b-c")
|
||||||
|
|
||||||
|
(define pl-lp-env-alcs2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "atomic_list_concat([x], '-', R)" pl-lp-env-alcs2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"atomic_list_concat([x], '-', R) -> x (single element, no sep)"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-lp-env-alcs2 "R")))
|
||||||
|
"x")
|
||||||
|
|
||||||
|
;; ── sum_list/2 ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-lp-env-sl1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "sum_list([1,2,3], S)" pl-lp-env-sl1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"sum_list([1,2,3], S) -> 6"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-lp-env-sl1 "S")))
|
||||||
|
6)
|
||||||
|
|
||||||
|
(define pl-lp-env-sl2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "sum_list([10], S)" pl-lp-env-sl2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"sum_list([10], S) -> 10"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-lp-env-sl2 "S")))
|
||||||
|
10)
|
||||||
|
|
||||||
|
(define pl-lp-env-sl3 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "sum_list([], S)" pl-lp-env-sl3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"sum_list([], S) -> 0"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-lp-env-sl3 "S")))
|
||||||
|
0)
|
||||||
|
|
||||||
|
;; ── max_list/2 ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-lp-env-mx1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "max_list([3,1,4,1,5,9,2,6], M)" pl-lp-env-mx1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"max_list([3,1,4,1,5,9,2,6], M) -> 9"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-lp-env-mx1 "M")))
|
||||||
|
9)
|
||||||
|
|
||||||
|
(define pl-lp-env-mx2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "max_list([7], M)" pl-lp-env-mx2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"max_list([7], M) -> 7"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-lp-env-mx2 "M")))
|
||||||
|
7)
|
||||||
|
|
||||||
|
;; ── min_list/2 ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-lp-env-mn1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "min_list([3,1,4,1,5,9,2,6], M)" pl-lp-env-mn1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"min_list([3,1,4,1,5,9,2,6], M) -> 1"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-lp-env-mn1 "M")))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(define pl-lp-env-mn2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "min_list([5,2,8], M)" pl-lp-env-mn2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"min_list([5,2,8], M) -> 2"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-lp-env-mn2 "M")))
|
||||||
|
2)
|
||||||
|
|
||||||
|
;; ── delete/3 ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
(define pl-lp-env-del1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "delete([1,2,3,2,1], 2, R)" pl-lp-env-del1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"delete([1,2,3,2,1], 2, R) -> [1,3,1]"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-lp-env-del1 "R")))
|
||||||
|
".(1, .(3, .(1, [])))")
|
||||||
|
|
||||||
|
(define pl-lp-env-del2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "delete([a,b,c], d, R)" pl-lp-env-del2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"delete([a,b,c], d, R) -> [a,b,c] (nothing deleted)"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-lp-env-del2 "R")))
|
||||||
|
".(a, .(b, .(c, [])))")
|
||||||
|
|
||||||
|
(define pl-lp-env-del3 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-lp-db
|
||||||
|
(pl-lp-goal "delete([], x, R)" pl-lp-env-del3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-lp-test!
|
||||||
|
"delete([], x, R) -> []"
|
||||||
|
(pl-format-term (pl-walk-deep (dict-get pl-lp-env-del3 "R")))
|
||||||
|
"[]")
|
||||||
|
|
||||||
|
(define pl-list-predicates-tests-run! (fn () {:failed pl-lp-test-fail :passed pl-lp-test-pass :total pl-lp-test-count :failures pl-lp-test-failures}))
|
||||||
197
lib/prolog/tests/meta_call.sx
Normal file
197
lib/prolog/tests/meta_call.sx
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
;; lib/prolog/tests/meta_call.sx — forall/2, maplist/2, maplist/3, include/3, exclude/3
|
||||||
|
(define pl-mc-test-count 0)
|
||||||
|
(define pl-mc-test-pass 0)
|
||||||
|
(define pl-mc-test-fail 0)
|
||||||
|
(define pl-mc-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-mc-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-mc-test-count (+ pl-mc-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-mc-test-pass (+ pl-mc-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-mc-test-fail (+ pl-mc-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-mc-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-mc-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-mc-term-to-sx
|
||||||
|
(fn
|
||||||
|
(t)
|
||||||
|
(cond
|
||||||
|
((pl-num? t) (pl-num-val t))
|
||||||
|
((pl-atom? t) (pl-atom-name t))
|
||||||
|
(else t))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-mc-list-sx
|
||||||
|
(fn
|
||||||
|
(t)
|
||||||
|
(let
|
||||||
|
((w (pl-walk-deep t)))
|
||||||
|
(cond
|
||||||
|
((and (pl-atom? w) (= (pl-atom-name w) "[]")) (list))
|
||||||
|
((and (pl-compound? w) (= (pl-fun w) "."))
|
||||||
|
(cons
|
||||||
|
(pl-mc-term-to-sx (first (pl-args w)))
|
||||||
|
(pl-mc-list-sx (nth (pl-args w) 1))))
|
||||||
|
(else (list :not-list))))))
|
||||||
|
|
||||||
|
(define pl-mc-db (pl-mk-db))
|
||||||
|
|
||||||
|
(pl-db-load!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-parse "member(X, [X|_]). member(X, [_|T]) :- member(X, T)."))
|
||||||
|
|
||||||
|
(pl-db-load! pl-mc-db (pl-parse "double(X, Y) :- Y is X * 2."))
|
||||||
|
|
||||||
|
(pl-db-load! pl-mc-db (pl-parse "even(X) :- 0 is X mod 2."))
|
||||||
|
|
||||||
|
;; -- forall/2 --
|
||||||
|
|
||||||
|
(pl-mc-test!
|
||||||
|
"forall(member(X,[2,4,6]), 0 is X mod 2) — all even"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "forall(member(X,[2,4,6]), 0 is X mod 2)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mc-test!
|
||||||
|
"forall(member(X,[2,3,6]), 0 is X mod 2) — 3 is odd, fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "forall(member(X,[2,3,6]), 0 is X mod 2)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-mc-test!
|
||||||
|
"forall(member(_,[]), true) — vacuously true"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "forall(member(_,[]), true)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; -- maplist/2 --
|
||||||
|
|
||||||
|
(pl-mc-test!
|
||||||
|
"maplist(atom, [a,b,c]) — all atoms"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "maplist(atom, [a,b,c])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mc-test!
|
||||||
|
"maplist(atom, [a,1,c]) — 1 is not atom, fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "maplist(atom, [a,1,c])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-mc-test!
|
||||||
|
"maplist(atom, []) — vacuously true"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "maplist(atom, [])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; -- maplist/3 --
|
||||||
|
|
||||||
|
(pl-mc-test!
|
||||||
|
"maplist(double, [1,2,3], [2,4,6]) — deterministic check"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "maplist(double, [1,2,3], [2,4,6])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mc-test!
|
||||||
|
"maplist(double, [1,2,3], [2,4,7]) — wrong result fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "maplist(double, [1,2,3], [2,4,7])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(define pl-mc-env-ml3 {:L (pl-mk-rt-var "L")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "maplist(double, [1,2,3], L)" pl-mc-env-ml3)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-mc-test!
|
||||||
|
"maplist(double, [1,2,3], L) — L bound to [2,4,6]"
|
||||||
|
(pl-mc-list-sx (dict-get pl-mc-env-ml3 "L"))
|
||||||
|
(list 2 4 6))
|
||||||
|
|
||||||
|
;; -- include/3 --
|
||||||
|
|
||||||
|
(pl-mc-test!
|
||||||
|
"include(even, [1,2,3,4,5,6], [2,4,6])"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "include(even, [1,2,3,4,5,6], [2,4,6])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mc-test!
|
||||||
|
"include(even, [], [])"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "include(even, [], [])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(define pl-mc-env-inc {:R (pl-mk-rt-var "R")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "include(even, [1,2,3,4,5,6], R)" pl-mc-env-inc)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-mc-test!
|
||||||
|
"include(even, [1,2,3,4,5,6], R) — R bound to [2,4,6]"
|
||||||
|
(pl-mc-list-sx (dict-get pl-mc-env-inc "R"))
|
||||||
|
(list 2 4 6))
|
||||||
|
|
||||||
|
;; -- exclude/3 --
|
||||||
|
|
||||||
|
(pl-mc-test!
|
||||||
|
"exclude(even, [1,2,3,4,5,6], [1,3,5])"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "exclude(even, [1,2,3,4,5,6], [1,3,5])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mc-test!
|
||||||
|
"exclude(even, [], [])"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "exclude(even, [], [])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(define pl-mc-env-exc {:R (pl-mk-rt-var "R")})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mc-db
|
||||||
|
(pl-mc-goal "exclude(even, [1,2,3,4,5,6], R)" pl-mc-env-exc)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-mc-test!
|
||||||
|
"exclude(even, [1,2,3,4,5,6], R) — R bound to [1,3,5]"
|
||||||
|
(pl-mc-list-sx (dict-get pl-mc-env-exc "R"))
|
||||||
|
(list 1 3 5))
|
||||||
|
|
||||||
|
(define pl-meta-call-tests-run! (fn () {:failed pl-mc-test-fail :passed pl-mc-test-pass :total pl-mc-test-count :failures pl-mc-test-failures}))
|
||||||
252
lib/prolog/tests/meta_predicates.sx
Normal file
252
lib/prolog/tests/meta_predicates.sx
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
;; lib/prolog/tests/meta_predicates.sx — \+/1, not/1, once/1, ignore/1, ground/1, sort/2, msort/2, atom_number/2, number_string/2
|
||||||
|
|
||||||
|
(define pl-mp-test-count 0)
|
||||||
|
(define pl-mp-test-pass 0)
|
||||||
|
(define pl-mp-test-fail 0)
|
||||||
|
(define pl-mp-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-mp-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-mp-test-count (+ pl-mp-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-mp-test-pass (+ pl-mp-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-mp-test-fail (+ pl-mp-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-mp-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-mp-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
(define pl-mp-db (pl-mk-db))
|
||||||
|
(pl-db-load!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-parse "member(X, [X|_]). member(X, [_|T]) :- member(X, T)."))
|
||||||
|
|
||||||
|
;; -- \+/1 --
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"\\+(fail) succeeds"
|
||||||
|
(pl-solve-once! pl-mp-db (pl-mp-goal "\\+(fail)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"\\+(true) fails"
|
||||||
|
(pl-solve-once! pl-mp-db (pl-mp-goal "\\+(true)" {}) (pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"\\+(member(d, [a,b,c])) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "\\+(member(d, [a,b,c]))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"\\+(member(a, [a,b,c])) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "\\+(member(a, [a,b,c]))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(define pl-mp-env-neg {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "\\+(X = 5)" pl-mp-env-neg)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-mp-test!
|
||||||
|
"\\+(X=5) fails, X stays unbound (bindings undone)"
|
||||||
|
(nil? (pl-var-binding (dict-get pl-mp-env-neg "X")))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; -- not/1 --
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"not(fail) succeeds"
|
||||||
|
(pl-solve-once! pl-mp-db (pl-mp-goal "not(fail)" {}) (pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"not(true) fails"
|
||||||
|
(pl-solve-once! pl-mp-db (pl-mp-goal "not(true)" {}) (pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; -- once/1 --
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"once(member(X,[1,2,3])) succeeds once"
|
||||||
|
(pl-solve-count!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "once(member(X,[1,2,3]))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(define pl-mp-env-once {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "once(member(X,[1,2,3]))" pl-mp-env-once)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-mp-test!
|
||||||
|
"once(member(X,[1,2,3])): X=1 (first solution)"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-mp-env-once "X")))
|
||||||
|
1)
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"once(fail) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "once(fail)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; -- ignore/1 --
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"ignore(true) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "ignore(true)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"ignore(fail) still succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "ignore(fail)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; -- ground/1 --
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"ground(foo(1, a)) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "ground(foo(1, a))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"ground(foo(X, a)) fails (X unbound)"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "ground(foo(X, a))" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"ground(42) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "ground(42)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; -- sort/2 --
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"sort([b,a,c], [a,b,c])"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "sort([b,a,c], [a,b,c])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"sort([b,a,a,c], [a,b,c]) (removes duplicates)"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "sort([b,a,a,c], [a,b,c])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"sort([], [])"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "sort([], [])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; -- msort/2 --
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"msort([b,a,a,c], [a,a,b,c]) (keeps duplicates)"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "msort([b,a,a,c], [a,a,b,c])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"msort([3,1,2,1], [1,1,2,3])"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "msort([3,1,2,1], [1,1,2,3])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
;; -- atom_number/2 --
|
||||||
|
|
||||||
|
(define pl-mp-env-an1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "atom_number('42', N)" pl-mp-env-an1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-mp-test!
|
||||||
|
"atom_number('42', N) -> N=42"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-mp-env-an1 "N")))
|
||||||
|
42)
|
||||||
|
|
||||||
|
(define pl-mp-env-an2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "atom_number(A, 7)" pl-mp-env-an2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-mp-test!
|
||||||
|
"atom_number(A, 7) -> A='7'"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-mp-env-an2 "A")))
|
||||||
|
"7")
|
||||||
|
|
||||||
|
(pl-mp-test!
|
||||||
|
"atom_number(foo, N) fails (not a number)"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "atom_number(foo, N)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
;; -- number_string/2 --
|
||||||
|
|
||||||
|
(define pl-mp-env-ns1 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "number_string(42, S)" pl-mp-env-ns1)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-mp-test!
|
||||||
|
"number_string(42, S) -> S='42'"
|
||||||
|
(pl-atom-name (pl-walk-deep (dict-get pl-mp-env-ns1 "S")))
|
||||||
|
"42")
|
||||||
|
|
||||||
|
(define pl-mp-env-ns2 {})
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mp-db
|
||||||
|
(pl-mp-goal "number_string(N, '3.14')" pl-mp-env-ns2)
|
||||||
|
(pl-mk-trail))
|
||||||
|
(pl-mp-test!
|
||||||
|
"number_string(N, '3.14') -> N=3.14"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-mp-env-ns2 "N")))
|
||||||
|
3.14)
|
||||||
|
|
||||||
|
(define pl-meta-predicates-tests-run! (fn () {:failed pl-mp-test-fail :passed pl-mp-test-pass :total pl-mp-test-count :failures pl-mp-test-failures}))
|
||||||
193
lib/prolog/tests/operators.sx
Normal file
193
lib/prolog/tests/operators.sx
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
;; lib/prolog/tests/operators.sx — operator-table parsing + comparison built-ins.
|
||||||
|
|
||||||
|
(define pl-op-test-count 0)
|
||||||
|
(define pl-op-test-pass 0)
|
||||||
|
(define pl-op-test-fail 0)
|
||||||
|
(define pl-op-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-op-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-op-test-count (+ pl-op-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-op-test-pass (+ pl-op-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-op-test-fail (+ pl-op-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-op-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define pl-op-empty-db (pl-mk-db))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-op-body
|
||||||
|
(fn (src) (nth (first (pl-parse (str "g :- " src "."))) 2)))
|
||||||
|
|
||||||
|
(define pl-op-goal (fn (src env) (pl-instantiate (pl-op-body src) env)))
|
||||||
|
|
||||||
|
;; ── parsing tests ──
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"infix +"
|
||||||
|
(pl-op-body "a + b")
|
||||||
|
(list "compound" "+" (list (list "atom" "a") (list "atom" "b"))))
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"infix * tighter than +"
|
||||||
|
(pl-op-body "a + b * c")
|
||||||
|
(list
|
||||||
|
"compound"
|
||||||
|
"+"
|
||||||
|
(list
|
||||||
|
(list "atom" "a")
|
||||||
|
(list "compound" "*" (list (list "atom" "b") (list "atom" "c"))))))
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"parens override precedence"
|
||||||
|
(pl-op-body "(a + b) * c")
|
||||||
|
(list
|
||||||
|
"compound"
|
||||||
|
"*"
|
||||||
|
(list
|
||||||
|
(list "compound" "+" (list (list "atom" "a") (list "atom" "b")))
|
||||||
|
(list "atom" "c"))))
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"+ is yfx (left-assoc)"
|
||||||
|
(pl-op-body "a + b + c")
|
||||||
|
(list
|
||||||
|
"compound"
|
||||||
|
"+"
|
||||||
|
(list
|
||||||
|
(list "compound" "+" (list (list "atom" "a") (list "atom" "b")))
|
||||||
|
(list "atom" "c"))))
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"; is xfy (right-assoc)"
|
||||||
|
(pl-op-body "a ; b ; c")
|
||||||
|
(list
|
||||||
|
"compound"
|
||||||
|
";"
|
||||||
|
(list
|
||||||
|
(list "atom" "a")
|
||||||
|
(list "compound" ";" (list (list "atom" "b") (list "atom" "c"))))))
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"= folds at 700"
|
||||||
|
(pl-op-body "X = 5")
|
||||||
|
(list "compound" "=" (list (list "var" "X") (list "num" 5))))
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"is + nests via 700>500>400"
|
||||||
|
(pl-op-body "X is 2 + 3 * 4")
|
||||||
|
(list
|
||||||
|
"compound"
|
||||||
|
"is"
|
||||||
|
(list
|
||||||
|
(list "var" "X")
|
||||||
|
(list
|
||||||
|
"compound"
|
||||||
|
"+"
|
||||||
|
(list
|
||||||
|
(list "num" 2)
|
||||||
|
(list "compound" "*" (list (list "num" 3) (list "num" 4))))))))
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"< parses at 700"
|
||||||
|
(pl-op-body "2 < 3")
|
||||||
|
(list "compound" "<" (list (list "num" 2) (list "num" 3))))
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"mod parses as yfx 400"
|
||||||
|
(pl-op-body "10 mod 3")
|
||||||
|
(list "compound" "mod" (list (list "num" 10) (list "num" 3))))
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"comma in body folds right-assoc"
|
||||||
|
(pl-op-body "a, b, c")
|
||||||
|
(list
|
||||||
|
"compound"
|
||||||
|
","
|
||||||
|
(list
|
||||||
|
(list "atom" "a")
|
||||||
|
(list "compound" "," (list (list "atom" "b") (list "atom" "c"))))))
|
||||||
|
|
||||||
|
;; ── solver tests via infix ──
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"X is 2 + 3 binds X = 5"
|
||||||
|
(let
|
||||||
|
((env {}) (trail (pl-mk-trail)))
|
||||||
|
(begin
|
||||||
|
(pl-solve-once! pl-op-empty-db (pl-op-goal "X is 2 + 3" env) trail)
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get env "X")))))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"infix conjunction parses + solves"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-op-empty-db
|
||||||
|
(pl-op-goal "X = 5, X = 5" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"infix mismatch fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-op-empty-db
|
||||||
|
(pl-op-goal "X = 5, X = 6" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"infix disjunction picks left"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-op-empty-db
|
||||||
|
(pl-op-goal "true ; fail" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"2 < 5 succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-op-empty-db
|
||||||
|
(pl-op-goal "2 < 5" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"5 < 2 fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-op-empty-db
|
||||||
|
(pl-op-goal "5 < 2" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"5 >= 5 succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-op-empty-db
|
||||||
|
(pl-op-goal "5 >= 5" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"3 =< 5 succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-op-empty-db
|
||||||
|
(pl-op-goal "3 =< 5" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-op-test!
|
||||||
|
"infix < with arithmetic both sides"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-op-empty-db
|
||||||
|
(pl-op-goal "1 + 2 < 2 * 3" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(define pl-operators-tests-run! (fn () {:failed pl-op-test-fail :passed pl-op-test-pass :total pl-op-test-count :failures pl-op-test-failures}))
|
||||||
5
lib/prolog/tests/programs/append.pl
Normal file
5
lib/prolog/tests/programs/append.pl
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
%% append/3 — list concatenation, classic Prolog
|
||||||
|
%% Two clauses: empty-prefix base case + recursive cons-prefix.
|
||||||
|
%% Bidirectional — works in all modes: build, check, split.
|
||||||
|
append([], L, L).
|
||||||
|
append([H|T], L, [H|R]) :- append(T, L, R).
|
||||||
114
lib/prolog/tests/programs/append.sx
Normal file
114
lib/prolog/tests/programs/append.sx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
;; lib/prolog/tests/programs/append.sx — append/3 test runner
|
||||||
|
;;
|
||||||
|
;; Mirrors the Prolog source in append.pl (embedded as a string here because
|
||||||
|
;; the SX runtime has no file-read primitive yet).
|
||||||
|
|
||||||
|
(define pl-ap-test-count 0)
|
||||||
|
(define pl-ap-test-pass 0)
|
||||||
|
(define pl-ap-test-fail 0)
|
||||||
|
(define pl-ap-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-ap-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-ap-test-count (+ pl-ap-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-ap-test-pass (+ pl-ap-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-ap-test-fail (+ pl-ap-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-ap-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-ap-term-to-sx
|
||||||
|
(fn
|
||||||
|
(t)
|
||||||
|
(cond
|
||||||
|
((pl-num? t) (pl-num-val t))
|
||||||
|
((pl-atom? t) (pl-atom-name t))
|
||||||
|
(true (list :complex)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-ap-list-walked
|
||||||
|
(fn
|
||||||
|
(w)
|
||||||
|
(cond
|
||||||
|
((and (pl-atom? w) (= (pl-atom-name w) "[]")) (list))
|
||||||
|
((and (pl-compound? w) (= (pl-fun w) ".") (= (len (pl-args w)) 2))
|
||||||
|
(cons
|
||||||
|
(pl-ap-term-to-sx (first (pl-args w)))
|
||||||
|
(pl-ap-list-walked (nth (pl-args w) 1))))
|
||||||
|
(true (list :not-list)))))
|
||||||
|
|
||||||
|
(define pl-ap-list-to-sx (fn (t) (pl-ap-list-walked (pl-walk-deep t))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-ap-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-ap-prog-src
|
||||||
|
"append([], L, L). append([H|T], L, [H|R]) :- append(T, L, R).")
|
||||||
|
|
||||||
|
(define pl-ap-db (pl-mk-db))
|
||||||
|
|
||||||
|
(pl-db-load! pl-ap-db (pl-parse pl-ap-prog-src))
|
||||||
|
|
||||||
|
(define pl-ap-env-1 {})
|
||||||
|
(define pl-ap-goal-1 (pl-ap-goal "append([], [a, b], X)" pl-ap-env-1))
|
||||||
|
(pl-solve-once! pl-ap-db pl-ap-goal-1 (pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-ap-test!
|
||||||
|
"append([], [a, b], X) → X = [a, b]"
|
||||||
|
(pl-ap-list-to-sx (dict-get pl-ap-env-1 "X"))
|
||||||
|
(list "a" "b"))
|
||||||
|
|
||||||
|
(define pl-ap-env-2 {})
|
||||||
|
(define pl-ap-goal-2 (pl-ap-goal "append([1, 2], [3, 4], X)" pl-ap-env-2))
|
||||||
|
(pl-solve-once! pl-ap-db pl-ap-goal-2 (pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-ap-test!
|
||||||
|
"append([1, 2], [3, 4], X) → X = [1, 2, 3, 4]"
|
||||||
|
(pl-ap-list-to-sx (dict-get pl-ap-env-2 "X"))
|
||||||
|
(list 1 2 3 4))
|
||||||
|
|
||||||
|
(pl-ap-test!
|
||||||
|
"append([1], [2, 3], [1, 2, 3]) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ap-db
|
||||||
|
(pl-ap-goal "append([1], [2, 3], [1, 2, 3])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-ap-test!
|
||||||
|
"append([1, 2], [3], [1, 2, 4]) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-ap-db
|
||||||
|
(pl-ap-goal "append([1, 2], [3], [1, 2, 4])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-ap-test!
|
||||||
|
"append(X, Y, [1, 2, 3]) backtracks 4 times"
|
||||||
|
(pl-solve-count!
|
||||||
|
pl-ap-db
|
||||||
|
(pl-ap-goal "append(X, Y, [1, 2, 3])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
4)
|
||||||
|
|
||||||
|
(define pl-ap-env-6 {})
|
||||||
|
(define pl-ap-goal-6 (pl-ap-goal "append(X, [3], [1, 2, 3])" pl-ap-env-6))
|
||||||
|
(pl-solve-once! pl-ap-db pl-ap-goal-6 (pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-ap-test!
|
||||||
|
"append(X, [3], [1, 2, 3]) deduces X = [1, 2]"
|
||||||
|
(pl-ap-list-to-sx (dict-get pl-ap-env-6 "X"))
|
||||||
|
(list 1 2))
|
||||||
|
|
||||||
|
(define pl-append-tests-run! (fn () {:failed pl-ap-test-fail :passed pl-ap-test-pass :total pl-ap-test-count :failures pl-ap-test-failures}))
|
||||||
24
lib/prolog/tests/programs/family.pl
Normal file
24
lib/prolog/tests/programs/family.pl
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
%% family — facts + transitive ancestor + derived relations.
|
||||||
|
%% Five-generation tree: tom -> bob -> {ann, pat} -> jim, plus tom's
|
||||||
|
%% other child liz.
|
||||||
|
|
||||||
|
parent(tom, bob).
|
||||||
|
parent(tom, liz).
|
||||||
|
parent(bob, ann).
|
||||||
|
parent(bob, pat).
|
||||||
|
parent(pat, jim).
|
||||||
|
|
||||||
|
male(tom).
|
||||||
|
male(bob).
|
||||||
|
male(jim).
|
||||||
|
male(pat).
|
||||||
|
female(liz).
|
||||||
|
female(ann).
|
||||||
|
|
||||||
|
father(F, C) :- parent(F, C), male(F).
|
||||||
|
mother(M, C) :- parent(M, C), female(M).
|
||||||
|
|
||||||
|
ancestor(X, Y) :- parent(X, Y).
|
||||||
|
ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y).
|
||||||
|
|
||||||
|
sibling(X, Y) :- parent(P, X), parent(P, Y), \=(X, Y).
|
||||||
116
lib/prolog/tests/programs/family.sx
Normal file
116
lib/prolog/tests/programs/family.sx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
;; lib/prolog/tests/programs/family.sx — facts + ancestor + sibling relations.
|
||||||
|
|
||||||
|
(define pl-fa-test-count 0)
|
||||||
|
(define pl-fa-test-pass 0)
|
||||||
|
(define pl-fa-test-fail 0)
|
||||||
|
(define pl-fa-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-fa-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-fa-test-count (+ pl-fa-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-fa-test-pass (+ pl-fa-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-fa-test-fail (+ pl-fa-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-fa-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-fa-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-fa-prog-src
|
||||||
|
"parent(tom, bob). parent(tom, liz). parent(bob, ann). parent(bob, pat). parent(pat, jim). male(tom). male(bob). male(jim). male(pat). female(liz). female(ann). father(F, C) :- parent(F, C), male(F). mother(M, C) :- parent(M, C), female(M). ancestor(X, Y) :- parent(X, Y). ancestor(X, Y) :- parent(X, Z), ancestor(Z, Y). sibling(X, Y) :- parent(P, X), parent(P, Y), \\=(X, Y).")
|
||||||
|
|
||||||
|
(define pl-fa-db (pl-mk-db))
|
||||||
|
(pl-db-load! pl-fa-db (pl-parse pl-fa-prog-src))
|
||||||
|
|
||||||
|
(pl-fa-test!
|
||||||
|
"parent(tom, bob) is a fact"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fa-db
|
||||||
|
(pl-fa-goal "parent(tom, bob)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-fa-test!
|
||||||
|
"parent(tom, ann) — not a direct parent"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fa-db
|
||||||
|
(pl-fa-goal "parent(tom, ann)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-fa-test!
|
||||||
|
"5 parent/2 facts in total"
|
||||||
|
(pl-solve-count!
|
||||||
|
pl-fa-db
|
||||||
|
(pl-fa-goal "parent(X, Y)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(pl-fa-test!
|
||||||
|
"ancestor(tom, jim) — three-step transitive"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fa-db
|
||||||
|
(pl-fa-goal "ancestor(tom, jim)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-fa-test!
|
||||||
|
"tom has 5 ancestors-of: bob, liz, ann, pat, jim"
|
||||||
|
(pl-solve-count!
|
||||||
|
pl-fa-db
|
||||||
|
(pl-fa-goal "ancestor(tom, X)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(pl-fa-test!
|
||||||
|
"father(bob, ann) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fa-db
|
||||||
|
(pl-fa-goal "father(bob, ann)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-fa-test!
|
||||||
|
"father(liz, ann) fails (liz is female)"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fa-db
|
||||||
|
(pl-fa-goal "father(liz, ann)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-fa-test!
|
||||||
|
"mother(liz, X) fails (liz has no children)"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fa-db
|
||||||
|
(pl-fa-goal "mother(liz, X)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-fa-test!
|
||||||
|
"sibling(ann, pat) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fa-db
|
||||||
|
(pl-fa-goal "sibling(ann, pat)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-fa-test!
|
||||||
|
"sibling(ann, ann) fails by \\="
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-fa-db
|
||||||
|
(pl-fa-goal "sibling(ann, ann)" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(define pl-family-tests-run! (fn () {:failed pl-fa-test-fail :passed pl-fa-test-pass :total pl-fa-test-count :failures pl-fa-test-failures}))
|
||||||
4
lib/prolog/tests/programs/member.pl
Normal file
4
lib/prolog/tests/programs/member.pl
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
%% member/2 — list membership.
|
||||||
|
%% Generates all solutions on backtracking when the element is unbound.
|
||||||
|
member(X, [X|_]).
|
||||||
|
member(X, [_|T]) :- member(X, T).
|
||||||
91
lib/prolog/tests/programs/member.sx
Normal file
91
lib/prolog/tests/programs/member.sx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
;; lib/prolog/tests/programs/member.sx — member/2 generator.
|
||||||
|
|
||||||
|
(define pl-mb-test-count 0)
|
||||||
|
(define pl-mb-test-pass 0)
|
||||||
|
(define pl-mb-test-fail 0)
|
||||||
|
(define pl-mb-test-failures (list))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-mb-test!
|
||||||
|
(fn
|
||||||
|
(name got expected)
|
||||||
|
(begin
|
||||||
|
(set! pl-mb-test-count (+ pl-mb-test-count 1))
|
||||||
|
(if
|
||||||
|
(= got expected)
|
||||||
|
(set! pl-mb-test-pass (+ pl-mb-test-pass 1))
|
||||||
|
(begin
|
||||||
|
(set! pl-mb-test-fail (+ pl-mb-test-fail 1))
|
||||||
|
(append!
|
||||||
|
pl-mb-test-failures
|
||||||
|
(str name "\n expected: " expected "\n got: " got)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
pl-mb-goal
|
||||||
|
(fn
|
||||||
|
(src env)
|
||||||
|
(pl-instantiate (nth (first (pl-parse (str "g :- " src "."))) 2) env)))
|
||||||
|
|
||||||
|
(define pl-mb-prog-src "member(X, [X|_]). member(X, [_|T]) :- member(X, T).")
|
||||||
|
|
||||||
|
(define pl-mb-db (pl-mk-db))
|
||||||
|
(pl-db-load! pl-mb-db (pl-parse pl-mb-prog-src))
|
||||||
|
|
||||||
|
(pl-mb-test!
|
||||||
|
"member(2, [1, 2, 3]) succeeds"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mb-db
|
||||||
|
(pl-mb-goal "member(2, [1, 2, 3])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(pl-mb-test!
|
||||||
|
"member(4, [1, 2, 3]) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mb-db
|
||||||
|
(pl-mb-goal "member(4, [1, 2, 3])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-mb-test!
|
||||||
|
"member(X, []) fails"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mb-db
|
||||||
|
(pl-mb-goal "member(X, [])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
false)
|
||||||
|
|
||||||
|
(pl-mb-test!
|
||||||
|
"member(X, [a, b, c]) generates 3 solutions"
|
||||||
|
(pl-solve-count!
|
||||||
|
pl-mb-db
|
||||||
|
(pl-mb-goal "member(X, [a, b, c])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(define pl-mb-env-1 {})
|
||||||
|
(define pl-mb-goal-1 (pl-mb-goal "member(X, [11, 22, 33])" pl-mb-env-1))
|
||||||
|
(pl-solve-once! pl-mb-db pl-mb-goal-1 (pl-mk-trail))
|
||||||
|
|
||||||
|
(pl-mb-test!
|
||||||
|
"member(X, [11, 22, 33]) first solution X = 11"
|
||||||
|
(pl-num-val (pl-walk-deep (dict-get pl-mb-env-1 "X")))
|
||||||
|
11)
|
||||||
|
|
||||||
|
(pl-mb-test!
|
||||||
|
"member(2, [1, 2, 3, 2, 1]) matches twice on backtrack"
|
||||||
|
(pl-solve-count!
|
||||||
|
pl-mb-db
|
||||||
|
(pl-mb-goal "member(2, [1, 2, 3, 2, 1])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(pl-mb-test!
|
||||||
|
"member with unbound list cell unifies"
|
||||||
|
(pl-solve-once!
|
||||||
|
pl-mb-db
|
||||||
|
(pl-mb-goal "member(a, [X, b, c])" {})
|
||||||
|
(pl-mk-trail))
|
||||||
|
true)
|
||||||
|
|
||||||
|
(define pl-member-tests-run! (fn () {:failed pl-mb-test-fail :passed pl-mb-test-pass :total pl-mb-test-count :failures pl-mb-test-failures}))
|
||||||
27
lib/prolog/tests/programs/nqueens.pl
Normal file
27
lib/prolog/tests/programs/nqueens.pl
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
%% nqueens — permutation-and-test formulation.
|
||||||
|
%% Caller passes the row list [1..N]; queens/2 finds N column placements
|
||||||
|
%% s.t. no two queens attack on a diagonal. Same-column attacks are
|
||||||
|
%% structurally impossible — Qs is a permutation, all distinct.
|
||||||
|
%%
|
||||||
|
%% No `>/2` `</2` `=</2` built-ins yet, so range/3 is omitted; tests pass
|
||||||
|
%; the literal range list. Once the operator table lands and arithmetic
|
||||||
|
%% comparison built-ins are in, range/3 can be added.
|
||||||
|
queens(L, Qs) :- permute(L, Qs), safe(Qs).
|
||||||
|
|
||||||
|
permute([], []).
|
||||||
|
permute(L, [H|T]) :- select(H, L, R), permute(R, T).
|
||||||
|
|
||||||
|
select(X, [X|T], T).
|
||||||
|
select(X, [H|T], [H|R]) :- select(X, T, R).
|
||||||
|
|
||||||
|
safe([]).
|
||||||
|
safe([Q|Qs]) :- safe(Qs), no_attack(Q, Qs, 1).
|
||||||
|
|
||||||
|
no_attack(_, [], _).
|
||||||
|
no_attack(Q, [Q1|Qs], D) :-
|
||||||
|
is(D2, +(Q, D)),
|
||||||
|
\=(D2, Q1),
|
||||||
|
is(D3, -(Q, D)),
|
||||||
|
\=(D3, Q1),
|
||||||
|
is(D1, +(D, 1)),
|
||||||
|
no_attack(Q, Qs, D1).
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user