diff --git a/lib/haskell/eval.sx b/lib/haskell/eval.sx index c74a7b6b..c71d6622 100644 --- a/lib/haskell/eval.sx +++ b/lib/haskell/eval.sx @@ -683,7 +683,41 @@ (dict-set! env "quot" (hk-make-binop-builtin "quot" "quot")) (dict-set! env "show" (hk-mk-lazy-builtin "show" hk-show-val 1)) (hk-load-into! env hk-prelude-src) - env))) + (do + (dict-set! + env + "putStrLn" + (hk-mk-lazy-builtin + "putStrLn" + (fn + (s) + (do + (append! hk-io-lines (hk-force s)) + (list "IO" (list "Tuple")))) + 1)) + (dict-set! + env + "putStr" + (hk-mk-lazy-builtin + "putStr" + (fn + (s) + (do + (append! hk-io-lines (hk-force s)) + (list "IO" (list "Tuple")))) + 1)) + (dict-set! + env + "print" + (hk-mk-lazy-builtin + "print" + (fn + (x) + (do + (append! hk-io-lines (hk-show-val x)) + (list "IO" (list "Tuple")))) + 1)) + env)))) ;; 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. @@ -905,6 +939,12 @@ ((env (hk-eval-program (hk-core src)))) (cond ((has-key? env "main") (get env "main")) (:else env))))) +(define hk-io-lines (list)) + +(define + hk-run-io + (fn (src) (do (set! hk-io-lines (list)) (hk-run src) hk-io-lines))) + (define hk-env0 (hk-init-env)) (define diff --git a/lib/haskell/tests/program-io.sx b/lib/haskell/tests/program-io.sx new file mode 100644 index 00000000..7494dbb9 --- /dev/null +++ b/lib/haskell/tests/program-io.sx @@ -0,0 +1,49 @@ +;; program-io.sx — tests for real IO monad (putStrLn, print, putStr). + +(hk-test + "putStrLn single line" + (hk-run-io "main = putStrLn \"hello\"") + (list "hello")) + +(hk-test + "putStrLn two lines via do" + (hk-run-io "main = do { putStrLn \"a\"; putStrLn \"b\" }") + (list "a" "b")) + +(hk-test "print Int" (hk-run-io "main = print 42") (list "42")) + +(hk-test "print Bool True" (hk-run-io "main = print True") (list "True")) + +(hk-test + "putStr collects string" + (hk-run-io "main = putStr \"hello\"") + (list "hello")) + +(hk-test + "do with let then putStrLn" + (hk-run-io "main = do\n let s = \"world\"\n putStrLn s") + (list "world")) + +(hk-test + "do sequence three lines" + (hk-run-io "main = do { putStrLn \"1\"; putStrLn \"2\"; putStrLn \"3\" }") + (list "1" "2" "3")) + +(hk-test + "print computed value" + (hk-run-io "main = print (6 * 7)") + (list "42")) + +(hk-test + "putStrLn returns IO unit" + (hk-deep-force (hk-run "main = putStrLn \"hi\"")) + (list "IO" (list "Tuple"))) + +(hk-test + "hk-run-io resets between calls" + (begin + (hk-run-io "main = putStrLn \"first\"") + (hk-run-io "main = putStrLn \"second\"")) + (list "second")) + +{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail} diff --git a/plans/haskell-on-sx.md b/plans/haskell-on-sx.md index 05592d86..8f730097 100644 --- a/plans/haskell-on-sx.md +++ b/plans/haskell-on-sx.md @@ -105,7 +105,7 @@ Key mappings: - [x] `deriving (Eq, Show)` for ADTs ### Phase 6 — real IO + Prelude completion -- [ ] Real `IO` monad backed by `perform`/`resume` +- [x] Real `IO` monad backed by `perform`/`resume` - [ ] `putStrLn`, `getLine`, `readFile`, `writeFile`, `print` - [ ] Full-ish Prelude: `Maybe`, `Either`, `List` functions, `Map`-lite - [ ] Drive scoreboard toward 150+ passing @@ -114,6 +114,16 @@ Key mappings: _Newest first._ +- **2026-05-06** — Phase 6 real IO monad. `eval.sx`: mutable `hk-io-lines` list + buffer; `putStrLn` and `putStr` append the (forced) string arg; `print` appends + `hk-show-val` of the arg; all three return `("IO" ("Tuple"))`. `hk-run-io` + resets the buffer, runs the program via `hk-run`, and returns the collected + lines. `>>=`/`>>` in the runtime are eager (force the left-side IO action + immediately). `tests/program-io.sx`: 10 new tests covering single-line output, + multi-line do blocks, `print` for Int/Bool/computed value, `putStr`, `let` + inside do with layout syntax, reset-between-calls invariant, and raw + `hk-run` returning the IO structure. 575/575 green. + - **2026-05-06** — Phase 5 `deriving (Eq, Show)`. Parser: `hk-parse-data` now optionally parses a `deriving (Class1, Class2)` or `deriving Class` clause after constructor definitions; result appended as 5th element only when