Compare commits
3 Commits
lib/smallt
...
loops/hask
| Author | SHA1 | Date | |
|---|---|---|---|
| 4510e7e475 | |||
| aa620b767f | |||
| 23afc9dde3 |
@@ -210,6 +210,7 @@
|
|||||||
:op (nth node 1)
|
:op (nth node 1)
|
||||||
(hk-desugar (nth node 2))
|
(hk-desugar (nth node 2))
|
||||||
(hk-desugar (nth node 3))))
|
(hk-desugar (nth node 3))))
|
||||||
|
((= tag "type-ann") (hk-desugar (nth node 1)))
|
||||||
((= tag "neg") (list :neg (hk-desugar (nth node 1))))
|
((= tag "neg") (list :neg (hk-desugar (nth node 1))))
|
||||||
((= tag "if")
|
((= tag "if")
|
||||||
(list
|
(list
|
||||||
|
|||||||
@@ -275,38 +275,47 @@
|
|||||||
(list :sect-right op-name expr-e))))))
|
(list :sect-right op-name expr-e))))))
|
||||||
(:else
|
(:else
|
||||||
(let
|
(let
|
||||||
((first-e (hk-parse-expr-inner))
|
((first-e (hk-parse-expr-inner)))
|
||||||
(items (list))
|
|
||||||
(is-tuple false))
|
|
||||||
(append! items first-e)
|
|
||||||
(define
|
|
||||||
hk-tup-loop
|
|
||||||
(fn
|
|
||||||
()
|
|
||||||
(when
|
|
||||||
(hk-match? "comma" nil)
|
|
||||||
(do
|
|
||||||
(hk-advance!)
|
|
||||||
(set! is-tuple true)
|
|
||||||
(append! items (hk-parse-expr-inner))
|
|
||||||
(hk-tup-loop)))))
|
|
||||||
(hk-tup-loop)
|
|
||||||
(cond
|
(cond
|
||||||
((hk-match? "rparen" nil)
|
((hk-match? "reservedop" "::")
|
||||||
(do
|
(do
|
||||||
(hk-advance!)
|
(hk-advance!)
|
||||||
(if is-tuple (list :tuple items) first-e)))
|
(let
|
||||||
|
((ann-type (hk-parse-type)))
|
||||||
|
(hk-expect! "rparen" nil)
|
||||||
|
(list :type-ann first-e ann-type))))
|
||||||
(:else
|
(:else
|
||||||
(let
|
(let
|
||||||
((op-info2 (hk-section-op-info)))
|
((items (list)) (is-tuple false))
|
||||||
|
(append! items first-e)
|
||||||
|
(define
|
||||||
|
hk-tup-loop
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(when
|
||||||
|
(hk-match? "comma" nil)
|
||||||
|
(do
|
||||||
|
(hk-advance!)
|
||||||
|
(set! is-tuple true)
|
||||||
|
(append! items (hk-parse-expr-inner))
|
||||||
|
(hk-tup-loop)))))
|
||||||
|
(hk-tup-loop)
|
||||||
(cond
|
(cond
|
||||||
((and (not (nil? op-info2)) (not is-tuple) (let ((after2 (hk-peek-at (get op-info2 "len")))) (and (not (nil? after2)) (= (get after2 "type") "rparen"))))
|
((hk-match? "rparen" nil)
|
||||||
(let
|
(do
|
||||||
((op-name (get op-info2 "name")))
|
|
||||||
(hk-consume-op!)
|
|
||||||
(hk-advance!)
|
(hk-advance!)
|
||||||
(list :sect-left op-name first-e)))
|
(if is-tuple (list :tuple items) first-e)))
|
||||||
(:else (hk-err "expected ')' after expression"))))))))))))))
|
(:else
|
||||||
|
(let
|
||||||
|
((op-info2 (hk-section-op-info)))
|
||||||
|
(cond
|
||||||
|
((and (not (nil? op-info2)) (not is-tuple) (let ((after2 (hk-peek-at (get op-info2 "len")))) (and (not (nil? after2)) (= (get after2 "type") "rparen"))))
|
||||||
|
(let
|
||||||
|
((op-name (get op-info2 "name")))
|
||||||
|
(hk-consume-op!)
|
||||||
|
(hk-advance!)
|
||||||
|
(list :sect-left op-name first-e)))
|
||||||
|
(:else (hk-err "expected ')' after expression")))))))))))))))))
|
||||||
(define
|
(define
|
||||||
hk-comp-qual-is-gen?
|
hk-comp-qual-is-gen?
|
||||||
(fn
|
(fn
|
||||||
@@ -1724,10 +1733,18 @@
|
|||||||
(= (hk-peek-type) "eof")
|
(= (hk-peek-type) "eof")
|
||||||
(hk-match? "vrbrace" nil)
|
(hk-match? "vrbrace" nil)
|
||||||
(hk-match? "rbrace" nil))))
|
(hk-match? "rbrace" nil))))
|
||||||
|
(define
|
||||||
|
hk-body-step
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(cond
|
||||||
|
((hk-match? "reserved" "import")
|
||||||
|
(append! imports (hk-parse-import)))
|
||||||
|
(:else (append! decls (hk-parse-decl))))))
|
||||||
(when
|
(when
|
||||||
(not (hk-body-at-end?))
|
(not (hk-body-at-end?))
|
||||||
(do
|
(do
|
||||||
(append! decls (hk-parse-decl))
|
(hk-body-step)
|
||||||
(define
|
(define
|
||||||
hk-body-loop
|
hk-body-loop
|
||||||
(fn
|
(fn
|
||||||
@@ -1738,7 +1755,7 @@
|
|||||||
(hk-advance!)
|
(hk-advance!)
|
||||||
(when
|
(when
|
||||||
(not (hk-body-at-end?))
|
(not (hk-body-at-end?))
|
||||||
(append! decls (hk-parse-decl)))
|
(hk-body-step))
|
||||||
(hk-body-loop)))))
|
(hk-body-loop)))))
|
||||||
(hk-body-loop)))
|
(hk-body-loop)))
|
||||||
(list imports decls))))
|
(list imports decls))))
|
||||||
|
|||||||
102
lib/haskell/tests/parse-extras.sx
Normal file
102
lib/haskell/tests/parse-extras.sx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
;; Phase 17 — parser polish unit tests.
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: literal int annotated"
|
||||||
|
(hk-deep-force (hk-run "main = (42 :: Int)"))
|
||||||
|
42)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: arithmetic annotated"
|
||||||
|
(hk-deep-force (hk-run "main = (1 + 2 :: Int)"))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: function arg annotated"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "f x = x + 1\nmain = f (1 :: Int)"))
|
||||||
|
2)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: string annotated"
|
||||||
|
(hk-deep-force (hk-run "main = (\"hi\" :: String)"))
|
||||||
|
"hi")
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: bool annotated"
|
||||||
|
(hk-deep-force (hk-run "main = (True :: Bool)"))
|
||||||
|
(list "True"))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: tuple annotated"
|
||||||
|
(hk-deep-force (hk-run "main = ((1, 2) :: (Int, Int))"))
|
||||||
|
(list "Tuple" 1 2))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: nested annotation in arithmetic"
|
||||||
|
(hk-deep-force (hk-run "main = (1 :: Int) + (2 :: Int)"))
|
||||||
|
3)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"type-ann: function-typed annotation passes through eval"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "main = let f = ((\\x -> x + 1) :: Int -> Int) in f 5"))
|
||||||
|
6)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"no regression: plain parens still work"
|
||||||
|
(hk-deep-force (hk-run "main = (5)"))
|
||||||
|
5)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"no regression: 3-tuple still works"
|
||||||
|
(hk-deep-force (hk-run "main = (1, 2, 3)"))
|
||||||
|
(list "Tuple" 1 2 3))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"no regression: section-left still works"
|
||||||
|
(hk-deep-force (hk-run "main = (3 +) 4"))
|
||||||
|
7)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"no regression: section-right still works"
|
||||||
|
(hk-deep-force (hk-run "main = (+ 3) 4"))
|
||||||
|
7)
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"import: still works as the very first decl"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "import qualified Data.IORef as I
|
||||||
|
main = do { r <- I.newIORef 7; I.readIORef r }"))
|
||||||
|
(list "IO" 7))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"import: between decls — after main"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "main = do { r <- I.newIORef 11; I.readIORef r }
|
||||||
|
import qualified Data.IORef as I"))
|
||||||
|
(list "IO" 11))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"import: between two decls — uses helper after import"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "f x = x + 100
|
||||||
|
import qualified Data.IORef as I
|
||||||
|
main = do { r <- I.newIORef 5; I.modifyIORef r f; I.readIORef r }"))
|
||||||
|
(list "IO" 105))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"import: two imports in different positions"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "import qualified Data.IORef as I
|
||||||
|
helper x = x * 2
|
||||||
|
import qualified Data.Map as M
|
||||||
|
main = do { r <- I.newIORef (helper 21); I.readIORef r }"))
|
||||||
|
(list "IO" 42))
|
||||||
|
|
||||||
|
(hk-test
|
||||||
|
"import: unqualified, mid-file"
|
||||||
|
(hk-deep-force
|
||||||
|
(hk-run "go x = x
|
||||||
|
import Data.IORef
|
||||||
|
main = go 9"))
|
||||||
|
9)
|
||||||
@@ -16,15 +16,18 @@
|
|||||||
true)))
|
true)))
|
||||||
|
|
||||||
;; ─── Valid programs pass through ─────────────────────────────────────────────
|
;; ─── Valid programs pass through ─────────────────────────────────────────────
|
||||||
(hk-test "typed ok: simple arithmetic" (hk-run-typed "main = 1 + 2") 3)
|
(hk-test "typed ok: simple arithmetic"
|
||||||
|
(hk-deep-force (hk-run-typed "main = 1 + 2")) 3)
|
||||||
|
|
||||||
(hk-test "typed ok: boolean" (hk-run-typed "main = True") (list "True"))
|
(hk-test "typed ok: boolean"
|
||||||
|
(hk-deep-force (hk-run-typed "main = True")) (list "True"))
|
||||||
|
|
||||||
(hk-test "typed ok: let binding" (hk-run-typed "main = let x = 1 in x + 2") 3)
|
(hk-test "typed ok: let binding"
|
||||||
|
(hk-deep-force (hk-run-typed "main = let x = 1 in x + 2")) 3)
|
||||||
|
|
||||||
(hk-test
|
(hk-test
|
||||||
"typed ok: two independent fns"
|
"typed ok: two independent fns"
|
||||||
(hk-run-typed "f x = x + 1\nmain = f 5")
|
(hk-deep-force (hk-run-typed "f x = x + 1\nmain = f 5"))
|
||||||
6)
|
6)
|
||||||
|
|
||||||
;; ─── Untypeable programs are rejected ────────────────────────────────────────
|
;; ─── Untypeable programs are rejected ────────────────────────────────────────
|
||||||
@@ -76,7 +79,7 @@
|
|||||||
|
|
||||||
(hk-test
|
(hk-test
|
||||||
"run-typed sig ok: Int declared matches"
|
"run-typed sig ok: Int declared matches"
|
||||||
(hk-run-typed "main :: Int\nmain = 1 + 2")
|
(hk-deep-force (hk-run-typed "main :: Int\nmain = 1 + 2"))
|
||||||
3)
|
3)
|
||||||
|
|
||||||
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
|
||||||
@@ -316,11 +316,11 @@ No OCaml changes are needed. The view type is fully representable as an SX dict.
|
|||||||
Real Haskell programs use these on every page; closing the gaps unblocks
|
Real Haskell programs use these on every page; closing the gaps unblocks
|
||||||
larger conformance programs and removes one-line workarounds in test sources.
|
larger conformance programs and removes one-line workarounds in test sources.
|
||||||
|
|
||||||
- [ ] Type annotations in expressions: `(x :: Int)`, `f (1 :: Int)`,
|
- [x] Type annotations in expressions: `(x :: Int)`, `f (1 :: Int)`,
|
||||||
`return (42 :: Int)`. Parser currently rejects `::` in `aexp` position;
|
`return (42 :: Int)`. Parser currently rejects `::` in `aexp` position;
|
||||||
desugar should drop the annotation (we have no inference at this layer
|
desugar should drop the annotation (we have no inference at this layer
|
||||||
yet, so it's a parse-only pass-through).
|
yet, so it's a parse-only pass-through).
|
||||||
- [ ] `import` declarations anywhere at the start of a module — currently
|
- [x] `import` declarations anywhere at the start of a module — currently
|
||||||
only the very-top-of-file form is recognised. Real test programs that
|
only the very-top-of-file form is recognised. Real test programs that
|
||||||
mix prelude code with `import qualified Data.IORef` need this.
|
mix prelude code with `import qualified Data.IORef` need this.
|
||||||
- [ ] Multi-line top-level `where` blocks (`where { ... }` with explicit
|
- [ ] Multi-line top-level `where` blocks (`where { ... }` with explicit
|
||||||
@@ -359,10 +359,100 @@ that to single-digit minutes.
|
|||||||
- [ ] Verify the scoreboard output is byte-identical to the old per-process
|
- [ ] Verify the scoreboard output is byte-identical to the old per-process
|
||||||
driver, then keep the per-process path as `--isolated` for debugging.
|
driver, then keep the per-process path as `--isolated` for debugging.
|
||||||
|
|
||||||
|
### Phase 20 — Close Algorithm W gaps
|
||||||
|
|
||||||
|
`lib/haskell/infer.sx` already implements core HM (TVar/TCon/TArr/TApp/TTuple/
|
||||||
|
TScheme, substitution, occurs-check unification, instantiate/generalize, let-
|
||||||
|
polymorphism). 75 inference unit tests + 15 typecheck integration tests pass.
|
||||||
|
The remaining gaps that block typing real programs:
|
||||||
|
|
||||||
|
- [ ] `case` expressions in `hk-w`. Needs to infer scrutinee type, then for
|
||||||
|
each `(:alt pat body)` infer the pattern's binding env (extending
|
||||||
|
`hk-w-pat`) and unify body types across alts.
|
||||||
|
- [ ] `do` notation: extend `hk-type-env0` with `return :: a -> IO a`,
|
||||||
|
`(>>=) :: IO a -> (a -> IO b) -> IO b`, `(>>) :: IO a -> IO b -> IO b`,
|
||||||
|
and primitive IO actions (`putStrLn :: String -> IO ()`,
|
||||||
|
`getLine :: IO String`, etc.). May need a `TApp (TCon "IO") a` shape.
|
||||||
|
- [ ] Record-accessor desugaring leaves `__rec_field` placeholder visible to
|
||||||
|
inference. Either skip generated accessor clauses during `hk-infer-prog`
|
||||||
|
or rewrite the desugar to produce a typed shape.
|
||||||
|
- [ ] Type annotations in expressions `(x :: Int)` (parser also needed; see
|
||||||
|
Phase 17). Infer should unify the inferred type with the annotation.
|
||||||
|
- [ ] Tests in `lib/haskell/tests/infer-extras.sx` (≥ 10) covering the
|
||||||
|
above shapes.
|
||||||
|
|
||||||
|
### Phase 21 — Type classes (Eq, Ord, Num, Show)
|
||||||
|
|
||||||
|
The evaluator already implements typeclass dispatch via dict-passing
|
||||||
|
(`__default__ClassName_method` + per-instance dicts). The type system
|
||||||
|
ignores `class` and `instance` decls. Closing this means HM with
|
||||||
|
constraints (qualified types `[ClassName var] => type`).
|
||||||
|
|
||||||
|
- [ ] Extend the type representation: `(TQual CONSTRAINTS TYPE)` where
|
||||||
|
`CONSTRAINTS = [(class-name . type-arg), …]`.
|
||||||
|
- [ ] Generalize → `forall vars. preds => type`; instantiate → fresh-rename
|
||||||
|
vars in both preds and type.
|
||||||
|
- [ ] During inference, when a primitive operator that needs a class is
|
||||||
|
used (e.g. `+`), emit a constraint `(Num t)`; collect constraints in
|
||||||
|
the substitution-threading.
|
||||||
|
- [ ] At let-generalization, simplify constraints (defaulting for `Num`
|
||||||
|
literals → `Int`; entailment via known instances).
|
||||||
|
- [ ] `class` declarations register members with their qualified type;
|
||||||
|
`instance` declarations register a witness.
|
||||||
|
- [ ] At top-level, if any unsolvable constraint remains → type error
|
||||||
|
("No instance for X").
|
||||||
|
- [ ] Tests in `lib/haskell/tests/typeclasses.sx` (≥ 12 covering Eq, Ord,
|
||||||
|
Num overloading, show on instances, instance ambiguity rejection).
|
||||||
|
|
||||||
|
### Phase 22 — Typecheck-then-run as the default
|
||||||
|
|
||||||
|
- [ ] Replace `hk-run` with a typecheck-first variant in the conformance
|
||||||
|
driver, or run conformance twice (once typed, once untyped) and report
|
||||||
|
both pass-rates in `scoreboard.md`.
|
||||||
|
- [ ] Investigate which existing 36 programs are untypeable due to gaps
|
||||||
|
closed in Phase 20-21 vs genuinely dynamically-typed; aim for ≥ 30/36
|
||||||
|
programs typechecking before committing to the swap.
|
||||||
|
- [ ] If swap is committed, retire `hk-run` callsites in tests in favour
|
||||||
|
of `hk-run-typed`; keep the untyped path available for parser/eval
|
||||||
|
development against in-progress features.
|
||||||
|
|
||||||
## Progress log
|
## Progress log
|
||||||
|
|
||||||
_Newest first._
|
_Newest first._
|
||||||
|
|
||||||
|
**2026-05-10** — Phase 17 second box: `import` declarations anywhere among
|
||||||
|
top-level decls. `hk-collect-module-body` previously ran a fixed
|
||||||
|
import-loop at the start, then a separate decl-loop; merged into a single
|
||||||
|
`hk-body-step` dispatcher that routes `import` to the imports list and
|
||||||
|
everything else to `hk-parse-decl`. Each call site (initial step + post-
|
||||||
|
semicolon loop) now uses the dispatcher. Imports collected mid-stream
|
||||||
|
still feed into `hk-bind-decls!` correctly because the eval side reads
|
||||||
|
them via the imports list, not by AST position. tests/parse-extras.sx
|
||||||
|
12 → 17 covering very-top, mid-stream, post-main, two-imports-different-
|
||||||
|
positions, and unqualified mid-file. Regression: eval 66/0, exceptions
|
||||||
|
14/0, typecheck 15/0, records 14/0, ioref 13/0, map 26/0, set 17/0.
|
||||||
|
|
||||||
|
**2026-05-08** — Phase 17 first box: expression type annotations `(x :: Int)`,
|
||||||
|
`f (1 :: Int)`, `(\x -> x+1) :: Int -> Int`. Parser's `hk-parse-parens`
|
||||||
|
gains a `::` arm after the first inner expression: consume `::`, parse a
|
||||||
|
type via the existing `hk-parse-type`, expect `)`, emit `(:type-ann EXPR
|
||||||
|
TYPE)`. Desugar drops the annotation — `:type-ann E _ → (hk-desugar E)` —
|
||||||
|
since the existing eval path has no type-directed dispatch; Phase 20 will
|
||||||
|
let inference consume the annotation. tests/parse-extras.sx 12/12; eval,
|
||||||
|
exceptions, typecheck, records, ioref still clean.
|
||||||
|
|
||||||
|
**2026-05-08** — Plan extends with Phases 20-22 (HM type system). Discovered
|
||||||
|
during planning that `lib/haskell/infer.sx` already lands core Algorithm W
|
||||||
|
(75 inference unit tests pass; let-polymorphism, sig checking, error
|
||||||
|
reporting via `hk-expr->brief`). Fixed five regressing tests in
|
||||||
|
`lib/haskell/tests/typecheck.sx` that compared an unforced thunk against
|
||||||
|
the expected value — added `hk-deep-force` around `hk-run-typed` to match
|
||||||
|
the existing untyped-path convention. typecheck.sx now 15/15.
|
||||||
|
Phase 20 captures the remaining Algorithm W gaps (case, do, record
|
||||||
|
accessors, expression annotations); Phase 21 captures type classes with
|
||||||
|
qualified types; Phase 22 captures the integration step (typecheck-then-run
|
||||||
|
across conformance).
|
||||||
|
|
||||||
**2026-05-08** — Phase 16 Exception handling complete (6 ops + module wiring +
|
**2026-05-08** — Phase 16 Exception handling complete (6 ops + module wiring +
|
||||||
14 unit tests + 2 conformance programs). `hk-bind-exceptions!` in `eval.sx`
|
14 unit tests + 2 conformance programs). `hk-bind-exceptions!` in `eval.sx`
|
||||||
registers `throwIO`, `throw`, `evaluate`, `catch`, `try`, `handle`, and
|
registers `throwIO`, `throw`, `evaluate`, `catch`, `try`, `handle`, and
|
||||||
|
|||||||
Reference in New Issue
Block a user