#!/usr/bin/env bash
# Fast OCaml-on-SX test runner — epoch protocol direct to sx_server.exe.
# Mirrors lib/lua/test.sh.
#
# Usage:
#   bash lib/ocaml/test.sh           # run all tests
#   bash lib/ocaml/test.sh -v        # verbose

set -uo pipefail
cd "$(git rev-parse --show-toplevel)"

SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
if [ ! -x "$SX_SERVER" ]; then
  SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
fi
if [ ! -x "$SX_SERVER" ]; then
  echo "ERROR: sx_server.exe not found. Run: cd hosts/ocaml && dune build"
  exit 1
fi

VERBOSE="${1:-}"
PASS=0
FAIL=0
ERRORS=""
TMPFILE=$(mktemp)
trap "rm -f $TMPFILE" EXIT

cat > "$TMPFILE" << 'EPOCHS'
(epoch 1)
(load "lib/guest/lex.sx")
(load "lib/guest/prefix.sx")
(load "lib/guest/pratt.sx")
(load "lib/guest/match.sx")
(load "lib/guest/hm.sx")
(load "lib/ocaml/tokenizer.sx")
(load "lib/ocaml/parser.sx")
(load "lib/ocaml/eval.sx")
(load "lib/ocaml/runtime.sx")
(load "lib/ocaml/infer.sx")
(load "lib/ocaml/tests/tokenize.sx")
(eval "(ocaml-load-stdlib!)")

;; ── empty / eof ────────────────────────────────────────────────
(epoch 100)
(eval "(ocaml-test-tok-count \"\")")
(epoch 101)
(eval "(ocaml-test-tok-type \"\" 0)")

;; ── numbers ────────────────────────────────────────────────────
(epoch 110)
(eval "(ocaml-test-tok-type \"42\" 0)")
(epoch 111)
(eval "(ocaml-test-tok-value \"42\" 0)")
(epoch 112)
(eval "(ocaml-test-tok-value \"3.14\" 0)")
(epoch 113)
(eval "(ocaml-test-tok-value \"0xff\" 0)")
(epoch 114)
(eval "(ocaml-test-tok-value \"1e3\" 0)")
(epoch 115)
(eval "(ocaml-test-tok-value \"1_000_000\" 0)")
(epoch 116)
(eval "(ocaml-test-tok-value \"3.14e-2\" 0)")

;; ── identifiers / constructors / keywords ─────────────────────
(epoch 120)
(eval "(ocaml-test-tok-type \"foo\" 0)")
(epoch 121)
(eval "(ocaml-test-tok-value \"foo_bar1\" 0)")
(epoch 122)
(eval "(ocaml-test-tok-type \"Some\" 0)")
(epoch 123)
(eval "(ocaml-test-tok-value \"Some\" 0)")
(epoch 124)
(eval "(ocaml-test-tok-type \"let\" 0)")
(epoch 125)
(eval "(ocaml-test-tok-value \"match\" 0)")
(epoch 126)
(eval "(ocaml-test-tok-type \"true\" 0)")
(epoch 127)
(eval "(ocaml-test-tok-value \"false\" 0)")
(epoch 128)
(eval "(ocaml-test-tok-value \"name'\" 0)")

;; ── strings ────────────────────────────────────────────────────
(epoch 130)
(eval "(ocaml-test-tok-type \"\\\"hi\\\"\" 0)")
(epoch 131)
(eval "(ocaml-test-tok-value \"\\\"hi\\\"\" 0)")
(epoch 132)
(eval "(ocaml-test-tok-value \"\\\"a\\\\nb\\\"\" 0)")

;; ── chars ──────────────────────────────────────────────────────
(epoch 140)
(eval "(ocaml-test-tok-type \"'a'\" 0)")
(epoch 141)
(eval "(ocaml-test-tok-value \"'a'\" 0)")
(epoch 142)
(eval "(ocaml-test-tok-value \"'\\\\n'\" 0)")

;; ── type variables ─────────────────────────────────────────────
(epoch 145)
(eval "(ocaml-test-tok-type \"'a\" 0)")
(epoch 146)
(eval "(ocaml-test-tok-value \"'a\" 0)")

;; ── multi-char operators ───────────────────────────────────────
(epoch 150)
(eval "(ocaml-test-tok-value \"->\" 0)")
(epoch 151)
(eval "(ocaml-test-tok-value \"|>\" 0)")
(epoch 152)
(eval "(ocaml-test-tok-value \"<-\" 0)")
(epoch 153)
(eval "(ocaml-test-tok-value \":=\" 0)")
(epoch 154)
(eval "(ocaml-test-tok-value \"::\" 0)")
(epoch 155)
(eval "(ocaml-test-tok-value \";;\" 0)")
(epoch 156)
(eval "(ocaml-test-tok-value \"@@\" 0)")
(epoch 157)
(eval "(ocaml-test-tok-value \"<>\" 0)")
(epoch 158)
(eval "(ocaml-test-tok-value \"&&\" 0)")
(epoch 159)
(eval "(ocaml-test-tok-value \"||\" 0)")

;; ── single-char punctuation ────────────────────────────────────
(epoch 160)
(eval "(ocaml-test-tok-value \"+\" 0)")
(epoch 161)
(eval "(ocaml-test-tok-value \"|\" 0)")
(epoch 162)
(eval "(ocaml-test-tok-value \";\" 0)")
(epoch 163)
(eval "(ocaml-test-tok-value \"(\" 0)")
(epoch 164)
(eval "(ocaml-test-tok-value \"!\" 0)")
(epoch 165)
(eval "(ocaml-test-tok-value \"@\" 0)")

;; ── comments ───────────────────────────────────────────────────
(epoch 170)
(eval "(ocaml-test-tok-count \"(* hi *)\")")
(epoch 171)
(eval "(ocaml-test-tok-value \"(* c *) 42\" 0)")
(epoch 172)
(eval "(ocaml-test-tok-count \"(* outer (* inner *) end *) 1\")")
(epoch 173)
(eval "(ocaml-test-tok-value \"(* outer (* inner *) end *) 1\" 0)")

;; ── compound expressions ───────────────────────────────────────
(epoch 180)
(eval "(ocaml-test-tok-count \"let x = 1\")")
(epoch 181)
(eval "(ocaml-test-tok-type \"let x = 1\" 0)")
(epoch 182)
(eval "(ocaml-test-tok-value \"let x = 1\" 0)")
(epoch 183)
(eval "(ocaml-test-tok-type \"let x = 1\" 1)")
(epoch 184)
(eval "(ocaml-test-tok-value \"let x = 1\" 2)")
(epoch 185)
(eval "(ocaml-test-tok-value \"let x = 1\" 3)")

(epoch 190)
(eval "(ocaml-test-tok-count \"match x with | None -> 0 | Some y -> y\")")
(epoch 191)
(eval "(ocaml-test-tok-value \"fun x -> x + 1\" 2)")
(epoch 192)
(eval "(ocaml-test-tok-type \"fun x -> x + 1\" 2)")
(epoch 193)
(eval "(ocaml-test-tok-type \"Some 42\" 0)")
(epoch 194)
(eval "(ocaml-test-tok-value \"a |> f |> g\" 1)")
(epoch 195)
(eval "(ocaml-test-tok-value \"x := !y\" 1)")

;; ── Phase 1.parse: parser ──────────────────────────────────────
;; Atoms
(epoch 200)
(eval "(ocaml-parse \"42\")")
(epoch 201)
(eval "(ocaml-parse \"3.14\")")
(epoch 202)
(eval "(ocaml-parse \"\\\"hi\\\"\")")
(epoch 203)
(eval "(ocaml-parse \"'a'\")")
(epoch 204)
(eval "(ocaml-parse \"true\")")
(epoch 205)
(eval "(ocaml-parse \"false\")")
(epoch 206)
(eval "(ocaml-parse \"x\")")
(epoch 207)
(eval "(ocaml-parse \"Some\")")
(epoch 208)
(eval "(ocaml-parse \"()\")")

;; Application (left-assoc)
(epoch 210)
(eval "(ocaml-parse \"f x\")")
(epoch 211)
(eval "(ocaml-parse \"f x y\")")
(epoch 212)
(eval "(ocaml-parse \"f (g x)\")")
(epoch 213)
(eval "(ocaml-parse \"Some 42\")")

;; Binops with precedence
(epoch 220)
(eval "(ocaml-parse \"1 + 2\")")
(epoch 221)
(eval "(ocaml-parse \"a + b * c\")")
(epoch 222)
(eval "(ocaml-parse \"a * b + c\")")
(epoch 223)
(eval "(ocaml-parse \"a && b || c\")")
(epoch 224)
(eval "(ocaml-parse \"a = b\")")
(epoch 225)
(eval "(ocaml-parse \"a ^ b ^ c\")")
(epoch 226)
(eval "(ocaml-parse \"a :: b :: []\")")
(epoch 227)
(eval "(ocaml-parse \"(a + b) * c\")")
(epoch 228)
(eval "(ocaml-parse \"a |> f |> g\")")
(epoch 229)
(eval "(ocaml-parse \"x mod 2\")")

;; Prefix
(epoch 230)
(eval "(ocaml-parse \"-x\")")
(epoch 231)
(eval "(ocaml-parse \"-1 + 2\")")

;; Tuples & lists
(epoch 240)
(eval "(ocaml-parse \"(1, 2, 3)\")")
(epoch 241)
(eval "(ocaml-parse \"[1; 2; 3]\")")
(epoch 242)
(eval "(ocaml-parse \"[]\")")

;; if / fun / let / let rec
(epoch 250)
(eval "(ocaml-parse \"if x then 1 else 2\")")
(epoch 251)
(eval "(ocaml-parse \"if c then x\")")
(epoch 252)
(eval "(ocaml-parse \"fun x -> x + 1\")")
(epoch 253)
(eval "(ocaml-parse \"fun x y -> x + y\")")
(epoch 254)
(eval "(ocaml-parse \"let x = 1 in x\")")
(epoch 255)
(eval "(ocaml-parse \"let f x = x + 1 in f 2\")")
(epoch 256)
(eval "(ocaml-parse \"let rec f x = f x in f 1\")")
(epoch 257)
(eval "(ocaml-parse \"let f x y = x + y in f 1 2\")")

;; begin/end
(epoch 260)
(eval "(ocaml-parse \"begin 1 + 2 end\")")

;; ── Top-level decls ────────────────────────────────────────────
(epoch 270)
(eval "(ocaml-parse-program \"let x = 1\")")
(epoch 271)
(eval "(ocaml-parse-program \"let x = 1 ;;\")")
(epoch 272)
(eval "(ocaml-parse-program \"let f x = x + 1\")")
(epoch 273)
(eval "(ocaml-parse-program \"let rec fact n = if n = 0 then 1 else n * fact (n - 1)\")")
(epoch 274)
(eval "(ocaml-parse-program \"let x = 1 let y = 2\")")
(epoch 275)
(eval "(ocaml-parse-program \"1 + 2 ;;\")")
(epoch 276)
(eval "(ocaml-parse-program \"let x = 1 ;; let y = 2 ;; x + y\")")
(epoch 277)
(eval "(len (ocaml-parse-program \"let x = 1 ;; let y = 2 ;; x + y\"))")
(epoch 278)
(eval "(ocaml-parse-program \"\")")

;; ── Match / patterns ───────────────────────────────────────────
(epoch 300)
(eval "(ocaml-parse \"match x with | None -> 0 | Some y -> y\")")
(epoch 301)
(eval "(ocaml-parse \"match x with None -> 0 | Some y -> y\")")
(epoch 302)
(eval "(ocaml-parse \"match l with | [] -> 0 | h :: t -> 1\")")
(epoch 303)
(eval "(ocaml-parse \"match p with | (a, b) -> a + b\")")
(epoch 304)
(eval "(ocaml-parse \"match n with | 0 -> 1 | _ -> n\")")
(epoch 305)
(eval "(ocaml-parse \"match x with | true -> 1 | false -> 0\")")
(epoch 306)
(eval "(ocaml-parse \"match x with | Pair (a, b) -> a + b\")")
(epoch 307)
(eval "(ocaml-parse \"match x with | \\\"hi\\\" -> 1 | _ -> 0\")")
(epoch 308)
(eval "(ocaml-parse \"match x with | () -> 0\")")

;; ── Sequences (;) ──────────────────────────────────────────────
(epoch 320)
(eval "(ocaml-parse \"1; 2\")")
(epoch 321)
(eval "(ocaml-parse \"1; 2; 3\")")
(epoch 322)
(eval "(ocaml-parse \"(1; 2)\")")
(epoch 323)
(eval "(ocaml-parse \"begin a; b; c end\")")
(epoch 324)
(eval "(ocaml-parse \"let x = 1 in x; x\")")
(epoch 325)
(eval "(ocaml-parse \"if c then (a; b) else c\")")
(epoch 326)
(eval "(ocaml-parse \"[1; 2; 3]\")")
(epoch 327)
(eval "(ocaml-parse \"1; 2;\")")
(epoch 328)
(eval "(ocaml-parse \"begin a; end\")")
(epoch 329)
(eval "(ocaml-parse \"match x with | _ -> a; b\")")

;; ── Phase 2: evaluator ─────────────────────────────────────────
;; Atoms
(epoch 400)
(eval "(ocaml-run \"42\")")
(epoch 401)
(eval "(ocaml-run \"3.14\")")
(epoch 402)
(eval "(ocaml-run \"true\")")
(epoch 403)
(eval "(ocaml-run \"false\")")
(epoch 404)
(eval "(ocaml-run \"\\\"hi\\\"\")")

;; Arithmetic
(epoch 410)
(eval "(ocaml-run \"1 + 2\")")
(epoch 411)
(eval "(ocaml-run \"10 - 3\")")
(epoch 412)
(eval "(ocaml-run \"4 * 5\")")
(epoch 413)
(eval "(ocaml-run \"20 / 4\")")
(epoch 414)
(eval "(ocaml-run \"10 mod 3\")")
(epoch 415)
(eval "(ocaml-run \"2 ** 10\")")
(epoch 416)
(eval "(ocaml-run \"(1 + 2) * 3\")")
(epoch 417)
(eval "(ocaml-run \"1 + 2 * 3\")")
(epoch 418)
(eval "(ocaml-run \"-5 + 10\")")

;; Comparison & boolean
(epoch 420)
(eval "(ocaml-run \"1 < 2\")")
(epoch 421)
(eval "(ocaml-run \"3 > 2\")")
(epoch 422)
(eval "(ocaml-run \"2 = 2\")")
(epoch 423)
(eval "(ocaml-run \"1 <> 2\")")
(epoch 424)
(eval "(ocaml-run \"true && false\")")
(epoch 425)
(eval "(ocaml-run \"true || false\")")
(epoch 426)
(eval "(ocaml-run \"not false\")")

;; String
(epoch 430)
(eval "(ocaml-run \"\\\"a\\\" ^ \\\"b\\\"\")")
(epoch 431)
(eval "(ocaml-run \"\\\"hello\\\" ^ \\\" \\\" ^ \\\"world\\\"\")")

;; Conditional
(epoch 440)
(eval "(ocaml-run \"if true then 1 else 2\")")
(epoch 441)
(eval "(ocaml-run \"if 1 > 2 then 100 else 200\")")

;; Let / lambda / app
(epoch 450)
(eval "(ocaml-run \"let x = 5 in x * 2\")")
(epoch 451)
(eval "(ocaml-run \"let f x = x + 1 in f 41\")")
(epoch 452)
(eval "(ocaml-run \"let f x y = x + y in f 3 4\")")
(epoch 453)
(eval "(ocaml-run \"(fun x -> x * x) 7\")")
(epoch 454)
(eval "(ocaml-run \"(fun x -> fun y -> x + y) 10 20\")")
(epoch 455)
(eval "(ocaml-run \"let f = fun x -> x + 1 in f 9\")")

;; Closures capture
(epoch 460)
(eval "(ocaml-run \"let x = 10 in let f y = x + y in f 5\")")
(epoch 461)
(eval "(ocaml-run \"let make_adder n = fun x -> n + x in (make_adder 100) 1\")")

;; Recursion
(epoch 470)
(eval "(ocaml-run \"let rec fact n = if n = 0 then 1 else n * fact (n - 1) in fact 5\")")
(epoch 471)
(eval "(ocaml-run \"let rec fib n = if n < 2 then n else fib (n - 1) + fib (n - 2) in fib 10\")")
(epoch 472)
(eval "(ocaml-run \"let rec sum n = if n = 0 then 0 else n + sum (n - 1) in sum 100\")")

;; Sequence
(epoch 480)
(eval "(ocaml-run \"1; 2; 3\")")
(epoch 481)
(eval "(ocaml-run \"begin 10 end\")")

;; Programs (top-level decls)
(epoch 490)
(eval "(ocaml-run-program \"let x = 1;; let y = 2;; x + y\")")
(epoch 491)
(eval "(ocaml-run-program \"let rec fact n = if n = 0 then 1 else n * fact (n - 1);; fact 6\")")
(epoch 492)
(eval "(ocaml-run-program \"let inc x = x + 1;; let double x = x * 2;; double (inc 4)\")")

;; Pipe
(epoch 495)
(eval "(ocaml-run \"let f x = x * 2 in 5 |> f\")")

;; ── Phase 3: ADTs + match (eval) ───────────────────────────────
;; Constructors
(epoch 500)
(eval "(ocaml-run \"None\")")
(epoch 501)
(eval "(ocaml-run \"Some 42\")")
(epoch 502)
(eval "(ocaml-run \"Some (1, 2)\")")

;; Match — option
(epoch 510)
(eval "(ocaml-run \"match Some 5 with | None -> 0 | Some y -> y\")")
(epoch 511)
(eval "(ocaml-run \"match None with | None -> 0 | Some y -> y\")")

;; Match — literals
(epoch 520)
(eval "(ocaml-run \"match 3 with | 1 -> 100 | 2 -> 200 | _ -> 999\")")
(epoch 521)
(eval "(ocaml-run \"match true with | true -> 1 | false -> 0\")")
(epoch 522)
(eval "(ocaml-run \"match \\\"hi\\\" with | \\\"hi\\\" -> 1 | _ -> 0\")")

;; Match — tuples
(epoch 530)
(eval "(ocaml-run \"match (1, 2) with | (a, b) -> a + b\")")
(epoch 531)
(eval "(ocaml-run \"match (1, 2, 3) with | (a, b, c) -> a * b * c\")")

;; Match — list cons / nil
(epoch 540)
(eval "(ocaml-run \"match [1; 2; 3] with | [] -> 0 | h :: _ -> h\")")
(epoch 541)
(eval "(ocaml-run \"match [] with | [] -> 0 | h :: _ -> h\")")
(epoch 542)
(eval "(ocaml-run \"match [1; 2; 3] with | [a; b; c] -> a + b + c | _ -> 0\")")
(epoch 543)
(eval "(ocaml-run \"let rec len lst = match lst with | [] -> 0 | _ :: t -> 1 + len t in len [1; 2; 3; 4; 5]\")")
(epoch 544)
(eval "(ocaml-run \"let rec sum lst = match lst with | [] -> 0 | h :: t -> h + sum t in sum [1; 2; 3; 4; 5]\")")

;; Match — wildcard + var
(epoch 550)
(eval "(ocaml-run \"match 99 with | _ -> 1\")")
(epoch 551)
(eval "(ocaml-run \"match 99 with | x -> x + 1\")")

;; Constructors with tuple args
(epoch 560)
(eval "(ocaml-run \"match Pair (1, 2) with | Pair (a, b) -> a * b\")")

;; ── References (ref / ! / :=) ──────────────────────────────────
(epoch 600)
(eval "(ocaml-run \"let r = ref 5 in !r\")")
(epoch 601)
(eval "(ocaml-run \"let r = ref 5 in r := 10; !r\")")
(epoch 602)
(eval "(ocaml-run \"let r = ref 0 in r := !r + 1; r := !r + 1; !r\")")
(epoch 603)
(eval "(ocaml-run \"let r = ref 100 in let f x = r := !r + x in f 5; f 10; !r\")")
(epoch 604)
(eval "(ocaml-run \"let r = ref \\\"a\\\" in r := \\\"b\\\"; !r\")")
(epoch 605)
(eval "(ocaml-run \"let count = ref 0 in let rec loop n = if n = 0 then !count else (count := !count + n; loop (n - 1)) in loop 5\")")

;; ── for / while loops ──────────────────────────────────────────
(epoch 620)
(eval "(ocaml-run \"let s = ref 0 in for i = 1 to 5 do s := !s + i done; !s\")")
(epoch 621)
(eval "(ocaml-run \"let s = ref 0 in for i = 5 downto 1 do s := !s + i done; !s\")")
(epoch 622)
(eval "(ocaml-run \"let i = ref 0 in let s = ref 0 in while !i < 5 do i := !i + 1; s := !s + !i done; !s\")")
(epoch 623)
(eval "(ocaml-run \"let s = ref 0 in for i = 1 to 100 do s := !s + i done; !s\")")
(epoch 624)
(eval "(ocaml-run \"let p = ref 1 in for i = 1 to 5 do p := !p * i done; !p\")")

;; ── function (sugar for fun + match) ───────────────────────────
(epoch 640)
(eval "(ocaml-run \"(function | None -> 0 | Some x -> x) (Some 7)\")")
(epoch 641)
(eval "(ocaml-run \"let f = function | None -> 0 | Some x -> x in f None\")")
(epoch 642)
(eval "(ocaml-run \"let rec len = function | [] -> 0 | _ :: t -> 1 + len t in len [1; 2; 3]\")")
(epoch 643)
(eval "(ocaml-run-program \"let rec map f = function | [] -> [] | h :: t -> f h :: map f t;; map (fun x -> x * x) [1; 2; 3; 4]\")")

;; ── try / with / raise ─────────────────────────────────────────
(epoch 660)
(eval "(ocaml-run \"try 1 + 2 with | _ -> 0\")")
(epoch 661)
(eval "(ocaml-run \"try raise (Foo 5) with | Foo x -> x | Bar -> 99\")")
(epoch 662)
(eval "(ocaml-run \"try raise Bar with | Foo x -> x | Bar -> 99\")")
(epoch 663)
(eval "(ocaml-run \"try failwith \\\"oops\\\" with | Failure msg -> msg\")")
(epoch 664)
(eval "(ocaml-run \"try (raise (Foo 1); 999) with | Foo x -> x + 100\")")
(epoch 665)
(eval "(ocaml-run \"let f x = if x < 0 then raise (NegArg x) else x * 2 in try f (-5) with | NegArg n -> n\")")

;; ── Phase 4: Modules + field access ────────────────────────────
(epoch 700)
(eval "(ocaml-parse \"M.x\")")
(epoch 701)
(eval "(ocaml-parse \"r.field\")")
(epoch 702)
(eval "(ocaml-parse \"M.M2.x\")")
(epoch 703)
(eval "(ocaml-parse \"f r.x\")")
(epoch 710)
(eval "(ocaml-run-program \"module M = struct let x = 42 end ;; M.x\")")
(epoch 711)
(eval "(ocaml-run-program \"module M = struct let f x = x + 1 end ;; M.f 41\")")
(epoch 712)
(eval "(ocaml-run-program \"module M = struct let x = 1 let y = 2 end ;; M.x + M.y\")")
(epoch 713)
(eval "(ocaml-run-program \"module Math = struct let pi = 3.14 let square x = x * x end ;; Math.square 5\")")
(epoch 714)
(eval "(ocaml-run-program \"module Outer = struct module Inner = struct let v = 99 end end ;; Outer.Inner.v\")")
(epoch 715)
(eval "(ocaml-run-program \"module M = struct let rec fact n = if n = 0 then 1 else n * fact (n - 1) end ;; M.fact 5\")")
(epoch 716)
(eval "(ocaml-run-program \"module Pair = struct let make a b = (a, b) let swap p = match p with | (x, y) -> (y, x) end ;; Pair.swap (Pair.make 1 2)\")")

;; ── open / include ─────────────────────────────────────────────
(epoch 730)
(eval "(ocaml-run-program \"module M = struct let x = 42 let f y = y + 1 end ;; open M ;; f x\")")
(epoch 731)
(eval "(ocaml-run-program \"module Math = struct let pi = 3 let sq x = x * x end ;; module Sphere = struct include Math let area r = 4 * pi * sq r end ;; Sphere.area 2\")")
(epoch 732)
(eval "(ocaml-run-program \"module M = struct let x = 1 end ;; module N = struct open M let y = x + 10 end ;; N.y\")")
(epoch 733)
(eval "(ocaml-run-program \"module Math = struct let pi = 3 let sq x = x * x end ;; module Sphere = struct include Math let area r = 4 * pi * sq r end ;; Sphere.pi\")")
(epoch 734)
(eval "(ocaml-run-program \"module M = struct let x = 1 let y = 2 end ;; module N = struct include M let z = x + y end ;; N.z\")")

;; ── Functors ───────────────────────────────────────────────────
(epoch 750)
(eval "(ocaml-run-program \"module Add (M) = struct let add x = x + M.n end ;; module Five = struct let n = 5 end ;; module AddFive = Add(Five) ;; AddFive.add 10\")")
(epoch 751)
(eval "(ocaml-run-program \"module M = struct let x = 1 end ;; module N = M ;; N.x\")")
(epoch 752)
(eval "(ocaml-run-program \"module Outer = struct module Inner = struct let v = 42 end end ;; module Alias = Outer.Inner ;; Alias.v\")")
(epoch 753)
(eval "(ocaml-run-program \"module Pair (A) (B) = struct let mk = (A.x, B.x) end ;; module One = struct let x = 1 end ;; module Two = struct let x = 2 end ;; module P = Pair(One)(Two) ;; P.mk\")")
(epoch 754)
(eval "(ocaml-run-program \"module Identity (M) = struct include M end ;; module Base = struct let v = 99 end ;; module Same = Identity(Base) ;; Same.v\")")

;; ── Phase 6: stdlib slice (List, Option, Result) ───────────────
(epoch 800)
(eval "(ocaml-run \"List.length [1; 2; 3; 4]\")")
(epoch 801)
(eval "(ocaml-run \"List.length []\")")
(epoch 802)
(eval "(ocaml-run \"List.map (fun x -> x * 2) [1; 2; 3]\")")
(epoch 803)
(eval "(ocaml-run \"List.filter (fun x -> x > 2) [1; 2; 3; 4; 5]\")")
(epoch 804)
(eval "(ocaml-run \"List.fold_left (fun a b -> a + b) 0 [1; 2; 3; 4; 5]\")")
(epoch 805)
(eval "(ocaml-run \"List.fold_right (fun x acc -> x :: acc) [1; 2; 3] []\")")
(epoch 806)
(eval "(ocaml-run \"List.rev [1; 2; 3]\")")
(epoch 807)
(eval "(ocaml-run \"List.append [1; 2] [3; 4]\")")
(epoch 808)
(eval "(ocaml-run \"List.mem 3 [1; 2; 3]\")")
(epoch 809)
(eval "(ocaml-run \"List.mem 99 [1; 2; 3]\")")
(epoch 810)
(eval "(ocaml-run \"List.for_all (fun x -> x > 0) [1; 2; 3]\")")
(epoch 811)
(eval "(ocaml-run \"List.exists (fun x -> x > 2) [1; 2; 3]\")")
(epoch 812)
(eval "(ocaml-run \"List.hd [10; 20; 30]\")")
(epoch 813)
(eval "(ocaml-run \"List.nth [10; 20; 30] 1\")")

(epoch 820)
(eval "(ocaml-run \"Option.map (fun x -> x + 1) (Some 41)\")")
(epoch 821)
(eval "(ocaml-run \"Option.map (fun x -> x + 1) None\")")
(epoch 822)
(eval "(ocaml-run \"Option.value (Some 7) 0\")")
(epoch 823)
(eval "(ocaml-run \"Option.value None 42\")")
(epoch 824)
(eval "(ocaml-run \"Option.is_some (Some 1)\")")
(epoch 825)
(eval "(ocaml-run \"Option.is_none None\")")

(epoch 830)
(eval "(ocaml-run \"Result.map (fun x -> x + 1) (Ok 5)\")")
(epoch 831)
(eval "(ocaml-run \"Result.is_ok (Ok 1)\")")
(epoch 832)
(eval "(ocaml-run \"Result.is_error (Error \\\"oops\\\")\")")

;; ── let ... and ... mutual recursion ──────────────────────────
(epoch 850)
(eval "(ocaml-run-program \"let rec even n = if n = 0 then true else odd (n - 1) and odd n = if n = 0 then false else even (n - 1);; even 10\")")
(epoch 851)
(eval "(ocaml-run-program \"let rec even n = if n = 0 then true else odd (n - 1) and odd n = if n = 0 then false else even (n - 1);; odd 7\")")
(epoch 852)
(eval "(ocaml-run-program \"let x = 1 and y = 2;; x + y\")")

;; ── Phase 5: Hindley-Milner type inference ────────────────────
(epoch 900)
(eval "(ocaml-type-of \"42\")")
(epoch 901)
(eval "(ocaml-type-of \"true\")")
(epoch 902)
(eval "(ocaml-type-of \"\\\"hi\\\"\")")
(epoch 903)
(eval "(ocaml-type-of \"1 + 2\")")
(epoch 904)
(eval "(ocaml-type-of \"fun x -> x + 1\")")
(epoch 905)
(eval "(ocaml-type-of \"fun x -> x\")")
(epoch 906)
(eval "(ocaml-type-of \"fun x y -> x + y\")")
(epoch 907)
(eval "(ocaml-type-of \"let f x = x + 1 in f 10\")")
(epoch 908)
(eval "(ocaml-type-of \"let id = fun x -> x in id 5\")")
(epoch 909)
(eval "(ocaml-type-of \"let id = fun x -> x in id true\")")
(epoch 910)
(eval "(ocaml-type-of \"if true then 1 else 2\")")
(epoch 911)
(eval "(ocaml-type-of \"fun f -> fun x -> f (f x)\")")
(epoch 912)
(eval "(ocaml-type-of \"fun b -> if b then 1 else 0\")")
(epoch 913)
(eval "(ocaml-type-of \"not true\")")

;; ── Phase 6 expanded: String / Char / Int / Float modules ─────
(epoch 950)
(eval "(ocaml-run \"String.length \\\"hello\\\"\")")
(epoch 951)
(eval "(ocaml-run \"String.uppercase_ascii \\\"hi\\\"\")")
(epoch 952)
(eval "(ocaml-run \"String.lowercase_ascii \\\"HI\\\"\")")
(epoch 953)
(eval "(ocaml-run \"String.sub \\\"hello\\\" 1 3\")")
(epoch 954)
(eval "(ocaml-run \"String.starts_with \\\"he\\\" \\\"hello\\\"\")")
(epoch 955)
(eval "(ocaml-run \"String.concat \\\",\\\" [\\\"a\\\"; \\\"b\\\"; \\\"c\\\"]\")")

(epoch 960)
(eval "(ocaml-run \"Char.code \\\"A\\\"\")")
(epoch 961)
(eval "(ocaml-run \"Char.chr 65\")")

(epoch 970)
(eval "(ocaml-run \"Int.to_string 42\")")
(epoch 971)
(eval "(ocaml-run \"Int.of_string \\\"123\\\"\")")
(epoch 972)
(eval "(ocaml-run \"Int.abs (-5)\")")
(epoch 973)
(eval "(ocaml-run \"Int.max 7 3\")")
(epoch 974)
(eval "(ocaml-run \"Int.min 7 3\")")

;; ── Unit / wildcard parameters ──────────────────────────────────
(epoch 1000)
(eval "(ocaml-run \"let f () = 42 in f ()\")")
(epoch 1001)
(eval "(ocaml-run \"(fun () -> 99) ()\")")
(epoch 1002)
(eval "(ocaml-run \"let f _ = 1 in f 5\")")
(epoch 1003)
(eval "(ocaml-run-program \"let f () = 7;; f ()\")")
(epoch 1004)
(eval "(ocaml-run-program \"let g _ x = x + 1;; g 99 41\")")

;; ── Records ────────────────────────────────────────────────────
(epoch 1100)
(eval "(ocaml-run \"let r = { x = 1; y = 2 } in r.x\")")
(epoch 1101)
(eval "(ocaml-run \"let r = { x = 1; y = 2 } in r.x + r.y\")")
(epoch 1102)
(eval "(ocaml-run \"let r = { x = 1; y = 2 } in let r2 = { r with x = 99 } in r2.x + r2.y\")")
(epoch 1103)
(eval "(ocaml-run \"let p = { name = \\\"Bob\\\"; age = 30 } in p.name\")")
(epoch 1104)
(eval "(ocaml-run \"let p = { name = \\\"Bob\\\"; age = 30 } in p.age\")")
(epoch 1105)
(eval "(ocaml-run-program \"let r = { x = 1; y = 2 };; r.x + r.y\")")

;; ── as / when in match ─────────────────────────────────────────
(epoch 1200)
(eval "(ocaml-run \"match Some 5 with | Some x as p -> x | None -> 0\")")
(epoch 1201)
(eval "(ocaml-run \"match 5 with | n when n > 0 -> 1 | n when n < 0 -> -1 | _ -> 0\")")
(epoch 1202)
(eval "(ocaml-run \"match (-3) with | n when n > 0 -> 1 | n when n < 0 -> -1 | _ -> 0\")")
(epoch 1203)
(eval "(ocaml-run \"match 0 with | n when n > 0 -> 1 | n when n < 0 -> -1 | _ -> 0\")")
(epoch 1204)
(eval "(ocaml-run \"match (Some 7) with | None -> 0 | Some x when x > 5 -> x * 10 | Some x -> x\")")
(epoch 1205)
(eval "(ocaml-run \"match (Some 3) with | None -> 0 | Some x when x > 5 -> x * 10 | Some x -> x\")")

;; ── type declarations (parser + runtime) ──────────────────────
(epoch 1300)
(eval "(ocaml-parse-program \"type color = Red | Green | Blue\")")
(epoch 1301)
(eval "(ocaml-parse-program \"type shape = Circle of int | Rect of int | Square of int\")")
(epoch 1302)
(eval "(ocaml-run-program \"type color = Red | Green | Blue ;; match Red with | Red -> 1 | Green -> 2 | Blue -> 3\")")
(epoch 1303)
(eval "(ocaml-run-program \"type color = Red | Green | Blue ;; match Blue with | Red -> 1 | Green -> 2 | Blue -> 3\")")
(epoch 1304)
(eval "(ocaml-run-program \"type shape = Circle of int | Square of int ;; match Circle 5 with | Circle r -> r | Square s -> s\")")

;; ── exception declarations ────────────────────────────────────
(epoch 1320)
(eval "(ocaml-parse-program \"exception MyExn\")")
(epoch 1321)
(eval "(ocaml-parse-program \"exception MyExn of int\")")
(epoch 1322)
(eval "(ocaml-run-program \"exception E of int ;; try raise (E 5) with | E n -> n\")")
(epoch 1323)
(eval "(ocaml-run-program \"exception E of string ;; try raise (E \\\"oops\\\") with | E s -> s\")")

;; ── Phase 6 expanded stdlib (List/Option/Result extensions) ───
(epoch 1400)
(eval "(ocaml-run \"List.concat [[1;2];[3];[4;5]]\")")
(epoch 1401)
(eval "(ocaml-run \"List.init 5 (fun i -> i * 10)\")")
(epoch 1402)
(eval "(ocaml-run \"List.find_opt (fun x -> x > 2) [1;2;3;4]\")")
(epoch 1403)
(eval "(ocaml-run \"List.find_opt (fun x -> x > 99) [1;2;3]\")")
(epoch 1404)
(eval "(ocaml-run \"List.mapi (fun i x -> i + x) [10;20;30]\")")
(epoch 1405)
(eval "(ocaml-run \"List.partition (fun x -> x > 2) [1;2;3;4]\")")
(epoch 1406)
(eval "(ocaml-run \"List.assoc 2 [(1, \\\"a\\\"); (2, \\\"b\\\"); (3, \\\"c\\\")]\")")
(epoch 1407)
(eval "(ocaml-run \"List.assoc_opt 99 [(1, \\\"a\\\")]\")")

(epoch 1410)
(eval "(ocaml-run \"Option.fold 0 (fun x -> x * 10) (Some 7)\")")
(epoch 1411)
(eval "(ocaml-run \"Option.fold 0 (fun x -> x * 10) None\")")
(epoch 1412)
(eval "(ocaml-run \"Option.to_list (Some 7)\")")
(epoch 1413)
(eval "(ocaml-run \"Option.to_list None\")")

(epoch 1420)
(eval "(ocaml-run \"Result.get_ok (Ok 42)\")")
(epoch 1421)
(eval "(ocaml-run \"Result.to_option (Ok 1)\")")
(epoch 1422)
(eval "(ocaml-run \"Result.map_error (fun e -> e + 1) (Error 5)\")")

;; ── HM extensions: tuples + lists ──────────────────────────────
(epoch 1500)
(eval "(ocaml-type-of \"(1, 2)\")")
(epoch 1501)
(eval "(ocaml-type-of \"(1, true, \\\"hi\\\")\")")
(epoch 1502)
(eval "(ocaml-type-of \"[1; 2; 3]\")")
(epoch 1503)
(eval "(ocaml-type-of \"[]\")")
(epoch 1504)
(eval "(ocaml-type-of \"fun x -> [x; x]\")")
(epoch 1505)
(eval "(ocaml-type-of \"fun x y -> (x, y)\")")
(epoch 1506)
(eval "(ocaml-type-of \"[true; false]\")")

;; ── Hashtbl ────────────────────────────────────────────────────
(epoch 1600)
(eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t \\\"x\\\" 42;; Hashtbl.find t \\\"x\\\"\")")
(epoch 1601)
(eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t 1 \\\"a\\\";; Hashtbl.add t 2 \\\"b\\\";; Hashtbl.length t\")")
(epoch 1602)
(eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t 1 100;; Hashtbl.find_opt t 99\")")
(epoch 1603)
(eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t 1 100;; Hashtbl.mem t 1\")")
(epoch 1604)
(eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t \\\"a\\\" 1;; Hashtbl.replace t \\\"a\\\" 2;; Hashtbl.find t \\\"a\\\"\")")
(epoch 1605)
(eval "(ocaml-run-program \"let t = Hashtbl.create 10;; Hashtbl.add t \\\"k\\\" 5;; Hashtbl.find_opt t \\\"k\\\"\")")

;; ── List.sort + compare ────────────────────────────────────────
(epoch 1700)
(eval "(ocaml-run \"compare 1 2\")")
(epoch 1701)
(eval "(ocaml-run \"compare 5 5\")")
(epoch 1702)
(eval "(ocaml-run \"compare 9 1\")")
(epoch 1703)
(eval "(ocaml-run \"List.sort compare [3; 1; 4; 1; 5; 9; 2; 6]\")")
(epoch 1704)
(eval "(ocaml-run \"List.sort (fun a b -> b - a) [3; 1; 4]\")")
(epoch 1705)
(eval "(ocaml-run \"List.sort compare []\")")
(epoch 1706)
(eval "(ocaml-run \"List.sort compare [\\\"b\\\"; \\\"a\\\"; \\\"c\\\"]\")")

;; ── HM pattern-match inference ─────────────────────────────────
(epoch 1800)
(eval "(ocaml-type-of \"match 1 with | n -> n + 1\")")
(epoch 1801)
(eval "(ocaml-type-of \"match [1;2] with | [] -> 0 | h :: t -> h\")")
(epoch 1802)
(eval "(ocaml-type-of \"match (1, 2) with | (a, b) -> a + b\")")
(epoch 1803)
(eval "(ocaml-type-of \"fun x -> match x with | 0 -> 0 | n -> n + 1\")")
(epoch 1804)
(eval "(ocaml-type-of \"fun lst -> match lst with | [] -> 0 | h :: _ -> h\")")

;; ── HM constructor inference (option/result) ───────────────────
(epoch 1900)
(eval "(ocaml-type-of \"Some 5\")")
(epoch 1901)
(eval "(ocaml-type-of \"None\")")
(epoch 1902)
(eval "(ocaml-type-of \"Ok 1\")")
(epoch 1903)
(eval "(ocaml-type-of \"Error \\\"oops\\\"\")")
(epoch 1904)
(eval "(ocaml-type-of \"fun x -> Some x\")")
(epoch 1905)
(eval "(ocaml-type-of \"match Some 5 with | None -> 0 | Some n -> n\")")
(epoch 1906)
(eval "(ocaml-type-of \"fun o -> match o with | None -> 0 | Some n -> n\")")

;; ── HM let-rec inference + cons / append ──────────────────────
(epoch 2000)
(eval "(ocaml-type-of \"let rec fact n = if n = 0 then 1 else n * fact (n - 1) in fact\")")
(epoch 2001)
(eval "(ocaml-type-of \"let rec len lst = match lst with | [] -> 0 | _ :: t -> 1 + len t in len\")")
(epoch 2002)
(eval "(ocaml-type-of \"let rec map f xs = match xs with | [] -> [] | h :: t -> f h :: map f t in map\")")
(epoch 2003)
(eval "(ocaml-type-of \"1 :: [2; 3]\")")
(epoch 2004)
(eval "(ocaml-type-of \"[1] @ [2; 3]\")")
(epoch 2005)
(eval "(ocaml-type-of \"let rec sum lst = match lst with | [] -> 0 | h :: t -> h + sum t in sum [1; 2; 3]\")")

;; ── HM with user type declarations ─────────────────────────────
(epoch 2100)
(eval "(ocaml-type-of-program \"type color = Red | Green | Blue;; Red\")")
(epoch 2101)
(eval "(ocaml-type-of-program \"type shape = Circle of int | Square of int;; Circle 5\")")
(epoch 2102)
(eval "(ocaml-type-of-program \"type color = Red | Green;; let c = Red;; c\")")
(epoch 2103)
(eval "(ocaml-type-of-program \"type shape = Circle of int | Square of int;; let area s = match s with | Circle r -> r * r | Square s -> s * s;; area\")")
(epoch 2104)
(eval "(ocaml-type-of-program \"let x = 1;; let y = 2;; x + y\")")
(epoch 2105)
(eval "(ocaml-type-of-program \"let rec fact n = if n = 0 then 1 else n * fact (n - 1);; fact 5\")")

;; ── More List functions: combine/split/iter2/fold_left2/map2 ──
(epoch 2200)
(eval "(ocaml-run \"List.combine [1;2;3] [\\\"a\\\";\\\"b\\\";\\\"c\\\"]\")")
(epoch 2201)
(eval "(ocaml-run \"List.split [(1,\\\"a\\\");(2,\\\"b\\\")]\")")
(epoch 2202)
(eval "(ocaml-run \"List.fold_left2 (fun a b c -> a + b + c) 0 [1;2;3] [10;20;30]\")")
(epoch 2203)
(eval "(ocaml-run \"List.map2 (fun a b -> a + b) [1;2;3] [10;20;30]\")")

;; ── Float arithmetic ───────────────────────────────────────────
(epoch 2300)
(eval "(ocaml-run \"1.5 +. 2.5\")")
(epoch 2301)
(eval "(ocaml-run \"3.0 *. 2.0\")")
(epoch 2302)
(eval "(ocaml-run \"10.0 /. 4.0\")")
(epoch 2303)
(eval "(ocaml-type-of \"1.5 +. 2.5\")")
(epoch 2304)
(eval "(ocaml-type-of \"fun x y -> x +. y\")")

;; ── Float module ───────────────────────────────────────────────
(epoch 2400)
(eval "(ocaml-run \"Float.sqrt 16.0\")")
(epoch 2401)
(eval "(ocaml-run \"Float.sin 0.0\")")
(epoch 2402)
(eval "(ocaml-run \"Float.cos 0.0\")")
(epoch 2403)
(eval "(ocaml-run \"Float.pow 2.0 10.0\")")
(epoch 2404)
(eval "(ocaml-run \"Float.floor 3.7\")")
(epoch 2405)
(eval "(ocaml-run \"Float.ceil 3.2\")")

;; ── Polymorphic variants ──────────────────────────────────────
(epoch 2500)
(eval "(ocaml-run \"\`Red\")")
(epoch 2501)
(eval "(ocaml-run \"\`Some 42\")")
(epoch 2502)
(eval "(ocaml-run \"match \`Red with | \`Red -> 1 | \`Green -> 2 | \`Blue -> 3\")")
(epoch 2503)
(eval "(ocaml-run \"match \`Pair (1, 2) with | \`Pair (a, b) -> a + b\")")

;; ── Record patterns ───────────────────────────────────────────
(epoch 2600)
(eval "(ocaml-run \"match { x = 1; y = 2 } with | { x = a; y = b } -> a + b\")")
(epoch 2601)
(eval "(ocaml-run \"match { name = \\\"Bob\\\"; age = 30 } with | { name = n; age = a } -> a\")")
(epoch 2602)
(eval "(ocaml-run \"match { x = 1; y = 2 } with | { x = 1; y = y } -> y | _ -> 0\")")
(epoch 2603)
(eval "(ocaml-run \"match { x = 5; y = 2 } with | { x = 1; y = y } -> y | _ -> 0\")")

;; ── module type S = sig … end ─────────────────────────────────
(epoch 2700)
(eval "(ocaml-parse-program \"module type S = sig val x : int val f : int -> int end\")")
(epoch 2701)
(eval "(ocaml-run-program \"module type S = sig val x : int end ;; module M = struct let x = 42 end ;; M.x\")")
(epoch 2702)
(eval "(ocaml-parse-program \"module type EMPTY = sig end\")")

;; ── or-patterns (parens-only) ─────────────────────────────────
(epoch 2800)
(eval "(ocaml-run \"match 1 with | (1 | 2 | 3) -> 100 | _ -> 0\")")
(epoch 2801)
(eval "(ocaml-run \"match 2 with | (1 | 2 | 3) -> 100 | _ -> 0\")")
(epoch 2802)
(eval "(ocaml-run \"match 5 with | (1 | 2 | 3) -> 100 | _ -> 0\")")
(epoch 2803)
(eval "(ocaml-run \"match Red with | (Red | Green) -> 1 | Blue -> 2\")")
(epoch 2804)
(eval "(ocaml-run \"match Blue with | (Red | Green) -> 1 | Blue -> 2\")")

;; ── More List utilities (take/drop/filter_map/flat_map) ──────
(epoch 2900)
(eval "(ocaml-run \"List.take 3 [1;2;3;4;5]\")")
(epoch 2901)
(eval "(ocaml-run \"List.drop 2 [1;2;3;4;5]\")")
(epoch 2902)
(eval "(ocaml-run \"List.filter_map (fun x -> if x > 2 then Some (x * 10) else None) [1;2;3;4]\")")
(epoch 2903)
(eval "(ocaml-run \"List.flat_map (fun x -> [x; x]) [1;2;3]\")")
(epoch 2904)
(eval "(ocaml-run \"List.take 0 [1;2;3]\")")
(epoch 2905)
(eval "(ocaml-run \"List.take 100 [1;2;3]\")")

;; ── String extensions ──────────────────────────────────────────
(epoch 3000)
(eval "(ocaml-run \"String.ends_with \\\"lo\\\" \\\"hello\\\"\")")
(epoch 3001)
(eval "(ocaml-run \"String.contains \\\"hello\\\" \\\"ell\\\"\")")
(epoch 3002)
(eval "(ocaml-run \"String.trim \\\"  hi  \\\"\")")
(epoch 3003)
(eval "(ocaml-run \"String.split_on_char \\\" \\\" \\\"a b c\\\"\")")
(epoch 3004)
(eval "(ocaml-run \"String.replace_all \\\"hello\\\" \\\"l\\\" \\\"r\\\"\")")
(epoch 3005)
(eval "(ocaml-run \"String.index_of \\\"hello\\\" \\\"ll\\\"\")")

;; ── HM with parsed ctor arg types ──────────────────────────────
(epoch 3100)
(eval "(ocaml-type-of-program \"type shape = Circle of int | Square of int;; let area s = match s with | Circle r -> r * r | Square s -> s * s;; area\")")
(epoch 3101)
(eval "(ocaml-type-of-program \"type tag = TStr of string | TInt of int;; TStr \\\"hi\\\"\")")
(epoch 3102)
(eval "(ocaml-type-of-program \"type t = A of bool | B of float;; A true\")")

;; ── Sys module stubs ──────────────────────────────────────────
(epoch 3200)
(eval "(ocaml-run \"Sys.os_type\")")
(epoch 3201)
(eval "(ocaml-run \"Sys.word_size\")")
(epoch 3202)
(eval "(ocaml-run \"Sys.unix\")")
(epoch 3203)
(eval "(ocaml-run \"Sys.win32\")")
(epoch 3204)
(eval "(ocaml-run \"Sys.executable_name\")")

;; ── Map.Make / Set.Make functors ──────────────────────────────
(epoch 3300)
(eval "(ocaml-run-program \"module IntOrd = struct let compare a b = compare a b end ;; module IntMap = Map.Make(IntOrd) ;; let m = IntMap.add 1 \\\"a\\\" IntMap.empty ;; let m = IntMap.add 2 \\\"b\\\" m ;; IntMap.find 1 m\")")
(epoch 3301)
(eval "(ocaml-run-program \"module IntOrd = struct let compare a b = compare a b end ;; module IntMap = Map.Make(IntOrd) ;; IntMap.cardinal (IntMap.add 1 \\\"a\\\" (IntMap.add 2 \\\"b\\\" IntMap.empty))\")")
(epoch 3302)
(eval "(ocaml-run-program \"module IntOrd = struct let compare a b = compare a b end ;; module IntSet = Set.Make(IntOrd) ;; IntSet.elements (IntSet.add 3 (IntSet.add 1 (IntSet.add 2 IntSet.empty)))\")")
(epoch 3303)
(eval "(ocaml-run-program \"module IntOrd = struct let compare a b = compare a b end ;; module IntSet = Set.Make(IntOrd) ;; IntSet.mem 2 (IntSet.add 3 (IntSet.add 1 (IntSet.add 2 IntSet.empty)))\")")

;; ── Map/Set fold/iter/filter/union/inter ──────────────────────
(epoch 3400)
(eval "(ocaml-run-program \"module IntOrd = struct let compare a b = compare a b end ;; module IntMap = Map.Make(IntOrd) ;; let m = IntMap.add 1 10 (IntMap.add 2 20 IntMap.empty) ;; IntMap.fold (fun k v acc -> acc + v) m 0\")")
(epoch 3401)
(eval "(ocaml-run-program \"module IntOrd = struct let compare a b = compare a b end ;; module IntMap = Map.Make(IntOrd) ;; let m = IntMap.add 1 10 IntMap.empty ;; IntMap.is_empty m\")")
(epoch 3402)
(eval "(ocaml-run-program \"module IntOrd = struct let compare a b = compare a b end ;; module IntSet = Set.Make(IntOrd) ;; let a = IntSet.add 1 (IntSet.add 2 IntSet.empty) ;; let b = IntSet.add 2 (IntSet.add 3 IntSet.empty) ;; IntSet.elements (IntSet.union a b)\")")
(epoch 3403)
(eval "(ocaml-run-program \"module IntOrd = struct let compare a b = compare a b end ;; module IntSet = Set.Make(IntOrd) ;; let a = IntSet.add 1 (IntSet.add 2 (IntSet.add 3 IntSet.empty)) ;; let b = IntSet.add 2 (IntSet.add 3 (IntSet.add 4 IntSet.empty)) ;; IntSet.elements (IntSet.inter a b)\")")

;; ── Buffer module ──────────────────────────────────────────────
(epoch 3500)
(eval "(ocaml-run-program \"let b = Buffer.create 16 ;; Buffer.add_string b \\\"Hello\\\" ;; Buffer.add_string b \\\", \\\" ;; Buffer.add_string b \\\"World\\\" ;; Buffer.contents b\")")
(epoch 3501)
(eval "(ocaml-run-program \"let b = Buffer.create 16 ;; Buffer.add_string b \\\"abc\\\" ;; Buffer.length b\")")
(epoch 3502)
(eval "(ocaml-run-program \"let b = Buffer.create 16 ;; Buffer.add_string b \\\"x\\\" ;; Buffer.clear b ;; Buffer.contents b\")")

;; ── Stack + Queue modules ─────────────────────────────────────
(epoch 3600)
(eval "(ocaml-run-program \"let s = Stack.create () ;; Stack.push 1 s ;; Stack.push 2 s ;; Stack.push 3 s ;; Stack.pop s\")")
(epoch 3601)
(eval "(ocaml-run-program \"let s = Stack.create () ;; Stack.push 1 s ;; Stack.push 2 s ;; Stack.length s\")")
(epoch 3602)
(eval "(ocaml-run-program \"let s = Stack.create () ;; Stack.push 1 s ;; Stack.top s\")")
(epoch 3603)
(eval "(ocaml-run-program \"let q = Queue.create () ;; Queue.push 1 q ;; Queue.push 2 q ;; Queue.push 3 q ;; Queue.pop q\")")
(epoch 3604)
(eval "(ocaml-run-program \"let q = Queue.create () ;; Queue.push 1 q ;; Queue.push 2 q ;; Queue.length q\")")

;; ── Option/Result/Bytes extensions ────────────────────────────
(epoch 3700)
(eval "(ocaml-run \"Option.join (Some (Some 5))\")")
(epoch 3701)
(eval "(ocaml-run \"Option.join None\")")
(epoch 3702)
(eval "(ocaml-run \"Option.to_result \\\"missing\\\" None\")")
(epoch 3703)
(eval "(ocaml-run \"Option.to_result \\\"missing\\\" (Some 7)\")")
(epoch 3704)
(eval "(ocaml-run \"Result.value (Ok 5) 0\")")
(epoch 3705)
(eval "(ocaml-run \"Result.value (Error \\\"e\\\") 99\")")
(epoch 3706)
(eval "(ocaml-run \"Result.fold (fun x -> x * 10) (fun e -> 0) (Ok 5)\")")
(epoch 3707)
(eval "(ocaml-run \"Bytes.length \\\"hello\\\"\")")
(epoch 3708)
(eval "(ocaml-run \"Bytes.concat \\\"-\\\" [\\\"a\\\";\\\"b\\\";\\\"c\\\"]\")")

;; ── HM let-mut / let-rec-mut ──────────────────────────────────
(epoch 3800)
(eval "(ocaml-type-of \"let x = 1 and y = 2 in x + y\")")
(epoch 3801)
(eval "(ocaml-type-of \"let rec even n = if n = 0 then true else odd (n - 1) and odd n = if n = 0 then false else even (n - 1) in even\")")
(epoch 3802)
(eval "(ocaml-type-of \"let f x = x + 1 and g x = x * 2 in f 1 + g 2\")")

;; ── let _ = expr top-level ─────────────────────────────────────
(epoch 3900)
(eval "(ocaml-run-program \"let _ = 1 + 2 ;; 42\")")
(epoch 3901)
(eval "(ocaml-run-program \"let x = 10 ;; let _ = x ;; x * 2\")")

;; ── Record type declarations ──────────────────────────────────
(epoch 4000)
(eval "(ocaml-parse-program \"type point = { x : int; y : int }\")")
(epoch 4001)
(eval "(ocaml-parse-program \"type r = { mutable x : int; y : string }\")")
(epoch 4002)
(eval "(ocaml-run-program \"type point = { x : int; y : int };; let p = { x = 3; y = 4 };; p.x + p.y\")")

;; ── Mutable record fields (r.f <- v) ──────────────────────────
(epoch 4100)
(eval "(ocaml-run \"let r = { x = 1; y = 2 } in r.x <- 5; r.x\")")
(epoch 4101)
(eval "(ocaml-run \"let r = { x = 0 } in for i = 1 to 5 do r.x <- r.x + i done; r.x\")")
(epoch 4102)
(eval "(ocaml-run \"let r = { name = \\\"Bob\\\"; age = 30 } in r.name <- \\\"Alice\\\"; r.name\")")
(epoch 4103)
(eval "(ocaml-run \"let r = { x = 1; y = 2 } in r.x <- r.y * 10; r.x\")")

;; ── HM top-level def-mut / def-rec-mut ────────────────────────
(epoch 4200)
(eval "(ocaml-type-of-program \"let x = 1 and y = 2;; x + y\")")
(epoch 4201)
(eval "(ocaml-type-of-program \"let rec even n = if n = 0 then true else odd (n - 1) and odd n = if n = 0 then false else even (n - 1);; even 10\")")
(epoch 4202)
(eval "(ocaml-type-of-program \"let rec map f xs = match xs with | [] -> [] | h :: t -> f h :: map f t and length lst = match lst with | [] -> 0 | _ :: t -> 1 + length t;; map\")")

;; ── Char predicate helpers ────────────────────────────────────
(epoch 4300)
(eval "(ocaml-run \"Char.is_digit \\\"5\\\"\")")
(epoch 4301)
(eval "(ocaml-run \"Char.is_digit \\\"x\\\"\")")
(epoch 4302)
(eval "(ocaml-run \"Char.is_alpha \\\"x\\\"\")")
(epoch 4303)
(eval "(ocaml-run \"Char.is_alnum \\\"5\\\"\")")
(epoch 4304)
(eval "(ocaml-run \"Char.is_whitespace \\\" \\\"\")")
(epoch 4305)
(eval "(ocaml-run \"Char.is_upper \\\"A\\\"\")")
(epoch 4306)
(eval "(ocaml-run \"Char.is_lower \\\"a\\\"\")")

;; ── function with `when` guard ────────────────────────────────
(epoch 4400)
(eval "(ocaml-run \"(function | n when n > 0 -> 1 | _ -> 0) 5\")")
(epoch 4401)
(eval "(ocaml-run \"(function | n when n > 0 -> 1 | _ -> 0) (-3)\")")
(epoch 4402)
(eval "(ocaml-run \"(function | n when n > 0 -> 1 | n when n < 0 -> -1 | _ -> 0) 0\")")

;; ── try/with `when` guard ─────────────────────────────────────
(epoch 4500)
(eval "(ocaml-run \"try raise (E 5) with | E n when n > 0 -> n | _ -> 0\")")
(epoch 4501)
(eval "(ocaml-run \"try raise (E (-3)) with | E n when n > 0 -> n | _ -> 0\")")
(epoch 4502)
(eval "(ocaml-run \"try raise (E 5) with | E n when n > 100 -> n | E n -> n + 1000\")")

;; ── type aliases ──────────────────────────────────────────────
(epoch 4600)
(eval "(ocaml-parse-program \"type t = int\")")
(epoch 4601)
(eval "(ocaml-run-program \"type t = int;; 42\")")

;; ── Type annotations: let x : T = e and (e : T) ──────────────
(epoch 4700)
(eval "(ocaml-run-program \"let x : int = 5;; x + 1\")")
(epoch 4701)
(eval "(ocaml-run \"let f (x : int) : int = x + 1 in f 41\")")
(epoch 4702)
(eval "(ocaml-run \"(5 : int)\")")
(epoch 4703)
(eval "(ocaml-run \"((1 + 2) : int) * 3\")")

;; ── Module body: def-mut / def-rec-mut ─────────────────────────
(epoch 4800)
(eval "(ocaml-run-program \"module M = struct let rec a n = if n = 0 then 0 else b (n - 1) and b n = if n = 0 then 1 else a (n - 1) end ;; M.a 5\")")
(epoch 4801)
(eval "(ocaml-run-program \"module M = struct let x = 1 and y = 2 end ;; M.x + M.y\")")

;; ── let open M in body ────────────────────────────────────────
(epoch 4900)
(eval "(ocaml-run \"let open List in length [1;2;3]\")")
(epoch 4901)
(eval "(ocaml-run \"let open List in map (fun x -> x * 2) [1;2;3]\")")
(epoch 4902)
(eval "(ocaml-run \"let open Option in map (fun x -> x + 1) (Some 5)\")")

;; ── M.(expr) local-open expression form ───────────────────────
(epoch 4910)
(eval "(ocaml-run \"List.(length [1;2;3])\")")
(epoch 4911)
(eval "(ocaml-run \"List.(map (fun x -> x + 1) [1;2;3])\")")
(epoch 4912)
(eval "(ocaml-run \"Option.(map (fun x -> x * 10) (Some 4))\")")

;; ── s.[i] string indexing ─────────────────────────────────────
(epoch 4920)
(eval "(ocaml-run \"let s = \\\"hello\\\" in s.[0]\")")
(epoch 4921)
(eval "(ocaml-run \"let s = \\\"abc\\\" in Char.code s.[2]\")")
(epoch 4922)
(eval "(ocaml-run \"let s = \\\"hi\\\" in let n = ref 0 in for i = 0 to String.length s - 1 do n := !n + Char.code s.[i] done; !n\")")

;; ── assert ────────────────────────────────────────────────────
(epoch 4930)
(eval "(ocaml-run \"assert true; 42\")")
(epoch 4931)
(eval "(ocaml-run \"let x = 5 in assert (x = 5); x + 1\")")
(epoch 4932)
(eval "(ocaml-run \"try (assert false; 0) with _ -> 99\")")

;; ── Printf.sprintf + global string_of_* ──────────────────────
(epoch 4940)
(eval "(ocaml-run \"Printf.sprintf \\\"hello\\\"\")")
(epoch 4941)
(eval "(ocaml-run \"Printf.sprintf \\\"x=%d\\\" 42\")")
(epoch 4942)
(eval "(ocaml-run \"Printf.sprintf \\\"%s = %d\\\" \\\"answer\\\" 42\")")
(epoch 4943)
(eval "(ocaml-run \"Printf.sprintf \\\"%d%%\\\" 50\")")
(epoch 4944)
(eval "(ocaml-run \"string_of_int 7 ^ \\\"-\\\" ^ string_of_bool true\")")

;; ── Hashtbl.iter / Hashtbl.fold ─────────────────────────────
(epoch 4950)
(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"a\\\" 1; Hashtbl.add t \\\"b\\\" 2; Hashtbl.add t \\\"c\\\" 3; Hashtbl.fold (fun _ v acc -> acc + v) t 0\")")
(epoch 4951)
(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"x\\\" 10; Hashtbl.add t \\\"y\\\" 20; let total = ref 0 in Hashtbl.iter (fun _ v -> total := !total + v) t; !total\")")

;; ── lazy / Lazy.force ─────────────────────────────────────────
(epoch 4960)
(eval "(ocaml-run \"let x = lazy (1 + 2) in Lazy.force x\")")
(epoch 4961)
(eval "(ocaml-run \"let counter = ref 0 in let lz = lazy (counter := !counter + 1; 42) in let a = Lazy.force lz in let b = Lazy.force lz in (a + b) * 100 + !counter\")")

;; ── Format alias of Printf ────────────────────────────────────
(epoch 4970)
(eval "(ocaml-run \"Format.sprintf \\\"%d\\\" 99\")")
(epoch 4971)
(eval "(ocaml-run \"Format.asprintf \\\"%s=%d\\\" \\\"n\\\" 7\")")

;; ── String.iter / fold / seq ─────────────────────────────────
(epoch 4980)
(eval "(ocaml-run \"let n = ref 0 in String.iter (fun c -> n := !n + Char.code c) \\\"abc\\\"; !n\")")
(epoch 4981)
(eval "(ocaml-run \"String.fold_left (fun acc c -> acc + Char.code c) 0 \\\"hi\\\"\")")
(epoch 4982)
(eval "(ocaml-run \"String.of_seq (List.rev (String.to_seq \\\"hello\\\"))\")")

;; ── List.sort_uniq / List.find_map ───────────────────────────
(epoch 4990)
(eval "(ocaml-run \"List.sort_uniq compare [3;1;2;1;3;2;4]\")")
(epoch 4991)
(eval "(ocaml-run \"List.find_map (fun x -> if x > 5 then Some (x * 2) else None) [1;2;3;6;7]\")")

;; ── Polymorphic variants ──────────────────────────────────────
(epoch 5000)
(eval "(ocaml-run \"let x = `Foo in match x with `Foo -> 1 | `Bar -> 2\")")
(epoch 5001)
(eval "(ocaml-run \"let x = `Pair (5, 7) in match x with `Pair (a, b) -> a + b | _ -> 0\")")
(epoch 5002)
(eval "(ocaml-run \"List.map (fun x -> match x with `On -> 1 | `Off -> 0) [`On; `Off; `On]\")")

;; ── Array module ─────────────────────────────────────────────
(epoch 5010)
(eval "(ocaml-run \"let a = Array.make 5 7 in Array.set a 2 99; Array.fold_left (+) 0 a\")")
(epoch 5011)
(eval "(ocaml-run \"let a = Array.init 4 (fun i -> i * i) in Array.fold_left (+) 0 a\")")
(epoch 5012)
(eval "(ocaml-run \"let a = Array.make 3 0 in for i = 0 to 2 do Array.set a i (i + 1) done; Array.length a + Array.get a 0 + Array.get a 1 + Array.get a 2\")")

;; ── (op) operator sections ───────────────────────────────────
(epoch 5020)
(eval "(ocaml-run \"List.fold_left (+) 0 [1;2;3;4;5]\")")
(epoch 5021)
(eval "(ocaml-run \"let f = ( * ) in f 6 7\")")
(epoch 5022)
(eval "(ocaml-run \"List.map ((-) 10) [1;2;3]\")")

;; ── arr.(i) and arr.(i) <- v ────────────────────────────────
(epoch 5030)
(eval "(ocaml-run \"let a = Array.make 5 7 in a.(2) <- 99; a.(2) + a.(0)\")")
(epoch 5031)
(eval "(ocaml-run \"let a = Array.init 4 (fun i -> i + 1) in a.(0) + a.(1) + a.(2) + a.(3)\")")
(epoch 5032)
(eval "(ocaml-run \"let a = Array.make 5 0 in for i = 0 to 4 do a.(i) <- i * i done; a.(3) + a.(4)\")")

;; ── Array.sort / sub / append / exists / mem ─────────────────
(epoch 5040)
(eval "(ocaml-run \"let a = Array.of_list [3;1;4;1;5;9;2;6] in Array.sort compare a; Array.to_list a\")")
(epoch 5041)
(eval "(ocaml-run \"let a = Array.of_list [10;20;30;40;50] in let b = Array.sub a 1 3 in Array.fold_left (+) 0 b\")")
(epoch 5042)
(eval "(ocaml-run \"let a = Array.of_list [1;2;3] in let b = Array.of_list [4;5;6] in Array.fold_left (+) 0 (Array.append a b)\")")
(epoch 5043)
(eval "(ocaml-run \"let a = Array.init 5 (fun i -> i * 2) in Array.exists (fun x -> x = 6) a\")")
(epoch 5044)
(eval "(ocaml-run \"let a = Array.of_list [1;2;3;4;5] in Array.mem 3 a\")")

;; ── Integer division + Int module + max_int ──────────────────
(epoch 5050)
(eval "(ocaml-run \"17 / 5\")")
(epoch 5051)
(eval "(ocaml-run \"-17 / 5\")")
(epoch 5052)
(eval "(ocaml-run \"Int.rem 17 5\")")
(epoch 5053)
(eval "(ocaml-run \"Int.compare 5 3\")")
(epoch 5054)
(eval "(ocaml-run \"max_int + min_int\")")

;; ── List.sort mergesort ──────────────────────────────────────
(epoch 5060)
(eval "(ocaml-run \"List.sort compare [5;2;8;1;9;3;7;4;6]\")")
(epoch 5061)
(eval "(ocaml-run \"List.sort (fun a b -> b - a) [3;1;4;1;5]\")")
(epoch 5062)
(eval "(ocaml-run \"List.length (List.sort compare [9;8;7;6;5;4;3;2;1;0])\")")

;; ── Printf %i %x %X %o ─────────────────────────────────────────
(epoch 5070)
(eval "(ocaml-run \"Printf.sprintf \\\"%i\\\" 42\")")
(epoch 5071)
(eval "(ocaml-run \"Printf.sprintf \\\"%x\\\" 255\")")
(epoch 5072)
(eval "(ocaml-run \"Printf.sprintf \\\"%X\\\" 4096\")")
(epoch 5073)
(eval "(ocaml-run \"Printf.sprintf \\\"%o\\\" 8\")")
(epoch 5074)
(eval "(ocaml-run \"Printf.sprintf \\\"%x %X %o\\\" 255 4096 8\")")

;; ── Printf width specifiers ─────────────────────────────────
(epoch 5080)
(eval "(ocaml-run \"Printf.sprintf \\\"%5d\\\" 42\")")
(epoch 5081)
(eval "(ocaml-run \"Printf.sprintf \\\"%-5d|\\\" 42\")")
(epoch 5082)
(eval "(ocaml-run \"Printf.sprintf \\\"%05d\\\" 42\")")
(epoch 5083)
(eval "(ocaml-run \"Printf.sprintf \\\"%4s\\\" \\\"hi\\\"\")")
(epoch 5084)
(eval "(ocaml-run \"Printf.sprintf \\\"hi=%-3d, hex=%04x\\\" 9 15\")")

;; ── let (a, b) = expr in body — tuple destructure ─────────────
(epoch 5090)
(eval "(ocaml-run \"let (a, b) = (1, 2) in a + b\")")
(epoch 5091)
(eval "(ocaml-run \"let (a, b, c) = (10, 20, 30) in a + b + c\")")
(epoch 5092)
(eval "(ocaml-run \"let pair = (5, 7) in let (x, y) = pair in x * y\")")

;; ── Hashtbl.keys / values / remove / clear ────────────────────
(epoch 5100)
(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"a\\\" 1; Hashtbl.add t \\\"b\\\" 2; List.length (Hashtbl.keys t)\")")
(epoch 5101)
(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"a\\\" 5; Hashtbl.add t \\\"b\\\" 7; List.fold_left (+) 0 (Hashtbl.values t)\")")
(epoch 5102)
(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"x\\\" 1; Hashtbl.add t \\\"y\\\" 2; Hashtbl.remove t \\\"x\\\"; Hashtbl.length t\")")
(epoch 5103)
(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"a\\\" 1; Hashtbl.clear t; Hashtbl.length t\")")

;; ── Random module (deterministic LCG) ─────────────────────────
(epoch 5110)
(eval "(ocaml-run \"Random.init 42; Random.int 100\")")
(epoch 5111)
(eval "(ocaml-run \"Random.init 42; let a = Random.int 100 in let b = Random.int 100 in let c = Random.int 100 in a + b + c\")")
(epoch 5112)
(eval "(ocaml-run \"Random.init 1; Random.int 10\")")
(epoch 5113)
(eval "(ocaml-run \"Random.init 7; Random.bool ()\")")

;; ── fun (a, b) -> body — tuple param destructure ──────────────
(epoch 5120)
(eval "(ocaml-run \"(fun (a, b) -> a + b) (3, 7)\")")
(epoch 5121)
(eval "(ocaml-run \"List.map (fun (a, b) -> a * b) [(1, 2); (3, 4); (5, 6)]\")")
(epoch 5122)
(eval "(ocaml-run \"List.map (fun (k, _) -> k) [(\\\"a\\\", 1); (\\\"b\\\", 2)]\")")
(epoch 5123)
(eval "(ocaml-run \"(fun a (b, c) d -> a + b + c + d) 1 (2, 3) 4\")")

;; ── let f (a, b) = body — tuple param on inner-let ────────────
(epoch 5130)
(eval "(ocaml-run \"let f (a, b) = a + b in f (3, 7)\")")
(epoch 5131)
(eval "(ocaml-run \"let g x (a, b) = x + a + b in g 1 (2, 3)\")")
(epoch 5132)
(eval "(ocaml-run \"let h (a, b) (c, d) = a * b + c * d in h (1, 2) (3, 4)\")")

;; ── top-level let f (a, b) = body — tuple param decl ──────────
(epoch 5140)
(eval "(ocaml-run-program \"let f (a, b) = a + b ;; f (3, 7)\")")
(epoch 5141)
(eval "(ocaml-run-program \"let g x (a, b) = x + a + b ;; g 1 (2, 3)\")")
(epoch 5142)
(eval "(ocaml-run-program \"let h (a, b) (c, d) = a * b + c * d ;; h (1, 2) (3, 4)\")")

;; ── Labeled / optional args (label dropped) ──────────────────
(epoch 5150)
(eval "(ocaml-run \"let f ~x ~y = x + y in f ~x:3 ~y:7\")")
(epoch 5151)
(eval "(ocaml-run \"let f ~x ~y = x * y in let x = 4 in let y = 5 in f ~x ~y\")")
(epoch 5152)
(eval "(ocaml-run \"let f ?x ~y = x + y in f ?x:1 ~y:2\")")

;; ── Filename module ──────────────────────────────────────────
(epoch 5160)
(eval "(ocaml-run \"Filename.basename \\\"/foo/bar/baz.ml\\\"\")")
(epoch 5161)
(eval "(ocaml-run \"Filename.dirname \\\"/foo/bar/baz.ml\\\"\")")
(epoch 5162)
(eval "(ocaml-run \"Filename.extension \\\"baz.tar.gz\\\"\")")
(epoch 5163)
(eval "(ocaml-run \"Filename.concat \\\"a\\\" \\\"b\\\"\")")
(epoch 5164)
(eval "(ocaml-run \"Filename.chop_extension \\\"hello.ml\\\"\")")

;; ── Char.compare / equal / escaped ─────────────────────────
(epoch 5170)
(eval "(ocaml-run \"Char.compare \\\"b\\\" \\\"a\\\"\")")
(epoch 5171)
(eval "(ocaml-run \"Char.equal \\\"a\\\" \\\"a\\\"\")")

;; ── Set.Make / Map.Make functor application ──────────────────
(epoch 5180)
(eval "(ocaml-run-program \"module IntOrd = struct let compare a b = a - b end ;; module S = Set.Make (IntOrd) ;; let s = List.fold_left (fun s x -> S.add x s) S.empty [5;1;3;1;5] ;; List.fold_left (+) 0 (S.elements s)\")")
(epoch 5181)
(eval "(ocaml-run-program \"module IntOrd = struct let compare a b = a - b end ;; module S = Set.Make (IntOrd) ;; let s = S.add 1 (S.add 2 (S.add 3 S.empty)) ;; S.mem 2 s\")")
(epoch 5182)
(eval "(ocaml-run-program \"module IntOrd = struct let compare a b = a - b end ;; module M = Map.Make (IntOrd) ;; let m = M.add 1 \\\"a\\\" (M.add 2 \\\"b\\\" M.empty) ;; M.cardinal m\")")

;; ── Seq module (eager list-backed) ────────────────────────────
(epoch 5190)
(eval "(ocaml-run \"Seq.fold_left (+) 0 (Seq.map (fun x -> x * x) (Seq.of_list [1;2;3;4]))\")")
(epoch 5191)
(eval "(ocaml-run \"Seq.length (Seq.filter (fun x -> x mod 2 = 0) (Seq.of_list [1;2;3;4;5;6;7;8;9;10]))\")")
(epoch 5192)
(eval "(ocaml-run \"Seq.fold_left (+) 0 (Seq.init 5 (fun i -> i * 2))\")")
(epoch 5193)
(eval "(ocaml-run \"Seq.fold_left (+) 0 (Seq.unfold (fun n -> if n > 4 then None else Some (n, n + 1)) 1)\")")

;; ── String.equal / compare / cat ─────────────────────────────
(epoch 5200)
(eval "(ocaml-run \"String.equal \\\"abc\\\" \\\"abc\\\"\")")
(epoch 5201)
(eval "(ocaml-run \"String.compare \\\"banana\\\" \\\"apple\\\"\")")
(epoch 5202)
(eval "(ocaml-run \"String.cat \\\"hello \\\" \\\"world\\\"\")")

;; ── Bool module + Option.equal/compare ───────────────────────
(epoch 5210)
(eval "(ocaml-run \"Bool.to_string true ^ \\\"-\\\" ^ Bool.to_string false\")")
(epoch 5211)
(eval "(ocaml-run \"Bool.compare true false\")")
(epoch 5212)
(eval "(ocaml-run \"Option.equal (=) (Some 1) (Some 1)\")")
(epoch 5213)
(eval "(ocaml-run \"Option.equal (=) (Some 1) None\")")
(epoch 5214)
(eval "(ocaml-run \"Option.compare compare (Some 5) (Some 3)\")")

;; ── List.equal / List.compare ────────────────────────────────
(epoch 5220)
(eval "(ocaml-run \"List.equal (=) [1;2;3] [1;2;3]\")")
(epoch 5221)
(eval "(ocaml-run \"List.equal (=) [1;2;3] [1;2;4]\")")
(epoch 5222)
(eval "(ocaml-run \"List.compare compare [1;2;3] [1;2;4]\")")
(epoch 5223)
(eval "(ocaml-run \"List.compare compare [1;2] [1;2;3]\")")
(epoch 5224)
(eval "(ocaml-run \"List.compare compare [] []\")")

;; ── Result.equal / Result.compare ────────────────────────────
(epoch 5230)
(eval "(ocaml-run \"Result.equal (=) (=) (Ok 1) (Ok 1)\")")
(epoch 5231)
(eval "(ocaml-run \"Result.compare compare compare (Ok 5) (Ok 3)\")")
(epoch 5232)
(eval "(ocaml-run \"Result.compare compare compare (Ok 1) (Error \\\"fail\\\")\")")

;; ── Float module additions ───────────────────────────────────
(epoch 5240)
(eval "(ocaml-run \"Float.add 3.5 4.5\")")
(epoch 5241)
(eval "(ocaml-run \"Float.compare 2.5 5.0\")")
(epoch 5242)
(eval "(ocaml-run \"Float.abs (-3.7)\")")
(epoch 5243)
(eval "(ocaml-run \"Float.max 3.14 2.71\")")
(epoch 5244)
(eval "(ocaml-run \"Float.equal 1.5 1.5\")")
(epoch 5245)
(eval "(ocaml-run \"Float.zero +. Float.one\")")

;; ── Either module + Hashtbl.copy ─────────────────────────────
(epoch 5250)
(eval "(ocaml-run \"Either.is_left (Left 5)\")")
(epoch 5251)
(eval "(ocaml-run \"Either.fold (fun x -> x + 100) (fun x -> x * 10) (Left 7)\")")
(epoch 5252)
(eval "(ocaml-run \"Either.fold (fun x -> x + 100) (fun x -> x * 10) (Right 7)\")")
(epoch 5253)
(eval "(ocaml-run \"let t = Hashtbl.create 4 in Hashtbl.add t \\\"a\\\" 1; let t2 = Hashtbl.copy t in Hashtbl.add t2 \\\"b\\\" 2; Hashtbl.length t + Hashtbl.length t2\")")

;; ── Bitwise ops land/lor/lxor/lsl/lsr ────────────────────────
(epoch 5260)
(eval "(ocaml-run \"5 land 3\")")
(epoch 5261)
(eval "(ocaml-run \"5 lor 3\")")
(epoch 5262)
(eval "(ocaml-run \"5 lxor 3\")")
(epoch 5263)
(eval "(ocaml-run \"1 lsl 8\")")
(epoch 5264)
(eval "(ocaml-run \"256 lsr 4\")")

EPOCHS

OUTPUT=$(timeout 360 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)

check() {
  local epoch="$1" desc="$2" expected="$3"
  local actual
  actual=$(echo "$OUTPUT" | grep -A1 "^(ok-len $epoch " | tail -1)
  if [ -z "$actual" ]; then
    actual=$(echo "$OUTPUT" | grep "^(ok $epoch " || true)
  fi
  if [ -z "$actual" ]; then
    actual=$(echo "$OUTPUT" | grep "^(error $epoch " || true)
  fi
  if [ -z "$actual" ]; then
    actual="<no output for epoch $epoch>"
  fi

  if echo "$actual" | grep -qF -- "$expected"; then
    PASS=$((PASS + 1))
    [ "$VERBOSE" = "-v" ] && echo "  ok $desc"
  else
    FAIL=$((FAIL + 1))
    ERRORS+="  FAIL $desc (epoch $epoch)
    expected: $expected
    actual:   $actual
"
  fi
}

# empty / eof
check 100 "empty tokens length"          '1'
check 101 "empty first is eof"           '"eof"'

# numbers
check 110 "int type"                     '"number"'
check 111 "int value"                    '42'
check 112 "float value"                  '3.14'
check 113 "hex value"                    '255'
check 114 "exponent"                     '1000'
check 115 "underscored int"              '1000000'
check 116 "neg exponent"                 '0.0314'

# idents / ctors / keywords
check 120 "ident type"                   '"ident"'
check 121 "ident value"                  '"foo_bar1"'
check 122 "ctor type"                    '"ctor"'
check 123 "ctor value"                   '"Some"'
check 124 "let keyword type"             '"keyword"'
check 125 "match keyword value"          '"match"'
check 126 "true is keyword"              '"keyword"'
check 127 "false value"                  '"false"'
check 128 "primed ident"                 "\"name'\""

# strings
check 130 "string type"                  '"string"'
check 131 "string value"                 '"hi"'
check 132 "escape sequence"              '"a'

# chars
check 140 "char type"                    '"char"'
check 141 "char value"                   '"a"'
check 142 "char escape"                  '"'

# tyvars
check 145 "tyvar type"                   '"tyvar"'
check 146 "tyvar value"                  '"a"'

# multi-char ops
check 150 "->"                           '"->"'
check 151 "|>"                           '"|>"'
check 152 "<-"                           '"<-"'
check 153 ":="                           '":="'
check 154 "::"                           '"::"'
check 155 ";;"                           '";;"'
check 156 "@@"                           '"@@"'
check 157 "<>"                           '"<>"'
check 158 "&&"                           '"&&"'
check 159 "||"                           '"||"'

# single ops
check 160 "+"                            '"+"'
check 161 "|"                            '"|"'
check 162 ";"                            '";"'
check 163 "("                            '"("'
check 164 "!"                            '"!"'
check 165 "@"                            '"@"'

# comments
check 170 "block comment alone -> eof"   '1'
check 171 "num after block comment"      '42'
check 172 "nested comment count"         '2'
check 173 "nested comment value"         '1'

# compound
check 180 "let x = 1 count"              '5'
check 181 "let is keyword"               '"keyword"'
check 182 "let value"                    '"let"'
check 183 "x is ident"                   '"ident"'
check 184 "= value"                      '"="'
check 185 "1 value"                      '1'

check 190 "match expr count"             '13'
check 191 "fun -> arrow value"           '"->"'
check 192 "fun -> arrow type"            '"op"'
check 193 "Some is ctor"                 '"ctor"'
check 194 "first |> value"               '"|>"'
check 195 "ref assign :="                '":="'

# ── Parser tests ────────────────────────────────────────────────
check 200 "parse int"                    '("int" 42)'
check 201 "parse float"                  '("float" 3.14)'
check 202 "parse string"                 '("string" "hi")'
check 203 "parse char"                   '("char" "a")'
check 204 "parse true"                   '("bool" true)'
check 205 "parse false"                  '("bool" false)'
check 206 "parse var"                    '("var" "x")'
check 207 "parse ctor"                   '("con" "Some")'
check 208 "parse unit"                   '("unit")'

check 210 "parse f x"                    '("app" ("var" "f") ("var" "x"))'
check 211 "parse f x y left-assoc"       '("app" ("app" ("var" "f") ("var" "x")) ("var" "y"))'
check 212 "parse f (g x)"                '("app" ("var" "f") ("app" ("var" "g") ("var" "x")))'
check 213 "parse Some 42"                '("app" ("con" "Some") ("int" 42))'

check 220 "parse 1+2"                    '("op" "+" ("int" 1) ("int" 2))'
check 221 "parse a + b * c prec"         '("op" "+" ("var" "a") ("op" "*"'
check 222 "parse a*b + c prec"           '("op" "+" ("op" "*"'
check 223 "parse && / || prec"           '("op" "||" ("op" "&&"'
check 224 "parse a = b"                  '("op" "=" ("var" "a") ("var" "b"))'
check 225 "parse ^ right-assoc"          '("op" "^" ("var" "a") ("op" "^"'
check 226 "parse :: right-assoc"         '("op" "::" ("var" "a") ("op" "::"'
check 227 "parse parens override"        '("op" "*" ("op" "+"'
check 228 "parse |> chain"               '("op" "|>" ("op" "|>"'
check 229 "parse mod kw-binop"           '("op" "mod" ("var" "x") ("int" 2))'

check 230 "parse -x"                     '("neg" ("var" "x"))'
check 231 "parse -1+2"                   '("op" "+" ("neg" ("int" 1)) ("int" 2))'

check 240 "parse tuple"                  '("tuple" ("int" 1) ("int" 2) ("int" 3))'
check 241 "parse list literal"           '("list" ("int" 1) ("int" 2) ("int" 3))'
check 242 "parse []"                     '("list")'

check 250 "parse if/then/else"           '("if" ("var" "x") ("int" 1) ("int" 2))'
check 251 "parse if w/o else"            '("if" ("var" "c") ("var" "x") ("unit"))'
check 252 "parse fun x -> ..."           '("fun" ("x") ("op" "+" ("var" "x") ("int" 1)))'
check 253 "parse fun x y ->"             '("fun" ("x" "y")'
check 254 "parse let x = 1 in x"         '("let" "x" () ("int" 1) ("var" "x"))'
check 255 "parse let f x ="              '("let" "f" ("x") ("op" "+"'
check 256 "parse let rec f x ="          '("let-rec" "f" ("x")'
check 257 "parse let f x y ="            '("let" "f" ("x" "y")'

check 260 "parse begin/end"              '("op" "+" ("int" 1) ("int" 2))'

# ── Top-level decls ─────────────────────────────────────────────
check 270 "program: let x = 1"           '("program" ("def" "x" () ("int" 1)))'
check 271 "program: let x = 1 ;;"        '("program" ("def" "x" () ("int" 1)))'
check 272 "program: let f x = x+1"       '("program" ("def" "f" ("x") ("op" "+"'
check 273 "program: let rec fact"        '("def-rec" "fact" ("n")'
check 274 "program: two decls"           '("def" "x" () ("int" 1)) ("def" "y"'
check 275 "program: bare expr"           '("program" ("expr" ("op" "+" ("int" 1) ("int" 2))))'
check 276 "program: mixed decls + expr"  '("def" "y" () ("int" 2)) ("expr"'
check 277 "program: 4 forms incl head"   '4'
check 278 "program: empty"               '("program")'

# ── Match / patterns ────────────────────────────────────────────
check 300 "match Some/None"              '("match" ("var" "x") (("case" ("pcon" "None") ("int" 0)) ("case" ("pcon" "Some" ("pvar" "y")) ("var" "y")))'
check 301 "match no leading bar"         '("match" ("var" "x") (("case" ("pcon" "None") ("int" 0)) ("case" ("pcon" "Some"'
check 302 "match list cons"              '("case" ("plist") ("int" 0)) ("case" ("pcons" ("pvar" "h") ("pvar" "t")) ("int" 1))'
check 303 "match tuple pat"              '("ptuple" ("pvar" "a") ("pvar" "b"))'
check 304 "match int + wildcard"         '("case" ("plit" ("int" 0)) ("int" 1)) ("case" ("pwild")'
check 305 "match bool literals"          '("plit" ("bool" true))'
check 306 "match ctor with tuple arg"    '("pcon" "Pair" ("pvar" "a") ("pvar" "b"))'
check 307 "match string literal"         '("plit" ("string" "hi"))'
check 308 "match unit pattern"           '("plit" ("unit"))'

# ── Sequences ───────────────────────────────────────────────────
check 320 "seq 1;2"                      '("seq" ("int" 1) ("int" 2))'
check 321 "seq 1;2;3"                    '("seq" ("int" 1) ("int" 2) ("int" 3))'
check 322 "seq in parens"                '("seq" ("int" 1) ("int" 2))'
check 323 "seq in begin/end"             '("seq" ("var" "a") ("var" "b") ("var" "c"))'
check 324 "let body absorbs seq"         '("let" "x" () ("int" 1) ("seq" ("var" "x") ("var" "x")))'
check 325 "if-branch parens for seq"     '("if" ("var" "c") ("seq" ("var" "a") ("var" "b"))'
check 326 "list ; is separator"          '("list" ("int" 1) ("int" 2) ("int" 3))'
check 327 "trailing ; OK"                '("seq" ("int" 1) ("int" 2))'
check 328 "begin a; end singleton seq"   '("seq" ("var" "a"))'
check 329 "match clause body absorbs ;"  '("case" ("pwild") ("seq" ("var" "a") ("var" "b")))'

# ── Phase 2: evaluator ──────────────────────────────────────────
# atoms
check 400 "eval int"                     '42'
check 401 "eval float"                   '3.14'
check 402 "eval true"                    'true'
check 403 "eval false"                   'false'
check 404 "eval string"                  '"hi"'

# arithmetic
check 410 "eval 1+2"                     '3'
check 411 "eval 10-3"                    '7'
check 412 "eval 4*5"                     '20'
check 413 "eval 20/4"                    '5'
check 414 "eval 10 mod 3"                '1'
check 415 "eval 2 ** 10"                 '1024'
check 416 "eval (1+2)*3"                 '9'
check 417 "eval 1+2*3 prec"              '7'
check 418 "eval -5+10"                   '5'

# comparison & boolean
check 420 "eval 1<2"                     'true'
check 421 "eval 3>2"                     'true'
check 422 "eval 2=2"                     'true'
check 423 "eval 1<>2"                    'true'
check 424 "eval true && false"           'false'
check 425 "eval true || false"           'true'
check 426 "eval not false"               'true'

# string
check 430 'eval "a" ^ "b"'               '"ab"'
check 431 "eval string concat 3"         '"hello world"'

# conditional
check 440 "eval if true 1 else 2"        '1'
check 441 "eval if 1>2 100 else 200"     '200'

# let / lambda / app
check 450 "eval let x=5 x*2"             '10'
check 451 "eval let f x = x+1; f 41"     '42'
check 452 "eval let f x y = x+y; f 3 4"  '7'
check 453 "eval (fun x -> x*x) 7"        '49'
check 454 "eval curried lambdas"         '30'
check 455 "eval named lambda"            '10'

# closures
check 460 "eval closure capture"         '15'
check 461 "eval make_adder"              '101'

# recursion
check 470 "eval fact 5"                  '120'
check 471 "eval fib 10"                  '55'
check 472 "eval sum 100"                 '5050'

# sequence
check 480 "eval 1; 2; 3 → 3"             '3'
check 481 "eval begin 10 end"            '10'

# programs
check 490 "run-prog x+y"                 '3'
check 491 "run-prog fact 6"              '720'
check 492 "run-prog inc + double"        '10'

# pipe
check 495 "eval x |> f"                  '10'

# ── Phase 3: ADTs + match (eval) ────────────────────────────────
# constructors
check 500 "eval None"                    '("None")'
check 501 "eval Some 42"                 '("Some" 42)'
check 502 "eval Pair tuple-arg"          '("Some" 1 2)'

# option match
check 510 "match Some 5 -> 5"            '5'
check 511 "match None -> 0"              '0'

# literal match
check 520 "match 3 -> _ -> 999"          '999'
check 521 "match bool true"              '1'
check 522 "match string lit"             '1'

# tuple match
check 530 "match (1,2)"                  '3'
check 531 "match (1,2,3)"                '6'

# list match
check 540 "match list cons head"         '1'
check 541 "match empty list"             '0'
check 542 "match list literal pat"       '6'
check 543 "match recursive len"          '5'
check 544 "match recursive sum"          '15'

# wildcard + var
check 550 "match _ -> 1"                 '1'
check 551 "match x -> x+1"               '100'

# ctor with tuple arg
check 560 "Pair(a,b) → a*b"              '2'

# ── References ──────────────────────────────────────────────────
check 600 "deref new ref"                '5'
check 601 ":= then deref"                '10'
check 602 "increment cell twice"         '2'
check 603 "ref captured by closure"      '115'
check 604 "ref of string"                '"b"'
check 605 "ref + recursion"              '15'

# ── for / while ─────────────────────────────────────────────────
check 620 "for 1..5 sum"                 '15'
check 621 "for 5 downto 1 sum"           '15'
check 622 "while loop"                   '15'
check 623 "for 1..100 sum"               '5050'
check 624 "for 1..5 product = 120"       '120'

# ── function ────────────────────────────────────────────────────
check 640 "function None|Some Some 7"    '7'
check 641 "function None=0"              '0'
check 642 "rec function len"             '3'
check 643 "rec function map x*x"         '(1 4 9 16)'

# ── try / with / raise ──────────────────────────────────────────
check 660 "try success"                  '3'
check 661 "try Foo caught"               '5'
check 662 "try Bar caught"               '99'
check 663 "try failwith"                 '"oops"'
check 664 "try sequence raises"          '101'
check 665 "raise from function"          '-5'

# ── Phase 4: Modules + field access ─────────────────────────────
check 700 "parse M.x"                    '("field" ("con" "M") "x")'
check 701 "parse r.field"                '("field" ("var" "r") "field")'
check 702 "parse M.M2.x left-assoc"      '("field" ("field" ("con" "M") "M2") "x")'
check 703 "parse f r.x bind tighter"     '("app" ("var" "f") ("field" ("var" "r") "x"))'
check 710 "module M.x = 42"              '42'
check 711 "module M.f 41 = 42"           '42'
check 712 "module two values"            '3'
check 713 "module fn: square 5"          '25'
check 714 "nested module Outer.Inner"    '99'
check 715 "module rec fact 5"            '120'
check 716 "module Pair.swap"             '("tuple" 2 1)'

# ── open / include ──────────────────────────────────────────────
check 730 "open M; f x"                  '43'
check 731 "include Math; area"           '48'
check 732 "module open inside"           '11'
check 733 "Sphere.pi via include"        '3'
check 734 "include M; N.z = x+y"         '3'

# ── Functors ────────────────────────────────────────────────────
check 750 "functor app Add(Five).add 10" '15'
check 751 "module alias N = M"           '1'
check 752 "submodule alias"              '42'
check 753 "multi-param functor"          '("tuple" 1 2)'
check 754 "Identity functor + include"   '99'

# ── Phase 6: stdlib slice ───────────────────────────────────────
# List
check 800 "List.length [1..4]"           '4'
check 801 "List.length []"               '0'
check 802 "List.map x*2 [1;2;3]"         '(2 4 6)'
check 803 "List.filter > 2"              '(3 4 5)'
check 804 "List.fold_left + 0 [1..5]"    '15'
check 805 "List.fold_right ::"           '(1 2 3)'
check 806 "List.rev"                     '(3 2 1)'
check 807 "List.append"                  '(1 2 3 4)'
check 808 "List.mem 3"                   'true'
check 809 "List.mem 99"                  'false'
check 810 "List.for_all >0"              'true'
check 811 "List.exists >2"               'true'
check 812 "List.hd"                      '10'
check 813 "List.nth idx 1"               '20'

# Option
check 820 "Option.map Some"              '("Some" 42)'
check 821 "Option.map None"              '("None")'
check 822 "Option.value Some"            '7'
check 823 "Option.value None"            '42'
check 824 "Option.is_some"               'true'
check 825 "Option.is_none"               'true'

# Result
check 830 "Result.map Ok"                '("Ok" 6)'
check 831 "Result.is_ok"                 'true'
check 832 "Result.is_error"              'true'

# ── let ... and ... mutual recursion ─────────────────────────────
check 850 "even 10 (mutual rec)"         'true'
check 851 "odd 7 (mutual rec)"           'true'
check 852 "let x = 1 and y = 2"          '3'

# ── Phase 5: Hindley-Milner type inference ────────────────────
check 900 "type 42 = Int"                '"Int"'
check 901 "type true = Bool"             '"Bool"'
check 902 'type string lit'              '"String"'
check 903 "type 1+2 = Int"               '"Int"'
check 904 "type fun x->x+1 = Int->Int"   '"Int -> Int"'
check 905 "type fun x->x = poly"         ' -> '
check 906 "type fun x y->x+y"            '"Int -> Int -> Int"'
check 907 "type let f x=x+1 in f 10"     '"Int"'
check 908 "type let id; id 5"            '"Int"'
check 909 "type let id; id true"         '"Bool"'
check 910 "type if/then/else"            '"Int"'
check 911 "type twice"                   ' -> '
check 912 "type bool branch"             '"Bool -> Int"'
check 913 "type not true"                '"Bool"'

# ── Phase 6 String / Char / Int ─────────────────────────────────
check 950 "String.length"                '5'
check 951 "String.uppercase_ascii"       '"HI"'
check 952 "String.lowercase_ascii"       '"hi"'
check 953 "String.sub"                   '"ell"'
check 954 "String.starts_with"           'true'
check 955 "String.concat"                '"a,b,c"'

check 960 "Char.code A"                  '65'
check 961 "Char.chr 65"                  '"A"'

check 970 "Int.to_string"                '"42"'
check 971 "Int.of_string"                '123'
check 972 "Int.abs -5"                   '5'
check 973 "Int.max"                      '7'
check 974 "Int.min"                      '3'

# ── Unit / wildcard parameters ──────────────────────────────────
check 1000 "let f () = 42 in f ()"        '42'
check 1001 "(fun () -> 99) ()"            '99'
check 1002 "let f _ = 1 in f 5"           '1'
check 1003 "top-level let f () ="         '7'
check 1004 "wildcard top-level"           '42'

# ── Records ─────────────────────────────────────────────────────
check 1100 "record literal + access"      '1'
check 1101 "record sum fields"            '3'
check 1102 "record with-update"           '101'
check 1103 "record string field"          '"Bob"'
check 1104 "record int field"             '30'
check 1105 "top-level record decl"        '3'

# ── as / when in match ──────────────────────────────────────────
check 1200 "Some x as p"                  '5'
check 1201 "when sign +"                  '1'
check 1202 "when sign -"                  '-1'
check 1203 "when sign 0"                  '0'
check 1204 "when guard fires"             '70'
check 1205 "when guard skips"             '3'

# ── type declarations ───────────────────────────────────────────
check 1300 "type color enum"              '("type-def" "color" () (("Red") ("Green") ("Blue")))'
check 1301 "type shape with-args"         '("type-def" "shape"'
check 1302 "type-decl + match Red"        '1'
check 1303 "type-decl + match Blue"       '3'
check 1304 "type-decl + Circle r"         '5'

# ── exception declarations ─────────────────────────────────────
check 1320 "exception nullary"            '("exception-def" "MyExn")'
check 1321 "exception arg"                '("exception-def" "MyExn"'
check 1322 "raise+catch with arg"         '5'
check 1323 "raise+catch string arg"       '"oops"'

# ── Phase 6 expanded stdlib ─────────────────────────────────────
check 1400 "List.concat"                  '(1 2 3 4 5)'
check 1401 "List.init"                    '(0 10 20 30 40)'
check 1402 "List.find_opt found"          '("Some" 3)'
check 1403 "List.find_opt missing"        '("None")'
check 1404 "List.mapi"                    '(10 21 32)'
check 1405 "List.partition"               '("tuple" (3 4) (1 2))'
check 1406 "List.assoc"                   '"b"'
check 1407 "List.assoc_opt missing"       '("None")'

check 1410 "Option.fold Some"             '70'
check 1411 "Option.fold None"             '0'
check 1412 "Option.to_list Some"          '(7)'
check 1413 "Option.to_list None"          '()'

check 1420 "Result.get_ok"                '42'
check 1421 "Result.to_option Ok"          '("Some" 1)'
check 1422 "Result.map_error"             '("Error" 6)'

# ── HM tuples + lists ───────────────────────────────────────────
check 1500 "type tuple Int*Int"           '"Int * Int"'
check 1501 "type 3-tuple"                 '"Int * Bool * String"'
check 1502 "type Int list"                '"Int list"'
check 1503 "type [] poly"                 ' list'
check 1504 "type fn -> list"              'list"'
check 1505 "type fn -> tuple"             ' * '
check 1506 "type Bool list"               '"Bool list"'

# ── Hashtbl ─────────────────────────────────────────────────────
check 1600 "Hashtbl find"                 '42'
check 1601 "Hashtbl length"               '2'
check 1602 "Hashtbl find_opt missing"     '("None")'
check 1603 "Hashtbl mem"                  'true'
check 1604 "Hashtbl replace"              '2'
check 1605 "Hashtbl find_opt found"       '("Some" 5)'

# ── List.sort + compare ─────────────────────────────────────────
check 1700 "compare 1<2"                  '-1'
check 1701 "compare 5=5"                  '0'
check 1702 "compare 9>1"                  '1'
check 1703 "List.sort ascending"          '(1 1 2 3 4 5 6 9)'
check 1704 "List.sort descending"         '(4 3 1)'
check 1705 "List.sort empty"              '()'
check 1706 "List.sort strings"            '("a" "b" "c")'

# ── HM match inference ──────────────────────────────────────────
check 1800 "match int"                    '"Int"'
check 1801 "match list"                   '"Int"'
check 1802 "match tuple"                  '"Int"'
check 1803 "fn match int -> int"          '"Int -> Int"'
check 1804 "fn list -> elem"              '"Int list -> Int"'

# ── HM ctor inference ──────────────────────────────────────────
check 1900 "Some 5 : Int option"          '"Int option"'
check 1901 "None : 'a option"             ' option'
check 1902 "Ok 1 : (Int, 'b) result"      '"(Int'
check 1903 "Error 'oops'"                 'String) result'
check 1904 "fun x -> Some x"              ' option'
check 1905 "match Some/None -> Int"       '"Int"'
check 1906 "Int option -> Int"            '"Int option -> Int"'

# ── HM let-rec + :: / @ ─────────────────────────────────────────
check 2000 "type fact"                    '"Int -> Int"'
check 2001 "type len"                     'list -> Int'
check 2002 "type map"                     'list -> '
check 2003 "type 1::list"                 '"Int list"'
check 2004 "type [1] @ [2;3]"             '"Int list"'
check 2005 "type sum"                     '"Int"'

# ── HM with user type-defs ──────────────────────────────────────
check 2100 "user type Red : color"        '"color"'
check 2101 "user type Circle 5 : shape"   '"shape"'
check 2102 "let c = Red; c"               '"color"'
check 2103 "shape -> Int"                 '"shape -> Int"'
check 2104 "program x+y"                  '"Int"'
check 2105 "program fact 5"               '"Int"'

# ── More List functions ─────────────────────────────────────────
check 2200 "List.combine"                 '("tuple" 3 "c")'
check 2201 "List.split"                   '("tuple" (1 2) ("a" "b"))'
check 2202 "List.fold_left2"              '66'
check 2203 "List.map2"                    '(11 22 33)'

# ── Float arithmetic ────────────────────────────────────────────
check 2300 "1.5 +. 2.5"                   '4'
check 2301 "3.0 *. 2.0"                   '6'
check 2302 "10.0 /. 4.0"                  '2.5'
check 2303 "type 1.5 +. 2.5"              '"Float"'
check 2304 "type fun x y -> x +. y"       '"Float -> Float -> Float"'

# ── Float module ────────────────────────────────────────────────
check 2400 "Float.sqrt 16"                '4'
check 2401 "Float.sin 0"                  '0'
check 2402 "Float.cos 0"                  '1'
check 2403 "Float.pow 2 10"               '1024'
check 2404 "Float.floor 3.7"              '3'
check 2405 "Float.ceil 3.2"               '4'

# ── Polymorphic variants ───────────────────────────────────────
check 2500 "polyvar Red"                  '("Red")'
check 2501 "polyvar Some 42"              '("Some" 42)'
check 2502 "polyvar match"                '1'
check 2503 "polyvar Pair (a,b)"           '3'

# ── Record patterns ────────────────────────────────────────────
check 2600 "match record bind both"       '3'
check 2601 "match record name+age"        '30'
check 2602 "match record literal x=1"     '2'
check 2603 "match record literal fail"    '0'

# ── module type S = sig … end ──────────────────────────────────
check 2700 "module type S parses"         '("module-type-def" "S")'
check 2701 "module type then module"      '42'
check 2702 "module type EMPTY"            '("module-type-def" "EMPTY")'

# ── or-patterns (parens-only) ──────────────────────────────────
check 2800 "(1|2|3) match 1"              '100'
check 2801 "(1|2|3) match 2"              '100'
check 2802 "(1|2|3) match 5"              '0'
check 2803 "(Red|Green) Red"              '1'
check 2804 "(Red|Green) Blue"             '2'

# ── List.take/drop/filter_map/flat_map ─────────────────────────
check 2900 "List.take 3"                  '(1 2 3)'
check 2901 "List.drop 2"                  '(3 4 5)'
check 2902 "List.filter_map"              '(30 40)'
check 2903 "List.flat_map double"         '(1 1 2 2 3 3)'
check 2904 "List.take 0"                  '()'
check 2905 "List.take overflow"           '(1 2 3)'

# ── String extensions ──────────────────────────────────────────
check 3000 "String.ends_with"             'true'
check 3001 "String.contains"              'true'
check 3002 "String.trim"                  '"hi"'
check 3003 "String.split_on_char"         '("a" "b" "c")'
check 3004 "String.replace_all"           '"herro"'
check 3005 "String.index_of"              '2'

# ── HM with parsed ctor arg types ──────────────────────────────
check 3100 "shape -> Int"                 '"shape -> Int"'
check 3101 "TStr 'hi' : tag"              '"tag"'
check 3102 "A true : t"                   '"t"'

# ── Sys stubs ──────────────────────────────────────────────────
check 3200 "Sys.os_type"                  '"SX"'
check 3201 "Sys.word_size"                '64'
check 3202 "Sys.unix"                     'true'
check 3203 "Sys.win32"                    'false'
check 3204 "Sys.executable_name"          '"ocaml-on-sx"'

# ── Map.Make / Set.Make ────────────────────────────────────────
check 3300 "Map.find via functor"         '"a"'
check 3301 "Map.cardinal"                 '2'
check 3302 "Set.elements sorted"          '(1 2 3)'
check 3303 "Set.mem"                      'true'

# ── Map/Set fold/iter/filter/union/inter ───────────────────────
check 3400 "Map.fold sum"                 '30'
check 3401 "Map.is_empty false"           'false'
check 3402 "Set.union"                    '(1 2 3)'
check 3403 "Set.inter"                    '(2 3)'

# ── Buffer module ──────────────────────────────────────────────
check 3500 "Buffer concat 'Hello, World'" '"Hello, World"'
check 3501 "Buffer.length 3"              '3'
check 3502 "Buffer.clear empties"         '""'

# ── Stack + Queue ──────────────────────────────────────────────
check 3600 "Stack.pop LIFO"               '3'
check 3601 "Stack.length"                 '2'
check 3602 "Stack.top"                    '1'
check 3603 "Queue.pop FIFO"               '1'
check 3604 "Queue.length"                 '2'

# ── Option/Result/Bytes extensions ─────────────────────────────
check 3700 "Option.join nested"           '("Some" 5)'
check 3701 "Option.join None"             '("None")'
check 3702 "Option.to_result None"        '("Error" "missing")'
check 3703 "Option.to_result Some"        '("Ok" 7)'
check 3704 "Result.value Ok"              '5'
check 3705 "Result.value Error fallback"  '99'
check 3706 "Result.fold Ok"               '50'
check 3707 "Bytes.length"                 '5'
check 3708 "Bytes.concat"                 '"a-b-c"'

# ── HM let-mut / let-rec-mut ───────────────────────────────────
check 3800 "let-mut x+y : Int"            '"Int"'
check 3801 "let-rec-mut even"             '"Int -> Bool"'
check 3802 "let-mut f and g"              '"Int"'

# ── let _ = expr top-level ─────────────────────────────────────
check 3900 "let _ = 1+2;; 42"             '42'
check 3901 "two top-level lets, _"        '20'

# ── Record type declarations ──────────────────────────────────
check 4000 "record type decl"             '("type-def-record" "point" () (("x") ("y")))'
check 4001 "mutable field decl"           '("mutable" "x")'
check 4002 "record decl + use"            '7'

# ── Mutable record fields ──────────────────────────────────────
check 4100 "r.x <- 5; r.x"                '5'
check 4101 "for-loop accum r.x"           '15'
check 4102 "r.name <- str"                '"Alice"'
check 4103 "r.x <- r.y * 10"              '20'

# ── HM top-level def-mut / def-rec-mut ─────────────────────────
check 4200 "let-mut x+y"                  '"Int"'
check 4201 "let-rec-mut even 10"          '"Bool"'
check 4202 "let-rec-mut map+length"       'list -> '

# ── Char predicates ────────────────────────────────────────────
check 4300 "Char.is_digit 5"              'true'
check 4301 "Char.is_digit x"              'false'
check 4302 "Char.is_alpha x"              'true'
check 4303 "Char.is_alnum 5"              'true'
check 4304 "Char.is_whitespace ' '"       'true'
check 4305 "Char.is_upper A"              'true'
check 4306 "Char.is_lower a"              'true'

# ── function with `when` guard ─────────────────────────────────
check 4400 "function when 5"              '1'
check 4401 "function when -3"             '0'
check 4402 "function sign 0"              '0'

# ── try/with `when` guard ──────────────────────────────────────
check 4500 "try when guard fires"         '5'
check 4501 "try when guard skips"         '0'
check 4502 "try when fall through"        '1005'

# ── type aliases ───────────────────────────────────────────────
check 4600 "type t = int parses"          '("type-alias" "t" ())'
check 4601 "type alias decl + use"        '42'

# ── Type annotations ───────────────────────────────────────────
check 4700 "let x : int = 5"              '6'
check 4701 "let f (x : int) : int"        '42'
check 4702 "(5 : int)"                    '5'
check 4703 "((1+2) : int) * 3"            '9'

# ── Module body: def-mut / def-rec-mut ─────────────────────────
check 4800 "module rec a/b mutual"        '1'
check 4801 "module x and y"               '3'

# ── let open M in body ─────────────────────────────────────────
check 4900 "let open List; length"        '3'
check 4901 "let open List; map"           '(2 4 6)'
check 4902 "let open Option; map"         '("Some" 6)'

# ── M.(expr) local-open expression form ──────────────────────────
check 4910 "M.(expr) length"              '3'
check 4911 "M.(expr) map"                 '(2 3 4)'
check 4912 "M.(expr) Option map"          '("Some" 40)'

# ── s.[i] string indexing ────────────────────────────────────────
check 4920 "s.[0] hello"                  '"h"'
check 4921 "Char.code s.[2] abc"          '99'
check 4922 "for i s.[i] sum hi"           '209'

# ── assert ───────────────────────────────────────────────────────
check 4930 "assert true; 42"              '42'
check 4931 "assert (x = 5); x + 1"        '6'
check 4932 "try (assert false; ...) with" '99'

# ── Printf.sprintf ───────────────────────────────────────────────
check 4940 "sprintf no args"              '"hello"'
check 4941 "sprintf one %d"               '"x=42"'
check 4942 "sprintf %s = %d"              '"answer = 42"'
check 4943 "sprintf %d%% literal percent" '"50%"'
check 4944 "string_of_int + string_of_b"  '"7-true"'

# ── Hashtbl.iter / Hashtbl.fold ─────────────────────────────────
check 4950 "Hashtbl.fold sum 1+2+3"       '6'
check 4951 "Hashtbl.iter ref accum 10+20" '30'

# ── lazy / Lazy.force ─────────────────────────────────────────
check 4960 "lazy 1+2 force"               '3'
check 4961 "lazy memoization counter=1"   '8401'

# ── Format alias ─────────────────────────────────────────────────
check 4970 "Format.sprintf %d"            '"99"'
check 4971 "Format.asprintf %s=%d"        '"n=7"'

# ── String.iter / fold_left / seq ───────────────────────────────
check 4980 "String.iter sum codes abc"    '294'
check 4981 "String.fold_left sum hi"      '209'
check 4982 "String.of_seq (rev to_seq)"   '"olleh"'

# ── List.sort_uniq / List.find_map ──────────────────────────────
check 4990 "sort_uniq dedupes & sorts"    '(1 2 3 4)'
check 4991 "find_map first >5 doubled"    '("Some" 12)'

# ── Polymorphic variants ─────────────────────────────────────────
check 5000 'match polyvar Foo / Bar'      '1'
check 5001 'match polyvar Pair (a, b)'    '12'
check 5002 'List.map polyvar On / Off'    '(1 0 1)'

# ── Array module ────────────────────────────────────────────────
check 5010 "Array.make + set 2 99"        '127'
check 5011 "Array.init 4 i*i"             '14'
check 5012 "Array.make 3 + for + length"  '9'

# ── (op) operator sections ──────────────────────────────────────
check 5020 "fold_left (+) sum"            '15'
check 5021 "let f = (*) in f 6 7"         '42'
check 5022 "List.map ((-) 10) [1;2;3]"    '(9 8 7)'

# ── arr.(i) and arr.(i) <- v ────────────────────────────────────
check 5030 "a.(2) <- 99; a.(2) + a.(0)"   '106'
check 5031 "Array.init 4 + sum a.(0..3)"  '10'
check 5032 "for + a.(i) <- i*i + sum"     '25'

# ── Array.sort / sub / append / exists / mem ────────────────────
check 5040 "Array.sort compare"           '(1 1 2 3 4 5 6 9)'
check 5041 "Array.sub 1 3 sum"            '90'
check 5042 "Array.append 6-len sum"       '21'
check 5043 "Array.exists = 6"             'true'
check 5044 "Array.mem 3 [1..5]"           'true'

# ── Int module + integer division ───────────────────────────────
check 5050 "17 / 5 = 3 (int div)"         '3'
check 5051 "-17 / 5 = -3 (trunc-zero)"    '-3'
check 5052 "Int.rem 17 5"                 '2'
check 5053 "Int.compare 5 3"              '1'
check 5054 "max_int + min_int (host int)" '0'

# ── List.sort mergesort ─────────────────────────────────────────
check 5060 "sort 9-element list"          '(1 2 3 4 5 6 7 8 9)'
check 5061 "sort with reverse cmp"        '(5 4 3 1 1)'
check 5062 "sort 10 reversed -> length"   '10'

# ── Printf %i %x %X %o ──────────────────────────────────────────
check 5070 "%i 42"                        '"42"'
check 5071 "%x 255"                       '"ff"'
check 5072 "%X 4096"                      '"1000"'
check 5073 "%o 8"                         '"10"'
check 5074 "%x %X %o multi"               '"ff 1000 10"'

# ── Printf width specifiers ─────────────────────────────────────
check 5080 "%5d 42 right-pad"             '"   42"'
check 5081 "%-5d| 42 left-pad"            '"42   |"'
check 5082 "%05d 42 zero-pad"             '"00042"'
check 5083 "%4s hi"                       '"  hi"'
check 5084 "%-3d %04x mixed"              '"hi=9  , hex=000f"'

# ── let (a, b) = expr in body ───────────────────────────────────
check 5090 "let (a, b) = (1,2)"           '3'
check 5091 "let (a, b, c) = (10,20,30)"   '60'
check 5092 "let pair; let (x, y) = pair"  '35'

# ── Hashtbl.keys/values/remove/clear ────────────────────────────
check 5100 "Hashtbl.keys length"          '2'
check 5101 "Hashtbl.values sum"           '12'
check 5102 "Hashtbl.remove + length"      '1'
check 5103 "Hashtbl.clear + length"       '0'

# ── Random (deterministic LCG) ──────────────────────────────────
check 5110 "Random.int 100 seed=42"       '48'
check 5111 "Random.int x3 seed=42 sum"    '152'
check 5112 "Random.int 10 seed=1"         '0'
check 5113 "Random.bool seed=7"           'true'

# ── fun (a, b) -> body tuple param ──────────────────────────────
check 5120 "fun (a, b) -> a + b"          '10'
check 5121 "List.map fun (a, b)"          '(2 12 30)'
check 5122 "List.map fun (k, _)"          '("a" "b")'
check 5123 "fun a (b, c) d mixed"         '10'

# ── let f (a, b) = body — let with tuple param ──────────────────
check 5130 "let f (a, b) = a + b"         '10'
check 5131 "let g x (a, b) mixed"         '6'
check 5132 "let h (a, b) (c, d) curried"  '14'

# ── top-level let f (a, b) = body — tuple param decl ────────────
check 5140 "top let f (a, b) = a+b"       '10'
check 5141 "top let g x (a, b) mixed"     '6'
check 5142 "top let h (a, b) (c, d)"      '14'

# ── Labeled / optional args (label dropped, positional) ─────────
check 5150 "f ~x:3 ~y:7 sum"              '10'
check 5151 "f ~x ~y punning"              '20'
check 5152 "f ?x:1 ~y:2 (no Some wrap)"   '3'

# ── Filename module ─────────────────────────────────────────────
check 5160 "basename /foo/bar/baz.ml"     '"baz.ml"'
check 5161 "dirname /foo/bar/baz.ml"      '"/foo/bar"'
check 5162 "extension baz.tar.gz"         '".gz"'
check 5163 "concat a b"                   '"a/b"'
check 5164 "chop_extension hello.ml"      '"hello"'

# ── Char.compare / equal ────────────────────────────────────────
check 5170 "Char.compare b a"             '1'
check 5171 "Char.equal a a"               'true'

# ── Set.Make / Map.Make functor application ─────────────────────
check 5180 "Set.Make dedupe sum"          '9'
check 5181 "Set.Make mem"                 'true'
check 5182 "Map.Make cardinal"            '2'

# ── Seq module (eager list-backed) ──────────────────────────────
check 5190 "Seq fold map squares"         '30'
check 5191 "Seq filter evens"             '5'
check 5192 "Seq init 5 i*2"               '20'
check 5193 "Seq unfold 1..4 sum"          '10'

# ── String.equal / compare / cat ────────────────────────────────
check 5200 "String.equal abc abc"         'true'
check 5201 "String.compare banana apple"  '1'
check 5202 "String.cat hello world"       '"hello world"'

# ── Bool module + Option.equal/compare ─────────────────────────
check 5210 "Bool.to_string true/false"    '"true-false"'
check 5211 "Bool.compare true false"      '1'
check 5212 "Option.equal Some 1 Some 1"   'true'
check 5213 "Option.equal Some 1 None"     'false'
check 5214 "Option.compare Some 5 Some 3" '1'

# ── List.equal / List.compare ───────────────────────────────────
check 5220 "List.equal [1;2;3] [1;2;3]"   'true'
check 5221 "List.equal [1;2;3] [1;2;4]"   'false'
check 5222 "List.compare [1;2;3] [1;2;4]" '-1'
check 5223 "List.compare [1;2] [1;2;3]"   '-1'
check 5224 "List.compare [] []"           '0'

# ── Result.equal / Result.compare ───────────────────────────────
check 5230 "Result.equal Ok 1 Ok 1"       'true'
check 5231 "Result.compare Ok 5 Ok 3"     '1'
check 5232 "Result.compare Ok < Error"    '-1'

# ── Float module additions ──────────────────────────────────────
check 5240 "Float.add 3.5 4.5"            '8'
check 5241 "Float.compare 2.5 5.0"        '-1'
check 5242 "Float.abs -3.7"               '3.7'
check 5243 "Float.max 3.14 2.71"          '3.14'
check 5244 "Float.equal 1.5 1.5"          'true'
check 5245 "Float.zero +. Float.one"      '1'

# ── Either module + Hashtbl.copy ────────────────────────────────
check 5250 "Either.is_left Left"          'true'
check 5251 "Either.fold Left 7+100"       '107'
check 5252 "Either.fold Right 7*10"       '70'
check 5253 "Hashtbl.copy independent"     '3'

# ── Bitwise ops land/lor/lxor/lsl/lsr ───────────────────────────
check 5260 "5 land 3 = 1"                 '1'
check 5261 "5 lor 3 = 7"                  '7'
check 5262 "5 lxor 3 = 6"                 '6'
check 5263 "1 lsl 8 = 256"                '256'
check 5264 "256 lsr 4 = 16"               '16'

TOTAL=$((PASS + FAIL))
if [ $FAIL -eq 0 ]; then
  echo "ok $PASS/$TOTAL OCaml-on-SX tests passed"
else
  echo "FAIL $PASS/$TOTAL passed, $FAIL failed:"
  echo ""
  echo "$ERRORS"
fi

[ $FAIL -eq 0 ]
