haskell: Phase 6 prelude extras (635/635)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled

- hk-list-append: string ++ string via str (fixes unwords/unlines/intercalate)
- --sx-to-hk-- in words/lines builtins: use ":"/"[]" not "Cons"/"Nil"
- lines builtin: empty-string case returns ("[]") not ("Nil")
- New test file prelude-extra.sx: 47 tests covering ord, isAlpha/isDigit/
  isSpace/isUpper/isLower/isAlphaNum, digitToInt, words, lines, unwords,
  unlines, sort, nub, splitAt, span, break, partition, intercalate,
  intersperse, isPrefixOf, isSuffixOf, isInfixOf

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 15:51:12 +00:00
parent 041cb9f3ef
commit 6bfb7b19f4
2 changed files with 391 additions and 2 deletions

View File

@@ -436,6 +436,7 @@
((and (list? a) (= (first a) "[]")) b) ((and (list? a) (= (first a) "[]")) b)
((and (list? a) (= (first a) ":")) ((and (list? a) (= (first a) ":"))
(hk-mk-cons (nth a 1) (hk-list-append (nth a 2) b))) (hk-mk-cons (nth a 1) (hk-list-append (nth a 2) b)))
((string? a) (str a b))
(:else (raise "++: not a list"))))) (:else (raise "++: not a list")))))
;; Eager finite-range spine — handles [from..to] and [from,next..to]. ;; Eager finite-range spine — handles [from..to] and [from,next..to].
@@ -540,7 +541,7 @@
;; the recursive list-building functions. ;; the recursive list-building functions.
(define (define
hk-prelude-src hk-prelude-src
"head (x:_) = x\ntail (_:xs) = xs\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\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 }\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\ninteract f = getContents >>= \\s -> putStr (f s)\n") "head (x:_) = x\ntail (_:xs) = xs\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 }\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\n")
(define (define
hk-load-into! hk-load-into!
@@ -791,7 +792,161 @@
(dict-set! hk-vfs (hk-force path) (hk-force contents)) (dict-set! hk-vfs (hk-force path) (hk-force contents))
(list "IO" (list "Tuple")))) (list "IO" (list "Tuple"))))
2)) 2))
env)))) (let
((--sx-to-hk-- (fn (lst) (if (empty? lst) (list "[]") (list ":" (first lst) (--sx-to-hk-- (rest lst))))))
(--words--
(fn
(s n i start acc)
(if
(>= i n)
(let
((w (substr s start (- n start))))
(reverse (if (= (len w) 0) acc (cons w acc))))
(let
((c (char-code (nth s i))))
(if
(or (= c 32) (= c 9) (= c 10) (= c 13))
(if
(= i start)
(--words-- s n (+ i 1) (+ i 1) acc)
(--words--
s
n
(+ i 1)
(+ i 1)
(cons (substr s start (- i start)) acc)))
(--words-- s n (+ i 1) start acc))))))
(--lines--
(fn
(s n i start acc)
(if
(>= i n)
(if
(= start n)
(reverse acc)
(reverse (cons (substr s start (- n start)) acc)))
(let
((c (char-code (nth s i))))
(if
(= c 10)
(--lines--
s
n
(+ i 1)
(+ i 1)
(cons (substr s start (- i start)) acc))
(--lines-- s n (+ i 1) start acc)))))))
(dict-set!
env
"ord"
(hk-mk-builtin "ord" (fn (c) (char-code (hk-force c))) 1))
(dict-set!
env
"isAlpha"
(hk-mk-builtin
"isAlpha"
(fn
(c)
(let
((code (char-code (hk-force c))))
(hk-of-bool
(or
(and (>= code 65) (<= code 90))
(and (>= code 97) (<= code 122))))))
1))
(dict-set!
env
"isAlphaNum"
(hk-mk-builtin
"isAlphaNum"
(fn
(c)
(let
((code (char-code (hk-force c))))
(hk-of-bool
(or
(and (>= code 65) (<= code 90))
(and (>= code 97) (<= code 122))
(and (>= code 48) (<= code 57))))))
1))
(dict-set!
env
"isDigit"
(hk-mk-builtin
"isDigit"
(fn
(c)
(let
((code (char-code (hk-force c))))
(hk-of-bool (and (>= code 48) (<= code 57)))))
1))
(dict-set!
env
"isSpace"
(hk-mk-builtin
"isSpace"
(fn
(c)
(let
((code (char-code (hk-force c))))
(hk-of-bool
(or (= code 32) (= code 9) (= code 10) (= code 13)))))
1))
(dict-set!
env
"isUpper"
(hk-mk-builtin
"isUpper"
(fn
(c)
(let
((code (char-code (hk-force c))))
(hk-of-bool (and (>= code 65) (<= code 90)))))
1))
(dict-set!
env
"isLower"
(hk-mk-builtin
"isLower"
(fn
(c)
(let
((code (char-code (hk-force c))))
(hk-of-bool (and (>= code 97) (<= code 122)))))
1))
(dict-set!
env
"digitToInt"
(hk-mk-builtin
"digitToInt"
(fn (c) (- (char-code (hk-force c)) 48))
1))
(dict-set!
env
"words"
(hk-mk-builtin
"words"
(fn
(s)
(let
((str (hk-force s)))
(--sx-to-hk-- (--words-- str (len str) 0 0 (list)))))
1))
(dict-set!
env
"lines"
(hk-mk-builtin
"lines"
(fn
(s)
(let
((str (hk-force s)))
(if
(= (len str) 0)
(list "[]")
(--sx-to-hk-- (--lines-- str (len str) 0 0 (list))))))
1))
env)))))
;; Eagerly build the Prelude env once at load time; each call to ;; 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. ;; hk-eval-expr-source copies it instead of re-parsing the whole Prelude.

View File

@@ -0,0 +1,234 @@
;; prelude-extra.sx — tests for Phase 6 prelude additions:
;; ord/isAlpha/isDigit/isSpace/isUpper/isLower/isAlphaNum/digitToInt
;; words/lines/unwords/unlines/sort/nub/splitAt/span/break
;; partition/intercalate/intersperse/isPrefixOf/isSuffixOf/isInfixOf
;; ── ord ──────────────────────────────────────────────────────
(hk-test "ord 'A'" (hk-eval-expr-source "ord 'A'") 65)
(hk-test "ord 'a'" (hk-eval-expr-source "ord 'a'") 97)
(hk-test "ord '0'" (hk-eval-expr-source "ord '0'") 48)
;; ── isAlpha / isDigit / isSpace / isUpper / isLower ──────────
(hk-test
"isAlpha 'a' True"
(hk-eval-expr-source "isAlpha 'a'")
(list "True"))
(hk-test
"isAlpha 'Z' True"
(hk-eval-expr-source "isAlpha 'Z'")
(list "True"))
(hk-test
"isAlpha '3' False"
(hk-eval-expr-source "isAlpha '3'")
(list "False"))
(hk-test
"isDigit '5' True"
(hk-eval-expr-source "isDigit '5'")
(list "True"))
(hk-test
"isDigit 'a' False"
(hk-eval-expr-source "isDigit 'a'")
(list "False"))
(hk-test
"isSpace ' ' True"
(hk-eval-expr-source "isSpace ' '")
(list "True"))
(hk-test
"isSpace 'x' False"
(hk-eval-expr-source "isSpace 'x'")
(list "False"))
(hk-test
"isUpper 'A' True"
(hk-eval-expr-source "isUpper 'A'")
(list "True"))
(hk-test
"isUpper 'a' False"
(hk-eval-expr-source "isUpper 'a'")
(list "False"))
(hk-test
"isLower 'z' True"
(hk-eval-expr-source "isLower 'z'")
(list "True"))
(hk-test
"isLower 'Z' False"
(hk-eval-expr-source "isLower 'Z'")
(list "False"))
(hk-test
"isAlphaNum '3' True"
(hk-eval-expr-source "isAlphaNum '3'")
(list "True"))
(hk-test
"isAlphaNum 'b' True"
(hk-eval-expr-source "isAlphaNum 'b'")
(list "True"))
(hk-test
"isAlphaNum '!' False"
(hk-eval-expr-source "isAlphaNum '!'")
(list "False"))
;; ── digitToInt ───────────────────────────────────────────────
(hk-test "digitToInt '0'" (hk-eval-expr-source "digitToInt '0'") 0)
(hk-test "digitToInt '7'" (hk-eval-expr-source "digitToInt '7'") 7)
(hk-test "digitToInt '9'" (hk-eval-expr-source "digitToInt '9'") 9)
;; ── words ────────────────────────────────────────────────────
(hk-test
"words single"
(hk-deep-force (hk-eval-expr-source "words \"hello\""))
(list ":" "hello" (list "[]")))
(hk-test
"words two"
(hk-deep-force (hk-eval-expr-source "words \"hello world\""))
(list ":" "hello" (list ":" "world" (list "[]"))))
(hk-test
"words leading/trailing spaces"
(hk-deep-force (hk-eval-expr-source "words \" foo bar \""))
(list ":" "foo" (list ":" "bar" (list "[]"))))
(hk-test
"words empty string"
(hk-deep-force (hk-eval-expr-source "words \"\""))
(list "[]"))
;; ── lines ────────────────────────────────────────────────────
(hk-test
"lines single no newline"
(hk-deep-force (hk-eval-expr-source "lines \"hello\""))
(list ":" "hello" (list "[]")))
(hk-test
"lines two lines"
(hk-deep-force (hk-eval-expr-source "lines \"a\\nb\""))
(list ":" "a" (list ":" "b" (list "[]"))))
(hk-test
"lines trailing newline"
(hk-deep-force (hk-eval-expr-source "lines \"a\\n\""))
(list ":" "a" (list "[]")))
(hk-test
"lines empty string"
(hk-deep-force (hk-eval-expr-source "lines \"\""))
(list "[]"))
;; ── unwords / unlines ────────────────────────────────────────
(hk-test
"unwords two"
(hk-eval-expr-source "unwords [\"hello\", \"world\"]")
"hello world")
(hk-test "unwords empty" (hk-eval-expr-source "unwords []") "")
(hk-test "unlines two" (hk-eval-expr-source "unlines [\"a\", \"b\"]") "a\nb\n")
;; ── sort / nub ───────────────────────────────────────────────
(hk-test
"sort ascending"
(hk-deep-force (hk-eval-expr-source "sort [3,1,2]"))
(list ":" 1 (list ":" 2 (list ":" 3 (list "[]")))))
(hk-test
"sort already sorted"
(hk-deep-force (hk-eval-expr-source "sort [1,2,3]"))
(list ":" 1 (list ":" 2 (list ":" 3 (list "[]")))))
(hk-test
"nub removes duplicates"
(hk-deep-force (hk-eval-expr-source "nub [1,2,1,3,2]"))
(list ":" 1 (list ":" 2 (list ":" 3 (list "[]")))))
(hk-test
"nub no duplicates unchanged"
(hk-deep-force (hk-eval-expr-source "nub [1,2,3]"))
(list ":" 1 (list ":" 2 (list ":" 3 (list "[]")))))
;; ── splitAt ──────────────────────────────────────────────────
(hk-test
"splitAt 2"
(hk-deep-force (hk-eval-expr-source "splitAt 2 [1,2,3,4]"))
(list
"Tuple"
(list ":" 1 (list ":" 2 (list "[]")))
(list ":" 3 (list ":" 4 (list "[]")))))
(hk-test
"splitAt 0"
(hk-deep-force (hk-eval-expr-source "splitAt 0 [1,2,3]"))
(list
"Tuple"
(list "[]")
(list ":" 1 (list ":" 2 (list ":" 3 (list "[]"))))))
;; ── span / break ─────────────────────────────────────────────
(hk-test
"span digits"
(hk-deep-force (hk-eval-expr-source "span (\\x -> x < 3) [1,2,3,4]"))
(list
"Tuple"
(list ":" 1 (list ":" 2 (list "[]")))
(list ":" 3 (list ":" 4 (list "[]")))))
(hk-test
"break digits"
(hk-deep-force (hk-eval-expr-source "break (\\x -> x >= 3) [1,2,3,4]"))
(list
"Tuple"
(list ":" 1 (list ":" 2 (list "[]")))
(list ":" 3 (list ":" 4 (list "[]")))))
;; ── partition ────────────────────────────────────────────────
(hk-test
"partition even/odd"
(hk-deep-force
(hk-eval-expr-source "partition (\\x -> x `mod` 2 == 0) [1,2,3,4,5]"))
(list
"Tuple"
(list ":" 2 (list ":" 4 (list "[]")))
(list ":" 1 (list ":" 3 (list ":" 5 (list "[]"))))))
;; ── intercalate / intersperse ────────────────────────────────
(hk-test
"intercalate"
(hk-eval-expr-source "intercalate \", \" [\"a\", \"b\", \"c\"]")
"a, b, c")
(hk-test
"intersperse"
(hk-deep-force (hk-eval-expr-source "intersperse 0 [1,2,3]"))
(list
":"
1
(list
":"
0
(list ":" 2 (list ":" 0 (list ":" 3 (list "[]")))))))
;; ── isPrefixOf / isSuffixOf / isInfixOf ──────────────────────
(hk-test
"isPrefixOf True"
(hk-deep-force (hk-eval-expr-source "isPrefixOf [1,2] [1,2,3]"))
(list "True"))
(hk-test
"isPrefixOf False"
(hk-deep-force (hk-eval-expr-source "isPrefixOf [2,3] [1,2,3]"))
(list "False"))
(hk-test
"isSuffixOf True"
(hk-deep-force (hk-eval-expr-source "isSuffixOf [2,3] [1,2,3]"))
(list "True"))
(hk-test
"isInfixOf True"
(hk-deep-force (hk-eval-expr-source "isInfixOf [2,3] [1,2,3,4]"))
(list "True"))
(hk-test
"isInfixOf False"
(hk-deep-force (hk-eval-expr-source "isInfixOf [5,6] [1,2,3,4]"))
(list "False"))
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}