Compare commits
3 Commits
0d2eede5fb
...
loops/hask
| Author | SHA1 | Date | |
|---|---|---|---|
| 4510e7e475 | |||
| aa620b767f | |||
| 23afc9dde3 |
@@ -270,15 +270,6 @@
|
|||||||
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)})))
|
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)})))
|
||||||
((= tt :name)
|
((= tt :name)
|
||||||
(cond
|
(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)
|
((some (fn (q) (= q tv)) apl-quad-fn-names)
|
||||||
(let
|
(let
|
||||||
((op-result (collect-ops tokens (+ i 1))))
|
((op-result (collect-ops tokens (+ i 1))))
|
||||||
@@ -344,22 +335,10 @@
|
|||||||
((= tt :glyph)
|
((= tt :glyph)
|
||||||
(cond
|
(cond
|
||||||
((or (= tv "⍺") (= tv "⍵"))
|
((or (= tv "⍺") (= tv "⍵"))
|
||||||
(if
|
(collect-segments-loop
|
||||||
(and
|
tokens
|
||||||
(< (+ i 1) (len tokens))
|
(+ i 1)
|
||||||
(= (tok-type (nth tokens (+ i 1))) :assign))
|
(append acc {:kind "val" :node (list :name tv)})))
|
||||||
(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 "∇")
|
((= tv "∇")
|
||||||
(collect-segments-loop
|
(collect-segments-loop
|
||||||
tokens
|
tokens
|
||||||
@@ -414,13 +393,7 @@
|
|||||||
ni
|
ni
|
||||||
(append acc {:kind "fn" :node fn-node})))))))
|
(append acc {:kind "fn" :node fn-node})))))))
|
||||||
((apl-parse-op-glyph? tv)
|
((apl-parse-op-glyph? tv)
|
||||||
(if
|
(collect-segments-loop tokens (+ i 1) acc))
|
||||||
(or (= tv "/") (= tv "⌿") (= tv "\\") (= tv "⍀"))
|
|
||||||
(collect-segments-loop
|
|
||||||
tokens
|
|
||||||
(+ i 1)
|
|
||||||
(append acc {:kind "fn" :node (list :fn-glyph tv)}))
|
|
||||||
(collect-segments-loop tokens (+ i 1) acc)))
|
|
||||||
(true (collect-segments-loop tokens (+ i 1) acc))))
|
(true (collect-segments-loop tokens (+ i 1) acc))))
|
||||||
(true (collect-segments-loop tokens (+ i 1) acc))))))))
|
(true (collect-segments-loop tokens (+ i 1) acc))))))))
|
||||||
|
|
||||||
|
|||||||
@@ -808,25 +808,6 @@
|
|||||||
((picked (map (fn (i) (nth arr-ravel i)) kept)))
|
((picked (map (fn (i) (nth arr-ravel i)) kept)))
|
||||||
(make-array (list (len picked)) picked))))))
|
(make-array (list (len picked)) picked))))))
|
||||||
|
|
||||||
(define
|
|
||||||
apl-compress-first
|
|
||||||
(fn
|
|
||||||
(mask arr)
|
|
||||||
(let
|
|
||||||
((mask-ravel (get mask :ravel))
|
|
||||||
(shape (get arr :shape))
|
|
||||||
(ravel (get arr :ravel)))
|
|
||||||
(if
|
|
||||||
(< (len shape) 2)
|
|
||||||
(apl-compress mask arr)
|
|
||||||
(let
|
|
||||||
((rows (first shape)) (cols (last shape)))
|
|
||||||
(let
|
|
||||||
((kept-rows (filter (fn (i) (not (= 0 (nth mask-ravel i)))) (range 0 rows))))
|
|
||||||
(let
|
|
||||||
((new-ravel (reduce (fn (acc r) (append acc (map (fn (j) (nth ravel (+ (* r cols) j))) (range 0 cols)))) (list) kept-rows)))
|
|
||||||
(make-array (cons (len kept-rows) (rest shape)) new-ravel))))))))
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
apl-primes
|
apl-primes
|
||||||
(fn
|
(fn
|
||||||
@@ -1004,28 +985,6 @@
|
|||||||
(some (fn (c) (= c 0)) codes)
|
(some (fn (c) (= c 0)) codes)
|
||||||
(some (fn (c) (= c (nth e 1))) codes)))))
|
(some (fn (c) (= c (nth e 1))) codes)))))
|
||||||
|
|
||||||
(define apl-rng-state 12345)
|
|
||||||
|
|
||||||
(define apl-rng-seed! (fn (s) (set! apl-rng-state s)))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-rng-next!
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(begin
|
|
||||||
(set!
|
|
||||||
apl-rng-state
|
|
||||||
(mod (+ (* apl-rng-state 1103515245) 12345) 2147483648))
|
|
||||||
apl-rng-state)))
|
|
||||||
|
|
||||||
(define
|
|
||||||
apl-roll
|
|
||||||
(fn
|
|
||||||
(arr)
|
|
||||||
(let
|
|
||||||
((n (if (scalar? arr) (first (get arr :ravel)) (first (get arr :ravel)))))
|
|
||||||
(apl-scalar (+ apl-io (mod (apl-rng-next!) n))))))
|
|
||||||
|
|
||||||
(define
|
(define
|
||||||
apl-cartesian
|
apl-cartesian
|
||||||
(fn
|
(fn
|
||||||
|
|||||||
@@ -312,146 +312,3 @@
|
|||||||
"train: mean of ⍳10 has shape ()"
|
"train: mean of ⍳10 has shape ()"
|
||||||
(mksh (apl-run "(+/÷≢) ⍳10"))
|
(mksh (apl-run "(+/÷≢) ⍳10"))
|
||||||
(list))
|
(list))
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compress: 1 0 1 0 1 / 10 20 30 40 50"
|
|
||||||
(mkrv (apl-run "1 0 1 0 1 / 10 20 30 40 50"))
|
|
||||||
(list 10 30 50))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"compress: empty mask → empty"
|
|
||||||
(mkrv (apl-run "0 0 0 / 1 2 3"))
|
|
||||||
(list))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"primes via classic idiom (multi-stmt)"
|
|
||||||
(mkrv (apl-run "P ← ⍳ 30 ⋄ (2 = +⌿ 0 = P ∘.| P) / P"))
|
|
||||||
(list 2 3 5 7 11 13 17 19 23 29))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"primes via classic idiom (n=20)"
|
|
||||||
(mkrv (apl-run "P ← ⍳ 20 ⋄ (2 = +⌿ 0 = P ∘.| P) / P"))
|
|
||||||
(list 2 3 5 7 11 13 17 19))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"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))
|
|
||||||
|
|
||||||
(begin (apl-rng-seed! 42) nil)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"?10 with seed 42 → 8 (deterministic)"
|
|
||||||
(mkrv (apl-run "?10"))
|
|
||||||
(list 8))
|
|
||||||
|
|
||||||
(apl-test "?10 next call → 5" (mkrv (apl-run "?10")) (list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"?100 stays in range"
|
|
||||||
(let ((v (first (mkrv (apl-run "?100"))))) (and (>= v 1) (<= v 100)))
|
|
||||||
true)
|
|
||||||
|
|
||||||
(begin (apl-rng-seed! 42) nil)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"?10 with re-seed 42 → 8 (reproducible)"
|
|
||||||
(mkrv (apl-run "?10"))
|
|
||||||
(list 8))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run-file: load primes.apl returns dfn AST"
|
|
||||||
(first (apl-run-file "lib/apl/tests/programs/primes.apl"))
|
|
||||||
:dfn)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run-file: life.apl parses without error"
|
|
||||||
(first (apl-run-file "lib/apl/tests/programs/life.apl"))
|
|
||||||
:dfn)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run-file: quicksort.apl parses without error"
|
|
||||||
(first (apl-run-file "lib/apl/tests/programs/quicksort.apl"))
|
|
||||||
:dfn)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"apl-run-file: source-then-call returns primes count"
|
|
||||||
(mksh
|
|
||||||
(apl-run
|
|
||||||
(str (file-read "lib/apl/tests/programs/primes.apl") " ⋄ primes 30")))
|
|
||||||
(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)
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⍉ monadic transpose 2x3 → 3x2"
|
|
||||||
(mkrv (apl-run "⍉ (2 3) ⍴ ⍳6"))
|
|
||||||
(list 1 4 2 5 3 6))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"⍉ transpose shape (3 2)"
|
|
||||||
(mksh (apl-run "⍉ (2 3) ⍴ ⍳6"))
|
|
||||||
(list 3 2))
|
|
||||||
|
|
||||||
(apl-test "⊢ monadic identity" (mkrv (apl-run "⊢ 1 2 3")) (list 1 2 3))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"5 ⊣ 1 2 3 → 5 (left)"
|
|
||||||
(mkrv (apl-run "5 ⊣ 1 2 3"))
|
|
||||||
(list 5))
|
|
||||||
|
|
||||||
(apl-test
|
|
||||||
"5 ⊢ 1 2 3 → 1 2 3 (right)"
|
|
||||||
(mkrv (apl-run "5 ⊢ 1 2 3"))
|
|
||||||
(list 1 2 3))
|
|
||||||
|
|
||||||
(apl-test "⍕ 42 → \"42\" (alias for ⎕FMT)" (apl-run "⍕ 42") "42")
|
|
||||||
|
|||||||
@@ -252,6 +252,8 @@
|
|||||||
|
|
||||||
(apl-test "queens 7 → 40 solutions" (mkrv (apl-queens 7)) (list 40))
|
(apl-test "queens 7 → 40 solutions" (mkrv (apl-queens 7)) (list 40))
|
||||||
|
|
||||||
|
(apl-test "queens 8 → 92 solutions" (mkrv (apl-queens 8)) (list 92))
|
||||||
|
|
||||||
(apl-test "permutations of 3 has 6" (len (apl-permutations 3)) 6)
|
(apl-test "permutations of 3 has 6" (len (apl-permutations 3)) 6)
|
||||||
|
|
||||||
(apl-test "permutations of 4 has 24" (len (apl-permutations 4)) 24)
|
(apl-test "permutations of 4 has 24" (len (apl-permutations 4)) 24)
|
||||||
|
|||||||
@@ -39,11 +39,6 @@
|
|||||||
((= g "⊖") apl-reverse-first)
|
((= g "⊖") apl-reverse-first)
|
||||||
((= g "⍋") apl-grade-up)
|
((= g "⍋") apl-grade-up)
|
||||||
((= g "⍒") apl-grade-down)
|
((= g "⍒") apl-grade-down)
|
||||||
((= g "?") apl-roll)
|
|
||||||
((= g "⍉") apl-transpose)
|
|
||||||
((= g "⊢") (fn (a) a))
|
|
||||||
((= g "⊣") (fn (a) a))
|
|
||||||
((= g "⍕") apl-quad-fmt)
|
|
||||||
((= g "⎕FMT") apl-quad-fmt)
|
((= g "⎕FMT") apl-quad-fmt)
|
||||||
((= g "⎕←") apl-quad-print)
|
((= g "⎕←") apl-quad-print)
|
||||||
(else (error "no monadic fn for glyph")))))
|
(else (error "no monadic fn for glyph")))))
|
||||||
@@ -85,11 +80,6 @@
|
|||||||
((= g "∊") apl-member)
|
((= g "∊") apl-member)
|
||||||
((= g "⍳") apl-index-of)
|
((= g "⍳") apl-index-of)
|
||||||
((= g "~") apl-without)
|
((= g "~") apl-without)
|
||||||
((= g "/") apl-compress)
|
|
||||||
((= g "⌿") apl-compress-first)
|
|
||||||
((= g "⍉") apl-transpose-dyadic)
|
|
||||||
((= g "⊢") (fn (a b) b))
|
|
||||||
((= g "⊣") (fn (a b) a))
|
|
||||||
(else (error "no dyadic fn for glyph")))))
|
(else (error "no dyadic fn for glyph")))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -129,14 +119,8 @@
|
|||||||
(let
|
(let
|
||||||
((nm (nth node 1)))
|
((nm (nth node 1)))
|
||||||
(cond
|
(cond
|
||||||
((= nm "⍺")
|
((= nm "⍺") (get env "alpha"))
|
||||||
(let
|
((= nm "⍵") (get env "omega"))
|
||||||
((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 "⎕IO") (apl-quad-io))
|
||||||
((= nm "⎕ML") (apl-quad-ml))
|
((= nm "⎕ML") (apl-quad-ml))
|
||||||
((= nm "⎕FR") (apl-quad-fr))
|
((= nm "⎕FR") (apl-quad-fr))
|
||||||
@@ -148,11 +132,7 @@
|
|||||||
(if
|
(if
|
||||||
(and (= (first fn-node) :fn-glyph) (= (nth fn-node 1) "∇"))
|
(and (= (first fn-node) :fn-glyph) (= (nth fn-node 1) "∇"))
|
||||||
(apl-call-dfn-m (get env "nabla") (apl-eval-ast arg env))
|
(apl-call-dfn-m (get env "nabla") (apl-eval-ast arg env))
|
||||||
(let
|
((apl-resolve-monadic fn-node env) (apl-eval-ast arg env)))))
|
||||||
((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)
|
((= tag :dyad)
|
||||||
(let
|
(let
|
||||||
((fn-node (nth node 1))
|
((fn-node (nth node 1))
|
||||||
@@ -164,13 +144,9 @@
|
|||||||
(get env "nabla")
|
(get env "nabla")
|
||||||
(apl-eval-ast lhs env)
|
(apl-eval-ast lhs env)
|
||||||
(apl-eval-ast rhs env))
|
(apl-eval-ast rhs env))
|
||||||
(let
|
((apl-resolve-dyadic fn-node env)
|
||||||
((rhs-val (apl-eval-ast rhs env)))
|
(apl-eval-ast lhs env)
|
||||||
(let
|
(apl-eval-ast rhs env)))))
|
||||||
((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 :program) (apl-eval-stmts (rest node) env))
|
||||||
((= tag :dfn) node)
|
((= tag :dfn) node)
|
||||||
((= tag :bracket)
|
((= tag :bracket)
|
||||||
@@ -183,8 +159,6 @@
|
|||||||
(fn (a) (if (= a :all) nil (apl-eval-ast a env)))
|
(fn (a) (if (= a :all) nil (apl-eval-ast a env)))
|
||||||
axis-exprs)))
|
axis-exprs)))
|
||||||
(apl-bracket-multi axes arr))))
|
(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)))))))
|
(else (error (list "apl-eval-ast: unknown node tag" tag node)))))))
|
||||||
|
|
||||||
(define
|
(define
|
||||||
@@ -564,5 +538,3 @@
|
|||||||
(else (error "apl-resolve-dyadic: unknown fn-node tag"))))))
|
(else (error "apl-resolve-dyadic: unknown fn-node tag"))))))
|
||||||
|
|
||||||
(define apl-run (fn (src) (apl-eval-ast (parse-apl src) {})))
|
(define apl-run (fn (src) (apl-eval-ast (parse-apl src) {})))
|
||||||
|
|
||||||
(define apl-run-file (fn (path) (apl-run (file-read path))))
|
|
||||||
|
|||||||
@@ -210,6 +210,7 @@
|
|||||||
:op (nth node 1)
|
:op (nth node 1)
|
||||||
(hk-desugar (nth node 2))
|
(hk-desugar (nth node 2))
|
||||||
(hk-desugar (nth node 3))))
|
(hk-desugar (nth node 3))))
|
||||||
|
((= tag "type-ann") (hk-desugar (nth node 1)))
|
||||||
((= tag "neg") (list :neg (hk-desugar (nth node 1))))
|
((= tag "neg") (list :neg (hk-desugar (nth node 1))))
|
||||||
((= tag "if")
|
((= tag "if")
|
||||||
(list
|
(list
|
||||||
|
|||||||
@@ -275,38 +275,47 @@
|
|||||||
(list :sect-right op-name expr-e))))))
|
(list :sect-right op-name expr-e))))))
|
||||||
(:else
|
(:else
|
||||||
(let
|
(let
|
||||||
((first-e (hk-parse-expr-inner))
|
((first-e (hk-parse-expr-inner)))
|
||||||
(items (list))
|
|
||||||
(is-tuple false))
|
|
||||||
(append! items first-e)
|
|
||||||
(define
|
|
||||||
hk-tup-loop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(hk-match? "comma" nil)
|
|
||||||
(do
|
|
||||||
(hk-advance!)
|
|
||||||
(set! is-tuple true)
|
|
||||||
(append! items (hk-parse-expr-inner))
|
|
||||||
(hk-tup-loop)))))
|
|
||||||
(hk-tup-loop)
|
|
||||||
(cond
|
(cond
|
||||||
((hk-match? "rparen" nil)
|
((hk-match? "reservedop" "::")
|
||||||
(do
|
(do
|
||||||
(hk-advance!)
|
(hk-advance!)
|
||||||
(if is-tuple (list :tuple items) first-e)))
|
(let
|
||||||
|
((ann-type (hk-parse-type)))
|
||||||
|
(hk-expect! "rparen" nil)
|
||||||
|
(list :type-ann first-e ann-type))))
|
||||||
(:else
|
(:else
|
||||||
(let
|
(let
|
||||||
((op-info2 (hk-section-op-info)))
|
((items (list)) (is-tuple false))
|
||||||
|
(append! items first-e)
|
||||||
|
(define
|
||||||
|
hk-tup-loop
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(hk-match? "comma" nil)
|
||||||
|
(do
|
||||||
|
(hk-advance!)
|
||||||
|
(set! is-tuple true)
|
||||||
|
(append! items (hk-parse-expr-inner))
|
||||||
|
(hk-tup-loop)))))
|
||||||
|
(hk-tup-loop)
|
||||||
(cond
|
(cond
|
||||||
((and (not (nil? op-info2)) (not is-tuple) (let ((after2 (hk-peek-at (get op-info2 "len")))) (and (not (nil? after2)) (= (get after2 "type") "rparen"))))
|
((hk-match? "rparen" nil)
|
||||||
(let
|
(do
|
||||||
((op-name (get op-info2 "name")))
|
|
||||||
(hk-consume-op!)
|
|
||||||
(hk-advance!)
|
(hk-advance!)
|
||||||
(list :sect-left op-name first-e)))
|
(if is-tuple (list :tuple items) first-e)))
|
||||||
(:else (hk-err "expected ')' after expression"))))))))))))))
|
(:else
|
||||||
|
(let
|
||||||
|
((op-info2 (hk-section-op-info)))
|
||||||
|
(cond
|
||||||
|
((and (not (nil? op-info2)) (not is-tuple) (let ((after2 (hk-peek-at (get op-info2 "len")))) (and (not (nil? after2)) (= (get after2 "type") "rparen"))))
|
||||||
|
(let
|
||||||
|
((op-name (get op-info2 "name")))
|
||||||
|
(hk-consume-op!)
|
||||||
|
(hk-advance!)
|
||||||
|
(list :sect-left op-name first-e)))
|
||||||
|
(:else (hk-err "expected ')' after expression")))))))))))))))))
|
||||||
(define
|
(define
|
||||||
hk-comp-qual-is-gen?
|
hk-comp-qual-is-gen?
|
||||||
(fn
|
(fn
|
||||||
@@ -1724,10 +1733,18 @@
|
|||||||
(= (hk-peek-type) "eof")
|
(= (hk-peek-type) "eof")
|
||||||
(hk-match? "vrbrace" nil)
|
(hk-match? "vrbrace" nil)
|
||||||
(hk-match? "rbrace" nil))))
|
(hk-match? "rbrace" nil))))
|
||||||
|
(define
|
||||||
|
hk-body-step
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(cond
|
||||||
|
((hk-match? "reserved" "import")
|
||||||
|
(append! imports (hk-parse-import)))
|
||||||
|
(:else (append! decls (hk-parse-decl))))))
|
||||||
(when
|
(when
|
||||||
(not (hk-body-at-end?))
|
(not (hk-body-at-end?))
|
||||||
(do
|
(do
|
||||||
(append! decls (hk-parse-decl))
|
(hk-body-step)
|
||||||
(define
|
(define
|
||||||
hk-body-loop
|
hk-body-loop
|
||||||
(fn
|
(fn
|
||||||
@@ -1738,7 +1755,7 @@
|
|||||||
(hk-advance!)
|
(hk-advance!)
|
||||||
(when
|
(when
|
||||||
(not (hk-body-at-end?))
|
(not (hk-body-at-end?))
|
||||||
(append! decls (hk-parse-decl)))
|
(hk-body-step))
|
||||||
(hk-body-loop)))))
|
(hk-body-loop)))))
|
||||||
(hk-body-loop)))
|
(hk-body-loop)))
|
||||||
(list imports decls))))
|
(list imports decls))))
|
||||||
|
|||||||
102
lib/haskell/tests/parse-extras.sx
Normal file
102
lib/haskell/tests/parse-extras.sx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
;; Phase 17 — parser polish unit tests.
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: literal int annotated"
|
||||||
|
(hk-deep-force (hk-run "main = (42 :: Int)"))
|
||||||
|
42)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: arithmetic annotated"
|
||||||
|
(hk-deep-force (hk-run "main = (1 + 2 :: Int)"))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: function arg annotated"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "f x = x + 1\nmain = f (1 :: Int)"))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: string annotated"
|
||||||
|
(hk-deep-force (hk-run "main = (\"hi\" :: String)"))
|
||||||
|
"hi")
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: bool annotated"
|
||||||
|
(hk-deep-force (hk-run "main = (True :: Bool)"))
|
||||||
|
(list "True"))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: tuple annotated"
|
||||||
|
(hk-deep-force (hk-run "main = ((1, 2) :: (Int, Int))"))
|
||||||
|
(list "Tuple" 1 2))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: nested annotation in arithmetic"
|
||||||
|
(hk-deep-force (hk-run "main = (1 :: Int) + (2 :: Int)"))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: function-typed annotation passes through eval"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "main = let f = ((\\x -> x + 1) :: Int -> Int) in f 5"))
|
||||||
|
6)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"no regression: plain parens still work"
|
||||||
|
(hk-deep-force (hk-run "main = (5)"))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"no regression: 3-tuple still works"
|
||||||
|
(hk-deep-force (hk-run "main = (1, 2, 3)"))
|
||||||
|
(list "Tuple" 1 2 3))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"no regression: section-left still works"
|
||||||
|
(hk-deep-force (hk-run "main = (3 +) 4"))
|
||||||
|
7)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"no regression: section-right still works"
|
||||||
|
(hk-deep-force (hk-run "main = (+ 3) 4"))
|
||||||
|
7)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"import: still works as the very first decl"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "import qualified Data.IORef as I
|
||||||
|
main = do { r <- I.newIORef 7; I.readIORef r }"))
|
||||||
|
(list "IO" 7))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"import: between decls — after main"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "main = do { r <- I.newIORef 11; I.readIORef r }
|
||||||
|
import qualified Data.IORef as I"))
|
||||||
|
(list "IO" 11))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"import: between two decls — uses helper after import"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "f x = x + 100
|
||||||
|
import qualified Data.IORef as I
|
||||||
|
main = do { r <- I.newIORef 5; I.modifyIORef r f; I.readIORef r }"))
|
||||||
|
(list "IO" 105))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"import: two imports in different positions"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "import qualified Data.IORef as I
|
||||||
|
helper x = x * 2
|
||||||
|
import qualified Data.Map as M
|
||||||
|
main = do { r <- I.newIORef (helper 21); I.readIORef r }"))
|
||||||
|
(list "IO" 42))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"import: unqualified, mid-file"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "go x = x
|
||||||
|
import Data.IORef
|
||||||
|
main = go 9"))
|
||||||
|
9)
|
||||||
@@ -16,15 +16,18 @@
|
|||||||
true)))
|
true)))
|
||||||
|
|
||||||
;; ─── Valid programs pass through ─────────────────────────────────────────────
|
;; ─── Valid programs pass through ─────────────────────────────────────────────
|
||||||
(hk-test "typed ok: simple arithmetic" (hk-run-typed "main = 1 + 2") 3)
|
(hk-test "typed ok: simple arithmetic"
|
||||||
|
(hk-deep-force (hk-run-typed "main = 1 + 2")) 3)
|
||||||
|
|
||||||
(hk-test "typed ok: boolean" (hk-run-typed "main = True") (list "True"))
|
(hk-test "typed ok: boolean"
|
||||||
|
(hk-deep-force (hk-run-typed "main = True")) (list "True"))
|
||||||
|
|
||||||
(hk-test "typed ok: let binding" (hk-run-typed "main = let x = 1 in x + 2") 3)
|
(hk-test "typed ok: let binding"
|
||||||
|
(hk-deep-force (hk-run-typed "main = let x = 1 in x + 2")) 3)
|
||||||
|
|
||||||
(hk-test
|
(hk-test
|
||||||
"typed ok: two independent fns"
|
"typed ok: two independent fns"
|
||||||
(hk-run-typed "f x = x + 1\nmain = f 5")
|
(hk-deep-force (hk-run-typed "f x = x + 1\nmain = f 5"))
|
||||||
6)
|
6)
|
||||||
|
|
||||||
;; ─── Untypeable programs are rejected ────────────────────────────────────────
|
;; ─── Untypeable programs are rejected ────────────────────────────────────────
|
||||||
@@ -76,7 +79,7 @@
|
|||||||
|
|
||||||
(hk-test
|
(hk-test
|
||||||
"run-typed sig ok: Int declared matches"
|
"run-typed sig ok: Int declared matches"
|
||||||
(hk-run-typed "main :: Int\nmain = 1 + 2")
|
(hk-deep-force (hk-run-typed "main :: Int\nmain = 1 + 2"))
|
||||||
3)
|
3)
|
||||||
|
|
||||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||||
@@ -177,56 +177,6 @@ programs run from source, and starts pushing on performance.
|
|||||||
300 s timeout). Target: profile the inner loop, eliminate quadratic
|
300 s timeout). Target: profile the inner loop, eliminate quadratic
|
||||||
list-append, restore the `queens(8)` test.
|
list-append, restore the `queens(8)` test.
|
||||||
|
|
||||||
### Phase 9 — make `.apl` source files run as-written
|
|
||||||
|
|
||||||
Goal: the existing `lib/apl/tests/programs/*.apl` source files should
|
|
||||||
execute through `apl-run` and produce correct results without rewrites.
|
|
||||||
Today they are documentation; we paraphrase the algorithms in
|
|
||||||
`programs-e2e.sx`. Phase 9 closes that gap.
|
|
||||||
|
|
||||||
- [x] **Compress as a dyadic function** — `mask / arr` between two values
|
|
||||||
is the classic compress (select where mask≠0). Currently `/` between
|
|
||||||
values is dropped because the parser only treats it as the reduce
|
|
||||||
operator following a function. Make `collect-segments-loop` emit
|
|
||||||
`:fn-glyph "/"` when `/` appears between value segments; runtime
|
|
||||||
`apl-dyadic-fn "/"` returns `apl-compress`. Same for `⌿`
|
|
||||||
(first-axis compress).
|
|
||||||
- [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
|
|
||||||
`<name> ← <expr>` 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`.)_
|
|
||||||
- [x] **`?` (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.
|
|
||||||
- [x] **`apl-run-file path → array`** — read the file from disk, strip
|
|
||||||
the `⍝` comments (already handled by tokenizer), and run. Needs an
|
|
||||||
IO primitive on the SX side. Probe `mcp` / `harness`-style file
|
|
||||||
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.)_
|
|
||||||
- [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, `⍵⌷⍨?≢⍵`.)_
|
|
||||||
- [x] **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
|
|
||||||
but the parser doesn't see.
|
|
||||||
_(Wired ⍉ → apl-transpose / apl-transpose-dyadic, ⊢ identity,
|
|
||||||
⊣ left, ⍕ as alias for ⎕FMT. ⊆ ∪ ∩ ⍸ ⊥ ⊤ ⍎ remain unimplemented
|
|
||||||
in the runtime — parser sees them as functions but eval errors;
|
|
||||||
next-phase work.)_
|
|
||||||
|
|
||||||
## SX primitive baseline
|
## SX primitive baseline
|
||||||
|
|
||||||
Use vectors for arrays; numeric tower + rationals for numbers; ADTs for tagged data;
|
Use vectors for arrays; numeric tower + rationals for numbers; ADTs for tagged data;
|
||||||
@@ -241,13 +191,6 @@ data; format for string templating.
|
|||||||
|
|
||||||
_Newest first._
|
_Newest first._
|
||||||
|
|
||||||
- 2026-05-07: Phase 9 step 6 — glyph audit. Wired ⍉ → apl-transpose/apl-transpose-dyadic, ⊢ → monadic+dyadic identity-right, ⊣ → identity-left, ⍕ → apl-quad-fmt. +6 tests; **Phase 9 complete, all unchecked items ticked**; pipeline 99/99
|
|
||||||
- 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.)
|
|
||||||
- 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
|
- 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
|
||||||
- 2026-05-07: Phase 8 step 5 — train/fork notation. Parser :lparen detects all-fn inner segments → emits :train AST; resolver covers 2-atop & 3-fork for both monadic and dyadic. `(+/÷≢) 1..5 → 3` (mean), `(- ⌊) 5 → -5` (atop), `2(+×-)5 → -21` (dyadic fork), `(⌈/-⌊/) → 8` (range); +6 tests; 496/496
|
- 2026-05-07: Phase 8 step 5 — train/fork notation. Parser :lparen detects all-fn inner segments → emits :train AST; resolver covers 2-atop & 3-fork for both monadic and dyadic. `(+/÷≢) 1..5 → 3` (mean), `(- ⌊) 5 → -5` (atop), `2(+×-)5 → -21` (dyadic fork), `(⌈/-⌊/) → 8` (range); +6 tests; 496/496
|
||||||
- 2026-05-07: Phase 8 step 4 — programs-e2e.sx runs classic-algorithm shapes through full pipeline (factorial via ∇, triangulars, sum-of-squares, divisor-counts, prime-mask, named-fn composition, dyadic max-of-two, Newton step); also added ⌿ + ⍀ to glyph sets (were silently skipped); +15 tests; 490/490
|
- 2026-05-07: Phase 8 step 4 — programs-e2e.sx runs classic-algorithm shapes through full pipeline (factorial via ∇, triangulars, sum-of-squares, divisor-counts, prime-mask, named-fn composition, dyadic max-of-two, Newton step); also added ⌿ + ⍀ to glyph sets (were silently skipped); +15 tests; 490/490
|
||||||
@@ -298,6 +241,4 @@ _Newest first._
|
|||||||
|
|
||||||
## Blockers
|
## Blockers
|
||||||
|
|
||||||
- 2026-05-07: **sx-tree MCP server disconnected mid-Phase-9.** `lib/apl/**.sx`
|
- _(none yet)_
|
||||||
edits require `sx-tree` per CLAUDE.md — Edit/Read on `.sx` is hook-blocked.
|
|
||||||
Loop paused at Phase 9 step 2 (inline assignment); resume once MCP restored.
|
|
||||||
|
|||||||
@@ -316,11 +316,11 @@ No OCaml changes are needed. The view type is fully representable as an SX dict.
|
|||||||
Real Haskell programs use these on every page; closing the gaps unblocks
|
Real Haskell programs use these on every page; closing the gaps unblocks
|
||||||
larger conformance programs and removes one-line workarounds in test sources.
|
larger conformance programs and removes one-line workarounds in test sources.
|
||||||
|
|
||||||
- [ ] Type annotations in expressions: `(x :: Int)`, `f (1 :: Int)`,
|
- [x] Type annotations in expressions: `(x :: Int)`, `f (1 :: Int)`,
|
||||||
`return (42 :: Int)`. Parser currently rejects `::` in `aexp` position;
|
`return (42 :: Int)`. Parser currently rejects `::` in `aexp` position;
|
||||||
desugar should drop the annotation (we have no inference at this layer
|
desugar should drop the annotation (we have no inference at this layer
|
||||||
yet, so it's a parse-only pass-through).
|
yet, so it's a parse-only pass-through).
|
||||||
- [ ] `import` declarations anywhere at the start of a module — currently
|
- [x] `import` declarations anywhere at the start of a module — currently
|
||||||
only the very-top-of-file form is recognised. Real test programs that
|
only the very-top-of-file form is recognised. Real test programs that
|
||||||
mix prelude code with `import qualified Data.IORef` need this.
|
mix prelude code with `import qualified Data.IORef` need this.
|
||||||
- [ ] Multi-line top-level `where` blocks (`where { ... }` with explicit
|
- [ ] Multi-line top-level `where` blocks (`where { ... }` with explicit
|
||||||
@@ -359,10 +359,100 @@ that to single-digit minutes.
|
|||||||
- [ ] Verify the scoreboard output is byte-identical to the old per-process
|
- [ ] Verify the scoreboard output is byte-identical to the old per-process
|
||||||
driver, then keep the per-process path as `--isolated` for debugging.
|
driver, then keep the per-process path as `--isolated` for debugging.
|
||||||
|
|
||||||
|
### Phase 20 — Close Algorithm W gaps
|
||||||
|
|
||||||
|
`lib/haskell/infer.sx` already implements core HM (TVar/TCon/TArr/TApp/TTuple/
|
||||||
|
TScheme, substitution, occurs-check unification, instantiate/generalize, let-
|
||||||
|
polymorphism). 75 inference unit tests + 15 typecheck integration tests pass.
|
||||||
|
The remaining gaps that block typing real programs:
|
||||||
|
|
||||||
|
- [ ] `case` expressions in `hk-w`. Needs to infer scrutinee type, then for
|
||||||
|
each `(:alt pat body)` infer the pattern's binding env (extending
|
||||||
|
`hk-w-pat`) and unify body types across alts.
|
||||||
|
- [ ] `do` notation: extend `hk-type-env0` with `return :: a -> IO a`,
|
||||||
|
`(>>=) :: IO a -> (a -> IO b) -> IO b`, `(>>) :: IO a -> IO b -> IO b`,
|
||||||
|
and primitive IO actions (`putStrLn :: String -> IO ()`,
|
||||||
|
`getLine :: IO String`, etc.). May need a `TApp (TCon "IO") a` shape.
|
||||||
|
- [ ] Record-accessor desugaring leaves `__rec_field` placeholder visible to
|
||||||
|
inference. Either skip generated accessor clauses during `hk-infer-prog`
|
||||||
|
or rewrite the desugar to produce a typed shape.
|
||||||
|
- [ ] Type annotations in expressions `(x :: Int)` (parser also needed; see
|
||||||
|
Phase 17). Infer should unify the inferred type with the annotation.
|
||||||
|
- [ ] Tests in `lib/haskell/tests/infer-extras.sx` (≥ 10) covering the
|
||||||
|
above shapes.
|
||||||
|
|
||||||
|
### Phase 21 — Type classes (Eq, Ord, Num, Show)
|
||||||
|
|
||||||
|
The evaluator already implements typeclass dispatch via dict-passing
|
||||||
|
(`__default__ClassName_method` + per-instance dicts). The type system
|
||||||
|
ignores `class` and `instance` decls. Closing this means HM with
|
||||||
|
constraints (qualified types `[ClassName var] => type`).
|
||||||
|
|
||||||
|
- [ ] Extend the type representation: `(TQual CONSTRAINTS TYPE)` where
|
||||||
|
`CONSTRAINTS = [(class-name . type-arg), …]`.
|
||||||
|
- [ ] Generalize → `forall vars. preds => type`; instantiate → fresh-rename
|
||||||
|
vars in both preds and type.
|
||||||
|
- [ ] During inference, when a primitive operator that needs a class is
|
||||||
|
used (e.g. `+`), emit a constraint `(Num t)`; collect constraints in
|
||||||
|
the substitution-threading.
|
||||||
|
- [ ] At let-generalization, simplify constraints (defaulting for `Num`
|
||||||
|
literals → `Int`; entailment via known instances).
|
||||||
|
- [ ] `class` declarations register members with their qualified type;
|
||||||
|
`instance` declarations register a witness.
|
||||||
|
- [ ] At top-level, if any unsolvable constraint remains → type error
|
||||||
|
("No instance for X").
|
||||||
|
- [ ] Tests in `lib/haskell/tests/typeclasses.sx` (≥ 12 covering Eq, Ord,
|
||||||
|
Num overloading, show on instances, instance ambiguity rejection).
|
||||||
|
|
||||||
|
### Phase 22 — Typecheck-then-run as the default
|
||||||
|
|
||||||
|
- [ ] Replace `hk-run` with a typecheck-first variant in the conformance
|
||||||
|
driver, or run conformance twice (once typed, once untyped) and report
|
||||||
|
both pass-rates in `scoreboard.md`.
|
||||||
|
- [ ] Investigate which existing 36 programs are untypeable due to gaps
|
||||||
|
closed in Phase 20-21 vs genuinely dynamically-typed; aim for ≥ 30/36
|
||||||
|
programs typechecking before committing to the swap.
|
||||||
|
- [ ] If swap is committed, retire `hk-run` callsites in tests in favour
|
||||||
|
of `hk-run-typed`; keep the untyped path available for parser/eval
|
||||||
|
development against in-progress features.
|
||||||
|
|
||||||
## Progress log
|
## Progress log
|
||||||
|
|
||||||
_Newest first._
|
_Newest first._
|
||||||
|
|
||||||
|
**2026-05-10** — Phase 17 second box: `import` declarations anywhere among
|
||||||
|
top-level decls. `hk-collect-module-body` previously ran a fixed
|
||||||
|
import-loop at the start, then a separate decl-loop; merged into a single
|
||||||
|
`hk-body-step` dispatcher that routes `import` to the imports list and
|
||||||
|
everything else to `hk-parse-decl`. Each call site (initial step + post-
|
||||||
|
semicolon loop) now uses the dispatcher. Imports collected mid-stream
|
||||||
|
still feed into `hk-bind-decls!` correctly because the eval side reads
|
||||||
|
them via the imports list, not by AST position. tests/parse-extras.sx
|
||||||
|
12 → 17 covering very-top, mid-stream, post-main, two-imports-different-
|
||||||
|
positions, and unqualified mid-file. Regression: eval 66/0, exceptions
|
||||||
|
14/0, typecheck 15/0, records 14/0, ioref 13/0, map 26/0, set 17/0.
|
||||||
|
|
||||||
|
**2026-05-08** — Phase 17 first box: expression type annotations `(x :: Int)`,
|
||||||
|
`f (1 :: Int)`, `(\x -> x+1) :: Int -> Int`. Parser's `hk-parse-parens`
|
||||||
|
gains a `::` arm after the first inner expression: consume `::`, parse a
|
||||||
|
type via the existing `hk-parse-type`, expect `)`, emit `(:type-ann EXPR
|
||||||
|
TYPE)`. Desugar drops the annotation — `:type-ann E _ → (hk-desugar E)` —
|
||||||
|
since the existing eval path has no type-directed dispatch; Phase 20 will
|
||||||
|
let inference consume the annotation. tests/parse-extras.sx 12/12; eval,
|
||||||
|
exceptions, typecheck, records, ioref still clean.
|
||||||
|
|
||||||
|
**2026-05-08** — Plan extends with Phases 20-22 (HM type system). Discovered
|
||||||
|
during planning that `lib/haskell/infer.sx` already lands core Algorithm W
|
||||||
|
(75 inference unit tests pass; let-polymorphism, sig checking, error
|
||||||
|
reporting via `hk-expr->brief`). Fixed five regressing tests in
|
||||||
|
`lib/haskell/tests/typecheck.sx` that compared an unforced thunk against
|
||||||
|
the expected value — added `hk-deep-force` around `hk-run-typed` to match
|
||||||
|
the existing untyped-path convention. typecheck.sx now 15/15.
|
||||||
|
Phase 20 captures the remaining Algorithm W gaps (case, do, record
|
||||||
|
accessors, expression annotations); Phase 21 captures type classes with
|
||||||
|
qualified types; Phase 22 captures the integration step (typecheck-then-run
|
||||||
|
across conformance).
|
||||||
|
|
||||||
**2026-05-08** — Phase 16 Exception handling complete (6 ops + module wiring +
|
**2026-05-08** — Phase 16 Exception handling complete (6 ops + module wiring +
|
||||||
14 unit tests + 2 conformance programs). `hk-bind-exceptions!` in `eval.sx`
|
14 unit tests + 2 conformance programs). `hk-bind-exceptions!` in `eval.sx`
|
||||||
registers `throwIO`, `throw`, `evaluate`, `catch`, `try`, `handle`, and
|
registers `throwIO`, `throw`, `evaluate`, `catch`, `try`, `handle`, and
|
||||||
|
|||||||
Reference in New Issue
Block a user