haskell: merge loops/haskell — Phases 1–6 complete (775 tests)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
Parser, layout, desugar, lazy eval, ADTs, HM inference, typeclasses (Eq/Ord/Show/Num/Functor/Monad), real IO monad, full Prelude. 775/775 green across 13 program suites. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
# haskell-on-sx loop agent (single agent, queue-driven)
|
||||
|
||||
Role: iterates `plans/haskell-on-sx.md` forever. Mini-Haskell 98 with real laziness (SX thunks are first-class). Phases 1-3 are untyped — laziness + ADTs first; HM inference is phase 4.
|
||||
Role: iterates `plans/haskell-completeness.md` forever. Mini-Haskell 98 with
|
||||
real laziness (SX thunks are first-class). Phases 1–6 are complete; this loop
|
||||
works Phases 7–16.
|
||||
|
||||
```
|
||||
description: haskell-on-sx queue loop
|
||||
@@ -11,66 +13,141 @@ isolation: worktree
|
||||
|
||||
## Prompt
|
||||
|
||||
You are the sole background agent working `/root/rose-ash/plans/haskell-on-sx.md`. Isolated worktree, forever, one commit per feature. Never push.
|
||||
|
||||
**Note:** there's an existing `/root/rose-ash/sx-haskell/` directory (~25 M). Check whether it has prior work you should fold into `lib/haskell/` rather than starting from scratch. Summarise what you find in the first iteration's Progress log entry; do not edit `sx-haskell/` itself.
|
||||
You are the sole background agent working
|
||||
`/root/rose-ash-loops/haskell/plans/haskell-completeness.md`. Isolated worktree,
|
||||
forever, one commit per feature. Push to `origin/loops/haskell` after every commit.
|
||||
|
||||
## Restart baseline — check before iterating
|
||||
|
||||
1. Read `plans/haskell-on-sx.md` — roadmap + Progress log.
|
||||
2. First-run only: peek at `/root/rose-ash/sx-haskell/` — does any of it belong in `lib/haskell/`? Report in Progress log. Don't edit sx-haskell/.
|
||||
3. `ls lib/haskell/` — pick up from the most advanced file.
|
||||
4. Run `lib/haskell/tests/*.sx` if they exist. Green before new work.
|
||||
5. If `lib/haskell/scoreboard.md` exists, that's your baseline.
|
||||
1. Read `plans/haskell-completeness.md` — roadmap + Progress log.
|
||||
2. `ls lib/haskell/` — orient on current state.
|
||||
3. Run `bash lib/haskell/test.sh`. All 775 tests must be green before new work.
|
||||
4. Check `lib/haskell/scoreboard.md` — baseline is 156/156 (18 programs).
|
||||
|
||||
## The queue
|
||||
|
||||
Phase order per `plans/haskell-on-sx.md`:
|
||||
Phase order per `plans/haskell-completeness.md`:
|
||||
|
||||
- **Phase 1** — tokenizer + parser + **layout rule** (indentation-sensitive, painful but required per Haskell 98 §10.3)
|
||||
- **Phase 2** — desugar + eager eval + ADTs (`data` declarations, constructor tagging, pattern matching). Still untyped.
|
||||
- **Phase 3** — **laziness**: thunk-wrap every application arg, `force` = WHNF, pattern match forces scrutinee. Classic programs (infinite Fibonacci, sieve of Eratosthenes, quicksort, n-queens, expression calculator) green.
|
||||
- **Phase 4** — Hindley-Milner type inference (Algorithm W, let-polymorphism, type-sig checking)
|
||||
- **Phase 5** — typeclasses (dictionary passing, Eq/Ord/Show/Num/Functor/Monad/Applicative, `deriving`)
|
||||
- **Phase 6** — real `IO` monad backed by `perform`/`resume`, full Prelude, drive corpus to 150+
|
||||
- **Phase 7** — String = [Char] via O(1) string-view dicts. No OCaml changes.
|
||||
Read the "String-view design" section below before touching anything.
|
||||
- **Phase 8** — `show` for arbitrary types; `deriving Show` generates proper
|
||||
instances; `print x = putStrLn (show x)`.
|
||||
- **Phase 9** — `error` / `undefined`; partial functions raise; top-level runner
|
||||
catches and a new `hk-test-error` helper checks error messages.
|
||||
- **Phase 10** — Numeric tower: `fromIntegral`, Float/Double literals,
|
||||
`sqrt`/`floor`/`ceiling`/`round`/`truncate`, `Fractional`/`Floating` stubs.
|
||||
- **Phase 11** — `Data.Map` — weight-balanced BST in pure SX in `map.sx`.
|
||||
- **Phase 12** — `Data.Set` — BST in pure SX in `set.sx`.
|
||||
- **Phase 13** — `where` in typeclass instances + default methods.
|
||||
- **Phase 14** — Record syntax: `data Foo = Foo { bar :: Int }`, accessors,
|
||||
update `r { field = v }`, record patterns.
|
||||
- **Phase 15** — `IORef` — mutable cells via existing `perform`/`resume` IO.
|
||||
- **Phase 16** — Exception handling: `catch`, `try`, `throwIO`, `evaluate`.
|
||||
|
||||
Within a phase, pick the checkbox with the best tests-per-effort ratio.
|
||||
|
||||
Every iteration: implement → test → commit → tick `[ ]` → Progress log → next.
|
||||
Every iteration: implement → test → commit → tick `[ ]` → Progress log → push.
|
||||
|
||||
## String-view design (Phase 7 — read before touching strings)
|
||||
|
||||
A string view is a pure-SX dict `{:hk-str buf :hk-off n}`. Native SX strings
|
||||
also satisfy `hk-str?` (offset = 0 implicitly). No OCaml changes needed.
|
||||
|
||||
- `hk-str?` covers both native strings and view dicts.
|
||||
- `hk-str-head v` returns the character at offset `n` as an **integer** (ord
|
||||
value). Char = integer throughout.
|
||||
- `hk-str-tail v` returns a new view dict with offset `n+1`; **O(1)**.
|
||||
- `hk-str-null? v` is true when offset ≥ string length.
|
||||
- In `match.sx`, the `":"` cons-pattern branch checks `hk-str?` on the scrutinee
|
||||
**before** the normal tagged-list path. On a string: head = char-int, tail =
|
||||
shifted view (or `(list "[]")` if exhausted).
|
||||
- `chr n` converts an integer back to a single-character SX string for display
|
||||
and for `++`.
|
||||
- `++` between two strings concatenates natively via `str`; no cons-spine built.
|
||||
- The natural hazard: any code that checks `(list? v)` or `(= (first v) ":")` on
|
||||
a value must be audited — string views are dicts, not lists. Check `hk-str?`
|
||||
first in every dispatch chain.
|
||||
|
||||
## Conformance test programs
|
||||
|
||||
For each phase's conformance programs:
|
||||
|
||||
1. **WebFetch the source** from one of:
|
||||
- 99 Haskell Problems: https://wiki.haskell.org/H-99:_Ninety-Nine_Haskell_Problems
|
||||
- Rosetta Code Haskell: https://rosettacode.org/wiki/Category:Haskell
|
||||
- Self-contained snippets from Real World Haskell / Learn You a Haskell
|
||||
2. **Adapt minimally** — no GHC extensions, no external packages beyond
|
||||
`Data.Map`/`Data.Set`/`Data.IORef` (once those phases are done).
|
||||
3. **Cite the source** as a comment at the top of the `.sx` test file.
|
||||
4. Add the program name (without `.sx`) to `PROGRAMS` in `lib/haskell/conformance.sh`.
|
||||
5. Run `bash lib/haskell/conformance.sh` and verify green before committing.
|
||||
|
||||
Target: scoreboard grows from 156 → 300+ as phases complete.
|
||||
|
||||
## Ground rules (hard)
|
||||
|
||||
- **Scope:** only `lib/haskell/**` and `plans/haskell-on-sx.md`. Do **not** edit `spec/`, `hosts/`, `shared/`, other `lib/<lang>/` dirs, `lib/stdlib.sx`, `lib/` root, or `sx-haskell/`. Haskell primitives go in `lib/haskell/runtime.sx`.
|
||||
- **NEVER call `sx_build`.** 600s watchdog. If sx_server binary broken → Blockers entry, stop.
|
||||
- **Shared-file issues** → plan's Blockers with minimal repro.
|
||||
- **SX thunks** (`make-thunk`, force on use) are already in the trampolining evaluator — reuse. Don't invent your own thunk type.
|
||||
- **SX files:** `sx-tree` MCP tools ONLY. `sx_validate` after edits.
|
||||
- **Worktree:** commit locally. Never push. Never touch `main`.
|
||||
- **Scope:** only `lib/haskell/**` and `plans/haskell-completeness.md`. Do
|
||||
**not** edit `spec/`, `hosts/`, `shared/`, other `lib/<lang>/` dirs,
|
||||
`lib/stdlib.sx`, `lib/` root.
|
||||
- **NEVER call `sx_build`.** 600s watchdog. If sx_server binary broken →
|
||||
Blockers entry in the plan, stop.
|
||||
- **Shared-file issues** → plan's Blockers section with minimal repro.
|
||||
- **SX thunks** (`make-thunk`, force on use) already in the trampolining
|
||||
evaluator — reuse. String views are SX dicts, not thunks.
|
||||
- **SX files:** `sx-tree` MCP tools ONLY (`sx_read_subtree`, `sx_find_all`,
|
||||
`sx_replace_node`, `sx_insert_child`, `sx_insert_near`,
|
||||
`sx_replace_by_pattern`, `sx_rename_symbol`, `sx_validate`, `sx_write_file`).
|
||||
`sx_validate` after every edit. Never `Edit`/`Read`/`Write` on `.sx` files.
|
||||
- **Shell, Markdown, JSON:** edit with normal tools.
|
||||
- **Worktree:** commit then push to `origin/loops/haskell`. Never touch `main`.
|
||||
- **Commit granularity:** one feature per commit.
|
||||
- **Plan file:** update Progress log + tick boxes every commit.
|
||||
- **Tests:** `bash lib/haskell/test.sh` must stay green. Never regress existing
|
||||
775 tests. After new programs, run `bash lib/haskell/conformance.sh`.
|
||||
|
||||
## Haskell-specific gotchas
|
||||
|
||||
- **Layout rule is the hard bit of parsing** — you need a lexer-parser feedback loop that inserts virtual `{`, `;`, `}` based on indentation. Budget proportionally.
|
||||
- **Every application arg is a thunk** — compiling `f x y` to `(f (thunk x) (thunk y))` not `(f x y)`. Pattern-match forces.
|
||||
- **ADT representation:** tagged list, e.g. `data Maybe a = Nothing | Just a` → constructors are `(:Nothing)` (0-ary) and `(:Just <thunk>)` (1-ary). Pattern match on the head symbol.
|
||||
- **Let-polymorphism** (phase 4): generalise at let-binding boundaries only, not at lambda.
|
||||
- **Typeclass dictionaries** (phase 5): each class is a record type; each instance builds the record; method call = project + apply.
|
||||
- **`IO`** (phase 6): internally `World -> (a, World)` but in practice backed by `perform`/`resume` for real side effects. Desugar `do`-notation to `>>=`.
|
||||
- **Out of scope:** GHC extensions. No `DataKinds`, `GADTs`, `TypeFamilies`, `TemplateHaskell`. Stick to Haskell 98.
|
||||
- **String views are dicts** — `(list? v)` returns false for a string view.
|
||||
Audit every value-dispatch chain in `match.sx` and `eval.sx` for this.
|
||||
- **Char = integer** — `'a'` parses to int 97. `chr 97 = "a"` (1-char string).
|
||||
Do not represent Char as a 1-char SX string internally.
|
||||
- **`deriving Show`** (Phase 8): nested constructor args need parens if their
|
||||
show string contains a space. Rule: `if string-contains (show arg) " " then
|
||||
"(" ++ show arg ++ ")" else show arg`.
|
||||
- **`error` tag** (Phase 9): use `(raise (list "hk-error" msg))`. The top-level
|
||||
`hk-run-io` guard must catch this tag; do not let `hk-error` leak as an
|
||||
uncaught SX exception into the test runner's output.
|
||||
- **`Data.Map` module resolution** (Phase 11): qualified imports `import
|
||||
qualified Data.Map as Map` need the eval import handler to resolve the dotted
|
||||
module name to the `map.sx` namespace dict. Check `hk-bind-decls!` import arm.
|
||||
- **Record update field index** (Phase 14): `r { field = v }` needs the field →
|
||||
positional-index mapping at runtime. Store it in `hk-constructors` when
|
||||
registering `:con-rec`.
|
||||
- **IORef mutation** (Phase 15): `dict-set!` is the SX in-place mutator. The
|
||||
`IORef` dict is heap-allocated and passed by reference — mutation is safe.
|
||||
- **Every application arg is a thunk** — `f x y` → `(f (thunk x) (thunk y))`.
|
||||
Pattern-match forces before matching. Builtins force their args.
|
||||
- **ADT representation:** `("Just" thunk)`, `("Nothing")`, `(":" h t)`, `("[]")`.
|
||||
- **Let-polymorphism:** generalise at let-binding boundaries only, not lambda.
|
||||
- **Typeclass dictionaries:** class = record; instance = record value; method
|
||||
call = project + apply. Defaults stored under `"__default__ClassName_method"`,
|
||||
used as fallback when the instance dict lacks the key.
|
||||
- **Out of scope:** GHC extensions. No `DataKinds`, `GADTs`, `TypeFamilies`,
|
||||
`TemplateHaskell`. Haskell 98 only.
|
||||
|
||||
## General gotchas (all loops)
|
||||
|
||||
- SX `do` = R7RS iteration. Use `begin` for multi-expr sequences.
|
||||
- `cond`/`when`/`let` clauses evaluate only the last expr.
|
||||
- SX `do` = R7RS iteration. Use `begin` for multi-expression sequences.
|
||||
- `cond`/`when`/`let` clauses evaluate only the last expression.
|
||||
- `type-of` on user fn returns `"lambda"`.
|
||||
- Shell heredoc `||` gets eaten — escape or use `case`.
|
||||
- Shell heredoc `||` gets eaten by bash — escape or use `case`.
|
||||
- `keys` on an SX dict returns keys in implementation-defined order.
|
||||
|
||||
## Style
|
||||
|
||||
- No comments in `.sx` unless non-obvious.
|
||||
- No new planning docs — update `plans/haskell-on-sx.md` inline.
|
||||
- Short, factual commit messages (`haskell: layout rule + first parse (+10)`).
|
||||
- No new planning docs — update `plans/haskell-completeness.md` inline.
|
||||
- Short, factual commit messages (`haskell: string-view O(1) head/tail (+15)`).
|
||||
- One feature per iteration. Commit. Log. Next.
|
||||
|
||||
Go. Read the plan; (first run only) peek at sx-haskell/ and report; find first `[ ]`; implement.
|
||||
Go. Read `plans/haskell-completeness.md`; find the first `[ ]`; implement.
|
||||
|
||||
285
plans/haskell-completeness.md
Normal file
285
plans/haskell-completeness.md
Normal file
@@ -0,0 +1,285 @@
|
||||
# Haskell-on-SX: completeness roadmap (Phases 7–16)
|
||||
|
||||
Continuation of `plans/haskell-on-sx.md`. Phases 1–6 are complete (156/156
|
||||
conformance tests, 18 programs, 775 total hk-on-sx tests). This document covers
|
||||
the next ten features toward a more complete Haskell 98 subset.
|
||||
|
||||
## Scope decisions (unchanged from haskell-on-sx.md)
|
||||
|
||||
- Haskell 98 subset only. No GHC extensions.
|
||||
- All work lives in `lib/haskell/**` and this file. Nothing else.
|
||||
- SX files: `sx-tree` MCP tools only.
|
||||
- One feature per commit. Keep `## Progress log` updated.
|
||||
|
||||
## String-view design note
|
||||
|
||||
Haskell defines `type String = [Char]`. Representing that naively as a linked
|
||||
cons-spine makes `length`, `++`, and `take` O(n) in allocation — unacceptable
|
||||
for string-processing programs. The design uses **string views** implemented as
|
||||
pure-SX dicts, requiring no OCaml changes.
|
||||
|
||||
### Representation
|
||||
|
||||
A string view is a dict `{:hk-str buf :hk-off n}` where `buf` is a native SX
|
||||
string and `n` is the current offset (zero-based code-unit index). Native SX
|
||||
strings also satisfy the predicate (offset = 0 implicitly).
|
||||
|
||||
- `hk-str?` returns true for both native strings and string-view dicts.
|
||||
- `hk-str-head v` extracts the character at offset `n` as an integer (ord value).
|
||||
- `hk-str-tail v` returns a new view with offset `n+1`; O(1).
|
||||
- `hk-str-null? v` is true when offset equals the string's length.
|
||||
|
||||
### Char = integer
|
||||
|
||||
`Char` is represented as a plain integer (its Unicode code point / ord value).
|
||||
`chr n` converts back to a single-character string for display and `++`. `ord c`
|
||||
is the identity (the integer itself). `toUpper`/`toLower` operate on the integer,
|
||||
looking up ASCII ranges. This is already consistent with the existing `ord 'A' =
|
||||
65` tests.
|
||||
|
||||
### Pattern matching
|
||||
|
||||
In `match.sx`, the cons-pattern branch (`":"` constructor) checks `hk-str?` on
|
||||
the scrutinee **before** the normal tagged-list path. When the scrutinee is a
|
||||
string view (or native string), decompose as:
|
||||
- head → `hk-str-head` (an integer char-code)
|
||||
- tail → `hk-str-tail` (a new string view, or `(list "[]")` if exhausted)
|
||||
|
||||
The nil-pattern `"[]"` matches when `hk-str-null?` is true.
|
||||
|
||||
### Complexity
|
||||
|
||||
- `head s` / `tail s` — O(1) via view shift
|
||||
- `s !! n` — O(n) (n tail calls)
|
||||
- `(c:s)` construction — O(n) for full `[Char]` construction (same as real Haskell)
|
||||
- `++` on two strings — native `str` concat, O(length left)
|
||||
- `length` — O(n); `words`/`lines` — O(n)
|
||||
|
||||
No OCaml changes are needed. The view type is fully representable as an SX dict.
|
||||
|
||||
## Ground rules
|
||||
|
||||
- **Scope:** only `lib/haskell/**` and `plans/haskell-completeness.md`. No edits
|
||||
to `spec/`, `hosts/`, `shared/`, other `lib/<lang>/` dirs, or `lib/` root.
|
||||
- **SX files:** `sx-tree` MCP tools only. `sx_validate` after every edit.
|
||||
- **Commits:** one feature per commit. Keep `## Progress log` updated.
|
||||
- **Tests:** `bash lib/haskell/test.sh` must be green before any commit. After
|
||||
adding new programs, run `bash lib/haskell/conformance.sh` and commit the
|
||||
updated `scoreboard.md`.
|
||||
- **Conformance programs:** WebFetch from 99 Haskell Problems or Rosetta Code.
|
||||
Adapt minimally (no GHC extensions). Cite the source URL in the file header.
|
||||
Add to `conformance.sh` PROGRAMS array.
|
||||
- **NEVER call `sx_build`.** If sx_server binary broken → Blockers entry, stop.
|
||||
|
||||
## Roadmap
|
||||
|
||||
### Phase 7 — String = [Char] (performant string views)
|
||||
|
||||
- [ ] 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
|
||||
`runtime.sx`.
|
||||
- [ ] 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,
|
||||
`toUpper`, `toLower` (ASCII range arithmetic on ints).
|
||||
- [ ] 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
|
||||
string literal, map over string, filter chars, chr/ord roundtrip, toUpper,
|
||||
toLower, null/empty string view).
|
||||
- [ ] 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
|
||||
pattern matching, `span`, character comparison.
|
||||
|
||||
### 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
|
||||
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
|
||||
type-check; no real parser needed yet.
|
||||
- [ ] 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:
|
||||
- `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 []` →
|
||||
`"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
|
||||
error result so test suites can inspect it without crashing.
|
||||
- [ ] `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
|
||||
content, undefined, head/tail/fromJust on bad input, `hk-test-error` helper).
|
||||
- [ ] 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
|
||||
(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
|
||||
the corresponding SX numeric primitives.
|
||||
- [ ] `Fractional` typeclass stub: `(/)`, `recip`, `fromRational`.
|
||||
- [ ] `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:
|
||||
- `statistics.hs` — mean, variance, std-dev on a `[Double]`. Exercises
|
||||
`fromIntegral`, `sqrt`, `/`.
|
||||
- `newton.hs` — Newton's method for square root. Exercises `Float`, `abs`,
|
||||
iteration.
|
||||
|
||||
### Phase 11 — Data.Map
|
||||
|
||||
- [ ] 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`,
|
||||
`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`
|
||||
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:
|
||||
- `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
|
||||
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`,
|
||||
`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,
|
||||
member hit/miss, delete, fromList deduplication, union, intersection,
|
||||
difference, isSubsetOf).
|
||||
- [ ] 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
|
||||
`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:
|
||||
`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
|
||||
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 <=
|
||||
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:
|
||||
- `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, … }`
|
||||
constructor bodies. AST node: `(:con-rec CNAME [(FNAME TYPE) …])`.
|
||||
- [ ] 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
|
||||
`(: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)])`.
|
||||
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`,
|
||||
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:
|
||||
- `person.hs` — `data Person = Person { name :: String, age :: Int }` with
|
||||
accessors, update, `deriving Show`.
|
||||
- `config.hs` — multi-field config record; partial update; defaultConfig
|
||||
constant.
|
||||
|
||||
### Phase 15 — IORef
|
||||
|
||||
- [ ] `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)`,
|
||||
returns `(IO ("Tuple"))`.
|
||||
- [ ] `modifyIORef :: IORef a -> (a -> a) -> IO ()` — read + apply + write.
|
||||
- [ ] `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,
|
||||
modify, modifyStrict, shared ref across do-steps, counter loop).
|
||||
- [ ] 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
|
||||
IO action, read at the end.
|
||||
|
||||
### Phase 16 — Exception handling
|
||||
|
||||
- [ ] `SomeException` type: `data SomeException = SomeException String`.
|
||||
`IOException = SomeException`.
|
||||
- [ ] `throwIO :: Exception e => e -> IO a` — raises `("hk-exception" e)`.
|
||||
- [ ] `evaluate :: a -> IO a` — forces arg strictly; any embedded `hk-error`
|
||||
surfaces as a catchable `SomeException`.
|
||||
- [ ] `catch :: Exception e => IO a -> (e -> IO a) -> IO a` — wraps action in
|
||||
SX `guard`; on `hk-error` or `hk-exception`, calls the handler with a
|
||||
`SomeException` value.
|
||||
- [ ] `try :: Exception e => IO a -> IO (Either e a)` — returns `Right v` on
|
||||
success, `Left e` on any exception.
|
||||
- [ ] `handle = flip catch`.
|
||||
- [ ] Tests in `lib/haskell/tests/exceptions.sx` (≥ 10 tests: catch success,
|
||||
catch error, try Right, try Left, nested catch, evaluate surfaces error,
|
||||
throwIO propagates, handle alias).
|
||||
- [ ] Conformance programs:
|
||||
- `safediv.hs` — safe division using `catch`; divide-by-zero raises,
|
||||
handler returns 0.
|
||||
- `trycatch.hs` — `try` pattern: run an action, branch on Left/Right.
|
||||
|
||||
## Progress log
|
||||
|
||||
_Newest first._
|
||||
@@ -55,58 +55,634 @@ Key mappings:
|
||||
|
||||
### Phase 1 — tokenizer + parser + layout rule
|
||||
- [x] Tokenizer: reserved words, qualified names, operators, numbers (int, float, Rational later), chars/strings, comments (`--` and `{-` nested)
|
||||
- [ ] Layout algorithm: turn indentation into virtual `{`, `;`, `}` tokens per Haskell 98 §10.3
|
||||
- [ ] Parser: modules, imports (stub), top-level decls, type sigs, function clauses with patterns + guards + where-clauses, expressions with operator precedence, lambdas, `let`, `if`, `case`, `do`, list comp, sections
|
||||
- [ ] AST design modelled on GHC's HsSyn at a surface level
|
||||
- [x] Layout algorithm: turn indentation into virtual `{`, `;`, `}` tokens per Haskell 98 §10.3
|
||||
- Parser (split into sub-items — implement one per iteration):
|
||||
- [x] Expressions: atoms, parens, tuples, lists, ranges, application, infix with full Haskell-98 precedence table, unary `-`, backtick operators, lambdas, `if`, `let`
|
||||
- [x] `case … of` and `do`-notation expressions (plus minimal patterns needed for arms/binds: var, wildcard, literal, 0-arity and applied constructor, tuple, list)
|
||||
- [x] Patterns — full: `as` patterns, nested, negative literal, `~` lazy, infix constructor (`:` / consym), extend lambdas/let with non-var patterns
|
||||
- [x] Top-level decls: function clauses (simple — no guards/where yet), pattern bindings, multi-name type signatures, `data` with type vars and recursive constructors, `type` synonyms, `newtype`, fixity (`infix`/`infixl`/`infixr` with optional precedence, comma-separated ops, backtick names). Types: vars / constructors / application / `->` (right-assoc) / tuples / lists. `hk-parse-top` entry.
|
||||
- [x] `where` clauses + guards (on fun-clauses, case alts, and let/do-let bindings — with the let funclause shorthand `let f x = …` now supported)
|
||||
- [x] Module header + imports — `module NAME [exports] where …`, qualified/as/hiding/explicit imports, operator exports, `module Foo` exports, dotted names, headerless-with-imports
|
||||
- [x] List comprehensions + operator sections — `(op)` / `(op e)` / `(e op)` (excluding `-` from right sections), `[e | q1, q2, …]` with `q-gen` / `q-guard` / `q-let` qualifiers
|
||||
- [x] AST design modelled on GHC's HsSyn at a surface level — keyword-tagged lists cover modules/imports/decls/types/patterns/expressions; see parser.sx docstrings for the full node catalogue
|
||||
- [x] Unit tests in `lib/haskell/tests/parse.sx` (43 tokenizer tests, all green)
|
||||
|
||||
### Phase 2 — desugar + eager-ish eval + ADTs (untyped)
|
||||
- [ ] Desugar: guards → nested `if`s; `where` → `let`; list comp → `concatMap`-based; do-notation stays for now (desugared in phase 3)
|
||||
- [ ] `data` declarations register constructors in runtime
|
||||
- [ ] Pattern match (tag-based, value-level): atoms, vars, wildcards, constructor patterns, `as` patterns, nested
|
||||
- [ ] Evaluator (still strict internally — laziness in phase 3): `let`, `lambda`, application, `case`, literals, constructors
|
||||
- [ ] 30+ eval tests in `lib/haskell/tests/eval.sx`
|
||||
- [x] Desugar: guards → nested `if`s; `where` → `let`; list comp → `concatMap`-based; do-notation stays for now (desugared in phase 3)
|
||||
- [x] `data` declarations register constructors in runtime
|
||||
- [x] Pattern match (tag-based, value-level): atoms, vars, wildcards, constructor patterns, `as` patterns, nested
|
||||
- [x] Evaluator (still strict internally — laziness in phase 3): `let`, `lambda`, application, `case`, literals, constructors
|
||||
- [x] 30+ eval tests in `lib/haskell/tests/eval.sx`
|
||||
|
||||
### Phase 3 — laziness + classic programs
|
||||
- [ ] Transpile to thunk-wrapped SX: every application arg becomes `(make-thunk (lambda () <arg>))`
|
||||
- [ ] `force` = SX eval-thunk-to-WHNF primitive
|
||||
- [ ] Pattern match forces scrutinee before matching
|
||||
- [ ] Infinite structures: `repeat x`, `iterate f x`, `[1..]`, Fibonacci stream, sieve of Eratosthenes
|
||||
- [ ] `seq`, `deepseq` from Prelude
|
||||
- [ ] Do-notation for a stub `IO` monad (just threading, no real side effects yet)
|
||||
- [ ] Classic programs in `lib/haskell/tests/programs/`:
|
||||
- [ ] `fib.hs` — infinite Fibonacci stream
|
||||
- [ ] `sieve.hs` — lazy sieve of Eratosthenes
|
||||
- [ ] `quicksort.hs` — naive QS
|
||||
- [ ] `nqueens.hs`
|
||||
- [ ] `calculator.hs` — parser combinator style expression evaluator
|
||||
- [ ] `lib/haskell/conformance.sh` + runner; `scoreboard.json` + `scoreboard.md`
|
||||
- [ ] Target: 5/5 classic programs passing
|
||||
- [x] Transpile to thunk-wrapped SX: every application arg becomes `(make-thunk (lambda () <arg>))`
|
||||
- [x] `force` = SX eval-thunk-to-WHNF primitive
|
||||
- [x] Pattern match forces scrutinee before matching
|
||||
- [x] Infinite structures: `repeat x`, `iterate f x`, `[1..]`, Fibonacci stream (sieve deferred — needs lazy `++` and is exercised under `Classic programs`)
|
||||
- [x] `seq`, `deepseq` from Prelude
|
||||
- [x] Do-notation for a stub `IO` monad (just threading, no real side effects yet)
|
||||
- [x] Classic programs in `lib/haskell/tests/programs/`:
|
||||
- [x] `fib.hs` — infinite Fibonacci stream
|
||||
- [x] `sieve.hs` — lazy sieve of Eratosthenes
|
||||
- [x] `quicksort.hs` — naive QS
|
||||
- [x] `nqueens.hs`
|
||||
- [x] `calculator.hs` — parser combinator style expression evaluator
|
||||
- [x] `lib/haskell/conformance.sh` + runner; `scoreboard.json` + `scoreboard.md`
|
||||
- [x] Target: 5/5 classic programs passing
|
||||
|
||||
### Phase 4 — Hindley-Milner inference
|
||||
- [ ] Algorithm W: unification + type schemes + generalisation + instantiation
|
||||
- [ ] Report type errors with meaningful positions
|
||||
- [ ] Reject untypeable programs that phase 3 was accepting
|
||||
- [ ] Type-sig checking: user writes `f :: Int -> Int`; verify
|
||||
- [ ] Let-polymorphism
|
||||
- [ ] Unit tests: inference for 50+ expressions
|
||||
- [x] Algorithm W: unification + type schemes + generalisation + instantiation
|
||||
- [x] Report type errors with meaningful positions
|
||||
- [x] Reject untypeable programs that phase 3 was accepting
|
||||
- [x] Type-sig checking: user writes `f :: Int -> Int`; verify
|
||||
- [x] Let-polymorphism
|
||||
- [x] Unit tests: inference for 50+ expressions
|
||||
|
||||
### Phase 5 — typeclasses (dictionary passing)
|
||||
- [ ] `class` / `instance` declarations
|
||||
- [ ] Dictionary-passing elaborator: inserts dict args at call sites
|
||||
- [ ] Standard classes: `Eq`, `Ord`, `Show`, `Num`, `Functor`, `Monad`, `Applicative`
|
||||
- [ ] `deriving (Eq, Show)` for ADTs
|
||||
- [x] `class` / `instance` declarations
|
||||
- [x] Dictionary-passing elaborator: inserts dict args at call sites
|
||||
- [x] Standard classes: `Eq`, `Ord`, `Show`, `Num`, `Functor`, `Monad`, `Applicative`
|
||||
- [x] `deriving (Eq, Show)` for ADTs
|
||||
|
||||
### Phase 6 — real IO + Prelude completion
|
||||
- [ ] 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
|
||||
- [x] Real `IO` monad backed by `perform`/`resume`
|
||||
- [x] `putStrLn`, `getLine`, `readFile`, `writeFile`, `print`
|
||||
- [x] Full-ish Prelude: `Maybe`, `Either`, `List` functions, `Map`-lite
|
||||
- [x] Drive scoreboard toward 150+ passing
|
||||
|
||||
## Progress log
|
||||
|
||||
_Newest first._
|
||||
|
||||
- **2026-05-06** — Scoreboard 156/156 tests, 18/18 programs (775 total hk-on-sx tests). Added
|
||||
13 new program test suites: collatz, palindrome, maybe, fizzbuzz, anagram, roman, binary,
|
||||
either, primes, zipwith, matrix, wordcount, powers. Updated conformance.sh PROGRAMS array.
|
||||
|
||||
- **2026-05-06** — Phase 6 prelude extras (635/635). `nub`, `sort`, `sortBy`, `sortOn`,
|
||||
`splitAt`, `span`, `break`, `partition`, `unzip`, `tails`, `inits`, `isPrefixOf`,
|
||||
`isSuffixOf`, `isInfixOf`, `intercalate`, `intersperse`, `unwords`, `unlines`,
|
||||
`interactApply/interact`. SX builtins: `ord`, `isAlpha`, `isAlphaNum`, `isDigit`,
|
||||
`isSpace`, `isUpper`, `isLower`, `digitToInt`, `words`, `lines`. Fixed `++` on SX
|
||||
strings (`hk-list-append` now handles string concat via `str`). Unified list repr:
|
||||
`--sx-to-hk--` now uses `":"/"[]"` matching `hk-mk-cons`. 47 new tests.
|
||||
|
||||
- **2026-05-06** — Phase 6 `getLine`/`getContents`/`readFile`/`writeFile`. `hk-force`
|
||||
extended: 0-arity builtins (`arity=0` dicts) are called immediately when forced,
|
||||
making `getLine`/`getContents` work naturally as IO actions (no arity-0 application
|
||||
needed — `>>=` forces them and gets the `("IO" value)` result). `getLine` pops
|
||||
from `hk-stdin-lines`; `getContents` drains it joining with `"\n"`; `readFile`
|
||||
reads from `hk-vfs` (dict), errors on missing key; `writeFile` sets `hk-vfs` key.
|
||||
`hk-run-io-with-input` resets both io-lines and stdin then runs. `>>=` and `>>`
|
||||
added to `hk-binop` for infix operator path. Bug caught: `sx_replace_node` on the
|
||||
thunk-force branch accidentally changed `"body"` → `"fn"` (key name); fixed.
|
||||
11 new tests in `tests/io-input.sx`. 587/587 green.
|
||||
|
||||
- **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
|
||||
non-empty (no AST churn for existing decls). Three token-type fixes: the
|
||||
deriving clause used `"special"` for `(`, `)`, `,` but the tokenizer
|
||||
produces `"lparen"`, `"rparen"`, `"comma"`. Eval: `hk-bind-decls!` `data`
|
||||
arm generates `dictShow_{Con}` and `dictEq_{Con}` dicts for each constructor
|
||||
that appears in a `deriving` list. `Show` delegates to `hk-show-val` (lazy).
|
||||
`Eq` needed structural equality — `hk-binop "=="` and `/=` now call
|
||||
`hk-deep-force` on both sides before `=` (SX dict equality is by reference,
|
||||
so two thunks wrapping the same number compared as not-equal without this).
|
||||
11 new tests in `lib/haskell/tests/deriving.sx`: nullary Show, constructor
|
||||
with arg, nested, second constructor, Eq same/different constructor, `/=`
|
||||
same/different, combined `(Eq, Show)`, Eq with args, different constructors
|
||||
with args. 565/565 green.
|
||||
|
||||
- **2026-05-06** — Phase 5 standard classes. Prelude extended: `foldr`, `foldl`,
|
||||
`foldl1`, `foldr1`, `zip`, `reverse`, `elem`, `notElem`, `any`, `all`, `and`,
|
||||
`or`, `sum`, `product`, `maximum`, `minimum`, `compare`, `min`, `max`,
|
||||
`signum`, `fromIntegral`, `null`, `flip`, `const`, `curry`, `uncurry`,
|
||||
`lookup`, `maybe`, `either`, `fmap`, `pure`, `when`, `unless`, `mapM_`,
|
||||
`sequence_`. `show` implemented as SX builtin (`hk-show-val`) dispatching on
|
||||
runtime type (number, string, bool, list, tuple, ADT). `hk-eval-program` now
|
||||
uses `hk-dict-copy hk-env0` instead of fresh `hk-init-env` — prelude parsed
|
||||
once at load time, each program gets a shallow copy (10× speedup per call).
|
||||
test.sh timeout 240s→360s for nqueens headroom. 48 new stdlib tests.
|
||||
554/554 green.
|
||||
|
||||
- **2026-05-06** — Phase 5 dict-passing elaborator. `hk-bind-decls!` class-decl
|
||||
arm now wraps dispatch functions as `hk-mk-lazy-builtin` (arity 1) so
|
||||
`hk-apply` can call them; instance methods called via `hk-apply` not native SX
|
||||
apply; thunk-forcing uses `hk-force` not `type-of == "thunk"` (Haskell thunks
|
||||
are dicts, not SX native thunks). `tests/class.sx` gains 3 dispatch tests
|
||||
(Int instance, Bool instance, error on unknown). 506/506 green.
|
||||
|
||||
- **2026-05-06** — Phase 5 class/instance declarations. Parser: `hk-parse-class`
|
||||
and `hk-parse-instance` added to the parser closure; `hk-parse-decl` gains
|
||||
arms for `"class"` and `"instance"` reserved words (tokenizer already marks
|
||||
them reserved). `class Eq a where { ... }` → `("class-decl" name tvar decls)`;
|
||||
`instance Eq Int where { ... }` → `("instance-decl" name inst-type decls)`.
|
||||
Eval: `hk-type-ast-str` converts type AST to a string key. `hk-bind-decls!`
|
||||
gains arms for `class-decl` (registers `__class__Name` marker) and
|
||||
`instance-decl` (builds method dict, binds as `dictClassName_TypeStr` in env).
|
||||
11 new tests in `tests/class.sx` covering AST shapes + runtime dict
|
||||
construction. 503/503 green.
|
||||
|
||||
- **2026-05-05** — Phase 4 inference unit tests (50+ expressions). Added 16 new
|
||||
`hk-t` expression tests to `tests/infer.sx`: nested application (`not(not True)`,
|
||||
`negate(negate 1)`), bool/mixed lambdas (`\\x->\\y->x&&y`, `\\x->x==1`),
|
||||
let variants (if-in-let, not-in-let, tuple-in-let, nested let, chain application),
|
||||
more if expressions, 2-element tuples, and list operations on Bool lists.
|
||||
infer.sx now has 75 tests covering 55+ distinct expression forms. Phase 4
|
||||
complete. 492/492 green.
|
||||
|
||||
- **2026-05-05** — Phase 4 let-polymorphism tests. `hk-w-let` already
|
||||
generalises let-bound types with `hk-generalise` before adding them to the
|
||||
env, so `id :: ∀a. a→a` is instantiated independently at each use site.
|
||||
6 new tests in `tests/infer.sx`: identity at Int and Bool separately, identity
|
||||
tuple `(id 1, id True) → (Int, Bool)`, `const` at two types, nested let with
|
||||
`f`/`g` sharing the polymorphic binding, and `twice` applied to an arithmetic
|
||||
lambda. All use the 2-arg `hk-t` form. 476/476 green.
|
||||
|
||||
- **2026-05-05** — Phase 4 type-sig checking. `hk-ast-type` converts parsed type
|
||||
AST nodes (`t-con`/`t-var`/`t-fun`/`t-app`/`t-list`/`t-tuple`) to internal
|
||||
type values. `hk-collect-tvars` gathers free type variable names. `hk-check-sig`
|
||||
wraps declared type in a scheme (if polymorphic), instantiates with fresh vars,
|
||||
and unifies against the inferred type. `hk-infer-prog` updated: first pass
|
||||
collects `type-sig` declarations into a `sigs` dict; second pass checks each
|
||||
successful fun-clause inference against its declared sig, returning
|
||||
`("err" "... declared type mismatch: ...")` on mismatch. 6 new tests in
|
||||
`typecheck.sx` cover monomorphic sig match, sig mismatch (error message),
|
||||
polymorphic `a->a` sig, and `hk-run-typed` with and without sig. 470/470 green.
|
||||
|
||||
- **2026-05-05** — Phase 4 reject untypeable programs. `hk-typecheck` runs
|
||||
`hk-infer-prog` on a program AST and raises the first type error found.
|
||||
`hk-run-typed` is a drop-in for `hk-run` that gates evaluation on a
|
||||
successful type check. `hk-infer-decl` now returns a 4th element (raw type
|
||||
value); `hk-infer-prog` propagates inferred types into the running type env
|
||||
so multi-function programs (`f x = x+1\ng y = f y+2`) infer correctly.
|
||||
test.sh extended to load infer.sx for `*typecheck*` files.
|
||||
9 new tests in `tests/typecheck.sx`: 4 valid programs pass through, 5
|
||||
invalid programs are rejected (Int+Bool, non-Bool if condition, unbound var,
|
||||
apply non-function). 464/464 green.
|
||||
|
||||
- **2026-05-05** — Phase 4 type error reporting. `hk-expr->brief` converts any AST
|
||||
node to a short human-readable string for error messages (handles var/con/int/float/
|
||||
str/char/bool/app/op/if/let/lambda/tuple/list/loc). `loc` nodes in `hk-w` delegate
|
||||
to inner expr (position is for outer context). `hk-infer-decl` wraps per-declaration
|
||||
inference in a `guard`, returning `("ok" name type)` or `("err" "in 'name': msg")`
|
||||
tagged results — avoids re-raise infinite loop in SX guard semantics.
|
||||
`hk-infer-prog` runs all declarations and accumulates tagged results. test.sh
|
||||
timeouts raised 120s→240s to accommodate eval.sx (Prelude init ~9s × 20 tests).
|
||||
21 new tests covering brief serializer, error message substrings, loc pass-through,
|
||||
decl inference, and prog-level inference. 455/455 green.
|
||||
|
||||
- **2026-05-05** — Phase 4 Algorithm W (`lib/haskell/infer.sx`). Full
|
||||
Hindley-Milner inference: type constructors (TVar/TCon/TArr/TApp/TTuple/TScheme),
|
||||
substitution (apply/compose/restrict), occurs-check unification, instantiation,
|
||||
generalisation (let-polymorphism). Algorithm W covers literals, var, con, lambda,
|
||||
multi-param lambda, application, let (simple bind + fun-clause), if, binary ops
|
||||
(desugared to double application), tuples, and list literals. Initial type
|
||||
environment provides monomorphic arithmetic/comparison/boolean ops plus
|
||||
polymorphic list functions (`head`/`tail`/`null`/`length`/`reverse`/`:`).
|
||||
`hk-infer-type` is the public entry point. test.sh updated to load infer.sx.
|
||||
32 new tests in `lib/haskell/tests/infer.sx` cover all node types + let-
|
||||
polymorphism. 434/434 green.
|
||||
|
||||
- **2026-04-25** — `conformance.sh` runner + `scoreboard.json` + `scoreboard.md`.
|
||||
Script runs each classic program's test suite, prints per-program pass/fail,
|
||||
and writes both files. `--check` mode skips writing for CI use.
|
||||
Initial snapshot: 16/16 tests, 5/5 programs passing. Phase 3 complete.
|
||||
|
||||
- **2026-04-25** — Classic program `calculator.hs`: recursive descent
|
||||
expression evaluator using ADTs for tokens and results.
|
||||
`data Token = TNum Int | TOp String` + `data Result = R Int [Token]`;
|
||||
parser threads token lists through `R` constructors enabling nested
|
||||
constructor pattern matching (`R v (TOp "+":rest)`). Handles two-level
|
||||
operator precedence (* / tighter than + −) and left-associativity.
|
||||
5 tests: addition, precedence, left-assoc subtraction, left-assoc
|
||||
div+mul, single number. All 5 classic programs complete. 402/402 green.
|
||||
|
||||
- **2026-04-25** — Classic program `nqueens.hs`: backtracking n-queens via list
|
||||
comprehension and multi-clause `where`. Three fixes needed: (1) `hk-eval-let`
|
||||
now delegates to `hk-bind-decls!` so multi-clause `where`/`let` bindings
|
||||
(e.g., `go 0 = [[]]; go k = [...]`) are grouped as multifuns; (2) added
|
||||
`concatMap`, `concat`, `abs`, `negate` to `hk-prelude-src` (list comprehensions
|
||||
desugar to `concatMap`); (3) cached the Prelude env in `hk-env0` so
|
||||
`hk-eval-expr-source` copies it instead of re-parsing. Tests: `queens 4 = 2`,
|
||||
`queens 5 = 10`. n=8 (92 solutions) is too slow at ~50s/n — omitted.
|
||||
397/397 green.
|
||||
|
||||
- **2026-04-25** — Classic program `quicksort.hs`: naive functional quicksort.
|
||||
`qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where smaller = filter (< x) xs; larger = filter (>= x) xs`.
|
||||
No new runtime additions needed — right sections, `filter`, `++` all worked out of the box.
|
||||
5 tests (general sort, empty, singleton, already-sorted, reverse-sorted). 395/395 green.
|
||||
|
||||
- **2026-04-25** — Classic program `sieve.hs`: lazy sieve of Eratosthenes.
|
||||
Added `mod`, `div`, `rem`, `quot` to `hk-binop` (and as first-class
|
||||
values in `hk-init-env`), enabling backtick operator use. The filter-based
|
||||
sieve `sieve (p:xs) = p : sieve (filter (\x -> x \`mod\` p /= 0) xs)` works
|
||||
with the existing lazy cons + Prelude `filter`. 2 new tests in
|
||||
`lib/haskell/tests/program-sieve.sx` (first 10 primes, 20th prime = 71).
|
||||
390/390 green.
|
||||
|
||||
- **2026-04-25** — First classic program: `fib.hs`. Canonical Haskell
|
||||
source lives at `lib/haskell/tests/programs/fib.hs` (the
|
||||
two-cons-cell self-referential fibs definition plus a hand-rolled
|
||||
`zipPlus`). The runner at `lib/haskell/tests/program-fib.sx`
|
||||
mirrors the source as an SX string (the OCaml server's
|
||||
`read-file` lives in the page-helpers env, not the default load
|
||||
env, so direct file reads from inside `eval` aren't available).
|
||||
Tests: `take 15 myFibs == [0,1,1,2,3,5,8,13,21,34,55,89,144,233,377]`,
|
||||
plus a spot-check that the user-defined `zipPlus` is also
|
||||
reachable. Found and fixed an ordering bug in `hk-bind-decls!`:
|
||||
pass 3 (0-arity body evaluation) iterated `(keys groups)` whose
|
||||
order is implementation-defined, so a top-down program where
|
||||
`result = take 15 myFibs` came after `myFibs = …` could see
|
||||
`myFibs` still bound to its `nil` placeholder. Now group names
|
||||
are tracked in source order via a parallel list and pass 3 walks
|
||||
that. 388/388 green.
|
||||
|
||||
- **2026-04-25** — Phase 3 do-notation + stub IO monad. Added a
|
||||
`hk-desugar-do` pass that follows Haskell 98 §3.14 verbatim:
|
||||
`do { e } = e`, `do { e ; ss } = e >> do { ss }`,
|
||||
`do { p <- e ; ss } = e >>= \p -> do { ss }`, and
|
||||
`do { let ds ; ss } = let ds in do { ss }`. The desugarer's
|
||||
`:do` branch now invokes this pass directly so the surface
|
||||
AST forms (`:do-expr`, `:do-bind`, `:do-let`) never reach the
|
||||
evaluator. IO is represented as a tagged value
|
||||
`("IO" payload)` — `return` (lazy builtin) wraps; `>>=` (lazy
|
||||
builtin) forces the action, unwraps, and calls the bound
|
||||
function on the payload; `>>` (lazy builtin) forces the
|
||||
action and returns the second one. All three are non-strict
|
||||
in their action arguments so deeply nested do-blocks don't
|
||||
walk the whole chain at construction time. 14 new tests in
|
||||
`lib/haskell/tests/do-io.sx` cover single-stmt do, single
|
||||
and multi-bind, `>>` sequencing (last action wins), do-let
|
||||
(single, multi, interleaved with bind), bind-to-`Just`,
|
||||
bind-to-tuple, do inside a top-level fun, nested do, and
|
||||
using `(>>=)`/`(>>)` directly as functions. 382/382 green.
|
||||
|
||||
- **2026-04-25** — Phase 3 `seq` + `deepseq`. Built-ins were strict
|
||||
in all args by default (every collected thunk forced before
|
||||
invoking the underlying SX fn) — that defeats `seq`'s purpose,
|
||||
which is strict in its first argument and lazy in its second.
|
||||
Added a tiny `lazy` flag on the builtin record (set by a new
|
||||
`hk-mk-lazy-builtin` constructor) and routed `hk-apply-builtin`
|
||||
to skip the auto-force when the flag is true. `seq a b` calls
|
||||
`hk-force a` then returns `b` unchanged so its laziness is
|
||||
preserved; `deepseq` does the same with `hk-deep-force`. 9 new
|
||||
tests in `lib/haskell/tests/seq.sx` cover primitive, computed,
|
||||
and let-bound first args, deepseq on a list / `Just` /
|
||||
tuple, seq inside arithmetic, seq via a fun-clause, and
|
||||
`[seq 1 10, seq 2 20]` to confirm seq composes inside list
|
||||
literals. The lazy-when-unused negative case is also tested:
|
||||
`let x = error "never" in 42 == 42`. 368/368 green.
|
||||
|
||||
- **2026-04-24** — Phase 3 infinite structures + Prelude. Two
|
||||
evaluator changes turn the lazy primitives into a working
|
||||
language:
|
||||
1. Op-form `:` is now non-strict in both args — `hk-eval-op`
|
||||
special-cases it before the eager force-and-binop path, so a
|
||||
cons-cell holds two thunks. This is what makes `repeat x =
|
||||
x : repeat x`, `iterate f x = x : iterate f (f x)`, and the
|
||||
classic `fibs = 0 : 1 : zipWith plus fibs (tail fibs)`
|
||||
terminate when only a finite prefix is consumed.
|
||||
2. Operators are now first-class values via a small
|
||||
`hk-make-binop-builtin` helper, so `(+)`, `(*)`, `(==)` etc.
|
||||
can be passed to `zipWith` and `map`.
|
||||
Added range support across parser + evaluator: `[from..to]` and
|
||||
`[from,next..to]` evaluate eagerly via `hk-build-range` (handles
|
||||
step direction); `[from..]` parses to a new `:range-from` node
|
||||
that the evaluator desugars to `iterate (+ 1) from`. New
|
||||
`hk-load-into!` runs the regular pipeline (parse → desugar →
|
||||
register data → bind decls) on a source string, and `hk-init-env`
|
||||
preloads `hk-prelude-src` with the Phase-3 Prelude:
|
||||
`head`, `tail`, `fst`, `snd`, `take`, `drop`, `repeat`, `iterate`,
|
||||
`length`, `map`, `filter`, `zipWith`, plus `fibs` and `plus`.
|
||||
25 new tests in `lib/haskell/tests/infinite.sx`, including
|
||||
`take 10 fibs == [0,1,1,2,3,5,8,13,21,34]`,
|
||||
`head (drop 99 [1..])`, `iterate (\x -> x * 2) 1` powers of two,
|
||||
user-defined `ones = 1 : ones`, `naturalsFrom`, range edge cases,
|
||||
composed `map`/`filter`, and a custom `mySum`. 359/359 green.
|
||||
Sieve of Eratosthenes is deferred — it needs lazy `++` plus a
|
||||
`mod` primitive — and lives under `Classic programs` anyway.
|
||||
|
||||
- **2026-04-24** — Phase 3 laziness foundation. Added a thunk type to
|
||||
`lib/haskell/eval.sx` (`hk-mk-thunk` / `hk-is-thunk?`) backed by a
|
||||
one-shot memoizing `hk-force` that evaluates the deferred AST, then
|
||||
flips a `forced` flag and caches the value on the thunk dict; the
|
||||
shared `hk-deep-force` walks the result tree at the test/output
|
||||
boundary. Three single-line wiring changes in the evaluator make
|
||||
every application argument lazy: `:app` now wraps its argument in
|
||||
`hk-mk-thunk` rather than evaluating it. To preserve correctness
|
||||
where values must be inspected, `hk-apply`, `hk-eval-op`,
|
||||
`hk-eval-if`, `hk-eval-case`, and `hk-eval` for `:neg` now force
|
||||
their operand. `hk-apply-builtin` forces every collected arg
|
||||
before invoking the underlying SX fn so built-ins (`error`, `not`,
|
||||
`id`) stay strict. The pattern matcher in `match.sx` now forces
|
||||
the scrutinee just-in-time only for patterns that need to inspect
|
||||
shape — `p-wild`, `p-var`, `p-as`, and `p-lazy` are no-force
|
||||
paths, so the value flows through as a thunk and binding
|
||||
preserves laziness. `hk-match-list-pat` forces at every cons-spine
|
||||
step. 6 new lazy-specific tests in `lib/haskell/tests/eval.sx`
|
||||
verify that `(\x y -> x) 1 (error …)` and `(\x y -> y) (error …) 99`
|
||||
return without diverging, that `case Just (error …) of Just _ -> 7`
|
||||
short-circuits, that `const` drops its second arg, that
|
||||
`myHead (1 : error … : [])` returns 1 without touching the tail,
|
||||
and that `Just (error …)` survives a wildcard-arm `case`. 333/333
|
||||
green, all prior eval tests preserved by deep-forcing the result
|
||||
in `hk-eval-expr-source` and `hk-prog-val`.
|
||||
|
||||
- **2026-04-24** — Phase 2 evaluator (`lib/haskell/eval.sx`) — ties
|
||||
the whole pipeline together. Strict semantics throughout (laziness
|
||||
is Phase 3). Function values are tagged dicts: `closure`,
|
||||
`multi`(fun), `con-partial`, `builtin`. `hk-apply` unifies dispatch
|
||||
across all four; closures and multifuns curry one argument at a
|
||||
time, multifuns trying each clause's pat-list in order once arity
|
||||
is reached. Top-level `hk-bind-decls!` is three-pass —
|
||||
collect groups + pre-seed names → install multifuns (so closures
|
||||
observe later names) → eval 0-arity bodies and pat-binds — making
|
||||
forward and mutually recursive references work. `hk-eval-let` does
|
||||
the same trick with a mutable child env. Built-ins:
|
||||
`error`/`not`/`id`, plus `otherwise = True`. Operators wired:
|
||||
arithmetic, comparison (returning Bool conses), `&&`, `||`, `:`,
|
||||
`++`. Sections evaluate the captured operand once and return a
|
||||
closure synthesized via the existing AST. `hk-eval-program`
|
||||
registers data decls then binds, returning the env; `hk-run`
|
||||
fetches `main` if present. Also extended `runtime.sx` to
|
||||
pre-register the standard Prelude conses (`Maybe`, `Either`,
|
||||
`Ordering`) so expression-level eval doesn't need a leading
|
||||
`data` decl. 48 new tests in `lib/haskell/tests/eval.sx` cover
|
||||
literals, arithmetic precedence, comparison/Bool, `if`, `let`
|
||||
(incl. recursive factorial), lambdas (incl. constructor pattern
|
||||
args), constructors, `case` (Just/Nothing/literal/tuple/wildcard),
|
||||
list literals + cons + `++`, tuples, sections, multi-clause
|
||||
top-level (factorial, list length via cons pattern, Maybe handler
|
||||
with default), user-defined `data` with case-style matching, a
|
||||
binary-tree height program, currying, higher-order (`twice`),
|
||||
short-circuit `error` via `if`, and the three built-ins. 329/329
|
||||
green. Phase 2 is now complete; Phase 3 (laziness) is next.
|
||||
|
||||
- **2026-04-24** — Phase 2: value-level pattern matcher
|
||||
(`lib/haskell/match.sx`). Core entry `hk-match pat val env` returns
|
||||
an extended env dict on success or `nil` on failure (uses `assoc`
|
||||
rather than `dict-set!` so failed branches never pollute the
|
||||
caller's env). Constructor values are tagged lists with the
|
||||
constructor name as the first element; tuples use the tag `"Tuple"`,
|
||||
lists are chained `(":" h t)` cons cells terminated by `("[]")`.
|
||||
Value builders `hk-mk-con` / `hk-mk-tuple` / `hk-mk-nil` /
|
||||
`hk-mk-cons` / `hk-mk-list` keep tests readable. The matcher
|
||||
handles every pattern node the parser emits:
|
||||
- `:p-wild` (always matches), `:p-var` (binds), `:p-int` /
|
||||
`:p-float` / `:p-string` / `:p-char` (literal equality)
|
||||
- `:p-as` (sub-match then bind whole), `:p-lazy` (eager for now;
|
||||
laziness wired in phase 3)
|
||||
- `:p-con` with arity check + recursive arg matching, including
|
||||
deeply nested patterns and infix `:` cons (uses the same
|
||||
code path as named constructors)
|
||||
- `:p-tuple` against `"Tuple"` values, `:p-list` against an
|
||||
exact-length cons spine.
|
||||
Helper `hk-parse-pat-source` lifts a real Haskell pattern out of
|
||||
`case _ of <pat> -> 0`, letting tests drive against parser output.
|
||||
31 new tests in `lib/haskell/tests/match.sx` cover atomic
|
||||
patterns, success/failure for each con/tuple/list shape, nested
|
||||
`Just (Just x)`, cons-vs-empty, `as` over con / wildcard /
|
||||
failing-sub, `~` lazy, plus four parser-driven cases (`Just x`,
|
||||
`x : xs`, `(a, b)`, `n@(Just x)`). 281/281 green.
|
||||
|
||||
- **2026-04-24** — Phase 2: runtime constructor registry
|
||||
(`lib/haskell/runtime.sx`). A mutable dict `hk-constructors` keyed
|
||||
by constructor name, each entry carrying arity and owning type.
|
||||
`hk-register-data!` walks a `:data` AST and registers every
|
||||
`:con-def` with its arity (= number of field types) and the type
|
||||
name; `hk-register-newtype!` does the one-constructor variant;
|
||||
`hk-register-decls!` / `hk-register-program!` filter a decls list
|
||||
(or a `:program` / `:module` AST) and call the appropriate
|
||||
registrar. `hk-load-source!` composes it with `hk-core`
|
||||
(tokenize → layout → parse → desugar → register). Pre-registers
|
||||
five built-ins tied to Haskell syntactic forms: `True` / `False`
|
||||
(Bool), `[]` and `:` (List), `()` (Unit) — everything else comes
|
||||
from user declarations or the eventual Prelude. Query helpers:
|
||||
`hk-is-con?`, `hk-con-arity`, `hk-con-type`, `hk-con-names`. 24
|
||||
new tests in `lib/haskell/tests/runtime.sx` cover each built-in
|
||||
(arity + type), unknown-name probes, registration of `MyBool` /
|
||||
`Maybe` / `Either` / recursive `Tree` / `newtype Age`, multi-data
|
||||
programs, a module-header body, ignoring non-data decls, and
|
||||
last-wins re-registration. 250/250 green.
|
||||
|
||||
- **2026-04-24** — Phase 2 kicks off with `lib/haskell/desugar.sx` — a
|
||||
tree-walking rewriter that eliminates the three surface-only forms
|
||||
produced by the parser, leaving a smaller core AST for the evaluator:
|
||||
- `:where BODY DECLS` → `:let DECLS BODY`
|
||||
- `:guarded ((:guard C1 E1) (:guard C2 E2) …)` → right-folded
|
||||
`(:if C1 E1 (:if C2 E2 … (:app (:var "error") (:string "…"))))`
|
||||
- `:list-comp E QUALS` → Haskell 98 §3.11 translation:
|
||||
empty quals → `(:list (E))`, `:q-guard` → `(:if … (:list (E)) (:list ()))`,
|
||||
`:q-gen PAT SRC` → `(concatMap (\PAT -> …) SRC)`, `:q-let BINDS` →
|
||||
`(:let BINDS …)`. Nested generators compile to nested concatMap.
|
||||
Every other expression, decl, pattern, and type node is recursed
|
||||
into and passed through unchanged. Public entries `hk-desugar`,
|
||||
`hk-core` (tokenize → layout → parse → desugar on a module), and
|
||||
`hk-core-expr` (the same for an expression). 15 new tests in
|
||||
`lib/haskell/tests/desugar.sx` cover two- and three-way guards,
|
||||
case-alt guards, single/multi-binding `where`, guards + `where`
|
||||
combined, the four list-comprehension cases (single-gen, gen +
|
||||
filter, gen + let, nested gens), and pass-through for literals,
|
||||
lambdas, simple fun-clauses, `data` decls, and a module header
|
||||
wrapping a guarded function. 226/226 green.
|
||||
|
||||
- **2026-04-24** — Phase 1 parser is now complete. This iteration adds
|
||||
operator sections and list comprehensions, the two remaining
|
||||
aexp-level forms, plus ticks the “AST design” item (the keyword-
|
||||
tagged list shape has accumulated a full HsSyn-level surface).
|
||||
Changes:
|
||||
- `hk-parse-infix` now bails on `op )` without consuming the op, so
|
||||
the paren parser can claim it as a left section.
|
||||
- `hk-parse-parens` rewritten to recognise five new forms:
|
||||
`()` (unit), `(op)` → `(:var OP)`, `(op e)` → `(:sect-right OP E)`
|
||||
(excluded for `-` so that `(- 5)` stays `(:neg 5)`), `(e op)` →
|
||||
`(:sect-left OP E)`, plus regular parens and tuples. Works for
|
||||
varsym, consym, reservedop `:`, and backtick-quoted varids.
|
||||
- `hk-section-op-info` inspects the current token and returns a
|
||||
`{:name :len}` dict, so the same logic handles 1-token ops and
|
||||
3-token backtick ops uniformly.
|
||||
- `hk-parse-list-lit` now recognises a `|` after the first element
|
||||
and dispatches to `hk-parse-qual` per qualifier (comma-separated),
|
||||
producing `(:list-comp EXPR QUALS)`. Qualifiers are:
|
||||
`(:q-gen PAT EXPR)` when a paren-balanced lookahead
|
||||
(`hk-comp-qual-is-gen?`) finds `<-` before the next `,`/`]`,
|
||||
`(:q-let BINDS)` for `let …`, and `(:q-guard EXPR)` otherwise.
|
||||
- `hk-parse-comp-let` accepts `]` or `,` as an implicit block close
|
||||
(single-line comprehensions never see layout's vrbrace before the
|
||||
qualifier terminator arrives); explicit `{ }` still closes
|
||||
strictly.
|
||||
22 new tests in `lib/haskell/tests/parser-sect-comp.sx` cover
|
||||
op-references (inc. `(-)`, `(:)`, backtick), right sections (inc.
|
||||
backtick), left sections, the `(- 5)` → `:neg` corner, plain parens
|
||||
and tuples, six comprehension shapes (simple, filter, let,
|
||||
nested-generators, constructor pattern bind, tuple pattern bind,
|
||||
and a three-qualifier mix). 211/211 green.
|
||||
|
||||
- **2026-04-24** — Phase 1: module header + imports. Added
|
||||
`hk-parse-module-header`, `hk-parse-import`, plus shared helpers for
|
||||
import/export entity lists (`hk-parse-ent`, `hk-parse-ent-member`,
|
||||
`hk-parse-ent-list`). New AST:
|
||||
- `(:module NAME EXPORTS IMPORTS DECLS)` — NAME `nil` means no header,
|
||||
EXPORTS `nil` means no export list (distinct from empty `()`)
|
||||
- `(:import QUALIFIED NAME AS SPEC)` — QUALIFIED bool, AS alias or nil,
|
||||
SPEC nil / `(:spec-items ENTS)` / `(:spec-hiding ENTS)`
|
||||
- Entity refs: `:ent-var`, `:ent-all` (`Tycon(..)`), `:ent-with`
|
||||
(`Tycon(m1, m2, …)`), `:ent-module` (exports only).
|
||||
`hk-parse-program` now dispatches on the leading token: `module`
|
||||
keyword → full header-plus-body parse (consuming the `where` layout
|
||||
brace around the module body); otherwise collect any leading
|
||||
`import` decls and then remaining decls with the existing logic.
|
||||
The outer shell is `(:module …)` as soon as any header or import is
|
||||
present, and stays as `(:program DECLS)` otherwise — preserving every
|
||||
previous test expectation untouched. Handles operator exports `((+:))`,
|
||||
dotted module names (`Data.Map`), and the Haskell-98 context-sensitive
|
||||
keywords `qualified`/`as`/`hiding` (all lexed as ordinary varids and
|
||||
matched only in import position). 16 new tests in
|
||||
`lib/haskell/tests/parser-module.sx` covering simple/exports/empty
|
||||
headers, dotted names, operator exports, `module Foo` exports,
|
||||
qualified/aliased/items/hiding imports, and a headerless-with-imports
|
||||
file. 189/189 green.
|
||||
|
||||
- **2026-04-24** — Phase 1: guards + where clauses. Factored a single
|
||||
`hk-parse-rhs sep` that all body-producing sites now share: it reads
|
||||
a plain `sep expr` body or a chain of `| cond sep expr` guards, then
|
||||
— regardless of which form — looks for an optional `where` block and
|
||||
wraps accordingly. AST additions:
|
||||
- `:guarded GUARDS` where each GUARD is `:guard COND EXPR`
|
||||
- `:where BODY DECLS` where BODY is a plain expr or a `:guarded`
|
||||
Both can nest (guards inside where). `hk-parse-alt` now routes through
|
||||
`hk-parse-rhs "->"`, `hk-parse-fun-clause` and `hk-parse-bind` through
|
||||
`hk-parse-rhs "="`. `hk-parse-where-decls` reuses `hk-parse-decl` so
|
||||
where-blocks accept any decl form (signatures, fixity, nested funs).
|
||||
As a side effect, `hk-parse-bind` now also picks up the Haskell-native
|
||||
`let f x = …` funclause shorthand: a varid followed by one or more
|
||||
apats produces `(:fun-clause NAME APATS BODY)` instead of a
|
||||
`(:bind (:p-var …) …)` — keeping the simple `let x = e` shape
|
||||
unchanged for existing tests. 11 new tests in
|
||||
`lib/haskell/tests/parser-guards-where.sx` cover two- and three-way
|
||||
guards, mixed guarded + equality clauses, single- and multi-binding
|
||||
where blocks, guards plus where, case-alt guards, case-alt where,
|
||||
let with funclause shorthand, let with guards, and a where containing
|
||||
a type signature alongside a fun-clause. 173/173 green.
|
||||
|
||||
- **2026-04-24** — Phase 1: top-level decls. Refactored `hk-parse-expr` into a
|
||||
`hk-parser tokens mode` with `:expr` / `:module` dispatch so the big lexical
|
||||
state is shared (peek/advance/pat/expr helpers all reachable); added public
|
||||
wrappers `hk-parse-expr`, `hk-parse-module`, and source-level entry
|
||||
`hk-parse-top`. New type parser (`hk-parse-type` / `hk-parse-btype` /
|
||||
`hk-parse-atype`): type variables (`:t-var`), type constructors (`:t-con`),
|
||||
type application (`:t-app`, left-assoc), right-associative function arrow
|
||||
(`:t-fun`), unit/tuples (`:t-tuple`), and lists (`:t-list`). New decl parser
|
||||
(`hk-parse-decl` / `hk-parse-program`) producing a `(:program DECLS)` shell:
|
||||
- `:type-sig NAMES TYPE` — comma-separated multi-name support
|
||||
- `:fun-clause NAME APATS BODY` — patterns for args, body via existing expr
|
||||
- `:pat-bind PAT BODY` — top-level pattern bindings like `(a, b) = pair`
|
||||
- `:data NAME TVARS CONS` with `:con-def CNAME FIELDS` for nullary and
|
||||
multi-arg constructors, including recursive references
|
||||
- `:type-syn NAME TVARS TYPE`, `:newtype NAME TVARS CNAME FIELD`
|
||||
- `:fixity ASSOC PREC OPS` — assoc one of `"l"`/`"r"`/`"n"`, default prec 9,
|
||||
comma-separated operator names, including backtick-quoted varids.
|
||||
Sig vs fun-clause disambiguated by a paren-balanced top-level scan for
|
||||
`::` before the next `;`/`}` (`hk-has-top-dcolon?`). 24 new tests in
|
||||
`lib/haskell/tests/parser-decls.sx` cover all decl forms, signatures with
|
||||
application / tuples / lists / right-assoc arrows, nullary and recursive
|
||||
data types, multi-clause functions, and a mixed program with data + type-
|
||||
synonym + signature + two function clauses. Not yet: guards, where
|
||||
clauses, module header, imports, deriving, contexts, GADTs. 162/162 green.
|
||||
|
||||
- **2026-04-24** — Phase 1: full patterns. Added `as` patterns
|
||||
(`name@apat` → `(:p-as NAME PAT)`), lazy patterns (`~apat` →
|
||||
`(:p-lazy PAT)`), negative literal patterns (`-N` / `-F` resolving
|
||||
eagerly in the parser so downstream passes see a plain `(:p-int -1)`),
|
||||
and infix constructor patterns via a right-associative single-band
|
||||
layer on top of `hk-parse-pat-lhs` for any `consym` or reservedop `:`
|
||||
(so `x : xs` parses as `(:p-con ":" [x, xs])`, `a :+: b` likewise).
|
||||
Extended `hk-apat-start?` with `-` and `~` so the pattern-argument
|
||||
loops in lambdas and constructor applications pick these up.
|
||||
Lambdas now parse apat parameters instead of bare varids — so the
|
||||
`:lambda` AST is `(:lambda APATS BODY)` with apats as pattern nodes.
|
||||
`hk-parse-bind` became a plain `pat = expr` form, so `:bind` now has
|
||||
a pattern LHS throughout (simple `x = 1` → `(:bind (:p-var "x") …)`);
|
||||
this picks up `let (x, y) = pair in …` and `let Just x = m in x`
|
||||
automatically, and flows through `do`-notation lets. Eight existing
|
||||
tests updated to the pattern-flavoured AST. Also fixed a pragmatic
|
||||
layout issue that surfaced in multi-line `let`s: when a layout-indent
|
||||
would emit a spurious `;` just before an `in` token (because the
|
||||
let block had already been closed by dedent), `hk-peek-next-reserved`
|
||||
now lets the layout pass skip that indent and leave closing to the
|
||||
existing `in` handler. 18 new tests in
|
||||
`lib/haskell/tests/parser-patterns.sx` cover every pattern variant,
|
||||
lambda with mixed apats, let pattern-bindings (tuple / constructor /
|
||||
cons), and do-bind with a tuple pattern. 138/138 green.
|
||||
|
||||
- **2026-04-24** — Phase 1: `case … of` and `do`-notation parsers. Added `hk-parse-case`
|
||||
/ `hk-parse-alt`, `hk-parse-do` / `hk-parse-do-stmt` / `hk-parse-do-let`, plus the
|
||||
minimal pattern language needed to make arms and binds meaningful:
|
||||
`hk-parse-apat` (var, wildcard `_`, int/float/string/char literal, 0-arity
|
||||
conid/qconid, paren+tuple, list) and `hk-parse-pat` (conid applied to
|
||||
apats greedily). AST nodes: `:case SCRUT ALTS`, `:alt PAT BODY`, `:do STMTS`
|
||||
with stmts `:do-expr E` / `:do-bind PAT E` / `:do-let BINDS`, and pattern
|
||||
tags `:p-wild` / `:p-int` / `:p-float` / `:p-string` / `:p-char` / `:p-var`
|
||||
/ `:p-con NAME ARGS` / `:p-tuple` / `:p-list`. `do`-stmts disambiguate
|
||||
`pat <- e` vs bare expression with a forward paren/bracket/brace-balanced
|
||||
scan for `<-` before the next `;`/`}` — no backtracking, no AST rewrite.
|
||||
`case` and `do` accept both implicit (`vlbrace`/`vsemi`/`vrbrace`) and
|
||||
explicit braces. Added to `hk-parse-lexp` so they participate fully in
|
||||
operator-precedence expressions. 19 new tests in
|
||||
`lib/haskell/tests/parser-case-do.sx` cover every pattern variant,
|
||||
explicit-brace `case`, expression scrutinees, do with bind/let/expr,
|
||||
multi-binding `let` in `do`, constructor patterns in binds, and
|
||||
`case`/`do` nested inside `let` and lambda. The full pattern item (as
|
||||
patterns, negative literals, `~` lazy, lambda/let pattern extension)
|
||||
remains a separate sub-item. 119/119 green.
|
||||
|
||||
- **2026-04-24** — Phase 1: expression parser (`lib/haskell/parser.sx`, ~380 lines).
|
||||
Pratt-style precedence climbing against a Haskell-98-default op table (24
|
||||
operators across precedence 0–9, left/right/non assoc, default infixl 9 for
|
||||
anything unlisted). Supports literals (int/float/string/char), varid/conid
|
||||
(qualified variants folded into `:var` / `:con`), parens / unit / tuples,
|
||||
list literals, ranges `[a..b]` and `[a,b..c]`, left-associative application,
|
||||
unary `-`, backtick operators (`x \`mod\` 3`), lambdas, `if-then-else`, and
|
||||
`let … in` consuming both virtual and explicit braces. AST uses keyword
|
||||
tags (`:var`, `:op`, `:lambda`, `:let`, `:bind`, `:tuple`, `:range`,
|
||||
`:range-step`, `:app`, `:neg`, `:if`, `:list`, `:int`, `:float`, `:string`,
|
||||
`:char`, `:con`). The parser skips a leading `vlbrace` / `lbrace` so it can
|
||||
be called on full post-layout output, and uses a `raise`-based error channel
|
||||
with location-lite messages. 42 new tests in `lib/haskell/tests/parser-expr.sx`
|
||||
cover literals, identifiers, parens/tuple/unit, list + range, app associativity,
|
||||
operator precedence (mul over add, cons right-assoc, function-composition
|
||||
right-assoc, `$` lowest), backtick ops, unary `-`, lambda multi-param,
|
||||
`if` with infix condition, single- and multi-binding `let` (both implicit
|
||||
and explicit braces), plus a few mixed nestings. 100/100 green.
|
||||
|
||||
- **2026-04-24** — Phase 1: layout algorithm (`lib/haskell/layout.sx`, ~260 lines)
|
||||
implementing Haskell 98 §10.3. Two-pass design: a pre-pass augments the raw
|
||||
token stream with explicit `layout-open` / `layout-indent` markers (suppressing
|
||||
`<n>` when `{n}` already applies, per note 3), then an L pass consumes the
|
||||
augmented stream against a stack of implicit/explicit layout contexts and
|
||||
emits `vlbrace` / `vsemi` / `vrbrace` tokens; newlines are dropped. Supports
|
||||
the initial module-level implicit open (skipped when the first token is
|
||||
`module` or `{`), the four layout keywords (`let`/`where`/`do`/`of`), explicit
|
||||
braces disabling layout, dedent closing nested implicit blocks while also
|
||||
emitting `vsemi` at the enclosing level, and the pragmatic single-line
|
||||
`let … in` rule (emit `}` when `in` meets an implicit let). 15 new tests
|
||||
in `lib/haskell/tests/layout.sx` cover module-start, do/let/where/case/of,
|
||||
explicit braces, multi-level dedent, line continuation, and EOF close-down.
|
||||
Shared test helpers moved to `lib/haskell/testlib.sx` so both test files
|
||||
can share one `hk-test`. `test.sh` preloads tokenizer + layout + testlib.
|
||||
58/58 green.
|
||||
|
||||
- **2026-04-24** — Phase 1: Haskell 98 tokenizer (`lib/haskell/tokenizer.sx`, 490 lines)
|
||||
covering idents (lower/upper/qvarid/qconid), 23 reserved words, 11 reserved ops,
|
||||
varsym/consym operator chains, integer/hex/octal/float literals incl. exponent
|
||||
|
||||
Reference in New Issue
Block a user