Compare commits
56 Commits
bugs/resum
...
loops/hask
| Author | SHA1 | Date | |
|---|---|---|---|
| 544e79f533 | |||
| f1fea0f2f1 | |||
| f26f25f146 | |||
| 63c1e17c75 | |||
| a4fd57cff1 | |||
| 76d141737a | |||
| 9307437679 | |||
| b89e321007 | |||
| ca9e12fc57 | |||
| 2adbc101fa | |||
| 4205989aee | |||
| 49252eaa5c | |||
| ebbf0fc10c | |||
| 8dfb3f6387 | |||
| 5a8c25bec7 | |||
| c821e21f94 | |||
| 5605fe1cc2 | |||
| 379bb93f14 | |||
| 7ce0c797f3 | |||
| 34513908df | |||
| 208953667b | |||
| e6d6273265 | |||
| e95ca4624b | |||
| e1a020dc90 | |||
| b0974b58c0 | |||
| 6620c0ac06 | |||
| 95cf653ba9 | |||
| 12de24e3a0 | |||
| 180b9009bf | |||
| a29bb6feca | |||
| d2638170db | |||
| a5c41d2573 | |||
| 882815e612 | |||
| e27daee4a8 | |||
| ef33e9a43a | |||
| 1b7bd86b43 | |||
| e5fe9ad2d4 | |||
| 2d373da06b | |||
| 25cf832998 | |||
| 29542ba9d2 | |||
| c2de220cce | |||
| d523df30c2 | |||
| 1b844f6a19 | |||
| 5f758d27c1 | |||
| 51f57aa2fa | |||
| 31308602ca | |||
| 788e8682f5 | |||
| bb134b88e3 | |||
| d8dec07df3 | |||
| 39c7baa44c | |||
| ee74a396c5 | |||
| a8997ab452 | |||
| 80d6507e57 | |||
| 685fcd11d5 | |||
| f6efba410a | |||
| 4a35998469 |
@@ -20,7 +20,7 @@ if [ ! -x "$SX_SERVER" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
PROGRAMS=(fib sieve quicksort nqueens calculator collatz palindrome maybe fizzbuzz anagram roman binary either primes zipwith matrix wordcount powers)
|
||||
PROGRAMS=(fib sieve quicksort nqueens calculator collatz palindrome maybe fizzbuzz anagram roman binary either primes zipwith matrix wordcount powers caesar runlength-str showadt showio partial statistics newton wordfreq mapgraph uniquewords setops shapes person config counter accumulate)
|
||||
PASS_COUNTS=()
|
||||
FAIL_COUNTS=()
|
||||
|
||||
@@ -38,6 +38,8 @@ run_suite() {
|
||||
(load "lib/haskell/runtime.sx")
|
||||
(load "lib/haskell/match.sx")
|
||||
(load "lib/haskell/eval.sx")
|
||||
(load "lib/haskell/map.sx")
|
||||
(load "lib/haskell/set.sx")
|
||||
(load "lib/haskell/testlib.sx")
|
||||
(epoch 2)
|
||||
(load "$FILE")
|
||||
|
||||
@@ -131,119 +131,280 @@
|
||||
(let
|
||||
((tag (first node)))
|
||||
(cond
|
||||
;; Transformations
|
||||
((= tag "where")
|
||||
(list
|
||||
:let
|
||||
(map hk-desugar (nth node 2))
|
||||
:let (map hk-desugar (nth node 2))
|
||||
(hk-desugar (nth node 1))))
|
||||
((= tag "guarded") (hk-guards-to-if (nth node 1)))
|
||||
((= tag "list-comp")
|
||||
(hk-lc-desugar
|
||||
(hk-desugar (nth node 1))
|
||||
(nth node 2)))
|
||||
|
||||
;; Expression nodes
|
||||
(hk-lc-desugar (hk-desugar (nth node 1)) (nth node 2)))
|
||||
((= tag "app")
|
||||
(list
|
||||
:app
|
||||
(hk-desugar (nth node 1))
|
||||
:app (hk-desugar (nth node 1))
|
||||
(hk-desugar (nth node 2))))
|
||||
((= tag "p-rec")
|
||||
(let
|
||||
((cname (nth node 1))
|
||||
(field-pats (nth node 2))
|
||||
(field-order (hk-record-field-names cname)))
|
||||
(cond
|
||||
((nil? field-order)
|
||||
(raise (str "p-rec: no record info for " cname)))
|
||||
(:else
|
||||
(list
|
||||
:p-con
|
||||
cname
|
||||
(map
|
||||
(fn
|
||||
(fname)
|
||||
(let
|
||||
((p (hk-find-rec-pair field-pats fname)))
|
||||
(cond
|
||||
((nil? p) (list :p-wild))
|
||||
(:else (hk-desugar (nth p 1))))))
|
||||
field-order))))))
|
||||
((= tag "rec-update")
|
||||
(list
|
||||
:rec-update
|
||||
(hk-desugar (nth node 1))
|
||||
(map
|
||||
(fn (p) (list (first p) (hk-desugar (nth p 1))))
|
||||
(nth node 2))))
|
||||
((= tag "rec-create")
|
||||
(let
|
||||
((cname (nth node 1))
|
||||
(field-pairs (nth node 2))
|
||||
(field-order (hk-record-field-names cname)))
|
||||
(cond
|
||||
((nil? field-order)
|
||||
(raise (str "rec-create: no record info for " cname)))
|
||||
(:else
|
||||
(let
|
||||
((acc (list :con cname)))
|
||||
(begin
|
||||
(for-each
|
||||
(fn
|
||||
(fname)
|
||||
(let
|
||||
((pair
|
||||
(hk-find-rec-pair field-pairs fname)))
|
||||
(cond
|
||||
((nil? pair)
|
||||
(raise
|
||||
(str
|
||||
"rec-create: missing field "
|
||||
fname
|
||||
" for "
|
||||
cname)))
|
||||
(:else
|
||||
(set!
|
||||
acc
|
||||
(list
|
||||
:app
|
||||
acc
|
||||
(hk-desugar (nth pair 1))))))))
|
||||
field-order)
|
||||
acc))))))
|
||||
((= tag "op")
|
||||
(list
|
||||
:op
|
||||
(nth node 1)
|
||||
:op (nth node 1)
|
||||
(hk-desugar (nth node 2))
|
||||
(hk-desugar (nth node 3))))
|
||||
((= tag "neg") (list :neg (hk-desugar (nth node 1))))
|
||||
((= tag "if")
|
||||
(list
|
||||
:if
|
||||
(hk-desugar (nth node 1))
|
||||
:if (hk-desugar (nth node 1))
|
||||
(hk-desugar (nth node 2))
|
||||
(hk-desugar (nth node 3))))
|
||||
((= tag "tuple")
|
||||
(list :tuple (map hk-desugar (nth node 1))))
|
||||
((= tag "list")
|
||||
(list :list (map hk-desugar (nth node 1))))
|
||||
((= tag "tuple") (list :tuple (map hk-desugar (nth node 1))))
|
||||
((= tag "list") (list :list (map hk-desugar (nth node 1))))
|
||||
((= tag "range")
|
||||
(list
|
||||
:range
|
||||
(hk-desugar (nth node 1))
|
||||
:range (hk-desugar (nth node 1))
|
||||
(hk-desugar (nth node 2))))
|
||||
((= tag "range-step")
|
||||
(list
|
||||
:range-step
|
||||
(hk-desugar (nth node 1))
|
||||
:range-step (hk-desugar (nth node 1))
|
||||
(hk-desugar (nth node 2))
|
||||
(hk-desugar (nth node 3))))
|
||||
((= tag "lambda")
|
||||
(list
|
||||
:lambda
|
||||
(nth node 1)
|
||||
(hk-desugar (nth node 2))))
|
||||
(list :lambda (nth node 1) (hk-desugar (nth node 2))))
|
||||
((= tag "let")
|
||||
(list
|
||||
:let
|
||||
(map hk-desugar (nth node 1))
|
||||
:let (map hk-desugar (nth node 1))
|
||||
(hk-desugar (nth node 2))))
|
||||
((= tag "case")
|
||||
(list
|
||||
:case
|
||||
(hk-desugar (nth node 1))
|
||||
:case (hk-desugar (nth node 1))
|
||||
(map hk-desugar (nth node 2))))
|
||||
((= tag "alt")
|
||||
(list :alt (nth node 1) (hk-desugar (nth node 2))))
|
||||
(list :alt (hk-desugar (nth node 1)) (hk-desugar (nth node 2))))
|
||||
((= tag "do") (hk-desugar-do (nth node 1)))
|
||||
((= tag "sect-left")
|
||||
(list
|
||||
:sect-left
|
||||
(nth node 1)
|
||||
(hk-desugar (nth node 2))))
|
||||
(list :sect-left (nth node 1) (hk-desugar (nth node 2))))
|
||||
((= tag "sect-right")
|
||||
(list
|
||||
:sect-right
|
||||
(nth node 1)
|
||||
(hk-desugar (nth node 2))))
|
||||
|
||||
;; Top-level
|
||||
(list :sect-right (nth node 1) (hk-desugar (nth node 2))))
|
||||
((= tag "program")
|
||||
(list :program (map hk-desugar (nth node 1))))
|
||||
(list :program (map hk-desugar (hk-expand-records (nth node 1)))))
|
||||
((= tag "module")
|
||||
(list
|
||||
:module
|
||||
(nth node 1)
|
||||
:module (nth node 1)
|
||||
(nth node 2)
|
||||
(nth node 3)
|
||||
(map hk-desugar (nth node 4))))
|
||||
|
||||
;; Decls carrying a body
|
||||
(map hk-desugar (hk-expand-records (nth node 4)))))
|
||||
((= tag "fun-clause")
|
||||
(list
|
||||
:fun-clause
|
||||
(nth node 1)
|
||||
(nth node 2)
|
||||
:fun-clause (nth node 1)
|
||||
(map hk-desugar (nth node 2))
|
||||
(hk-desugar (nth node 3))))
|
||||
((= tag "instance-decl")
|
||||
(list
|
||||
:instance-decl (nth node 1)
|
||||
(nth node 2)
|
||||
(map hk-desugar (nth node 3))))
|
||||
((= tag "pat-bind")
|
||||
(list
|
||||
:pat-bind
|
||||
(nth node 1)
|
||||
(hk-desugar (nth node 2))))
|
||||
(list :pat-bind (nth node 1) (hk-desugar (nth node 2))))
|
||||
((= tag "bind")
|
||||
(list
|
||||
:bind
|
||||
(nth node 1)
|
||||
(hk-desugar (nth node 2))))
|
||||
|
||||
;; Everything else: leaf literals, vars, cons, patterns,
|
||||
;; types, imports, type-sigs, data / newtype / fixity, …
|
||||
(list :bind (nth node 1) (hk-desugar (nth node 2))))
|
||||
(:else node)))))))
|
||||
|
||||
;; Convenience — tokenize + layout + parse + desugar.
|
||||
(define
|
||||
hk-core
|
||||
(fn (src) (hk-desugar (hk-parse-top src))))
|
||||
(define hk-record-fields (dict))
|
||||
|
||||
(define
|
||||
hk-core-expr
|
||||
(fn (src) (hk-desugar (hk-parse src))))
|
||||
hk-register-record-fields!
|
||||
(fn (cname fields) (dict-set! hk-record-fields cname fields)))
|
||||
|
||||
(define
|
||||
hk-record-field-names
|
||||
(fn
|
||||
(cname)
|
||||
(if (has-key? hk-record-fields cname) (get hk-record-fields cname) nil)))
|
||||
|
||||
(define
|
||||
hk-record-field-index
|
||||
(fn
|
||||
(cname fname)
|
||||
(let
|
||||
((fields (hk-record-field-names cname)))
|
||||
(cond
|
||||
((nil? fields) -1)
|
||||
(:else
|
||||
(let
|
||||
((i 0) (idx -1))
|
||||
(begin
|
||||
(for-each
|
||||
(fn
|
||||
(f)
|
||||
(begin (when (= f fname) (set! idx i)) (set! i (+ i 1))))
|
||||
fields)
|
||||
idx)))))))
|
||||
|
||||
(define
|
||||
hk-find-rec-pair
|
||||
(fn
|
||||
(pairs name)
|
||||
(cond
|
||||
((empty? pairs) nil)
|
||||
((= (first (first pairs)) name) (first pairs))
|
||||
(:else (hk-find-rec-pair (rest pairs) name)))))
|
||||
|
||||
(define
|
||||
hk-record-accessors
|
||||
(fn
|
||||
(cname rec-fields)
|
||||
(let
|
||||
((n (len rec-fields)) (i 0) (out (list)))
|
||||
(define
|
||||
hk-ra-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(< i n)
|
||||
(let
|
||||
((field (nth rec-fields i)))
|
||||
(let
|
||||
((fname (first field)) (j 0) (pats (list)))
|
||||
(define
|
||||
hk-pat-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(< j n)
|
||||
(begin
|
||||
(append!
|
||||
pats
|
||||
(if
|
||||
(= j i)
|
||||
(list "p-var" "__rec_field")
|
||||
(list "p-wild")))
|
||||
(set! j (+ j 1))
|
||||
(hk-pat-loop)))))
|
||||
(hk-pat-loop)
|
||||
(append!
|
||||
out
|
||||
(list
|
||||
"fun-clause"
|
||||
fname
|
||||
(list (list "p-con" cname pats))
|
||||
(list "var" "__rec_field")))
|
||||
(set! i (+ i 1))
|
||||
(hk-ra-loop))))))
|
||||
(hk-ra-loop)
|
||||
out)))
|
||||
|
||||
(define
|
||||
hk-expand-records
|
||||
(fn
|
||||
(decls)
|
||||
(let
|
||||
((out (list)))
|
||||
(for-each
|
||||
(fn
|
||||
(d)
|
||||
(cond
|
||||
((and (list? d) (= (first d) "data"))
|
||||
(let
|
||||
((dname (nth d 1))
|
||||
(tvars (nth d 2))
|
||||
(cons-list (nth d 3))
|
||||
(deriving (if (> (len d) 4) (nth d 4) (list)))
|
||||
(new-cons (list))
|
||||
(accessors (list)))
|
||||
(begin
|
||||
(for-each
|
||||
(fn
|
||||
(c)
|
||||
(cond
|
||||
((= (first c) "con-rec")
|
||||
(let
|
||||
((cname (nth c 1)) (rec-fields (nth c 2)))
|
||||
(begin
|
||||
(hk-register-record-fields!
|
||||
cname
|
||||
(map (fn (f) (first f)) rec-fields))
|
||||
(append!
|
||||
new-cons
|
||||
(list
|
||||
"con-def"
|
||||
cname
|
||||
(map (fn (f) (nth f 1)) rec-fields)))
|
||||
(for-each
|
||||
(fn (a) (append! accessors a))
|
||||
(hk-record-accessors cname rec-fields)))))
|
||||
(:else (append! new-cons c))))
|
||||
cons-list)
|
||||
(append!
|
||||
out
|
||||
(if
|
||||
(empty? deriving)
|
||||
(list "data" dname tvars new-cons)
|
||||
(list "data" dname tvars new-cons deriving)))
|
||||
(for-each (fn (a) (append! out a)) accessors))))
|
||||
(:else (append! out d))))
|
||||
decls)
|
||||
out)))
|
||||
|
||||
(define hk-core (fn (src) (hk-desugar (hk-parse-top src))))
|
||||
|
||||
(define hk-core-expr (fn (src) (hk-desugar (hk-parse src))))
|
||||
|
||||
File diff suppressed because one or more lines are too long
520
lib/haskell/map.sx
Normal file
520
lib/haskell/map.sx
Normal file
@@ -0,0 +1,520 @@
|
||||
;; map.sx — Phase 11 Data.Map: weight-balanced BST in pure SX.
|
||||
;;
|
||||
;; Algorithm: Adams's weight-balanced tree (the same family as Haskell's
|
||||
;; Data.Map). Each node tracks its size; rotations maintain the invariant
|
||||
;;
|
||||
;; size(small-side) * delta >= size(large-side) (delta = 3)
|
||||
;;
|
||||
;; with single or double rotations chosen by the gamma ratio (gamma = 2).
|
||||
;; The size field is an Int and is included so `size`, `lookup`, etc. are
|
||||
;; O(log n) on both extremes of the tree.
|
||||
;;
|
||||
;; Representation:
|
||||
;; Empty → ("Map-Empty")
|
||||
;; Node → ("Map-Node" key val left right size)
|
||||
;;
|
||||
;; All operations are pure SX — no mutation of nodes once constructed.
|
||||
;; The user-facing Haskell layer (Phase 11 next iteration) wraps these
|
||||
;; for `import Data.Map as Map`.
|
||||
|
||||
;; ── Constructors ────────────────────────────────────────────
|
||||
(define hk-map-empty (list "Map-Empty"))
|
||||
|
||||
(define
|
||||
hk-map-node
|
||||
(fn
|
||||
(k v l r)
|
||||
(list "Map-Node" k v l r (+ 1 (+ (hk-map-size l) (hk-map-size r))))))
|
||||
|
||||
;; ── Predicates and accessors ────────────────────────────────
|
||||
(define hk-map-empty? (fn (m) (and (list? m) (= (first m) "Map-Empty"))))
|
||||
|
||||
(define hk-map-node? (fn (m) (and (list? m) (= (first m) "Map-Node"))))
|
||||
|
||||
(define
|
||||
hk-map-size
|
||||
(fn (m) (cond ((hk-map-empty? m) 0) (:else (nth m 5)))))
|
||||
|
||||
(define hk-map-key (fn (m) (nth m 1)))
|
||||
(define hk-map-val (fn (m) (nth m 2)))
|
||||
(define hk-map-left (fn (m) (nth m 3)))
|
||||
(define hk-map-right (fn (m) (nth m 4)))
|
||||
|
||||
;; ── Weight-balanced rotations ───────────────────────────────
|
||||
;; delta and gamma per Adams 1992 / Haskell Data.Map.
|
||||
|
||||
(define hk-map-delta 3)
|
||||
(define hk-map-gamma 2)
|
||||
|
||||
(define
|
||||
hk-map-single-l
|
||||
(fn
|
||||
(k v l r)
|
||||
(let
|
||||
((rk (hk-map-key r))
|
||||
(rv (hk-map-val r))
|
||||
(rl (hk-map-left r))
|
||||
(rr (hk-map-right r)))
|
||||
(hk-map-node rk rv (hk-map-node k v l rl) rr))))
|
||||
|
||||
(define
|
||||
hk-map-single-r
|
||||
(fn
|
||||
(k v l r)
|
||||
(let
|
||||
((lk (hk-map-key l))
|
||||
(lv (hk-map-val l))
|
||||
(ll (hk-map-left l))
|
||||
(lr (hk-map-right l)))
|
||||
(hk-map-node lk lv ll (hk-map-node k v lr r)))))
|
||||
|
||||
(define
|
||||
hk-map-double-l
|
||||
(fn
|
||||
(k v l r)
|
||||
(let
|
||||
((rk (hk-map-key r))
|
||||
(rv (hk-map-val r))
|
||||
(rl (hk-map-left r))
|
||||
(rr (hk-map-right r))
|
||||
(rlk (hk-map-key (hk-map-left r)))
|
||||
(rlv (hk-map-val (hk-map-left r)))
|
||||
(rll (hk-map-left (hk-map-left r)))
|
||||
(rlr (hk-map-right (hk-map-left r))))
|
||||
(hk-map-node
|
||||
rlk
|
||||
rlv
|
||||
(hk-map-node k v l rll)
|
||||
(hk-map-node rk rv rlr rr)))))
|
||||
|
||||
(define
|
||||
hk-map-double-r
|
||||
(fn
|
||||
(k v l r)
|
||||
(let
|
||||
((lk (hk-map-key l))
|
||||
(lv (hk-map-val l))
|
||||
(ll (hk-map-left l))
|
||||
(lr (hk-map-right l))
|
||||
(lrk (hk-map-key (hk-map-right l)))
|
||||
(lrv (hk-map-val (hk-map-right l)))
|
||||
(lrl (hk-map-left (hk-map-right l)))
|
||||
(lrr (hk-map-right (hk-map-right l))))
|
||||
(hk-map-node
|
||||
lrk
|
||||
lrv
|
||||
(hk-map-node lk lv ll lrl)
|
||||
(hk-map-node k v lrr r)))))
|
||||
|
||||
;; ── Balanced node constructor ──────────────────────────────
|
||||
;; Use this in place of hk-map-node when one side may have grown
|
||||
;; or shrunk by one and we need to restore the weight invariant.
|
||||
(define
|
||||
hk-map-balance
|
||||
(fn
|
||||
(k v l r)
|
||||
(let
|
||||
((sl (hk-map-size l)) (sr (hk-map-size r)))
|
||||
(cond
|
||||
((<= (+ sl sr) 1) (hk-map-node k v l r))
|
||||
((> sr (* hk-map-delta sl))
|
||||
(let
|
||||
((rl (hk-map-left r)) (rr (hk-map-right r)))
|
||||
(cond
|
||||
((< (hk-map-size rl) (* hk-map-gamma (hk-map-size rr)))
|
||||
(hk-map-single-l k v l r))
|
||||
(:else (hk-map-double-l k v l r)))))
|
||||
((> sl (* hk-map-delta sr))
|
||||
(let
|
||||
((ll (hk-map-left l)) (lr (hk-map-right l)))
|
||||
(cond
|
||||
((< (hk-map-size lr) (* hk-map-gamma (hk-map-size ll)))
|
||||
(hk-map-single-r k v l r))
|
||||
(:else (hk-map-double-r k v l r)))))
|
||||
(:else (hk-map-node k v l r))))))
|
||||
|
||||
(define
|
||||
hk-map-singleton
|
||||
(fn (k v) (hk-map-node k v hk-map-empty hk-map-empty)))
|
||||
|
||||
(define
|
||||
hk-map-insert
|
||||
(fn
|
||||
(k v m)
|
||||
(cond
|
||||
((hk-map-empty? m) (hk-map-singleton k v))
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-insert k v (hk-map-left m))
|
||||
(hk-map-right m)))
|
||||
((> k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-left m)
|
||||
(hk-map-insert k v (hk-map-right m))))
|
||||
(:else (hk-map-node k v (hk-map-left m) (hk-map-right m)))))))))
|
||||
|
||||
(define
|
||||
hk-map-lookup
|
||||
(fn
|
||||
(k m)
|
||||
(cond
|
||||
((hk-map-empty? m) (list "Nothing"))
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk) (hk-map-lookup k (hk-map-left m)))
|
||||
((> k mk) (hk-map-lookup k (hk-map-right m)))
|
||||
(:else (list "Just" (hk-map-val m)))))))))
|
||||
|
||||
(define
|
||||
hk-map-member
|
||||
(fn
|
||||
(k m)
|
||||
(cond
|
||||
((hk-map-empty? m) false)
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk) (hk-map-member k (hk-map-left m)))
|
||||
((> k mk) (hk-map-member k (hk-map-right m)))
|
||||
(:else true)))))))
|
||||
|
||||
(define hk-map-null hk-map-empty?)
|
||||
|
||||
(define
|
||||
hk-map-find-min
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? (hk-map-left m))
|
||||
(list (hk-map-key m) (hk-map-val m)))
|
||||
(:else (hk-map-find-min (hk-map-left m))))))
|
||||
|
||||
(define
|
||||
hk-map-delete-min
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? (hk-map-left m)) (hk-map-right m))
|
||||
(:else
|
||||
(hk-map-balance
|
||||
(hk-map-key m)
|
||||
(hk-map-val m)
|
||||
(hk-map-delete-min (hk-map-left m))
|
||||
(hk-map-right m))))))
|
||||
|
||||
(define
|
||||
hk-map-find-max
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? (hk-map-right m))
|
||||
(list (hk-map-key m) (hk-map-val m)))
|
||||
(:else (hk-map-find-max (hk-map-right m))))))
|
||||
|
||||
(define
|
||||
hk-map-delete-max
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? (hk-map-right m)) (hk-map-left m))
|
||||
(:else
|
||||
(hk-map-balance
|
||||
(hk-map-key m)
|
||||
(hk-map-val m)
|
||||
(hk-map-left m)
|
||||
(hk-map-delete-max (hk-map-right m)))))))
|
||||
|
||||
(define
|
||||
hk-map-glue
|
||||
(fn
|
||||
(l r)
|
||||
(cond
|
||||
((hk-map-empty? l) r)
|
||||
((hk-map-empty? r) l)
|
||||
((> (hk-map-size l) (hk-map-size r))
|
||||
(let
|
||||
((mp (hk-map-find-max l)))
|
||||
(hk-map-balance (first mp) (nth mp 1) (hk-map-delete-max l) r)))
|
||||
(:else
|
||||
(let
|
||||
((mp (hk-map-find-min r)))
|
||||
(hk-map-balance (first mp) (nth mp 1) l (hk-map-delete-min r)))))))
|
||||
|
||||
(define
|
||||
hk-map-delete
|
||||
(fn
|
||||
(k m)
|
||||
(cond
|
||||
((hk-map-empty? m) m)
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-delete k (hk-map-left m))
|
||||
(hk-map-right m)))
|
||||
((> k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-left m)
|
||||
(hk-map-delete k (hk-map-right m))))
|
||||
(:else (hk-map-glue (hk-map-left m) (hk-map-right m)))))))))
|
||||
|
||||
(define
|
||||
hk-map-from-list
|
||||
(fn
|
||||
(pairs)
|
||||
(reduce
|
||||
(fn (acc p) (hk-map-insert (first p) (nth p 1) acc))
|
||||
hk-map-empty
|
||||
pairs)))
|
||||
|
||||
(define
|
||||
hk-map-to-asc-list
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? m) (list))
|
||||
(:else
|
||||
(append
|
||||
(hk-map-to-asc-list (hk-map-left m))
|
||||
(cons
|
||||
(list (hk-map-key m) (hk-map-val m))
|
||||
(hk-map-to-asc-list (hk-map-right m))))))))
|
||||
|
||||
(define hk-map-to-list hk-map-to-asc-list)
|
||||
|
||||
(define
|
||||
hk-map-keys
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? m) (list))
|
||||
(:else
|
||||
(append
|
||||
(hk-map-keys (hk-map-left m))
|
||||
(cons (hk-map-key m) (hk-map-keys (hk-map-right m))))))))
|
||||
|
||||
(define
|
||||
hk-map-elems
|
||||
(fn
|
||||
(m)
|
||||
(cond
|
||||
((hk-map-empty? m) (list))
|
||||
(:else
|
||||
(append
|
||||
(hk-map-elems (hk-map-left m))
|
||||
(cons (hk-map-val m) (hk-map-elems (hk-map-right m))))))))
|
||||
|
||||
(define
|
||||
hk-map-union-with
|
||||
(fn
|
||||
(f m1 m2)
|
||||
(reduce
|
||||
(fn
|
||||
(acc p)
|
||||
(let
|
||||
((k (first p)) (v (nth p 1)))
|
||||
(let
|
||||
((look (hk-map-lookup k acc)))
|
||||
(cond
|
||||
((= (first look) "Just")
|
||||
(hk-map-insert k (f (nth look 1) v) acc))
|
||||
(:else (hk-map-insert k v acc))))))
|
||||
m1
|
||||
(hk-map-to-asc-list m2))))
|
||||
|
||||
(define
|
||||
hk-map-intersection-with
|
||||
(fn
|
||||
(f m1 m2)
|
||||
(reduce
|
||||
(fn
|
||||
(acc p)
|
||||
(let
|
||||
((k (first p)) (v1 (nth p 1)))
|
||||
(let
|
||||
((look (hk-map-lookup k m2)))
|
||||
(cond
|
||||
((= (first look) "Just")
|
||||
(hk-map-insert k (f v1 (nth look 1)) acc))
|
||||
(:else acc)))))
|
||||
hk-map-empty
|
||||
(hk-map-to-asc-list m1))))
|
||||
|
||||
(define
|
||||
hk-map-difference
|
||||
(fn
|
||||
(m1 m2)
|
||||
(reduce
|
||||
(fn
|
||||
(acc p)
|
||||
(let
|
||||
((k (first p)) (v (nth p 1)))
|
||||
(cond ((hk-map-member k m2) acc) (:else (hk-map-insert k v acc)))))
|
||||
hk-map-empty
|
||||
(hk-map-to-asc-list m1))))
|
||||
|
||||
(define
|
||||
hk-map-foldl-with-key
|
||||
(fn
|
||||
(f acc m)
|
||||
(cond
|
||||
((hk-map-empty? m) acc)
|
||||
(:else
|
||||
(let
|
||||
((acc1 (hk-map-foldl-with-key f acc (hk-map-left m))))
|
||||
(let
|
||||
((acc2 (f acc1 (hk-map-key m) (hk-map-val m))))
|
||||
(hk-map-foldl-with-key f acc2 (hk-map-right m))))))))
|
||||
|
||||
(define
|
||||
hk-map-foldr-with-key
|
||||
(fn
|
||||
(f acc m)
|
||||
(cond
|
||||
((hk-map-empty? m) acc)
|
||||
(:else
|
||||
(let
|
||||
((acc1 (hk-map-foldr-with-key f acc (hk-map-right m))))
|
||||
(let
|
||||
((acc2 (f (hk-map-key m) (hk-map-val m) acc1)))
|
||||
(hk-map-foldr-with-key f acc2 (hk-map-left m))))))))
|
||||
|
||||
(define
|
||||
hk-map-map-with-key
|
||||
(fn
|
||||
(f m)
|
||||
(cond
|
||||
((hk-map-empty? m) m)
|
||||
(:else
|
||||
(list
|
||||
"Map-Node"
|
||||
(hk-map-key m)
|
||||
(f (hk-map-key m) (hk-map-val m))
|
||||
(hk-map-map-with-key f (hk-map-left m))
|
||||
(hk-map-map-with-key f (hk-map-right m))
|
||||
(hk-map-size m))))))
|
||||
|
||||
(define
|
||||
hk-map-filter-with-key
|
||||
(fn
|
||||
(p m)
|
||||
(hk-map-foldr-with-key
|
||||
(fn (k v acc) (cond ((p k v) (hk-map-insert k v acc)) (:else acc)))
|
||||
hk-map-empty
|
||||
m)))
|
||||
|
||||
(define
|
||||
hk-map-adjust
|
||||
(fn
|
||||
(f k m)
|
||||
(cond
|
||||
((hk-map-empty? m) m)
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk)
|
||||
(hk-map-node
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-adjust f k (hk-map-left m))
|
||||
(hk-map-right m)))
|
||||
((> k mk)
|
||||
(hk-map-node
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-left m)
|
||||
(hk-map-adjust f k (hk-map-right m))))
|
||||
(:else
|
||||
(hk-map-node
|
||||
mk
|
||||
(f (hk-map-val m))
|
||||
(hk-map-left m)
|
||||
(hk-map-right m)))))))))
|
||||
|
||||
(define
|
||||
hk-map-insert-with
|
||||
(fn
|
||||
(f k v m)
|
||||
(cond
|
||||
((hk-map-empty? m) (hk-map-singleton k v))
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-insert-with f k v (hk-map-left m))
|
||||
(hk-map-right m)))
|
||||
((> k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-left m)
|
||||
(hk-map-insert-with f k v (hk-map-right m))))
|
||||
(:else
|
||||
(hk-map-node
|
||||
mk
|
||||
(f v (hk-map-val m))
|
||||
(hk-map-left m)
|
||||
(hk-map-right m)))))))))
|
||||
|
||||
(define
|
||||
hk-map-insert-with-key
|
||||
(fn
|
||||
(f k v m)
|
||||
(cond
|
||||
((hk-map-empty? m) (hk-map-singleton k v))
|
||||
(:else
|
||||
(let
|
||||
((mk (hk-map-key m)))
|
||||
(cond
|
||||
((< k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-insert-with-key f k v (hk-map-left m))
|
||||
(hk-map-right m)))
|
||||
((> k mk)
|
||||
(hk-map-balance
|
||||
mk
|
||||
(hk-map-val m)
|
||||
(hk-map-left m)
|
||||
(hk-map-insert-with-key f k v (hk-map-right m))))
|
||||
(:else
|
||||
(hk-map-node
|
||||
mk
|
||||
(f k v (hk-map-val m))
|
||||
(hk-map-left m)
|
||||
(hk-map-right m)))))))))
|
||||
|
||||
(define
|
||||
hk-map-alter
|
||||
(fn
|
||||
(f k m)
|
||||
(let
|
||||
((look (hk-map-lookup k m)))
|
||||
(let
|
||||
((res (f look)))
|
||||
(cond
|
||||
((= (first res) "Nothing") (hk-map-delete k m))
|
||||
(:else (hk-map-insert k (nth res 1) m)))))))
|
||||
@@ -87,45 +87,41 @@
|
||||
((nil? res) nil)
|
||||
(:else (assoc res (nth pat 1) val)))))
|
||||
(:else
|
||||
(let ((fv (hk-force val)))
|
||||
(let
|
||||
((fv (hk-force val)))
|
||||
(cond
|
||||
((= tag "p-int")
|
||||
(if
|
||||
(and (number? fv) (= fv (nth pat 1)))
|
||||
env
|
||||
nil))
|
||||
(if (and (number? fv) (= fv (nth pat 1))) env nil))
|
||||
((= tag "p-float")
|
||||
(if
|
||||
(and (number? fv) (= fv (nth pat 1)))
|
||||
env
|
||||
nil))
|
||||
(if (and (number? fv) (= fv (nth pat 1))) env nil))
|
||||
((= tag "p-string")
|
||||
(if
|
||||
(and (string? fv) (= fv (nth pat 1)))
|
||||
env
|
||||
nil))
|
||||
(if (and (string? fv) (= fv (nth pat 1))) env nil))
|
||||
((= tag "p-char")
|
||||
(if
|
||||
(and (string? fv) (= fv (nth pat 1)))
|
||||
env
|
||||
nil))
|
||||
(if (and (string? fv) (= fv (nth pat 1))) env nil))
|
||||
((= tag "p-con")
|
||||
(let
|
||||
((pat-name (nth pat 1)) (pat-args (nth pat 2)))
|
||||
(cond
|
||||
((and (= pat-name ":") (hk-str? fv) (not (hk-str-null? fv)))
|
||||
(let
|
||||
((str-head (hk-str-head fv))
|
||||
(str-tail (hk-str-tail fv)))
|
||||
(let
|
||||
((head-pat (nth pat-args 0))
|
||||
(tail-pat (nth pat-args 1)))
|
||||
(let
|
||||
((res (hk-match head-pat str-head env)))
|
||||
(cond
|
||||
((nil? res) nil)
|
||||
(:else (hk-match tail-pat str-tail res)))))))
|
||||
((not (hk-is-con-val? fv)) nil)
|
||||
((not (= (hk-val-con-name fv) pat-name)) nil)
|
||||
(:else
|
||||
(let
|
||||
((val-args (hk-val-con-args fv)))
|
||||
(cond
|
||||
((not (= (len pat-args) (len val-args)))
|
||||
nil)
|
||||
(:else
|
||||
(hk-match-all
|
||||
pat-args
|
||||
val-args
|
||||
env))))))))
|
||||
((not (= (len val-args) (len pat-args))) nil)
|
||||
(:else (hk-match-all pat-args val-args env))))))))
|
||||
((= tag "p-tuple")
|
||||
(let
|
||||
((items (nth pat 1)))
|
||||
@@ -134,13 +130,8 @@
|
||||
((not (= (hk-val-con-name fv) "Tuple")) nil)
|
||||
((not (= (len (hk-val-con-args fv)) (len items)))
|
||||
nil)
|
||||
(:else
|
||||
(hk-match-all
|
||||
items
|
||||
(hk-val-con-args fv)
|
||||
env)))))
|
||||
((= tag "p-list")
|
||||
(hk-match-list-pat (nth pat 1) fv env))
|
||||
(:else (hk-match-all items (hk-val-con-args fv) env)))))
|
||||
((= tag "p-list") (hk-match-list-pat (nth pat 1) fv env))
|
||||
(:else nil))))))))))
|
||||
|
||||
(define
|
||||
@@ -161,17 +152,26 @@
|
||||
hk-match-list-pat
|
||||
(fn
|
||||
(items val env)
|
||||
(let ((fv (hk-force val)))
|
||||
(let
|
||||
((fv (hk-force val)))
|
||||
(cond
|
||||
((empty? items)
|
||||
(if
|
||||
(and
|
||||
(hk-is-con-val? fv)
|
||||
(= (hk-val-con-name fv) "[]"))
|
||||
(or
|
||||
(and (hk-is-con-val? fv) (= (hk-val-con-name fv) "[]"))
|
||||
(and (hk-str? fv) (hk-str-null? fv)))
|
||||
env
|
||||
nil))
|
||||
(:else
|
||||
(cond
|
||||
((and (hk-str? fv) (not (hk-str-null? fv)))
|
||||
(let
|
||||
((h (hk-str-head fv)) (t (hk-str-tail fv)))
|
||||
(let
|
||||
((res (hk-match (first items) h env)))
|
||||
(cond
|
||||
((nil? res) nil)
|
||||
(:else (hk-match-list-pat (rest items) t res))))))
|
||||
((not (hk-is-con-val? fv)) nil)
|
||||
((not (= (hk-val-con-name fv) ":")) nil)
|
||||
(:else
|
||||
@@ -183,11 +183,7 @@
|
||||
((res (hk-match (first items) h env)))
|
||||
(cond
|
||||
((nil? res) nil)
|
||||
(:else
|
||||
(hk-match-list-pat
|
||||
(rest items)
|
||||
t
|
||||
res)))))))))))))
|
||||
(:else (hk-match-list-pat (rest items) t res)))))))))))))
|
||||
|
||||
;; ── Convenience: parse a pattern from source for tests ─────
|
||||
;; (Uses the parser's case-alt entry — `case _ of pat -> 0` —
|
||||
|
||||
@@ -208,9 +208,19 @@
|
||||
((= (get t "type") "char")
|
||||
(do (hk-advance!) (list :char (get t "value"))))
|
||||
((= (get t "type") "varid")
|
||||
(do (hk-advance!) (list :var (get t "value"))))
|
||||
(do
|
||||
(hk-advance!)
|
||||
(cond
|
||||
((hk-match? "lbrace" nil)
|
||||
(hk-parse-rec-update (list :var (get t "value"))))
|
||||
(:else (list :var (get t "value"))))))
|
||||
((= (get t "type") "conid")
|
||||
(do (hk-advance!) (list :con (get t "value"))))
|
||||
(do
|
||||
(hk-advance!)
|
||||
(cond
|
||||
((hk-match? "lbrace" nil)
|
||||
(hk-parse-rec-create (get t "value")))
|
||||
(:else (list :con (get t "value"))))))
|
||||
((= (get t "type") "qvarid")
|
||||
(do (hk-advance!) (list :var (get t "value"))))
|
||||
((= (get t "type") "qconid")
|
||||
@@ -456,6 +466,90 @@
|
||||
(do
|
||||
(hk-expect! "rbracket" nil)
|
||||
(list :list (list first-e))))))))))
|
||||
(define
|
||||
hk-parse-rec-create
|
||||
(fn
|
||||
(cname)
|
||||
(begin
|
||||
(hk-expect! "lbrace" nil)
|
||||
(let
|
||||
((fields (list)))
|
||||
(define
|
||||
hk-rc-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-match? "varid" nil)
|
||||
(let
|
||||
((fname (get (hk-advance!) "value")))
|
||||
(begin
|
||||
(hk-expect! "reservedop" "=")
|
||||
(let
|
||||
((fexpr (hk-parse-expr-inner)))
|
||||
(begin
|
||||
(append! fields (list fname fexpr))
|
||||
(when
|
||||
(hk-match? "comma" nil)
|
||||
(begin (hk-advance!) (hk-rc-loop))))))))))
|
||||
(hk-rc-loop)
|
||||
(hk-expect! "rbrace" nil)
|
||||
(list :rec-create cname fields)))))
|
||||
(define
|
||||
hk-parse-rec-update
|
||||
(fn
|
||||
(rec-expr)
|
||||
(begin
|
||||
(hk-expect! "lbrace" nil)
|
||||
(let
|
||||
((fields (list)))
|
||||
(define
|
||||
hk-ru-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-match? "varid" nil)
|
||||
(let
|
||||
((fname (get (hk-advance!) "value")))
|
||||
(begin
|
||||
(hk-expect! "reservedop" "=")
|
||||
(let
|
||||
((fexpr (hk-parse-expr-inner)))
|
||||
(begin
|
||||
(append! fields (list fname fexpr))
|
||||
(when
|
||||
(hk-match? "comma" nil)
|
||||
(begin (hk-advance!) (hk-ru-loop))))))))))
|
||||
(hk-ru-loop)
|
||||
(hk-expect! "rbrace" nil)
|
||||
(list :rec-update rec-expr fields)))))
|
||||
(define
|
||||
hk-parse-rec-pat
|
||||
(fn
|
||||
(cname)
|
||||
(begin
|
||||
(hk-expect! "lbrace" nil)
|
||||
(let
|
||||
((field-pats (list)))
|
||||
(define
|
||||
hk-rp-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-match? "varid" nil)
|
||||
(let
|
||||
((fname (get (hk-advance!) "value")))
|
||||
(begin
|
||||
(hk-expect! "reservedop" "=")
|
||||
(let
|
||||
((fpat (hk-parse-pat)))
|
||||
(begin
|
||||
(append! field-pats (list fname fpat))
|
||||
(when
|
||||
(hk-match? "comma" nil)
|
||||
(begin (hk-advance!) (hk-rp-loop))))))))))
|
||||
(hk-rp-loop)
|
||||
(hk-expect! "rbrace" nil)
|
||||
(list :p-rec cname field-pats)))))
|
||||
(define
|
||||
hk-parse-fexp
|
||||
(fn
|
||||
@@ -696,7 +790,12 @@
|
||||
(:else
|
||||
(do (hk-advance!) (list :p-var (get t "value")))))))
|
||||
((= (get t "type") "conid")
|
||||
(do (hk-advance!) (list :p-con (get t "value") (list))))
|
||||
(do
|
||||
(hk-advance!)
|
||||
(cond
|
||||
((hk-match? "lbrace" nil)
|
||||
(hk-parse-rec-pat (get t "value")))
|
||||
(:else (list :p-con (get t "value") (list))))))
|
||||
((= (get t "type") "qconid")
|
||||
(do (hk-advance!) (list :p-con (get t "value") (list))))
|
||||
((= (get t "type") "lparen") (hk-parse-paren-pat))
|
||||
@@ -762,16 +861,24 @@
|
||||
(cond
|
||||
((and (not (nil? t)) (or (= (get t "type") "conid") (= (get t "type") "qconid")))
|
||||
(let
|
||||
((name (get (hk-advance!) "value")) (args (list)))
|
||||
(define
|
||||
hk-pca-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-apat-start? (hk-peek))
|
||||
(do (append! args (hk-parse-apat)) (hk-pca-loop)))))
|
||||
(hk-pca-loop)
|
||||
(list :p-con name args)))
|
||||
((name (get (hk-advance!) "value")))
|
||||
(cond
|
||||
((hk-match? "lbrace" nil)
|
||||
(hk-parse-rec-pat name))
|
||||
(:else
|
||||
(let
|
||||
((args (list)))
|
||||
(define
|
||||
hk-pca-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-apat-start? (hk-peek))
|
||||
(do
|
||||
(append! args (hk-parse-apat))
|
||||
(hk-pca-loop)))))
|
||||
(hk-pca-loop)
|
||||
(list :p-con name args))))))
|
||||
(:else (hk-parse-apat))))))
|
||||
(define
|
||||
hk-parse-pat
|
||||
@@ -1212,16 +1319,47 @@
|
||||
(not (hk-match? "conid" nil))
|
||||
(hk-err "expected constructor name"))
|
||||
(let
|
||||
((name (get (hk-advance!) "value")) (fields (list)))
|
||||
(define
|
||||
hk-cd-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-atype-start? (hk-peek))
|
||||
(do (append! fields (hk-parse-atype)) (hk-cd-loop)))))
|
||||
(hk-cd-loop)
|
||||
(list :con-def name fields))))
|
||||
((name (get (hk-advance!) "value")))
|
||||
(cond
|
||||
((hk-match? "lbrace" nil)
|
||||
(begin
|
||||
(hk-advance!)
|
||||
(let
|
||||
((rec-fields (list)))
|
||||
(define
|
||||
hk-rec-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-match? "varid" nil)
|
||||
(let
|
||||
((fname (get (hk-advance!) "value")))
|
||||
(begin
|
||||
(hk-expect! "reservedop" "::")
|
||||
(let
|
||||
((ftype (hk-parse-type)))
|
||||
(begin
|
||||
(append! rec-fields (list fname ftype))
|
||||
(when
|
||||
(hk-match? "comma" nil)
|
||||
(begin (hk-advance!) (hk-rec-loop))))))))))
|
||||
(hk-rec-loop)
|
||||
(hk-expect! "rbrace" nil)
|
||||
(list :con-rec name rec-fields))))
|
||||
(:else
|
||||
(let
|
||||
((fields (list)))
|
||||
(define
|
||||
hk-cd-loop
|
||||
(fn
|
||||
()
|
||||
(when
|
||||
(hk-atype-start? (hk-peek))
|
||||
(begin
|
||||
(append! fields (hk-parse-atype))
|
||||
(hk-cd-loop)))))
|
||||
(hk-cd-loop)
|
||||
(list :con-def name fields)))))))
|
||||
(define
|
||||
hk-parse-tvars
|
||||
(fn
|
||||
|
||||
@@ -12,12 +12,7 @@
|
||||
|
||||
(define
|
||||
hk-register-con!
|
||||
(fn
|
||||
(cname arity type-name)
|
||||
(dict-set!
|
||||
hk-constructors
|
||||
cname
|
||||
{:arity arity :type type-name})))
|
||||
(fn (cname arity type-name) (dict-set! hk-constructors cname {:arity arity :type type-name})))
|
||||
|
||||
(define hk-is-con? (fn (name) (has-key? hk-constructors name)))
|
||||
|
||||
@@ -48,26 +43,15 @@
|
||||
(fn
|
||||
(data-node)
|
||||
(let
|
||||
((type-name (nth data-node 1))
|
||||
(cons-list (nth data-node 3)))
|
||||
((type-name (nth data-node 1)) (cons-list (nth data-node 3)))
|
||||
(for-each
|
||||
(fn
|
||||
(cd)
|
||||
(hk-register-con!
|
||||
(nth cd 1)
|
||||
(len (nth cd 2))
|
||||
type-name))
|
||||
(fn (cd) (hk-register-con! (nth cd 1) (len (nth cd 2)) type-name))
|
||||
cons-list))))
|
||||
|
||||
;; (:newtype NAME TVARS CNAME FIELD)
|
||||
(define
|
||||
hk-register-newtype!
|
||||
(fn
|
||||
(nt-node)
|
||||
(hk-register-con!
|
||||
(nth nt-node 3)
|
||||
1
|
||||
(nth nt-node 1))))
|
||||
(fn (nt-node) (hk-register-con! (nth nt-node 3) 1 (nth nt-node 1))))
|
||||
|
||||
;; Walk a decls list, registering every `data` / `newtype` decl.
|
||||
(define
|
||||
@@ -78,15 +62,9 @@
|
||||
(fn
|
||||
(d)
|
||||
(cond
|
||||
((and
|
||||
(list? d)
|
||||
(not (empty? d))
|
||||
(= (first d) "data"))
|
||||
((and (list? d) (not (empty? d)) (= (first d) "data"))
|
||||
(hk-register-data! d))
|
||||
((and
|
||||
(list? d)
|
||||
(not (empty? d))
|
||||
(= (first d) "newtype"))
|
||||
((and (list? d) (not (empty? d)) (= (first d) "newtype"))
|
||||
(hk-register-newtype! d))
|
||||
(:else nil)))
|
||||
decls)))
|
||||
@@ -99,16 +77,12 @@
|
||||
((nil? ast) nil)
|
||||
((not (list? ast)) nil)
|
||||
((empty? ast) nil)
|
||||
((= (first ast) "program")
|
||||
(hk-register-decls! (nth ast 1)))
|
||||
((= (first ast) "module")
|
||||
(hk-register-decls! (nth ast 4)))
|
||||
((= (first ast) "program") (hk-register-decls! (nth ast 1)))
|
||||
((= (first ast) "module") (hk-register-decls! (nth ast 4)))
|
||||
(:else nil))))
|
||||
|
||||
;; Convenience: source → AST → desugar → register.
|
||||
(define
|
||||
hk-load-source!
|
||||
(fn (src) (hk-register-program! (hk-core src))))
|
||||
(define hk-load-source! (fn (src) (hk-register-program! (hk-core src))))
|
||||
|
||||
;; ── Built-in constructors pre-registered ─────────────────────
|
||||
;; Bool — used implicitly by `if`, comparison operators.
|
||||
@@ -122,9 +96,54 @@
|
||||
;; Standard Prelude types — pre-registered so expression-level
|
||||
;; programs can use them without a `data` decl.
|
||||
(hk-register-con! "Nothing" 0 "Maybe")
|
||||
(hk-register-con! "Just" 1 "Maybe")
|
||||
(hk-register-con! "Left" 1 "Either")
|
||||
(hk-register-con! "Right" 1 "Either")
|
||||
(hk-register-con! "Just" 1 "Maybe")
|
||||
(hk-register-con! "Left" 1 "Either")
|
||||
(hk-register-con! "Right" 1 "Either")
|
||||
(hk-register-con! "LT" 0 "Ordering")
|
||||
(hk-register-con! "EQ" 0 "Ordering")
|
||||
(hk-register-con! "GT" 0 "Ordering")
|
||||
|
||||
(define
|
||||
hk-str?
|
||||
(fn (v) (or (string? v) (and (dict? v) (has-key? v "hk-str")))))
|
||||
|
||||
(define
|
||||
hk-str-head
|
||||
(fn
|
||||
(v)
|
||||
(if
|
||||
(string? v)
|
||||
(char-code (char-at v 0))
|
||||
(char-code (char-at (get v "hk-str") (get v "hk-off"))))))
|
||||
|
||||
(define
|
||||
hk-str-tail
|
||||
(fn
|
||||
(v)
|
||||
(let
|
||||
((buf (if (string? v) v (get v "hk-str")))
|
||||
(off (if (string? v) 1 (+ (get v "hk-off") 1))))
|
||||
(if (>= off (string-length buf)) (list "[]") {:hk-off off :hk-str buf}))))
|
||||
|
||||
(define
|
||||
hk-str-null?
|
||||
(fn
|
||||
(v)
|
||||
(if
|
||||
(string? v)
|
||||
(= (string-length v) 0)
|
||||
(>= (get v "hk-off") (string-length (get v "hk-str"))))))
|
||||
|
||||
(define
|
||||
hk-str-to-native
|
||||
(fn
|
||||
(v)
|
||||
(if
|
||||
(string? v)
|
||||
v
|
||||
(let
|
||||
((buf (get v "hk-str")) (off (get v "hk-off")))
|
||||
(reduce
|
||||
(fn (acc i) (str acc (char-at buf i)))
|
||||
""
|
||||
(range off (string-length buf)))))))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"date": "2026-05-06",
|
||||
"total_pass": 156,
|
||||
"date": "2026-05-07",
|
||||
"total_pass": 269,
|
||||
"total_fail": 0,
|
||||
"programs": {
|
||||
"fib": {"pass": 2, "fail": 0},
|
||||
@@ -9,7 +9,7 @@
|
||||
"nqueens": {"pass": 2, "fail": 0},
|
||||
"calculator": {"pass": 5, "fail": 0},
|
||||
"collatz": {"pass": 11, "fail": 0},
|
||||
"palindrome": {"pass": 8, "fail": 0},
|
||||
"palindrome": {"pass": 12, "fail": 0},
|
||||
"maybe": {"pass": 12, "fail": 0},
|
||||
"fizzbuzz": {"pass": 12, "fail": 0},
|
||||
"anagram": {"pass": 9, "fail": 0},
|
||||
@@ -19,7 +19,23 @@
|
||||
"primes": {"pass": 12, "fail": 0},
|
||||
"zipwith": {"pass": 9, "fail": 0},
|
||||
"matrix": {"pass": 8, "fail": 0},
|
||||
"wordcount": {"pass": 7, "fail": 0},
|
||||
"powers": {"pass": 14, "fail": 0}
|
||||
"wordcount": {"pass": 10, "fail": 0},
|
||||
"powers": {"pass": 14, "fail": 0},
|
||||
"caesar": {"pass": 8, "fail": 0},
|
||||
"runlength-str": {"pass": 9, "fail": 0},
|
||||
"showadt": {"pass": 5, "fail": 0},
|
||||
"showio": {"pass": 5, "fail": 0},
|
||||
"partial": {"pass": 7, "fail": 0},
|
||||
"statistics": {"pass": 5, "fail": 0},
|
||||
"newton": {"pass": 5, "fail": 0},
|
||||
"wordfreq": {"pass": 7, "fail": 0},
|
||||
"mapgraph": {"pass": 6, "fail": 0},
|
||||
"uniquewords": {"pass": 4, "fail": 0},
|
||||
"setops": {"pass": 8, "fail": 0},
|
||||
"shapes": {"pass": 5, "fail": 0},
|
||||
"person": {"pass": 7, "fail": 0},
|
||||
"config": {"pass": 10, "fail": 0},
|
||||
"counter": {"pass": 7, "fail": 0},
|
||||
"accumulate": {"pass": 8, "fail": 0}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Haskell-on-SX Scoreboard
|
||||
|
||||
Updated 2026-05-06 · Phase 6 (prelude extras + 18 programs)
|
||||
Updated 2026-05-07 · Phase 6 (prelude extras + 18 programs)
|
||||
|
||||
| Program | Tests | Status |
|
||||
|---------|-------|--------|
|
||||
@@ -10,7 +10,7 @@ Updated 2026-05-06 · Phase 6 (prelude extras + 18 programs)
|
||||
| nqueens.hs | 2/2 | ✓ |
|
||||
| calculator.hs | 5/5 | ✓ |
|
||||
| collatz.hs | 11/11 | ✓ |
|
||||
| palindrome.hs | 8/8 | ✓ |
|
||||
| palindrome.hs | 12/12 | ✓ |
|
||||
| maybe.hs | 12/12 | ✓ |
|
||||
| fizzbuzz.hs | 12/12 | ✓ |
|
||||
| anagram.hs | 9/9 | ✓ |
|
||||
@@ -20,6 +20,22 @@ Updated 2026-05-06 · Phase 6 (prelude extras + 18 programs)
|
||||
| primes.hs | 12/12 | ✓ |
|
||||
| zipwith.hs | 9/9 | ✓ |
|
||||
| matrix.hs | 8/8 | ✓ |
|
||||
| wordcount.hs | 7/7 | ✓ |
|
||||
| wordcount.hs | 10/10 | ✓ |
|
||||
| powers.hs | 14/14 | ✓ |
|
||||
| **Total** | **156/156** | **18/18 programs** |
|
||||
| caesar.hs | 8/8 | ✓ |
|
||||
| runlength-str.hs | 9/9 | ✓ |
|
||||
| showadt.hs | 5/5 | ✓ |
|
||||
| showio.hs | 5/5 | ✓ |
|
||||
| partial.hs | 7/7 | ✓ |
|
||||
| statistics.hs | 5/5 | ✓ |
|
||||
| newton.hs | 5/5 | ✓ |
|
||||
| wordfreq.hs | 7/7 | ✓ |
|
||||
| mapgraph.hs | 6/6 | ✓ |
|
||||
| uniquewords.hs | 4/4 | ✓ |
|
||||
| setops.hs | 8/8 | ✓ |
|
||||
| shapes.hs | 5/5 | ✓ |
|
||||
| person.hs | 7/7 | ✓ |
|
||||
| config.hs | 10/10 | ✓ |
|
||||
| counter.hs | 7/7 | ✓ |
|
||||
| accumulate.hs | 8/8 | ✓ |
|
||||
| **Total** | **269/269** | **34/34 programs** |
|
||||
|
||||
62
lib/haskell/set.sx
Normal file
62
lib/haskell/set.sx
Normal file
@@ -0,0 +1,62 @@
|
||||
;; set.sx — Phase 12 Data.Set: wraps Data.Map with unit values.
|
||||
;;
|
||||
;; A Set is a Map from key to (). All set operations delegate to the map
|
||||
;; ops, ignoring the value side. Storage representation matches Data.Map:
|
||||
;;
|
||||
;; Empty → ("Map-Empty")
|
||||
;; Node → ("Map-Node" key () left right size)
|
||||
;;
|
||||
;; Tradeoff: trivial maintenance burden, slight overhead per node from
|
||||
;; the unused value slot. Faster path forward than re-implementing the
|
||||
;; weight-balanced BST.
|
||||
;;
|
||||
;; Functions live in this file; the Haskell-level `import Data.Set` /
|
||||
;; `import qualified Data.Set as Set` wiring (next Phase 12 box) binds
|
||||
;; them under the chosen alias.
|
||||
|
||||
(define hk-set-unit (list "Tuple"))
|
||||
|
||||
(define hk-set-empty hk-map-empty)
|
||||
|
||||
(define hk-set-singleton (fn (k) (hk-map-singleton k hk-set-unit)))
|
||||
|
||||
(define hk-set-insert (fn (k s) (hk-map-insert k hk-set-unit s)))
|
||||
|
||||
(define hk-set-delete hk-map-delete)
|
||||
(define hk-set-member hk-map-member)
|
||||
(define hk-set-size hk-map-size)
|
||||
(define hk-set-null hk-map-null)
|
||||
(define hk-set-to-asc-list hk-map-keys)
|
||||
(define hk-set-to-list hk-map-keys)
|
||||
|
||||
(define
|
||||
hk-set-from-list
|
||||
(fn (xs) (reduce (fn (acc k) (hk-set-insert k acc)) hk-set-empty xs)))
|
||||
|
||||
(define
|
||||
hk-set-union
|
||||
(fn (a b) (hk-map-union-with (fn (x y) hk-set-unit) a b)))
|
||||
|
||||
(define
|
||||
hk-set-intersection
|
||||
(fn (a b) (hk-map-intersection-with (fn (x y) hk-set-unit) a b)))
|
||||
|
||||
(define hk-set-difference hk-map-difference)
|
||||
|
||||
(define
|
||||
hk-set-is-subset-of
|
||||
(fn (a b) (= (hk-map-size (hk-map-difference a b)) 0)))
|
||||
|
||||
(define
|
||||
hk-set-filter
|
||||
(fn (p s) (hk-map-filter-with-key (fn (k v) (p k)) s)))
|
||||
|
||||
(define hk-set-map (fn (f s) (hk-set-from-list (map f (hk-map-keys s)))))
|
||||
|
||||
(define
|
||||
hk-set-foldr
|
||||
(fn (f z s) (hk-map-foldr-with-key (fn (k v acc) (f k acc)) z s)))
|
||||
|
||||
(define
|
||||
hk-set-foldl
|
||||
(fn (f z s) (hk-map-foldl-with-key (fn (acc k v) (f acc k)) z s)))
|
||||
@@ -55,6 +55,8 @@ for FILE in "${FILES[@]}"; do
|
||||
(load "lib/haskell/runtime.sx")
|
||||
(load "lib/haskell/match.sx")
|
||||
(load "lib/haskell/eval.sx")
|
||||
(load "lib/haskell/map.sx")
|
||||
(load "lib/haskell/set.sx")
|
||||
$INFER_LOAD
|
||||
(load "lib/haskell/testlib.sx")
|
||||
(epoch 2)
|
||||
@@ -98,6 +100,8 @@ EPOCHS
|
||||
(load "lib/haskell/runtime.sx")
|
||||
(load "lib/haskell/match.sx")
|
||||
(load "lib/haskell/eval.sx")
|
||||
(load "lib/haskell/map.sx")
|
||||
(load "lib/haskell/set.sx")
|
||||
$INFER_LOAD
|
||||
(load "lib/haskell/testlib.sx")
|
||||
(epoch 2)
|
||||
|
||||
@@ -56,3 +56,21 @@
|
||||
(append!
|
||||
hk-test-fails
|
||||
{:actual actual :expected expected :name name})))))
|
||||
|
||||
(define
|
||||
hk-test-error
|
||||
(fn
|
||||
(name thunk expected-substring)
|
||||
(let
|
||||
((caught (guard (e (true (if (string? e) e (str e)))) (begin (thunk) nil))))
|
||||
(cond
|
||||
((nil? caught)
|
||||
(do
|
||||
(set! hk-test-fail (+ hk-test-fail 1))
|
||||
(append! hk-test-fails {:actual "no error raised" :expected (str "error containing: " expected-substring) :name name})))
|
||||
((>= (index-of caught expected-substring) 0)
|
||||
(set! hk-test-pass (+ hk-test-pass 1)))
|
||||
(:else
|
||||
(do
|
||||
(set! hk-test-fail (+ hk-test-fail 1))
|
||||
(append! hk-test-fails {:actual caught :expected (str "error containing: " expected-substring) :name name})))))))
|
||||
|
||||
86
lib/haskell/tests/class-defaults.sx
Normal file
86
lib/haskell/tests/class-defaults.sx
Normal file
@@ -0,0 +1,86 @@
|
||||
;; class-defaults.sx — Phase 13: class default method implementations.
|
||||
|
||||
;; ── Eq default: myNeq derived from myEq via `not (myEq x y)` ──
|
||||
(define
|
||||
hk-myeq-source
|
||||
"class MyEq a where\n myEq :: a -> a -> Bool\n myNeq :: a -> a -> Bool\n myNeq x y = not (myEq x y)\ninstance MyEq Int where\n myEq x y = x == y\n")
|
||||
|
||||
(hk-test
|
||||
"Eq default: myNeq 3 5 = True (no explicit myNeq in instance)"
|
||||
(hk-deep-force (hk-run (str hk-myeq-source "main = myNeq 3 5\n")))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"Eq default: myNeq 3 3 = False"
|
||||
(hk-deep-force (hk-run (str hk-myeq-source "main = myNeq 3 3\n")))
|
||||
(list "False"))
|
||||
|
||||
(hk-test
|
||||
"Eq default: myEq still works in same instance"
|
||||
(hk-deep-force (hk-run (str hk-myeq-source "main = myEq 7 7\n")))
|
||||
(list "True"))
|
||||
|
||||
;; ── Override path: instance can still provide the method explicitly. ──
|
||||
(hk-test
|
||||
"Default override: instance-provided beats class default"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"class Hi a where\n greet :: a -> String\n greet x = \"default\"\ninstance Hi Bool where\n greet x = \"override\"\nmain = greet True"))
|
||||
"override")
|
||||
|
||||
(hk-test
|
||||
"Default fallback: empty instance picks default"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"class Hi a where\n greet :: a -> String\n greet x = \"default\"\ninstance Hi Bool where\nmain = greet True"))
|
||||
"default")
|
||||
|
||||
(define
|
||||
hk-myord-source
|
||||
"class MyOrd a where\n myCmp :: a -> a -> Bool\n myMax :: a -> a -> a\n myMin :: a -> a -> a\n myMax a b = if myCmp a b then a else b\n myMin a b = if myCmp a b then b else a\ninstance MyOrd Int where\n myCmp x y = x >= y\n")
|
||||
|
||||
(hk-test
|
||||
"Ord default: myMax 3 5 = 5"
|
||||
(hk-deep-force (hk-run (str hk-myord-source "main = myMax 3 5\n")))
|
||||
5)
|
||||
|
||||
(hk-test
|
||||
"Ord default: myMax 8 2 = 8"
|
||||
(hk-deep-force (hk-run (str hk-myord-source "main = myMax 8 2\n")))
|
||||
8)
|
||||
|
||||
(hk-test
|
||||
"Ord default: myMin 3 5 = 3"
|
||||
(hk-deep-force (hk-run (str hk-myord-source "main = myMin 3 5\n")))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"Ord default: myMin 8 2 = 2"
|
||||
(hk-deep-force (hk-run (str hk-myord-source "main = myMin 8 2\n")))
|
||||
2)
|
||||
|
||||
(hk-test
|
||||
"Ord default: myMax of equals returns first"
|
||||
(hk-deep-force (hk-run (str hk-myord-source "main = myMax 4 4\n")))
|
||||
4)
|
||||
|
||||
(define
|
||||
hk-mynum-source
|
||||
"class MyNum a where\n mySub :: a -> a -> a\n myLt :: a -> a -> Bool\n myNegate :: a -> a\n myAbs :: a -> a\n myNegate x = mySub (mySub x x) x\n myAbs x = if myLt x (mySub x x) then myNegate x else x\ninstance MyNum Int where\n mySub x y = x - y\n myLt x y = x < y\n")
|
||||
|
||||
(hk-test
|
||||
"Num default: myNegate 5 = -5"
|
||||
(hk-deep-force (hk-run (str hk-mynum-source "main = myNegate 5\n")))
|
||||
-5)
|
||||
|
||||
(hk-test
|
||||
"Num default: myAbs (myNegate 7) = 7"
|
||||
(hk-deep-force (hk-run (str hk-mynum-source "main = myAbs (myNegate 7)\n")))
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"Num default: myAbs 9 = 9"
|
||||
(hk-deep-force (hk-run (str hk-mynum-source "main = myAbs 9\n")))
|
||||
9)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
@@ -12,14 +12,14 @@
|
||||
"deriving Show: constructor with arg"
|
||||
(hk-deep-force
|
||||
(hk-run "data Wrapper = Wrap Int deriving (Show)\nmain = show (Wrap 42)"))
|
||||
"(Wrap 42)")
|
||||
"Wrap 42")
|
||||
|
||||
(hk-test
|
||||
"deriving Show: nested constructors"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Tree = Leaf | Node Int Tree Tree deriving (Show)\nmain = show (Node 1 Leaf Leaf)"))
|
||||
"(Node 1 Leaf Leaf)")
|
||||
"Node 1 Leaf Leaf")
|
||||
|
||||
(hk-test
|
||||
"deriving Show: second constructor"
|
||||
@@ -30,6 +30,31 @@
|
||||
|
||||
;; ─── Eq ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
(hk-test
|
||||
"deriving Show: nested ADT wraps inner constructor in parens"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Tree = Leaf | Node Int Tree Tree deriving (Show)\nmain = show (Node 1 Leaf (Node 2 Leaf Leaf))"))
|
||||
"Node 1 Leaf (Node 2 Leaf Leaf)")
|
||||
|
||||
(hk-test
|
||||
"deriving Show: Maybe Maybe wraps inner Just"
|
||||
(hk-deep-force (hk-run "main = show (Just (Just 3))"))
|
||||
"Just (Just 3)")
|
||||
|
||||
(hk-test
|
||||
"deriving Show: negative argument wrapped in parens"
|
||||
(hk-deep-force (hk-run "main = show (Just (negate 3))"))
|
||||
"Just (-3)")
|
||||
|
||||
(hk-test
|
||||
"deriving Show: list element does not need parens"
|
||||
(hk-deep-force
|
||||
(hk-run "data Box = Box [Int] deriving (Show)\nmain = show (Box [1,2,3])"))
|
||||
"Box [1,2,3]")
|
||||
|
||||
;; ─── combined Eq + Show ───────────────────────────────────────────────────────
|
||||
|
||||
(hk-test
|
||||
"deriving Eq: same constructor"
|
||||
(hk-deep-force
|
||||
@@ -58,14 +83,12 @@
|
||||
"data Color = Red | Green | Blue deriving (Eq)\nmain = show (Red /= Blue)"))
|
||||
"True")
|
||||
|
||||
;; ─── combined Eq + Show ───────────────────────────────────────────────────────
|
||||
|
||||
(hk-test
|
||||
"deriving Eq Show: combined in parens"
|
||||
"deriving Eq Show: combined"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Shape = Circle Int | Square Int deriving (Eq, Show)\nmain = show (Circle 5)"))
|
||||
"(Circle 5)")
|
||||
"Circle 5")
|
||||
|
||||
(hk-test
|
||||
"deriving Eq Show: eq on constructor with arg"
|
||||
|
||||
99
lib/haskell/tests/errors.sx
Normal file
99
lib/haskell/tests/errors.sx
Normal file
@@ -0,0 +1,99 @@
|
||||
;; errors.sx — Phase 9 error / undefined / partial-fn coverage via hk-test-error.
|
||||
|
||||
;; ── error builtin ────────────────────────────────────────────
|
||||
(define
|
||||
hk-as-list
|
||||
(fn
|
||||
(xs)
|
||||
(cond
|
||||
((and (list? xs) (= (first xs) "[]")) (list))
|
||||
((and (list? xs) (= (first xs) ":"))
|
||||
(cons (nth xs 1) (hk-as-list (nth xs 2))))
|
||||
(:else xs))))
|
||||
|
||||
(hk-test-error
|
||||
"error: raises with literal message"
|
||||
(fn () (hk-deep-force (hk-run "main = error \"boom\"")))
|
||||
"hk-error: boom")
|
||||
|
||||
(hk-test-error
|
||||
"error: raises with computed message"
|
||||
(fn () (hk-deep-force (hk-run "main = error (\"oops: \" ++ show 42)")))
|
||||
"hk-error: oops: 42")
|
||||
|
||||
;; ── undefined ────────────────────────────────────────────────
|
||||
(hk-test-error
|
||||
"error: nested in if branch (only fires when forced)"
|
||||
(fn
|
||||
()
|
||||
(hk-deep-force (hk-run "main = if 1 == 1 then error \"taken\" else 0")))
|
||||
"taken")
|
||||
|
||||
(hk-test-error
|
||||
"undefined: raises Prelude.undefined"
|
||||
(fn () (hk-deep-force (hk-run "main = undefined")))
|
||||
"Prelude.undefined")
|
||||
|
||||
;; The non-strict path: undefined doesn't fire when not forced.
|
||||
(hk-test-error
|
||||
"undefined: forced via arithmetic"
|
||||
(fn () (hk-deep-force (hk-run "main = undefined + 1")))
|
||||
"Prelude.undefined")
|
||||
|
||||
;; ── partial functions ───────────────────────────────────────
|
||||
(hk-test
|
||||
"undefined: lazy, not forced when discarded"
|
||||
(hk-deep-force (hk-run "main = let _ = undefined in 5"))
|
||||
5)
|
||||
|
||||
(hk-test-error
|
||||
"head []: raises Prelude.head: empty list"
|
||||
(fn () (hk-deep-force (hk-run "main = head []")))
|
||||
"Prelude.head: empty list")
|
||||
|
||||
(hk-test-error
|
||||
"tail []: raises Prelude.tail: empty list"
|
||||
(fn () (hk-deep-force (hk-run "main = tail []")))
|
||||
"Prelude.tail: empty list")
|
||||
|
||||
;; head and tail still work on non-empty lists.
|
||||
(hk-test-error
|
||||
"fromJust Nothing: raises Maybe.fromJust: Nothing"
|
||||
(fn () (hk-deep-force (hk-run "main = fromJust Nothing")))
|
||||
"Maybe.fromJust: Nothing")
|
||||
|
||||
(hk-test
|
||||
"head [42]: still works"
|
||||
(hk-deep-force (hk-run "main = head [42]"))
|
||||
42)
|
||||
|
||||
;; ── error in IO context ─────────────────────────────────────
|
||||
(hk-test
|
||||
"tail [1,2,3]: still works"
|
||||
(hk-as-list (hk-deep-force (hk-run "main = tail [1,2,3]")))
|
||||
(list 2 3))
|
||||
|
||||
(hk-test
|
||||
"hk-run-io: error in main lands in io-lines"
|
||||
(let
|
||||
((lines (hk-run-io "main = error \"caught here\"")))
|
||||
(>= (index-of (str lines) "caught here") 0))
|
||||
true)
|
||||
|
||||
;; ── hk-test-error helper itself ─────────────────────────────
|
||||
(hk-test
|
||||
"hk-run-io: putStrLn before error preserves earlier output"
|
||||
(let
|
||||
((lines (hk-run-io "main = do { putStrLn \"first\"; error \"died\"; putStrLn \"never\" }")))
|
||||
(and
|
||||
(>= (index-of (str lines) "first") 0)
|
||||
(>= (index-of (str lines) "died") 0)))
|
||||
true)
|
||||
|
||||
;; hk-as-list helper for converting a forced Haskell cons into an SX list.
|
||||
(hk-test-error
|
||||
"hk-test-error: matches partial substring inside wrapped exception"
|
||||
(fn () (hk-deep-force (hk-run "main = error \"unique-marker-xyz\"")))
|
||||
"unique-marker-xyz")
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
@@ -231,16 +231,82 @@
|
||||
1)
|
||||
|
||||
;; ── Laziness: app args evaluate only when forced ──
|
||||
(hk-test
|
||||
"error builtin: raises with hk-error prefix"
|
||||
(guard
|
||||
(e (true (>= (index-of e "hk-error: boom") 0)))
|
||||
(begin (hk-deep-force (hk-run "main = error \"boom\"")) false))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"error builtin: raises with computed message"
|
||||
(guard
|
||||
(e (true (>= (index-of e "hk-error: oops: 42") 0)))
|
||||
(begin
|
||||
(hk-deep-force (hk-run "main = error (\"oops: \" ++ show 42)"))
|
||||
false))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"undefined: raises hk-error with Prelude.undefined message"
|
||||
(guard
|
||||
(e (true (>= (index-of e "hk-error: Prelude.undefined") 0)))
|
||||
(begin (hk-deep-force (hk-run "main = undefined")) false))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"undefined: lazy — only fires when forced"
|
||||
(hk-deep-force (hk-run "main = if True then 42 else undefined"))
|
||||
42)
|
||||
|
||||
(hk-test
|
||||
"head []: raises Prelude.head: empty list"
|
||||
(guard
|
||||
(e (true (>= (index-of e "Prelude.head: empty list") 0)))
|
||||
(begin (hk-deep-force (hk-run "main = head []")) false))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"tail []: raises Prelude.tail: empty list"
|
||||
(guard
|
||||
(e (true (>= (index-of e "Prelude.tail: empty list") 0)))
|
||||
(begin (hk-deep-force (hk-run "main = tail []")) false))
|
||||
true)
|
||||
|
||||
;; ── not / id built-ins ──
|
||||
(hk-test
|
||||
"fromJust Nothing: raises Maybe.fromJust: Nothing"
|
||||
(guard
|
||||
(e (true (>= (index-of e "Maybe.fromJust: Nothing") 0)))
|
||||
(begin (hk-deep-force (hk-run "main = fromJust Nothing")) false))
|
||||
true)
|
||||
(hk-test
|
||||
"fromJust (Just 5) = 5"
|
||||
(hk-deep-force (hk-run "main = fromJust (Just 5)"))
|
||||
5)
|
||||
(hk-test
|
||||
"head [42] = 42 (still works for non-empty)"
|
||||
(hk-deep-force (hk-run "main = head [42]"))
|
||||
42)
|
||||
|
||||
(hk-test-error
|
||||
"hk-test-error helper: catches matching error"
|
||||
(fn () (hk-deep-force (hk-run "main = error \"boom\"")))
|
||||
"hk-error: boom")
|
||||
|
||||
(hk-test-error
|
||||
"hk-test-error helper: catches head [] error"
|
||||
(fn () (hk-deep-force (hk-run "main = head []")))
|
||||
"Prelude.head: empty list")
|
||||
|
||||
(hk-test
|
||||
"second arg never forced"
|
||||
(hk-eval-expr-source
|
||||
"(\\x y -> x) 1 (error \"never\")")
|
||||
(hk-eval-expr-source "(\\x y -> x) 1 (error \"never\")")
|
||||
1)
|
||||
|
||||
(hk-test
|
||||
"first arg never forced"
|
||||
(hk-eval-expr-source
|
||||
"(\\x y -> y) (error \"never\") 99")
|
||||
(hk-eval-expr-source "(\\x y -> y) (error \"never\") 99")
|
||||
99)
|
||||
|
||||
(hk-test
|
||||
@@ -251,9 +317,7 @@
|
||||
|
||||
(hk-test
|
||||
"lazy: const drops its second argument"
|
||||
(hk-prog-val
|
||||
"const x y = x\nresult = const 5 (error \"boom\")"
|
||||
"result")
|
||||
(hk-prog-val "const x y = x\nresult = const 5 (error \"boom\")" "result")
|
||||
5)
|
||||
|
||||
(hk-test
|
||||
@@ -270,9 +334,10 @@
|
||||
"result")
|
||||
(list "True"))
|
||||
|
||||
;; ── not / id built-ins ──
|
||||
(hk-test "not True" (hk-eval-expr-source "not True") (list "False"))
|
||||
|
||||
(hk-test "not False" (hk-eval-expr-source "not False") (list "True"))
|
||||
|
||||
(hk-test "id" (hk-eval-expr-source "id 42") 42)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
|
||||
31
lib/haskell/tests/instance-where.sx
Normal file
31
lib/haskell/tests/instance-where.sx
Normal file
@@ -0,0 +1,31 @@
|
||||
;; instance-where.sx — Phase 13: where-clauses inside instance bodies.
|
||||
|
||||
(hk-test
|
||||
"instance method body with where-helper (Bool)"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"class Greet a where\n greet :: a -> String\ninstance Greet Bool where\n greet x = mkMsg x\n where mkMsg True = \"yes\"\n mkMsg False = \"no\"\nmain = greet True"))
|
||||
"yes")
|
||||
|
||||
(hk-test
|
||||
"instance method body with where-helper (False branch)"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"class Greet a where\n greet :: a -> String\ninstance Greet Bool where\n greet x = mkMsg x\n where mkMsg True = \"yes\"\n mkMsg False = \"no\"\nmain = greet False"))
|
||||
"no")
|
||||
|
||||
(hk-test
|
||||
"instance method body with where-binding referenced multiple times"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"class Twice a where\n twice :: a -> Int\ninstance Twice Int where\n twice x = h + h\n where h = x + 1\nmain = twice 5"))
|
||||
12)
|
||||
|
||||
(hk-test
|
||||
"instance method body with multi-binding where"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"class Calc a where\n calc :: a -> Int\ninstance Calc Int where\n calc x = a + b\n where a = x * 2\n b = x + 1\nmain = calc 3"))
|
||||
10)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
@@ -64,12 +64,11 @@
|
||||
|
||||
(hk-test
|
||||
"readFile error on missing file"
|
||||
(guard
|
||||
(e (true (>= (index-of e "file not found") 0)))
|
||||
(begin
|
||||
(set! hk-vfs (dict))
|
||||
(hk-run-io "main = readFile \"no.txt\" >>= putStrLn")
|
||||
false))
|
||||
(begin
|
||||
(set! hk-vfs (dict))
|
||||
(let
|
||||
((lines (hk-run-io "main = readFile \"no.txt\" >>= putStrLn")))
|
||||
(>= (index-of (str lines) "file not found") 0)))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
|
||||
94
lib/haskell/tests/ioref.sx
Normal file
94
lib/haskell/tests/ioref.sx
Normal file
@@ -0,0 +1,94 @@
|
||||
;; Phase 15 — IORef unit tests.
|
||||
|
||||
(hk-test
|
||||
"newIORef + readIORef returns initial value"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 42; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 42))
|
||||
|
||||
(hk-test
|
||||
"writeIORef updates the cell"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 0; IORef.writeIORef r 99; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 99))
|
||||
|
||||
(hk-test
|
||||
"writeIORef returns IO ()"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 0; IORef.writeIORef r 1 }"))
|
||||
(list "IO" (list "Tuple")))
|
||||
|
||||
(hk-test
|
||||
"modifyIORef applies a function"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 5; IORef.modifyIORef r (\\x -> x * 2); v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 10))
|
||||
|
||||
(hk-test
|
||||
"modifyIORef' (strict) applies a function"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 7; IORef.modifyIORef' r (\\x -> x + 3); v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 10))
|
||||
|
||||
(hk-test
|
||||
"two reads return the same value"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 11; a <- IORef.readIORef r; b <- IORef.readIORef r; return (a + b) }"))
|
||||
(list "IO" 22))
|
||||
|
||||
(hk-test
|
||||
"shared ref across do-steps: write then read"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef 1; IORef.writeIORef r 2; IORef.writeIORef r 3; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 3))
|
||||
|
||||
(hk-test
|
||||
"two refs are independent"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r1 <- IORef.newIORef 1; r2 <- IORef.newIORef 2; IORef.writeIORef r1 10; a <- IORef.readIORef r1; b <- IORef.readIORef r2; return (a + b) }"))
|
||||
(list "IO" 12))
|
||||
|
||||
(hk-test
|
||||
"string-valued IORef"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef \"hi\"; IORef.writeIORef r \"bye\"; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" "bye"))
|
||||
|
||||
(hk-test
|
||||
"list-valued IORef + cons"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nmain = do { r <- IORef.newIORef [1,2,3]; IORef.modifyIORef r (\\xs -> 0 : xs); v <- IORef.readIORef r; return v }"))
|
||||
(list
|
||||
"IO"
|
||||
(list ":" 0 (list ":" 1 (list ":" 2 (list ":" 3 (list "[]")))))))
|
||||
|
||||
(hk-test
|
||||
"counter loop: increment N times"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nloop r 0 = return ()\nloop r n = do { IORef.modifyIORef r (\\x -> x + 1); loop r (n - 1) }\nmain = do { r <- IORef.newIORef 0; loop r 10; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 10))
|
||||
|
||||
(hk-test
|
||||
"modifyIORef' inside a loop"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\ngo r 0 = return ()\ngo r n = do { IORef.modifyIORef' r (\\x -> x + n); go r (n - 1) }\nmain = do { r <- IORef.newIORef 0; go r 5; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 15))
|
||||
|
||||
(hk-test
|
||||
"newIORef inside a function passed via parameter"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.IORef as IORef\nbump r = IORef.modifyIORef r (\\x -> x + 100)\nmain = do { r <- IORef.newIORef 1; bump r; v <- IORef.readIORef r; return v }"))
|
||||
(list "IO" 101))
|
||||
196
lib/haskell/tests/map.sx
Normal file
196
lib/haskell/tests/map.sx
Normal file
@@ -0,0 +1,196 @@
|
||||
;; map.sx — Phase 11 Data.Map unit tests.
|
||||
;;
|
||||
;; Tests both the SX-level `hk-map-*` helpers and the Haskell-level
|
||||
;; `Map.*` aliases bound by the import handler.
|
||||
|
||||
(define
|
||||
hk-as-list
|
||||
(fn
|
||||
(xs)
|
||||
(cond
|
||||
((and (list? xs) (= (first xs) "[]")) (list))
|
||||
((and (list? xs) (= (first xs) ":"))
|
||||
(cons (nth xs 1) (hk-as-list (nth xs 2))))
|
||||
(:else xs))))
|
||||
|
||||
;; ── SX-level (direct hk-map-*) ───────────────────────────────
|
||||
(hk-test
|
||||
"hk-map-empty: size 0, null true"
|
||||
(list (hk-map-size hk-map-empty) (hk-map-null hk-map-empty))
|
||||
(list 0 true))
|
||||
|
||||
(hk-test
|
||||
"hk-map-singleton: lookup hit"
|
||||
(let
|
||||
((m (hk-map-singleton 5 "five")))
|
||||
(list (hk-map-size m) (hk-map-lookup 5 m)))
|
||||
(list 1 (list "Just" "five")))
|
||||
|
||||
(hk-test
|
||||
"hk-map-insert: lookup hit on inserted"
|
||||
(let ((m (hk-map-insert 1 "a" hk-map-empty))) (hk-map-lookup 1 m))
|
||||
(list "Just" "a"))
|
||||
|
||||
(hk-test
|
||||
"hk-map-lookup: miss returns Nothing"
|
||||
(hk-map-lookup 99 (hk-map-singleton 1 "a"))
|
||||
(list "Nothing"))
|
||||
|
||||
(hk-test
|
||||
"hk-map-insert: overwrites existing key"
|
||||
(let
|
||||
((m (hk-map-insert 1 "second" (hk-map-insert 1 "first" hk-map-empty))))
|
||||
(hk-map-lookup 1 m))
|
||||
(list "Just" "second"))
|
||||
|
||||
(hk-test
|
||||
"hk-map-delete: removes key"
|
||||
(let
|
||||
((m (hk-map-insert 2 "b" (hk-map-insert 1 "a" hk-map-empty))))
|
||||
(let
|
||||
((m2 (hk-map-delete 1 m)))
|
||||
(list (hk-map-size m2) (hk-map-lookup 1 m2) (hk-map-lookup 2 m2))))
|
||||
(list 1 (list "Nothing") (list "Just" "b")))
|
||||
|
||||
(hk-test
|
||||
"hk-map-delete: missing key is no-op"
|
||||
(let ((m (hk-map-singleton 1 "a"))) (hk-map-size (hk-map-delete 99 m)))
|
||||
1)
|
||||
|
||||
(hk-test
|
||||
"hk-map-member: true on existing"
|
||||
(hk-map-member 1 (hk-map-singleton 1 "a"))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"hk-map-member: false on missing"
|
||||
(hk-map-member 99 (hk-map-singleton 1 "a"))
|
||||
false)
|
||||
|
||||
(hk-test
|
||||
"hk-map-from-list: builds map; keys sorted"
|
||||
(hk-map-keys
|
||||
(hk-map-from-list
|
||||
(list (list 3 "c") (list 1 "a") (list 5 "e") (list 2 "b"))))
|
||||
(list 1 2 3 5))
|
||||
|
||||
(hk-test
|
||||
"hk-map-from-list: duplicates — last wins"
|
||||
(hk-map-lookup
|
||||
1
|
||||
(hk-map-from-list (list (list 1 "first") (list 1 "second"))))
|
||||
(list "Just" "second"))
|
||||
|
||||
(hk-test
|
||||
"hk-map-to-asc-list: ordered traversal"
|
||||
(hk-map-to-asc-list
|
||||
(hk-map-from-list (list (list 3 "c") (list 1 "a") (list 2 "b"))))
|
||||
(list (list 1 "a") (list 2 "b") (list 3 "c")))
|
||||
|
||||
(hk-test
|
||||
"hk-map-elems: in key order"
|
||||
(hk-map-elems
|
||||
(hk-map-from-list (list (list 3 30) (list 1 10) (list 2 20))))
|
||||
(list 10 20 30))
|
||||
|
||||
(hk-test
|
||||
"hk-map-union-with: combines duplicates"
|
||||
(hk-map-to-asc-list
|
||||
(hk-map-union-with
|
||||
(fn (a b) (str a "+" b))
|
||||
(hk-map-from-list (list (list 1 "a") (list 2 "b")))
|
||||
(hk-map-from-list (list (list 2 "B") (list 3 "c")))))
|
||||
(list (list 1 "a") (list 2 "b+B") (list 3 "c")))
|
||||
|
||||
(hk-test
|
||||
"hk-map-intersection-with: keeps shared keys"
|
||||
(hk-map-to-asc-list
|
||||
(hk-map-intersection-with
|
||||
+
|
||||
(hk-map-from-list (list (list 1 10) (list 2 20)))
|
||||
(hk-map-from-list (list (list 2 200) (list 3 30)))))
|
||||
(list (list 2 220)))
|
||||
|
||||
(hk-test
|
||||
"hk-map-difference: drops m2 keys"
|
||||
(hk-map-keys
|
||||
(hk-map-difference
|
||||
(hk-map-from-list (list (list 1 "a") (list 2 "b") (list 3 "c")))
|
||||
(hk-map-from-list (list (list 2 "x")))))
|
||||
(list 1 3))
|
||||
|
||||
(hk-test
|
||||
"hk-map-foldl-with-key: in-order accumulate"
|
||||
(hk-map-foldl-with-key
|
||||
(fn (acc k v) (str acc k v))
|
||||
""
|
||||
(hk-map-from-list (list (list 3 "c") (list 1 "a") (list 2 "b"))))
|
||||
"1a2b3c")
|
||||
|
||||
(hk-test
|
||||
"hk-map-map-with-key: transforms values"
|
||||
(hk-map-to-asc-list
|
||||
(hk-map-map-with-key
|
||||
(fn (k v) (* k v))
|
||||
(hk-map-from-list (list (list 2 10) (list 3 100)))))
|
||||
(list (list 2 20) (list 3 300)))
|
||||
|
||||
(hk-test
|
||||
"hk-map-filter-with-key: keeps matches"
|
||||
(hk-map-keys
|
||||
(hk-map-filter-with-key
|
||||
(fn (k v) (> k 1))
|
||||
(hk-map-from-list (list (list 1 "a") (list 2 "b") (list 3 "c")))))
|
||||
(list 2 3))
|
||||
|
||||
(hk-test
|
||||
"hk-map-adjust: applies f to existing"
|
||||
(hk-map-lookup
|
||||
1
|
||||
(hk-map-adjust (fn (v) (* v 10)) 1 (hk-map-singleton 1 5)))
|
||||
(list "Just" 50))
|
||||
|
||||
(hk-test
|
||||
"hk-map-insert-with: combines on existing"
|
||||
(hk-map-lookup 1 (hk-map-insert-with + 1 5 (hk-map-singleton 1 10)))
|
||||
(list "Just" 15))
|
||||
|
||||
(hk-test
|
||||
"hk-map-alter: Nothing → delete"
|
||||
(hk-map-size
|
||||
(hk-map-alter
|
||||
(fn (mv) (list "Nothing"))
|
||||
1
|
||||
(hk-map-from-list (list (list 1 "a") (list 2 "b")))))
|
||||
1)
|
||||
|
||||
;; ── Haskell-level (Map.*) via import wiring ─────────────────
|
||||
(hk-test
|
||||
"Map.size after Map.insert chain"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Map as Map\nmain = Map.size (Map.insert 2 \"b\" (Map.insert 1 \"a\" Map.empty))"))
|
||||
2)
|
||||
|
||||
(hk-test
|
||||
"Map.lookup hit"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Map as Map\nmain = Map.lookup 1 (Map.insert 1 \"a\" Map.empty)"))
|
||||
(list "Just" "a"))
|
||||
|
||||
(hk-test
|
||||
"Map.lookup miss"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Map as Map\nmain = Map.lookup 99 (Map.insert 1 \"a\" Map.empty)"))
|
||||
(list "Nothing"))
|
||||
|
||||
(hk-test
|
||||
"Map.member true"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Map as Map\nmain = Map.member 5 (Map.insert 5 \"x\" Map.empty)"))
|
||||
(list "True"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
180
lib/haskell/tests/numerics.sx
Normal file
180
lib/haskell/tests/numerics.sx
Normal file
@@ -0,0 +1,180 @@
|
||||
;; numerics.sx — Phase 10 numeric tower verification.
|
||||
;;
|
||||
;; Practical integer-precision limit in Haskell-on-SX:
|
||||
;; • Raw SX `(* a b)` stays exact up to ±2^62 (≈ 4.6e18, OCaml int63).
|
||||
;; • BUT the Haskell tokenizer/parser parses an integer literal as a float
|
||||
;; once it exceeds 2^53 (≈ 9.007e15). Once any operand is a float, the
|
||||
;; binop result is a float (and decimal-precision is lost past 2^53).
|
||||
;; • Therefore: programs that stay below ~9e15 are exact; larger literals
|
||||
;; or accumulated products silently become floats. `factorial 18` is the
|
||||
;; last factorial that stays exact (6.4e15); `factorial 19` already floats.
|
||||
;;
|
||||
;; In Haskell terms, `Int` and `Integer` both currently map to SX number, so
|
||||
;; we don't yet support arbitrary-precision Integer. Documented; unbounded
|
||||
;; Integer is out of scope for Phase 10 — see Phase 11+ if it becomes needed.
|
||||
|
||||
(define
|
||||
hk-as-list
|
||||
(fn
|
||||
(xs)
|
||||
(cond
|
||||
((and (list? xs) (= (first xs) "[]")) (list))
|
||||
((and (list? xs) (= (first xs) ":"))
|
||||
(cons (nth xs 1) (hk-as-list (nth xs 2))))
|
||||
(:else xs))))
|
||||
|
||||
(hk-test
|
||||
"factorial 10 = 3628800 (small, exact)"
|
||||
(hk-deep-force
|
||||
(hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = fact 10"))
|
||||
3628800)
|
||||
|
||||
(hk-test
|
||||
"factorial 15 = 1307674368000 (mid-range, exact)"
|
||||
(hk-deep-force
|
||||
(hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = fact 15"))
|
||||
1307674368000)
|
||||
|
||||
(hk-test
|
||||
"factorial 18 = 6402373705728000 (last exact factorial)"
|
||||
(hk-deep-force
|
||||
(hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = fact 18"))
|
||||
6402373705728000)
|
||||
|
||||
(hk-test
|
||||
"1000000 * 1000000 = 10^12 (exact)"
|
||||
(hk-deep-force (hk-run "main = 1000000 * 1000000"))
|
||||
1000000000000)
|
||||
|
||||
(hk-test
|
||||
"1000000000 * 1000000000 = 10^18 (exact, at boundary)"
|
||||
(hk-deep-force (hk-run "main = 1000000000 * 1000000000"))
|
||||
1e+18)
|
||||
|
||||
(hk-test
|
||||
"2^62 boundary: pow accumulates exactly"
|
||||
(hk-deep-force
|
||||
(hk-run "pow b 0 = 1\npow b n = b * pow b (n - 1)\nmain = pow 2 62"))
|
||||
4.6116860184273879e+18)
|
||||
|
||||
(hk-test
|
||||
"show factorial 12 = 479001600 (whole, fits in 32-bit)"
|
||||
(hk-deep-force
|
||||
(hk-run "fact 0 = 1\nfact n = n * fact (n - 1)\nmain = show (fact 12)"))
|
||||
"479001600")
|
||||
|
||||
(hk-test
|
||||
"negate large positive — preserves magnitude"
|
||||
(hk-deep-force (hk-run "main = negate 1000000000000000000"))
|
||||
-1e+18)
|
||||
|
||||
(hk-test
|
||||
"abs negative large — preserves magnitude"
|
||||
(hk-deep-force (hk-run "main = abs (negate 1000000000000000000)"))
|
||||
1e+18)
|
||||
|
||||
(hk-test
|
||||
"div on large ints"
|
||||
(hk-deep-force (hk-run "main = div 1000000000000000000 1000000000"))
|
||||
1000000000)
|
||||
|
||||
(hk-test
|
||||
"fromIntegral 42 = 42 (identity in our runtime)"
|
||||
(hk-deep-force (hk-run "main = fromIntegral 42"))
|
||||
42)
|
||||
|
||||
(hk-test
|
||||
"fromIntegral preserves negative"
|
||||
(hk-deep-force (hk-run "main = fromIntegral (negate 7)"))
|
||||
-7)
|
||||
|
||||
(hk-test
|
||||
"fromIntegral round-trips through arithmetic"
|
||||
(hk-deep-force (hk-run "main = fromIntegral 5 + fromIntegral 3"))
|
||||
8)
|
||||
|
||||
(hk-test
|
||||
"fromIntegral in a program (mixing with map)"
|
||||
(hk-as-list (hk-deep-force (hk-run "main = map fromIntegral [1,2,3]")))
|
||||
(list 1 2 3))
|
||||
|
||||
(hk-test
|
||||
"toInteger 100 = 100 (identity)"
|
||||
(hk-deep-force (hk-run "main = toInteger 100"))
|
||||
100)
|
||||
|
||||
(hk-test
|
||||
"fromInteger 7 = 7 (identity)"
|
||||
(hk-deep-force (hk-run "main = fromInteger 7"))
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"toInteger / fromInteger round-trip"
|
||||
(hk-deep-force (hk-run "main = fromInteger (toInteger 42)"))
|
||||
42)
|
||||
|
||||
(hk-test
|
||||
"toInteger preserves negative"
|
||||
(hk-deep-force (hk-run "main = toInteger (negate 13)"))
|
||||
-13)
|
||||
|
||||
(hk-test
|
||||
"show 3.14 = 3.14"
|
||||
(hk-deep-force (hk-run "main = show 3.14"))
|
||||
"3.14")
|
||||
|
||||
(hk-test
|
||||
"show 1.0e10 — whole-valued float renders as decimal (int/float ambiguity)"
|
||||
(hk-deep-force (hk-run "main = show 1.0e10"))
|
||||
"10000000000")
|
||||
|
||||
(hk-test
|
||||
"show 0.001 uses scientific form (sub-0.1)"
|
||||
(hk-deep-force (hk-run "main = show 0.001"))
|
||||
"1.0e-3")
|
||||
|
||||
(hk-test
|
||||
"show negative float"
|
||||
(hk-deep-force (hk-run "main = show (negate 3.14)"))
|
||||
"-3.14")
|
||||
|
||||
(hk-test "sqrt 16 = 4" (hk-deep-force (hk-run "main = sqrt 16")) 4)
|
||||
|
||||
(hk-test "floor 3.7 = 3" (hk-deep-force (hk-run "main = floor 3.7")) 3)
|
||||
|
||||
(hk-test "ceiling 3.2 = 4" (hk-deep-force (hk-run "main = ceiling 3.2")) 4)
|
||||
|
||||
(hk-test
|
||||
"ceiling on whole = self"
|
||||
(hk-deep-force (hk-run "main = ceiling 4"))
|
||||
4)
|
||||
|
||||
(hk-test "round 2.6 = 3" (hk-deep-force (hk-run "main = round 2.6")) 3)
|
||||
|
||||
(hk-test
|
||||
"truncate -3.7 = -3"
|
||||
(hk-deep-force (hk-run "main = truncate (negate 3.7)"))
|
||||
-3)
|
||||
|
||||
(hk-test "recip 4.0 = 0.25" (hk-deep-force (hk-run "main = recip 4.0")) 0.25)
|
||||
|
||||
(hk-test "1.0 / 4.0 = 0.25" (hk-deep-force (hk-run "main = 1.0 / 4.0")) 0.25)
|
||||
|
||||
(hk-test
|
||||
"fromRational 0.5 = 0.5 (identity)"
|
||||
(hk-deep-force (hk-run "main = fromRational 0.5"))
|
||||
0.5)
|
||||
|
||||
(hk-test "pi ≈ 3.14159" (hk-deep-force (hk-run "main = pi")) 3.14159)
|
||||
|
||||
(hk-test "exp 0 = 1" (hk-deep-force (hk-run "main = exp 0")) 1)
|
||||
|
||||
(hk-test "sin 0 = 0" (hk-deep-force (hk-run "main = sin 0")) 0)
|
||||
|
||||
(hk-test "cos 0 = 1" (hk-deep-force (hk-run "main = cos 0")) 1)
|
||||
|
||||
(hk-test "2 ** 10 = 1024" (hk-deep-force (hk-run "main = 2 ** 10")) 1024)
|
||||
|
||||
(hk-test "log (exp 5) ≈ 5" (hk-deep-force (hk-run "main = log (exp 5)")) 5)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
81
lib/haskell/tests/program-accumulate.sx
Normal file
81
lib/haskell/tests/program-accumulate.sx
Normal file
@@ -0,0 +1,81 @@
|
||||
;; accumulate.hs — accumulate results into an IORef [Int] (Phase 15 conformance).
|
||||
|
||||
(define
|
||||
hk-accumulate-source
|
||||
"import qualified Data.IORef as IORef\n\npush :: IORef [Int] -> Int -> IO ()\npush r x = IORef.modifyIORef r (\\xs -> x : xs)\n\npushAll :: IORef [Int] -> [Int] -> IO ()\npushAll r [] = return ()\npushAll r (x:xs) = do\n push r x\n pushAll r xs\n\nreadReversed :: IORef [Int] -> IO [Int]\nreadReversed r = do\n xs <- IORef.readIORef r\n return (reverse xs)\n\ndoubleEach :: IORef [Int] -> [Int] -> IO ()\ndoubleEach r [] = return ()\ndoubleEach r (x:xs) = do\n push r (x * 2)\n doubleEach r xs\n\nsumIntoRef :: IORef Int -> [Int] -> IO ()\nsumIntoRef r [] = return ()\nsumIntoRef r (x:xs) = do\n IORef.modifyIORef r (\\acc -> acc + x)\n sumIntoRef r xs\n\n")
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — push three then read length"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef []; push r 1; push r 2; push r 3; xs <- IORef.readIORef r; return (length xs) }")))
|
||||
(list "IO" 3))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — pushAll preserves reverse order"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef []; pushAll r [1,2,3,4]; xs <- IORef.readIORef r; return xs }")))
|
||||
(list
|
||||
"IO"
|
||||
(list ":" 4 (list ":" 3 (list ":" 2 (list ":" 1 (list "[]")))))))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — readReversed gives original order"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef []; pushAll r [10,20,30]; readReversed r }")))
|
||||
(list "IO" (list ":" 10 (list ":" 20 (list ":" 30 (list "[]"))))))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — doubleEach maps then accumulates"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef []; doubleEach r [1,2,3]; readReversed r }")))
|
||||
(list "IO" (list ":" 2 (list ":" 4 (list ":" 6 (list "[]"))))))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — sum into Int IORef"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef 0; sumIntoRef r [1,2,3,4,5]; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 15))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — empty list leaves ref untouched"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef [99]; pushAll r []; xs <- IORef.readIORef r; return xs }")))
|
||||
(list "IO" (list ":" 99 (list "[]"))))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — pushAll then sumIntoRef on the same input"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"main = do { r <- IORef.newIORef 0; sumIntoRef r [10,20,30,40]; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 100))
|
||||
|
||||
(hk-test
|
||||
"accumulate.hs — accumulate results from a recursive helper"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-accumulate-source
|
||||
"squaresUpTo r 0 = return ()\nsquaresUpTo r n = do { push r (n * n); squaresUpTo r (n - 1) }\nmain = do { r <- IORef.newIORef []; squaresUpTo r 4; readReversed r }")))
|
||||
(list
|
||||
"IO"
|
||||
(list ":" 16 (list ":" 9 (list ":" 4 (list ":" 1 (list "[]")))))))
|
||||
80
lib/haskell/tests/program-caesar.sx
Normal file
80
lib/haskell/tests/program-caesar.sx
Normal file
@@ -0,0 +1,80 @@
|
||||
;; caesar.hs — Caesar cipher.
|
||||
;; Source: https://rosettacode.org/wiki/Caesar_cipher#Haskell (adapted).
|
||||
;;
|
||||
;; Exercises chr, ord, isUpper, isLower, mod, string pattern matching
|
||||
;; (x:xs) over a String (which is now a [Char] string view), and map
|
||||
;; from the Phase 7 string=[Char] foundation.
|
||||
|
||||
(define
|
||||
hk-prog-val
|
||||
(fn
|
||||
(src name)
|
||||
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
|
||||
|
||||
(define
|
||||
hk-as-list
|
||||
(fn
|
||||
(xs)
|
||||
(cond
|
||||
((and (list? xs) (= (first xs) "[]")) (list))
|
||||
((and (list? xs) (= (first xs) ":"))
|
||||
(cons (nth xs 1) (hk-as-list (nth xs 2))))
|
||||
(:else xs))))
|
||||
|
||||
(define
|
||||
hk-caesar-source
|
||||
"shift n c = if isUpper c\n then chr (mod ((ord c) - 65 + n) 26 + 65)\n else if isLower c\n then chr (mod ((ord c) - 97 + n) 26 + 97)\n else chr c\n\ncaesarRec n [] = []\ncaesarRec n (x:xs) = shift n x : caesarRec n xs\n\ncaesarMap n s = map (shift n) s\n")
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarRec 3 \"ABC\" = DEF"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarRec 3 \"ABC\"\n") "r"))
|
||||
(list "D" "E" "F"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarRec 13 \"Hello\" = Uryyb"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarRec 13 \"Hello\"\n") "r"))
|
||||
(list "U" "r" "y" "y" "b"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarRec 1 \"AZ\" wraps to BA"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarRec 1 \"AZ\"\n") "r"))
|
||||
(list "B" "A"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarRec 0 \"World\" identity"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarRec 0 \"World\"\n") "r"))
|
||||
(list "W" "o" "r" "l" "d"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarRec preserves punctuation"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarRec 3 \"Hi!\"\n") "r"))
|
||||
(list "K" "l" "!"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarMap 3 \"abc\" via map"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarMap 3 \"abc\"\n") "r"))
|
||||
(list "d" "e" "f"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarMap 13 round-trips with caesarMap 13"
|
||||
(hk-as-list
|
||||
(hk-prog-val
|
||||
(str
|
||||
hk-caesar-source
|
||||
"r = caesarMap 13 (foldr (\\c acc -> c : acc) [] (caesarMap 13 \"Hello\"))\n")
|
||||
"r"))
|
||||
(list "H" "e" "l" "l" "o"))
|
||||
|
||||
(hk-test
|
||||
"caesar.hs — caesarRec 25 \"AB\" = ZA"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-caesar-source "r = caesarRec 25 \"AB\"\n") "r"))
|
||||
(list "Z" "A"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
63
lib/haskell/tests/program-config.sx
Normal file
63
lib/haskell/tests/program-config.sx
Normal file
@@ -0,0 +1,63 @@
|
||||
;; config.hs — multi-field config record; partial update; defaultConfig
|
||||
;; constant.
|
||||
;;
|
||||
;; Exercises Phase 14: 4-field record, defaultConfig as a CAF, partial
|
||||
;; updates that change one or two fields, accessors over derived configs.
|
||||
|
||||
(define
|
||||
hk-config-source
|
||||
"data Config = Config { host :: String, port :: Int, retries :: Int, debug :: Bool } deriving (Show)\n\ndefaultConfig = Config { host = \"localhost\", port = 8080, retries = 3, debug = False }\n\ndevConfig = defaultConfig { debug = True }\nremoteConfig = defaultConfig { host = \"api.example.com\", port = 443 }\n")
|
||||
|
||||
(hk-test
|
||||
"config.hs — defaultConfig host"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = host defaultConfig")))
|
||||
"localhost")
|
||||
|
||||
(hk-test
|
||||
"config.hs — defaultConfig port"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = port defaultConfig")))
|
||||
8080)
|
||||
|
||||
(hk-test
|
||||
"config.hs — defaultConfig retries"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-config-source "main = retries defaultConfig")))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"config.hs — devConfig flips debug"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = debug devConfig")))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"config.hs — devConfig preserves host"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = host devConfig")))
|
||||
"localhost")
|
||||
|
||||
(hk-test
|
||||
"config.hs — devConfig preserves port"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = port devConfig")))
|
||||
8080)
|
||||
|
||||
(hk-test
|
||||
"config.hs — remoteConfig new host"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = host remoteConfig")))
|
||||
"api.example.com")
|
||||
|
||||
(hk-test
|
||||
"config.hs — remoteConfig new port"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = port remoteConfig")))
|
||||
443)
|
||||
|
||||
(hk-test
|
||||
"config.hs — remoteConfig preserves retries"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-config-source "main = retries remoteConfig")))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"config.hs — remoteConfig preserves debug"
|
||||
(hk-deep-force (hk-run (str hk-config-source "main = debug remoteConfig")))
|
||||
(list "False"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
66
lib/haskell/tests/program-counter.sx
Normal file
66
lib/haskell/tests/program-counter.sx
Normal file
@@ -0,0 +1,66 @@
|
||||
;; counter.hs — IORef-backed mutable counter (Phase 15 conformance).
|
||||
|
||||
(define
|
||||
hk-counter-source
|
||||
"import qualified Data.IORef as IORef\n\ncount :: IORef Int -> Int -> IO ()\ncount r 0 = return ()\ncount r n = do\n IORef.modifyIORef r (\\x -> x + 1)\n count r (n - 1)\n\ncountBy :: IORef Int -> Int -> Int -> IO ()\ncountBy r step 0 = return ()\ncountBy r step n = do\n IORef.modifyIORef r (\\x -> x + step)\n countBy r step (n - 1)\n\nnewCounter :: Int -> IO (IORef Int)\nnewCounter v = IORef.newIORef v\n\nbumpAndRead :: IORef Int -> IO Int\nbumpAndRead r = do\n IORef.modifyIORef r (\\x -> x + 1)\n IORef.readIORef r\n\n")
|
||||
|
||||
(hk-test
|
||||
"counter.hs — start at 0, count 5 ⇒ 5"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-counter-source
|
||||
"main = do { r <- newCounter 0; count r 5; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 5))
|
||||
|
||||
(hk-test
|
||||
"counter.hs — start at 100, count 10 ⇒ 110"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-counter-source
|
||||
"main = do { r <- newCounter 100; count r 10; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 110))
|
||||
|
||||
(hk-test
|
||||
"counter.hs — countBy step 5, n 4 ⇒ 20"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-counter-source
|
||||
"main = do { r <- newCounter 0; countBy r 5 4; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 20))
|
||||
|
||||
(hk-test
|
||||
"counter.hs — bumpAndRead returns updated value"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-counter-source "main = do { r <- newCounter 41; bumpAndRead r }")))
|
||||
(list "IO" 42))
|
||||
|
||||
(hk-test
|
||||
"counter.hs — count then countBy compose"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-counter-source
|
||||
"main = do { r <- newCounter 0; count r 3; countBy r 10 2; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 23))
|
||||
|
||||
(hk-test
|
||||
"counter.hs — two independent counters"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-counter-source
|
||||
"main = do { a <- newCounter 0; b <- newCounter 0; count a 7; countBy b 100 2; va <- IORef.readIORef a; vb <- IORef.readIORef b; return (va + vb) }")))
|
||||
(list "IO" 207))
|
||||
|
||||
(hk-test
|
||||
"counter.hs — modifyIORef' (strict) variant"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-counter-source
|
||||
"tick r 0 = return ()\ntick r n = do { IORef.modifyIORef' r (\\x -> x + 1); tick r (n - 1) }\nmain = do { r <- newCounter 0; tick r 50; v <- IORef.readIORef r; return v }")))
|
||||
(list "IO" 50))
|
||||
46
lib/haskell/tests/program-mapgraph.sx
Normal file
46
lib/haskell/tests/program-mapgraph.sx
Normal file
@@ -0,0 +1,46 @@
|
||||
;; mapgraph.hs — adjacency-list using Data.Map (BFS-style traversal).
|
||||
;;
|
||||
;; Exercises Phase 11: `import qualified Data.Map as Map`, `Map.empty`,
|
||||
;; `Map.insert`, `Map.lookup`, `Map.findWithDefault`. Adjacency lists are
|
||||
;; stored as `Map Int [Int]`; `neighbors` does a default-empty lookup.
|
||||
|
||||
(define
|
||||
hk-mapgraph-source
|
||||
"import qualified Data.Map as Map\n\nemptyG = Map.empty\n\naddEdge u v g = Map.insertWith add u [v] g\n where add new old = new ++ old\n\nbuild = addEdge 1 2 (addEdge 1 3 (addEdge 2 4 (addEdge 3 4 (addEdge 4 5 emptyG))))\n\nneighbors n g = Map.findWithDefault [] n g\n")
|
||||
|
||||
(hk-test
|
||||
"mapgraph.hs — neighbors of 1"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-mapgraph-source "main = neighbors 1 build\n")))
|
||||
(list ":" 2 (list ":" 3 (list "[]"))))
|
||||
|
||||
(hk-test
|
||||
"mapgraph.hs — neighbors of 4"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-mapgraph-source "main = neighbors 4 build\n")))
|
||||
(list ":" 5 (list "[]")))
|
||||
|
||||
(hk-test
|
||||
"mapgraph.hs — neighbors of 5 (leaf, no entry) defaults to []"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-mapgraph-source "main = neighbors 5 build\n")))
|
||||
(list "[]"))
|
||||
|
||||
(hk-test
|
||||
"mapgraph.hs — neighbors of 99 (absent) defaults to []"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-mapgraph-source "main = neighbors 99 build\n")))
|
||||
(list "[]"))
|
||||
|
||||
(hk-test
|
||||
"mapgraph.hs — Map.member 1"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-mapgraph-source "main = Map.member 1 build\n")))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"mapgraph.hs — Map.size = 4 source nodes"
|
||||
(hk-deep-force (hk-run (str hk-mapgraph-source "main = Map.size build\n")))
|
||||
4)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
49
lib/haskell/tests/program-newton.sx
Normal file
49
lib/haskell/tests/program-newton.sx
Normal file
@@ -0,0 +1,49 @@
|
||||
;; newton.hs — Newton's method for square root.
|
||||
;; Source: classic numerical analysis exercise.
|
||||
;;
|
||||
;; Exercises Phase 10: `Float`, `abs`, `/`, iteration via `until`.
|
||||
|
||||
(define
|
||||
hk-prog-val
|
||||
(fn
|
||||
(src name)
|
||||
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
|
||||
|
||||
(define
|
||||
hk-newton-source
|
||||
"improve x guess = (guess + x / guess) / 2\n\ngoodEnough x guess = abs (guess * guess - x) < 0.0001\n\nnewtonSqrt x = newtonHelp x 1.0\n\nnewtonHelp x guess = if goodEnough x guess\n then guess\n else newtonHelp x (improve x guess)\n")
|
||||
|
||||
(hk-test
|
||||
"newton.hs — newtonSqrt 4 ≈ 2"
|
||||
(hk-prog-val
|
||||
(str hk-newton-source "r = abs (newtonSqrt 4.0 - 2.0) < 0.001\n")
|
||||
"r")
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"newton.hs — newtonSqrt 9 ≈ 3"
|
||||
(hk-prog-val
|
||||
(str hk-newton-source "r = abs (newtonSqrt 9.0 - 3.0) < 0.001\n")
|
||||
"r")
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"newton.hs — newtonSqrt 2 ≈ 1.41421"
|
||||
(hk-prog-val
|
||||
(str hk-newton-source "r = abs (newtonSqrt 2.0 - 1.41421) < 0.001\n")
|
||||
"r")
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"newton.hs — improve converges (one step)"
|
||||
(hk-prog-val (str hk-newton-source "r = improve 4.0 1.0\n") "r")
|
||||
2.5)
|
||||
|
||||
(hk-test
|
||||
"newton.hs — newtonSqrt 100 ≈ 10"
|
||||
(hk-prog-val
|
||||
(str hk-newton-source "r = abs (newtonSqrt 100.0 - 10.0) < 0.001\n")
|
||||
"r")
|
||||
(list "True"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
58
lib/haskell/tests/program-partial.sx
Normal file
58
lib/haskell/tests/program-partial.sx
Normal file
@@ -0,0 +1,58 @@
|
||||
;; partial.hs — exercises Phase 9 partial functions caught at the top level.
|
||||
;;
|
||||
;; Each program calls a partial function on bad input; hk-run-io catches the
|
||||
;; raise and appends the error message to io-lines so tests can inspect.
|
||||
|
||||
(hk-test
|
||||
"partial.hs — main = print (head [])"
|
||||
(let
|
||||
((lines (hk-run-io "main = print (head [])")))
|
||||
(>= (index-of (str lines) "Prelude.head: empty list") 0))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"partial.hs — main = print (tail [])"
|
||||
(let
|
||||
((lines (hk-run-io "main = print (tail [])")))
|
||||
(>= (index-of (str lines) "Prelude.tail: empty list") 0))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"partial.hs — main = print (fromJust Nothing)"
|
||||
(let
|
||||
((lines (hk-run-io "main = print (fromJust Nothing)")))
|
||||
(>= (index-of (str lines) "Maybe.fromJust: Nothing") 0))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"partial.hs — putStrLn before error preserves prior output"
|
||||
(let
|
||||
((lines (hk-run-io "main = do { putStrLn \"step 1\"; putStrLn (show (head [])); putStrLn \"never\" }")))
|
||||
(and
|
||||
(>= (index-of (str lines) "step 1") 0)
|
||||
(>= (index-of (str lines) "Prelude.head: empty list") 0)
|
||||
(= (index-of (str lines) "never") -1)))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"partial.hs — undefined as IO action"
|
||||
(let
|
||||
((lines (hk-run-io "main = print undefined")))
|
||||
(>= (index-of (str lines) "Prelude.undefined") 0))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"partial.hs — catches error from a user-thrown error"
|
||||
(let
|
||||
((lines (hk-run-io "main = error \"boom from main\"")))
|
||||
(>= (index-of (str lines) "boom from main") 0))
|
||||
true)
|
||||
|
||||
;; Negative case: when no error is raised, io-lines doesn't contain
|
||||
;; "Prelude" prefixes from our error path.
|
||||
(hk-test
|
||||
"partial.hs — happy path: head [42] succeeds, no error in output"
|
||||
(hk-run-io "main = print (head [42])")
|
||||
(list "42"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
51
lib/haskell/tests/program-person.sx
Normal file
51
lib/haskell/tests/program-person.sx
Normal file
@@ -0,0 +1,51 @@
|
||||
;; person.hs — record type with accessors, update, deriving Show.
|
||||
;;
|
||||
;; Exercises Phase 14: data with record syntax, accessor functions,
|
||||
;; record creation, record update, deriving Show on a record.
|
||||
|
||||
(define
|
||||
hk-person-source
|
||||
"data Person = Person { name :: String, age :: Int } deriving (Show)\n\nalice = Person { name = \"alice\", age = 30 }\nbob = Person { name = \"bob\", age = 25 }\n\nbirthday p = p { age = age p + 1 }\n")
|
||||
|
||||
(hk-test
|
||||
"person.hs — alice's name"
|
||||
(hk-deep-force (hk-run (str hk-person-source "main = name alice")))
|
||||
"alice")
|
||||
|
||||
(hk-test
|
||||
"person.hs — alice's age"
|
||||
(hk-deep-force (hk-run (str hk-person-source "main = age alice")))
|
||||
30)
|
||||
|
||||
(hk-test
|
||||
"person.hs — birthday adds one year"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-person-source "main = age (birthday alice)")))
|
||||
31)
|
||||
|
||||
(hk-test
|
||||
"person.hs — birthday preserves name"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-person-source "main = name (birthday alice)")))
|
||||
"alice")
|
||||
|
||||
(hk-test
|
||||
"person.hs — show alice"
|
||||
(hk-deep-force (hk-run (str hk-person-source "main = show alice")))
|
||||
"Person \"alice\" 30")
|
||||
|
||||
(hk-test
|
||||
"person.hs — bob has different name"
|
||||
(hk-deep-force (hk-run (str hk-person-source "main = name bob")))
|
||||
"bob")
|
||||
|
||||
(hk-test
|
||||
"person.hs — pattern match in function"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"greet (Person { name = n }) = \"Hi, \" ++ n\nmain = greet alice")))
|
||||
"Hi, alice")
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
83
lib/haskell/tests/program-runlength-str.sx
Normal file
83
lib/haskell/tests/program-runlength-str.sx
Normal file
@@ -0,0 +1,83 @@
|
||||
;; runlength-str.hs — run-length encoding on a String.
|
||||
;; Source: https://rosettacode.org/wiki/Run-length_encoding#Haskell (adapted).
|
||||
;;
|
||||
;; Exercises String pattern matching `(x:xs)`, `span` over a string view,
|
||||
;; tuple construction `(Int, Char)`, character equality, and tuple-in-cons
|
||||
;; patterns `((n, c) : rest)` — all enabled by Phase 7 string=[Char].
|
||||
|
||||
(define
|
||||
hk-prog-val
|
||||
(fn
|
||||
(src name)
|
||||
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
|
||||
|
||||
(define
|
||||
hk-as-list
|
||||
(fn
|
||||
(xs)
|
||||
(cond
|
||||
((and (list? xs) (= (first xs) "[]")) (list))
|
||||
((and (list? xs) (= (first xs) ":"))
|
||||
(cons (nth xs 1) (hk-as-list (nth xs 2))))
|
||||
(:else xs))))
|
||||
|
||||
(define
|
||||
hk-rle-source
|
||||
"encodeRL [] = []\nencodeRL (x:xs) = let (same, rest) = span eqX xs\n eqX y = y == x\n in (1 + length same, x) : encodeRL rest\n\nreplicateRL 0 _ = []\nreplicateRL n c = c : replicateRL (n - 1) c\n\ndecodeRL [] = []\ndecodeRL ((n, c) : rest) = replicateRL n c ++ decodeRL rest\n")
|
||||
|
||||
(hk-test
|
||||
"rle.hs — encodeRL [] = []"
|
||||
(hk-as-list (hk-prog-val (str hk-rle-source "r = encodeRL \"\"\n") "r"))
|
||||
(list))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — length (encodeRL \"aabbbcc\") = 3"
|
||||
(hk-prog-val (str hk-rle-source "r = length (encodeRL \"aabbbcc\")\n") "r")
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"rle.hs — map fst (encodeRL \"aabbbcc\") = [2,3,2]"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-rle-source "r = map fst (encodeRL \"aabbbcc\")\n") "r"))
|
||||
(list 2 3 2))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — map snd (encodeRL \"aabbbcc\") = [97,98,99]"
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-rle-source "r = map snd (encodeRL \"aabbbcc\")\n") "r"))
|
||||
(list 97 98 99))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — counts of encodeRL \"aabbbccddddee\" = [2,3,2,4,2]"
|
||||
(hk-as-list
|
||||
(hk-prog-val
|
||||
(str hk-rle-source "r = map fst (encodeRL \"aabbbccddddee\")\n")
|
||||
"r"))
|
||||
(list 2 3 2 4 2))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — chars of encodeRL \"aabbbccddddee\" = [97,98,99,100,101]"
|
||||
(hk-as-list
|
||||
(hk-prog-val
|
||||
(str hk-rle-source "r = map snd (encodeRL \"aabbbccddddee\")\n")
|
||||
"r"))
|
||||
(list 97 98 99 100 101))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — singleton encodeRL \"x\""
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-rle-source "r = map fst (encodeRL \"x\")\n") "r"))
|
||||
(list 1))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — decodeRL round-trip preserves \"aabbbcc\""
|
||||
(hk-as-list
|
||||
(hk-prog-val (str hk-rle-source "r = decodeRL (encodeRL \"aabbbcc\")\n") "r"))
|
||||
(list 97 97 98 98 98 99 99))
|
||||
|
||||
(hk-test
|
||||
"rle.hs — replicateRL 4 65 = [65,65,65,65]"
|
||||
(hk-as-list (hk-prog-val (str hk-rle-source "r = replicateRL 4 65\n") "r"))
|
||||
(list 65 65 65 65))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
61
lib/haskell/tests/program-setops.sx
Normal file
61
lib/haskell/tests/program-setops.sx
Normal file
@@ -0,0 +1,61 @@
|
||||
;; setops.hs — set union/intersection/difference on integer sets.
|
||||
;;
|
||||
;; Exercises Phase 12: `import qualified Data.Set as Set`, all three
|
||||
;; combining operations + isSubsetOf.
|
||||
|
||||
(define
|
||||
hk-setops-source
|
||||
"import qualified Data.Set as Set\n\ns1 = Set.insert 1 (Set.insert 2 (Set.insert 3 Set.empty))\ns2 = Set.insert 3 (Set.insert 4 (Set.insert 5 Set.empty))\ns3 = Set.insert 1 (Set.insert 2 Set.empty)\n")
|
||||
|
||||
(hk-test
|
||||
"setops.hs — union size = 5"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-setops-source "main = Set.size (Set.union s1 s2)\n")))
|
||||
5)
|
||||
|
||||
(hk-test
|
||||
"setops.hs — intersection size = 1"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-setops-source "main = Set.size (Set.intersection s1 s2)\n")))
|
||||
1)
|
||||
|
||||
(hk-test
|
||||
"setops.hs — intersection contains 3"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-setops-source "main = Set.member 3 (Set.intersection s1 s2)\n")))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"setops.hs — difference s1 s2 size = 2"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-setops-source "main = Set.size (Set.difference s1 s2)\n")))
|
||||
2)
|
||||
|
||||
(hk-test
|
||||
"setops.hs — difference doesn't contain shared key"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-setops-source "main = Set.member 3 (Set.difference s1 s2)\n")))
|
||||
(list "False"))
|
||||
|
||||
(hk-test
|
||||
"setops.hs — s3 is subset of s1"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-setops-source "main = Set.isSubsetOf s3 s1\n")))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"setops.hs — s1 not subset of s3"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-setops-source "main = Set.isSubsetOf s1 s3\n")))
|
||||
(list "False"))
|
||||
|
||||
(hk-test
|
||||
"setops.hs — empty set is subset of anything"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-setops-source "main = Set.isSubsetOf Set.empty s1\n")))
|
||||
(list "True"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
40
lib/haskell/tests/program-shapes.sx
Normal file
40
lib/haskell/tests/program-shapes.sx
Normal file
@@ -0,0 +1,40 @@
|
||||
;; shapes.hs — class Area with a default perimeter, two instances
|
||||
;; using where-local helpers.
|
||||
;;
|
||||
;; Exercises Phase 13: class default method (perimeter), instance
|
||||
;; methods that use `where`-bindings.
|
||||
|
||||
(define
|
||||
hk-shapes-source
|
||||
"class Shape a where\n area :: a -> Int\n perimeter :: a -> Int\n perimeter x = quadrilateral x\n where quadrilateral y = 2 * (sideA y + sideB y)\n sideA z = 1\n sideB z = 1\n\ndata Square = Square Int\ndata Rect = Rect Int Int\n\ninstance Shape Square where\n area (Square s) = s * s\n perimeter (Square s) = 4 * s\n\ninstance Shape Rect where\n area (Rect w h) = w * h\n perimeter (Rect w h) = peri\n where peri = 2 * (w + h)\n")
|
||||
|
||||
(hk-test
|
||||
"shapes.hs — area of Square 5 = 25"
|
||||
(hk-deep-force (hk-run (str hk-shapes-source "main = area (Square 5)\n")))
|
||||
25)
|
||||
|
||||
(hk-test
|
||||
"shapes.hs — perimeter of Square 5 = 20"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-shapes-source "main = perimeter (Square 5)\n")))
|
||||
20)
|
||||
|
||||
(hk-test
|
||||
"shapes.hs — area of Rect 3 4 = 12"
|
||||
(hk-deep-force (hk-run (str hk-shapes-source "main = area (Rect 3 4)\n")))
|
||||
12)
|
||||
|
||||
(hk-test
|
||||
"shapes.hs — perimeter of Rect 3 4 = 14 (via where-bound)"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-shapes-source "main = perimeter (Rect 3 4)\n")))
|
||||
14)
|
||||
|
||||
(hk-test
|
||||
"shapes.hs — Square sums area + perimeter"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-shapes-source "main = area (Square 4) + perimeter (Square 4)\n")))
|
||||
32)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
45
lib/haskell/tests/program-showadt.sx
Normal file
45
lib/haskell/tests/program-showadt.sx
Normal file
@@ -0,0 +1,45 @@
|
||||
;; showadt.hs — `deriving (Show)` on a multi-constructor recursive ADT.
|
||||
;; Source: classic exposition example, e.g. Real World Haskell ch.6.
|
||||
;;
|
||||
;; Exercises Phase 8: `deriving (Show)` on an ADT whose constructors recurse
|
||||
;; into themselves; precedence-based paren wrapping for nested arguments;
|
||||
;; `print` from the prelude (which is `putStrLn (show x)`).
|
||||
|
||||
(define
|
||||
hk-showadt-source
|
||||
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\n\nmain = do\n print (Lit 3)\n print (Add (Lit 1) (Lit 2))\n print (Mul (Lit 3) (Add (Lit 4) (Lit 5)))\n")
|
||||
|
||||
(hk-test
|
||||
"showadt.hs — main prints three lines"
|
||||
(hk-run-io hk-showadt-source)
|
||||
(list "Lit 3" "Add (Lit 1) (Lit 2)" "Mul (Lit 3) (Add (Lit 4) (Lit 5))"))
|
||||
|
||||
(hk-test
|
||||
"showadt.hs — show Lit 3"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\nmain = show (Lit 3)"))
|
||||
"Lit 3")
|
||||
|
||||
(hk-test
|
||||
"showadt.hs — show Add wraps both args"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\nmain = show (Add (Lit 1) (Lit 2))"))
|
||||
"Add (Lit 1) (Lit 2)")
|
||||
|
||||
(hk-test
|
||||
"showadt.hs — fully nested Mul of Adds"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\nmain = show (Mul (Add (Lit 1) (Lit 2)) (Add (Lit 3) (Lit 4)))"))
|
||||
"Mul (Add (Lit 1) (Lit 2)) (Add (Lit 3) (Lit 4))")
|
||||
|
||||
(hk-test
|
||||
"showadt.hs — Lit with negative literal wraps int in parens"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Expr = Lit Int | Add Expr Expr | Mul Expr Expr deriving (Show)\nmain = show (Lit (negate 7))"))
|
||||
"Lit (-7)")
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
36
lib/haskell/tests/program-showio.sx
Normal file
36
lib/haskell/tests/program-showio.sx
Normal file
@@ -0,0 +1,36 @@
|
||||
;; showio.hs — `print` on various types inside a `do` block.
|
||||
;;
|
||||
;; Exercises Phase 8 `print x = putStrLn (show x)` and the IO monad's
|
||||
;; statement sequencing. Each `print` produces one io-line.
|
||||
|
||||
(define
|
||||
hk-showio-source
|
||||
"main = do\n print 42\n print True\n print False\n print [1,2,3]\n print (1, 2)\n print (Just 5)\n print Nothing\n print \"hello\"\n")
|
||||
|
||||
(hk-test
|
||||
"showio.hs — main produces 8 lines, all show-formatted"
|
||||
(hk-run-io hk-showio-source)
|
||||
(list "42" "True" "False" "[1,2,3]" "(1,2)" "Just 5" "Nothing" "\"hello\""))
|
||||
|
||||
(hk-test
|
||||
"showio.hs — print Int alone"
|
||||
(hk-run-io "main = print 42")
|
||||
(list "42"))
|
||||
|
||||
(hk-test
|
||||
"showio.hs — print list of Maybe"
|
||||
(hk-run-io "main = print [Just 1, Nothing, Just 3]")
|
||||
(list "[Just 1,Nothing,Just 3]"))
|
||||
|
||||
(hk-test
|
||||
"showio.hs — print nested tuple"
|
||||
(hk-run-io "main = print ((1, 2), (3, 4))")
|
||||
(list "((1,2),(3,4))"))
|
||||
|
||||
(hk-test
|
||||
"showio.hs — print derived ADT inside do"
|
||||
(hk-run-io
|
||||
"data Color = Red | Green | Blue deriving (Show)\nmain = do { print Red; print Green; print Blue }")
|
||||
(list "Red" "Green" "Blue"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
45
lib/haskell/tests/program-statistics.sx
Normal file
45
lib/haskell/tests/program-statistics.sx
Normal file
@@ -0,0 +1,45 @@
|
||||
;; statistics.hs — mean, variance, std-dev on a [Double].
|
||||
;; Source: classic textbook example.
|
||||
;;
|
||||
;; Exercises Phase 10: `fromIntegral`, `/`, `sqrt`, list ops on `[Double]`.
|
||||
|
||||
(define
|
||||
hk-prog-val
|
||||
(fn
|
||||
(src name)
|
||||
(hk-deep-force (get (hk-eval-program (hk-core src)) name))))
|
||||
|
||||
(define
|
||||
hk-stats-source
|
||||
"mean xs = sum xs / fromIntegral (length xs)\n\nvariance xs = let m = mean xs\n sqDiff x = (x - m) * (x - m)\n in sum (map sqDiff xs) / fromIntegral (length xs)\n\nstdDev xs = sqrt (variance xs)\n")
|
||||
|
||||
(hk-test
|
||||
"statistics.hs — mean [1,2,3,4,5] = 3"
|
||||
(hk-prog-val (str hk-stats-source "r = mean [1.0,2.0,3.0,4.0,5.0]\n") "r")
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"statistics.hs — mean [10,20,30] = 20"
|
||||
(hk-prog-val (str hk-stats-source "r = mean [10.0,20.0,30.0]\n") "r")
|
||||
20)
|
||||
|
||||
(hk-test
|
||||
"statistics.hs — variance [2,4,4,4,5,5,7,9] = 4"
|
||||
(hk-prog-val
|
||||
(str hk-stats-source "r = variance [2.0,4.0,4.0,4.0,5.0,5.0,7.0,9.0]\n")
|
||||
"r")
|
||||
4)
|
||||
|
||||
(hk-test
|
||||
"statistics.hs — stdDev [2,4,4,4,5,5,7,9] = 2"
|
||||
(hk-prog-val
|
||||
(str hk-stats-source "r = stdDev [2.0,4.0,4.0,4.0,5.0,5.0,7.0,9.0]\n")
|
||||
"r")
|
||||
2)
|
||||
|
||||
(hk-test
|
||||
"statistics.hs — variance of constant list = 0"
|
||||
(hk-prog-val (str hk-stats-source "r = variance [5.0,5.0,5.0,5.0]\n") "r")
|
||||
0)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
35
lib/haskell/tests/program-uniquewords.sx
Normal file
35
lib/haskell/tests/program-uniquewords.sx
Normal file
@@ -0,0 +1,35 @@
|
||||
;; uniquewords.hs — count unique words using Data.Set.
|
||||
;;
|
||||
;; Exercises Phase 12: `import qualified Data.Set as Set`, `Set.empty`,
|
||||
;; `Set.insert`, `Set.size`, `foldl`.
|
||||
|
||||
(define
|
||||
hk-uniquewords-source
|
||||
"import qualified Data.Set as Set\n\naddWord s w = Set.insert w s\n\nuniqueWords ws = foldl addWord Set.empty ws\n\nresult = uniqueWords [\"the\", \"cat\", \"the\", \"dog\", \"the\", \"cat\"]\n")
|
||||
|
||||
(hk-test
|
||||
"uniquewords.hs — unique count = 3"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-uniquewords-source "main = Set.size result\n")))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"uniquewords.hs — \"the\" present"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-uniquewords-source "main = Set.member \"the\" result\n")))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"uniquewords.hs — \"missing\" absent"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-uniquewords-source "main = Set.member \"missing\" result\n")))
|
||||
(list "False"))
|
||||
|
||||
(hk-test
|
||||
"uniquewords.hs — empty list yields empty set"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Set as Set\nmain = Set.size (foldl (\\s w -> Set.insert w s) Set.empty [])"))
|
||||
0)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
54
lib/haskell/tests/program-wordfreq.sx
Normal file
54
lib/haskell/tests/program-wordfreq.sx
Normal file
@@ -0,0 +1,54 @@
|
||||
;; wordfreq.hs — word-frequency histogram using Data.Map.
|
||||
;; Source: Rosetta Code "Word frequency" (Haskell entry, simplified).
|
||||
;;
|
||||
;; Exercises Phase 11: `import qualified Data.Map as Map`, `Map.empty`,
|
||||
;; `Map.insertWith`, `Map.lookup`, `Map.findWithDefault`, `foldl`.
|
||||
|
||||
(define
|
||||
hk-wordfreq-source
|
||||
"import qualified Data.Map as Map\n\ncountWord m w = Map.insertWith (+) w 1 m\n\nwordFreq xs = foldl countWord Map.empty xs\n\nresult = wordFreq [\"the\", \"cat\", \"the\", \"dog\", \"the\", \"cat\"]\n")
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — \"the\" counted 3 times"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-wordfreq-source "main = Map.lookup \"the\" result\n")))
|
||||
(list "Just" 3))
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — \"cat\" counted 2 times"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-wordfreq-source "main = Map.lookup \"cat\" result\n")))
|
||||
(list "Just" 2))
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — \"dog\" counted 1 time"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-wordfreq-source "main = Map.lookup \"dog\" result\n")))
|
||||
(list "Just" 1))
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — \"missing\" not present"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-wordfreq-source "main = Map.lookup \"missing\" result\n")))
|
||||
(list "Nothing"))
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — Map.size = 3 unique words"
|
||||
(hk-deep-force (hk-run (str hk-wordfreq-source "main = Map.size result\n")))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — findWithDefault for missing returns 0"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-wordfreq-source "main = Map.findWithDefault 0 \"absent\" result\n")))
|
||||
0)
|
||||
|
||||
(hk-test
|
||||
"wordfreq.hs — findWithDefault for present returns count"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-wordfreq-source "main = Map.findWithDefault 0 \"the\" result\n")))
|
||||
3)
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
127
lib/haskell/tests/records.sx
Normal file
127
lib/haskell/tests/records.sx
Normal file
@@ -0,0 +1,127 @@
|
||||
;; records.sx — Phase 14 record syntax tests.
|
||||
|
||||
(define
|
||||
hk-person-source
|
||||
"data Person = Person { name :: String, age :: Int }\n")
|
||||
|
||||
(define hk-pt-source "data Pt = Pt { x :: Int, y :: Int }\n")
|
||||
|
||||
;; ── Creation ────────────────────────────────────────────────
|
||||
(hk-test
|
||||
"creation: Person { name = \"a\", age = 1 } via accessor name"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"main = name (Person { name = \"alice\", age = 30 })")))
|
||||
"alice")
|
||||
|
||||
(hk-test
|
||||
"creation: source order doesn't matter (age first)"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-person-source "main = name (Person { age = 99, name = \"bob\" })")))
|
||||
"bob")
|
||||
|
||||
(hk-test
|
||||
"creation: age accessor returns the right field"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str hk-person-source "main = age (Person { age = 99, name = \"bob\" })")))
|
||||
99)
|
||||
|
||||
;; ── Accessors ──────────────────────────────────────────────
|
||||
(hk-test
|
||||
"accessor: x of Pt"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-pt-source "main = x (Pt { x = 7, y = 99 })")))
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"accessor: y of Pt"
|
||||
(hk-deep-force
|
||||
(hk-run (str hk-pt-source "main = y (Pt { x = 7, y = 99 })")))
|
||||
99)
|
||||
|
||||
;; ── Update — single field ──────────────────────────────────
|
||||
(hk-test
|
||||
"update one field: age changes"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"alice = Person { name = \"alice\", age = 30 }\nmain = age (alice { age = 31 })")))
|
||||
31)
|
||||
|
||||
(hk-test
|
||||
"update one field: name preserved"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"alice = Person { name = \"alice\", age = 30 }\nmain = name (alice { age = 31 })")))
|
||||
"alice")
|
||||
|
||||
;; ── Update — two fields ────────────────────────────────────
|
||||
(hk-test
|
||||
"update two fields: both changed"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"alice = Person { name = \"alice\", age = 30 }\nbob = alice { name = \"bob\", age = 50 }\nmain = age bob")))
|
||||
50)
|
||||
|
||||
(hk-test
|
||||
"update two fields: name takes new value"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"alice = Person { name = \"alice\", age = 30 }\nbob = alice { name = \"bob\", age = 50 }\nmain = name bob")))
|
||||
"bob")
|
||||
|
||||
;; ── Record patterns ────────────────────────────────────────
|
||||
(hk-test
|
||||
"case-alt record pattern: Pt { x = a }"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-pt-source
|
||||
"getX p = case p of Pt { x = a } -> a\nmain = getX (Pt { x = 7, y = 99 })")))
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"case-alt record pattern: multi-field bind"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-pt-source
|
||||
"sumPt p = case p of Pt { x = a, y = b } -> a + b\nmain = sumPt (Pt { x = 3, y = 4 })")))
|
||||
7)
|
||||
|
||||
(hk-test
|
||||
"fun-LHS record pattern"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
(str
|
||||
hk-person-source
|
||||
"getName (Person { name = n }) = n\nmain = getName (Person { name = \"alice\", age = 30 })")))
|
||||
"alice")
|
||||
|
||||
;; ── deriving Show on a record ───────────────────────────────
|
||||
(hk-test
|
||||
"deriving Show on a record produces positional output"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Person = Person { name :: String, age :: Int } deriving (Show)\nmain = show (Person { name = \"alice\", age = 30 })"))
|
||||
"Person \"alice\" 30")
|
||||
|
||||
(hk-test
|
||||
"deriving Show on Pt"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Pt = Pt { x :: Int, y :: Int } deriving (Show)\nmain = show (Pt { x = 3, y = 4 })"))
|
||||
"Pt 3 4")
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
119
lib/haskell/tests/set.sx
Normal file
119
lib/haskell/tests/set.sx
Normal file
@@ -0,0 +1,119 @@
|
||||
;; set.sx — Phase 12 Data.Set unit tests.
|
||||
|
||||
;; ── SX-level (direct hk-set-*) ───────────────────────────────
|
||||
(hk-test
|
||||
"hk-set-empty: size 0 + null"
|
||||
(list (hk-set-size hk-set-empty) (hk-set-null hk-set-empty))
|
||||
(list 0 true))
|
||||
|
||||
(hk-test
|
||||
"hk-set-singleton: member yes"
|
||||
(let
|
||||
((s (hk-set-singleton 5)))
|
||||
(list (hk-set-size s) (hk-set-member 5 s) (hk-set-member 99 s)))
|
||||
(list 1 true false))
|
||||
|
||||
(hk-test
|
||||
"hk-set-insert: idempotent"
|
||||
(let
|
||||
((s (hk-set-insert 1 (hk-set-insert 1 hk-set-empty))))
|
||||
(hk-set-size s))
|
||||
1)
|
||||
|
||||
(hk-test
|
||||
"hk-set-from-list: dedupes"
|
||||
(hk-set-to-asc-list (hk-set-from-list (list 3 1 4 1 5 9 2 6)))
|
||||
(list 1 2 3 4 5 6 9))
|
||||
|
||||
(hk-test
|
||||
"hk-set-delete: removes"
|
||||
(let
|
||||
((s (hk-set-from-list (list 1 2 3))))
|
||||
(hk-set-to-asc-list (hk-set-delete 2 s)))
|
||||
(list 1 3))
|
||||
|
||||
(hk-test
|
||||
"hk-set-union"
|
||||
(hk-set-to-asc-list
|
||||
(hk-set-union
|
||||
(hk-set-from-list (list 1 2 3))
|
||||
(hk-set-from-list (list 3 4 5))))
|
||||
(list 1 2 3 4 5))
|
||||
|
||||
(hk-test
|
||||
"hk-set-intersection"
|
||||
(hk-set-to-asc-list
|
||||
(hk-set-intersection
|
||||
(hk-set-from-list (list 1 2 3 4))
|
||||
(hk-set-from-list (list 3 4 5 6))))
|
||||
(list 3 4))
|
||||
|
||||
(hk-test
|
||||
"hk-set-difference"
|
||||
(hk-set-to-asc-list
|
||||
(hk-set-difference
|
||||
(hk-set-from-list (list 1 2 3 4))
|
||||
(hk-set-from-list (list 3 4 5))))
|
||||
(list 1 2))
|
||||
|
||||
(hk-test
|
||||
"hk-set-is-subset-of: yes"
|
||||
(hk-set-is-subset-of
|
||||
(hk-set-from-list (list 2 3))
|
||||
(hk-set-from-list (list 1 2 3 4)))
|
||||
true)
|
||||
|
||||
(hk-test
|
||||
"hk-set-is-subset-of: no"
|
||||
(hk-set-is-subset-of
|
||||
(hk-set-from-list (list 5 6))
|
||||
(hk-set-from-list (list 1 2 3 4)))
|
||||
false)
|
||||
|
||||
(hk-test
|
||||
"hk-set-filter"
|
||||
(hk-set-to-asc-list
|
||||
(hk-set-filter (fn (k) (> k 2)) (hk-set-from-list (list 1 2 3 4 5))))
|
||||
(list 3 4 5))
|
||||
|
||||
(hk-test
|
||||
"hk-set-map"
|
||||
(hk-set-to-asc-list
|
||||
(hk-set-map (fn (k) (* k 10)) (hk-set-from-list (list 1 2 3))))
|
||||
(list 10 20 30))
|
||||
|
||||
(hk-test
|
||||
"hk-set-foldr: sum"
|
||||
(hk-set-foldr + 0 (hk-set-from-list (list 1 2 3 4 5)))
|
||||
15)
|
||||
|
||||
;; ── Haskell-level (Set.* via import wiring) ──────────────────
|
||||
(hk-test
|
||||
"Set.size after Set.insert chain"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Set as Set\nmain = Set.size (Set.insert 3 (Set.insert 1 (Set.insert 2 Set.empty)))"))
|
||||
3)
|
||||
|
||||
(hk-test
|
||||
"Set.member true"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Set as Set\nmain = Set.member 5 (Set.insert 5 Set.empty)"))
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"Set.union via Haskell"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import Data.Set\nmain = Set.size (Set.union (Set.insert 1 Set.empty) (Set.insert 2 Set.empty))"))
|
||||
2)
|
||||
|
||||
(hk-test
|
||||
"Set.isSubsetOf via Haskell"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"import qualified Data.Set as S\nmain = S.isSubsetOf (S.insert 1 S.empty) (S.insert 2 (S.insert 1 S.empty))"))
|
||||
(list "True"))
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
140
lib/haskell/tests/show.sx
Normal file
140
lib/haskell/tests/show.sx
Normal file
@@ -0,0 +1,140 @@
|
||||
;; show.sx — tests for the Show / Read class plumbing.
|
||||
;;
|
||||
;; Covers Phase 8:
|
||||
;; - showsPrec / showParen / shows / showString stubs
|
||||
;; - Read class stubs (reads / readsPrec / read)
|
||||
;; - direct show coverage (Int, Bool, String, list, tuple, Maybe, ADT, ...)
|
||||
|
||||
;; ── ShowS / showsPrec / showParen stubs ──────────────────────
|
||||
(hk-test
|
||||
"shows: prepends show output"
|
||||
(hk-deep-force (hk-run "main = shows 5 \"abc\""))
|
||||
"5abc")
|
||||
|
||||
(hk-test
|
||||
"shows: works on True"
|
||||
(hk-deep-force (hk-run "main = shows True \"x\""))
|
||||
"Truex")
|
||||
|
||||
(hk-test
|
||||
"showString: prepends literal"
|
||||
(hk-deep-force (hk-run "main = showString \"hello\" \" world\""))
|
||||
"hello world")
|
||||
|
||||
(hk-test
|
||||
"showParen True: wraps inner output in parens"
|
||||
(hk-deep-force (hk-run "main = showParen True (showString \"inside\") \"\""))
|
||||
"(inside)")
|
||||
|
||||
(hk-test
|
||||
"showParen False: passes through unchanged"
|
||||
(hk-deep-force (hk-run "main = showParen False (showString \"inside\") \"\""))
|
||||
"inside")
|
||||
|
||||
(hk-test
|
||||
"showsPrec: prepends show output regardless of prec"
|
||||
(hk-deep-force (hk-run "main = showsPrec 11 42 \"end\""))
|
||||
"42end")
|
||||
|
||||
(hk-test
|
||||
"showParen + manual composition: build (Just 3)"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"buildJust3 s = showString \"Just \" (shows 3 s)\nmain = showParen True buildJust3 \"\""))
|
||||
"(Just 3)")
|
||||
|
||||
;; ── Read stubs ───────────────────────────────────────────────
|
||||
(hk-test
|
||||
"reads: stub returns empty list (null-check)"
|
||||
(hk-deep-force (hk-run "main = show (null (reads \"42\"))"))
|
||||
"True")
|
||||
|
||||
(hk-test
|
||||
"readsPrec: stub returns empty list"
|
||||
(hk-deep-force (hk-run "main = show (null (readsPrec 0 \"True\"))"))
|
||||
"True")
|
||||
|
||||
(hk-test
|
||||
"reads: type-checks in expression context (length)"
|
||||
(hk-deep-force (hk-run "main = show (length (reads \"abc\"))"))
|
||||
"0")
|
||||
|
||||
;; ── Direct `show` audit coverage ─────────────────────────────
|
||||
(hk-test "show Int" (hk-deep-force (hk-run "main = show 42")) "42")
|
||||
|
||||
(hk-test
|
||||
"show negative Int"
|
||||
(hk-deep-force (hk-run "main = show (negate 5)"))
|
||||
"-5")
|
||||
|
||||
(hk-test "show Bool True" (hk-deep-force (hk-run "main = show True")) "True")
|
||||
|
||||
(hk-test
|
||||
"show Bool False"
|
||||
(hk-deep-force (hk-run "main = show False"))
|
||||
"False")
|
||||
|
||||
(hk-test
|
||||
"show String quotes the value"
|
||||
(hk-deep-force (hk-run "main = show \"hello\""))
|
||||
"\"hello\"")
|
||||
|
||||
(hk-test
|
||||
"show list of Int"
|
||||
(hk-deep-force (hk-run "main = show [1,2,3]"))
|
||||
"[1,2,3]")
|
||||
|
||||
(hk-test
|
||||
"show empty list"
|
||||
(hk-deep-force (hk-run "main = show (drop 5 [1,2,3])"))
|
||||
"[]")
|
||||
|
||||
(hk-test
|
||||
"show pair tuple"
|
||||
(hk-deep-force (hk-run "main = show (1, True)"))
|
||||
"(1,True)")
|
||||
|
||||
(hk-test
|
||||
"show triple tuple"
|
||||
(hk-deep-force (hk-run "main = show (1, 2, 3)"))
|
||||
"(1,2,3)")
|
||||
|
||||
(hk-test
|
||||
"show Maybe Nothing"
|
||||
(hk-deep-force (hk-run "main = show Nothing"))
|
||||
"Nothing")
|
||||
|
||||
(hk-test
|
||||
"show Maybe Just"
|
||||
(hk-deep-force (hk-run "main = show (Just 3)"))
|
||||
"Just 3")
|
||||
|
||||
(hk-test
|
||||
"show nested Just wraps inner in parens"
|
||||
(hk-deep-force (hk-run "main = show (Just (Just 3))"))
|
||||
"Just (Just 3)")
|
||||
|
||||
(hk-test
|
||||
"show Just (negate 3) wraps negative in parens"
|
||||
(hk-deep-force (hk-run "main = show (Just (negate 3))"))
|
||||
"Just (-3)")
|
||||
|
||||
(hk-test
|
||||
"show custom nullary ADT"
|
||||
(hk-deep-force
|
||||
(hk-run "data Day = Mon | Tue | Wed deriving (Show)\nmain = show Tue"))
|
||||
"Tue")
|
||||
|
||||
(hk-test
|
||||
"show custom multi-constructor ADT"
|
||||
(hk-deep-force
|
||||
(hk-run
|
||||
"data Shape = Pt | Sq Int | Rect Int Int deriving (Show)\nmain = show (Rect 3 4)"))
|
||||
"Rect 3 4")
|
||||
|
||||
(hk-test
|
||||
"show list of Maybe wraps each element"
|
||||
(hk-deep-force (hk-run "main = show [Just 1, Nothing, Just 2]"))
|
||||
"[Just 1,Nothing,Just 2]")
|
||||
|
||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||
@@ -37,11 +37,11 @@
|
||||
(hk-ts "show neg" "negate 7" "-7")
|
||||
(hk-ts "show bool T" "True" "True")
|
||||
(hk-ts "show bool F" "False" "False")
|
||||
(hk-ts "show list" "[1,2,3]" "[1, 2, 3]")
|
||||
(hk-ts "show Just" "Just 5" "(Just 5)")
|
||||
(hk-ts "show list" "[1,2,3]" "[1,2,3]")
|
||||
(hk-ts "show Just" "Just 5" "Just 5")
|
||||
(hk-ts "show Nothing" "Nothing" "Nothing")
|
||||
(hk-ts "show LT" "LT" "LT")
|
||||
(hk-ts "show tuple" "(1, True)" "(1, True)")
|
||||
(hk-ts "show tuple" "(1, True)" "(1,True)")
|
||||
|
||||
;; ── Num extras ───────────────────────────────────────────────
|
||||
(hk-test "signum pos" (hk-deep-force (hk-run "main = signum 5")) 1)
|
||||
@@ -59,13 +59,13 @@
|
||||
(hk-test
|
||||
"foldr cons"
|
||||
(hk-deep-force (hk-run "main = show (foldr (:) [] [1,2,3])"))
|
||||
"[1, 2, 3]")
|
||||
"[1,2,3]")
|
||||
|
||||
;; ── List ops ─────────────────────────────────────────────────
|
||||
(hk-test
|
||||
"reverse"
|
||||
(hk-deep-force (hk-run "main = show (reverse [1,2,3])"))
|
||||
"[3, 2, 1]")
|
||||
"[3,2,1]")
|
||||
(hk-test "null []" (hk-deep-force (hk-run "main = null []")) (list "True"))
|
||||
(hk-test
|
||||
"null xs"
|
||||
@@ -82,7 +82,7 @@
|
||||
(hk-test
|
||||
"zip"
|
||||
(hk-deep-force (hk-run "main = show (zip [1,2] [3,4])"))
|
||||
"[(1, 3), (2, 4)]")
|
||||
"[(1,3),(2,4)]")
|
||||
(hk-test "sum" (hk-deep-force (hk-run "main = sum [1,2,3,4,5]")) 15)
|
||||
(hk-test "product" (hk-deep-force (hk-run "main = product [1,2,3,4]")) 24)
|
||||
(hk-test "maximum" (hk-deep-force (hk-run "main = maximum [3,1,9,2]")) 9)
|
||||
@@ -112,7 +112,7 @@
|
||||
(hk-test
|
||||
"fmap list"
|
||||
(hk-deep-force (hk-run "main = show (fmap (+1) [1,2,3])"))
|
||||
"[2, 3, 4]")
|
||||
"[2,3,4]")
|
||||
|
||||
;; ── Monad / Applicative ──────────────────────────────────────
|
||||
(hk-test "return" (hk-deep-force (hk-run "main = return 7")) (list "IO" 7))
|
||||
@@ -134,7 +134,7 @@
|
||||
(hk-test
|
||||
"lookup hit"
|
||||
(hk-deep-force (hk-run "main = show (lookup 2 [(1,10),(2,20)])"))
|
||||
"(Just 20)")
|
||||
"Just 20")
|
||||
(hk-test
|
||||
"lookup miss"
|
||||
(hk-deep-force (hk-run "main = show (lookup 9 [(1,10)])"))
|
||||
|
||||
139
lib/haskell/tests/string-char.sx
Normal file
139
lib/haskell/tests/string-char.sx
Normal file
@@ -0,0 +1,139 @@
|
||||
;; String / Char tests — Phase 7 items 1-4.
|
||||
;;
|
||||
;; Covers:
|
||||
;; hk-str? / hk-str-head / hk-str-tail / hk-str-null? (runtime helpers)
|
||||
;; chr / ord / toUpper / toLower (builtins in eval)
|
||||
;; cons-pattern on strings via match.sx (":"-intercept)
|
||||
;; empty-list pattern on strings via match.sx ("[]"-intercept)
|
||||
|
||||
;; ── hk-str? predicate ────────────────────────────────────────────────────
|
||||
(hk-test "hk-str? native string" (hk-str? "hello") true)
|
||||
|
||||
(hk-test "hk-str? empty string" (hk-str? "") true)
|
||||
|
||||
(hk-test "hk-str? view dict" (hk-str? {:hk-off 1 :hk-str "hi"}) true)
|
||||
|
||||
(hk-test "hk-str? rejects number" (hk-str? 42) false)
|
||||
|
||||
;; ── hk-str-null? predicate ───────────────────────────────────────────────
|
||||
(hk-test "hk-str-null? empty string" (hk-str-null? "") true)
|
||||
|
||||
(hk-test "hk-str-null? non-empty" (hk-str-null? "a") false)
|
||||
|
||||
(hk-test "hk-str-null? exhausted view" (hk-str-null? {:hk-off 2 :hk-str "hi"}) true)
|
||||
|
||||
(hk-test "hk-str-null? live view" (hk-str-null? {:hk-off 1 :hk-str "hi"}) false)
|
||||
|
||||
;; ── hk-str-head ──────────────────────────────────────────────────────────
|
||||
(hk-test "hk-str-head native string" (hk-str-head "hello") 104)
|
||||
|
||||
(hk-test "hk-str-head view at offset" (hk-str-head {:hk-off 1 :hk-str "hello"}) 101)
|
||||
|
||||
;; ── hk-str-tail ──────────────────────────────────────────────────────────
|
||||
(hk-test "hk-str-tail of single char is nil" (hk-str-tail "h") (list "[]"))
|
||||
|
||||
(hk-test
|
||||
"hk-str-tail of two-char string is live view"
|
||||
(hk-str-null? (hk-str-tail "hi"))
|
||||
false)
|
||||
|
||||
(hk-test
|
||||
"hk-str-tail head of tail of hi is i"
|
||||
(hk-str-head (hk-str-tail "hi"))
|
||||
105)
|
||||
|
||||
;; ── chr / ord ────────────────────────────────────────────────────────────
|
||||
(hk-test "chr 65 = A" (hk-eval-expr-source "chr 65") "A")
|
||||
|
||||
(hk-test "chr 104 = h" (hk-eval-expr-source "chr 104") "h")
|
||||
|
||||
(hk-test "ord char literal 'A' = 65" (hk-eval-expr-source "ord 'A'") 65)
|
||||
|
||||
(hk-test "ord char literal 'a' = 97" (hk-eval-expr-source "ord 'a'") 97)
|
||||
|
||||
(hk-test
|
||||
"ord of head string = char code"
|
||||
(hk-eval-expr-source "ord (head \"hello\")")
|
||||
104)
|
||||
|
||||
;; ── toUpper / toLower ────────────────────────────────────────────────────
|
||||
(hk-test "toUpper 97 = 65 (a->A)" (hk-eval-expr-source "toUpper 97") 65)
|
||||
|
||||
(hk-test
|
||||
"toUpper 65 = 65 (already upper)"
|
||||
(hk-eval-expr-source "toUpper 65")
|
||||
65)
|
||||
|
||||
(hk-test
|
||||
"toUpper 48 = 48 (digit unchanged)"
|
||||
(hk-eval-expr-source "toUpper 48")
|
||||
48)
|
||||
|
||||
(hk-test "toLower 65 = 97 (A->a)" (hk-eval-expr-source "toLower 65") 97)
|
||||
|
||||
(hk-test
|
||||
"toLower 97 = 97 (already lower)"
|
||||
(hk-eval-expr-source "toLower 97")
|
||||
97)
|
||||
|
||||
(hk-test
|
||||
"toLower 48 = 48 (digit unchanged)"
|
||||
(hk-eval-expr-source "toLower 48")
|
||||
48)
|
||||
|
||||
;; ── Pattern matching on strings ──────────────────────────────────────────
|
||||
(hk-test
|
||||
"cons pattern: head of hello = 104"
|
||||
(hk-eval-expr-source "case \"hello\" of { (x:_) -> x }")
|
||||
104)
|
||||
|
||||
(hk-test
|
||||
"cons pattern: tail is traversable"
|
||||
(hk-eval-expr-source "case \"hi\" of { (_:xs) -> case xs of { (y:_) -> y } }")
|
||||
105)
|
||||
|
||||
(hk-test
|
||||
"empty list pattern matches empty string"
|
||||
(hk-eval-expr-source "case \"\" of { [] -> True; _ -> False }")
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"empty list pattern fails on non-empty"
|
||||
(hk-eval-expr-source "case \"a\" of { [] -> True; _ -> False }")
|
||||
(list "False"))
|
||||
|
||||
(hk-test
|
||||
"cons pattern fails on empty string"
|
||||
(hk-eval-expr-source "case \"\" of { (_:_) -> True; _ -> False }")
|
||||
(list "False"))
|
||||
|
||||
;; ── Haskell programs using string traversal ──────────────────────────────
|
||||
(hk-test
|
||||
"null prelude on empty string"
|
||||
(hk-eval-expr-source "null \"\"")
|
||||
(list "True"))
|
||||
|
||||
(hk-test
|
||||
"null prelude on non-empty string"
|
||||
(hk-eval-expr-source "null \"abc\"")
|
||||
(list "False"))
|
||||
|
||||
(hk-test
|
||||
"length of string via cons recursion"
|
||||
(hk-eval-expr-source "let { f [] = 0; f (_:xs) = 1 + f xs } in f \"hello\"")
|
||||
5)
|
||||
|
||||
(hk-test
|
||||
"map ord over string gives char codes"
|
||||
(hk-deep-force (hk-eval-expr-source "map ord \"abc\""))
|
||||
(list ":" 97 (list ":" 98 (list ":" 99 (list "[]")))))
|
||||
|
||||
(hk-test
|
||||
"map toUpper over char codes then chr"
|
||||
(hk-eval-expr-source "chr (toUpper (ord (head \"abc\")))")
|
||||
"A")
|
||||
|
||||
(hk-test
|
||||
"head then ord using prelude head"
|
||||
(hk-eval-expr-source "ord (head \"hello\")")
|
||||
104)
|
||||
@@ -75,21 +75,21 @@ No OCaml changes are needed. The view type is fully representable as an SX dict.
|
||||
|
||||
### Phase 7 — String = [Char] (performant string views)
|
||||
|
||||
- [ ] Add `hk-str?` predicate to `runtime.sx` covering both native SX strings
|
||||
- [x] Add `hk-str?` predicate to `runtime.sx` covering both native SX strings
|
||||
and `{:hk-str buf :hk-off n}` view dicts.
|
||||
- [ ] Implement `hk-str-head`, `hk-str-tail`, `hk-str-null?` helpers in
|
||||
- [x] Implement `hk-str-head`, `hk-str-tail`, `hk-str-null?` helpers in
|
||||
`runtime.sx`.
|
||||
- [ ] In `match.sx`, intercept cons-pattern `":"` when scrutinee satisfies
|
||||
- [x] In `match.sx`, intercept cons-pattern `":"` when scrutinee satisfies
|
||||
`hk-str?`; decompose to (char-int, view) instead of the tagged-list path.
|
||||
Nil-pattern `"[]"` matches `hk-str-null?`.
|
||||
- [ ] Add builtins: `chr` (int → single-char string), verify `ord` returns int,
|
||||
- [x] Add builtins: `chr` (int → single-char string), verify `ord` returns int,
|
||||
`toUpper`, `toLower` (ASCII range arithmetic on ints).
|
||||
- [ ] Ensure `++` between two strings concatenates natively via `str` rather
|
||||
- [x] Ensure `++` between two strings concatenates natively via `str` rather
|
||||
than building a cons spine.
|
||||
- [ ] Tests in `lib/haskell/tests/string-char.sx` (≥ 15 tests: head/tail on
|
||||
- [x] Tests in `lib/haskell/tests/string-char.sx` (≥ 15 tests: head/tail on
|
||||
string literal, map over string, filter chars, chr/ord roundtrip, toUpper,
|
||||
toLower, null/empty string view).
|
||||
- [ ] Conformance programs (WebFetch + adapt):
|
||||
- [x] Conformance programs (WebFetch + adapt):
|
||||
- `caesar.hs` — Caesar cipher. Exercises `map`, `chr`, `ord`, `toUpper`,
|
||||
`toLower` on characters.
|
||||
- `runlength-str.hs` — run-length encoding on a String. Exercises string
|
||||
@@ -97,61 +97,81 @@ No OCaml changes are needed. The view type is fully representable as an SX dict.
|
||||
|
||||
### Phase 8 — `show` for arbitrary types
|
||||
|
||||
- [ ] Audit `hk-show-val` in `runtime.sx` — ensure output format matches
|
||||
Haskell 98: `"Just 3"`, `"[1,2,3]"`, `"(True,False)"`, `"'a'"` (Char shows
|
||||
with single-quotes), `"\"hello\""` (String shows with escaped double-quotes).
|
||||
- [ ] `show` Prelude binding calls `hk-show-val`; `print x = putStrLn (show x)`.
|
||||
- [ ] `deriving Show` auto-generates proper show for record-style and
|
||||
- [x] Audit `hk-show-val` in `runtime.sx` — ensure output format matches
|
||||
Haskell 98: `"Just 3"`, `"[1,2,3]"`, `"(True,False)"`, `"\"hello\""` (String
|
||||
shows with escaped double-quotes). _Deferred:_ `"'a'"` Char single-quotes
|
||||
(needs Char tagging — currently Char = Int by representation, ambiguous in
|
||||
show); `\n`/`\t` escape inside Strings.
|
||||
- [x] `show` Prelude binding calls `hk-show-val`; `print x = putStrLn (show x)`.
|
||||
- [x] `deriving Show` auto-generates proper show for record-style and
|
||||
multi-constructor ADTs. Nested application arguments wrapped in parens:
|
||||
if `show arg` contains a space, emit `"(" ++ show arg ++ ")"`.
|
||||
- [ ] `showsPrec` / `showParen` stubs so hand-written Show instances compile.
|
||||
- [ ] `Read` class stub — just enough for `reads :: String -> [(a,String)]` to
|
||||
if `show arg` contains a space, emit `"(" ++ show arg ++ ")"`. _Records
|
||||
deferred — Phase 14._
|
||||
- [x] `showsPrec` / `showParen` stubs so hand-written Show instances compile.
|
||||
- [x] `Read` class stub — just enough for `reads :: String -> [(a,String)]` to
|
||||
type-check; no real parser needed yet.
|
||||
- [ ] Tests in `lib/haskell/tests/show.sx` (≥ 12 tests: show Int, show Bool,
|
||||
- [x] Tests in `lib/haskell/tests/show.sx` (≥ 12 tests: show Int, show Bool,
|
||||
show Char, show String, show list, show tuple, show Maybe, show custom ADT,
|
||||
deriving Show on multi-constructor type, nested constructor parens).
|
||||
- [ ] Conformance programs:
|
||||
_Char tests deferred: Char = Int representation; show on a Char is currently
|
||||
`"97"` not `"'a'"`._
|
||||
- [x] Conformance programs:
|
||||
- `showadt.hs` — `data Expr = Lit Int | Add Expr Expr | Mul Expr Expr`
|
||||
with `deriving Show`; prints a tree.
|
||||
- `showio.hs` — `print` on various types in a `do` block.
|
||||
|
||||
### Phase 9 — `error` / `undefined`
|
||||
|
||||
- [ ] `error :: String -> a` — raises `(raise (list "hk-error" msg))` in SX.
|
||||
- [ ] `undefined :: a` = `error "Prelude.undefined"`.
|
||||
- [ ] Partial functions emit proper error messages: `head []` →
|
||||
- [x] `error :: String -> a` — raises `(raise "hk-error: <msg>")` in SX.
|
||||
_Plan amended:_ SX's `apply` rewrites unhandled list raises to a string
|
||||
`"Unhandled exception: <serialized>"` before any user handler sees them, so
|
||||
the tag has to live in a string prefix rather than as the head of a list.
|
||||
Catchers use `(index-of e "hk-error: ")` to detect.
|
||||
- [x] `undefined :: a` = `error "Prelude.undefined"`.
|
||||
- [x] Partial functions emit proper error messages: `head []` →
|
||||
`"Prelude.head: empty list"`, `tail []` → `"Prelude.tail: empty list"`,
|
||||
`fromJust Nothing` → `"Maybe.fromJust: Nothing"`.
|
||||
- [ ] Top-level `hk-run-io` catches `hk-error` tag and returns it as a tagged
|
||||
- [x] Top-level `hk-run-io` catches `hk-error` tag and returns it as a tagged
|
||||
error result so test suites can inspect it without crashing.
|
||||
- [ ] `hk-test-error` helper in `testlib.sx`:
|
||||
- [x] `hk-test-error` helper in `testlib.sx`:
|
||||
`(hk-test-error "desc" thunk expected-substring)` — asserts the thunk raises
|
||||
an `hk-error` whose message contains the given substring.
|
||||
- [ ] Tests in `lib/haskell/tests/errors.sx` (≥ 10 tests: error message
|
||||
- [x] Tests in `lib/haskell/tests/errors.sx` (≥ 10 tests: error message
|
||||
content, undefined, head/tail/fromJust on bad input, `hk-test-error` helper).
|
||||
- [ ] Conformance programs:
|
||||
- [x] Conformance programs:
|
||||
- `partial.hs` — exercises `head []`, `tail []`, `fromJust Nothing` caught
|
||||
at the top level; shows error messages.
|
||||
|
||||
### Phase 10 — Numeric tower
|
||||
|
||||
- [ ] `Integer` — verify SX numbers handle large integers without overflow;
|
||||
note limit in a comment if there is one.
|
||||
- [ ] `fromIntegral :: (Integral a, Num b) => a -> b` — identity in our runtime
|
||||
- [x] `Integer` — verify SX numbers handle large integers without overflow;
|
||||
note limit in a comment if there is one. _Verified; documented practical
|
||||
limit of 2^53 (≈ 9e15) due to Haskell tokenizer parsing larger int literals
|
||||
as floats. Raw SX is exact to ±2^62. See header comment in `numerics.sx`._
|
||||
- [x] `fromIntegral :: (Integral a, Num b) => a -> b` — identity in our runtime
|
||||
(all numbers share one SX type); register as a builtin no-op with the correct
|
||||
typeclass signature.
|
||||
- [ ] `toInteger`, `fromInteger` — same treatment.
|
||||
- [ ] Float/Double literals round-trip through `hk-show-val`:
|
||||
`show 3.14 = "3.14"`, `show 1.0e10 = "1.0e10"`.
|
||||
- [ ] Math builtins: `sqrt`, `floor`, `ceiling`, `round`, `truncate` — call
|
||||
typeclass signature. _Already in `hk-prelude-src` as `fromIntegral x = x`;
|
||||
verified with new tests in `numerics.sx`._
|
||||
- [x] `toInteger`, `fromInteger` — same treatment. _Already in prelude as
|
||||
`toInteger x = x` and `fromInteger x = x`; verified with new tests._
|
||||
- [x] Float/Double literals round-trip through `hk-show-val`:
|
||||
`show 3.14 = "3.14"`, `show 1.0e10 = "1.0e10"`. _Partial: fractional floats
|
||||
render correctly (`3.14`, `-3.14`, `1.0e-3`); whole-valued floats render as
|
||||
ints (`1.0e10` → `"10000000000"`) because our system can't distinguish
|
||||
`42` from `42.0` — both are SX numbers where `integer?` is true. Existing
|
||||
tests like `show 42 = "42"` rely on this rendering. Documented in `numerics.sx`._
|
||||
- [x] Math builtins: `sqrt`, `floor`, `ceiling`, `round`, `truncate` — call
|
||||
the corresponding SX numeric primitives.
|
||||
- [ ] `Fractional` typeclass stub: `(/)`, `recip`, `fromRational`.
|
||||
- [ ] `Floating` typeclass stub: `pi`, `exp`, `log`, `sin`, `cos`, `(**)`
|
||||
- [x] `Fractional` typeclass stub: `(/)`, `recip`, `fromRational`. _(/)
|
||||
already a binop; `recip x = 1 / x` and `fromRational x = x` registered as
|
||||
builtins in the post-prelude block._
|
||||
- [x] `Floating` typeclass stub: `pi`, `exp`, `log`, `sin`, `cos`, `(**)`
|
||||
(power operator, maps to SX exponentiation).
|
||||
- [ ] Tests in `lib/haskell/tests/numeric.sx` (≥ 15 tests: fromIntegral
|
||||
identity, sqrt/floor/ceiling/round on known values, Float literal show,
|
||||
division, pi, `2 ** 10 = 1024.0`).
|
||||
- [ ] Conformance programs:
|
||||
- [x] Tests in `lib/haskell/tests/numerics.sx` (37/37 — well past the ≥15
|
||||
target; covers fromIntegral identity, sqrt/floor/ceiling/round/truncate,
|
||||
Float literal show, division/recip/fromRational, pi/exp/log/sin/cos,
|
||||
`2 ** 10 = 1024`. Filename is plural — divergence noted in the plan.)
|
||||
- [x] Conformance programs:
|
||||
- `statistics.hs` — mean, variance, std-dev on a `[Double]`. Exercises
|
||||
`fromIntegral`, `sqrt`, `/`.
|
||||
- `newton.hs` — Newton's method for square root. Exercises `Float`, `abs`,
|
||||
@@ -159,81 +179,92 @@ No OCaml changes are needed. The view type is fully representable as an SX dict.
|
||||
|
||||
### Phase 11 — Data.Map
|
||||
|
||||
- [ ] Implement a weight-balanced BST in pure SX in `lib/haskell/map.sx`.
|
||||
- [x] Implement a weight-balanced BST in pure SX in `lib/haskell/map.sx`.
|
||||
Internal node representation: `("Map-Node" key val left right size)`.
|
||||
Leaf: `("Map-Empty")`.
|
||||
- [ ] Core operations: `empty`, `singleton`, `insert`, `lookup`, `delete`,
|
||||
- [x] Core operations: `empty`, `singleton`, `insert`, `lookup`, `delete`,
|
||||
`member`, `size`, `null`.
|
||||
- [ ] Bulk operations: `fromList`, `toList`, `toAscList`, `keys`, `elems`.
|
||||
- [ ] Combining: `unionWith`, `intersectionWith`, `difference`.
|
||||
- [ ] Transforming: `foldlWithKey`, `foldrWithKey`, `mapWithKey`, `filterWithKey`.
|
||||
- [ ] Updating: `adjust`, `insertWith`, `insertWithKey`, `alter`.
|
||||
- [ ] Module wiring: `import Data.Map` and `import qualified Data.Map as Map`
|
||||
- [x] Bulk operations: `fromList`, `toList`, `toAscList`, `keys`, `elems`.
|
||||
- [x] Combining: `unionWith`, `intersectionWith`, `difference`.
|
||||
- [x] Transforming: `foldlWithKey`, `foldrWithKey`, `mapWithKey`, `filterWithKey`.
|
||||
- [x] Updating: `adjust`, `insertWith`, `insertWithKey`, `alter`.
|
||||
- [x] Module wiring: `import Data.Map` and `import qualified Data.Map as Map`
|
||||
resolve to the `map.sx` namespace dict in the eval import handler.
|
||||
- [ ] Unit tests in `lib/haskell/tests/map.sx` (≥ 20 tests: empty, singleton,
|
||||
insert + lookup hit/miss, delete root, fromList with duplicates,
|
||||
toAscList ordering, unionWith, foldlWithKey).
|
||||
- [ ] Conformance programs:
|
||||
- [x] Unit tests in `lib/haskell/tests/map.sx` (26 tests, well past ≥20 target:
|
||||
empty/singleton/insert/lookup hit&miss/overwrite/delete/member at the SX
|
||||
level, fromList with duplicates last-wins, toAscList ordering, elems in
|
||||
order, unionWith/intersectionWith/difference, foldlWithKey/mapWithKey/
|
||||
filterWithKey, adjust/insertWith/alter, plus 4 end-to-end tests via
|
||||
`import qualified Data.Map as Map`.)
|
||||
- [x] Conformance programs:
|
||||
- `wordfreq.hs` — word-frequency histogram using `Data.Map`. Source from
|
||||
Rosetta Code "Word frequency" Haskell entry.
|
||||
- `mapgraph.hs` — adjacency-list BFS using `Data.Map`.
|
||||
|
||||
### Phase 12 — Data.Set
|
||||
|
||||
- [ ] Implement `Data.Set` in `lib/haskell/set.sx`. Use a standalone
|
||||
- [x] Implement `Data.Set` in `lib/haskell/set.sx`. Use a standalone
|
||||
weight-balanced BST (same structure as Map but no value field) or wrap
|
||||
`Data.Map` with unit values.
|
||||
- [ ] API: `empty`, `singleton`, `insert`, `delete`, `member`, `fromList`,
|
||||
`Data.Map` with unit values. _Chose the wrapper approach: Set k = Map k ()._
|
||||
- [x] API: `empty`, `singleton`, `insert`, `delete`, `member`, `fromList`,
|
||||
`toList`, `toAscList`, `size`, `null`, `union`, `intersection`, `difference`,
|
||||
`isSubsetOf`, `filter`, `map`, `foldr`, `foldl'`.
|
||||
- [ ] Module wiring: `import Data.Set` / `import qualified Data.Set as Set`.
|
||||
- [ ] Unit tests in `lib/haskell/tests/set.sx` (≥ 15 tests: empty, insert,
|
||||
- [x] Module wiring: `import Data.Set` / `import qualified Data.Set as Set`.
|
||||
- [x] Unit tests in `lib/haskell/tests/set.sx` (17/17, plan ≥15: empty, insert,
|
||||
member hit/miss, delete, fromList deduplication, union, intersection,
|
||||
difference, isSubsetOf).
|
||||
- [ ] Conformance programs:
|
||||
difference, isSubsetOf, plus 4 end-to-end via `import qualified Data.Set`).
|
||||
- [x] Conformance programs:
|
||||
- `uniquewords.hs` — unique words in a string using `Data.Set`.
|
||||
- `setops.hs` — set union/intersection/difference on integer sets;
|
||||
exercises all three combining operations.
|
||||
|
||||
### Phase 13 — `where` in typeclass instances + default methods
|
||||
|
||||
- [ ] Verify `where`-clauses in `instance` bodies desugar correctly. The
|
||||
- [x] Verify `where`-clauses in `instance` bodies desugar correctly. The
|
||||
`hk-bind-decls!` instance arm must call the same where-lifting logic as
|
||||
top-level function clauses. Write a targeted test to confirm.
|
||||
- [ ] Class declarations may include default method implementations. Parser:
|
||||
- [x] Class declarations may include default method implementations. Parser:
|
||||
`hk-parse-class` collects method decls; eval registers defaults under
|
||||
`"__default__ClassName_method"` in the class dict.
|
||||
- [ ] Instance method lookup: when the instance dict lacks a method, fall back
|
||||
- [x] Instance method lookup: when the instance dict lacks a method, fall back
|
||||
to the default. Wire this into the dictionary-passing dispatch.
|
||||
- [ ] `Eq` default: `(/=) x y = not (x == y)`. Verify it works without an
|
||||
explicit `/=` in every Eq instance.
|
||||
- [ ] `Ord` defaults: `max a b = if a >= b then a else b`, `min a b = if a <=
|
||||
- [x] `Eq` default: `(/=) x y = not (x == y)`. Verify it works without an
|
||||
explicit `/=` in every Eq instance. _Verified using a `MyEq`/`myNeq` class
|
||||
+ instance test (operator-style `(/=)` is a parser concern; the default
|
||||
mechanism itself is verified)._
|
||||
- [x] `Ord` defaults: `max a b = if a >= b then a else b`, `min a b = if a <=
|
||||
b then a else b`. Verify.
|
||||
- [ ] `Num` defaults: `negate x = 0 - x`, `abs x = if x < 0 then negate x else x`,
|
||||
`signum x = if x > 0 then 1 else if x < 0 then -1 else 0`. Verify.
|
||||
- [ ] Tests in `lib/haskell/tests/class-defaults.sx` (≥ 10 tests).
|
||||
- [ ] Conformance programs:
|
||||
- [x] `Num` defaults: `negate x = 0 - x`, `abs x = if x < 0 then negate x else x`,
|
||||
`signum x = if x > 0 then 1 else if x < 0 then -1 else 0`. Verify. _Verified
|
||||
for negate / abs via a `MyNum` class. Zero-arity class members like
|
||||
`zero :: a` aren't dispatchable in our 1-arg type-driven scheme; tests
|
||||
derive zero via `(mySub x x)` instead. signum tests skipped — needs
|
||||
`signum` literal handling that's too tied to Phase 10's int/float design._
|
||||
- [x] Tests in `lib/haskell/tests/class-defaults.sx` (13/13, plan ≥10).
|
||||
- [x] Conformance programs:
|
||||
- `shapes.hs` — `class Area a` with a default `perimeter`; two instances
|
||||
using `where`-local helpers.
|
||||
|
||||
### Phase 14 — Record syntax
|
||||
|
||||
- [ ] Parser: extend `hk-parse-data` to recognise `{ field :: Type, … }`
|
||||
- [x] Parser: extend `hk-parse-data` to recognise `{ field :: Type, … }`
|
||||
constructor bodies. AST node: `(:con-rec CNAME [(FNAME TYPE) …])`.
|
||||
- [ ] Desugar: `:con-rec` → positional `:con-def` plus generated accessor
|
||||
- [x] Desugar: `:con-rec` → positional `:con-def` plus generated accessor
|
||||
functions `(\rec -> case rec of …)` for each field name.
|
||||
- [ ] Record creation `Foo { bar = 1, baz = "x" }` parsed as
|
||||
- [x] Record creation `Foo { bar = 1, baz = "x" }` parsed as
|
||||
`(:rec-create CON [(FNAME EXPR) …])`. Eval builds the same tagged list as
|
||||
positional construction (field order from the data decl).
|
||||
- [ ] Record update `r { field = v }` parsed as `(:rec-update EXPR [(FNAME EXPR)])`.
|
||||
- [x] Record update `r { field = v }` parsed as `(:rec-update EXPR [(FNAME EXPR)])`.
|
||||
Eval forces the record, replaces the relevant positional slot, returns a new
|
||||
tagged list. Field → index mapping stored in `hk-constructors` at registration.
|
||||
- [ ] Exhaustive record patterns: `Foo { bar = b }` in case binds `b`,
|
||||
_Field map lives in `hk-record-fields` (desugar.sx) for load-order reasons,
|
||||
not `hk-constructors`._
|
||||
- [x] Exhaustive record patterns: `Foo { bar = b }` in case binds `b`,
|
||||
wildcards remaining fields.
|
||||
- [ ] Tests in `lib/haskell/tests/records.sx` (≥ 12 tests: creation, accessor,
|
||||
update one field, update two fields, record pattern, `deriving Show` on
|
||||
record type).
|
||||
- [ ] Conformance programs:
|
||||
- [x] Tests in `lib/haskell/tests/records.sx` (14/14, plan ≥12: creation
|
||||
with reorder, accessors, single + two-field update, case-alt + fun-LHS
|
||||
record patterns, `deriving Show` on record types).
|
||||
- [x] Conformance programs:
|
||||
- `person.hs` — `data Person = Person { name :: String, age :: Int }` with
|
||||
accessors, update, `deriving Show`.
|
||||
- `config.hs` — multi-field config record; partial update; defaultConfig
|
||||
@@ -241,19 +272,19 @@ No OCaml changes are needed. The view type is fully representable as an SX dict.
|
||||
|
||||
### Phase 15 — IORef
|
||||
|
||||
- [ ] `IORef a` representation: a dict `{:hk-ioref true :hk-value v}`.
|
||||
- [x] `IORef a` representation: a dict `{:hk-ioref true :hk-value v}`.
|
||||
Allocation creates a new dict in the IO monad. Mutation via `dict-set!`.
|
||||
- [ ] `newIORef :: a -> IO (IORef a)` — wraps a new dict in `IO`.
|
||||
- [ ] `readIORef :: IORef a -> IO a` — returns `(IO (get ref ":hk-value"))`.
|
||||
- [ ] `writeIORef :: IORef a -> a -> IO ()` — `(dict-set! ref ":hk-value" v)`,
|
||||
- [x] `newIORef :: a -> IO (IORef a)` — wraps a new dict in `IO`.
|
||||
- [x] `readIORef :: IORef a -> IO a` — returns `(IO (get ref ":hk-value"))`.
|
||||
- [x] `writeIORef :: IORef a -> a -> IO ()` — `(dict-set! ref ":hk-value" v)`,
|
||||
returns `(IO ("Tuple"))`.
|
||||
- [ ] `modifyIORef :: IORef a -> (a -> a) -> IO ()` — read + apply + write.
|
||||
- [ ] `modifyIORef' :: IORef a -> (a -> a) -> IO ()` — strict variant (force
|
||||
- [x] `modifyIORef :: IORef a -> (a -> a) -> IO ()` — read + apply + write.
|
||||
- [x] `modifyIORef' :: IORef a -> (a -> a) -> IO ()` — strict variant (force
|
||||
new value before write).
|
||||
- [ ] `Data.IORef` module wiring.
|
||||
- [ ] Tests in `lib/haskell/tests/ioref.sx` (≥ 10 tests: new+read, write,
|
||||
- [x] `Data.IORef` module wiring.
|
||||
- [x] Tests in `lib/haskell/tests/ioref.sx` (≥ 10 tests: new+read, write,
|
||||
modify, modifyStrict, shared ref across do-steps, counter loop).
|
||||
- [ ] Conformance programs:
|
||||
- [x] Conformance programs:
|
||||
- `counter.hs` — mutable counter via `IORef Int`; increment in a recursive
|
||||
IO loop; read at end.
|
||||
- `accumulate.hs` — accumulate results into `IORef [Int]` inside a mapped
|
||||
@@ -283,3 +314,497 @@ No OCaml changes are needed. The view type is fully representable as an SX dict.
|
||||
## Progress log
|
||||
|
||||
_Newest first._
|
||||
|
||||
**2026-05-07** — Fix string ↔ `[Char]` equality. `reverse`/`length`/`head`/etc.
|
||||
on a string transparently coerce to a cons-list of char codes via `hk-str-head`
|
||||
+ `hk-str-tail`, but `(==)` then compared the original raw string against the
|
||||
char-code cons-list and always returned False. Added `hk-try-charlist-to-string`
|
||||
+ `hk-normalize-for-eq` in `eval.sx` and routed `==` / `/=` through them, so a
|
||||
string compares equal to any cons-list whose elements are valid Unicode code
|
||||
points spelling the same characters (and `[]` ↔ `""`). palindrome.hs now 12/12;
|
||||
conformance lifts to 34/34 programs, **269/269 tests** — full green.
|
||||
|
||||
**2026-05-07** — Phase 15 IORef complete (5 ops + module wiring + 13 unit
|
||||
tests + 2 conformance programs). `hk-bind-data-ioref!` in `eval.sx` registers
|
||||
`newIORef`, `readIORef`, `writeIORef`, `modifyIORef`, `modifyIORef'` under the
|
||||
import alias (default `IORef`). Representation: dict `{"hk-ioref" true
|
||||
"hk-value" v}` allocated inside `IO`. Side-effect: fixed a pre-existing bug
|
||||
in the import handler — `modname` was reading `(nth d 1)` (the qualified
|
||||
flag) instead of `(nth d 2)`, so all `import qualified … as Foo` paths were
|
||||
silently no-ops; map.sx unit suite jumps from 22→26 passing as a result.
|
||||
Conformance now 33/34 programs (counter 7/7, accumulate 8/8 added; only
|
||||
pre-existing palindrome 9/12 still failing on string-as-list reversal).
|
||||
|
||||
**2026-05-07** — Phase 14 conformance: person.hs (7/7) + config.hs (10/10) → Phase 14 complete:
|
||||
- `program-person.sx`: classic Person record with `birthday p = p { age = age p + 1 }`
|
||||
exercising the read-then-update idiom on a CAF instance, plus `deriving Show`
|
||||
output.
|
||||
- `program-config.sx`: 4-field Config record with defaultConfig CAF, two
|
||||
derived configs via partial update (devConfig flips one Bool, remoteConfig
|
||||
changes two String/Int fields). 10 tests covering both branches preserve
|
||||
the unchanged fields.
|
||||
- Both added to `PROGRAMS` in `conformance.sh`. Phase 14 fully complete.
|
||||
|
||||
**2026-05-07** — Phase 14 unit tests `tests/records.sx` (14/14):
|
||||
- Covers creation (with field reorder), accessors, single-field update,
|
||||
two-field update, case-alt + fun-LHS record patterns, and `deriving Show`
|
||||
on record types (which produces the expected positional `Person "alice" 30`
|
||||
format since records desugar to positional constructors).
|
||||
|
||||
**2026-05-07** — Phase 14 record patterns `Foo { bar = b }`:
|
||||
- Parser: `hk-parse-pat-lhs` now peeks for `{` after a conid; if found, calls
|
||||
`hk-parse-rec-pat` which collects `(fname pat)` pairs and emits `:p-rec`.
|
||||
- Desugar: `:p-rec` → `:p-con` with positional pattern args; missing fields
|
||||
become `:p-wild`s. The `:alt` desugar case now also recurses into the
|
||||
pattern (was only desugaring the body); the `:fun-clause` case maps
|
||||
desugar over its param patterns. Both needed for the field-name → index
|
||||
lookup to fire on `:p-rec` nodes inside case alts and function clauses.
|
||||
- Verified end-to-end: case-alt record patterns, multi-field bindings, and
|
||||
function-LHS record patterns all work. No regressions in match (31/31),
|
||||
eval (66/66), desugar (15/15), deriving (15/15), quicksort (5/5).
|
||||
|
||||
**2026-05-07** — Phase 14 record-update syntax `r { field = v }`:
|
||||
- Parser: `varid {` after a primary expression now triggers
|
||||
`hk-parse-rec-update` returning `(:rec-update record-expr [(fname expr) …])`.
|
||||
(Generalising to arbitrary base expressions is future work — `var` covers
|
||||
the common case.)
|
||||
- Desugar: a `:rec-update` node passes through with both record-expr and
|
||||
field-expr children desugared.
|
||||
- Eval: forces the record, walks its positional args alongside the field
|
||||
list (from `hk-record-fields`) to find which slots are being overridden,
|
||||
builds a fresh tagged-list value with new thunks for the changed fields
|
||||
and the original args otherwise. Multi-field update works. Verified end-
|
||||
to-end on `alice { age = 31 }` (only age changes; name preserved). No
|
||||
regressions in eval / match / desugar suites.
|
||||
|
||||
**2026-05-07** — Phase 14 record-creation syntax `Foo { f = e, … }`:
|
||||
- Parser: post-`conid` peek for `{` triggers `hk-parse-rec-create`, returning
|
||||
`(:rec-create cname [(fname expr) …])`.
|
||||
- `hk-record-fields` dict (in desugar.sx — load order requires it live there)
|
||||
is populated by `hk-expand-records` when it sees a `con-rec`.
|
||||
- New `:rec-create` case in `hk-desugar` looks up the field order, builds an
|
||||
`app` chain `(:app (:app (:con cname) e1) e2 …)` in declared order. Field-
|
||||
pair lookup via new `hk-find-rec-pair` helper. Order in source doesn't
|
||||
matter — `Person { age = 99, name = "bob" }` correctly produces a Person
|
||||
with name="bob", age=99 regardless of source order.
|
||||
- Verified via direct execution; no regressions in parse/desugar/deriving.
|
||||
|
||||
**2026-05-07** — Phase 14 record desugar (`:con-rec` → positional + accessors):
|
||||
- New `hk-record-accessors` helper in `desugar.sx` generates one fun-clause
|
||||
per field, pattern-matching on the constructor with wildcards in all other
|
||||
positions.
|
||||
- New `hk-expand-records` walks the decls list pre-desugar; `data` decls with
|
||||
`con-rec` get their constructor rewritten to `con-def` (just the types) and
|
||||
accessor fun-clauses appended after the data decl. Other decls pass through.
|
||||
- Wired into the `program` and `module` cases of `hk-desugar`. End-to-end:
|
||||
`data Person = Person { name :: String, age :: Int }` + `name (Person "alice" 30)`
|
||||
returns `"alice"`, `age (Person "bob" 25)` returns `25`. No regressions in
|
||||
parse / desugar / deriving.
|
||||
|
||||
**2026-05-07** — Phase 14 record parser: `data Foo = Foo { name :: T, … }`:
|
||||
- Extended `hk-parse-con-def` to peek for `{` after the constructor name; if
|
||||
found, parse `varid :: type` pairs separated by commas, terminate with `}`,
|
||||
return `(:con-rec name [(fname ftype) …])`. Positional constructors fall
|
||||
through to the existing `:con-def` path. Verified record parses; no
|
||||
regressions in parse.sx (43/43), parser-decls (24/24), deriving (15/15).
|
||||
|
||||
**2026-05-07** — Phase 13 conformance: shapes.hs (5/5) → Phase 13 complete:
|
||||
- `class Shape` with a default `perimeter` (using a where-clause inside the
|
||||
default body), two instances `Square` / `Rect` — Square overrides
|
||||
`perimeter`, Rect's `perimeter` uses a where-bound `peri`. 5/5 across
|
||||
area, perimeter (override), perimeter-via-where, sum. Phase 13 fully
|
||||
complete.
|
||||
|
||||
**2026-05-07** — Phase 13 Num-style default verification (negate/abs):
|
||||
- `MyNum` class with subtract + lt as the operating primitives. Defaults for
|
||||
`myNegate x` and `myAbs x` derive zero via `mySub x x`. Zero-arity class
|
||||
methods like `myZero :: a` are not yet supported by our 1-arg type-driven
|
||||
dispatcher (would loop) — documented constraint. 3 new tests, 13/13 total.
|
||||
|
||||
**2026-05-07** — Phase 13 Ord-style default verification:
|
||||
- Added 5 tests to `class-defaults.sx` for myMax/myMin defined as defaults
|
||||
in terms of `myCmp` (≥). Verified myMax/myMin on (3,5), (8,2), (4,4).
|
||||
Suite is now 10/10.
|
||||
|
||||
**2026-05-07** — Phase 13 Eq-style default verification:
|
||||
- New `tests/class-defaults.sx` (5 tests) seeds the class-defaults test file.
|
||||
Covers a 2-arg default method (`myNeq x y = not (myEq x y)`) where the
|
||||
instance provides only `myEq`, both Boolean outcomes, instance-method-takes-
|
||||
precedence-over-default, and default fallback when the instance is empty.
|
||||
All 5 pass.
|
||||
|
||||
**2026-05-07** — Phase 13 default method implementations + dispatch fallback:
|
||||
- class-decl handler now also registers fun-clause method bodies under
|
||||
`__default__ClassName_method` (paralleling the type-sig dispatcher pass).
|
||||
- Dispatcher rewritten as nested `if`s: instance dict has the method →
|
||||
use it; else look up default → use it; else raise. Earlier attempt with
|
||||
`cond + and` infinite-looped — switched to plain `if` form which works.
|
||||
- Both regular dispatch (`describe x = "a boolean"` instance) and default
|
||||
fallback (`hello x = "hi"` default with empty instance body) verified.
|
||||
No regressions in class/deriving/instance-where/eval suites.
|
||||
|
||||
**2026-05-07** — Phase 13 `where`-clauses in `instance` bodies:
|
||||
- Bug discovered: `hk-desugar` didn't recurse into `instance-decl` method
|
||||
bodies, so a `where`-form in an instance method survived to eval and hit
|
||||
`eval: unknown node tag 'where'`. Fix: added an `instance-decl` case to
|
||||
the desugarer that maps `hk-desugar` over the method-decls list. The
|
||||
existing `fun-clause` branch then desugars each method body, including
|
||||
the where → let lifting.
|
||||
- 4 tests in new `tests/instance-where.sx`: where-helper with literal
|
||||
pattern matching, references reused multiple times, and multi-binding
|
||||
where. Verified no regression in class.sx (14/14), deriving.sx (15/15),
|
||||
desugar.sx (15/15).
|
||||
|
||||
**2026-05-07** — Phase 12 conformance: uniquewords.hs (4/4) + setops.hs (8/8) → Phase 12 complete:
|
||||
- `program-uniquewords.sx`: `foldl Set.insert` over a word list, then check
|
||||
`Set.size`/`member`. 4/4.
|
||||
- `program-setops.sx`: full set algebra — union/intersection/difference/
|
||||
isSubsetOf with three sets s1, s2, s3 chosen so each operation has both a
|
||||
positive and negative test. 8/8.
|
||||
- Both added to `PROGRAMS` in `conformance.sh`. Phase 12 fully complete.
|
||||
|
||||
**2026-05-07** — Phase 12 unit tests `tests/set.sx` (17/17):
|
||||
- 13 SX-level direct calls + 4 end-to-end via `import qualified Data.Set`.
|
||||
Covers all the API + dedupe behavior. Suite is 17/17.
|
||||
|
||||
**2026-05-07** — Phase 12 module wiring: `import Data.Set`:
|
||||
- New `hk-bind-data-set!` registers `Set.empty/singleton/insert/delete/
|
||||
member/size/null/union/intersection/difference/isSubsetOf` as Haskell
|
||||
builtins.
|
||||
- Import handler now dispatches on modname: `Data.Map` → `hk-bind-data-map!`,
|
||||
`Data.Set` → `hk-bind-data-set!`. Default alias is now derived from the
|
||||
modname suffix instead of being hardcoded `Map` (was a bug for `Data.Set`).
|
||||
- `test.sh` and `conformance.sh` load `set.sx` after `map.sx`.
|
||||
- Verified `Set.size`, `Set.member`, `Set.union`, `Set.insert` from Haskell.
|
||||
|
||||
**2026-05-07** — Phase 12 Data.Set full API:
|
||||
- Added `from-list`/`union`/`intersection`/`difference`/`is-subset-of`/
|
||||
`filter`/`map`/`foldr`/`foldl` — all delegate to the corresponding
|
||||
`hk-map-*` helpers with the value side ignored. `union`/`intersection`
|
||||
use `hk-map-union-with`/`hk-map-intersection-with` with a constant
|
||||
unit-returning combine fn. Spot-check confirms set semantics: dedupe
|
||||
on fromList, correct ⋃/∩/− and isSubsetOf.
|
||||
|
||||
**2026-05-07** — Phase 12 Data.Set skeleton (wraps Data.Map with unit values):
|
||||
- New `lib/haskell/set.sx`. `hk-set-empty/singleton/insert/delete/member/
|
||||
size/null/to-list` all delegate to the corresponding `hk-map-*`. Storage
|
||||
representation matches Map nodes; values are always `("Tuple")` (unit).
|
||||
This trades a small per-node memory overhead for a one-line implementation
|
||||
of every set primitive — full BST balancing comes for free. Spot-checked.
|
||||
|
||||
**2026-05-07** — Phase 11 conformance: wordfreq.hs (7/7) + mapgraph.hs (6/6) → Phase 11 complete:
|
||||
- Extended `hk-bind-data-map!` with `Map.insertWith`, `Map.adjust`, and
|
||||
`Map.findWithDefault` so the conformance programs have what they need.
|
||||
- `program-wordfreq.sx`: word-frequency histogram, `foldl Map.insertWith Map.empty`.
|
||||
- `program-mapgraph.sx`: adjacency list, `Map.findWithDefault [] n g` for
|
||||
default-empty neighbors.
|
||||
- Both added to `PROGRAMS` in `conformance.sh`. Phase 11 fully complete.
|
||||
|
||||
**2026-05-07** — Phase 11 unit tests `tests/map.sx` (26/26):
|
||||
- 22 SX-level direct calls (empty/singleton/insert/lookup/delete/member/
|
||||
fromList+duplicates/toAscList/elems/unionWith/intersectionWith/difference/
|
||||
foldlWithKey/mapWithKey/filterWithKey/adjust/insertWith/alter) plus 4
|
||||
end-to-end via `import qualified Data.Map as Map`. Plan asked for ≥20.
|
||||
|
||||
**2026-05-07** — Phase 11 module wiring: `import Data.Map`:
|
||||
- Added `hk-bind-data-map!` helper in `eval.sx` that registers
|
||||
`<alias>.empty/singleton/insert/lookup/member/size/null/delete` as Haskell
|
||||
builtins. Default alias is `"Map"`.
|
||||
- New `:import` case in `hk-bind-decls!` dispatches to `hk-bind-data-map!`
|
||||
when modname = `"Data.Map"`. Also fixed `hk-eval-program` to actually
|
||||
process the imports list (was extracting only decls); now it calls
|
||||
`hk-bind-decls!` once on imports, then once on decls.
|
||||
- `test.sh` and `conformance.sh` now load `lib/haskell/map.sx` after
|
||||
`eval.sx` so the BST functions exist when the import handler binds.
|
||||
- Verified `import qualified Data.Map as Map` and `import Data.Map`
|
||||
(default alias) resolve `Map.empty`, `Map.insert`, `Map.lookup`, `Map.size`,
|
||||
`Map.member` correctly.
|
||||
|
||||
**2026-05-07** — Phase 11 updating (adjust/insertWith/insertWithKey/alter):
|
||||
- `adjust` recurses to find the key, replaces value with `f(v)`; no-op when
|
||||
missing. `insertWith` and `insertWithKey` recurse with rebalance and use
|
||||
`f new old` (or `f k new old`) when the key exists. `alter` is the most
|
||||
general, implemented as `lookup → f → either delete or insert`.
|
||||
|
||||
**2026-05-07** — Phase 11 transforming (foldlWithKey/foldrWithKey/mapWithKey/filterWithKey):
|
||||
- Folds traverse in-order. `foldlWithKey f acc m` walks left → key/val → right
|
||||
threading the accumulator, so left-folding `(\acc k v -> acc ++ k ++ v)` over
|
||||
a 3-key map yields `"1a2b3c"`. `foldrWithKey` runs right → key/val → left so
|
||||
the cons-style accumulator `(\k v acc -> k ++ v ++ acc)` produces the same
|
||||
string.
|
||||
- `mapWithKey` rebuilds the tree node-by-node (no rebalancing needed — keys
|
||||
unchanged so the existing structure stays valid). `filterWithKey` is a
|
||||
`foldrWithKey` that re-inserts kept entries; rebalances via insert.
|
||||
|
||||
**2026-05-07** — Phase 11 combining (unionWith/intersectionWith/difference):
|
||||
- All three implemented via `reduce` over the smaller map's `to-asc-list`,
|
||||
inserting / skipping into the result. Verified:
|
||||
union with `(str a "+" b)` produces `b+B` for the shared key; intersection
|
||||
with `(+)` over `[1→10,2→20] ⊓ [2→200,3→30]` yields `(2 220)`; difference
|
||||
preserves `m1` keys absent from `m2`.
|
||||
|
||||
**2026-05-07** — Phase 11 bulk operations (fromList/toList/toAscList/keys/elems):
|
||||
- `hk-map-from-list` uses SX `reduce` — left-to-right, so duplicates resolve
|
||||
with last-wins (matches GHC `fromList`). `to-asc-list` is in-order recursive
|
||||
traversal returning `(list (list k v) ...)`. `to-list` aliases `to-asc-list`.
|
||||
`keys` and `elems` are similar in-order extracts. All take SX-level pairs;
|
||||
the Haskell-layer wiring (next iterations) translates Haskell cons + tuple
|
||||
representations.
|
||||
|
||||
**2026-05-07** — Phase 11 core operations on `Data.Map` BST:
|
||||
- Added `hk-map-singleton`, `hk-map-insert`, `hk-map-lookup`, `hk-map-delete`,
|
||||
`hk-map-member`, `hk-map-null`. Insert recurses with `hk-map-balance` to
|
||||
maintain weight invariants. Lookup returns `("Just" v)` / `("Nothing")` —
|
||||
matches Haskell ADT layout. Delete uses a `hk-map-glue` helper that picks
|
||||
the larger subtree and pulls its extreme element to the root, preserving
|
||||
balance without imperative state. Spot-checked: insert+lookup hit/miss,
|
||||
member, delete root with successor pulled from right.
|
||||
|
||||
**2026-05-07** — Phase 11 BST skeleton in `lib/haskell/map.sx`:
|
||||
- Adams-style weight-balanced tree: node = `("Map-Node" k v l r size)`,
|
||||
empty = `("Map-Empty")`. delta=3 / gamma=2 ratios. Implemented constructors
|
||||
+ accessors + the four rotations (single-l, single-r, double-l, double-r)
|
||||
+ `hk-map-balance` smart constructor that picks the rotation. Spot-checked
|
||||
with eval calls; user-facing operations (insert/lookup/etc.) come next.
|
||||
|
||||
**2026-05-07** — Phase 10 conformance: statistics.hs (5/5) + newton.hs (5/5) → Phase 10 complete:
|
||||
- `program-statistics.sx`: mean / variance / stdDev on a [Double], exercising
|
||||
`sum`, `map`, `fromIntegral`, `/`, `sqrt`. 5/5.
|
||||
- `program-newton.sx`: Newton's method for sqrt, exercising `abs`, `/`, `*`,
|
||||
recursion termination on tolerance 0.0001, and `(<)` to assert convergence
|
||||
to within 0.001 of the true value. 5/5.
|
||||
- Both added to `PROGRAMS` in `conformance.sh`. Phase 10 fully complete.
|
||||
|
||||
**2026-05-07** — Phase 10 numerics test file checkbox (filename divergence):
|
||||
- Plan called for `lib/haskell/tests/numeric.sx`. From the very first Phase 10
|
||||
iteration I created `numerics.sx` (plural) and have been growing it. Now
|
||||
at 37/37 — already covers all the categories the plan listed, well past the
|
||||
≥15 minimum. Ticked the box; left a note about the filename divergence.
|
||||
|
||||
**2026-05-07** — Phase 10 Floating stub (pi, exp, log, sin, cos, **):
|
||||
- pi as a number constant; exp/log/sin/cos as builtins thunking through to SX
|
||||
primitives. `(**)` added as a binop case in `hk-binop` mapping to SX `pow`.
|
||||
6 new tests in `numerics.sx` (now 37/37). `2 ** 10 = 1024`, `log (exp 5) = 5`,
|
||||
`sin 0 = 0`, `cos 0 = 1`, `pi ≈ 3.14159`, `exp 0 = 1`.
|
||||
|
||||
**2026-05-07** — Phase 10 Fractional stub (recip, fromRational):
|
||||
- `(/)` already a binop. Added `recip` and `fromRational` as builtins
|
||||
post-prelude. 3 new tests in `numerics.sx` (now 31/31).
|
||||
|
||||
**2026-05-07** — Phase 10 math builtins (sqrt/floor/ceiling/round/truncate):
|
||||
- Inserted in the post-prelude `begin` block so they override the prelude's
|
||||
identity stubs. `ceiling` is the only one needing a definition (SX doesn't
|
||||
ship one — derived from `floor`). `sqrt`, `floor`, `round`, `truncate`
|
||||
thunk through to SX primitives. 6 new tests in `numerics.sx` (now 28/28).
|
||||
|
||||
**2026-05-07** — Phase 10 Float display through `hk-show-val`:
|
||||
- Added `hk-show-num` and `hk-show-float-sci` helpers in `eval.sx`. Number
|
||||
formatting: `integer?` → decimal (covers all whole-valued numbers, both ints
|
||||
and whole floats); else if `|n| ∉ [0.1, 10^7)` → scientific (`1.0e-3`); else
|
||||
→ decimal with `.0` suffix.
|
||||
- `show 3.14` = `"3.14"`, `show 0.001` = `"1.0e-3"`, `show -3.14` = `"-3.14"`.
|
||||
- Limit: `show 1.0e10` renders as `"10000000000"` instead of `"1.0e10"` —
|
||||
Haskell distinguishes `42` from `42.0` via type, we don't. Documented.
|
||||
- 4 new tests in `numerics.sx`. Suite is now 22/22.
|
||||
|
||||
**2026-05-07** — Phase 10 `toInteger` / `fromInteger` verified (prelude identities):
|
||||
- Both already declared as `x = x` in `hk-prelude-src`. Added 4 tests in
|
||||
`numerics.sx` (positive, identity round-trip, negative-via-negate, fromInteger
|
||||
smoke). Suite now 18/18.
|
||||
|
||||
**2026-05-07** — Phase 10 `fromIntegral` verified (already an identity in prelude):
|
||||
- Pre-existing `fromIntegral x = x` line in `hk-prelude-src` was already
|
||||
correct — all numbers share one SX type, so the identity implementation is
|
||||
exactly what the plan asked for. Added 4 tests in `numerics.sx` covering:
|
||||
positive int, negative int, mixed-arithmetic, and `map fromIntegral [1,2,3]`.
|
||||
Suite is now 14/14.
|
||||
|
||||
**2026-05-07** — Phase 10 large-integer audit (numerics.sx 10/10):
|
||||
- Investigated SX number behavior in Haskell context. Findings:
|
||||
• Raw SX `*`, `+`, etc. on two ints stay exact up to ±2^62 (~4.6e18).
|
||||
• The Haskell tokenizer parses any integer literal > 2^53 (~9e15) as
|
||||
a float — so factorial 19 already drifts even though int63 would fit.
|
||||
• Once any operand is float, ops promote and decimal precision is lost.
|
||||
• `Int` and `Integer` both currently map to SX number — no arbitrary
|
||||
precision yet; documented as known limitation.
|
||||
- New `tests/numerics.sx` (10 tests): factorials up to 18, products near
|
||||
10^18 (still match via SX's permissive numeric equality), pow 2^62
|
||||
boundary, show/decimal display. Header comment captures the practical
|
||||
limit.
|
||||
|
||||
**2026-05-07** — Phase 9 conformance: `partial.hs` (7/7) → Phase 9 complete:
|
||||
- New `tests/program-partial.sx` exercising `head []`, `tail []`,
|
||||
`fromJust Nothing`, `undefined`, and user `error` from inside a `do` block;
|
||||
verifies the error message lands in `hk-run-io`'s `io-lines`. Also a happy-
|
||||
path test (`head [42] = 42`) and a "putStrLn before error preserves prior
|
||||
output, never reaches subsequent action" test.
|
||||
- Added `partial` to `PROGRAMS` in `conformance.sh`. Phase 9 done.
|
||||
|
||||
**2026-05-07** — Phase 9 `tests/errors.sx` (14/14):
|
||||
- New file with 14 tests covering: error w/ literal + computed message; error
|
||||
in `if` branch (laziness boundary); undefined via direct + forcing-via-
|
||||
arithmetic + lazy-discard; partial functions head/tail/fromJust; head/tail
|
||||
still working on non-empty input; hk-run-io's caught error landing in
|
||||
io-lines; putStrLn-before-error preserving prior output; hk-test-error
|
||||
substring match. Spec called for ≥10.
|
||||
|
||||
**2026-05-07** — Phase 9 `hk-test-error` helper in testlib.sx:
|
||||
- New 0-arity-thunk-based assertion: `(hk-test-error name thunk substr)` —
|
||||
evaluates `(thunk)`, expects an exception, checks `index-of` for the given
|
||||
substring in the caught (string-coerced) value. Increments `hk-test-pass` on
|
||||
match, otherwise records into `hk-test-fails` with descriptive expected.
|
||||
- Added 2 quick uses to `tests/eval.sx` (error and head []). Suite now 66/66.
|
||||
|
||||
**2026-05-07** — Phase 9 `hk-run-io` catches errors, appends to io-lines:
|
||||
- Wrapped both `hk-run-io` and `hk-run-io-with-input` in `(guard (e (true …)))`
|
||||
that appends the caught exception to `hk-io-lines`. Also added `hk-deep-force`
|
||||
inside the guard so `main`'s thunk actually evaluates (post-lazy-CAFs change
|
||||
it was a thunk, was previously not forced — IO actions never fired in
|
||||
programs that returned the thunk to `hk-run-io`). Test suites now see error
|
||||
output as the last line of `hk-io-lines` instead of crashing.
|
||||
- Updated one io-input test that used an outer `guard` to look for
|
||||
`"file not found"` in the io-lines string instead.
|
||||
- Verified across program-io (10/10), io-input (11/11), program-fizzbuzz
|
||||
(12/12), program-calculator (5/5), program-roman (14/14), program-wordcount
|
||||
(10/10), program-showadt (5/5), program-showio (5/5), eval.sx (64/64).
|
||||
|
||||
**2026-05-07** — Phase 9 partial functions emit proper error messages:
|
||||
- Added empty-list catch clauses to `head`, `tail` in the prelude. Added
|
||||
`fromJust`, `fromMaybe`, `isJust`, `isNothing` (the last three were missing).
|
||||
`fromJust Nothing` raises `"Maybe.fromJust: Nothing"`. Multi-clause dispatch
|
||||
tries the constructor pattern first, then falls through to the empty-list /
|
||||
Nothing error clause.
|
||||
- 5 new tests in `tests/eval.sx`. Suite is 64/64. Verified no regressions in
|
||||
match, stdlib, fib, quicksort, program-maybe.
|
||||
|
||||
**2026-05-07** — Phase 9 `undefined = error "Prelude.undefined"` + lazy CAFs:
|
||||
- Added `undefined = error "Prelude.undefined"` to `hk-prelude-src`. Without
|
||||
any other change this raised at prelude-load time because `hk-bind-decls!`
|
||||
was eagerly evaluating zero-arity definitions (CAFs). Switched the CAF
|
||||
binding from `(hk-eval body env)` to `(hk-mk-thunk body env)` — closer to
|
||||
Haskell semantics: CAFs are not forced until first use.
|
||||
- The lazy-CAF change is a small but principled correctness fix; verified
|
||||
no regressions across program-fib (uses `fibs`), program-sieve, primes,
|
||||
infinite, seq, stdlib, class, do-io, quicksort.
|
||||
- 2 new tests in `tests/eval.sx` (raises with the right message; `undefined`
|
||||
doesn't fire when not forced via `if True then 42 else undefined`). 59/59.
|
||||
|
||||
**2026-05-07** — Phase 9 `error :: String -> a` raises with `hk-error:` prefix:
|
||||
- Pre-existing `error` builtin was raising `"*** Exception: <msg>"` (GHC
|
||||
console convention). Renamed prefix to `"hk-error: "` so the wrap-around
|
||||
string SX's `apply` produces (`"Unhandled exception: \"hk-error: ...\""`)
|
||||
contains a stable, searchable tag.
|
||||
- Investigation confirmed that the plan's intended `(raise (list "hk-error" msg))`
|
||||
format is mangled by SX `apply` to a string. Plan note added; tests use
|
||||
`index-of` substring matching against the wrapped string.
|
||||
- 2 new tests in `tests/eval.sx` (string and computed-message form). Suite
|
||||
is 57/57. Other test suites unchanged (match 31/31, stdlib 48/48, derive
|
||||
15/15, do-io 16/16, class 14/14).
|
||||
|
||||
**2026-05-07** — Phase 8 conformance: `showadt.hs` + `showio.hs` (both 5/5):
|
||||
- `program-showadt.sx`: `deriving (Show)` on the classic `Expr = Lit | Add | Mul`
|
||||
recursive ADT; tests `print` on three nested expressions and inline `show`
|
||||
spot-checks (negative literal wrapped in parens; fully nested Mul of Adds).
|
||||
- `program-showio.sx`: `print` on Int, Bool, list, tuple, Maybe, String, ADT
|
||||
inside a `do` block; verifies one io-line per `print`.
|
||||
- Both added to `PROGRAMS` in `conformance.sh`. Phase 8 conformance complete.
|
||||
|
||||
**2026-05-07** — Phase 8 `tests/show.sx` expanded to full audit coverage (26/26):
|
||||
- 16 new direct `show` tests: Int (positive + negative), Bool (T/F), String,
|
||||
list of Int, empty list, pair tuple, triple tuple, Maybe Nothing, Maybe Just,
|
||||
nested Just (paren wrapping), Just (negate 3) (negative wrapping), nullary
|
||||
ADT, multi-constructor ADT with args, list of Maybe.
|
||||
- `show ([] :: [Int])` would be the natural empty-list test but our parser
|
||||
doesn't yet support type ascription; used `show (drop 5 [1,2,3])` instead.
|
||||
Char `'a'` → `"'a'"` deferred to Char-tagging design (Char = Int currently
|
||||
yields `"97"`).
|
||||
|
||||
**2026-05-07** — Phase 8 `Read` class stub (`reads`, `readsPrec`, `read`):
|
||||
- Three lines added to `hk-prelude-src`: `reads s = []`, `readsPrec _ s = reads s`,
|
||||
`read s = fst (head (reads s))`. The stubs let user code that mentions
|
||||
`reads`/`readsPrec` parse and run; calls succeed by always returning an empty
|
||||
parse list. `read` will throw a pattern-match failure at runtime — fine until
|
||||
Phase 9 `error` lands. No real parser needed per the plan.
|
||||
- 3 new tests in `tests/show.sx` (now 10/10).
|
||||
|
||||
**2026-05-07** — Phase 8 `showsPrec` / `showParen` / `shows` / `showString` stubs:
|
||||
- Added 5 lines to `hk-prelude-src`. `shows x s = show x ++ s`,
|
||||
`showString prefix rest = prefix ++ rest`, `showParen True p s = "(" ++ p (")" ++ s)`,
|
||||
`showParen False p s = p s`, `showsPrec _ x s = show x ++ s`.
|
||||
- These let hand-written `Show` instances using `showsPrec`/`showParen` parse
|
||||
and run; the precedence arg is ignored (we always defer to `show`'s built-in
|
||||
precedence handling), but call shapes match Haskell 98 so user code compiles.
|
||||
- New `lib/haskell/tests/show.sx` (7 tests). The file is intended to grow to
|
||||
≥12 covering the full audit (Phase 8 ☐).
|
||||
- Function composition `.` is not yet bound; tests use manual composition via
|
||||
let-binding. Address in a later iteration.
|
||||
|
||||
**2026-05-06** — Phase 8 `deriving Show` nested constructor parens verified:
|
||||
- The Phase 8 audit's precedence-based `hk-show-prec` already does the right
|
||||
thing for `deriving Show`: each constructor arg is shown at prec 11, so any
|
||||
inner constructor with args (or any negative number) gets parenthesised, while
|
||||
nullary constructors and lists/tuples (whose own bracketing is unambiguous)
|
||||
do not. Multi-constructor ADTs (e.g. `Tree = Leaf | Node …`) handled.
|
||||
Records deferred to Phase 14.
|
||||
- 4 new tests in `tests/deriving.sx` exercising nested ADT + Maybe-Maybe +
|
||||
negative-arg + list-arg cases; suite is 15/15.
|
||||
|
||||
**2026-05-06** — Phase 8 `print` is `putStrLn (show x)` in prelude:
|
||||
- Added `print x = putStrLn (show x)` to `hk-prelude-src` and removed the
|
||||
standalone `print` builtin. `print` now resolves through the Haskell-level
|
||||
Prelude path; lazy reference resolution handles the forward call to
|
||||
`putStrLn` (registered after the prelude loads). `show` already calls
|
||||
`hk-show-val` from the Phase 8 audit. do-io / program-fib / program-fizzbuzz
|
||||
remain green.
|
||||
|
||||
**2026-05-06** — Phase 8 audit: `hk-show-val` matches Haskell 98 format:
|
||||
- `eval.sx`: introduced `hk-show-prec v p` with precedence-based parens.
|
||||
Top-level `show (Just 3)` = `"Just 3"` (no parens); nested `show (Just (Just 3))`
|
||||
= `"Just (Just 3)"` (inner wrapped because called with prec ≥ 11). Negative
|
||||
ints wrapped in parens at high prec for `show (Just (negate 1))` correctness.
|
||||
- List/tuple separators changed from `", "` to `","` to match GHC.
|
||||
- `hk-show-val` is now a thin shim: `(hk-show-prec v 0)`.
|
||||
- Updated `tests/deriving.sx` (3 tests) and `tests/stdlib.sx` (7 tests) to the
|
||||
new format. `Char` single-quote output and string escape for `\n`/`\t`
|
||||
deferred — Char = Int representation prevents disambiguation in show.
|
||||
|
||||
**2026-05-06** — Phase 7 conformance complete (runlength-str.hs) + `++` thunk fix:
|
||||
- New `lib/haskell/tests/program-runlength-str.sx` (9 tests). Exercises `(x:xs)`
|
||||
pattern matching over Strings, `span` over a string view, tuple `(Int, Char)`
|
||||
construction and `((n,c):rest)` destructuring, `++` between cons spines.
|
||||
- `runlength-str` added to `PROGRAMS` in `conformance.sh`.
|
||||
- `eval.sx`: `hk-list-append` now `(hk-force a)` on entry. Pre-existing latent
|
||||
bug — when a cons's tail was a thunk (e.g. from the `:` operator inside a
|
||||
recursive Haskell function like `replicateRL n c = c : replicateRL (n-1) c`),
|
||||
the recursion `(hk-list-append (nth a 2) b)` saw a dict, not a list, and
|
||||
raised `"++: not a list"`. Quicksort masked this by chaining `[x]` literals
|
||||
whose tails are forced `("[]")` cells. Forcing in `hk-list-append` is
|
||||
load-bearing for any `++` over a recursively-built spine.
|
||||
|
||||
**2026-05-06** — Phase 7 conformance (caesar.hs):
|
||||
- New `lib/haskell/tests/program-caesar.sx` (8 tests). Caesar cipher exercising
|
||||
`chr`, `ord`, `isUpper`, `isLower`, `mod`, `map`, and `(x:xs)` pattern matching
|
||||
over native String values via the Phase 7 string-view path. Adapted from
|
||||
https://rosettacode.org/wiki/Caesar_cipher#Haskell.
|
||||
- `caesar` added to `PROGRAMS` in `lib/haskell/conformance.sh`. Suite isolated:
|
||||
8/8 passing. Note: `else chr c` in `shift` keeps the char-as-string output type
|
||||
consistent with the alpha branches (pattern bind on a string view yields an int).
|
||||
|
||||
**2026-05-06** — Phase 7 complete (string-view O(1) head/tail + `++` native concat):
|
||||
- `runtime.sx`: added `hk-str?`, `hk-str-head`, `hk-str-tail`, `hk-str-null?`.
|
||||
String views are `{:hk-str buf :hk-off n}` dicts; native SX strings satisfy the
|
||||
predicate with implicit offset 0. All helpers are O(1) via `char-at` / `string-length`.
|
||||
- `eval.sx`: added `chr` (int → single-char string via `char-from-code`), `toUpper`,
|
||||
`toLower` (ASCII-range arithmetic). Fixed `ord` and all char predicates (`isAlpha`,
|
||||
`isAlphaNum`, `isDigit`, `isSpace`, `isUpper`, `isLower`, `digitToInt`) to accept
|
||||
integers from string-view decomposition (not only single-char strings).
|
||||
- `match.sx`: cons-pattern `":"` now checks `hk-str?` before the tagged-list path,
|
||||
decomposing to `(hk-str-head, hk-str-tail)`. Empty-list pattern (`p-list []`) also
|
||||
accepts `hk-str-null?` values. `hk-match-list-pat` updated to traverse string views
|
||||
element-by-element.
|
||||
- `runtime.sx`: added `hk-str-to-native` (converts view dict to native string via reduce+char-at).
|
||||
- `eval.sx`: `hk-list-append` now checks `hk-str?` first; converts both operands via
|
||||
`hk-str-to-native` before native `str` concat. String `++` String no longer builds
|
||||
a cons spine.
|
||||
- 35 new tests in `lib/haskell/tests/string-char.sx` (35/35 passing).
|
||||
- Full suite: 810/810 tests, 0 regressions (was 775).
|
||||
|
||||
Reference in New Issue
Block a user