diff --git a/lib/haskell/eval.sx b/lib/haskell/eval.sx index 84fead3b..16750e1c 100644 --- a/lib/haskell/eval.sx +++ b/lib/haskell/eval.sx @@ -452,6 +452,48 @@ ((= step 0) (hk-mk-nil)) (:else (hk-mk-cons from (hk-build-range (+ from step) to step)))))) +(define + hk-try-charlist-to-string + (fn + (v) + (let + ((acc "") (ok true) (cur v)) + (begin + (define + hk-tcs-loop + (fn + () + (when + ok + (cond + ((not (list? cur)) (set! ok false)) + ((empty? cur) (set! ok false)) + ((= (first cur) "[]") nil) + ((= (first cur) ":") + (let + ((h (hk-deep-force (nth cur 1)))) + (cond + ((and (number? h) (>= h 0) (<= h 1114111)) + (begin + (set! acc (str acc (char-from-code h))) + (set! cur (hk-deep-force (nth cur 2))) + (hk-tcs-loop))) + (:else (set! ok false))))) + (:else (set! ok false)))))) + (hk-tcs-loop) + (if ok acc nil))))) + +(define + hk-normalize-for-eq + (fn + (v) + (cond + ((string? v) v) + ((and (list? v) (not (empty? v)) (= (first v) ":")) + (let ((s (hk-try-charlist-to-string v))) (if (nil? s) v s))) + ((and (list? v) (not (empty? v)) (= (first v) "[]")) "") + (:else v)))) + (define hk-binop (fn @@ -461,9 +503,17 @@ ((= op "-") (- lv rv)) ((= op "*") (* lv rv)) ((= op "/") (/ lv rv)) - ((= op "==") (hk-of-bool (= (hk-deep-force lv) (hk-deep-force rv)))) + ((= op "==") + (hk-of-bool + (= + (hk-normalize-for-eq (hk-deep-force lv)) + (hk-normalize-for-eq (hk-deep-force rv))))) ((= op "/=") - (hk-of-bool (not (= (hk-deep-force lv) (hk-deep-force rv))))) + (hk-of-bool + (not + (= + (hk-normalize-for-eq (hk-deep-force lv)) + (hk-normalize-for-eq (hk-deep-force rv)))))) ((= op "<") (hk-of-bool (< lv rv))) ((= op "<=") (hk-of-bool (<= lv rv))) ((= op ">") (hk-of-bool (> lv rv))) @@ -489,6 +539,10 @@ (raise "(>>): left side is not an IO action"))) (:else (raise (str "unknown operator: " op)))))) +;; ── Top-level program evaluation ──────────────────────────── +;; Operator-as-value built-ins — let `(+)`, `(*)`, etc. work as +;; first-class functions for `zipWith (+)` and friends. Strict in +;; both args (built-ins are forced via hk-apply-builtin). (define hk-eval-sect-left (fn @@ -503,6 +557,10 @@ (list :op op (list :var "__hk-sect-l") (list :var "__hk-sect-x")) cenv))))) +;; Inline Prelude source — loaded into the initial env so simple +;; programs can use `head`, `take`, `repeat`, etc. without each +;; user file redefining them. The Prelude itself uses lazy `:` for +;; the recursive list-building functions. (define hk-eval-sect-right (fn @@ -517,20 +575,12 @@ (list :op op (list :var "__hk-sect-x") (list :var "__hk-sect-r")) cenv))))) -;; ── Top-level program evaluation ──────────────────────────── -;; Operator-as-value built-ins — let `(+)`, `(*)`, etc. work as -;; first-class functions for `zipWith (+)` and friends. Strict in -;; both args (built-ins are forced via hk-apply-builtin). (define hk-make-binop-builtin (fn (name op-name) (hk-mk-builtin name (fn (a b) (hk-binop op-name a b)) 2))) -;; Inline Prelude source — loaded into the initial env so simple -;; programs can use `head`, `take`, `repeat`, etc. without each -;; user file redefining them. The Prelude itself uses lazy `:` for -;; the recursive list-building functions. (define hk-prelude-src "head (x:_) = x\nhead [] = error \"Prelude.head: empty list\"\ntail (_:xs) = xs\ntail [] = error \"Prelude.tail: empty list\"\nfst (a, _) = a\nsnd (_, b) = b\ntake 0 _ = []\ntake _ [] = []\ntake n (x:xs) = x : take (n - 1) xs\ndrop 0 xs = xs\ndrop _ [] = []\ndrop n (_:xs) = drop (n - 1) xs\nrepeat x = x : repeat x\niterate f x = x : iterate f (f x)\nlength [] = 0\nlength (_:xs) = 1 + length xs\nmap _ [] = []\nmap f (x:xs) = f x : map f xs\nfilter _ [] = []\nfilter p (x:xs) = if p x then x : filter p xs else filter p xs\nzipWith _ [] _ = []\nzipWith _ _ [] = []\nzipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys\nfibs = 0 : 1 : zipWith plus fibs (tail fibs)\nplus a b = a + b\nconcat [] = []\nconcat (xs:xss) = xs ++ concat xss\nconcatMap f [] = []\nconcatMap f (x:xs) = f x ++ concatMap f xs\nabs x = if x < 0 then 0 - x else x\nnegate x = 0 - x\nnull [] = True\nnull _ = False\nflip f x y = f y x\nconst x _ = x\nid x = x\ncurry f x y = f (x, y)\nuncurry f p = f (fst p) (snd p)\nfoldr f z [] = z\nfoldr f z (x:xs) = f x (foldr f z xs)\nfoldl f z [] = z\nfoldl f z (x:xs) = foldl f (f z x) xs\nfoldl1 f (x:xs) = foldl f x xs\nfoldr1 f [x] = x\nfoldr1 f (x:xs) = f x (foldr1 f xs)\nzip [] _ = []\nzip _ [] = []\nzip (x:xs) (y:ys) = (x, y) : zip xs ys\nreverse [] = []\nreverse (x:xs) = reverse xs ++ [x]\nelem _ [] = False\nelem x (y:ys) = if x == y then True else elem x ys\nnotElem x xs = not (elem x xs)\nany _ [] = False\nany f (x:xs) = if f x then True else any f xs\nall _ [] = True\nall f (x:xs) = if f x then all f xs else False\nand [] = True\nand (x:xs) = if x then and xs else False\nor [] = False\nor (x:xs) = if x then True else or xs\nsum [] = 0\nsum (x:xs) = x + sum xs\nproduct [] = 1\nproduct (x:xs) = x * product xs\nmaximum [x] = x\nmaximum (x:xs) = let m = maximum xs in if x >= m then x else m\nminimum [x] = x\nminimum (x:xs) = let m = minimum xs in if x <= m then x else m\ncompare x y = if x < y then LT else if x == y then EQ else GT\nmin x y = if x <= y then x else y\nmax x y = if x >= y then x else y\nsignum x = if x < 0 then negate 1 else if x == 0 then 0 else 1\nfromIntegral x = x\nfromInteger x = x\ntoInteger x = x\nceiling x = x\nfloor x = x\nround x = x\ntruncate x = x\nlookup _ [] = Nothing\nlookup k ((k2,v):rest) = if k == k2 then Just v else lookup k rest\nmaybe d _ Nothing = d\nmaybe _ f (Just x) = f x\neither f _ (Left x) = f x\neither _ g (Right y) = g y\nmapMaybe _ [] = []\nmapMaybe f (x:xs) = case f x of { Nothing -> mapMaybe f xs; Just y -> y : mapMaybe f xs }\nfromJust (Just x) = x\nfromJust Nothing = error \"Maybe.fromJust: Nothing\"\nfromMaybe d Nothing = d\nfromMaybe _ (Just x) = x\nisJust (Just _) = True\nisJust Nothing = False\nisNothing Nothing = True\nisNothing (Just _) = False\nfmap = map\npure = return\nwhen b m = if b then m else return ()\nunless b m = if b then return () else m\nmapM_ _ [] = return ()\nmapM_ f (x:xs) = f x >> mapM_ f xs\nsequence_ [] = return ()\nsequence_ (m:ms) = m >> sequence_ ms\ninteractApply f s = putStr (f s)\ninteract f = getContents >>= interactApply f\nnub [] = []\nnub (x:xs) = x : nub (filter notEqX xs)\n where notEqX y = y /= x\nsort [] = []\nsort (x:xs) = sort (filter ltX xs) ++ [x] ++ sort (filter geX xs)\n where ltX y = y < x\n geX y = y >= x\nsortBy _ [] = []\nsortBy cmp (x:xs) = sortBy cmp smaller ++ [x] ++ sortBy cmp bigger\n where smaller = filter ltCmp xs\n bigger = filter geCmp xs\n ltCmp y = cmp y x /= GT\n geCmp y = cmp y x == GT\nsortOnCmpFst p1 p2 = compare (fst p1) (fst p2)\nsortOn f xs = map snd (sortBy sortOnCmpFst (zip (map f xs) xs))\nsplitAt 0 xs = ([], xs)\nsplitAt _ [] = ([], [])\nsplitAt n (x:xs) = (x : a, b) where (a, b) = splitAt (n - 1) xs\nspan _ [] = ([], [])\nspan p (x:xs) = if p x then (x : a, b) else ([], x : xs) where (a, b) = span p xs\nbreak p xs = span notP xs\n where notP y = not (p y)\npartition _ [] = ([], [])\npartition p (x:xs) = if p x then (x : a, b) else (a, x : b) where (a, b) = partition p xs\nunzip [] = ([], [])\nunzip ((a, b) : rest) = (a : as, b : bs) where (as, bs) = unzip rest\ntails [] = [[]]\ntails (x:xs) = (x:xs) : tails xs\ninits [] = [[]]\ninits (x:xs) = [] : map (x:) (inits xs)\nisPrefixOf [] _ = True\nisPrefixOf _ [] = False\nisPrefixOf (x:xs) (y:ys) = if x == y then isPrefixOf xs ys else False\nisSuffixOf xs ys = isPrefixOf (reverse xs) (reverse ys)\nisInfixOf [] _ = True\nisInfixOf _ [] = False\nisInfixOf xs ys = if isPrefixOf xs ys then True else isInfixOf xs (tail ys)\nintercalate _ [] = []\nintercalate _ [x] = x\nintercalate sep (x:xs) = x ++ sep ++ intercalate sep xs\nintersperse _ [] = []\nintersperse _ [x] = [x]\nintersperse sep (x:xs) = x : sep : intersperse sep xs\nunwords [] = \"\"\nunwords [w] = w\nunwords (w:ws) = w ++ \" \" ++ unwords ws\nunlines [] = \"\"\nunlines (l:ls) = l ++ \"\\n\" ++ unlines ls\nprint x = putStrLn (show x)\nshows x s = show x ++ s\nshowString prefix rest = prefix ++ rest\nshowParen True p s = \"(\" ++ p (\")\" ++ s)\nshowParen False p s = p s\nshowsPrec _ x s = show x ++ s\nreads s = []\nreadsPrec _ s = reads s\nread s = fst (head (reads s))\nundefined = error \"Prelude.undefined\"\n") @@ -559,6 +609,7 @@ (for-each (fn (s) (set! acc (str acc sep s))) (rest strs)) acc))))) +;; ── Source-level convenience ──────────────────────────────── (define hk-collect-hk-list (fn @@ -570,6 +621,8 @@ (loop v) result)))) +;; Eagerly build the Prelude env once at load time; each call to +;; hk-eval-expr-source copies it instead of re-parsing the whole Prelude. (define hk-show-num (fn @@ -586,7 +639,6 @@ ((s (str n))) (if (>= (index-of s ".") 0) s (str s ".0")))))))))) -;; ── Source-level convenience ──────────────────────────────── (define hk-show-float-sci (fn @@ -620,8 +672,6 @@ "e" e))))))) -;; Eagerly build the Prelude env once at load time; each call to -;; hk-eval-expr-source copies it instead of re-parsing the whole Prelude. (define hk-show-prec (fn diff --git a/lib/haskell/scoreboard.json b/lib/haskell/scoreboard.json index 61573d6e..1c9a4dea 100644 --- a/lib/haskell/scoreboard.json +++ b/lib/haskell/scoreboard.json @@ -1,7 +1,7 @@ { "date": "2026-05-07", - "total_pass": 266, - "total_fail": 3, + "total_pass": 269, + "total_fail": 0, "programs": { "fib": {"pass": 2, "fail": 0}, "sieve": {"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": 9, "fail": 3}, + "palindrome": {"pass": 12, "fail": 0}, "maybe": {"pass": 12, "fail": 0}, "fizzbuzz": {"pass": 12, "fail": 0}, "anagram": {"pass": 9, "fail": 0}, diff --git a/lib/haskell/scoreboard.md b/lib/haskell/scoreboard.md index 4b656ca9..3ce93d52 100644 --- a/lib/haskell/scoreboard.md +++ b/lib/haskell/scoreboard.md @@ -10,7 +10,7 @@ Updated 2026-05-07 · Phase 6 (prelude extras + 18 programs) | nqueens.hs | 2/2 | ✓ | | calculator.hs | 5/5 | ✓ | | collatz.hs | 11/11 | ✓ | -| palindrome.hs | 9/12 | ✗ | +| palindrome.hs | 12/12 | ✓ | | maybe.hs | 12/12 | ✓ | | fizzbuzz.hs | 12/12 | ✓ | | anagram.hs | 9/9 | ✓ | @@ -38,4 +38,4 @@ Updated 2026-05-07 · Phase 6 (prelude extras + 18 programs) | config.hs | 10/10 | ✓ | | counter.hs | 7/7 | ✓ | | accumulate.hs | 8/8 | ✓ | -| **Total** | **266/269** | **33/34 programs** | +| **Total** | **269/269** | **34/34 programs** | diff --git a/plans/haskell-completeness.md b/plans/haskell-completeness.md index fe95ea50..5e7d78b6 100644 --- a/plans/haskell-completeness.md +++ b/plans/haskell-completeness.md @@ -315,6 +315,15 @@ No OCaml changes are needed. The view type is fully representable as an SX dict. _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