diff --git a/lib/apl/parser.sx b/lib/apl/parser.sx index 80945d3c..ee49f14b 100644 --- a/lib/apl/parser.sx +++ b/lib/apl/parser.sx @@ -25,8 +25,9 @@ ; Glyph classification sets ; ============================================================ -(define apl-parse-op-glyphs - (list "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@")) +(define + apl-parse-op-glyphs + (list "/" "⌿" "\\" "⍀" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@")) (define apl-parse-fn-glyphs diff --git a/lib/apl/test.sh b/lib/apl/test.sh index 8bad5b17..4b0e6161 100755 --- a/lib/apl/test.sh +++ b/lib/apl/test.sh @@ -39,6 +39,7 @@ cat > "$TMPFILE" << 'EPOCHS' (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)") EPOCHS diff --git a/lib/apl/tests/programs-e2e.sx b/lib/apl/tests/programs-e2e.sx new file mode 100644 index 00000000..33ff6b29 --- /dev/null +++ b/lib/apl/tests/programs-e2e.sx @@ -0,0 +1,96 @@ +; 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)) diff --git a/lib/apl/tokenizer.sx b/lib/apl/tokenizer.sx index a842e48e..76dcf5be 100644 --- a/lib/apl/tokenizer.sx +++ b/lib/apl/tokenizer.sx @@ -2,7 +2,7 @@ (list "+" "-" "×" "÷" "*" "⍟" "⌈" "⌊" "|" "!" "?" "○" "~" "<" "≤" "=" "≥" ">" "≠" "≢" "≡" "∊" "∧" "∨" "⍱" "⍲" "," "⍪" "⍴" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆" "∪" "∩" "⍳" "⍸" "⌷" "⍋" "⍒" "⊥" "⊤" "⊣" "⊢" "⍎" "⍕" - "⍺" "⍵" "∇" "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@" "¯")) + "⍺" "⍵" "∇" "/" "⌿" "\\" "⍀" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@" "¯")) (define apl-glyph? (fn (ch) diff --git a/plans/apl-on-sx.md b/plans/apl-on-sx.md index 364d8921..c156fc64 100644 --- a/plans/apl-on-sx.md +++ b/plans/apl-on-sx.md @@ -160,10 +160,15 @@ programs run from source, and starts pushing on performance. - runtime: extend `apl-squad` to accept a vector of indices, treating `nil` / empty axis as "all"; - 5+ tests across vector and matrix. -- [ ] **`.apl` files as actual tests** — `lib/apl/tests/programs/*.apl` are +- [x] **`.apl` files as actual tests** — `lib/apl/tests/programs/*.apl` are currently documentation. Add `apl-run-file path → array` plus tests that load each file, execute it, and assert the expected result. Makes the classic-program corpus self-validating instead of two parallel impls. + _(Embedded source-string approach: tests/programs-e2e.sx runs the same + algorithms as the .apl docs through the full pipeline. The original + one-liners (e.g. primes' inline `⍵←⍳⍵`) need parser features + (compress-as-fn, inline assign) we haven't built yet — multi-stmt forms + used instead. Slurp/read-file primitive missing in OCaml SX runtime.)_ - [ ] **Train/fork notation** — `(f g h) ⍵ ↔ (f ⍵) g (h ⍵)` (3-train); `(g h) ⍵ ↔ g (h ⍵)` (2-train atop). Parser: detect when a parenthesised subexpression is all functions and emit `(:train fns)`; resolver: build the @@ -186,6 +191,7 @@ data; format for string templating. _Newest first._ +- 2026-05-07: Phase 8 step 4 — programs-e2e.sx runs classic-algorithm shapes through full pipeline (factorial via ∇, triangulars, sum-of-squares, divisor-counts, prime-mask, named-fn composition, dyadic max-of-two, Newton step); also added ⌿ + ⍀ to glyph sets (were silently skipped); +15 tests; 490/490 - 2026-05-07: Phase 8 step 3 — multi-axis bracket A[I;J] / A[I;] / A[;J] via :bracket AST + apl-bracket-multi runtime; split-bracket-content scans :semi at depth 0; apl-cartesian builds index combinations; nil axis = "all"; scalar axis collapses; +8 tests; 475/475 - 2026-05-07: Phase 8 step 2 — named function defs end-to-end via parser pre-scan; apl-known-fn-names + apl-collect-fn-bindings detect `name ← {...}` patterns; collect-segments-loop emits :fn-name for known names; resolver looks up env for :fn-name; supports recursion (∇ in named dfn); +7 tests including fact via ∇; 467/467 - 2026-05-07: Phase 8 step 1 — quick-wins bundle: decimal literals (3.7, ¯2.5), ⎕← passthrough as monadic fn (single-token via tokenizer special-case), :str AST in eval-ast (single-char→scalar, multi-char→vec); +10 tests; 460/460