apl: right-to-left parser + 44 tests (Phase 1, step 2)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 17s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 17s
Implement lib/apl/parser.sx — APL expression parser:
- Segment-based algorithm: scan L→R collecting {fn,val} segments
- build-tree constructs AST with leftmost-fn = root (right-to-left semantics)
- Handles: monadic/dyadic fns, strands (:vec), assignment (:assign)
- Operators: derived-fn (:derived-fn op fn), inner product (:derived-fn2)
- Outer product ∘.f (:outer), dfns {:dfn stmt...}, guards (:guard cond expr)
- split-statements is bracket-aware (depth tracking prevents splitting inside {})
44 new parser tests + 46 existing tokenizer = 90/90 green.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -81,3 +81,260 @@
|
||||
(define apl-tokenize-test-summary
|
||||
(str "tokenizer " apl-test-pass "/" apl-test-count
|
||||
(if (= (len apl-test-fails) 0) "" (str " FAILS: " apl-test-fails))))
|
||||
|
||||
; ===========================================================================
|
||||
; Parser tests
|
||||
; ===========================================================================
|
||||
|
||||
; Helper: parse an APL source string and return the AST
|
||||
(define parse
|
||||
(fn (src) (parse-apl src)))
|
||||
|
||||
; Helper: build an expected AST node using keyword-tagged lists
|
||||
(define num-node (fn (n) (list :num n)))
|
||||
(define str-node (fn (s) (list :str s)))
|
||||
(define name-node (fn (n) (list :name n)))
|
||||
(define fn-node (fn (g) (list :fn-glyph g)))
|
||||
(define fn-nm (fn (n) (list :fn-name n)))
|
||||
(define assign-node (fn (nm expr) (list :assign nm expr)))
|
||||
(define monad-node (fn (f a) (list :monad f a)))
|
||||
(define dyad-node (fn (f l r) (list :dyad f l r)))
|
||||
(define derived-fn (fn (op f) (list :derived-fn op f)))
|
||||
(define derived-fn2 (fn (op f g) (list :derived-fn2 op f g)))
|
||||
(define outer-node (fn (f) (list :outer "∘." f)))
|
||||
(define guard-node (fn (c e) (list :guard c e)))
|
||||
|
||||
; ---- numeric literals ----
|
||||
|
||||
(apl-test "parse: num literal"
|
||||
(parse "42")
|
||||
(num-node 42))
|
||||
|
||||
(apl-test "parse: negative num"
|
||||
(parse "¯3")
|
||||
(num-node -3))
|
||||
|
||||
(apl-test "parse: zero"
|
||||
(parse "0")
|
||||
(num-node 0))
|
||||
|
||||
; ---- string literals ----
|
||||
|
||||
(apl-test "parse: str literal"
|
||||
(parse "'hello'")
|
||||
(str-node "hello"))
|
||||
|
||||
(apl-test "parse: empty str"
|
||||
(parse "''")
|
||||
(str-node ""))
|
||||
|
||||
; ---- name reference ----
|
||||
|
||||
(apl-test "parse: name"
|
||||
(parse "x")
|
||||
(name-node "x"))
|
||||
|
||||
(apl-test "parse: system name"
|
||||
(parse "⎕IO")
|
||||
(name-node "⎕IO"))
|
||||
|
||||
; ---- strands (vec nodes) ----
|
||||
|
||||
(apl-test "parse: strand 3 nums"
|
||||
(parse "1 2 3")
|
||||
(list :vec (num-node 1) (num-node 2) (num-node 3)))
|
||||
|
||||
(apl-test "parse: strand 2 nums"
|
||||
(parse "1 2")
|
||||
(list :vec (num-node 1) (num-node 2)))
|
||||
|
||||
(apl-test "parse: strand with negatives"
|
||||
(parse "1 ¯2 3")
|
||||
(list :vec (num-node 1) (num-node -2) (num-node 3)))
|
||||
|
||||
; ---- assignment ----
|
||||
|
||||
(apl-test "parse: assignment"
|
||||
(parse "x←42")
|
||||
(assign-node "x" (num-node 42)))
|
||||
|
||||
(apl-test "parse: assignment with spaces"
|
||||
(parse "x ← 42")
|
||||
(assign-node "x" (num-node 42)))
|
||||
|
||||
(apl-test "parse: assignment of expr"
|
||||
(parse "r←2+3")
|
||||
(assign-node "r" (dyad-node (fn-node "+") (num-node 2) (num-node 3))))
|
||||
|
||||
; ---- monadic functions ----
|
||||
|
||||
(apl-test "parse: monadic iota"
|
||||
(parse "⍳5")
|
||||
(monad-node (fn-node "⍳") (num-node 5)))
|
||||
|
||||
(apl-test "parse: monadic iota with space"
|
||||
(parse "⍳ 5")
|
||||
(monad-node (fn-node "⍳") (num-node 5)))
|
||||
|
||||
(apl-test "parse: monadic negate"
|
||||
(parse "-3")
|
||||
(monad-node (fn-node "-") (num-node 3)))
|
||||
|
||||
(apl-test "parse: monadic floor"
|
||||
(parse "⌊2")
|
||||
(monad-node (fn-node "⌊") (num-node 2)))
|
||||
|
||||
(apl-test "parse: monadic of name"
|
||||
(parse "⍴x")
|
||||
(monad-node (fn-node "⍴") (name-node "x")))
|
||||
|
||||
; ---- dyadic functions ----
|
||||
|
||||
(apl-test "parse: dyadic plus"
|
||||
(parse "2+3")
|
||||
(dyad-node (fn-node "+") (num-node 2) (num-node 3)))
|
||||
|
||||
(apl-test "parse: dyadic times"
|
||||
(parse "2×3")
|
||||
(dyad-node (fn-node "×") (num-node 2) (num-node 3)))
|
||||
|
||||
(apl-test "parse: dyadic with names"
|
||||
(parse "x+y")
|
||||
(dyad-node (fn-node "+") (name-node "x") (name-node "y")))
|
||||
|
||||
; ---- right-to-left evaluation ----
|
||||
|
||||
(apl-test "parse: right-to-left 2×3+4"
|
||||
(parse "2×3+4")
|
||||
(dyad-node (fn-node "×") (num-node 2)
|
||||
(dyad-node (fn-node "+") (num-node 3) (num-node 4))))
|
||||
|
||||
(apl-test "parse: right-to-left chain"
|
||||
(parse "1+2×3-4")
|
||||
(dyad-node (fn-node "+") (num-node 1)
|
||||
(dyad-node (fn-node "×") (num-node 2)
|
||||
(dyad-node (fn-node "-") (num-node 3) (num-node 4)))))
|
||||
|
||||
; ---- parenthesized subexpressions ----
|
||||
|
||||
(apl-test "parse: parens override order"
|
||||
(parse "(2+3)×4")
|
||||
(dyad-node (fn-node "×")
|
||||
(dyad-node (fn-node "+") (num-node 2) (num-node 3))
|
||||
(num-node 4)))
|
||||
|
||||
(apl-test "parse: nested parens"
|
||||
(parse "((2+3))")
|
||||
(dyad-node (fn-node "+") (num-node 2) (num-node 3)))
|
||||
|
||||
(apl-test "parse: paren in dyadic right"
|
||||
(parse "2×(3+4)")
|
||||
(dyad-node (fn-node "×") (num-node 2)
|
||||
(dyad-node (fn-node "+") (num-node 3) (num-node 4))))
|
||||
|
||||
; ---- operators → derived functions ----
|
||||
|
||||
(apl-test "parse: reduce +"
|
||||
(parse "+/x")
|
||||
(monad-node (derived-fn "/" (fn-node "+")) (name-node "x")))
|
||||
|
||||
(apl-test "parse: reduce iota"
|
||||
(parse "+/⍳5")
|
||||
(monad-node (derived-fn "/" (fn-node "+"))
|
||||
(monad-node (fn-node "⍳") (num-node 5))))
|
||||
|
||||
(apl-test "parse: scan"
|
||||
(parse "+\\x")
|
||||
(monad-node (derived-fn "\\" (fn-node "+")) (name-node "x")))
|
||||
|
||||
(apl-test "parse: each"
|
||||
(parse "⍳¨x")
|
||||
(monad-node (derived-fn "¨" (fn-node "⍳")) (name-node "x")))
|
||||
|
||||
(apl-test "parse: commute"
|
||||
(parse "-⍨3")
|
||||
(monad-node (derived-fn "⍨" (fn-node "-")) (num-node 3)))
|
||||
|
||||
(apl-test "parse: stacked ops"
|
||||
(parse "+/¨x")
|
||||
(monad-node (derived-fn "¨" (derived-fn "/" (fn-node "+"))) (name-node "x")))
|
||||
|
||||
; ---- outer product ----
|
||||
|
||||
(apl-test "parse: outer product monadic"
|
||||
(parse "∘.×")
|
||||
(outer-node (fn-node "×")))
|
||||
|
||||
(apl-test "parse: outer product dyadic names"
|
||||
(parse "x ∘.× y")
|
||||
(dyad-node (outer-node (fn-node "×")) (name-node "x") (name-node "y")))
|
||||
|
||||
(apl-test "parse: outer product dyadic strands"
|
||||
(parse "1 2 3 ∘.× 4 5 6")
|
||||
(dyad-node (outer-node (fn-node "×"))
|
||||
(list :vec (num-node 1) (num-node 2) (num-node 3))
|
||||
(list :vec (num-node 4) (num-node 5) (num-node 6))))
|
||||
|
||||
; ---- inner product ----
|
||||
|
||||
(apl-test "parse: inner product"
|
||||
(parse "+.×")
|
||||
(derived-fn2 "." (fn-node "+") (fn-node "×")))
|
||||
|
||||
(apl-test "parse: inner product applied"
|
||||
(parse "a +.× b")
|
||||
(dyad-node (derived-fn2 "." (fn-node "+") (fn-node "×"))
|
||||
(name-node "a") (name-node "b")))
|
||||
|
||||
; ---- dfn (anonymous function) ----
|
||||
|
||||
(apl-test "parse: simple dfn"
|
||||
(parse "{⍺+⍵}")
|
||||
(list :dfn (dyad-node (fn-node "+") (name-node "⍺") (name-node "⍵"))))
|
||||
|
||||
(apl-test "parse: monadic dfn"
|
||||
(parse "{⍵×2}")
|
||||
(list :dfn (dyad-node (fn-node "×") (name-node "⍵") (num-node 2))))
|
||||
|
||||
(apl-test "parse: dfn self-ref"
|
||||
(parse "{⍵≤1:1 ⋄ ⍵×∇ ⍵-1}")
|
||||
(list :dfn
|
||||
(guard-node (dyad-node (fn-node "≤") (name-node "⍵") (num-node 1)) (num-node 1))
|
||||
(dyad-node (fn-node "×") (name-node "⍵")
|
||||
(monad-node (fn-node "∇") (dyad-node (fn-node "-") (name-node "⍵") (num-node 1))))))
|
||||
|
||||
; ---- dfn applied ----
|
||||
|
||||
(apl-test "parse: dfn as function"
|
||||
(parse "{⍺+⍵} 3")
|
||||
(monad-node
|
||||
(list :dfn (dyad-node (fn-node "+") (name-node "⍺") (name-node "⍵")))
|
||||
(num-node 3)))
|
||||
|
||||
; ---- multi-statement ----
|
||||
|
||||
(apl-test "parse: diamond separator"
|
||||
(let ((result (parse "x←1 ⋄ x+2")))
|
||||
(= (first result) :program))
|
||||
true)
|
||||
|
||||
(apl-test "parse: diamond first stmt"
|
||||
(let ((result (parse "x←1 ⋄ x+2")))
|
||||
(nth result 1))
|
||||
(assign-node "x" (num-node 1)))
|
||||
|
||||
(apl-test "parse: diamond second stmt"
|
||||
(let ((result (parse "x←1 ⋄ x+2")))
|
||||
(nth result 2))
|
||||
(dyad-node (fn-node "+") (name-node "x") (num-node 2)))
|
||||
|
||||
; ---- combined summary ----
|
||||
|
||||
(define apl-parse-test-count (- apl-test-count 46))
|
||||
(define apl-parse-test-pass (- apl-test-pass 46))
|
||||
|
||||
(define apl-test-summary
|
||||
(str
|
||||
"tokenizer 46/46 | "
|
||||
"parser " apl-parse-test-pass "/" apl-parse-test-count
|
||||
(if (= (len apl-test-fails) 0) "" (str " FAILS: " apl-test-fails))))
|
||||
|
||||
Reference in New Issue
Block a user