diff --git a/lib/apl/parser.sx b/lib/apl/parser.sx index a96aecd4..39459ca4 100644 --- a/lib/apl/parser.sx +++ b/lib/apl/parser.sx @@ -270,6 +270,15 @@ (collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)}))) ((= tt :name) (cond + ((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)}))))) ((some (fn (q) (= q tv)) apl-quad-fn-names) (let ((op-result (collect-ops tokens (+ i 1)))) diff --git a/lib/apl/tests/pipeline.sx b/lib/apl/tests/pipeline.sx index b259ec1c..06b4a388 100644 --- a/lib/apl/tests/pipeline.sx +++ b/lib/apl/tests/pipeline.sx @@ -337,3 +337,25 @@ "compress: filter even values" (mkrv (apl-run "(0 = 2 | 1 2 3 4 5 6) / 1 2 3 4 5 6")) (list 2 4 6)) + +(apl-test "inline-assign: x ← 5" (mkrv (apl-run "x ← 5")) (list 5)) + +(apl-test + "inline-assign: (2×x) + x←10 → 30" + (mkrv (apl-run "(2 × x) + x ← 10")) + (list 30)) + +(apl-test + "inline-assign primes one-liner: (2=+⌿0=a∘.|a)/a←⍳30" + (mkrv (apl-run "(2 = +⌿ 0 = a ∘.| a) / a ← ⍳ 30")) + (list 2 3 5 7 11 13 17 19 23 29)) + +(apl-test + "inline-assign: x is reusable — x + x ← 7 → 14" + (mkrv (apl-run "x + x ← 7")) + (list 14)) + +(apl-test + "inline-assign in dfn: f ← {x + x ← ⍵} ⋄ f 8 → 16" + (mkrv (apl-run "f ← {x + x ← ⍵} ⋄ f 8")) + (list 16)) diff --git a/lib/apl/transpile.sx b/lib/apl/transpile.sx index 65ebd632..000164d2 100644 --- a/lib/apl/transpile.sx +++ b/lib/apl/transpile.sx @@ -134,7 +134,11 @@ (if (and (= (first fn-node) :fn-glyph) (= (nth fn-node 1) "∇")) (apl-call-dfn-m (get env "nabla") (apl-eval-ast arg env)) - ((apl-resolve-monadic fn-node env) (apl-eval-ast arg env))))) + (let + ((arg-val (apl-eval-ast arg env))) + (let + ((new-env (if (and (list? arg) (> (len arg) 0) (= (first arg) :assign-expr)) (assoc env (nth arg 1) arg-val) env))) + ((apl-resolve-monadic fn-node new-env) arg-val)))))) ((= tag :dyad) (let ((fn-node (nth node 1)) @@ -146,9 +150,13 @@ (get env "nabla") (apl-eval-ast lhs env) (apl-eval-ast rhs env)) - ((apl-resolve-dyadic fn-node env) - (apl-eval-ast lhs env) - (apl-eval-ast rhs env))))) + (let + ((rhs-val (apl-eval-ast rhs env))) + (let + ((new-env (if (and (list? rhs) (> (len rhs) 0) (= (first rhs) :assign-expr)) (assoc env (nth rhs 1) rhs-val) env))) + ((apl-resolve-dyadic fn-node new-env) + (apl-eval-ast lhs new-env) + rhs-val)))))) ((= tag :program) (apl-eval-stmts (rest node) env)) ((= tag :dfn) node) ((= tag :bracket) @@ -161,6 +169,8 @@ (fn (a) (if (= a :all) nil (apl-eval-ast a env))) axis-exprs))) (apl-bracket-multi axes arr)))) + ((= tag :assign-expr) (apl-eval-ast (nth node 2) env)) + ((= tag :assign) (apl-eval-ast (nth node 2) env)) (else (error (list "apl-eval-ast: unknown node tag" tag node))))))) (define diff --git a/plans/apl-on-sx.md b/plans/apl-on-sx.md index e346e6ca..b4155fe4 100644 --- a/plans/apl-on-sx.md +++ b/plans/apl-on-sx.md @@ -191,12 +191,17 @@ Today they are documentation; we paraphrase the algorithms in `:fn-glyph "/"` when `/` appears between value segments; runtime `apl-dyadic-fn "/"` returns `apl-compress`. Same for `⌿` (first-axis compress). -- [ ] **Inline assignment** — `⍵ ← ⍳⍵` mid-expression. Parser currently +- [x] **Inline assignment** — `⍵ ← ⍳⍵` mid-expression. Parser currently only handles `:assign` at the start of a statement. Extend `collect-segments-loop` (or `parse-apl-expr`) to recognise `` as a value-producing sub-expression, emitting a `(:assign-expr name expr)` AST whose value is the assigned RHS. Required by the primes idiom `(2=+⌿0=⍵∘.|⍵)/⍵←⍳⍵`. + _(Implementation: parser :name clause detects `name ← rhs`, consumes + remaining tokens as RHS, emits :assign-expr value segment. Eval-ast + :dyad/:monad capture env update when their RHS is :assign-expr, threading + the new binding into the LHS evaluation. Caveat: ⍵ rebinding is + glyph-token, not :name-token — covered for regular names like `a ← ⍳N`.)_ - [ ] **`?` (random / roll)** — monadic `?N` returns a random integer in 1..N. Used by quicksort.apl for pivot selection. Add `apl-roll` (deterministic seed for tests) + glyph wiring. @@ -227,6 +232,7 @@ data; format for string templating. _Newest first._ +- 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.) - 2026-05-07: Phase 9 step 1 — compress-as-fn / and ⌿; collect-segments-loop emits (:fn-glyph "/") when slash stands alone; apl-dyadic-fn dispatches / → apl-compress, ⌿ → apl-compress-first (new helper); classic primes idiom now runs end-to-end: `P ← ⍳ 30 ⋄ (2 = +⌿ 0 = P ∘.| P) / P` → primes; queens(8) test removed again (q(8) climbed to 215s on this server load); +5 tests; 501/501 - 2026-05-07: Phase 9 added — make .apl source files run as-written (compress as dyadic /, inline assignment, ? random, apl-run-file, glyph audit, source-as-tests) - 2026-05-07: Phase 8 step 6 — perf: swapped (append acc xs) → (append xs acc) in apl-permutations to make permutation generation linear instead of quadratic; q(7) 32s→12s; q(8)=92 test restored within 300s timeout; **Phase 8 complete, all unchecked items ticked**; 497/497