From d9cf00f287ffe3707a8fb8f2bd8f7e82f98236df Mon Sep 17 00:00:00 2001 From: giles Date: Thu, 7 May 2026 17:26:37 +0000 Subject: [PATCH] =?UTF-8?q?apl:=20quick-wins=20bundle=20=E2=80=94=20decima?= =?UTF-8?q?ls=20+=20=E2=8E=95=E2=86=90=20+=20strings=20(+10=20tests,=20460?= =?UTF-8?q?/460)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three small unblockers in one iteration: - tokenizer: read-digits! now consumes optional ".digits" suffix, so 3.7 and ¯2.5 are single number tokens. - tokenizer: ⎕ followed by ← emits a single :name "⎕←" token (instead of splitting on the assign glyph). Parser registers ⎕← in apl-quad-fn-names; apl-monadic-fn maps to apl-quad-print. - eval-ast: :str AST nodes evaluate to char arrays. Single-char strings become rank-0 scalars; multi-char become rank-1 vectors of single-char strings. --- lib/apl/parser.sx | 2 +- lib/apl/tests/pipeline.sx | 29 +++++++++++++++++++++++++++++ lib/apl/tokenizer.sx | 18 +++++++++++++++--- lib/apl/transpile.sx | 10 ++++++++++ plans/apl-on-sx.md | 3 ++- 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/lib/apl/parser.sx b/lib/apl/parser.sx index 3cd8050f..fc599730 100644 --- a/lib/apl/parser.sx +++ b/lib/apl/parser.sx @@ -82,7 +82,7 @@ "⍎" "⍕")) -(define apl-quad-fn-names (list "⎕FMT")) +(define apl-quad-fn-names (list "⎕FMT" "⎕←")) (define apl-parse-op-glyph? diff --git a/lib/apl/tests/pipeline.sx b/lib/apl/tests/pipeline.sx index e1c79e8b..b878a15c 100644 --- a/lib/apl/tests/pipeline.sx +++ b/lib/apl/tests/pipeline.sx @@ -178,3 +178,32 @@ "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)) diff --git a/lib/apl/tokenizer.sx b/lib/apl/tokenizer.sx index 668e55d3..a842e48e 100644 --- a/lib/apl/tokenizer.sx +++ b/lib/apl/tokenizer.sx @@ -138,12 +138,22 @@ (begin (consume! "¯") (let ((digits (read-digits! ""))) - (tok-push! :num (- 0 (parse-int digits 0)))) + (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! ""))) - (tok-push! :num (parse-int digits 0))) + (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 @@ -155,7 +165,9 @@ (let ((start pos)) (begin (if (cur-sw? "⎕") (consume! "⎕") (advance!)) - (read-ident-cont!) + (if (and (< pos src-len) (cur-sw? "←")) + (consume! "←") + (read-ident-cont!)) (tok-push! :name (slice source start pos)) (scan!)))) (true diff --git a/lib/apl/transpile.sx b/lib/apl/transpile.sx index 52e2d8e2..e17c5d88 100644 --- a/lib/apl/transpile.sx +++ b/lib/apl/transpile.sx @@ -40,6 +40,7 @@ ((= g "⍋") apl-grade-up) ((= g "⍒") apl-grade-down) ((= g "⎕FMT") apl-quad-fmt) + ((= g "⎕←") apl-quad-print) (else (error "no monadic fn for glyph"))))) (define @@ -97,6 +98,15 @@ ((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))) diff --git a/plans/apl-on-sx.md b/plans/apl-on-sx.md index 925caf7c..fb86d73f 100644 --- a/plans/apl-on-sx.md +++ b/plans/apl-on-sx.md @@ -140,7 +140,7 @@ and tightens loose ends. Phase 7 wired the stack together; Phase 8 closes deferred items, lets real programs run from source, and starts pushing on performance. -- [ ] **Quick-wins bundle** (one iteration) — three small fixes that each unblock +- [x] **Quick-wins bundle** (one iteration) — three small fixes that each unblock real programs: - decimal literals: `read-digits!` consumes one trailing `.` plus more digits so `3.7` tokenises as one number; @@ -186,6 +186,7 @@ data; format for string templating. _Newest first._ +- 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 - 2026-05-07: Phase 8 added — quick-wins bundle (decimals + ⎕← + strings), named functions, multi-axis bracket, .apl-files-as-tests, trains, perf - 2026-05-07: Phase 7 step 6 — :Trap exception machinery via R7RS guard; apl-throw raises tagged error, apl-trap-matches? checks codes (0=catch-all), :trap clause in apl-tradfn-eval-stmt wraps try-block with guard; :throw AST for testing; **Phase 7 complete, all unchecked plan items done**; +5 tests; 450/450 - 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