;; Haskell layout-rule tests. hk-tokenizer + hk-layout produce a
;; virtual-brace-annotated stream; these tests cover the algorithm
;; from Haskell 98 §10.3 plus the pragmatic let/in single-line rule.

;; Convenience — tokenize, run layout, strip eof, keep :type/:value.
(define
  hk-lay
  (fn
    (src)
    (map
      (fn (tok) {:value (get tok "value") :type (get tok "type")})
      (filter
        (fn (tok) (not (= (get tok "type") "eof")))
        (hk-layout (hk-tokenize src))))))

;; ── 1. Basics ──
(hk-test
  "empty input produces empty module { }"
  (hk-lay "")
  (list
    {:value "{" :type "vlbrace"}
    {:value "}" :type "vrbrace"}))

(hk-test
  "single token → module open+close"
  (hk-lay "foo")
  (list
    {:value "{" :type "vlbrace"}
    {:value "foo" :type "varid"}
    {:value "}" :type "vrbrace"}))

(hk-test
  "two top-level decls get vsemi between"
  (hk-lay "foo = 1\nbar = 2")
  (list
    {:value "{" :type "vlbrace"}
    {:value "foo" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value 1 :type "integer"}
    {:value ";" :type "vsemi"}
    {:value "bar" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value 2 :type "integer"}
    {:value "}" :type "vrbrace"}))

;; ── 2. Layout keywords — do / let / where / of ──
(hk-test
  "do block with two stmts"
  (hk-lay "f = do\n  x\n  y")
  (list
    {:value "{" :type "vlbrace"}
    {:value "f" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value "do" :type "reserved"}
    {:value "{" :type "vlbrace"}
    {:value "x" :type "varid"}
    {:value ";" :type "vsemi"}
    {:value "y" :type "varid"}
    {:value "}" :type "vrbrace"}
    {:value "}" :type "vrbrace"}))

(hk-test
  "single-line let ... in"
  (hk-lay "let x = 1 in x")
  (list
    {:value "{" :type "vlbrace"}
    {:value "let" :type "reserved"}
    {:value "{" :type "vlbrace"}
    {:value "x" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value 1 :type "integer"}
    {:value "}" :type "vrbrace"}
    {:value "in" :type "reserved"}
    {:value "x" :type "varid"}
    {:value "}" :type "vrbrace"}))

(hk-test
  "where block with two bindings"
  (hk-lay "f = g\n  where\n    g = 1\n    h = 2")
  (list
    {:value "{" :type "vlbrace"}
    {:value "f" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value "g" :type "varid"}
    {:value "where" :type "reserved"}
    {:value "{" :type "vlbrace"}
    {:value "g" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value 1 :type "integer"}
    {:value ";" :type "vsemi"}
    {:value "h" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value 2 :type "integer"}
    {:value "}" :type "vrbrace"}
    {:value "}" :type "vrbrace"}))

(hk-test
  "case … of with arms"
  (hk-lay "f x = case x of\n  Just y -> y\n  Nothing -> 0")
  (list
    {:value "{" :type "vlbrace"}
    {:value "f" :type "varid"}
    {:value "x" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value "case" :type "reserved"}
    {:value "x" :type "varid"}
    {:value "of" :type "reserved"}
    {:value "{" :type "vlbrace"}
    {:value "Just" :type "conid"}
    {:value "y" :type "varid"}
    {:value "->" :type "reservedop"}
    {:value "y" :type "varid"}
    {:value ";" :type "vsemi"}
    {:value "Nothing" :type "conid"}
    {:value "->" :type "reservedop"}
    {:value 0 :type "integer"}
    {:value "}" :type "vrbrace"}
    {:value "}" :type "vrbrace"}))

;; ── 3. Explicit braces disable layout ──
(hk-test
  "explicit braces — no implicit vlbrace/vsemi/vrbrace inside"
  (hk-lay "do { x ; y }")
  (list
    {:value "{" :type "vlbrace"}
    {:value "do" :type "reserved"}
    {:value "{" :type "lbrace"}
    {:value "x" :type "varid"}
    {:value ";" :type "semi"}
    {:value "y" :type "varid"}
    {:value "}" :type "rbrace"}
    {:value "}" :type "vrbrace"}))

;; ── 4. Dedent closes nested blocks ──
(hk-test
  "dedent back to module level closes do block"
  (hk-lay "f = do\n  x\n  y\ng = 2")
  (list
    {:value "{" :type "vlbrace"}
    {:value "f" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value "do" :type "reserved"}
    {:value "{" :type "vlbrace"}
    {:value "x" :type "varid"}
    {:value ";" :type "vsemi"}
    {:value "y" :type "varid"}
    {:value "}" :type "vrbrace"}
    {:value ";" :type "vsemi"}
    {:value "g" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value 2 :type "integer"}
    {:value "}" :type "vrbrace"}))

(hk-test
  "dedent closes inner let, emits vsemi at outer do level"
  (hk-lay "main = do\n  let x = 1\n  print x")
  (list
    {:value "{" :type "vlbrace"}
    {:value "main" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value "do" :type "reserved"}
    {:value "{" :type "vlbrace"}
    {:value "let" :type "reserved"}
    {:value "{" :type "vlbrace"}
    {:value "x" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value 1 :type "integer"}
    {:value "}" :type "vrbrace"}
    {:value ";" :type "vsemi"}
    {:value "print" :type "varid"}
    {:value "x" :type "varid"}
    {:value "}" :type "vrbrace"}
    {:value "}" :type "vrbrace"}))

;; ── 5. Module header skips outer implicit open ──
(hk-test
  "module M where — only where opens a block"
  (hk-lay "module M where\n  f = 1")
  (list
    {:value "module" :type "reserved"}
    {:value "M" :type "conid"}
    {:value "where" :type "reserved"}
    {:value "{" :type "vlbrace"}
    {:value "f" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value 1 :type "integer"}
    {:value "}" :type "vrbrace"}))

;; ── 6. Newlines are stripped ──
(hk-test
  "newline tokens do not appear in output"
  (let
    ((toks (hk-layout (hk-tokenize "foo\nbar"))))
    (every?
      (fn (t) (not (= (get t "type") "newline")))
      toks))
  true)

;; ── 7. Continuation — deeper indent does NOT emit vsemi ──
(hk-test
  "line continuation (deeper indent) just merges"
  (hk-lay "foo = 1 +\n   2")
  (list
    {:value "{" :type "vlbrace"}
    {:value "foo" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value 1 :type "integer"}
    {:value "+" :type "varsym"}
    {:value 2 :type "integer"}
    {:value "}" :type "vrbrace"}))

;; ── 8. Stack closing at EOF ──
(hk-test
  "EOF inside nested do closes all implicit blocks"
  (let
    ((toks (hk-lay "main = do\n  do\n    x")))
    (let
      ((n (len toks)))
      (list
        (get (nth toks (- n 1)) "type")
        (get (nth toks (- n 2)) "type")
        (get (nth toks (- n 3)) "type"))))
  (list "vrbrace" "vrbrace" "vrbrace"))

;; ── 9. Qualified-newline: x at deeper col than stack top does nothing ──
(hk-test
  "mixed where + do"
  (hk-lay "f = do\n    x\n  where\n    x = 1")
  (list
    {:value "{" :type "vlbrace"}
    {:value "f" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value "do" :type "reserved"}
    {:value "{" :type "vlbrace"}
    {:value "x" :type "varid"}
    {:value "}" :type "vrbrace"}
    {:value "where" :type "reserved"}
    {:value "{" :type "vlbrace"}
    {:value "x" :type "varid"}
    {:value "=" :type "reservedop"}
    {:value 1 :type "integer"}
    {:value "}" :type "vrbrace"}
    {:value "}" :type "vrbrace"}))

{:fails hk-test-fails :pass hk-test-pass :fail hk-test-fail}
