diff --git a/lib/apl/parser.sx b/lib/apl/parser.sx index 58432c05..3cd8050f 100644 --- a/lib/apl/parser.sx +++ b/lib/apl/parser.sx @@ -28,11 +28,59 @@ (define apl-parse-op-glyphs (list "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@")) -(define apl-parse-fn-glyphs - (list "+" "-" "×" "÷" "*" "⍟" "⌈" "⌊" "|" "!" "?" "○" "~" - "<" "≤" "=" "≥" ">" "≠" "∊" "∧" "∨" "⍱" "⍲" - "," "⍪" "⍴" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆" - "∪" "∩" "⍳" "⍸" "⌷" "⍋" "⍒" "⊥" "⊤" "⊣" "⊢" "⍎" "⍕")) +(define + apl-parse-fn-glyphs + (list + "+" + "-" + "×" + "÷" + "*" + "⍟" + "⌈" + "⌊" + "|" + "!" + "?" + "○" + "~" + "<" + "≤" + "=" + "≥" + ">" + "≠" + "≢" + "≡" + "∊" + "∧" + "∨" + "⍱" + "⍲" + "," + "⍪" + "⍴" + "⌽" + "⊖" + "⍉" + "↑" + "↓" + "⊂" + "⊃" + "⊆" + "∪" + "∩" + "⍳" + "⍸" + "⌷" + "⍋" + "⍒" + "⊥" + "⊤" + "⊣" + "⊢" + "⍎" + "⍕")) (define apl-quad-fn-names (list "⎕FMT")) diff --git a/lib/apl/tests/idioms.sx b/lib/apl/tests/idioms.sx index e9de393f..40475a3d 100644 --- a/lib/apl/tests/idioms.sx +++ b/lib/apl/tests/idioms.sx @@ -222,3 +222,138 @@ (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)) diff --git a/lib/apl/tokenizer.sx b/lib/apl/tokenizer.sx index f3ff4a0e..668e55d3 100644 --- a/lib/apl/tokenizer.sx +++ b/lib/apl/tokenizer.sx @@ -1,6 +1,6 @@ (define apl-glyph-set (list "+" "-" "×" "÷" "*" "⍟" "⌈" "⌊" "|" "!" "?" "○" "~" "<" "≤" "=" "≥" ">" "≠" - "∊" "∧" "∨" "⍱" "⍲" "," "⍪" "⍴" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆" + "≢" "≡" "∊" "∧" "∨" "⍱" "⍲" "," "⍪" "⍴" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆" "∪" "∩" "⍳" "⍸" "⌷" "⍋" "⍒" "⊥" "⊤" "⊣" "⊢" "⍎" "⍕" "⍺" "⍵" "∇" "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@" "¯")) diff --git a/plans/apl-on-sx.md b/plans/apl-on-sx.md index 8b8d5476..5b3a93f5 100644 --- a/plans/apl-on-sx.md +++ b/plans/apl-on-sx.md @@ -127,7 +127,7 @@ and tightens loose ends. `A[I;J]` end-to-end; confirm parser desugars to `⌷` and runtime returns expected slices. Add 5+ tests. _(Single-axis only — multi-axis `A[I;J]` requires semicolon parsing, deferred.)_ -- [ ] **Idiom corpus expansion** — extend `idioms.sx` from 34 to 60+ once +- [x] **Idiom corpus expansion** — extend `idioms.sx` from 34 to 60+ once end-to-end works (we can express idioms as APL strings, not as runtime calls). Source-string-based idioms validate the whole stack. - [ ] **`:Trap` / `:EndTrap`** — minimal exception machinery: `:Trap n` @@ -149,6 +149,7 @@ data; format for string templating. _Newest first._ +- 2026-05-07: Phase 7 step 5 — idiom corpus 34→64 (+30 source-string idioms via apl-run); also fixed tokenizer + parser to recognize ≢ and ≡ glyphs (were silently skipped); 445/445 - 2026-05-07: Phase 7 step 4 — bracket indexing `A[I]` desugared to `(:dyad ⌷ I A)` via maybe-bracket helper, wired into :name + :lparen branches of collect-segments-loop; multi-axis (A[I;J]) deferred (semicolon split); +7 tests; 415/415 - 2026-05-07: Phase 7 step 3 — :quad-name end-to-end; tokenizer already produced :name "⎕FMT"; parser is-fn-tok? extended via apl-quad-fn-names; eval-ast :name dispatches ⎕IO/⎕ML/⎕FR/⎕TS to apl-quad-*; apl-monadic-fn handles ⎕FMT; ⎕← deferred (tokenizer splits ⎕←); +8 tests; 408/408 - 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**