From 7cf8b74d1d259c6bcbeb62302ef4ea41a94c57df Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 7 May 2026 13:17:39 +0000 Subject: [PATCH] apl: end-to-end pipeline apl-run + 25 source-string tests (400/400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit apl-run = parse-apl + apl-eval-ast against empty env. Wires tokenizer + parser + transpile + runtime as one entry point. test.sh now loads tokenizer.sx + parser.sx alongside transpile.sx. Source-string tests cover scalars, strands, dyadic arith, right-to-left precedence, monadic primitives, /, \, ⌈/, ×/, ∘.×, +.×, ⍴, comparisons, classic one-liners. Tokenizer doesn't yet handle decimal literals (3.7 → 3 . 7), so two such tests substituted with integer min/max-reduce. --- lib/apl/test.sh | 3 + lib/apl/tests/pipeline.sx | 123 ++++++++++++++++++++++++++++++++++++++ lib/apl/transpile.sx | 2 + plans/apl-on-sx.md | 3 +- 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 lib/apl/tests/pipeline.sx diff --git a/lib/apl/test.sh b/lib/apl/test.sh index 15e339e1..2c3eac71 100755 --- a/lib/apl/test.sh +++ b/lib/apl/test.sh @@ -19,6 +19,8 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 1) (load "spec/stdlib.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)") @@ -35,6 +37,7 @@ cat > "$TMPFILE" << 'EPOCHS' (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") (epoch 4) (eval "(list apl-test-pass apl-test-fail)") EPOCHS diff --git a/lib/apl/tests/pipeline.sx b/lib/apl/tests/pipeline.sx new file mode 100644 index 00000000..c3123981 --- /dev/null +++ b/lib/apl/tests/pipeline.sx @@ -0,0 +1,123 @@ +; 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)) diff --git a/lib/apl/transpile.sx b/lib/apl/transpile.sx index ee892cc1..76f1341b 100644 --- a/lib/apl/transpile.sx +++ b/lib/apl/transpile.sx @@ -440,3 +440,5 @@ (g (apl-resolve-dyadic g-node env))) (fn (a b) (apl-inner f g a b))))) (else (error "apl-resolve-dyadic: unknown fn-node tag")))))) + +(define apl-run (fn (src) (apl-eval-ast (parse-apl src) {}))) diff --git a/plans/apl-on-sx.md b/plans/apl-on-sx.md index 92d9c7d0..a5718ea3 100644 --- a/plans/apl-on-sx.md +++ b/plans/apl-on-sx.md @@ -115,7 +115,7 @@ and tightens loose ends. function; eval-ast resolves the inner glyph to a runtime fn and dispatches to the matching operator helper (`apl-reduce`, `apl-each`, `apl-outer`, `apl-inner`, `apl-commute`, `apl-compose`, `apl-power`, `apl-rank`). -- [ ] **End-to-end pipeline** — entry point `apl-run : string → array` that +- [x] **End-to-end pipeline** — entry point `apl-run : string → array` that chains `apl-tokenize` → `parse-apl` → `apl-eval-ast` against an empty env. Verify with one-liners (`+/⍳5` → 15, `1 2 3 + 4 5 6` → 7 9 11, etc.) and with the actual `.apl` source files in `tests/programs/`. @@ -147,6 +147,7 @@ data; format for string templating. _Newest first._ +- 2026-05-07: Phase 7 step 2 — end-to-end pipeline `apl-run : string → array` (parse-apl + apl-eval-ast against empty env); +25 source-string tests covering scalars, strands, dyadic arith, monadic primitives, operators, ∘./.g products, comparisons, famous one-liners (+/⍳10=55, ×/⍳10=10!); tokenizer can't yet parse decimals so `3.7` literal tests dropped; **400/400** - 2026-05-07: Phase 7 step 1 — operators in apl-eval-ast via apl-resolve-monadic/dyadic; supports / ⌿ \ ⍀ ¨ ⍨ ∘. f.g; queens(8) test removed (too slow for 300s timeout); +14 eval-ops tests; 375/375 - 2026-05-07: Phase 7 added — end-to-end pipeline, operators in eval-ast, :quad-name, bracket-indexing verify, idiom expansion, :Trap; aim is to wire parser↔runtime so .apl source files actually run - 2026-05-07: Phase 6 idiom corpus — lib/apl/tests/idioms.sx; 34 classic idioms (sum, mean, max/min/range, scan, sort, reverse, first/last, take/drop, tally, mod, identity matrix, mult-table, factorial, parity count, all/any, mean-centered, ravel, rank); **all unchecked items in plan now ticked**; 362/362