Files
rose-ash/lib/haskell/parser.sx
giles 1c45262577
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 34s
haskell: deriving (Eq, Show) for ADTs (+11 tests, 565/565)
Parser parses optional deriving clause; only appended to AST when non-empty.
hk-bind-decls! data arm generates dictShow_Con / dictEq_Con per constructor.
hk-binop == and /= now deep-force both sides (SX dict equality is by
reference — two thunks wrapping the same value compared as not-equal without
this). Three token-type fixes in the deriving parser (lparen/rparen/comma,
not "special").

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:25:51 +00:00

1659 lines
59 KiB
Plaintext

;; Haskell 98 expression parser.
;;
;; Input: the post-layout token list from (hk-layout (hk-tokenize src)).
;; Output: an AST. Nodes are plain lists tagged by a keyword head
;; (keywords evaluate to their string name, so `(list :var "x")` is
;; indistinguishable from `(list "var" "x")` at runtime — this lets
;; tests literally write `(list :var "x")` on both sides).
;;
;; Scope (this iteration — expressions only):
;; atoms int/float/string/char/var/con, parens, tuple, list, range
;; application left-associative, f x y z
;; prefix - unary negation on an lexp
;; infix ops precedence-climbing, full Haskell 98 default table
;; lambda \x y -> body
;; if if c then t else e
;; let let { x = e ; y = e } in body (uses layout braces)
;;
;; AST shapes:
;; (:int N)
;; (:float F)
;; (:string S)
;; (:char C)
;; (:var NAME)
;; (:con NAME)
;; (:app FN ARG) — binary, chain for multi-arg
;; (:op OP LHS RHS) — binary infix
;; (:neg E)
;; (:tuple ITEMS) — ITEMS is a list of AST nodes
;; (:list ITEMS) — enumerated list
;; (:range FROM TO) — [from..to]
;; (:range-step FROM NEXT TO) — [from,next..to]
;; (:if C T E)
;; (:lambda PARAMS BODY) — PARAMS is list of varids
;; (:let BINDS BODY) — BINDS is list of (:bind NAME EXPR)
;; ── Operator precedence table (Haskell 98 defaults) ──────────────
(define
hk-op-prec-table
(let
((t (dict)))
(dict-set! t "!!" {:prec 9 :assoc "left"})
(dict-set! t "." {:prec 9 :assoc "right"})
(dict-set! t "^" {:prec 8 :assoc "right"})
(dict-set! t "^^" {:prec 8 :assoc "right"})
(dict-set! t "**" {:prec 8 :assoc "right"})
(dict-set! t "*" {:prec 7 :assoc "left"})
(dict-set! t "/" {:prec 7 :assoc "left"})
(dict-set! t "+" {:prec 6 :assoc "left"})
(dict-set! t "-" {:prec 6 :assoc "left"})
(dict-set! t ":" {:prec 5 :assoc "right"})
(dict-set! t "++" {:prec 5 :assoc "right"})
(dict-set! t "==" {:prec 4 :assoc "non"})
(dict-set! t "/=" {:prec 4 :assoc "non"})
(dict-set! t "<" {:prec 4 :assoc "non"})
(dict-set! t "<=" {:prec 4 :assoc "non"})
(dict-set! t ">" {:prec 4 :assoc "non"})
(dict-set! t ">=" {:prec 4 :assoc "non"})
(dict-set! t "&&" {:prec 3 :assoc "right"})
(dict-set! t "||" {:prec 2 :assoc "right"})
(dict-set! t ">>" {:prec 1 :assoc "left"})
(dict-set! t ">>=" {:prec 1 :assoc "left"})
(dict-set! t "=<<" {:prec 1 :assoc "right"})
(dict-set! t "$" {:prec 0 :assoc "right"})
(dict-set! t "$!" {:prec 0 :assoc "right"})
t))
(define
hk-op-info
(fn
(op)
(if
(has-key? hk-op-prec-table op)
(get hk-op-prec-table op)
{:prec 9 :assoc "left"})))
;; ── Atom-start predicate ─────────────────────────────────────────
(define
hk-atom-start?
(fn
(tok)
(if
(nil? tok)
false
(let
((ty (get tok "type")))
(or
(= ty "integer")
(= ty "float")
(= ty "string")
(= ty "char")
(= ty "varid")
(= ty "conid")
(= ty "qvarid")
(= ty "qconid")
(= ty "lparen")
(= ty "lbracket"))))))
;; apat-start? — what can begin an atomic pattern
(define
hk-apat-start?
(fn
(tok)
(if
(nil? tok)
false
(let
((ty (get tok "type")) (val (get tok "value")))
(or
(and (= ty "reserved") (= val "_"))
(= ty "integer")
(= ty "float")
(= ty "string")
(= ty "char")
(= ty "varid")
(= ty "conid")
(= ty "qconid")
(= ty "lparen")
(= ty "lbracket")
(and (= ty "varsym") (= val "-"))
(and (= ty "reservedop") (= val "~")))))))
;; ── Atype-start predicate (types) ───────────────────────────────
(define
hk-atype-start?
(fn
(tok)
(if
(nil? tok)
false
(let
((ty (get tok "type")))
(or
(= ty "conid")
(= ty "qconid")
(= ty "varid")
(= ty "lparen")
(= ty "lbracket"))))))
;; ── Main entry ───────────────────────────────────────────────────
(define
hk-parser
(fn
(tokens mode)
(let
((toks tokens) (pos 0) (n (len tokens)))
(define hk-peek (fn () (if (< pos n) (nth toks pos) nil)))
(define
hk-peek-at
(fn
(offset)
(if (< (+ pos offset) n) (nth toks (+ pos offset)) nil)))
(define
hk-advance!
(fn () (let ((t (hk-peek))) (set! pos (+ pos 1)) t)))
(define hk-next hk-advance!)
(define
hk-peek-type
(fn
()
(let ((t (hk-peek))) (if (nil? t) "<eof>" (get t "type")))))
(define
hk-peek-value
(fn () (let ((t (hk-peek))) (if (nil? t) nil (get t "value")))))
(define
hk-match?
(fn
(ty v)
(let
((t (hk-peek)))
(and
(not (nil? t))
(= (get t "type") ty)
(or (nil? v) (= (get t "value") v))))))
(define
hk-err
(fn
(msg)
(raise
(str
"parse error: "
msg
" (at "
(hk-peek-type)
(if (nil? (hk-peek-value)) "" (str " " (hk-peek-value)))
")"))))
(define
hk-expect!
(fn
(ty v)
(if
(hk-match? ty v)
(hk-advance!)
(hk-err (str "expected " ty (if (nil? v) "" (str " '" v "'")))))))
(define
hk-parse-aexp
(fn
()
(let
((t (hk-peek)))
(cond
((nil? t) (hk-err "unexpected end of input"))
((= (get t "type") "integer")
(do (hk-advance!) (list :int (get t "value"))))
((= (get t "type") "float")
(do (hk-advance!) (list :float (get t "value"))))
((= (get t "type") "string")
(do (hk-advance!) (list :string (get t "value"))))
((= (get t "type") "char")
(do (hk-advance!) (list :char (get t "value"))))
((= (get t "type") "varid")
(do (hk-advance!) (list :var (get t "value"))))
((= (get t "type") "conid")
(do (hk-advance!) (list :con (get t "value"))))
((= (get t "type") "qvarid")
(do (hk-advance!) (list :var (get t "value"))))
((= (get t "type") "qconid")
(do (hk-advance!) (list :con (get t "value"))))
((= (get t "type") "lparen") (hk-parse-parens))
((= (get t "type") "lbracket") (hk-parse-list-lit))
(:else (hk-err "unexpected token in expression"))))))
(define
hk-section-op-info
(fn
()
(let
((t (hk-peek)))
(cond
((nil? t) nil)
((= (get t "type") "varsym") {:len 1 :name (get t "value")})
((= (get t "type") "consym") {:len 1 :name (get t "value")})
((and (= (get t "type") "reservedop") (= (get t "value") ":"))
{:len 1 :name ":"})
((= (get t "type") "backtick")
(let
((varid-t (hk-peek-at 1)))
(cond
((and (not (nil? varid-t)) (= (get varid-t "type") "varid"))
{:len 3 :name (get varid-t "value")})
(:else nil))))
(:else nil)))))
(define
hk-parse-parens
(fn
()
(hk-expect! "lparen" nil)
(cond
((hk-match? "rparen" nil) (do (hk-advance!) (list :con "()")))
(:else
(let
((op-info (hk-section-op-info)))
(cond
((and (not (nil? op-info)) (let ((after (hk-peek-at (get op-info "len")))) (or (and (not (nil? after)) (= (get after "type") "rparen")) (not (= (get op-info "name") "-")))))
(let
((op-name (get op-info "name"))
(op-len (get op-info "len"))
(after (hk-peek-at (get op-info "len"))))
(hk-consume-op!)
(cond
((and (not (nil? after)) (= (get after "type") "rparen"))
(do (hk-advance!) (list :var op-name)))
(:else
(let
((expr-e (hk-parse-expr-inner)))
(hk-expect! "rparen" nil)
(list :sect-right op-name expr-e))))))
(:else
(let
((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
((hk-match? "rparen" nil)
(do
(hk-advance!)
(if is-tuple (list :tuple items) first-e)))
(: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
hk-comp-qual-is-gen?
(fn
()
(let
((j pos) (depth 0) (found false) (done false))
(define
hk-qsc-loop
(fn
()
(when
(and (not done) (< j n))
(let
((t (nth toks j)) (ty (get t "type")))
(cond
((and (= depth 0) (or (= ty "comma") (= ty "rbracket")))
(set! done true))
((and (= depth 0) (= ty "reservedop") (= (get t "value") "<-"))
(do (set! found true) (set! done true)))
((or (= ty "lparen") (= ty "lbracket") (= ty "lbrace") (= ty "vlbrace"))
(set! depth (+ depth 1)))
((or (= ty "rparen") (= ty "rbrace") (= ty "vrbrace"))
(set! depth (- depth 1)))
(:else nil))
(set! j (+ j 1))
(hk-qsc-loop)))))
(hk-qsc-loop)
found)))
(define
hk-parse-comp-let
(fn
()
(hk-expect! "reserved" "let")
(let
((explicit (hk-match? "lbrace" nil)))
(if explicit (hk-advance!) (hk-expect! "vlbrace" nil))
(let
((binds (list)))
(when
(not
(if
explicit
(hk-match? "rbrace" nil)
(hk-match? "vrbrace" nil)))
(do
(append! binds (hk-parse-bind))
(define
hk-cl-loop
(fn
()
(when
(or (hk-match? "semi" nil) (hk-match? "vsemi" nil))
(do
(hk-advance!)
(when
(not
(if
explicit
(hk-match? "rbrace" nil)
(hk-match? "vrbrace" nil)))
(append! binds (hk-parse-bind)))
(hk-cl-loop)))))
(hk-cl-loop)))
(cond
(explicit (hk-expect! "rbrace" nil))
((hk-match? "vrbrace" nil) (hk-advance!))
((or (hk-match? "rbracket" nil) (hk-match? "comma" nil))
nil)
(:else (hk-err "expected end of let block in comprehension")))
(list :q-let binds)))))
(define
hk-parse-qual
(fn
()
(cond
((hk-match? "reserved" "let") (hk-parse-comp-let))
((hk-comp-qual-is-gen?)
(let
((pat (hk-parse-pat)))
(hk-expect! "reservedop" "<-")
(list :q-gen pat (hk-parse-expr-inner))))
(:else (list :q-guard (hk-parse-expr-inner))))))
(define
hk-parse-list-lit
(fn
()
(hk-expect! "lbracket" nil)
(cond
((hk-match? "rbracket" nil)
(do (hk-advance!) (list :list (list))))
(:else
(let
((first-e (hk-parse-expr-inner)))
(cond
((hk-match? "reservedop" "..")
(do
(hk-advance!)
(cond
((hk-match? "rbracket" nil)
(do (hk-advance!) (list :range-from first-e)))
(:else
(let
((end-e (hk-parse-expr-inner)))
(hk-expect! "rbracket" nil)
(list :range first-e end-e))))))
((hk-match? "reservedop" "|")
(do
(hk-advance!)
(let
((quals (list)))
(append! quals (hk-parse-qual))
(define
hk-lc-loop
(fn
()
(when
(hk-match? "comma" nil)
(do
(hk-advance!)
(append! quals (hk-parse-qual))
(hk-lc-loop)))))
(hk-lc-loop)
(hk-expect! "rbracket" nil)
(list :list-comp first-e quals))))
((hk-match? "comma" nil)
(do
(hk-advance!)
(let
((second-e (hk-parse-expr-inner)))
(cond
((hk-match? "reservedop" "..")
(do
(hk-advance!)
(let
((end-e (hk-parse-expr-inner)))
(hk-expect! "rbracket" nil)
(list :range-step first-e second-e end-e))))
(:else
(let
((items (list)))
(append! items first-e)
(append! items second-e)
(define
hk-list-loop
(fn
()
(when
(hk-match? "comma" nil)
(do
(hk-advance!)
(append! items (hk-parse-expr-inner))
(hk-list-loop)))))
(hk-list-loop)
(hk-expect! "rbracket" nil)
(list :list items)))))))
(:else
(do
(hk-expect! "rbracket" nil)
(list :list (list first-e))))))))))
(define
hk-parse-fexp
(fn
()
(let
((fn-e (hk-parse-aexp)))
(define
hk-app-loop
(fn
()
(when
(hk-atom-start? (hk-peek))
(let
((arg (hk-parse-aexp)))
(set! fn-e (list :app fn-e arg))
(hk-app-loop)))))
(hk-app-loop)
fn-e)))
(define
hk-parse-lambda
(fn
()
(hk-expect! "reservedop" "\\")
(let
((params (list)))
(when
(not (hk-apat-start? (hk-peek)))
(hk-err "lambda needs at least one pattern parameter"))
(define
hk-lam-loop
(fn
()
(when
(hk-apat-start? (hk-peek))
(do (append! params (hk-parse-apat)) (hk-lam-loop)))))
(hk-lam-loop)
(hk-expect! "reservedop" "->")
(list :lambda params (hk-parse-expr-inner)))))
(define
hk-parse-if
(fn
()
(hk-expect! "reserved" "if")
(let
((c (hk-parse-expr-inner)))
(hk-expect! "reserved" "then")
(let
((th (hk-parse-expr-inner)))
(hk-expect! "reserved" "else")
(let ((el (hk-parse-expr-inner))) (list :if c th el))))))
(define
hk-parse-let
(fn
()
(hk-expect! "reserved" "let")
(let
((explicit (hk-match? "lbrace" nil)))
(if explicit (hk-advance!) (hk-expect! "vlbrace" nil))
(let
((binds (list)))
(when
(not
(if
explicit
(hk-match? "rbrace" nil)
(hk-match? "vrbrace" nil)))
(do
(append! binds (hk-parse-bind))
(define
hk-let-loop
(fn
()
(when
(or (hk-match? "semi" nil) (hk-match? "vsemi" nil))
(do
(hk-advance!)
(when
(not
(if
explicit
(hk-match? "rbrace" nil)
(hk-match? "vrbrace" nil)))
(append! binds (hk-parse-bind)))
(hk-let-loop)))))
(hk-let-loop)))
(if
explicit
(hk-expect! "rbrace" nil)
(hk-expect! "vrbrace" nil))
(hk-expect! "reserved" "in")
(list :let binds (hk-parse-expr-inner))))))
(define
hk-parse-where-decls
(fn
()
(let
((explicit (hk-match? "lbrace" nil)))
(if explicit (hk-advance!) (hk-expect! "vlbrace" nil))
(let
((decls (list)))
(when
(not
(if
explicit
(hk-match? "rbrace" nil)
(hk-match? "vrbrace" nil)))
(do
(append! decls (hk-parse-decl))
(define
hk-wd-loop
(fn
()
(when
(or (hk-match? "vsemi" nil) (hk-match? "semi" nil))
(do
(hk-advance!)
(when
(not
(if
explicit
(hk-match? "rbrace" nil)
(hk-match? "vrbrace" nil)))
(append! decls (hk-parse-decl)))
(hk-wd-loop)))))
(hk-wd-loop)))
(if
explicit
(hk-expect! "rbrace" nil)
(hk-expect! "vrbrace" nil))
decls))))
(define
hk-parse-guarded
(fn
(sep)
(let
((guards (list)))
(define
hk-g-loop
(fn
()
(when
(hk-match? "reservedop" "|")
(do
(hk-advance!)
(let
((cond-e (hk-parse-expr-inner)))
(hk-expect! "reservedop" sep)
(let
((expr-e (hk-parse-expr-inner)))
(append! guards (list :guard cond-e expr-e))
(hk-g-loop)))))))
(hk-g-loop)
(list :guarded guards))))
(define
hk-parse-rhs
(fn
(sep)
(let
((body (cond ((hk-match? "reservedop" "|") (hk-parse-guarded sep)) (:else (do (hk-expect! "reservedop" sep) (hk-parse-expr-inner))))))
(cond
((hk-match? "reserved" "where")
(do (hk-advance!) (list :where body (hk-parse-where-decls))))
(:else body)))))
(define
hk-parse-bind
(fn
()
(let
((t (hk-peek)))
(cond
((and (not (nil? t)) (= (get t "type") "varid"))
(let
((name (get (hk-advance!) "value")) (pats (list)))
(define
hk-b-loop
(fn
()
(when
(hk-apat-start? (hk-peek))
(do (append! pats (hk-parse-apat)) (hk-b-loop)))))
(hk-b-loop)
(if
(= (len pats) 0)
(list :bind (list :p-var name) (hk-parse-rhs "="))
(list :fun-clause name pats (hk-parse-rhs "=")))))
(:else
(let
((pat (hk-parse-pat)))
(list :bind pat (hk-parse-rhs "="))))))))
(define
hk-parse-apat
(fn
()
(let
((t (hk-peek)))
(cond
((nil? t) (hk-err "unexpected end of input in pattern"))
((and (= (get t "type") "reserved") (= (get t "value") "_"))
(do (hk-advance!) (list :p-wild)))
((and (= (get t "type") "reservedop") (= (get t "value") "~"))
(do (hk-advance!) (list :p-lazy (hk-parse-apat))))
((and (= (get t "type") "varsym") (= (get t "value") "-"))
(do
(hk-advance!)
(let
((n (hk-peek)))
(cond
((nil? n)
(hk-err "expected numeric literal after '-'"))
((= (get n "type") "integer")
(do
(hk-advance!)
(list :p-int (- 0 (get n "value")))))
((= (get n "type") "float")
(do
(hk-advance!)
(list :p-float (- 0 (get n "value")))))
(:else
(hk-err
"only numeric literals may follow '-' in a pattern"))))))
((= (get t "type") "integer")
(do (hk-advance!) (list :p-int (get t "value"))))
((= (get t "type") "float")
(do (hk-advance!) (list :p-float (get t "value"))))
((= (get t "type") "string")
(do (hk-advance!) (list :p-string (get t "value"))))
((= (get t "type") "char")
(do (hk-advance!) (list :p-char (get t "value"))))
((= (get t "type") "varid")
(let
((next-t (hk-peek-at 1)))
(cond
((and (not (nil? next-t)) (= (get next-t "type") "reservedop") (= (get next-t "value") "@"))
(do
(hk-advance!)
(hk-advance!)
(list :p-as (get t "value") (hk-parse-apat))))
(:else
(do (hk-advance!) (list :p-var (get t "value")))))))
((= (get t "type") "conid")
(do (hk-advance!) (list :p-con (get t "value") (list))))
((= (get t "type") "qconid")
(do (hk-advance!) (list :p-con (get t "value") (list))))
((= (get t "type") "lparen") (hk-parse-paren-pat))
((= (get t "type") "lbracket") (hk-parse-list-pat))
(:else (hk-err "unexpected token in pattern"))))))
(define
hk-parse-paren-pat
(fn
()
(hk-expect! "lparen" nil)
(cond
((hk-match? "rparen" nil)
(do (hk-advance!) (list :p-con "()" (list))))
(:else
(let
((first-p (hk-parse-pat)) (items (list)) (is-tup false))
(append! items first-p)
(define
hk-ppt-loop
(fn
()
(when
(hk-match? "comma" nil)
(do
(hk-advance!)
(set! is-tup true)
(append! items (hk-parse-pat))
(hk-ppt-loop)))))
(hk-ppt-loop)
(hk-expect! "rparen" nil)
(if is-tup (list :p-tuple items) first-p))))))
(define
hk-parse-list-pat
(fn
()
(hk-expect! "lbracket" nil)
(cond
((hk-match? "rbracket" nil)
(do (hk-advance!) (list :p-list (list))))
(:else
(let
((items (list)))
(append! items (hk-parse-pat))
(define
hk-plt-loop
(fn
()
(when
(hk-match? "comma" nil)
(do
(hk-advance!)
(append! items (hk-parse-pat))
(hk-plt-loop)))))
(hk-plt-loop)
(hk-expect! "rbracket" nil)
(list :p-list items))))))
(define
hk-parse-pat-lhs
(fn
()
(let
((t (hk-peek)))
(cond
((and (not (nil? t)) (or (= (get t "type") "conid") (= (get t "type") "qconid")))
(let
((name (get (hk-advance!) "value")) (args (list)))
(define
hk-pca-loop
(fn
()
(when
(hk-apat-start? (hk-peek))
(do (append! args (hk-parse-apat)) (hk-pca-loop)))))
(hk-pca-loop)
(list :p-con name args)))
(:else (hk-parse-apat))))))
(define
hk-parse-pat
(fn
()
(let
((left (hk-parse-pat-lhs)))
(cond
((or (= (hk-peek-type) "consym") (and (= (hk-peek-type) "reservedop") (= (hk-peek-value) ":")))
(let
((op (get (hk-advance!) "value")))
(let
((right (hk-parse-pat)))
(list :p-con op (list left right)))))
(:else left)))))
(define
hk-parse-alt
(fn
()
(let ((pat (hk-parse-pat))) (list :alt pat (hk-parse-rhs "->")))))
(define
hk-parse-case
(fn
()
(hk-expect! "reserved" "case")
(let
((scrut (hk-parse-expr-inner)))
(hk-expect! "reserved" "of")
(let
((explicit (hk-match? "lbrace" nil)))
(if explicit (hk-advance!) (hk-expect! "vlbrace" nil))
(let
((alts (list)))
(when
(not
(if
explicit
(hk-match? "rbrace" nil)
(hk-match? "vrbrace" nil)))
(do
(append! alts (hk-parse-alt))
(define
hk-case-loop
(fn
()
(when
(or (hk-match? "semi" nil) (hk-match? "vsemi" nil))
(do
(hk-advance!)
(when
(not
(if
explicit
(hk-match? "rbrace" nil)
(hk-match? "vrbrace" nil)))
(append! alts (hk-parse-alt)))
(hk-case-loop)))))
(hk-case-loop)))
(if
explicit
(hk-expect! "rbrace" nil)
(hk-expect! "vrbrace" nil))
(list :case scrut alts))))))
(define
hk-do-stmt-is-bind?
(fn
()
(let
((j pos) (depth 0) (found false) (done false))
(define
hk-scan-loop
(fn
()
(when
(and (not done) (< j n))
(let
((t (nth toks j)) (ty nil))
(set! ty (get t "type"))
(cond
((and (= depth 0) (or (= ty "semi") (= ty "vsemi") (= ty "rbrace") (= ty "vrbrace")))
(set! done true))
((and (= depth 0) (= ty "reservedop") (= (get t "value") "<-"))
(do (set! found true) (set! done true)))
((or (= ty "lparen") (= ty "lbracket") (= ty "lbrace") (= ty "vlbrace"))
(set! depth (+ depth 1)))
((or (= ty "rparen") (= ty "rbracket"))
(set! depth (- depth 1)))
(:else nil))
(set! j (+ j 1))
(hk-scan-loop)))))
(hk-scan-loop)
found)))
(define
hk-parse-do-let
(fn
()
(hk-expect! "reserved" "let")
(let
((explicit (hk-match? "lbrace" nil)))
(if explicit (hk-advance!) (hk-expect! "vlbrace" nil))
(let
((binds (list)))
(when
(not
(if
explicit
(hk-match? "rbrace" nil)
(hk-match? "vrbrace" nil)))
(do
(append! binds (hk-parse-bind))
(define
hk-dlet-loop
(fn
()
(when
(or (hk-match? "semi" nil) (hk-match? "vsemi" nil))
(do
(hk-advance!)
(when
(not
(if
explicit
(hk-match? "rbrace" nil)
(hk-match? "vrbrace" nil)))
(append! binds (hk-parse-bind)))
(hk-dlet-loop)))))
(hk-dlet-loop)))
(if
explicit
(hk-expect! "rbrace" nil)
(hk-expect! "vrbrace" nil))
(list :do-let binds)))))
(define
hk-parse-do-stmt
(fn
()
(cond
((hk-match? "reserved" "let") (hk-parse-do-let))
((hk-do-stmt-is-bind?)
(let
((pat (hk-parse-pat)))
(hk-expect! "reservedop" "<-")
(list :do-bind pat (hk-parse-expr-inner))))
(:else (list :do-expr (hk-parse-expr-inner))))))
(define
hk-parse-do
(fn
()
(hk-expect! "reserved" "do")
(let
((explicit (hk-match? "lbrace" nil)))
(if explicit (hk-advance!) (hk-expect! "vlbrace" nil))
(let
((stmts (list)))
(when
(not
(if
explicit
(hk-match? "rbrace" nil)
(hk-match? "vrbrace" nil)))
(do
(append! stmts (hk-parse-do-stmt))
(define
hk-do-loop
(fn
()
(when
(or (hk-match? "semi" nil) (hk-match? "vsemi" nil))
(do
(hk-advance!)
(when
(not
(if
explicit
(hk-match? "rbrace" nil)
(hk-match? "vrbrace" nil)))
(append! stmts (hk-parse-do-stmt)))
(hk-do-loop)))))
(hk-do-loop)))
(if
explicit
(hk-expect! "rbrace" nil)
(hk-expect! "vrbrace" nil))
(list :do stmts)))))
(define
hk-parse-lexp
(fn
()
(cond
((hk-match? "reservedop" "\\") (hk-parse-lambda))
((hk-match? "reserved" "if") (hk-parse-if))
((hk-match? "reserved" "let") (hk-parse-let))
((hk-match? "reserved" "case") (hk-parse-case))
((hk-match? "reserved" "do") (hk-parse-do))
(:else (hk-parse-fexp)))))
(define
hk-parse-prefix
(fn
()
(cond
((and (hk-match? "varsym" "-"))
(do (hk-advance!) (list :neg (hk-parse-lexp))))
(:else (hk-parse-lexp)))))
(define
hk-is-infix-op?
(fn
(tok)
(if
(nil? tok)
false
(or
(= (get tok "type") "varsym")
(= (get tok "type") "consym")
(and
(= (get tok "type") "reservedop")
(= (get tok "value") ":"))
(= (get tok "type") "backtick")))))
(define
hk-consume-op!
(fn
()
(let
((t (hk-peek)))
(cond
((= (get t "type") "backtick")
(do
(hk-advance!)
(let
((v (hk-expect! "varid" nil)))
(hk-expect! "backtick" nil)
(get v "value"))))
(:else (do (hk-advance!) (get t "value")))))))
(define
hk-parse-infix
(fn
(min-prec)
(let
((left (hk-parse-prefix)))
(define
hk-inf-loop
(fn
()
(when
(hk-is-infix-op? (hk-peek))
(let
((op-tok (hk-peek)))
(let
((op-len (if (= (get op-tok "type") "backtick") 3 1))
(op-name
(if
(= (get op-tok "type") "backtick")
(get (hk-peek-at 1) "value")
(get op-tok "value"))))
(let
((after-op (hk-peek-at op-len))
(info (hk-op-info op-name)))
(cond
((and (not (nil? after-op)) (= (get after-op "type") "rparen"))
nil)
((>= (get info "prec") min-prec)
(do
(hk-consume-op!)
(let
((next-min (cond ((= (get info "assoc") "left") (+ (get info "prec") 1)) ((= (get info "assoc") "right") (get info "prec")) (:else (+ (get info "prec") 1)))))
(let
((right (hk-parse-infix next-min)))
(set! left (list :op op-name left right))
(hk-inf-loop)))))
(:else nil))))))))
(hk-inf-loop)
left)))
(define hk-parse-expr-inner (fn () (hk-parse-infix 0)))
(define
hk-parse-paren-type
(fn
()
(hk-expect! "lparen" nil)
(cond
((hk-match? "rparen" nil)
(do (hk-advance!) (list :t-con "()")))
(:else
(let
((first-t (hk-parse-type)) (items (list)) (is-tup false))
(append! items first-t)
(define
hk-pt-loop
(fn
()
(when
(hk-match? "comma" nil)
(do
(hk-advance!)
(set! is-tup true)
(append! items (hk-parse-type))
(hk-pt-loop)))))
(hk-pt-loop)
(hk-expect! "rparen" nil)
(if is-tup (list :t-tuple items) first-t))))))
(define
hk-parse-list-type
(fn
()
(hk-expect! "lbracket" nil)
(cond
((hk-match? "rbracket" nil)
(do (hk-advance!) (list :t-con "[]")))
(:else
(let
((inner (hk-parse-type)))
(hk-expect! "rbracket" nil)
(list :t-list inner))))))
(define
hk-parse-atype
(fn
()
(let
((t (hk-peek)))
(cond
((nil? t) (hk-err "unexpected end of input in type"))
((= (get t "type") "conid")
(do (hk-advance!) (list :t-con (get t "value"))))
((= (get t "type") "qconid")
(do (hk-advance!) (list :t-con (get t "value"))))
((= (get t "type") "varid")
(do (hk-advance!) (list :t-var (get t "value"))))
((= (get t "type") "lparen") (hk-parse-paren-type))
((= (get t "type") "lbracket") (hk-parse-list-type))
(:else (hk-err "unexpected token in type"))))))
(define
hk-parse-btype
(fn
()
(let
((head (hk-parse-atype)))
(define
hk-bt-loop
(fn
()
(when
(hk-atype-start? (hk-peek))
(do
(set! head (list :t-app head (hk-parse-atype)))
(hk-bt-loop)))))
(hk-bt-loop)
head)))
(define
hk-parse-type
(fn
()
(let
((left (hk-parse-btype)))
(cond
((hk-match? "reservedop" "->")
(do (hk-advance!) (list :t-fun left (hk-parse-type))))
(:else left)))))
(define
hk-has-top-dcolon?
(fn
()
(let
((j pos) (depth 0) (found false) (done false))
(define
hk-dcol-loop
(fn
()
(when
(and (not done) (< j n))
(let
((t (nth toks j)) (ty (get t "type")))
(cond
((and (= depth 0) (or (= ty "vsemi") (= ty "semi") (= ty "rbrace") (= ty "vrbrace")))
(set! done true))
((and (= depth 0) (= ty "reservedop") (= (get t "value") "::"))
(do (set! found true) (set! done true)))
((or (= ty "lparen") (= ty "lbracket") (= ty "lbrace") (= ty "vlbrace"))
(set! depth (+ depth 1)))
((or (= ty "rparen") (= ty "rbracket"))
(set! depth (- depth 1)))
(:else nil))
(set! j (+ j 1))
(hk-dcol-loop)))))
(hk-dcol-loop)
found)))
(define
hk-parse-type-sig
(fn
()
(let
((names (list)))
(when
(not (hk-match? "varid" nil))
(hk-err "type signature must start with a variable"))
(append! names (get (hk-advance!) "value"))
(define
hk-sig-loop
(fn
()
(when
(hk-match? "comma" nil)
(do
(hk-advance!)
(when
(not (hk-match? "varid" nil))
(hk-err "expected name after ','"))
(append! names (get (hk-advance!) "value"))
(hk-sig-loop)))))
(hk-sig-loop)
(hk-expect! "reservedop" "::")
(list :type-sig names (hk-parse-type)))))
(define
hk-parse-fun-clause
(fn
()
(let
((t (hk-peek)))
(cond
((and (not (nil? t)) (= (get t "type") "varid"))
(let
((name (get (hk-advance!) "value")) (pats (list)))
(define
hk-fc-loop
(fn
()
(when
(hk-apat-start? (hk-peek))
(do (append! pats (hk-parse-apat)) (hk-fc-loop)))))
(hk-fc-loop)
(list :fun-clause name pats (hk-parse-rhs "="))))
(:else
(let
((pat (hk-parse-pat)))
(list :pat-bind pat (hk-parse-rhs "="))))))))
(define
hk-parse-con-def
(fn
()
(when
(not (hk-match? "conid" nil))
(hk-err "expected constructor name"))
(let
((name (get (hk-advance!) "value")) (fields (list)))
(define
hk-cd-loop
(fn
()
(when
(hk-atype-start? (hk-peek))
(do (append! fields (hk-parse-atype)) (hk-cd-loop)))))
(hk-cd-loop)
(list :con-def name fields))))
(define
hk-parse-tvars
(fn
()
(let
((vs (list)))
(define
hk-tv-loop
(fn
()
(when
(hk-match? "varid" nil)
(do
(append! vs (get (hk-advance!) "value"))
(hk-tv-loop)))))
(hk-tv-loop)
vs)))
(define
hk-parse-data
(fn
()
(hk-expect! "reserved" "data")
(when
(not (hk-match? "conid" nil))
(hk-err "data declaration needs a type name"))
(let
((name (get (hk-advance!) "value"))
(tvars (hk-parse-tvars))
(cons-list (list))
(deriving-list (list)))
(when
(hk-match? "reservedop" "=")
(do
(hk-advance!)
(append! cons-list (hk-parse-con-def))
(define
hk-dc-loop
(fn
()
(when
(hk-match? "reservedop" "|")
(do
(hk-advance!)
(append! cons-list (hk-parse-con-def))
(hk-dc-loop)))))
(hk-dc-loop)))
(when
(hk-match? "reserved" "deriving")
(do
(hk-advance!)
(cond
((hk-match? "lparen" nil)
(do
(hk-advance!)
(define
hk-der-loop
(fn
()
(when
(hk-match? "conid" nil)
(do
(append!
deriving-list
(get (hk-advance!) "value"))
(when (hk-match? "comma" nil) (hk-advance!))
(hk-der-loop)))))
(hk-der-loop)
(hk-expect! "rparen" nil)))
((hk-match? "conid" nil)
(append! deriving-list (get (hk-advance!) "value"))))))
(if
(empty? deriving-list)
(list :data name tvars cons-list)
(list :data name tvars cons-list deriving-list)))))
(define
hk-parse-class
(fn
()
(hk-next)
(let
((cls (get (hk-next) "value")))
(let
((tvar (get (hk-next) "value")))
(hk-expect! "reserved" "where")
(list "class-decl" cls tvar (hk-parse-where-decls))))))
(define
hk-parse-instance
(fn
()
(hk-next)
(let
((cls (get (hk-next) "value")))
(let
((inst-type (hk-parse-atype)))
(hk-expect! "reserved" "where")
(list "instance-decl" cls inst-type (hk-parse-where-decls))))))
(define
hk-parse-type-syn
(fn
()
(hk-expect! "reserved" "type")
(when
(not (hk-match? "conid" nil))
(hk-err "type synonym needs a name"))
(let
((name (get (hk-advance!) "value")) (tvars (hk-parse-tvars)))
(hk-expect! "reservedop" "=")
(list :type-syn name tvars (hk-parse-type)))))
(define
hk-parse-newtype
(fn
()
(hk-expect! "reserved" "newtype")
(when
(not (hk-match? "conid" nil))
(hk-err "newtype needs a type name"))
(let
((name (get (hk-advance!) "value")) (tvars (hk-parse-tvars)))
(hk-expect! "reservedop" "=")
(when
(not (hk-match? "conid" nil))
(hk-err "newtype needs a constructor name"))
(let
((cname (get (hk-advance!) "value")))
(when
(not (hk-atype-start? (hk-peek)))
(hk-err "newtype constructor needs one field"))
(list :newtype name tvars cname (hk-parse-atype))))))
(define
hk-parse-op
(fn
()
(cond
((hk-match? "varsym" nil) (get (hk-advance!) "value"))
((hk-match? "consym" nil) (get (hk-advance!) "value"))
((and (hk-match? "reservedop" nil) (= (hk-peek-value) ":"))
(do (hk-advance!) ":"))
((hk-match? "backtick" nil)
(do
(hk-advance!)
(let
((v (hk-expect! "varid" nil)))
(hk-expect! "backtick" nil)
(get v "value"))))
(:else (hk-err "expected operator name in fixity decl")))))
(define
hk-parse-fixity
(fn
()
(let
((assoc "n"))
(cond
((hk-match? "reserved" "infixl") (set! assoc "l"))
((hk-match? "reserved" "infixr") (set! assoc "r"))
((hk-match? "reserved" "infix") (set! assoc "n"))
(:else (hk-err "expected fixity keyword")))
(hk-advance!)
(let
((prec 9))
(when
(hk-match? "integer" nil)
(set! prec (get (hk-advance!) "value")))
(let
((ops (list)))
(append! ops (hk-parse-op))
(define
hk-fx-loop
(fn
()
(when
(hk-match? "comma" nil)
(do
(hk-advance!)
(append! ops (hk-parse-op))
(hk-fx-loop)))))
(hk-fx-loop)
(list :fixity assoc prec ops))))))
(define
hk-parse-decl
(fn
()
(cond
((hk-match? "reserved" "data") (hk-parse-data))
((hk-match? "reserved" "type") (hk-parse-type-syn))
((hk-match? "reserved" "newtype") (hk-parse-newtype))
((or (hk-match? "reserved" "infix") (hk-match? "reserved" "infixl") (hk-match? "reserved" "infixr"))
(hk-parse-fixity))
((hk-match? "reserved" "class") (hk-parse-class))
((hk-match? "reserved" "instance") (hk-parse-instance))
((hk-has-top-dcolon?) (hk-parse-type-sig))
(:else (hk-parse-fun-clause)))))
(define
hk-parse-ent-member
(fn
()
(cond
((hk-match? "varid" nil) (get (hk-advance!) "value"))
((hk-match? "conid" nil) (get (hk-advance!) "value"))
((hk-match? "lparen" nil)
(do
(hk-advance!)
(let
((op-name (cond ((hk-match? "varsym" nil) (get (hk-advance!) "value")) ((hk-match? "consym" nil) (get (hk-advance!) "value")) ((and (hk-match? "reservedop" nil) (= (hk-peek-value) ":")) (do (hk-advance!) ":")) (:else (hk-err "expected operator in member list")))))
(hk-expect! "rparen" nil)
op-name)))
(:else (hk-err "expected identifier in member list")))))
(define
hk-parse-ent
(fn
(allow-module?)
(cond
((hk-match? "varid" nil)
(list :ent-var (get (hk-advance!) "value")))
((hk-match? "qvarid" nil)
(list :ent-var (get (hk-advance!) "value")))
((and allow-module? (hk-match? "reserved" "module"))
(do
(hk-advance!)
(cond
((or (hk-match? "conid" nil) (hk-match? "qconid" nil))
(list :ent-module (get (hk-advance!) "value")))
(:else (hk-err "expected module name in export")))))
((or (hk-match? "conid" nil) (hk-match? "qconid" nil))
(let
((name (get (hk-advance!) "value")))
(cond
((hk-match? "lparen" nil)
(do
(hk-advance!)
(cond
((hk-match? "reservedop" "..")
(do
(hk-advance!)
(hk-expect! "rparen" nil)
(list :ent-all name)))
((hk-match? "rparen" nil)
(do (hk-advance!) (list :ent-with name (list))))
(:else
(let
((mems (list)))
(append! mems (hk-parse-ent-member))
(define
hk-mem-loop
(fn
()
(when
(hk-match? "comma" nil)
(do
(hk-advance!)
(when
(not (hk-match? "rparen" nil))
(append! mems (hk-parse-ent-member)))
(hk-mem-loop)))))
(hk-mem-loop)
(hk-expect! "rparen" nil)
(list :ent-with name mems))))))
(:else (list :ent-var name)))))
((hk-match? "lparen" nil)
(do
(hk-advance!)
(let
((op-name (cond ((hk-match? "varsym" nil) (get (hk-advance!) "value")) ((hk-match? "consym" nil) (get (hk-advance!) "value")) ((and (hk-match? "reservedop" nil) (= (hk-peek-value) ":")) (do (hk-advance!) ":")) (:else (hk-err "expected operator in parens")))))
(hk-expect! "rparen" nil)
(list :ent-var op-name))))
(:else (hk-err "expected entity in import/export list")))))
(define
hk-parse-ent-list
(fn
(allow-module?)
(hk-expect! "lparen" nil)
(cond
((hk-match? "rparen" nil) (do (hk-advance!) (list)))
(:else
(let
((items (list)))
(append! items (hk-parse-ent allow-module?))
(define
hk-el-loop
(fn
()
(when
(hk-match? "comma" nil)
(do
(hk-advance!)
(when
(not (hk-match? "rparen" nil))
(append! items (hk-parse-ent allow-module?)))
(hk-el-loop)))))
(hk-el-loop)
(hk-expect! "rparen" nil)
items)))))
(define
hk-parse-import
(fn
()
(hk-expect! "reserved" "import")
(let
((qualified false) (modname nil) (as-name nil) (spec nil))
(when
(hk-match? "varid" "qualified")
(do (hk-advance!) (set! qualified true)))
(cond
((or (hk-match? "conid" nil) (hk-match? "qconid" nil))
(set! modname (get (hk-advance!) "value")))
(:else (hk-err "expected module name in import")))
(when
(hk-match? "varid" "as")
(do
(hk-advance!)
(cond
((or (hk-match? "conid" nil) (hk-match? "qconid" nil))
(set! as-name (get (hk-advance!) "value")))
(:else (hk-err "expected name after 'as'")))))
(cond
((hk-match? "varid" "hiding")
(do
(hk-advance!)
(set! spec (list :spec-hiding (hk-parse-ent-list false)))))
((hk-match? "lparen" nil)
(set! spec (list :spec-items (hk-parse-ent-list false)))))
(list :import qualified modname as-name spec))))
(define
hk-parse-module-header
(fn
()
(hk-expect! "reserved" "module")
(let
((modname nil) (exports nil))
(cond
((or (hk-match? "conid" nil) (hk-match? "qconid" nil))
(set! modname (get (hk-advance!) "value")))
(:else (hk-err "expected module name")))
(when
(hk-match? "lparen" nil)
(set! exports (hk-parse-ent-list true)))
(hk-expect! "reserved" "where")
(list modname exports))))
(define
hk-collect-module-body
(fn
()
(let
((imports (list)) (decls (list)))
(define
hk-imp-loop
(fn
()
(when
(hk-match? "reserved" "import")
(do
(append! imports (hk-parse-import))
(when
(or (hk-match? "vsemi" nil) (hk-match? "semi" nil))
(do (hk-advance!) (hk-imp-loop)))))))
(hk-imp-loop)
(define
hk-body-at-end?
(fn
()
(or
(nil? (hk-peek))
(= (hk-peek-type) "eof")
(hk-match? "vrbrace" nil)
(hk-match? "rbrace" nil))))
(when
(not (hk-body-at-end?))
(do
(append! decls (hk-parse-decl))
(define
hk-body-loop
(fn
()
(when
(or (hk-match? "vsemi" nil) (hk-match? "semi" nil))
(do
(hk-advance!)
(when
(not (hk-body-at-end?))
(append! decls (hk-parse-decl)))
(hk-body-loop)))))
(hk-body-loop)))
(list imports decls))))
(define
hk-parse-program
(fn
()
(cond
((hk-match? "reserved" "module")
(let
((header (hk-parse-module-header)))
(let
((explicit (hk-match? "lbrace" nil)))
(if explicit (hk-advance!) (hk-expect! "vlbrace" nil))
(let
((body (hk-collect-module-body)))
(if
explicit
(hk-expect! "rbrace" nil)
(hk-expect! "vrbrace" nil))
(list
:module (nth header 0)
(nth header 1)
(nth body 0)
(nth body 1))))))
(:else
(let
((body (hk-collect-module-body)))
(if
(empty? (nth body 0))
(list :program (nth body 1))
(list :module nil nil (nth body 0) (nth body 1))))))))
(let
((start-brace (or (hk-match? "vlbrace" nil) (hk-match? "lbrace" nil))))
(when start-brace (hk-advance!))
(let
((result (cond ((= mode :expr) (hk-parse-expr-inner)) ((= mode :module) (hk-parse-program)) (:else (hk-err "unknown parser mode")))))
(when
start-brace
(when
(or (hk-match? "vrbrace" nil) (hk-match? "rbrace" nil))
(hk-advance!)))
result)))))
(define hk-parse-expr (fn (tokens) (hk-parser tokens :expr)))
(define hk-parse-module (fn (tokens) (hk-parser tokens :module)))
;; ── Convenience: tokenize + layout + parse ───────────────────────
(define
hk-parse
(fn (src) (hk-parse-expr (hk-layout (hk-tokenize src)))))
(define
hk-parse-top
(fn (src) (hk-parse-module (hk-layout (hk-tokenize src)))))