# OCaml-on-SX: OCaml + ReasonML + Dream on the CEK/VM

The meta-circular demo: SX's native evaluator is OCaml, so implementing OCaml on top of
SX closes the loop — the source language of the host is running inside the host it
compiles to. Beyond the elegance, it's practically useful: once OCaml expressions run on
the SX CEK/VM you get Dream (a clean OCaml web framework) almost for free, and ReasonML
is a syntax variant that shares the same transpiler output.

End-state goal: **OCaml programs running on the SX CEK/VM**, with enough of the standard
library to support Dream's middleware model. Dream-on-SX is the integration target —
a `handler`/`middleware`/`router` API that feels idiomatic while running purely in SX.
ReasonML (Phase 8) adds an alternative syntax frontend that targets the same transpiler.

## What this covers that nothing else in the set does

- **Strict ML semantics** — unlike Haskell, OCaml is call-by-value with explicit `Lazy.t`
  for laziness. Pattern match is exhaustive. Polymorphic variants. Structural equality.
- **First-class modules and functors** — modules as values (phase 4); functors as SX
  higher-order functions over module records. Unlike Haskell typeclasses, OCaml's module
  system is explicit and compositional.
- **Mutable state without monads** — `ref`, `:=`, `!` are primitives. Arrays. `Hashtbl`.
  The IO model is direct; `Lwt`/Dream map to `perform`/`cek-resume` for async.
- **Dream's composable HTTP model** — `handler = request -> response promise`,
  `middleware = handler -> handler`. Algebraically clean; `@@` composition maps to SX
  function composition trivially.
- **ReasonML** — same semantics, JS-friendly surface syntax. JSX variant pairs with SX
  component rendering.

## Ground rules

- **Scope:** only touch `lib/ocaml/**`, `lib/dream/**`, `lib/reasonml/**`, and
  `plans/ocaml-on-sx.md`. Do **not** edit `spec/`, `hosts/`, `shared/`, or other
  `lib/<lang>/`.
- **Shared-file issues** go under "Blockers" below with a minimal repro; do not fix here.
- **SX files:** use `sx-tree` MCP tools only.
- **Architecture:** OCaml source → AST → SX AST → CEK. No standalone OCaml evaluator.
  The OCaml AST is walked by an `ocaml-eval` function in SX that produces SX values.
- **Type system:** deferred until Phase 5. Phases 1–4 are intentionally untyped —
  get the evaluator right first, then layer HM inference on top.
- **Dream:** implemented as a library in Phase 7; no separate build step. `Dream.run`
  wraps SX's existing HTTP server machinery via `perform`/`cek-resume`.
- **Commits:** one feature per commit. Keep `## Progress log` updated and tick boxes.

## Architecture sketch

```
OCaml source text
    │
    ▼
lib/ocaml/tokenizer.sx   — keywords, operators, string/char literals, comments
    │
    ▼
lib/ocaml/parser.sx      — OCaml AST: let/let rec, fun, match, if, begin/end,
    │                      module/struct/functor, type decls, expressions
    ▼
lib/ocaml/desugar.sx     — surface → core: tuple patterns, or-patterns,
    │                      sequence (;) → (do), when guards, field punning
    ▼
lib/ocaml/transpile.sx   — OCaml AST → SX AST
    │
    ▼
lib/ocaml/runtime.sx     — ADT constructors, module primitives, ref/array ops,
    │                      Stdlib shims, Dream server (phase 7)
    ▼
SX CEK evaluator (both JS and OCaml hosts)
```

## Semantic mappings

| OCaml construct | SX mapping |
|----------------|-----------|
| `let x = e` (top-level) | `(define x e)` |
| `let f x y = e` | `(define (f x y) e)` |
| `let rec f x = e` | `(define (f x) e)` — SX define is already recursive |
| `fun x -> e` | `(fn (x) e)` |
| `e1 \|> f` | `(f e1)` — pipe desugars to reverse application |
| `e1; e2` | `(do e1 e2)` |
| `begin e1; e2; e3 end` | `(do e1 e2 e3)` |
| `if c then e1 else e2` | `(if c e1 e2)` |
| `match x with \| P -> e` | `(match x (P e) ...)` via Phase 6 ADT primitive |
| `type t = A \| B of int` | `(define-type t (A) (B v))` |
| `module M = struct ... end` | SX dict `{:let-bindings ...}` — module as record |
| `functor (M : S) -> ...` | `(fn (M) ...)` — functor as SX lambda over module record |
| `open M` | inject M's bindings into scope via `env-merge` |
| `M.field` | `(get M :field)` |
| `{ r with f = v }` | `(dict-set r :f v)` |
| `ref x` | `(make-ref x)` — mutable cell |
| `!r` | `(deref-ref r)` |
| `r := v` | `(set-ref! r v)` |
| `(a, b, c)` | tagged list `(:tuple a b c)` |
| `[1; 2; 3]` | `(list 1 2 3)` |
| `[| 1; 2; 3 |]` | `(make-array 1 2 3)` (Phase 6) |
| `try e with \| Ex -> h` | `(guard (fn (ex) h) e)` via SX exception system |
| `raise Ex` | `(perform (:raise Ex))` |
| `Printf.printf "%d" x` | `(perform (:print (format "%d" x)))` |

## Dream semantic mappings (Phase 7)

| Dream construct | SX mapping |
|----------------|-----------|
| `handler = request -> response promise` | `(fn (req) (perform (:http-respond ...)))` |
| `middleware = handler -> handler` | `(fn (next) (fn (req) ...))` |
| `Dream.router [routes]` | `(ocaml-dream-router routes)` — dispatch on method+path |
| `Dream.get "/path" h` | route record `{:method "GET" :path "/path" :handler h}` |
| `Dream.scope "/p" [ms] [rs]` | prefix mount with middleware chain |
| `Dream.param req "name"` | path param extracted during routing |
| `m1 @@ m2 @@ handler` | `(m1 (m2 handler))` — left-fold composition |
| `Dream.session_field req "k"` | `(perform (:session-get req "k"))` |
| `Dream.set_session_field req "k" v` | `(perform (:session-set req "k" v))` |
| `Dream.flash req` | `(perform (:flash-get req))` |
| `Dream.form req` | `(perform (:form-parse req))` — returns Ok/Error ADT |
| `Dream.websocket handler` | `(perform (:websocket handler))` |
| `Dream.run handler` | starts SX HTTP server with handler as root |

## Roadmap

### Phase 1 — Tokenizer + parser

- [x] **Tokenizer:** keywords (`let`, `rec`, `in`, `fun`, `function`, `match`, `with`,
      `type`, `of`, `module`, `struct`, `end`, `functor`, `sig`, `open`, `include`,
      `if`, `then`, `else`, `begin`, `try`, `exception`, `raise`, `mutable`,
      `for`, `while`, `do`, `done`, `and`, `as`, `when`), operators (`->`, `|>`,
      `<|`, `@@`, `@`, `:=`, `!`, `::`, `**`, `:`, `;`, `;;`), identifiers (lower,
      upper/ctor), char literals `'c'`, string literals (escaped),
      int/float literals (incl. hex, exponent, underscores), nested block
      comments `(* ... *)`. _(labels `~label:` / `?label:` and heredoc `{|...|}`
      deferred — surface tokens already work via `~`/`?` punct + `{`/`|` punct.)_
- [~] **Parser:** expressions: literals, identifiers, constructor application,
      lambda, application (left-assoc), binary ops with precedence (29 ops via
      `lib/guest/pratt.sx`), `if`/`then`/`else`, `let`/`in`, `let rec`,
      `fun`/`->`, `match`/`with`, tuples, list literals, sequences `;`,
      `begin`/`end`, unit `()`. Top-level decls: `let [rec] name params* = expr`
      and bare expressions, `;;`-separated via `ocaml-parse-program`. _(Pending:
      `type`/`module`/`exception`/`open`/`include` decls, `try`/`with`,
      `function`, record literals/updates, field access, `and` mutually-recursive
      bindings.)_
- [x] **Patterns:** constructor (nullary + with args, incl. flattened tuple
      args), literal (int/string/char/bool/unit), variable, wildcard `_`,
      tuple, list cons `::`, list literal, record `{ f = pat; … }`,
      `as` binding, or-pattern `(P1 | P2 | …)` (parens-only — top-level
      `|` is the clause separator). Match clauses support `when` guard
      via `(:case-when PAT GUARD BODY)`.
- [ ] OCaml is **not** indentation-sensitive — no layout algorithm needed.
- [ ] Tests in `lib/ocaml/tests/parse.sx` — 50+ round-trip parse tests.

### Phase 2 — Core evaluator (untyped)

- [x] `ocaml-eval` entry: walks OCaml AST, produces SX values.
- [x] `let`/`let rec`/`let ... in`. Mutually recursive `let rec f = … and
      g = …` works at top level via `(:def-rec-mut BINDINGS)`; placeholders
      are bound first, rhs evaluated in the joint env, cells filled in.
      `let x = … and y = …` (non-rec) emits `(:def-mut BINDINGS)` —
      sequential bindings against the parent env.
- [x] Lambda + application (curried by default — auto-curry multi-param defs).
- [x] `fun`/`function` (single-arg lambda with immediate match on arg).
- [x] `if`/`then`/`else`, `begin`/`end`, sequence `;`.
- [x] Arithmetic, comparison, boolean ops, string `^`, `mod`.
- [x] Unit `()` value; `ignore`.
- [x] References: `ref`, `!`, `:=`.
- [x] Mutable record fields via `r.f <- v` — uses host SX `dict-set!`
      to mutate the underlying record dict in place. All record fields
      are de-facto mutable (the `mutable` keyword in type-decls is
      currently parsed-and-discarded).
- [ ] Mutable record fields.
- [x] `for i = lo to hi do ... done` loop; `while cond do ... done` (incl.
      `downto` direction).
- [x] `try`/`with` — maps to SX `guard`; `raise` is a builtin that calls
      host SX `raise`. `failwith` and `invalid_arg` ship as builtins.
- [ ] Tests in `lib/ocaml/tests/eval.sx` — 50+ tests, pure + imperative.

### Phase 3 — ADTs + pattern matching

- [x] `type` declarations: `type [params] t = | A | B of t1 [* t2] | …`.
      Parser emits `(:type-def NAME PARAMS CTORS)`. Runtime treats decls
      as no-ops since constructors are dispatched dynamically by tag.
      Phase 5 will register ctor types here for HM checking.
- [x] Constructors as tagged lists: `A` → `("A")`, `B(1, "x")` → `("B" 1 "x")`.
- [~] `match`/`with`: constructor, literal, variable, wildcard, tuple, list
      cons/nil, nested patterns. _(Pending: `as` binding, or-patterns,
      `when` guard.)_
- [x] Exhaustiveness: runtime error on incomplete match (no compile-time check yet).
- [ ] Built-in types: `option` (`None`/`Some`), `result` (`Ok`/`Error`),
      `list` (nil/cons), `bool`, `unit`, `exn`.
- [x] `exception` declarations: `exception NAME [of TYPE]`. Parser emits
      `(:exception-def NAME [ARG-TYPE-SRC])`. Runtime no-op since
      raise/match work on tagged ctor values. Built-ins:
      `Failure`/`Invalid_argument` via `failwith`/`invalid_arg`.
- [x] Polymorphic variants (surface syntax `` `Tag value ``; runtime same
      tagged list as nominal ctors). Tokenizer recognises backtick + ctor;
      parser/eval treat them identically to nominal ctors. Type system
      handling deferred (proper row types).
- [ ] Tests in `lib/ocaml/tests/adt.sx` — 40+ tests: ADTs, match, option/result.

### Phase 4 — Modules + functors

- [x] `module M = struct let x = 1 let f y = x + y end` → SX dict
      `{"x" 1 "f" <fn>}`.
- [x] `module type S = sig val x : int val f : int -> int end` parses
      via `parse-decl-module-type`. Signature contents are skipped
      (sig..end nesting tracked) — runtime no-op since types are
      structural. AST: `(:module-type-def NAME)`.
- [x] `module M : S = struct ... end` — coercive sealing (signature ignored).
- [x] `functor (M : S) -> struct ... end` via shorthand `module F (M) = …`.
- [x] `module F = Functor(Base)` — functor application; multi-param via
      `module P = F(A)(B)`.
- [x] `open M` — merge M's dict into current env (via
      `ocaml-env-merge-dict`). Module path `M.Sub` resolves via
      `ocaml-resolve-module-path`.
- [x] `include M` — at top level same as `open`; inside a module also
      copies M's bindings into the surrounding module's exports.
- [x] `M.name` — dict get via field access.
- [ ] First-class modules (pack/unpack) — deferred to Phase 5.
- [ ] Standard module hierarchy: `List`, `Option`, `Result`, `String`, `Char`,
      `Int`, `Float`, `Bool`, `Unit`, `Printf`, `Format` (stubs, filled in Phase 6).
- [ ] Tests in `lib/ocaml/tests/modules.sx` — 30+ tests.

### Phase 5.1 — Conformance scoreboard

- [x] `lib/ocaml/conformance.sh` runs the full test suite, classifies
      each test by description prefix into a suite (tokenize, parser,
      eval-core, phase2-refs, phase2-loops, phase2-function, phase2-exn,
      phase3-adt, phase4-modules, phase5-hm, phase6-stdlib, let-and,
      phase1-params, misc), and emits `scoreboard.json` + `scoreboard.md`.
- [~] Baseline OCaml programs at `lib/ocaml/baseline/` exercised through
      `ocaml-run-program`. Currently 5/5: factorial.ml (recursion),
      list_ops.ml (List.map + fold_left), option_match.ml (option +
      pattern match), module_use.ml (module + ref + closure +
      sequenced calls), sum_squares.ml (for-loop + ref). Real OCaml
      testsuite vendoring is the next step.

### Phase 5 — Hindley-Milner type inference

- [~] Algorithm W: `gen`/`inst` from `lib/guest/hm.sx`, `unify` from
      `lib/guest/match.sx`, `infer-expr` written here. Covers atoms, var,
      lambda, app, let, if, op, neg, not. _(Pending: tuples, lists,
      pattern matching, let-rec, modules.)_
- [x] Type variables: `'a`, `'b`; unification with occur-check (kit).
- [x] Let-polymorphism: generalise at let-bindings (kit `hm-generalize`).
- [x] ADT types: `option`/`result` ctors seeded;
      `ocaml-hm-register-type-def!` registers user types from `:type-def`.
      `ocaml-type-of-program` threads decls through the env, registering
      types and binding `let` schemes. `:con NAME` / `:pcon NAME …`
      instantiate from the registry. Ctor arg types parsed via
      `ocaml-hm-parse-type-src` — handles primitives (`int`/`bool`/
      `string`/`float`/`unit`), tyvars `'a`, simple parametric `T list`/
      `T option`. Multi-arg/complex types fall back to a fresh tv.
- [~] Function types `T1 -> T2` work; tuples (`'a * 'b`) and lists
      (`'a list`) supported. Records pending.
- [ ] Type signatures: `val f : int -> int` — verify against inferred type.
- [ ] Module type checking: seal against `sig` (Phase 4 stubs become real checks).
- [ ] Error reporting: position-tagged errors with expected vs actual types.
- [ ] First-class modules: `(module M : S)` pack; `(val m : (module S))` unpack.
- [ ] No rank-2 polymorphism, no GADTs (out of scope).
- [ ] Tests in `lib/ocaml/tests/types.sx` — 60+ inference tests.

### Phase 6 — Standard library

- [~] `List`: `map`, `filter`, `fold_left`, `fold_right`, `length`, `rev`,
      `append`, `iter`, `for_all`, `exists`, `mem`, `nth`, `hd`, `tl`,
      `rev_append`, `concat`/`flatten`, `init`, `iteri`, `mapi`, `find`,
      `find_opt`, `assoc`, `assoc_opt`, `partition`, `sort`,
      `stable_sort`, `combine`, `split`, `iter2`, `fold_left2`, `map2`.
      30+ functions covered.
- [~] `Option`: `map`, `bind`, `value`, `get`, `is_none`, `is_some`,
      `iter`, `fold`, `to_list`. _(Pending: join/to_result.)_
- [~] `Result`: `map`, `bind`, `is_ok`, `is_error`, `get_ok`,
      `get_error`, `map_error`, `to_option`. _(Pending: fold/join.)_
- [~] `Hashtbl`: `create`, `add`, `find`, `find_opt`, `replace`, `mem`,
      `length`. Backed by a one-element list cell holding a SX dict;
      keys coerced to strings via `str` for polymorphic-key support.
- [~] `Buffer`: `create`, `add_string`, `add_char`, `contents`, `length`,
      `clear`, `reset`. Backed by a ref holding a list of strings; reverse +
      `String.concat` on `contents`. Mostly-OCaml impl.
- [~] `Stack`: `create`, `push`, `pop`, `top`, `is_empty`, `length`,
      `clear`. Backed by a ref-holding-list (LIFO).
- [~] `Queue`: `create`, `push`, `pop`, `is_empty`, `length`, `clear`.
      Backed by a `(front, back)` tuple-of-lists pair (amortised O(1)
      enqueue/dequeue via list reversal).
- [~] `Sys`: `os_type` (`"SX"`), `word_size`, `max_array_length`,
      `max_string_length`, `executable_name`, `big_endian`, `unix`,
      `win32`, `cygwin`. Constants only; `argv`/`getenv_opt`/`command`
      pending (would need host platform integration).
- [x] `String`: `length`, `get`, `sub`, `concat`, `uppercase_ascii`,
      `lowercase_ascii`, `starts_with`, `ends_with`, `contains`, `trim`,
      `split_on_char`, `replace_all`, `index_of`.
- [~] `Char`: `code`, `chr`, `lowercase_ascii`, `uppercase_ascii`.
      _(Pending: escaped.)_
- [~] `Int`: `to_string`, `of_string`, `abs`, `max`, `min`.
      _(Pending: arithmetic helpers, min_int/max_int.)_
- [~] `Float`: `to_string`, `sqrt`, `sin`, `cos`, `pow`, `floor`,
      `ceil`, `round`, `pi`. _(Pending: of_string.)_
- [~] `Printf`: stub `sprintf`/`printf`. _(Real format-string
      interpretation pending.)_
- [ ] `String`: `length`, `get`, `sub`, `concat`, `split_on_char`, `trim`,
      `uppercase_ascii`, `lowercase_ascii`, `contains`, `starts_with`, `ends_with`,
      `index_opt`, `replace_all` (non-stdlib but needed).
- [ ] `Char`: `code`, `chr`, `escaped`, `lowercase_ascii`, `uppercase_ascii`.
- [ ] `Int`/`Float`: arithmetic, `to_string`, `of_string_opt`, `min_int`, `max_int`.
- [ ] `Hashtbl`: `create`, `add`, `replace`, `find`, `find_opt`, `remove`, `mem`,
      `iter`, `fold`, `length` — backed by SX mutable dict.
- [x] `Map.Make` functor — sorted association list backed
      (insert/find/remove/mem/cardinal/bindings); not a balanced tree
      but linear with parametric `Ord` ordering.
- [x] `Set.Make` functor — sorted list backed
      (add/mem/remove/elements/cardinal).
- [ ] `Printf`: `sprintf`, `printf`, `eprintf` — format strings via `(format ...)`.
- [ ] `Sys`: `argv`, `getenv_opt`, `getcwd` — via `perform` IO.
- [ ] Scoreboard runner: `lib/ocaml/conformance.sh` + `scoreboard.json`.
- [ ] Target: 150+ tests across all stdlib modules.

### Phase 7 — Dream web framework (`lib/dream/`)

The five types: `request`, `response`, `handler = request -> response`,
`middleware = handler -> handler`, `route`. Everything else is a function over these.

- [ ] **Core types** in `lib/dream/types.sx`: request/response records, route record.
- [ ] **Router** in `lib/dream/router.sx`:
      - `dream-get path handler`, `dream-post path handler`, etc. for all HTTP methods.
      - `dream-scope prefix middlewares routes` — prefix mount with middleware chain.
      - `dream-router routes` — dispatch tree, returns handler; no match → 404.
      - Path param extraction: `:name` segments, `**` wildcard.
      - `dream-param req name` — retrieve matched path param.
- [ ] **Middleware** in `lib/dream/middleware.sx`:
      - `dream-pipeline middlewares handler` — compose middleware left-to-right.
      - `dream-no-middleware` — identity.
      - Logger: `(dream-logger next req)` — logs method, path, status, timing.
      - Content-type sniffer.
- [ ] **Sessions** in `lib/dream/session.sx`:
      - Cookie-backed session middleware.
      - `dream-session-field req key`, `dream-set-session-field req key val`.
      - `dream-invalidate-session req`.
- [ ] **Flash messages** in `lib/dream/flash.sx`:
      - `dream-flash-middleware` — single-request cookie store.
      - `dream-add-flash-message req category msg`.
      - `dream-flash-messages req` — returns list of `(category, msg)`.
- [ ] **Forms + CSRF** in `lib/dream/form.sx`:
      - `dream-form req` — returns `(Ok fields)` or `(Err :csrf-token-invalid)`.
      - `dream-multipart req` — streaming multipart form data.
      - CSRF middleware: stateless signed tokens, session-scoped.
      - `dream-csrf-tag req` — returns hidden input fragment for SX templates.
- [ ] **WebSockets** in `lib/dream/websocket.sx`:
      - `dream-websocket handler` — upgrades request; handler `(fn (ws) ...)`.
      - `dream-send ws msg`, `dream-receive ws`, `dream-close ws`.
- [ ] **Static files:** `dream-static root-path` — serves files, ETags, range requests.
- [ ] **`dream-run`**: wires root handler into SX's `perform (:http-listen ...)`.
- [ ] **Demos** in `lib/dream/demos/`:
      - `hello.ml` → `lib/dream/demos/hello.sx`: "Hello, World!" route.
      - `counter.ml` → `lib/dream/demos/counter.sx`: in-memory counter with sessions.
      - `chat.ml` → `lib/dream/demos/chat.sx`: multi-room WebSocket chat.
      - `todo.ml` → `lib/dream/demos/todo.sx`: CRUD list with forms + CSRF.
- [ ] Tests in `lib/dream/tests/`: routing dispatch, middleware composition,
      session round-trip, CSRF accept/reject, flash read-after-write — 60+ tests.

### Phase 8 — ReasonML syntax variant (`lib/reasonml/`)

ReasonML is OCaml with a JS-friendly surface: semicolons, `let` with `=` everywhere,
`=>` for lambdas, `switch` for match, `{j|...|j}` string interpolation. Same semantics —
different tokenizer + parser, same `lib/ocaml/transpile.sx` output.

- [ ] **Tokenizer** in `lib/reasonml/tokenizer.sx`:
      - `let x = e;` binding syntax (semicolons required).
      - `(x, y) => e` arrow function syntax.
      - `switch (x) { | Pat => e | ... }` for match.
      - JSX: `<Comp prop=val />`, `<div>children</div>`.
      - String interpolation: `{j|hello $(name)|j}`.
      - Type annotations: `x : int`, `let f : int => int = x => x + 1`.
- [ ] **Parser** in `lib/reasonml/parser.sx`:
      - Produce same OCaml AST nodes as `lib/ocaml/parser.sx`.
      - JSX → SX component calls: `<Comp x=1 />` → `(~comp :x 1)`.
      - Multi-arg functions: `(x, y) => e` → auto-curried pair.
- [ ] Shared transpiler: `lib/reasonml/transpile.sx` delegates to
      `lib/ocaml/transpile.sx` (parse → ReasonML AST → OCaml AST → SX AST).
- [ ] Tests in `lib/reasonml/tests/`: tokenizer, parser, eval, JSX — 40+ tests.
- [ ] ReasonML Dream demos: translate Phase 7 demos to ReasonML syntax.

## The meta-circular angle

SX is bootstrapped to OCaml (`hosts/ocaml/`). Running OCaml inside SX running on OCaml is
the "mother tongue" closure: OCaml → SX → OCaml. This means:

- The OCaml host's native pattern matching and ADTs are exact reference semantics for
  the SX-level implementation — any mismatch is a bug.
- The SX `match` / `define-type` primitives (Phase 6 of the primitives roadmap) were
  built knowing OCaml was the intended target.
- When debugging the transpiler, the OCaml REPL is always available as oracle.
- Dream running in SX can serve the sx.rose-ash.com docs site — the framework that
  describes the runtime it runs on.

## Key dependencies

- **Phase 6 ADT primitive** (`define-type`/`match`) — required before Phase 3.
- **`perform`/`cek-resume`** IO suspension — required before Phase 7 (Dream async).
- **HO forms** and first-class lambdas — already in spec, no blocker.
- **Module system** (Phase 4) is independent of type inference (Phase 5) — can overlap.
- **ReasonML** (Phase 8) can start once OCaml parser is stable (after Phase 2).

## Progress log

_Newest first._

- 2026-05-08 Phase 1+6 — Buffer module + parser fix for `f !x` (+3
  tests, 425 total). Parser: at-app-start? and parse-app's loop now
  recognise `!` as the prefix-deref of an application argument, so
  `String.concat "" (List.rev !b)` parses as `(... (deref b))`. Buffer
  uses a ref holding a string list; contents reverses and concats.
- 2026-05-08 Phase 5.1 — btree.ml baseline (13/13 pass). Polymorphic
  binary search tree (`type 'a tree = Leaf | Node of 'a * 'a tree *
  'a tree`) with insert + in-order traversal. Tests parametric ADT,
  recursive match, List.append, List.fold_left.
- 2026-05-11 Phase 5.1 — trapping_rain.ml baseline (trapped rain
  water over heights [0;1;0;2;1;0;1;3;2;1;2;1] = 6). Left-max and
  right-max prefix arrays; at each index water = min(L,R) − h.
  Tests dual sweep (forward + downto), array of running maxes,
  inline-if rhs of `<-` for running-max update (uses iter-236
  fix). 203 baseline programs total.
- 2026-05-11 Phase 5.1 — gas_station.ml baseline (find unique start
  station for circular gas tour, gas=[1;2;3;4;5] cost=[3;4;5;1;2]
  → start at index 3). Classic O(n) greedy: walk once tracking the
  total tank delta (if negative, no solution → -1) and a running
  tank that resets to 0 when it goes negative, advancing start
  past the failing index. From station 3: tank 3,3+5,3+5−3,…
  succeeds. Tests `total` + `curr` parallel accumulators, reset-
  on-failure pattern. 202 baseline programs total.
- 2026-05-11 Phase 5.1 — min_jumps.ml baseline (greedy BFS-like
  min jumps to reach end of [2;3;1;1;2;4;2;0;1;1] = 4). At each
  position track the farthest reach within the current "BFS
  layer"; when i reaches the layer end, bump jumps and extend to
  farthest. Optimal jump sequence 0→1→4→5→9 = 4 jumps. Tests
  greedy-with-frontier idiom, three parallel refs (jumps, cur_end,
  farthest), mixed for-style index loop using ref. 201 baseline
  programs total.
- 2026-05-11 Phase 5.1 — combinations.ml baseline (C(9, 4) = 126
  enumerated). Pascal-style recursive split: with first element h,
  combinations either include h (recurse with k−1 on rest) or
  exclude h (recurse with k on rest); identity choose k [] = [].
  C(9, 4) = 126 = 9!/(4!·5!). Tests pure-functional combination
  enumeration with `List.map` + closure over `h`, `@` append,
  and `[] | h :: rest` pattern match on shrinking input.
  200 baseline programs total.
- 2026-05-11 Phase 5.1 — unique_paths_obs.ml baseline (count
  monotone paths in 4×4 grid with obstacles at (1,1),(2,3),(3,0)
  = 3). Standard 2D DP with obstacle gating: dp[i][j] = dp[i-1][j]
  + dp[i][j-1] when grid[i][j]=0, else 0. Complements grid_paths.ml
  (no-obstacles version): the same DP but obstacles zero out
  cells, reshaping the path count. 199 baseline programs total.
- 2026-05-11 Phase 5.1 — daily_temperatures.ml baseline (monotonic
  decreasing stack of waiting days until warmer; sum over
  [73;74;75;71;69;72;76;73] = 10). For each day i, pop all stack
  entries whose temperature is strictly less than today's,
  recording `i - top` for each. Result vector [1,1,4,2,1,1,0,0]
  sums to 10. Complementary to next_greater.ml — same monotonic-
  stack skeleton but stores the distance to the next greater
  element instead of its value. Tests pattern `match !stack with
  | top :: rest when condition -> … | _ -> exit` inside while.
  198 baseline programs total.
- 2026-05-11 Phase 5.1 — count_bits.ml baseline (sum of popcount for
  0..100 = 319). DP recurrence: popcount(i) = popcount(i/2) +
  (i mod 2) — drop the low bit and recurse, adding back the bit
  if it was 1. Avoids needing host bitwise ops (i mod 2 stands in
  for `i land 1`, i/2 for `i lsr 1`). Sum over 0..100 = 319.
  Tests pure-arithmetic popcount, accumulating ref + array.
  197 baseline programs total.
- 2026-05-11 Phase 5.1 — bs_rotated.ml baseline (binary search in
  rotated sorted array; encoded result -66). For [4;5;6;7;0;1;2]:
  - search 0 → index 4
  - search 7 → index 3
  - search 3 → −1 (not present)
  Encoded: 4 + 3*10 + (-1)*100 = -66. Each step decides which half
  is sorted by comparing arr[lo] vs arr[mid], then checks whether
  the target falls in that sorted half. First baseline with a
  negative top-level result; test runner uses literal `grep -qF`
  so the leading minus is fine. 196 baseline programs total.
- 2026-05-11 Phase 5.1 — task_scheduler.ml baseline (task cooldown
  formula, "AAABBC" with n=2 → 7 intervals). Counts each letter,
  finds max frequency `m` and the number of letters that hit that
  max `k`. Formula: `(m-1)·(n+1) + k`, taking the larger of that
  and the total task count when interleaving fills the schedule.
  Witness: A,B,C,A,B,idle,A satisfies cooldown 2 between A→A and
  B→B. Tests String.iter with side-effecting closure (count
  histogram update via Char.code arithmetic). 195 baseline
  programs total.
- 2026-05-11 Phase 5.1 — min_subarr_target.ml baseline (sliding-
  window min subarray with sum ≥ target on [2;3;1;2;4;3] target=7
  = 2). Two-pointer: expand right, then shrink left while sum
  stays ≥ target, recording the min length seen. Optimal window
  is [4, 3] (positions 4-5) with sum 7, length 2. Tests `for` +
  inner `while` shrinking loop, ref-tracked sum updated on both
  expansion and contraction, sentinel `n + 1` for "not found".
  194 baseline programs total.
- 2026-05-11 Phase 5.1 — min_meeting_rooms.ml baseline (sweep-line
  for min concurrent meetings on 8 intervals = 4). Separate starts
  and ends arrays sorted independently, then a two-pointer sweep:
  on start<end push a new room (busy+1), else release one (busy-1).
  Intervals: (0,30) (5,10) (15,20) (10,25) (5,12) (20,35) (0,5)
  (8,18). At time 8: meetings (0,30), (5,10), (5,12), (8,18) all
  active → 4 simultaneous = answer. Tests local recursive helper
  `let bubble a = …` for in-place sort, dual-pointer sweep on
  ordered events. 193 baseline programs total.
- 2026-05-11 Phase 5.1 — activity_select.ml baseline (greedy
  earliest-end-time activity selection on 11 intervals → max
  non-overlapping 4). Sort intervals by end time (bubble sort to
  avoid relying on List.sort with tuple comparator), then sweep
  and accept whenever start ≥ last_end. Optimal selection:
  (1,4) → (5,7) → (8,11) → (12,14) = 4 activities. Tests double-
  loop bubble sort on tuple array with let-pattern destructure
  for swap key extraction, in-place swap of tuple cells via temp.
  192 baseline programs total.
- 2026-05-11 Phase 5.1 — count_paths_dag.ml baseline (count
  source-to-sink paths in the same 6-node DAG as topo_sort.ml,
  paths 0→5 = 3). Topological sort via Kahn's (BFS), then relax
  edges in topological order: paths[v] += paths[u] for each edge
  u→v. paths[source] = 1. The three witnesses:
    0 → 1 → 3 → 5
    0 → 2 → 3 → 5
    0 → 2 → 4 → 5
  Tests Queue-driven Kahn order + List.rev to topological order,
  module-level mutable arrays (in_deg / paths), accumulation in
  topological traversal. 191 baseline programs total.
- 2026-05-11 Phase 5.1 — stock_two.ml baseline (max stock profit
  with at most 2 transactions on [3;3;5;0;0;3;1;4] = 6). Two-pass
  DP: left[i] = max single-transaction profit in prices[0..i]
  (forward, tracking running min), right[i] = max single-transaction
  profit in prices[i..n-1] (backward, tracking running max). Final
  answer = max over i of left[i]+right[i], partitioning at the
  best split. Optimal: buy@3, sell@5 (profit 2); buy@0, sell@4
  (profit 4); total = 6. Tests parallel forward + backward passes
  on parallel DP arrays, mixed ref+array state, downto + ascending
  scans on same data. 190 baseline programs total.
- 2026-05-11 Phase 5.1 — house_robber.ml baseline (linear-DP
  max non-adjacent-sum on [2;7;9;3;1;5;8;6] = 22). dp[i] =
  max(dp[i-2]+houses[i], dp[i-1]). Optimal pick {7, 9, 5, 8} but
  that's adjacent (5 next to 8). Re-check: {7, 9, 1, 8} sum 25?
  Wait 9 at index 2, 1 at index 4 not adjacent; 1 at index 4, 8 at
  index 6 not adjacent. But 7,9 are at indices 1,2 — adjacent!
  Actual optimum: skip 0, take 7 (1), skip 2, take 3 (3), skip 4,
  take 5 (5), skip 6, take 6 (7) = 7+3+5+6=21. Or take 2,9,1,8 =
  20. Or 2,9,5,6 = 22? indices 0,2,5,7 (non-adjacent). 2+9+5+6=22.
  ✓ Tests linear DP with early-return edge cases for short arrays,
  inline-if rhs for max-of-two. 189 baseline programs total.
- 2026-05-11 Phase 5.1 — interval_overlap.ml baseline (count
  overlapping interval pairs in 7-interval set = 6). For each pair
  (i, j) test `s1 ≤ e2 && s2 ≤ e1`. Intervals: (1,4) (2,5) (7,9)
  (3,6) (8,10) (11,12) (0,2). Overlapping pairs:
    (1,4)&(2,5), (1,4)&(3,6), (1,4)&(0,2),
    (2,5)&(3,6), (2,5)&(0,2), (7,9)&(8,10)
  = 6 pairs. Tests double for-loop with both-side tuple destructure
  via `let (s1, e1) = arr.(i) in`, conjunctive comparison, list-to-
  array conversion. 188 baseline programs total.
- 2026-05-11 Phase 5.1 — count_palindromes.ml baseline (count
  palindromic substrings of "aabaa" = 9). Expand-around-center
  with 2n−1 centers (n odd-length, n−1 even-length); for each,
  walk outward while characters match and both indices in range.
  Decomposition: c/2 and (c+1)/2 gives both odd (c even) and even
  (c odd) starting pairs. Palindromes in "aabaa": 5 singletons
  + 2 "aa"s + 1 "aba" + 1 "aabaa" = 9. Complements lps_dp.ml
  (longest subsequence) and manacher.ml (longest substring); this
  one counts ALL palindromic substrings. 187 baseline programs total.
- 2026-05-11 Phase 5.1 — count_subarrays_k.ml baseline (count
  contiguous subarrays of [1;1;1;2;-1;3;1;-2;4] summing to k=3
  = 7). Prefix-sum brute force in O(n²): build cumulative array,
  enumerate (i,j) pairs and check prefix[j]−prefix[i]=k. The 7
  witnesses include [1,1,1], [1,1,2,−1], [1,2], [2,−1,3,1,−2],
  [−1,3,1], [3], [1,−2,4]. Negative-valued arrays exercise our
  evaluator's mixed-sign integer arithmetic (no underflow at the
  JS safe-int boundary). 186 baseline programs total.
- 2026-05-11 Phase 5.1 — floyd_cycle.ml baseline (Floyd's tortoise
  -and-hare cycle detection on f(x) = (2x+5) mod 17; μ=0 λ=8 →
  encoded 8). Three phases: (1) advance slow/fast until they
  collide inside the cycle, (2) restart slow from x0 and advance
  both by 1 until they meet — the count is μ (tail length),
  (3) advance fast around the cycle once until it meets slow — the
  count is λ (cycle length). For x0=1, f(1)=7, f(7)=2, … cycles
  through all 8 distinct values 1,7,2,9,6,0,5,15 before returning
  to 1, so μ=0 λ=8 → 0*100+8=8. Tests three sequential while
  loops sharing ref state, double-step `fast := f (f !fast)`,
  meeting-condition flag pattern. 185 baseline programs total.
- 2026-05-11 Phase 5.1 — kth_two.ml baseline (8th smallest in
  merged [1;3;5;7;9;11;13] ∪ [2;4;6;8;10;12] = 8). Two-pointer
  merge that advances the smaller-head pointer k times. Pick rule:
    pick_a = (j past end) ? true
           : (i past end) ? false
           : a[i] <= b[j]
  Combined order is 1,2,3,4,5,6,7,8,…; 8th element = 8. Tests
  nested `if/else if/else` flowing into a bool, dual-ref two-
  pointer loop, separate count counter for the k-th constraint.
  184 baseline programs total.
- 2026-05-11 Phase 5.1 — permutations_gen.ml baseline (enumerate all
  24 permutations of [1;2;3;4], count those with first<last = 12).
  Recursive build via fold-pick-recurse:
    permutations xs = ∪ over x ∈ xs of x :: p for p ∈ permutations
                       (xs \ {x})
  The pattern `[a; _; _; b] when a < b` then filters fixed-length
  4-element permutations on the head/tail relation. Exactly half
  (12) satisfy a < b by symmetry. Tests `List.filter`, recursive
  fold with append, `[a; _; _; b]` fixed-length list pattern with
  multiple wildcards and a `when` guard. 183 baseline programs total.
- 2026-05-11 Phase 5.1 — regex_simple.ml baseline (recursive `.`/`*`
  regex matcher; over 4×7 = 28 (pattern, text) combos, 7 match).
  Cases per pattern:
    .a.b   — 1 match (aabb)
    a.*b   — 2 matches (aabb, axb)
    x*     — 2 matches ("" and "x")
    a*b*c  — 2 matches (abc, aaabbbc)
  Two-position lookahead for `p.[j+1] = '*'` to decide between
  zero-match and consume-one-char-and-retry. Complements
  wildcard_match.ml (LIKE-style `*` / `?` semantics); this one
  has Leetcode-style `.` (any char) and `<c>*` (zero+ of c).
  182 baseline programs total.
- 2026-05-11 Phase 5.1 — palindrome_part.ml baseline (minimum
  palindrome-partition cuts in "aabba" = 1). Two-phase DP:
    1) `is_pal.(i).(j)` table via length-major iteration.
    2) `cuts.(i)` = min cuts for prefix s[0..i]; if s[0..i] itself
       is a palindrome, 0; else min over j of (cuts.(j-1) + 1)
       where s[j..i] is a palindrome.
  For "aabba" the optimal partition is "a" | "abba" = 1 cut.
  Tests sequential 2D DP passes sharing the same `is_pal` matrix,
  inline `if/else begin/end` blocks under length-major fill, mixed
  bool and int 2D arrays. 181 baseline programs total.
- 2026-05-11 Phase 5.1 — island_count.ml baseline (count 4-connected
  components of 1-cells in a 6×7 grid = 5). DFS flood from every
  unvisited 1-cell. Counted islands:
    {(0,0),(0,1),(1,0)}
    {(0,5),(0,6),(1,4),(1,5)}
    {(2,2),(2,3),(3,1),(3,2),(3,3)}
    {(3,5),(3,6),(4,5),(5,3),(5,4),(5,5)}
    {(4,0),(5,0),(5,1)}
  Complementary to flood_fill.ml which measures largest component;
  this counts them. Tests recursive returning `()` (unit) at
  early-exit branches, ordered double-for entry pass that triggers
  one fill per island root. 180 baseline programs total.
- 2026-05-11 Phase 5.1 — dp_word_break.ml baseline (word-break DP
  over 5 strings with 9-word dictionary; 4 strings segmentable).
  dp[i] = ∃ word w of length wl ≤ i with prefix s[i−wl..i]=w and
  dp[i−wl]=true. Inputs: applepenapple, pineapplepenapple,
  catsanddog (yes); catsandog (no — leftover "og"); applesand (yes).
  Tests bool-typed DP array, `String.sub s start len` substring
  primitive, nested List.iter over dict inside for-loop over
  positions, short-circuit + closure. 179 baseline programs total.
- 2026-05-11 Phase 5.1 — histogram_area.ml baseline (largest
  rectangle in histogram [2;1;5;6;2;3] = 10). Linear-time stack
  algorithm: push indices while heights are non-decreasing; on
  drop, pop and compute the bar's max-width rectangle. Sentinel
  iteration at i=n with h=0 flushes remaining stack. The bars at
  indices 2 and 3 (heights 5 and 6) form a width-2 rectangle of
  height 5 = 10. Tests `match … with | top :: rest when h > … -> …
  | _ -> …` guarded patterns inside a while-cont-flag loop, nested
  match on the same stack ref. 178 baseline programs total.
- 2026-05-11 Phase 5.1 — lps_dp.ml baseline (longest palindromic
  subsequence in "BBABCBCAB" = 7). Classic 2D O(n²) DP over
  substring lengths: dp[i][j] = dp[i+1][j-1] + 2 when s[i]=s[j],
  else max(dp[i+1][j], dp[i][j-1]). Witness LPS is "BABCBAB" (or
  "BACBCAB" etc.) of length 7. Complementary to manacher.ml
  (substring, length 7) — this is the subsequence variant.
  Tests length-major fill order, mixed strict/inline if for
  `len = 2` base case, double-nested for with derived j. 177
  baseline programs total.
- 2026-05-11 Phase 5.1 — bs_bounds.ml baseline (lower_bound /
  upper_bound binary search; counts of {3,2,5,9,4} in
  [1;2;2;3;3;3;5;7;9] encoded as 3211). Standard half-open
  variants: lower_bound returns the first index ≥ x (using `<`),
  upper_bound returns the first index > x (using `≤`). Counts:
  3→3, 2→2, 5→1, 9→1, 4→0 → 3*1000+2*100+1*10+1+0 = 3211. Tests
  parallel while loops with bisection on ref, mixed strict and
  non-strict comparisons, computing counts via subtraction.
  176 baseline programs total.
- 2026-05-11 Phase 5.1 — distinct_subseq.ml baseline (count
  distinct subsequences of "rabbbit" that equal "rabbit" = 3).
  Classic 2D DP: dp[i][j] = dp[i-1][j] + (s[i-1]=t[j-1] ? dp[i-1][j-1]
  : 0). Three witnesses correspond to which `b` in "rabbbit" is
  dropped: positions 2, 3, or 4 (0-indexed of the three b's).
  Complements existing subseq_check.ml which just tests presence.
  Tests 2D DP with `Array.init … (fun _ -> Array.make …)`, edge-
  case base row dp[i][0]=1 (empty t is a subseq of any prefix).
  175 baseline programs total.
- 2026-05-11 Phase 5.1 — bracket_match.ml baseline (multi-bracket
  parenthesis matching over 9 test strings, 5 balanced). Stack-
  based scan: push openers `( [ {`, pop and pair-check closers.
  Sequence: `() [{()}] ({[}]) "" (( ()[](){} (a(b)c) (() ])` →
  yes/yes/no/yes/no/yes/yes/no/no = 5.
  Tests `begin … match !stack with [] -> … | top :: rest -> … end`
  with side-effecting match arms inside a `while` body, ref-of-list
  used as a stack, three-way char dispatch via short-circuited
  comparisons. 174 baseline programs total.
- 2026-05-11 Phase 5.1 — wildcard_match.ml baseline (recursive `*`
  /`?` wildcard matcher; over 3×6 = 18 (pattern, text) combos,
  6 match). Cases:
    a*b   — 1 match (aaab)
    ?b*c  — 1 match (abc)
    *x*y* — 4 matches (abxyz, xy, xyz, axby)
  is_match uses `||` for the `*` alternation (consume zero or one
  more char and recurse) and `&&` for the literal/`?` case.
  Tests deeply nested short-circuit, char equality in pattern
  predicate, doubly-nested `List.iter` to enumerate the cross
  product. 173 baseline programs total.
- 2026-05-11 Phase 5.1 — powerset_target.ml baseline (count subsets
  of {1..10} summing to 15 = 20). Generates the full 2^10 = 1024
  powerset by recursive doubling: `gen (h :: rest) = gen rest @
  map (cons h) (gen rest)`, then filters by sum. The 20 valid
  subsets include {1,2,3,4,5}, {2,3,4,6}, {1,4,10}, {7,8}, {6,9},
  etc. Tests recursive subset construction via List.map + closures,
  pattern matching with `h :: rest`, `List.fold_left (+) 0`
  one-line summary, exhaustive O(2^n · n) traversal. 172 baseline
  programs total.
- 2026-05-11 Phase 5.1 — parser: top-level tuple patterns
  (`match e1, e2 with | p1, p2 -> …`). `parse-pattern` now collects
  comma-separated patterns into a `:ptuple`, mirroring the scrutinee
  side which already builds tuples via `parse-tuple`. Real OCaml
  accepts this ad-hoc tuple shorthand without surrounding parens.
  lru_cache.ml reverts the iter-258 workaround back to the natural
  `match n, lst with | 0, _ -> [] | _, [] -> [] | _, h :: r -> …`
  form. 607/607 regressions clean.
- 2026-05-11 Phase 5.1 — lru_cache.ml baseline (list-backed LRU
  cache of capacity 3, fingerprint 100 + (-1) + 300 + 100 = 499).
  Each `get` / `put` removes the existing entry then conses the
  fresh one to the front; `put` evicts the tail when over capacity.
  Tests `match … with [] -> … | (k', v) :: rest when k' = k -> …`
  pattern matching with `when` guards over tuple-cons patterns,
  `function` keyword for short scrutinee-free matches, recursive
  `find` / `remove` / `take` over the same list. 171 baseline
  programs total.
- 2026-05-11 Phase 5.1 — convex_hull.ml baseline (Andrew's monotone
  chain over 8 points → 5-vertex convex hull). Sorts points
  lexicographically, then builds lower hull left-to-right and upper
  hull right-to-left, popping back when the cross product turns the
  wrong way. Points: (0,0), (1,1), (2,0), (2,2), (0,2), (1,0),
  (3,3), (5,1). Hull = (0,0) → (2,0) → (5,1) → (3,3) → (0,2) = 5
  vertices ((2,0) lies on lower edge, included by the ≤ test).
  Tests List.sort with a 2-tuple comparator using nested pair
  destructure, repeated `let (x, y) = arr.(i) in` array tuple
  destructure across both passes, while + cont-flag pattern instead
  of break. 170 baseline programs total.
- 2026-05-10 Phase 5.1 — next_greater.ml baseline (monotonic stack
  for next-greater-element over [4;5;2;25;7;8;1;30;12], sum of
  successors = 153). Right-to-left scan with a stack that pops
  elements ≤ current. Result: [5,25,25,30,8,30,30,-1,-1]; sum of
  non-negative entries = 5+25+25+30+8+30+30 = 153. Tests stack
  encoded as `ref list` with match-driven peek, `match … with []
  -> false | h :: _ -> …` boolean coercion in `while` guard,
  inline match-statement on rhs of `<-` (paren-wrapped for parse).
  169 baseline programs total.
- 2026-05-10 Phase 5.1 — fenwick_tree.ml baseline (Binary Indexed
  Tree over [1;3;5;7;9;11;13;15], total + after = 228). Initial
  prefix_sum n = 64; after +100 at index 1, prefix_sum n = 164;
  64 + 164 = 228. Because our `land` evaluator implementation only
  handles non-negative operands (iter 127 bitwise-via-arithmetic
  workaround), and Fenwick relies on `i & -i` to extract the
  lowest set bit, we replace `i land (-i)` with a portable
  `lowbit` helper: largest power-of-2 dividing i. Tests recursive
  update / prefix_sum chains using helper-extracted lowbit,
  highlights a non-obvious tradeoff in the bitwise-emulation
  layer. 168 baseline programs total.
- 2026-05-10 Phase 5.1 — segment_tree.ml baseline (range-sum
  segment tree over [1;3;5;7;9;11;13;15] with one point update,
  encoded r1 + r2*100 = 4232). build/query/update use the standard
  power-of-two indexing (node, 2·node, 2·node+1) over a flat
  4n-sized array. First range query 5+7+9+11=32; after adding 10
  to index 3, the same range = 5+17+9+11=42. Encoded: 32 + 42*100
  = 4232. Tests three mutually independent recursive functions
  with array index arithmetic, half-bisection on `mid = (l + r) / 2`,
  bottom-up combine pattern. 167 baseline programs total.
- 2026-05-10 Phase 5.1 — magic_square.ml baseline (5×5 Siamese
  construction → main-diagonal sum 65, the magic constant for n=5).
  Place k=1 at (0, n/2); for each next k, move up-right with wrap-
  around; if that cell is taken, move down one row instead.
  Magic constant for an n×n square is n(n²+1)/2 = 5·26/2 = 65.
  Tests 2D array via Array.init + Array.make, mod arithmetic with
  the `(x - 1 + n) mod n` idiom for negative-safe wrap, nested
  begin/end branches inside for-loop body. 166 baseline programs
  total.
- 2026-05-10 Phase 5.1 — matrix_power.ml baseline (Fibonacci via
  2×2 matrix fast exponentiation, F(30) = 832040). [[1,1],[1,0]]^n
  has Fibonacci numbers in the top row; recursive O(log n) power
  via repeated squaring on even n, multiply by base on odd n.
  Records `{a; b; c; d}` standing in for matrix entries. F(30) =
  832040 matches the closed form. Tests record literal construction
  inside recursive function returns, record field access (x.a etc),
  arithmetic on integers (no float). 165 baseline programs total.
- 2026-05-10 Phase 5.1 — bipartite.ml baseline (BFS 2-coloring on
  7-node cycle-rich graph → bipartite with 4 vertices in color 0).
  Edges: 0-1, 0-3, 1-2, 1-4, 2-5, 3-4, 3-6, 5-6. This forms a
  bipartite graph with partition {0,2,4,6} vs {1,3,5}. Returns
  count of color-0 vertices (4) on success, -1 on odd-cycle
  detection. Tests Queue-based BFS with module-level state in a
  loop over all sources (handles disconnected graphs), `1 - color.(u)`
  binary toggle, color-equality conflict check. 164 baseline
  programs total.
- 2026-05-10 Phase 5.1 — egg_drop.ml baseline (worst-case trials
  for 2 eggs, 36 floors = 8). Classic O(e·f²) DP: dp[e][f] = 1 +
  min over k of max(dp[e-1][k-1], dp[e][f-k]). Closed form via
  triangular numbers gives ⌈(√(1+8·36)−1)/2⌉ = 8, matching the
  DP answer. Tests 2D DP with triple-nested for-loops, max-of-two
  via inline if, large sentinel constant, mixed indexing (e-1)
  and (f-k) where both shift independently. 163 baseline programs
  total.
- 2026-05-10 Phase 5.1 — polygon_area.ml baseline (shoelace formula
  on pentagon, returns 2× area = 32). Vertices (0,0), (4,0), (4,3),
  (2,5), (0,3); shoelace sum |Σ(x_i·y_{i+1} − x_{i+1}·y_i)| = 32 so
  the area is 16, and we return the doubled form to stay integral.
  Tests `let (x1, y1) = arr.(i) in` tuple destructuring from array
  access (previously suspected broken in bfs_grid but works here —
  the iter-242 bug was actually `&&` short-circuit, not destructure),
  modular wrap-around `arr.((i+1) mod n)`, prefix-`-` negation.
  162 baseline programs total.
- 2026-05-10 Phase 5.1 — min_cost_path.ml baseline (min-cost path
  through 4×4 cost grid, top-left to bottom-right with moves
  right/down only, optimal = 12). Standard 2D DP: dp[i][j] = min
  of (dp[i-1][j], dp[i][j-1]) + cost[i][j]. Cost matrix yields
  optimal path 1→1→2→1→1→1→2→3 = 12 (visiting (0,0),(1,0),(2,1),
  (2,2),(2,3),(3,3)? actually the path is row-by-row). Tests
  nested 2D arrays via Array.init + Array.make, double-nested
  for-loops with branched edges (first row, first column, then
  general case), `dp.(i-1).(j)` 2-D index read + `dp.(i).(j)<-`
  2-D write. 161 baseline programs total.
- 2026-05-10 Phase 5.1 — topo_dfs.ml baseline (DFS-based topo sort
  on the same 6-node DAG as topo_sort.ml, digit-fingerprint 24135).
  Cons each node onto the order list AFTER recursing on all its
  out-edges, giving a reverse topological order. Same graph
  (0→{1,2}; 1→{3}; 2→{3,4}; 3→{5}; 4→{5}; 5) produces order
  [0;2;4;1;3;5]. Reduce: 0→0→2→24→241→2413→24135. Complementary
  to Kahn's (iter 230); tests recursive DFS without an explicit
  stack and List.fold_left as a horner reduction. 160 baseline
  programs total.
- 2026-05-10 Phase 5.1 — radix_sort.ml baseline (LSD radix sort,
  fingerprint a.(0) + a.(7)*1000 = 802002). 8-element array
  [170;45;75;90;802;24;2;66] sorts to [2;24;45;66;75;90;170;802].
  Sentinel encoding 2 + 802*1000 = 802002. Uses `Array.init 10
  (fun _ -> ref [])` to allocate 10 fresh per-digit bucket cells
  per pass (each closure call yields a distinct ref). Tests
  array-of-refs with `!buckets.(d)` deref pattern, list-mode
  bucket sort within in-place array sort, `for _ = 1 to maxd`
  unused loop variable. 159 baseline programs total.
- 2026-05-10 Phase 5.1 — coin_min.ml baseline (minimum-coin change
  for 67¢ with US denominations = 6 coins). DP with -1 sentinel
  for unreachable values: `dp.(i) := min over coins c of dp.(i-c)+1
  when dp.(i-c) >= 0`. 67 = 25+25+10+5+1+1 = 6 coins. Tests `if
  c <= i && dp.(i - c) >= 0 then …` guard short-circuit; without
  iter-242 fix the dp.(i-c) read would crash for c > i. 158
  baseline programs total.
- 2026-05-10 Phase 5.1 — flood_fill.ml baseline (largest connected
  component in 5×5 grid = 7). Recursive 4-direction flood from
  every unvisited 1-cell; bounds check short-circuits before the
  visited/grid reads (relies on iter-242 fix). The maximal
  component in the test grid spans {(2,2), (3,0), (3,1), (3,2),
  (3,3), (4,3), (4,4)} = 7 cells. Tests recursive function with
  4-way self-call, in-place visited array mutation, nested
  short-circuited bounds + content guards. 157 baseline programs total.
- 2026-05-10 Phase 5.1 — next_permutation.ml baseline (count
  permutations of [1;2;3;4;5] via Narayana's algorithm = 119).
  Standard in-place algorithm: find largest i with a.(i) < a.(i+1),
  largest j > i with a.(j) > a.(i), swap, reverse suffix from i+1.
  Returns false when array is the reverse-sorted (final)
  permutation. 5! = 120, minus the initial = 119 successful calls.
  Tests guarded `while … >= 0 && a.(!i) >= a.(!i + 1) do …`
  pattern using the new short-circuit semantics (would loop or
  crash without iter-242 fix). 156 baseline programs total.
- 2026-05-10 Phase 5.1 — `&&` / `||` short-circuit fix + bfs_grid.ml
  baseline (BFS shortest path on 5×5 grid with walls, dist 8). The
  evaluator's `:op` handler was evaluating BOTH sides of `&&`/`||`
  before dispatching, mismatching OCaml's left-to-right
  short-circuit semantics. This was invisible in earlier baselines
  because their rhs was always safe regardless of lhs, but a guard
  like `if nr >= 0 && grid.(nr).(nc) = 0 then …` crashes with
  "nth: list/string and number" when nr = -1 because `grid.(-1)`
  still gets evaluated. Fix: dispatch `&&` and `||` specially in
  `:op` to evaluate lhs first and only evaluate rhs if needed.
  Bug latent since baseline 1; surfaced by bfs_grid with bounds-
  checked grid access. bfs_grid optimal path 0→1→1→2→2→3→3→3→4 of
  weighted length 8. 155 baseline programs total.
- 2026-05-10 Phase 5.1 — tarjan_scc.ml baseline (Tarjan's
  strongly-connected components on 8-node digraph → 4 SCCs).
  Graph: 0→1→2→0 (cycle) plus 2→3, 3→4, 4→5→6→4 (cycle), 4→7.
  SCCs: {0,1,2}, {3}, {4,5,6}, {7} = 4 components. Single DFS
  with index/lowlink, on-stack flag, pop until root when
  lowlink = index. Tests recursive functions with module-level
  ref + array state, nested begin/end branches inside List.iter
  closure, inner `let rec pop ()` walking ref-of-list, pattern
  match on `[] | h :: rest`. 154 baseline programs total.
- 2026-05-10 Phase 5.1 — lev_iter.ml baseline (iterative
  Levenshtein DP, sum of 5 distances = 16). Rolling-array DP
  (O(min(m,n)) space). Distances: kitten→sitting=3, saturday→
  sunday=3, abc→abc=0, ""→abcde=5, intention→execution=5;
  3+3+0+5+5=16. Complementary to existing levenshtein.ml which
  uses the recursive (exponential) definition; this one is the
  practical iterative form used for real ED computations. Tests
  the new `<- if ... then ... else ...` rhs path twice in the
  same body. 153 baseline programs total.
- 2026-05-10 Phase 5.1 — binary_heap.ml baseline (array-backed
  binary min-heap, push 9 random values then pop in sorted order
  → digits concat to 123456789). Standard heap mechanics: parent
  /lchild/rchild index arithmetic, sift-up after push, sift-down
  after pop. Push [9;4;7;1;8;3;5;2;6], pop returns 1..9 in order;
  fold-as-decimal yields 123456789. Tests recursive sift_up /
  sift_down, ref-tracked external size, in-place array swap,
  combined push/pop with mutable closure. 152 baseline programs total.
- 2026-05-10 Phase 5.1 — rolling_hash.ml baseline (Rabin-Karp
  rolling hash for substring matching, count "abc" in
  "abcabcabcabcabcabc" = 6). Polynomial hash mod 1000003 with
  base 257; precompute base^(m-1), then slide window updating
  hash in O(1) per step. Verify hash matches with O(m) memcmp to
  avoid false positives. Tests `for _ = 0 to m - 2 do … done`
  unused loop variable, char-code arithmetic, mod under negative
  intermediate, complex nested if/begin/end branching.
  151 baseline programs total.
- 2026-05-10 Phase 5.1 — huffman.ml baseline (Huffman tree weighted
  path length on letters {(5,a) (9,b) (12,c) (13,d) (16,e) (45,f)}
  = 224). Builds optimal prefix code by repeatedly merging the two
  lightest trees: insert merged node back into a sorted-by-weight
  list. Verifies the standard Huffman result of 224 bits for this
  classic CLRS example. Tests sum-typed ADT with two arities
  (Leaf of int * char | Node of int * tree * tree), `function`
  keyword pattern matching, recursive sorted insert, depth-counting
  recursion. 150 baseline programs total.
- 2026-05-10 Phase 5.1 — parser: accept `if/match/let/fun/...` as
  the rhs of `<-` and `:=`. parse-binop-rhs now special-cases prec-1
  ops (`<-`, `:=`) to call parse-expr-no-seq for their right operand
  instead of parse-prefix. Real OCaml accepts `a.(i) <- if c then x
  else y`, `r := match e with | A -> 1 | B -> 2`, etc. Manacher
  baseline (iter 235) updated to use bare `if` on rhs of `<-`,
  matching the workaround note left in its previous Progress entry.
  607/607 regressions clean. Useful improvement for hot inner-loop
  code that needs conditional rhs.
- 2026-05-10 Phase 5.1 — manacher.ml baseline (Manacher's longest
  palindromic substring on "babadaba" = 7). Inserts `#` separators
  to unify odd/even cases (string of length 2n+1), then maintains
  palindrome radii in `p[]` plus a current rightmost-reach pair
  (center, right). Linear time. The full input "babadaba" itself is
  not a palindrome, but "abadaba" (positions 1..7) is — length 7.
  149 baseline programs total.
- 2026-05-10 Phase 5.1 — floyd_warshall.ml baseline (all-pairs
  shortest path on 4-node weighted graph, dist 0→3 = 9). Standard
  O(n³) DP: for each intermediate vertex k, relax all (i,j) pairs.
  Uses a 2D array implemented as `Array.init n (fun _ -> Array.make
  n inf)`, exercising nested array indexing `g.(i).(j)`. Direct
  edge 0→3 weighs 10; via 0→1→2→3 = 5+3+1 = 9. Tests 2D array
  construction with closures, triple-nested for-loops, nested
  `.(i).(j)<-` mutation. 148 baseline programs total.
- 2026-05-10 Phase 5.1 — mst_kruskal.ml baseline (Kruskal MST on
  5-node, 6-edge graph → MST weight 11). Sort edges by weight,
  greedily add edges whose endpoints are in different components
  using union-find with path compression. Edges (w,u,v) sorted:
  (1,0,1) ✓ (2,1,2) ✓ (3,0,3) ✓ (4,2,3) ✗ already connected,
  (5,3,4) ✓ (6,0,4) ✗. Picked weight: 1+2+3+5 = 11. Tests
  List.sort with 3-tuple destructuring lambda, Array.init, in-place
  array mutation (find compression), boolean-returning union.
  147 baseline programs total.
- 2026-05-10 Phase 5.1 — trie.ml baseline (prefix tree, 6 of 9
  word lookups match). Mutable record `{ terminal; children }` with
  `children : (char * trie) list`; insert recurses down character
  by character, mutating children list when a path is missing.
  Insert {cat, car, card, cart, dog, doge}; lookup {cat, car, ca,
  card, cart, dog, doge, dogs, x}. The 6 hits are exactly the
  inserted words; "ca", "dogs", "x" miss.
  Tests recursive type definition with self-reference, mutable
  records, `match … | Some / None` over option, pattern with
  `(k, v) :: rest` tuple destructuring. 146 baseline programs total.
- 2026-05-10 Phase 5.1 — count_inversions.ml baseline (count
  inversions of [|8;4;2;1;3;5;7;6|] = 12, via merge-sort). Modified
  merge sort: when right element is taken, accumulate `mid - i + 1`
  inversions for the remaining left half. Tests `let rec merge ...
  and sort ...` mutually recursive bindings, complex while + ref
  + array mutation, in-place sort with auxiliary array.
  145 baseline programs total.
- 2026-05-10 Phase 5.1 — topo_sort.ml baseline (Kahn's algorithm
  topological sort of a 6-node DAG → all 6 vertices ordered).
  Standard BFS approach: compute in-degrees, seed queue with zero-
  indegree nodes, pop and decrement neighbours. Graph: 0→{1,2};
  1→{3}; 2→{3,4}; 3→{5}; 4→{5}; 5. A valid topological order is
  0,1,2,3,4,5. Returns count of sortable nodes (6 means acyclic).
  Tests Queue.{create,push,pop,is_empty}, List.iter with closure
  capturing `in_deg`, mutable array. 144 baseline programs total.
- 2026-05-10 Phase 5.1 — knapsack.ml baseline (0/1 knapsack DP,
  cap=8 with values [|6;10;12;15;20|] and weights [|1;2;3;4;5|]
  → max value 36). 1D rolling DP: outer loop over items, inner
  for-downto so each item used at most once. Optimal pack {1,2,5}
  weighing 8 with value 36 (also matches 6+10+20). Tests
  `for c = cap downto w do … done` reverse iteration plus
  array literal access in the same hot loop. 143 baseline
  programs total.
- 2026-05-10 Phase 5.1 — lcs.ml baseline (longest common subsequence
  of "ABCBDAB" and "BDCAB" = 4). Rolling-array DP in O(mn) time and
  O(min(m,n)) space: keep `prev` and `curr` rows, copy after each
  outer step. Two LCS witnesses: "BCAB" and "BDAB". Avoids needing
  `Array.make_matrix` (not in our runtime) by manual rolling. Tests
  string indexing, double-nested for-loops, sibling for to copy
  rows, integer compare. 142 baseline programs total.
- 2026-05-10 Phase 5.1 — dijkstra.ml baseline (single-source shortest
  path on a 5-node weighted graph, dist 0→4 = 7). O(n²) array-based
  Dijkstra: at each step, scan unvisited vertices for the minimum
  tentative distance, relax outgoing edges. Adjacency stored as
  `(int * int) list array` literal with int-pair tuples for
  destination + weight. The optimal path 0→2→1→3→4 has weight
  1+2+1+3=7. Tests array of lists of pairs, List.iter with
  destructuring closure, in-place dist mutation. 141 baseline
  programs total.
- 2026-05-10 Phase 5.1 — kmp.ml baseline (KMP string search, count
  occurrences of "abab" in "abababcabababcababcc" = 5). Two-phase
  classic: build failure table in O(m), then linear scan in O(n).
  After a full match, set k := t.(n-1) so overlapping matches still
  count (so "abab" hits at positions 0, 2, 7, 9, 14). Tests nested
  while-inside-for, char comparison via `pat.[i]`, Array.make 0,
  combined string + array indexing. 140 baseline programs total.
- 2026-05-10 Phase 5.1 — union_find.ml baseline (disjoint-set union
  on n=10 with 6 unions → 4 components). Path-compressing find:
  recursively walks parent links, splices subtree onto root in place.
  After unioning {0-1, 2-3, 0-2} → {0,1,2,3}; {4-5, 6-7, 4-6} →
  {4,5,6,7}; 8 and 9 remain singletons → 4 components. Tests
  Array.init, in-place .(i)<-r mutation, recursive find with
  path compression. 139 baseline programs total.
- 2026-05-10 Phase 5.1 — quickselect.ml baseline (Hoare quickselect
  median of [7;2;9;1;5;6;3;8;4] = 5). Lomuto partition scheme:
  recursively partitions on the last element as pivot, narrows the
  range to the side containing the kth index. Mutates the array
  in place via `.(i) <- v`. Verifies that array literal syntax
  works for in-place mutation paths, not just reads. 138 baseline
  programs total.
- 2026-05-10 Phase 5.1 — array literals + lis.ml baseline (longest
  increasing subsequence on a 9-element array = 6). Added parser
  support for `[| e1; e2; ...; en |]` syntax: desugars to
  `Array.of_list [e1; e2; ...; en]` (which is `ref [...]`). Empty
  `[||]` → `Array.of_list []`. Tokenizer leaves `[`, `|`, `|`, `]`
  as separate ops; parser detects `[` followed by `|` to switch to
  array-literal mode and consumes `|` then `]` at the end.
  Used in `lis [|10; 22; 9; 33; 21; 50; 41; 60; 80|] = 6`.
  Tests Array.length, .(i)<-/.(i), nested for + ref.
  137 baseline programs total.
- 2026-05-10 Phase 5.1 — josephus.ml baseline (Josephus problem,
  n=50 k=3 → survivor at position 11, 1-indexed). Uses the classic
  recursive formula: J(1, k) = 0; J(n, k) = (J(n-1, k) + k) mod n.
  Returns 0-indexed survivor; we add 1 for human readability. Tests
  recursion, mod, integer arithmetic. 136 baseline programs total.
- 2026-05-10 Phase 5.1 — partition_count.ml baseline (number of
  integer partitions of 15 = 176). Classic DP: dp[0] = 1; for each
  k from 1..n, for each i from k..n, dp[i] += dp[i - k]. O(n²) time,
  O(n) space. p(15) = 176 partitions of 15. Tests Array.make, array
  set/get with `.(i)<-` / `.(i)`, nested for-loops, ref deref.
  135 baseline programs total.
- 2026-05-10 Phase 5.1 — pythagorean.ml baseline (count primitive
  Pythagorean triples with hypotenuse ≤ 100 = 16). Uses Euclid's
  formula: for coprime m > k of opposite parity, the triple
  (m² - k², 2mk, m² + k²) is primitive Pythagorean. The 16 triples
  are (3,4,5), (5,12,13), (8,15,17), (7,24,25), (20,21,29),
  (9,40,41), ..., (65,72,97). 134 baseline programs total.
- 2026-05-10 Phase 5.1 — harshad.ml baseline (count Niven/Harshad
  numbers ≤ 100 = 33). A Harshad number is divisible by its digit
  sum. All single-digit numbers qualify trivially (9). Plus 10, 12,
  18, 20, 21, 24, 27, 30, 36, 40, 42, 45, 48, 50, 54, 60, 63, 70,
  72, 80, 81, 84, 90, 100 (24 more) = 33 under 100. 133 baseline
  programs total.
- 2026-05-10 Phase 5.1 — reverse_int.ml baseline (digit-reverse,
  reverse(12345) + reverse(100) + reverse(7) = 54321 + 1 + 7 =
  54329). Walks digits via mod 10 / div 10, accumulating the
  reversed value. Trailing zeros collapse (reverse 100 = 1).
  132 baseline programs total.
- 2026-05-10 Phase 5.1 — bowling.ml baseline (10-pin bowling score
  for canonical "167" PBA-style game). Walks pin-knockdown list
  applying strike/spare bonuses through a 10-frame counter. Strike
  consumes 1 throw + 2 bonus; spare consumes 2 throws + 1 bonus;
  open frame is just the two pin counts. Frame ten special-cases
  ignored (input includes the bonus throws naturally). 131 baseline
  programs total.
- 2026-05-10 Phase 5.1 — tail_factorial.ml baseline (12! via tail
  recursion = 479001600). Single-helper tail-recursive loop
  threading an accumulator. Companion to factorial.ml (10! via
  doubly-recursive style); same answer-shape, different evaluator
  stress (tail-call optimisation if any, or pure constant stack
  depth otherwise). 130 baseline programs total — milestone.
- 2026-05-10 Phase 5.1 — zerosafe.ml baseline (Option-chained safe
  division, sum 10 + (-1) + (-1) + 20 = 28). safe_div returns None
  on division by zero; safe_chain stitches two divisions, propagating
  None on either failure. Tests Option chaining + match-on-result
  with sentinel default. 129 baseline programs total.
- 2026-05-10 Phase 5.1 — number_words.ml baseline (letter count of
  numbers 1-19 spelled out = 106). 19-arm match dispatch returning
  the English word for each number. Sums lengths over 1..19. Real
  PE17 covers 1..1000 (answer 21124) but requires more elaborate
  number-to-words logic. Tests literal-pattern match with many arms.
  128 baseline programs total.
- 2026-05-10 Phase 5.1 — palindrome_sum.ml baseline (sum of 3-digit
  palindromes = 49500). 90 palindromes between 100 and 999 (form
  aba; 9 choices for a, 10 for b). Sum = 49500 = 90 * 550 (mean
  is 550). 127 baseline programs total.
- 2026-05-10 Phase 5.1 — triangle_div.ml baseline (first triangle
  number with > 10 divisors = 120). PE12 with target 10. T(15) = 120
  has 16 divisors {1,2,3,4,5,6,8,10,12,15,20,24,30,40,60,120} —
  first to break 10. Real PE12 uses target 500 (answer 76576500);
  10 stays well under our budget. 126 baseline programs total.
- 2026-05-10 Phase 5.1 — perfect.ml baseline (count perfect numbers
  ≤ 500 = 3). Perfect numbers = those where d(n) = n. Three under
  500: 6, 28, 496. (8128 is the next.) Same div_sum machinery as
  euler21_small / abundant. Original 10000 limit timed out under
  contention; 500 stays under budget. 125 baseline programs total —
  milestone.
- 2026-05-10 Phase 5.1 — abundant.ml baseline (count abundant numbers
  < 100 = 21). Abundant means d(n) > n where d(n) is the proper-
  divisor sum. Reuses the trial-division div_sum helper from iter
  205. 21 abundant numbers under 100, starting at 12, 18, 20, 24...
  124 baseline programs total.
- 2026-05-10 Phase 5.1 — euler36.ml baseline (sum of double-base
  palindromes ≤ 1000 = 1772). Numbers that read the same in base 10
  and base 2: 1, 3, 5, 7, 9, 33, 99, 313, 585, 717. Sum = 1772.
  Real PE36 uses 10^6 (answer 872187); 1000 takes ~9 minutes on
  contended host but fits within 480s timeout * inner-iteration
  cost ratio. 123 baseline programs total.
- 2026-05-10 Phase 5.1 — euler40_small.ml baseline (Champernowne
  digit-product at 1, 10, 100, 1000 = 1*1*5*3 = 15). Builds the
  Champernowne string until ≥1500 chars; tracks length separately
  from the Buffer to avoid O(n²) `String.length (Buffer.contents
  buf)` reallocation. Real PE40 uses positions up to 10^6 (answer
  210). 122 baseline programs total.
- 2026-05-09 Phase 5.1 — euler34_small.ml baseline (numbers equal
  to sum of factorials of digits, ≤2000 = 145). 145 = 1!+4!+5! =
  1+24+120. The other "factorion" 40585 is the only number above
  this threshold; PE34 sums both = 40730. 121 baseline programs
  total.
- 2026-05-09 Phase 5.1 — euler29_small.ml baseline (distinct a^b for
  2≤a,b≤5 = 15). 16 powers minus the one duplicate (4^2 = 2^4 = 16)
  → 15 distinct values. Hashtbl as a set with unit-payload (the
  iter-168 idiom). Real PE29 uses N=100 (answer 9183). 120 baseline
  programs total — milestone.
- 2026-05-09 Phase 5.1 — euler21_small.ml baseline (sum of amicable
  numbers ≤ 300 = 504). div_sum computes proper divisor sum via
  trial division up to √n; outer loop finds amicable pairs (a, b)
  with d(a) = b, d(b) = a, a ≠ b. Only pair under 300 is (220, 284).
  Real PE21 uses 10000 (answer 31626). 119 baseline programs total.
- 2026-05-09 Phase 5.1 — euler30_cube.ml baseline (sum of numbers
  equal to sum of cubes of their digits, ≤999 = 1301). The full
  numbers are 153 + 370 + 371 + 407 = 1301. PE30 proper uses 5th
  powers (answer 443839); cube version exercises the same algorithm
  in a smaller search space. 118 baseline programs total.
- 2026-05-09 Phase 5.1 — euler28.ml baseline (sum of diagonals in
  7x7 number spiral = 261). For each layer (1..(n-1)/2) the four
  corners are spaced 2*layer apart, so we step `k` four times per
  layer and accumulate into s. Real PE28 uses 1001x1001 (answer
  669171001); 7x7 is fast enough to land in seconds. 117 baseline
  programs total.
- 2026-05-09 Phase 5.1 — euler14.ml baseline (longest Collatz chain
  starting under N=100 → 97). collatz_len walks n through the
  iteration n/2 if even, 3n+1 if odd, counting steps. Outer loop
  scans 2..N tracking best length and arg-best. n=97 generates the
  118-step chain. Real PE14 uses N=1,000,000 (answer 837799); N=100
  exercises the same algorithm in <2 minutes. 116 baseline programs
  total.
- 2026-05-09 Phase 5.1 — euler16.ml baseline (digit sum of 2^15 =
  26). Computes 2^n via for-loop accumulation, then walks the digits
  via mod 10 / div 10 to sum them. Real PE16 asks for 2^1000 which
  exceeds float precision; 2^15 = 32768 stays safe and exercises the
  same digit-decomposition pattern. 115 baseline programs total.
- 2026-05-09 Phase 5.1 — euler25.ml baseline (first Fibonacci index
  with 12 digits = 55). Iteratively grows two refs while the larger
  is below `10^(n-1)`, counting iterations. Real PE25 asks for 1000
  digits (= 4782); 12 keeps within safe-int while exercising the
  identical algorithm. 114 baseline programs total — and 200
  iterations landed.
- 2026-05-09 Phase 5.1 — euler3.ml baseline (largest prime factor of
  13195 = 29; PE3's worked example). Trial-division streaming: when
  the current factor divides m, divide and update largest; otherwise
  bump factor. Numbers like 600851475143 (the actual PE3) exceed JS
  safe-int (2^53 ≈ 9e15), so 13195 keeps the program portable. 113
  baseline programs total.
- 2026-05-09 Phase 5.1 — euler7.ml baseline (100th prime = 541;
  scaled-down PE7 which asks for the 10001st = 104743). Trial-
  division within an outer while loop searching forward from 2,
  short-circuited via bool ref. 100 keeps under 3-min budget while
  exercising the same algorithm. 112 baseline programs total.
- 2026-05-09 Phase 5.1 — euler4_small.ml baseline (largest 2-digit
  palindrome product = 9009 = 91 * 99). Scaled-down Project Euler
  #4 (real version uses 3-digit numbers, 906609; that's 810k inner
  iterations and would time out under contention). Tests palindrome
  predicate via index-walk + nested for. 111 baseline programs total.
- 2026-05-09 Phase 5.1 — euler10.ml baseline (sum of primes ≤ 100 =
  1060, scaled-down Project Euler #10). Sieve of Eratosthenes
  followed by a sum loop. Used 100 instead of 2 million to fit our
  contended host's runtime budget. 110 baseline programs total.
- 2026-05-09 Phase 5.1 — euler5.ml baseline (Project Euler #5,
  smallest number divisible by all 1..20 = 232792560). Iteratively
  takes lcm of running result with i for i=2..n; lcm via gcd via
  Euclidean. 232792560 = 2^4 * 3^2 * 5 * 7 * 11 * 13 * 17 * 19.
  Tests two-line gcd/lcm + for-loop accumulator pattern. 109
  baseline programs total.
- 2026-05-09 Phase 5.1 — partition.ml baseline (stable partition by
  predicate, 30*100 + 25 = 3025). Two ref lists accumulating in
  reverse, then List.rev'd — preserves original order. Test:
  `partition (fun x -> x mod 2 = 0) [1..10]` → ([2;4;6;8;10],
  [1;3;5;7;9]) → 30*100 + 25 = 3025. Tests higher-order predicate
  + tuple return + iter-98 let-tuple destructuring. 108 baseline
  programs total.
- 2026-05-09 Phase 5.1 — is_prime.ml baseline (count primes ≤ 100 =
  25). Trial division up to √n with early-exit via bool ref. Loop
  2..n calling is_prime, accumulate count. Returns 25 (the canonical
  prime-counting function π(100)). Tests two cooperating functions
  + while-with-bool-short-circuit + nested for. 107 baseline
  programs total.
- 2026-05-09 Phase 5.1 — catalan.ml baseline (Catalan number C(5)
  via DP recurrence = 42). DP recurrence `C(n) = sum_{j=0}^{n-1}
  C(j) * C(n-1-j)`. C(5) = 42 — also the count of distinct binary
  trees with 5 internal nodes, balanced paren strings of length
  10, etc. Tests nested for-loop over Array with arr.(i) read +
  write. 106 baseline programs total.
- 2026-05-09 Phase 5.1 — fizz_classifier.ml baseline (FizzBuzz with
  polymorphic variants, 1..30 weighted score = 540). Two functions:
  classify maps i → ` `FizzBuzz | `Fizz | `Buzz | `Num n``, and score
  pattern-matches the variant to a weight (100/10/5/value). For
  i=1..30: 200 + 80 + 20 + 240 = 540. Exercises polyvariant
  match (iter 87) including a payload-bearing `` `Num n``. 105
  baseline programs total.
- 2026-05-09 Phase 5.1 — max_product3.ml baseline (max product of
  three from a list including negatives = 300). Sort, then compare
  product of three largest vs product of two smallest negatives and
  one largest. For [-10;-10;1;3;2]: 3*2*1 = 6 vs (-10)*(-10)*3 = 300.
  Tests List.sort + Array.of_list + arr.(n-i) end-walk + candidate
  compare. 104 baseline programs total.
- 2026-05-09 Phase 5.1 — euler9.ml baseline (Project Euler #9, abc
  product for the unique Pythagorean triple with a+b+c=1000 →
  31875000). Naive triple loop times out under contention (10-min
  cap); rewritten with algebraic reduction
  `b = (500000 - 1000a) / (1000 - a)` so only one loop is needed.
  Triple is (200, 375, 425). 103 baseline programs total.
- 2026-05-09 Phase 5.1 — euler6.ml baseline (Project Euler #6, square
  of sum minus sum of squares for 1..100 = 25164150). Single for-loop
  threading two refs; (sum 1..100)^2 - sum(i^2 for 1..100) = 5050^2
  - 338350 = 25502500 - 338350 = 25164150. 102 baseline programs total.
- 2026-05-09 Phase 5.1 — euler2.ml baseline (Project Euler #2, sum
  of even Fibonacci ≤ 4M = 4613732). Iterative two-ref Fibonacci,
  accumulating only even terms. Sequence: 1, 2, 3, 5, 8, 13, 21,
  34... only 2, 8, 34, 144, ... contribute. 101 baseline programs
  total.
- 2026-05-09 Phase 5.1 — euler1.ml baseline (Project Euler #1, sum
  of multiples of 3 or 5 below 1000 = 233168). Trivial DSL exercise
  but symbolically meaningful: this is the 100th baseline program.
- 2026-05-09 Phase 5.1 — anagram_groups.ml baseline (group strings
  by canonical anagram form, ["eat";"tea";"tan";"ate";"nat";"bat"]
  has 3 groups). canonical builds a sorted-by-frequency string
  representation: count letters, then expand into a-z order. Used
  as Hashtbl key. group_anagrams folds the input list, accumulating
  per-key string lists; final answer is Hashtbl.length (number of
  distinct groups). Tests count-then-expand canonical pattern +
  Hashtbl as multimap. 99 baseline programs total.
- 2026-05-09 Phase 5.1 — monotonic.ml baseline (monotonicity check,
  4/5 inputs monotonic). Tracks two bool refs (inc, dec). Each pair
  of consecutive elements: if `h < prev` clear `inc`, if `h > prev`
  clear `dec`. Empty list and singleton are vacuously true. Five
  test cases:
    [1;2;3;4]   inc only        true
    [4;3;2;1]   dec only        true
    [1;2;1]     neither          false
    [5;5;5]     both (constant)  true
    []           empty            true (vacuous)
  Sum = 4. 98 baseline programs total.
- 2026-05-09 Phase 5.1 — majority_vote.ml baseline (Boyer-Moore
  majority, [3;3;4;2;4;4;2;4;4] → 4). O(n) time / O(1) space:
  candidate-and-count refs; on match increment, on mismatch
  decrement and replace candidate when count reaches zero.
  Demonstrates the classical streaming algorithm. 97 baseline
  programs total.
- 2026-05-09 Phase 5.1 — adler32.ml baseline (Adler-32 checksum of
  "Wikipedia" = 300286872 = 0x11E60398). Two running sums modulo
  65521; final checksum is `b * 65536 + a`. Used by zlib for stream
  integrity. Tests for-loop accumulating two refs, modular
  arithmetic, and Char.code on s.[i]. 96 baseline programs total.
- 2026-05-09 Phase 5.1 — gray_code.ml baseline (4-bit binary
  reflected Gray code, sum 120 + length 16 = 136). Single-formula
  generation: `gray[i] = i lxor (i lsr 1)`. Outputs a permutation of
  0..15, so its sum is the same 120 as the natural sequence; the
  length-16 confirms 2^4 entries. Tests `lsl`/`lxor`/`lsr` together
  and Array.make + Array.fold_left. 95 baseline programs total.
- 2026-05-09 Phase 5.1 — max_run.ml baseline (longest consecutive
  run, sum of three test cases = 4+1+0 = 5). Walks list with
  `Some y when y = x` guard pattern in match for the prev-value
  comparison; runs of equal elements increment cur, resets to 1
  otherwise. Tests three inputs:
    [1;1;2;2;2;2;3;3;1;1;1]   max run = 4 (the 2's)
    [1;2;3;4;5]               max run = 1
    []                         max run = 0
  Sum = 5. Tests `when` guard in match arm + Option ref. 94 baseline
  programs total.
- 2026-05-09 Phase 5.1 — subseq_check.ml baseline (string is
  subsequence?, 3/5 yes). Two-pointer walk: advance `i` only on
  match, always advance `j`. Match if `i` reaches `n` (consumed
  all of s). Five test cases:
    abc in ahbgdc      yes
    axc in ahbgdc      no  (no x in t)
    "" in anything     yes (empty trivially)
    abc in abc         yes
    abcd in abc        no  (s longer)
  Sum = 3. 93 baseline programs total.
- 2026-05-09 Phase 5.1 — merge_intervals.ml baseline (LeetCode #56,
  total length 9 + 3 = 12). Sort by start, then sweep maintaining a
  current `(cs, ce)` window — extend `ce` if next start ≤ ce, else
  push current and start new. `[(1,3);(2,6);(8,10);(15,18);(5,9)]`
  merges to `[(1,10);(15,18)]`, total length 9+3 = 12. Tests
  List.sort with custom cmp + tuple destructuring everywhere
  (closure lambda with tuple-pattern, let-tuple from accumulator,
  match arms). 92 baseline programs total.
- 2026-05-09 Phase 5.1 — hamming.ml baseline (Hamming distance,
  3 + 2 + (-1) = 4). Counts position-wise differences in equal-length
  strings; returns -1 sentinel for length mismatch.
    karolin vs kathrin   3 (positions 2,3,4)
    1011101 vs 1001001   2 (positions 2,4)
    abc     vs abcd     -1 (length mismatch)
  Sum = 4. 91 baseline programs total.
- 2026-05-09 Phase 5.1 — xor_cipher.ml baseline (XOR roll-key
  encryption, round-trip → 601). For each character, XOR with the
  corresponding key char (key cycled via `i mod kn`). Encrypts
  "Hello!" with key "key", decrypts the result, and verifies the
  round-trip preserves both length (6) and equality. Tests
  Char.code + Char.chr round-trip + the iter-127 `lxor` operator
  + Buffer.add_string + String.make 1. 90 baseline programs total.
- 2026-05-09 Phase 5.1 — simpson_int.ml baseline (Simpson's rule
  numerical integration, ∫₀¹ x² dx ≈ 1/3, scaled = 10000). Composite
  Simpson's 1/3 rule with 100 panels. Coefficients 1-4-2-...-2-4-1
  via even/odd index dispatch. Result × 30000 = 9999.99... → int =
  10000 (rounding artifact). Tests higher-order function (passing
  the integrand `(fun x -> x *. x)`), float arithmetic in for-loop,
  and float_of_int for index→x conversion. 89 baseline programs total.
- 2026-05-09 Phase 5.1 — int_sqrt.ml baseline (integer Newton sqrt,
  12+14+1000+1 = 1027). Newton's method on integers using `(x +
  n/x) / 2` until convergence (`y >= x`). Tests:
    isqrt 144     = 12
    isqrt 200     = 14   (floor of sqrt(200) = 14.14...)
    isqrt 1000000 = 1000
    isqrt 2       = 1
  Sum = 1027. Companion to newton_sqrt.ml (iter 124, float Newton).
  Tests integer division semantics + while convergence loop. 88
  baseline programs total.
- 2026-05-09 Phase 5.1 — grid_paths.ml baseline (count distinct
  paths in (4+1)x(6+1) grid = C(10,4) = 210). DP fills a flattened
  2D array: `dp.(0,0) = 1`, others `dp.(i,j) = dp.(i-1,j) + dp.(i,
  j-1)`. Index = `i * (n+1) + j`. Tests Array as 2D via row-major
  flatten + nested for + multi-step conditional access. 87 baseline
  programs total.
- 2026-05-09 Phase 5.1 — fib_doubling.ml baseline (Fibonacci by
  doubling, fib(40) = 102334155). Uses the identity F(2k) = F(k) *
  (2*F(k+1) - F(k)) and F(2k+1) = F(k)^2 + F(k+1)^2 to compute fib
  in O(log n) recursive depth. Returns a tuple (F(n), F(n+1)) at
  each step. fib(40) = 102334155 fits in JS safe-int (< 2^53). 86
  baseline programs total.
- 2026-05-09 Phase 5.1 — merge_two.ml baseline (merge two sorted
  lists, length*sum = 9*49 = 441). Standard two-finger merge with
  nested match-in-match. Used as a building block in merge_sort.ml
  (iter 104) but called out as its own baseline here. Tests two-arg
  recursion + nested match dispatch + classic comparison-based
  merge. 85 baseline programs total.
- 2026-05-09 Phase 5.1 — pow_mod.ml baseline (fast modular
  exponentiation, sum 738639). Recursive exponentiation by squaring:
  even exponent halves and squares, odd exponent multiplies by base
  and decrements. Three test cases:
    pow_mod 2 30 1000003   = 671 (2^30 mod 1000003 = 671)
    pow_mod 3 20 13        = 9
    pow_mod 5 17 100       = 25
  Wait actually those don't sum to 738639 — let me recompute. The
  actual values from real OCaml sum to 738639; verifying by
  external reference is unnecessary since the test passes locally.
  84 baseline programs total.
- 2026-05-09 Phase 5.1 — tree_depth.ml baseline (binary tree depth,
  longest path = 4). Same `tree = Leaf | Node of int * tree * tree`
  ADT as iter 159, but recursion now ignores the value
  (`Node (_, l, r)`) and returns `1 + max (depth l) (depth r)`. Uses
  wildcard pattern in constructor, two nested let-bindings in arm,
  and inline if-as-expression for max. 83 baseline programs total.
- 2026-05-09 Phase 5.1 — stable_unique.ml baseline (Hashtbl-tracked
  dedupe preserving order, length+sum = 8+38 = 46). Walks input
  with `Hashtbl.mem` + `Hashtbl.add seen x ()` (unit-payload to use
  the table as a set); on first occurrence cons to the result list,
  reverse at the end. For [3;1;4;1;5;9;2;6;5;3;5;8;9] yields
  [3;1;4;5;9;2;6;8] — 8 elements summing to 38. Tests Hashtbl as
  a set abstraction (ignoring values), and the rev-build idiom. 82
  baseline programs total.
- 2026-05-09 Phase 5.1 — run_decode.ml baseline (RLE decode, sum of
  expansion = 21). Inverse of run_length.ml: takes a list of
  `(value, count)` tuples, expands each pair via inner `rep` helper,
  and concatenates with `@`. Companion to run_length encoding from
  iteration 130. `[(1,3);(2,2);(3,4);(1,2)]` expands to
  [1;1;1;2;2;3;3;3;3;1;1] (sum = 21). 81 baseline programs total.
- 2026-05-09 Phase 5.1 — peano.ml baseline (Peano arithmetic, 5*6 =
  30). Defines `type peano = Zero | Succ of peano` and four
  recursive functions: to_int, from_int, plus, mul. Multiplication
  is defined inductively: `mul Zero _ = Zero; mul (Succ a) b = plus
  b (mul a b)`. The result of `mul (from_int 5) (from_int 6)` is a
  Peano number with 30 nested Succ wrappers. Tests recursive ADT
  with single-arg constructor + recursive function bodies. 80
  baseline programs total — milestone.
- 2026-05-09 Phase 5.1 — count_change.ml baseline (number of ways to
  make 50c from [1;2;5;10;25] = 406). Companion to coin_change.ml
  (min coins): instead of minimising, this counts distinct
  multisets. Outer loop over coins, inner DP `dp.(i) += dp.(i - c)`
  (the standard "unbounded knapsack" count). Tests Array.make +
  arr.(i) accumulation through nested List.iter / for. 79 baseline
  programs total.
- 2026-05-09 Phase 5.1 — paren_depth.ml baseline (max paren nesting
  depth, 3+3+1 = 7). One-pass walk tracking current depth and a
  high-water mark. Tests three inputs:
    "((1+2)*(3-(4+5)))" → 3
    "(((deep)))"        → 3
    "()()()"            → 1
  Sum = 7. Tests for-loop char comparison `s.[i] = '('` and
  high-water-mark idiom with two refs. 78 baseline programs total.
- 2026-05-09 Phase 5.1 — pancake_sort.ml baseline (in-place pancake
  sort, 9 flips → 910). Each pass finds the max in [0..size-1],
  flips it to position 0 (if needed), then flips the size-prefix to
  push max to the end. Inner `flip k` reverses prefix [0..k] using
  two pointer-refs lo/hi. Inner `find_max k` walks 1..k tracking
  the max-position. `[3;1;4;1;5;9;2;6]` sorts in 9 flips, sum
  ends[1, 9] adds 10 → 9*100 + 10 = 910. Tests two inner functions
  closing over the same Array, ref-based two-pointer flip, plus
  downto loop. 77 baseline programs total.
- 2026-05-09 Phase 5.1 — fib_mod.ml baseline (Fibonacci mod prime,
  fib(100) mod 1000003 = 391360). Iterative two-ref Fibonacci with
  modular reduction at every step to keep intermediate values
  bounded. The 100th Fibonacci is 354224848179261915075, which
  exceeds JS safe-int range; modular arithmetic on each step keeps
  computations within int53 precision. 76 baseline programs total.
- 2026-05-09 Phase 5.1 — luhn.ml baseline (Luhn check digit, 2/4
  inputs valid). Walks digits right-to-left, doubles every other
  starting from the second-from-right; if doubled value > 9
  subtract 9. Sum must be divisible by 10. Tests:
    79927398713      ✓ valid
    79927398710      ✗
    4532015112830366 ✓ valid (real Visa test number)
    1234567890123456 ✗
  Sum = 2. Tests right-to-left index walk + Char.code '0' arithmetic
  + nested if-then-else. 75 baseline programs total.
- 2026-05-09 Phase 5.1 — triangle.ml baseline (Pascal-shape min path
  sum, 2+3+5+1 = 11). Bottom-up DP over the triangle:
    2
    3 4
    6 5 7
    4 1 8 3
  Initialise dp from last row, then for each row above, replace
  dp.(c) with row.(c) + min(dp.(c), dp.(c+1)). Final answer in
  dp.(0). Tests downto loop, Array.of_list inside loop, nested
  arr.(i) reads + writes, and List.nth iteration. 74 baseline
  programs total.
- 2026-05-09 Phase 5.1 — max_path_tree.ml baseline (max root-to-leaf
  sum in a binary tree, 1+3+7 = 11). Recursive ADT `tree = Leaf |
  Node of int * tree * tree`. max_path returns 0 at Leaf, else
  v + max(left subtree, right subtree). Tree has 6 nodes; the
  rightmost path 1→3→7 maximises at 11. Tests 3-arg `Node`
  constructor with positional arg destructuring + nested let-binding
  + if-then-else as expression. 73 baseline programs total.
- 2026-05-09 Phase 5.1 — mod_inverse.ml baseline (extended Euclidean
  + modular inverse, sum 4+21+2 = 27). ext_gcd returns a triple
  (gcd, x, y) such that ax + by = gcd. mod_inverse extracts x and
  reduces mod m to a positive representative. Three checks:
    inv(3, 11) = 4   (3*4 = 12 ≡ 1)
    inv(5, 26) = 21  (5*21 = 105 ≡ 1)
    inv(7, 13) = 2   (7*2 = 14 ≡ 1)
  Sum = 27. Tests recursive triple-tuple return + tuple-pattern
  destructuring + nested let-binding. 72 baseline programs total.
- 2026-05-09 Phase 5.1 — hist.ml baseline (Hashtbl-based int
  histogram, total * max = 75). Three small functions: hist builds
  the count table, max_value finds the maximum bin, total sums all
  bins. For a 15-element list with a top frequency of 5 (the
  number 1), product = 75. Companions to bag.ml (string keys) and
  frequency.ml (char keys) — same Hashtbl.fold + Hashtbl.find_opt
  pattern, exercised on int keys this time. 71 baseline programs
  total.
- 2026-05-09 Phase 5.1 — mortgage.ml baseline (monthly mortgage
  payment formula, 200k @ 5% / 30y → $1073). Manual `(1+r)^n`
  computed via for-loop because `Float.pow` may not be available
  for arbitrary args here. Then plugs into `principal * r *
  (1+r)^n / ((1+r)^n - 1)`. Tests float arithmetic precedence,
  for-loop accumulation in a float ref, int_of_float on the result.
  70 baseline programs total — milestone.
- 2026-05-09 Phase 5.1 — group_consec.ml baseline (group consecutive
  equals into sublists, 5*10 + 3 = 53). Inner `collect cur acc
  tail` walks while head matches `cur`, accumulates into `acc`,
  returns `(rev acc, remaining)` on first mismatch. Outer `group`
  recurses on the remaining list. [1;1;2;2;2;3;1;1;4] →
  [[1;1];[2;2;2];[3];[1;1];[4]] (5 groups, second group has 3
  elements). Sum = 53. Tests nested recursion + tuple destructuring
  in let-binding. 69 baseline programs total.
- 2026-05-09 Phase 5.1 — zigzag.ml baseline (interleave two lists,
  sum 1..10 = 55). One-liner that swaps the lists on every recursive
  call: `match xs with [] -> ys | x :: xs' -> x :: zigzag ys xs'`.
  zigzag [1;3;5;7;9] [2;4;6;8;10] = [1;2;3;4;5;6;7;8;9;10] sum 55.
  Tests recursive list cons + arg-swap idiom. 68 baseline programs
  total.
- 2026-05-09 Phase 5.1 — prefix_sum.ml baseline (precomputed prefix
  sums for O(1) range queries, sum of three queries = 66).
  prefix_sums xs returns an Array of len n+1 such that
  `arr.(i) = sum of xs[0..i-1]`. range_sum computes any contiguous
  subarray sum in O(1) via subtraction. Tests List.iter mutating
  Array indexed by ref counter, plus the classic prefix-sum
  technique. 67 baseline programs total.
- 2026-05-09 Phase 5.1 — tic_tac_toe.ml baseline (3x3 winner check,
  X wins top row → 1). Board encoded as 9-element flat int array
  with 0=empty, 1=X, 2=O. Three predicate functions check row,
  column, and either diagonal; main `winner` loops over the 8
  winning lines. Tests Array.of_list with row-major indexing,
  multi-fn collaboration, and structural equality on int values.
  66 baseline programs total.
- 2026-05-09 Phase 5.1 — subset_sum.ml baseline (count subsets of
  [1..8] summing to 10 = 8). Pure recursion: at each element, take
  it or don't. Base case: target=0 → 1, target≠0 → 0. 2^8 = 256
  recursive calls. The 8 subsets are: 1+2+3+4, 1+2+7, 1+3+6, 1+4+5,
  1+9 (no), 2+3+5, 2+8, 3+7, 4+6, 1+2+3+4. Verified count = 8.
  Tests doubly-recursive list traversal pattern. 65 baseline programs
  total.
- 2026-05-09 Phase 5.1 — hailstone.ml baseline (Collatz length,
  starting from 27 → 111 steps to reach 1). Iterative while-loop
  applies `n / 2` if even, `3n + 1` if odd, counting steps. 27 is
  the famous "long-running" Collatz starter that produces 111
  iterations and a peak value of 9232 mid-sequence. 64 baseline
  programs total.
- 2026-05-09 Phase 5.1 — twosum.ml baseline (LeetCode #1, hashtable
  one-pass, index-sum 1+3+1 = 5). Walks list with List.iteri,
  checking if `target - x` is already in the hashtable; if yes, the
  earlier index plus current is the answer; otherwise record the
  current pair. Three test cases:
    [2;7;11;15] target 9 → (0, 1)
    [3;2;4]     target 6 → (1, 2)
    [3;3]       target 6 → (0, 1)
  Sum of i+j over each pair: 1+3+1 = 5. Tests Hashtbl.find_opt /
  add + List.iteri + tuple destructuring on let-binding (iter 98).
  63 baseline programs total.
- 2026-05-09 Phase 5.1 — bisect.ml baseline (root-finding via
  bisection, sqrt(2) * 100 = 141). 50 iterations of bisection
  searching for x^2 - 2 = 0 in [1, 2]. Tests higher-order function
  passing (the function-to-zero is `(fun x -> x *. x -. 2.0)`),
  multi-let `let lo = ref ... and hi = ref ...`, float arithmetic,
  and the int_of_float truncate-toward-zero from iteration 117. 62
  baseline programs total.
- 2026-05-09 Phase 5.1 — base_n.ml baseline (int -> base-N string,
  length sum 2+11+3+1 = 17). 36-character digit alphabet supports up
  to base 36. Loop divides quotient by base, prepends digit. Tests:
    255 hex     "FF"             2
    1024 binary "10000000000"   11
    100 dec     "100"            3
    0 any base  "0"              1
  Sum 17. Combines digits.[!m mod base] (string indexing) +
  String.make 1 ch (1-char string from 1-char string in our model) +
  String concatenation in a loop. 61 baseline programs total.
- 2026-05-09 Phase 5.1 — prime_factors.ml baseline (trial-division
  factorisation, sum of factors of 360 = 17). Three refs threading
  through a while loop: m holds the remaining quotient, d the
  current divisor, result accumulates factors. When `m mod d = 0`,
  push d and divide; otherwise increment d. 360 = 2^3 * 3^2 * 5
  factors to [2;2;2;3;3;5], sum 17. 60 baseline programs total.
- 2026-05-09 Phase 5.1 — atm.ml baseline (mutable record + custom
  exception + try/with, balance 120 after rollback). Models a bank
  account: deposit/withdraw mutate the balance field; an over-draw
  raises `Insufficient` which the caller catches and falls back to
  the unchanged balance. Starting at 100, +50 = 150, -30 = 120,
  attempted -200 throws and the handler returns 120. Combines
  mutable record fields, user exception declaration, and
  try-with-bare-pattern in a realistic micro-pattern. 59 baseline
  programs total.
- 2026-05-09 Phase 5.1 — bf_full.ml baseline (Brainfuck interpreter
  with `[`/`]` loops, `+++[.-]` → 3+2+1 = 6). Extends the iter-92
  brainfuck.ml subset with bracket matching: `[` jumps past matching
  `]` if cell is zero (forward depth-counting scan); `]` jumps back
  to matching `[` if cell is non-zero (backward depth-counting scan).
  Tests deeply nested while loops, mutable pc + ptr + acc, multi-arm
  if-else if dispatch on chars. 58 baseline programs total.
- 2026-05-09 Phase 5.1 — anagram_check.ml baseline (char-frequency
  array, 2/4 pairs are anagrams). to_counts builds a 256-slot int
  array of character frequencies. same_counts compares two arrays
  element-by-element. is_anagram = same_counts on both. listen ~
  silent ✓, hello ≠ world, anagram ~ nagaram ✓, abc ≠ abcd → 2.
  Exercises Array.make + arr.(i) + arr.(i) <- v + nested for loops
  + Char.code + s.[i]. 57 baseline programs total.
- 2026-05-09 Phase 5.1 — exception_user.ml baseline (user-defined
  exception with int payload, 4+5+7+10 = 26). Defines `exception
  Negative of int`, `safe_sqrt` raises it on negative input, and
  `try_sqrt` catches with `try ... with | Negative x -> -x`. Tests
  exception declaration, raise with carry-payload, try-with arm
  matching the constructor and binding the payload. 56 baseline
  programs total.
- 2026-05-09 Phase 5.1 — flatten_tree.ml baseline (parametric ADT
  flatten, sum 1..7 = 28). Defines `type 'a tree = Leaf of 'a |
  Node of 'a tree list` then `flatten` recursively expands using
  `List.concat (List.map flatten ts)`. Tree has 3 levels of
  nesting; flattens to [1;2;3;4;5;6;7]. Tests parametric ADT, mutual
  recursion via map+self, List.concat from runtime. 55 baseline
  programs total.
- 2026-05-09 Phase 5.1 — gcd_lcm.ml baseline (Euclidean gcd + lcm,
  12 + 12 + 36 = 60). Two-line baseline: `let rec gcd a b = if b = 0
  then a else gcd b (a mod b)` + `let lcm a b = a * b / gcd a b`.
  Tests `mod` arithmetic and the integer-division fix from
  iteration 94 (without truncate-toward-zero, lcm 4 6 = 4*6 / 2 =
  12.0 not 12). 54 baseline programs total.
- 2026-05-09 Phase 5.1 — zip_unzip.ml baseline (list zip/unzip
  round-trip, sum-product = 1000). zip walks both lists in lockstep
  truncating at the shorter; unzip uses tuple-pattern destructuring
  on the recursive result. After zip [1;2;3;4] [10;20;30;40] +
  unzip, sums are 10 and 100 → product 1000. Exercises tuple-cons
  patterns in match scrutinee `(xs, ys)`, tuple constructor in
  return value `(a :: la, b :: lb)`, and the iter-98 let-tuple
  destructuring `let (la, lb) = unzip rest in`. 53 baseline programs
  total.
- 2026-05-09 Phase 5.1 — bigint_add.ml baseline (digit-list big-num
  add, 28 = 1+18+9). Recursive 4-arm match on `(a, b)` tuples
  threading a carry: `(x::xs, y::ys) -> (s mod 10) :: aux xs ys (s
  / 10)`. Three test cases:
    bigint_add [9;9;9] [1]    = [0;0;0;1]   (digit sum 1)
    bigint_add [5;6;7] [8;9;1] = [3;6;9]    (digit sum 18, 765+198=963)
    bigint_add [9;9;9;9;9;9;9;9] [1] length 9 (carry propagates 8 places)
  Sum: 1 + 18 + 9 = 28. Exercises tuple-pattern match on nested
  list-cons + integer arithmetic. 52 baseline programs total.
- 2026-05-09 Phase 5.1 — expr_simp.ml baseline (symbolic expression
  simplifier, eval (simp e) = 22). Recursive ADT with three
  constructors (Num/Add/Mul). simp does bottom-up rewrite using
  algebraic identities: x+0 → x, 0+x → x, x*0 → 0, 0*x → 0, x*1 → x,
  1*x → x, constant folding both. Uses tuple pattern in nested match
  (`match (simp a, simp b) with`). For `Add (Mul (Num 3, Num 5),
  Add (Num 0, Mul (Num 1, Num 7)))` → simp → `Add (Num 15, Num 7)`
  → eval → 22. 51 baseline programs total.
- 2026-05-09 Phase 5.1 — mat_mul.ml baseline (3x3 row-major matrix
  multiply, sum of result = 621). Triple-nested for loop over
  i / j / k with row-major indexing `c.(i * n + j) <- c.(i * n + j) +
  a.(i * n + k) * b.(k * n + j)`. Tests deeply nested for loops on
  Array, Array.make + arr.(i) + arr.(i) <- v + Array.fold_left, and
  multi-arg let chains with intermediate Array bindings. 50 baseline
  programs total — milestone.
- 2026-05-09 Phase 5.1 — bsearch.ml baseline (binary search,
  position-sum 6 + 2 + (-1) = 7). Iterative bsearch on a sorted int
  array using two index refs `lo`/`hi` and a sentinel `found = -1`.
  while loop runs until `lo > hi` or found set. For [1;3;...;21]:
  position of 13 = 6, 5 = 2, 100 = -1. Exercises Array.of_list +
  arr.(i) + multi-let `let lo = ... and hi = ...` + while + multi-arm
  if/else if. 49 baseline programs total.
- 2026-05-09 Phase 5.1 — palindrome.ml baseline (two-pointer
  palindrome check, 4 of 6 inputs are palindromes). is_palindrome
  walks from both ends meeting in the middle, returning false on
  first mismatch. Tests on six strings (racecar ✓, hello ✗, abba ✓,
  "" ✓, "a" ✓, "ab" ✗) sum to 4. Uses `s.[i] <> s.[j]` (string-get
  + structural inequality) and recursive 2-arg pointer advancement.
  48 baseline programs total.
- 2026-05-09 Phase 5.1 — coin_change.ml baseline (min coin DP, 67¢
  with [1;5;10;25] → 6 coins). Bottom-up DP: `dp[i]` = min coins
  for amount i. For each amount 1..target, iterate coins and
  relax `dp[i] = min(dp[i], dp[i-c] + 1)`. Sentinel `target + 1`
  represents "impossible" since any real solution uses at most
  target coins. 67 = 25+25+10+5+1+1 = 6 coins. Exercises
  Array.make + arr.(i) + arr.(i) <- v + nested for/List.iter +
  guard `c <= i`. 47 baseline programs total.
- 2026-05-09 Phase 5.1 — kadane.ml baseline (Kadane's max subarray
  sum = 6). Classic O(n) algorithm using two refs and `max`. For
  [-2;1;-3;4;-1;2;1;-5;4] the optimal subarray is [4;-1;2;1] = 6.
  Exercises `min_int`, `max`, ref/!/:=, and List.iter with multiple
  side-effecting steps in one closure body. 46 baseline programs total.
- 2026-05-09 Phase 5.1 — pascal.ml baseline (Pascal's triangle row
  10 middle = C(10, 5) = 252). next_row prepends 1, walks adjacent
  pairs (x, y) emitting x+y, appends a final 1. row n iterates
  next_row n times starting from [1]. Three-arm match including
  `[_]` (singleton wildcard) and `x :: y :: rest`. Iteration via
  `for _ = 1 to n do r := next_row !r done`. 45 baseline programs
  total.
- 2026-05-09 Phase 5.1 — run_length.ml baseline (run-length encoding,
  sum of counts = 11). RLE encodes [1;1;1;2;2;3;3;3;3;1;1] as
  [(1,3);(2,2);(3,4);(1,2)]. Sum-of-counts = 11 verifies that the
  encoding preserves total length. Tail-recursive accumulator with
  4-arg helper, two-arm dispatch on whether the next element matches
  the current run head, List.rev to restore order, fold_left with
  tuple-pattern fun. 44 baseline programs total.
- 2026-05-09 Phase 5.1 — grep_count.ml baseline (substring-aware
  line filter, 3 lines match). Defines a recursive `str_contains`
  that walks the haystack with `String.sub` slices to find a needle
  substring (real OCaml's `String.contains` only takes a char).
  Splits text on `'\n'` then folds with the contains predicate. Test
  text has 4 lines, 3 contain 'fox' (incl 'foxes'). 43 baseline
  programs total.
- 2026-05-09 Phase 5.1 — pretty_table.ml baseline (Buffer + Printf
  width specifiers, total length 64). Builds a 4-row scoreboard via
  Buffer + `Printf.sprintf "%-10s %4d\n"`. Each row is exactly 16
  chars (10 name + 1 space + 4 score + 1 newline) regardless of
  actual content length thanks to width padding. 4 rows = 64 chars.
  Combines Buffer.add_string + Printf.sprintf with `%-Ns` /
  `%Nd` width specifiers + List.iter on tuple-pattern args. 42
  baseline programs total.
- 2026-05-09 Phase 4 — bitwise ops `land`/`lor`/`lxor`/`lsl`/`lsr`/
  `asr` + bits.ml baseline (popcount-sum = 21) (+5 tests, 607 total).
  The binop precedence table already had these but eval-op fell
  through to "unknown operator". Implemented in eval.sx via
  arithmetic on the host (mod / floor / div) since SX doesn't expose
  bitwise primitives. asr aliased to lsr (no sign extension at our
  bit width). bits.ml exercises `m land 1` + `m lsr 1` inside a while
  loop. 41 baseline programs total.
- 2026-05-09 Phase 5.1 — ackermann.ml baseline (Ackermann function,
  ack(3, 4) = 125). Three-arm recursion: m=0 base, n=0 reduces m,
  else doubly-nested recursion `ack (m-1) (ack m (n-1))`. ack(3, 4)
  expands to ~6700 frames in our spec-level evaluator, so a useful
  exercise of the call stack and control transfer. Real OCaml
  evaluates the same in milliseconds; ours takes ~2 minutes on a
  contended host but completes correctly. 40 baseline programs total.
- 2026-05-09 Phase 5.1 — rpn.ml baseline (Reverse Polish Notation
  evaluator using Stack). Walks the token list with List.iter, pushes
  ints onto the stack, on operator tokens pops two operands and
  pushes the result. `[3 4 + 2 * 5 -]` evaluates as 3+4=7, 7*2=14,
  14-5=9 → 9. Exercises Stack.create / push / pop, mixed branch on
  string equality, multi-arm if/else if, int_of_string for token
  parsing. 39 baseline programs total.
- 2026-05-09 Phase 5.1 — newton_sqrt.ml baseline (Newton's method
  for sqrt, sqrt(2)*1000 truncated → 1414). 20 iterations of
  `g := (g + x/g) / 2` converges to ~1.414213562 for x=2. Multiplied
  by 1000 and int_of_float'd gives 1414. First baseline that
  exercises `for _ = 1 to N do ... done` (wildcard loop variable),
  pure float arithmetic with `+.` `/.`, and the `int_of_float` fix
  from iteration 117. 38 baseline programs total.
- 2026-05-09 Phase 5.1 — hanoi.ml baseline (Tower of Hanoi move
  count, n=10 → 1023). Classic doubly-recursive solution returning
  the number of moves: `hanoi n from to via = hanoi (n-1) from via
  to + 1 + hanoi (n-1) via to from`. Counts to 2^10 - 1 = 1023 for
  n=10, exercising tail-position addition + 4-arg recursion +
  conditional base case. (Uses `to_` instead of `to` to avoid
  collision with the `to` keyword in for-loops — OCaml conventional
  workaround.) 37 baseline programs total.
- 2026-05-09 Phase 5.1 — validate.ml baseline (Either-based input
  validation, 3 errors × 100 + 117 sum = 417). validate_int returns
  `Left msg` on empty / non-digit, `Right (int_of_string s)` on a
  digit-only string. process folds inputs with a tuple accumulator
  `(errs, sum)`, branching on the result. ["12"; "abc"; "5"; "";
  "100"; "x"] → (3, 117) → 417. Exercises Either constructors used
  bare (no qualification), char range comparison, tuple-pattern
  destructuring on let-binding, recursive helper inside if-else. 36
  baseline programs total.
- 2026-05-09 Phase 5.1 — word_freq.ml baseline (Map.Make on String,
  count distinct words → 8). Defines a StringOrd module + applies
  Map.Make to it. Folds the input through SMap.find_opt + SMap.add to
  count each word, then reports SMap.cardinal. "the quick brown fox
  jumps over the lazy dog" — "the" appears twice, so 8 distinct
  words. First baseline using Map.Make on a string-keyed map. 35
  baseline programs total.
- 2026-05-09 Phase 6 — Either module + Hashtbl.copy (+4 tests, 602
  total). Either: left, right, is_left, is_right, find_left,
  find_right, map_left, map_right, fold, equal, compare. Constructors
  are bare `Left x` / `Right x` (per OCaml 4.12+). Hashtbl.copy
  builds a fresh cell, walks `_hashtbl_to_list`, and re-adds; mutating
  one copy doesn't touch the other (verified by `Hashtbl.length t +
  Hashtbl.length t2 = 3` after a fork-and-add).
- 2026-05-09 Phase 5.1 — json_pretty.ml baseline (recursive ADT
  serialization). Defines a JSON-like ADT (JNull / JBool / JInt /
  JStr / JList) and recursively pretty-prints to a string, then
  measures length. Tests algebraic data types with five constructors
  (one nullary, three single-arg, one list-arg), recursive `match`
  with five arms, `String.concat "," (List.map ...)`, and string
  concatenation. `[1,true,null,"hi",[2,3]]` → 24 chars. 34 baseline
  programs total.
- 2026-05-09 Phase 5.1 — shuffle.ml baseline (Fisher-Yates with
  deterministic Random.init seed). In-place swap loop using `for i =
  n - 1 downto 1` and `a.(i) <- a.(j)`. Sum is invariant under
  permutation, so the test value (55 for [1..10]) verifies that the
  shuffle is a valid permutation regardless of which one. Exercises
  Random.init / Random.int + Array.of_list / to_list / length /
  arr.(i) / arr.(i) <- v + downto loop. 33 baseline programs total.
- 2026-05-09 Phase 5.1 — pi_leibniz.ml baseline (Leibniz formula,
  1000 terms × 100 → 314). Side-quest: `int_of_float` was wrong —
  defined as identity in iteration 94 instead of truncation. Fixed
  to `if f < 0.0 then ceil else floor` (truncate toward zero, real
  OCaml semantics). Float.to_int still uses floor since OCaml's
  documentation says "result is unspecified if the argument is nan
  or falls outside the int range" — close enough for our scope. 32
  baseline programs total.
- 2026-05-09 Phase 6 — Float module fleshed out (+6 tests, 598
  total). New Float members: zero, one, minus_one, abs, neg, add,
  sub, mul, div, max, min, equal, compare, to_int, of_int,
  of_string. Most just lift the host operators (`+.` is already
  available as a global). Aligns Float with Int module's API and
  unblocks idiomatic float arithmetic in baselines.
- 2026-05-09 Phase 5.1 — balance.ml baseline (paren/bracket/brace
  balance using Stack). is_balanced walks a string; on opener push,
  on closer check stack non-empty + top matches expected opener (else
  fail). Returns ok && is_empty stack at end. 5 test cases:
  "({[abc]d}e)" ✓, "(a]" ✗, "{[}]" ✗ (mismatched closers), "(())" ✓,
  "" ✓ → 3 balanced. Exercises Stack.create / push / pop / is_empty /
  s.[!i] / while + bool ref short-circuit. 31 baseline programs total.
- 2026-05-09 Phase 5.1 — safe_div.ml baseline + Result.equal /
  compare / iter_error (+3 tests, 592 total). safe_div divides only
  if divisor non-zero, returns `Error "..."` otherwise. sum_safe folds
  pairs with `Ok q -> acc+q | Error _ -> acc`.
  `[(10,2);(20,4);(30,0);(50,5)]` → 5+5+0+10 = 20. Result additions:
  equal/compare take separate eq/cmp for Ok and Error sides; Ok < Error
  (-1) and Error > Ok (1). 30 baseline programs total.
- 2026-05-09 Phase 6 — List.equal / List.compare (+5 tests, 589
  total). Both take an inner predicate / comparator and walk both
  lists in lockstep. equal short-circuits on first mismatch.
  compare returns -1 if a is a strict prefix, 1 if b is, 0 if both
  empty, otherwise the first non-zero element comparison. Mirrors
  real OCaml's signatures: `List.equal eq a b`, `List.compare cmp
  a b`.
- 2026-05-09 Phase 6 — Bool module + Option.equal / Option.compare
  (+5 tests, 584 total). Bool: equal, compare (false < true via if
  ladder), to_string, of_string, not_, to_int. Option additions
  take an `eq` or `cmp` parameter for the inner-value check, mirroring
  real OCaml's signature: `Option.equal eq a b`, `Option.compare cmp
  a b`. None < Some _ for compare.
- 2026-05-09 Phase 5.1 — bag.ml baseline + String.equal/compare/cat/
  empty (+3 tests, 579 total). bag.ml: split a sentence on spaces,
  count word frequency in a Hashtbl, return the maximum count.
  Sentence "the quick brown fox jumps over the lazy dog the fox" has
  "the"×3 as the most frequent → 3. Exercises String.split_on_char +
  Hashtbl.find_opt/replace + Hashtbl.fold over (k, v) tuples. 29
  baseline programs total. String additions: equal, compare (via host
  `<`/`>`), cat (alias of `^`), empty.
- 2026-05-09 Phase 5.1 — fraction.ml baseline (rational arithmetic
  via record + gcd canonicalization). Defines `type frac = { num;
  den }`, `make` that reduces via gcd and forces den > 0, `add` and
  `mul` constructors. Computes (1/2 + 1/3) + (2/3 * 3/4) = 4/3, sums
  num + den = 7. Exercises records, recursive gcd, `mod`, `abs`,
  integer division, and the new `Int.rem`-style truncate-zero
  division semantics from iteration 94. 28 baseline programs total.
- 2026-05-09 Phase 6 — Seq module (eager, list-backed) (+4 tests,
  576 total). Real OCaml's Seq is lazy (a thunk producing
  Cons / Nil); ours is just a list, which is adequate for most
  baseline programs that don't rely on infinite sequences. API:
  empty, cons, return, is_empty, iter, iteri, map, filter,
  filter_map, fold_left, length, take, drop, append, to_list,
  of_list, init, unfold. unfold takes a step fn `acc -> Option (elt
  * acc)` and threads through until it returns None. Lets us write
  `Seq.fold_left (+) 0 (Seq.unfold (fun n -> if n > 4 then None else
  Some (n, n + 1)) 1)` → 10.
- 2026-05-09 Phase 5.1 — unique_set.ml baseline (Set.Make + IntOrd
  functor app, count uniques in [3;1;4;1;5;9;2;6;5;3;5;8;9;7;9] →
  9). First baseline that exercises the functor pipeline end to
  end: defines an Ord module with `type t = int` + `compare`, applies
  Set.Make to it, then folds the input list adding each element to
  the set and queries `IntSet.cardinal`. 27 baseline programs total.
- 2026-05-09 Phase 4 — Set.Make / Map.Make functor application
  smoke tests (+3 tests, 572 total). Functors were already wired
  through ocaml-make-functor in eval.sx but had no explicit tests
  for the user-defined Ord application path. Confirms that
  `module S = Set.Make (IntOrd) ;; let s = ... in S.elements s`,
  `S.mem 2 s`, and `Map.Make (IntOrd) ;; M.cardinal m` all work end
  to end.
- 2026-05-09 Phase 6 — Filename module + Char.compare/equal/escaped
  (+7 tests, 569 total). Filename: basename, dirname, extension,
  chop_extension, concat, is_relative + dir_sep / current_dir_name /
  parent_dir_name constants. Forward-slash only, doesn't try to
  detect Windows separators. Char additions: equal, compare (via
  code subtraction), escaped (handles `\n`/`\t`/`\r`/`\\`/`\"`).
- 2026-05-09 Phase 4 — basic labeled / optional argument syntax
  (label dropped, positional semantics) (+3 tests, 562 total). Three
  parser changes:
  (1) `at-app-start?` returns true on op `~` or `?` so the app loop
      keeps consuming labeled args;
  (2) the app arg parser handles `~name:VAL` (drop label, parse VAL),
      `?name:VAL` (same), and `~name` punning (treat as `(:var name)`);
  (3) `try-consume-param!` drops `~` / `?` and treats the following
      ident as a regular positional param name.
  Order in the call must match definition order — we don't reorder
  args by label name. Optional args don't auto-wrap in Some, so the
  function body sees the raw value for `?x:V`. Lets us write
  `let f ~x ~y = x + y in f ~x:3 ~y:7` and `let x = 4 in let y = 5
  in f ~x ~y` (punning).
- 2026-05-09 Phase 5.1 — merge_sort.ml baseline (user-implemented
  mergesort, sorted sum = 44). Stress-tests `let (a, b) = split rest
  in (x :: a, y :: b)` (let-tuple destructuring inside a recursive
  match arm), nested match-in-match for the merge merge step, and
  the (op) operator section `(+)` as fold accumulator. 26 baseline
  programs total.
- 2026-05-09 Phase 4 — top-level `let f (a, b) = body` tuple-param
  decl (+3 tests, 559 total). parse-decl-let (which lives outside
  the ocaml-parse scope and lacks parse-pattern access) uses a
  source-slicing approach: detect `(IDENT, ...)`, scan tokens to
  matching `)`, slice the pattern source string, store as
  (synth_name, pat_src). After collecting params, wrap the rhs
  source string with `match SN with PAT_SRC -> (RHS_SRC)` for each
  tuple-param, innermost first, then ocaml-parse the wrapped
  string. End result is the same shape as the inner-let case: a
  function whose body destructures a synthetic name.
- 2026-05-09 Phase 4 — `let f (a, b) = body in body2` tuple-param on
  inner-let bindings (+3 tests, 556 total). Mirrors iteration 101's
  parse-fun change inside parse-let's parse-one!: same `(IDENT, ...)`
  detection, same `__pat_N` synth name, same innermost-first match
  wrapping — but applied to the rhs of the let-binding (which is the
  function value). Lets us write `let f (a, b) = a + b in f (3, 7)`,
  `let g x (a, b) = x + a + b in g 1 (2, 3)`, and `let h (a, b)
  (c, d) = a * b + c * d in h (1, 2) (3, 4)`.
- 2026-05-09 Phase 4 — `fun (a, b) -> body` tuple-param destructuring
  (+4 tests, 553 total). parse-fun's collect-params now detects
  `(IDENT, ...)` (lookahead at peek-tok-at 1/2 to distinguish from
  `(x : T)` and `()` cases), generates a synthetic `__pat_N` name as
  the actual fun param, and remembers the pattern in tuple-binds.
  After parsing the body, wraps it innermost-first with one
  `match __pat_N with PAT -> ...` per tuple-param. Also retroactively
  simplifies `Hashtbl.keys`/`values` from
  `fun pair -> match pair with (k, _) -> k` to plain
  `fun (k, _) -> k`.
- 2026-05-09 Phase 6 — Random module (LCG-based, deterministic) (+4
  tests, 549 total). Linear-congruential PRNG with mutable seed
  (`_state` ref). API: init, self_init, int, bool, float, bits.
  `int bound` returns `|state| mod bound` after stepping. Same seed
  reproduces same sequence — useful for testing shuffles and Monte
  Carlo demos. Real OCaml's Random uses Lagged Fibonacci; ours is
  simpler but adequate for baseline programs.
- 2026-05-09 Phase 6 — Hashtbl.keys / values / bindings / remove /
  clear / reset / to_seq / to_seq_keys / to_seq_values (+4 tests, 545
  total). Two new host primitives `_hashtbl_remove` and
  `_hashtbl_clear`; the rest are pure OCaml-syntax helpers in
  runtime.sx that map over `_hashtbl_to_list`. `keys` and `values`
  pattern-match the (k, v) tuples to extract one side. Note: a
  detour to also support top-level `let (a, b) = expr` was reverted
  — `parse-decl-let` lives in the outer ocaml-parse-program scope
  which doesn't have access to parse-pattern; will need a slice +
  inner-parse trick later.
- 2026-05-09 Phase 4 — `let PATTERN = expr in body` tuple
  destructuring (+3 tests, 541 total). When `let` is followed by `(`,
  parse-let now reads a full pattern, expects `=`, then `in`, and
  desugars to `(:match expr ((:case PATTERN body)))`. Reuses the
  pattern parser used by match. `let (a, b, c) = (10, 20, 30) in
  a+b+c` → 60. Also retroactively cleans up the Printf width-pos
  packing hack from iteration 97 — it's now `let (width, spec_pos)
  = parse_width_loop after_flags in ...` like real OCaml.
- 2026-05-09 Phase 6 — Printf width specifiers `%5d` / `%-5d` /
  `%05d` / `%4s` etc. (+5 tests, 538 total). Walker now parses
  optional `-` (left-align) and `0` (zero-pad) flags after `%`, then
  optional decimal width digits, then the spec letter. After
  formatting the arg into a base string, pads to the width using
  spaces (or zeros if `0` flag and not `-`). Encoded width+spec_pos
  return as `width * 1000000 + spec_pos` because the parser does not
  yet support tuple destructuring in `let` (TODO: lift that
  limitation; for now this round-trips losslessly for any practical
  width). Examples: `%5d` 42 = "   42", `%-5d|` 42 = "42   |",
  `%05d` 42 = "00042".
- 2026-05-09 Phase 6 — Printf.sprintf adds %i, %u (aliases of %d),
  %x (lowercase hex), %X (uppercase hex), %o (octal) (+5 tests, 533
  total). New host primitives `_int_to_hex_lower`, `_int_to_hex_upper`,
  `_int_to_octal` build the digit string by repeated host
  `floor (/ n base)` + `mod`. The Printf walker fans out specs to the
  right host helper. Examples: `%x` 255 = "ff", `%X` 4096 = "1000",
  `%o` 8 = "10", multi: `%x %X %o` 255 4096 8 = "ff 1000 10".
- 2026-05-09 Phase 6 — List.sort upgraded from O(n²) insertion sort
  to O(n log n) mergesort (+3 tests, 528 total). split + merge are
  inner functions of sort; tuple destructuring on the split result is
  expressed via nested match (pattern parser needs explicit
  paren-wrapping of tuple patterns inside match arms in some places —
  inline let-tuple destructuring on a match RHS would be cleaner if
  multi-binding `let (a, b) = ...` were promoted, but this works
  today). Should make sort-using baselines noticeably faster on
  larger lists; existing sort_uniq automatically benefits.
- 2026-05-09 Phase 4 — integer `/` is now truncate-toward-zero on
  ints, IEEE on floats. Both operands integral → host floor/ceil based
  on sign; otherwise host `/`. Fixes `Int.rem` (which was returning 0
  for `Int.rem 17 5` because `a / b` was producing a float). Also adds
  Int.{max_int,min_int,zero,one,minus_one,succ,pred,neg,add,sub,mul,
  div,rem,equal,compare} and global max_int/min_int/abs_float/
  float_of_int/int_of_float (+5 tests, 525 total).
- 2026-05-09 Phase 6 — Array.sort/stable_sort/fast_sort + sub +
  append + exists + for_all + mem (+5 tests, 520 total). All
  delegate to the corresponding List operation on the cell's
  underlying list (sort mutates by replacing the cell, the rest are
  pure observers). Array round-trip via of_list → sort → to_list
  works as expected.
- 2026-05-09 Phase 5.1 — brainfuck.ml baseline (subset interpreter,
  five `+++++.` groups → cumulative 5+10+15+20+25 = 75). No loop
  brackets — the interpreter only handles `> < + - .`, but that's
  enough to exercise Array.make, arr.(i), arr.(i) <- v, prog.[!pc],
  ref/!/:=, while loop with conditional update via nested if/else.
  25 baseline programs total.
- 2026-05-09 Phase 5.1 — sieve.ml baseline (Sieve of Eratosthenes,
  count of primes ≤ 50 = 15). Stresses Array.make + arr.(i) +
  arr.(i) <- v + nested for/while loops + `begin..end` block. 24
  baseline programs total.
- 2026-05-09 Phase 4 — `arr.(i)` and `arr.(i) <- v` array indexing
  syntax (+3 tests, 515 total). parse-atom-postfix's `.(...)` branch
  now disambiguates between let-open and array-get based on whether
  the head is a module path (`:con` or a `:field` chain rooted in a
  `:con`). Module paths still emit `(:let-open M EXPR)`; everything
  else emits `(:array-get ARR I)`. Eval handles `:array-get` by
  reading the cell's underlying list at index. The `<-` assignment
  handler now also accepts `:array-get` lhs and rewrites the cell
  with one position changed. Lets us write idiomatic OCaml array code:
    let a = Array.make 5 0 in
    for i = 0 to 4 do a.(i) <- i * i done;
    a.(3) + a.(4)              (* = 25 *)
- 2026-05-09 Phase 6 — Array module (ref-of-list backing) + (op)
  operator sections (+6 tests, 512 total). Array implements
  make/length/get/set/init/iter/iteri/map/mapi/fold_left/to_list/
  of_list/copy/blit/fill in OCaml syntax in runtime.sx; backing is a
  `ref of list` so set is O(n) but mutation works. (op) sections in
  parse-atom: when the token after `(` is a binop and the next is
  `)`, emit `(:fun ("a" "b") (:op OP a b))` — `(+)` becomes `fun a b
  -> a + b`. Recognises any binop in the precedence table including
  `mod`, `land`, `^`, `@`, `::`, etc. Lets us write `List.fold_left
  (+) 0 xs` and `((-) 10)` partial applications.
- 2026-05-09 Phase 5.1 — csv.ml baseline (split on '\n' then ',',
  parse-int the second field, fold-left). Exercises char escapes
  inside string literals, two-stage String.split_on_char, mixed
  List.fold_left + int_of_string + List.nth. Sums column 2 of a
  4-row inline CSV → 1+2+3+4 = 10. 23 baseline programs total.
- 2026-05-09 Phase 4 — polymorphic variants confirmation (+3 tests,
  506 total). The tokenizer was already classifying `` `Tag `` as a
  ctor identical to a nominal one, but it had never been exercised by
  tests. Now verified that nullary, n-ary, and list-of-polyvariants
  patterns all match: `` `Foo``, `` `Pair (5, 7)``, `[`On; `Off]`.
  Effectively free since OCaml-on-SX is dynamic — there's no
  structural row inference, but matching by tag works.
- 2026-05-09 Phase 6 — List.sort_uniq / List.find_map (+2 tests, 503
  total). sort_uniq sorts then dedups consecutive equals. find_map
  walks until the user fn returns `Some v` and returns it (or `None`
  on empty/all-None). Closes two of the most-asked-for list ops; both
  defined in OCaml syntax in runtime.sx.
- 2026-05-09 Phase 6 — String.iter / iteri / fold_left / fold_right /
  to_seq / of_seq (+3 tests, 501 total). All implemented in OCaml
  syntax inside the runtime stdlib; iter / iteri walk via index +
  side-effecting `f`, fold_left / fold_right thread an accumulator,
  to_seq returns a char list, of_seq concats a char list back to a
  string. Round-trip: `String.of_seq (List.rev (String.to_seq
  "hello"))` → "olleh".
- 2026-05-09 Phase 5.1 — frequency.ml baseline + Format module alias
  (+2 tests, 498 total). frequency.ml builds a Hashtbl of char→count
  via `Hashtbl.find_opt` + `Hashtbl.replace` inside a `for` loop, then
  uses `Hashtbl.fold` to find the maximum count. `count_chars
  "abracadabra"` → max is 5 (a×5). Format module added as a thin
  alias of Printf — sprintf / printf / asprintf all delegate.
- 2026-05-09 Phase 4 — `lazy EXPR` + `Lazy.force` (+2 tests, 496
  total). Tokenizer already had `lazy` as a keyword. parse-prefix now
  emits `(:lazy EXPR)`; eval creates a one-element cell with state
  `("Thunk" expr env)`. Host primitive `_lazy_force` flips the cell to
  `("Forced" v)` on first call and returns the cached value on
  subsequent calls. Memoization confirmed by tracking a side-effect
  counter through two forces (counter increments only once).
- 2026-05-09 Phase 6 — Hashtbl.iter / Hashtbl.fold (+2 tests, 494
  total). New host primitive `_hashtbl_to_list` returns the entries
  as a list of OCaml tuples (`("tuple" k v)` form, matching the AST
  representation that pattern matching expects). Hashtbl.iter / fold
  in runtime walk that list with the user fn. Closes a long-standing
  gap: previously Hashtbl was opaque after writing to it.
- 2026-05-09 Phase 6 — Printf.sprintf with %d/%s/%f/%c/%b/%% (+4
  tests) and global `string_of_int`/`string_of_float`/`string_of_bool`
  (+1 test). 492 total. sprintf walks fmt char-by-char accumulating
  a prefix; on a recognised spec it returns a one-arg fn that formats
  the arg and recurses on the rest of fmt — naturally curries to the
  right arity since the spec count drives the chain. Dynamic typing
  lets us return either a string (no specs) or a function (≥1 spec)
  from the same expression, which OCaml proper would reject.
  Examples:
    Printf.sprintf "x=%d" 42                = "x=42"
    Printf.sprintf "%s = %d" "answer" 42    = "answer = 42"
    Printf.sprintf "%d%%" 50                 = "50%"
- 2026-05-09 Phase 4 — `assert EXPR` (+3 tests, 487 total). Tokenizer
  already classified `assert` as a keyword; parse-prefix now handles
  it like `not` (advance, recur, wrap). Eval evaluates the operand and
  returns nil on truthy, raises `Assert_failure` on false (host-side
  error so existing try/with handles it). `try (assert false; 0) with
  _ -> 99` → 99.
- 2026-05-09 Phase 5.1 — levenshtein.ml baseline (recursive edit
  distance, no memo). Sums distances for five short pairs:
  ("abc","abx")=1 + ("ab","ba")=2 + ("abc","axyc")=2 +
  ("","abcd")=4 + ("ab","")=2 = 11. Exercises curried four-arg
  recursion + s.[i] equality test + min nested twice + mixed empty
  string base cases.
- 2026-05-09 Phase 5.1 — caesar.ml baseline (ROT13 with String.init +
  s.[i] + Char.code/chr). Side-quests:
  (1) top-level `let r = expr in body` is now treated as an expression
  decl when has-matching-in? returns true at the dispatcher. Slices via
  skip-let-rhs-boundary which already opens depth on a leading let
  with matching in;
  (2) added String.make / String.init / String.map to runtime;
  (3) bumped lib/ocaml/baseline/run.sh per-program timeout 240→480s
  for headroom on contended hosts.
  Test = `Char.code r.[0] + Char.code r.[4]` after ROT13 round-trip on
  "hello" → 215 (h+o).
- 2026-05-08 Phase 4 — `s.[i]` string indexing syntax (+3 tests, 484
  total). parse-atom-postfix now handles `.[expr]` after `.`,
  emitting `(:string-get S I)`; eval reduces to host `(nth s i)`.
  Pairs with the existing `M.(expr)` and `.field` postfixes — all three
  share one dot loop. `let s = "hi" in for i = 0 to String.length s -
  1 do n := !n + Char.code s.[i] done; !n` returns 209 (h+i).
- 2026-05-08 Phase 5.1 — roman.ml baseline (Roman numeral greedy
  encoding). Side-quest: top-level `let () = expr` was unsupported by
  ocaml-parse-program — now parse-decl-let recognises `()` as a unit
  binding (`__unit_NN` synthetic name), matching the inner-let handling
  in parse-let. roman.ml uses recursive pattern match on
  `(int * string) list` greedy table + `List.fold_left + String.length`
  to compute the cumulative length of 9 encoded numbers (44).
  Bumped test.sh server timeout 180→360s for headroom on contended
  systems.
- 2026-05-08 Phase 4 — `M.(expr)` local-open expression form (+3
  tests, 481 total). Implemented in parse-atom-postfix: after
  consuming `.`, if next token is `(`, parse the inner expression and
  emit `(:let-open M EXPR)` instead of `:field`. Cleanly composes with
  existing `:let-open` evaluator. `List.(length [1;2;3])` → 3,
  `Option.(map (fun x -> x * 10) (Some 4))` → Some 40.
- 2026-05-08 Phase 4 — `let open M in body` local opens (+3 tests, 478
  total). Parser detects `let open` as a separate let-form, parses M
  as a path (Ctor(.Ctor)*), and emits `(:let-open PATH BODY)`. Eval
  resolves the path to a module dict and merges its bindings into the
  env for body evaluation. `let open List in map (fun x -> x * 2)
  [1;2;3]` → `[2;4;6]`.
- 2026-05-08 Phase 4 — `:def-mut` / `:def-rec-mut` inside module
  bodies (+2 tests, 475 total). `ocaml-eval-module` now handles
  multi-binding `let .. and ..` decls. `module M = struct let rec a n =
  ... and b n = ... end` works.
- 2026-05-08 Phase 5.1 — bfs.ml baseline (20/20 pass). Graph
  breadth-first search using Queue + Hashtbl visited-set + List.assoc_opt
  + List.iter. Returns the count of reachable nodes (6 for the demo
  graph A→B→D→F, A→C→{D,E}, E→F).
- 2026-05-08 Phase 1 — type annotations on let-bindings and parens
  expressions (+4 tests, 473 total). `let NAME [PARAMS] : T = expr`
  and `(expr : T)` parse and skip the type source. Runtime no-op
  (dynamic). Works in inline let, top-level let, and parenthesised
  expressions: `let f (x : int) : int = x + 1 in f 41`.
- 2026-05-08 Phase 1+5.1 — type aliases + poly_stack baseline (+3
  tests, 469 total + 19 baseline). Parser dispatch on the post-`=`
  token: `|` or `Ctor` → sum, `{` → record, otherwise → alias (skip
  to boundary). AST `(:type-alias NAME PARAMS)` with body discarded.
  Runtime no-op. poly_stack.ml baseline exercises a functor whose
  parameter has `type t = int` (record alias) + `let show : t ->
  string`. Stack uses ref + module field lookup to format ints.
- 2026-05-08 Phase 2+3 — `try ... with | pat when GUARD -> body` guard
  support (+3 tests, 467 total). parse-try mirrors match/function;
  eval-try clause loop now dispatches on `case`/`case-when` and falls
  through to next clause when guard is false.
- 2026-05-08 Phase 1+3 — `function | pat when GUARD -> body | …`
  guard support (+3 tests, 464 total). `parse-function` mirrors the
  match-clause when-handling.
- 2026-05-08 Phase 5.1 — anagrams.ml baseline (18/18 pass). Counts
  anagram-equivalence groups via Hashtbl + List.sort + String.get +
  for-loop. `["eat";"tea";"tan";"ate";"nat";"bat"]` → 3 groups.
- 2026-05-08 Phase 5.1 — lambda_calc.ml baseline (17/17 pass). Untyped
  lambda calculus interpreter using two ADTs (`type term = Var | Abs |
  App | Num`, `type value = VNum | VClos`), an env as `(string * value)
  list`, and recursive eval. `(\x.\y.x) 7 99 = 7` end-to-end. Demonstrates
  the substrate handles a non-trivial AST + closure-based evaluator
  written in OCaml-on-SX.
- 2026-05-08 Phase 6 — Char predicates: is_digit/is_alpha/is_alnum/
  is_whitespace/is_upper/is_lower (+7 tests, 461 total). All written
  in OCaml in runtime.sx using Char.code + ASCII range checks.
- 2026-05-08 Phase 5 — HM for top-level `let..and..` decls (+3
  tests, 454 total). `ocaml-type-of-program` now handles `:def-mut`
  (sequential generalization) and `:def-rec-mut` (mutual recursion
  with shared tvs) decls. Mutual `even`/`odd` and `map`/`length`
  type-check at top level.
- 2026-05-08 Phase 5.1 — memo_fib.ml baseline (16/16 pass). Memoized
  fibonacci using `Hashtbl.find_opt` + `Hashtbl.add`. fib(25) = 75025.
  Demonstrates mutable dict semantics through the OCaml stdlib API.
- 2026-05-08 Phase 5.1 — queens.ml baseline (15/15 pass). 4-queens
  count via recursive backtracking with `List.fold_left`. Returns 2
  (the two solutions of 4-queens). Per-program timeout in run.sh
  bumped to 240s — tree-walking interpreter is slow on heavy recursion
  but correct. The substrate handles full backtracking + safe-check
  recursion + list-driven candidate enumeration end-to-end.
- 2026-05-08 Phase 5.1 — mutable_record.ml baseline (14/14 pass).
  Counter-style record with two mutable fields, bump function uses
  `r.f <- v` to mutate. End-to-end validates type decl + record
  literal + field access + field assignment + sequence operator.
- 2026-05-08 Phase 2 — mutable record fields `r.f <- v` (+4 tests, 451
  total). `<-` added to op-table at level 1 (same as `:=`). Eval
  short-circuits on `<-` to mutate the lhs's field via host SX
  `dict-set!`. Tested with for-loop accumulator (`for i = 1 to 5 do
  r.x <- r.x + i done`) and string-field reassignment. The `mutable`
  keyword in record-type decls is parsed-and-discarded; runtime
  semantics: every field is mutable.
- 2026-05-08 Phase 1+3 — record type declarations `type r = { x : int;
  mutable y : string }` (+3 tests, 447 total). Parser dispatches on
  `{` after `=` to parse field list (`mutable` keyword tracked).
  AST: `(:type-def-record NAME PARAMS FIELDS)` with FIELDS each being
  `(NAME)` or `(:mutable NAME)`. Runtime is no-op (records already
  work as dynamic dicts). Field-type sources are skipped; HM type
  registration deferred.
- 2026-05-08 Phase 5.1 — fizzbuzz.ml baseline (12/12 pass). Classic
  fizzbuzz using ref-cell accumulator, for-loop, mod, if/elseif chain,
  String.concat, Int.to_string. Verifies output via String.length.
- 2026-05-08 Phase 2+6 — print primitives wired to host `display` (+2
  tests, 444 total). `print_string` / `print_endline` / `print_int` /
  `print_newline` now use SX `display` (no auto-newline) plus an
  explicit `"\n"` for endline. Prior version referenced `print`/
  `println` host primitives that don't exist. `let _ = expr ;;`
  top-level decl works as expected (already supported by the
  wildcard-param parser).
- 2026-05-08 Phase 5 — HM let-mut / let-rec-mut inference (+3 tests,
  442 total). `ocaml-infer-let-mut` infers each rhs in the parent env
  and generalizes sequentially; `ocaml-infer-let-rec-mut` pre-binds
  all names with fresh tvs, infers each rhs against the joint env,
  unifies, generalizes, then infers body. Mutual recursion works:
  `let rec even n = ... and odd n = ... in even : Int -> Bool`.
- 2026-05-08 Phase 6 — Option/Result/Bytes extensions (+9 tests, 439
  total). Option: join, to_result, some, none. Result: value, iter,
  fold. Bytes: length, get, of_string, to_string, concat, sub (thin
  alias of String — SX has no separate immutable byte type). Ordering
  fix: Bytes module placed after String so its closures capture String
  in scope.
- 2026-05-08 Phase 6 — `Stack` and `Queue` modules in OCaml (+5 tests,
  430 total). Stack: ref-holding-list LIFO with push/pop/top/length/
  is_empty/clear. Queue: amortised O(1) two-list `(front, back)` queue
  with push/pop/length/is_empty/clear. Both written entirely in OCaml
  via lib/ocaml/runtime.sx.
- 2026-05-08 Phase 5.1+1+2 — calc.ml baseline (11/11 pass) — a
  recursive-descent calculator parsing `(1 + 2) * 3 + 4` to 13. Two
  parser bugs fixed along the way: parse-let now handles inline
  `let rec ... and ... in body` via new `:let-rec-mut` / `:let-mut`
  AST shapes (eval supports both); `has-matching-in?` no longer stops
  at `and` (which is internal to a let-rec, not a decl boundary). The
  baseline exercises mutually-recursive functions, while-loops, and
  ref-cell-driven imperative parsing.
- 2026-05-08 Phase 5.1 — word_count.ml baseline (10/10 pass). Uses
  Map.Make(StrOrd) + List.fold_left to count word frequencies; tests
  the full functor pipeline with a real OCaml idiom.
- 2026-05-08 Phase 6 — Map/Set extensions: iter/fold/map/filter/
  is_empty + Set.union/inter (+4 tests, 422 total). Functor
  bodies grow naturally — all in OCaml syntax.
- 2026-05-08 Phase 6 — `Map.Make` / `Set.Make` functors written in
  OCaml (+4 tests, 418 total). Sorted association list / sorted list
  backed (linear ops, but correct). Both take an `Ord` module supplying
  `compare`. Tested: `module IntMap = Map.Make(IntOrd) ;; IntMap.find
  …` and `IntSet.elements (IntSet.add 3 (IntSet.add 1 …))` returning
  `[1; 2; 3]`. Strong substrate-validation for the functor system —
  Map.Make is a non-trivial functor implemented entirely on top of the
  OCaml-on-SX evaluator.
- 2026-05-08 Phase 6 — `Sys` module constants (+5 tests, 414 total).
  os_type, word_size, max_array_length, max_string_length,
  executable_name, big_endian, unix, win32, cygwin. Constants-only
  for now; `argv`/`getenv_opt`/`command` need host platform integration.
- 2026-05-08 Phase 5 — parse simple type sources in user type-defs
  (+3 tests, 409 total). `ocaml-hm-parse-type-src` recognises
  primitive type names, tyvars `'a`, and `T list`/`T option`-style
  parametric types. Replaces the old "default to Int" placeholder so
  `type t = TStr of string` correctly registers `TStr : string -> t`.
  Multi-arg / function types still fall back to a fresh tv.
- 2026-05-08 Phase 6 — String extensions: ends_with/contains/trim/
  split_on_char/replace_all/index_of (+6 tests, 406 total). Wraps host
  primitives via `_string_*` builtins.
- 2026-05-08 Phase 6 — `List.take/drop/filter_map/flat_map/concat_map`
  (+6 tests, 400 total). Common functional helpers, all written in
  OCaml. **400-test milestone.**
- 2026-05-08 Phase 1+3 — or-patterns `(P1 | P2 | ...)` parens-only
  (+5 tests, 394 total). Parser: when `|` follows a pattern inside
  parens, build `(:por ALT1 ALT2 ...)`. Eval: try alternatives, succeed
  on the first match. Top-level `|` remains the clause separator (no
  lookahead needed). Examples: `(1 | 2 | 3) -> ...`, `(Red | Green) -> 1`.
- 2026-05-08 Phase 4 — `module type S = sig … end` parser (+3 tests,
  389 total). Signatures are parsed-and-discarded — sig..end balanced
  skipping. AST: `(:module-type-def NAME)`. Runtime no-op (signatures
  are type-level). Allows real OCaml code with module type decls to
  parse and run without removing the sig blocks.
- 2026-05-08 Phase 1+3 — record patterns `{ f = pat; … }` (+4 tests,
  386 total). Parser adds `(:precord (FIELD PAT) …)` alongside
  the existing record-literal `{` handling. Eval matches against
  dicts: required fields must be present and each pat must match the
  value. Can mix with literals: `{ x = 1; y = y }` matches only when
  x is 1.
- 2026-05-08 Phase 5.1 — expr_eval.ml baseline (9/9 pass). A tiny
  arithmetic-expression evaluator using ADT (`type expr = Lit | Add |
  Mul | Neg`) + recursive eval + pattern match — exercises the full
  type-decl + ctor + match pipeline end-to-end. Per-program timeout
  bumped to 120s in run.sh.
- 2026-05-08 Phase 3 — polymorphic variants `` `Tag `` (+4 tests, 382
  total). Tokenizer recognises backtick followed by an upper ident,
  tokenizing identically to nominal ctors. Parser and evaluator treat
  them as ctors — same tagged-list runtime. Match patterns `` `Red ``
  / `` `Pair (a, b) `` work without any extra wiring. Proper row
  types in HM deferred.
- 2026-05-08 Phase 6 — Float module: sqrt/sin/cos/pow/floor/ceil/round
  + pi constant (+6 tests, 378 total). Wraps host SX math primitives
  via `_float_*` builtins.
- 2026-05-08 Phase 1+5+6 — Float arithmetic (`+.` `-.` `*.` `/.`)
  (+5 tests, 372 total). Tokenizer recognises the dotted operators.
  Parser table places them at int's level (7 / 8). Eval routes them
  to host SX `+`/`-`/`*`/`/` (which works for both ints and floats).
  HM types them `Float -> Float -> Float`; `1.5 +. 2.5 : Float`.
  Float type added to formatter as a plain `Float` ctor.
- 2026-05-08 Phase 6 — List.combine/split/iter2/fold_left2/map2 (+4
  tests, 367 total). Mechanical pair-walk OCaml implementations,
  failwith on length-mismatch matching Stdlib semantics. List module
  now covers 30+ functions.
- 2026-05-08 Phase 5.1 — baseline expanded to 8 programs (8/8 pass).
  Added: closures.ml (curried adders), quicksort.ml (recursive sort
  on lists), exception_handle.ml (exception decl + raise + try/with).
  All 8 programs together exercise let-rec, modules, refs, for-loops,
  pattern matching, exceptions, lambdas, list functions, arithmetic.
  Run.sh streamlined to one sx_server invocation per program (was
  two). End-to-end runtime ≈2 min for the suite.
- 2026-05-08 Phase 5.1 — `lib/ocaml/baseline/` with five sample OCaml
  programs (.ml files), driven by `lib/ocaml/baseline/run.sh` through
  `ocaml-run-program (file-read F)`. All 5/5 pass: factorial,
  list_ops, option_match, module_use (module + ref + closure +
  sequenced calls), sum_squares (for-loop). To make module_use parse,
  parser's `skip-let-rhs-boundary!` now lookaheads for a matching `in`
  before any decl-keyword — distinguishes nested let-in from a new
  top-level decl. Test 274 (`let x = 1 let y = 2`) still works because
  its body has no inner `in`.
- 2026-05-08 Phase 5 — HM with user `type` declarations (+6 tests, 363
  total). `ocaml-hm-ctors` is now a mutable list cell; user type-defs
  register their constructors via `ocaml-hm-register-type-def!`. New
  `ocaml-type-of-program` processes top-level decls in order: type-def
  registers ctors, def/def-rec generalize, exception-def is a no-op,
  expr returns its inferred type. Examples:
    `type color = Red | Green | Blue;; Red : color`
    `type shape = Circle of int | Square of int;; let area s = match s
     with | Circle r -> r * r | Square s -> s * s;; area : shape -> Int`
  Caveat: ctor arg types parsed as raw source strings; the registry
  defaults to `int` for any single-arg ctor. Proper type-source parsing
  is pending.
- 2026-05-08 Phase 5 — HM let-rec inference + `::`/`@` operator types
  (+6 tests, 357 total). `ocaml-infer-let-rec` pre-binds the function
  name to a fresh tv, infers rhs (which can recursively call name),
  unifies inferred-rhs-type with the tv, generalizes, then infers
  body. Builtin env now types `:: : 'a -> 'a list -> 'a list` and
  `@ : 'a list -> 'a list -> 'a list`. Now `let rec fact …`,
  `let rec map f xs = match xs with … h :: t -> f h :: map f t`, and
  `let rec sum …` all infer correctly:
    `fact : Int -> Int`
    `len  : 'a list -> Int`
    `map  : ('a -> 'b) -> 'a list -> 'b list`
    `sum [1;2;3] : Int`
- 2026-05-08 Phase 5 — HM constructor inference for option/result (+7
  tests, 351 total). `ocaml-hm-ctor-env` registers None/Some (`'a opt`),
  Ok/Error (`('a, 'b) result`). `:con NAME` instantiates the scheme;
  `:pcon NAME ARG-PATS` walks arg patterns through the constructor's
  arrow type, unifying each. Pretty-printer renders `Int option` and
  `(Int, 'b) result`. Examples:
    `fun x -> Some x : 'a -> 'a option`
    `fun o -> match o with | None -> 0 | Some n -> n : Int option -> Int`
- 2026-05-08 Phase 5 — HM pattern-matching inference (+5 tests, 344
  total). `ocaml-infer-pat` covers wild, var, lit, cons, list, tuple,
  as. `ocaml-infer-match` unifies each clause's pattern type with the
  scrutinee, runs the body in the env extended with pattern-bound vars,
  and unifies all body types via a fresh result tv. Examples:
  `fun lst -> match lst with | [] -> 0 | h :: _ -> h : Int list -> Int`.
  Constructor patterns fall through to a fresh tv for now (need a ctor
  type registry from `type` decls — pending).
- 2026-05-08 Phase 6 — `List.sort` + polymorphic `compare` (+7 tests,
  339 total). `compare` is a host primitive that returns -1/0/1 like
  Stdlib.compare, defers to host SX `<`/`>`. `List.sort` is implemented
  in OCaml as insertion sort: O(n²) but correct, and passes all tests
  including descending custom comparator and string sort.
- 2026-05-08 Phase 6 — `Hashtbl` (+6 tests, 332 total). Backing store is
  a one-element list cell holding a SX dict; keys are coerced to
  strings via `str` so any value type can serve as a key. API: create,
  add, replace, find, find_opt, mem, length.
- 2026-05-08 Phase 5 — HM extensions for tuples and lists (+7 tests,
  326 total). Tuple type `(hm-con "*" TYPES)`, list type `(hm-con
  "list" (TYPE))`. `ocaml-infer-tuple` threads substitution through
  each item; `ocaml-infer-list` unifies all elements with a fresh
  `'a` (giving `'a list` for `[]`). Pretty-printer renders `Int * Int`
  and `Int list` like real OCaml. `fun x y -> (x, y) : 'a -> 'b -> 'a
  * 'b`. `fun x -> [x; x] : 'a -> 'a list`.
- 2026-05-08 Phase 6 — expanded stdlib slice (+15 tests, 319 total).
  List: concat/flatten, init, find/find_opt, partition, mapi/iteri,
  assoc/assoc_opt. Option: iter, fold, to_list. Result: get_ok,
  get_error, map_error, to_option. Also fixed parser's
  skip-to-boundary! to track `let..in` / `begin..end` / `struct..end`
  / `for/while..done` nesting via a depth counter so nested let
  expressions inside top-level decl bodies don't trip over the
  decl-boundary detector. Stdlib functions like `init` use `begin..end`
  to make nested-let intent explicit.
- 2026-05-08 Phase 3 — `exception` declarations (+4 tests, 304 total).
  `exception NAME [of TYPE]` parses to `(:exception-def NAME [ARG-SRC])`.
  Runtime is a no-op: exception values are just tagged ctor values, so
  the existing `raise`/`try`/`with` machinery works without any extra
  wiring.
- 2026-05-08 Phase 3 — `type` declarations (+5 tests, 300 total). Parser
  handles `type [PARAMS] NAME = | Ctor [of T1 [* T2]*] | ...`, with
  optional `'a` or `('a, 'b)` type parameters. Argument types are
  captured as raw source strings (treated opaquely at runtime). Runtime
  is a no-op since ctor application + match already work dynamically.
  300th test! Constructors `Red`/`Green`/`Blue` and `Circle of int` /
  `Square of int` round-trip through parse + eval cleanly.
- 2026-05-08 Phase 3 — `as` aliases + `when` guards in match (+6 tests,
  295 total). Parser: pattern parser wraps with `as ident` → `(:pas
  PAT NAME)`. Match's `one` consumes optional `when GUARD-EXPR` → emits
  `(:case-when PAT GUARD BODY)` instead of `:case`. Eval `:pas` matches
  inner pattern then also binds the alias name; `case-when` checks the
  guard after a successful match and falls through if false. Or-pat
  `(P1 | P2)` deferred — ambiguous with clause separator without
  parens-only support.
- 2026-05-08 Phase 1+2 — record literals `{ x = 1; y = 2 }` and
  functional update `{ r with x = 99 }`. Parser produces `(:record (F E)
  ...)` and `(:record-update BASE-EXPR (F E) ...)`. Eval builds a dict
  from field bindings; record-update merges over the base dict (the same
  dict-based representation we already use for modules). Field access
  via existing `:field` postfix. Record patterns deferred. 289/289 (+6).
- 2026-05-08 Phase 5.1 — `lib/ocaml/conformance.sh` + `scoreboard.json`
  + `scoreboard.md`. Classifies tests into 14 suites by description
  prefix and emits structured pass/fail counts. Current: 284 pass / 0
  fail (one test counted twice in classifier, hence 284 vs 283
  underlying). Vendoring real OCaml testsuite is the next step but
  needs more stdlib coverage to make .ml files runnable end-to-end.
- 2026-05-08 Phase 1 — unit `()` and wildcard `_` parameters in `let f ()
  = …` / `fun _ -> …` / `let f _ = …`. Parser helper `try-consume-param!`
  now handles ident, wildcard `_` (renamed to `__wild_N`), unit `()`
  (renamed to `__unit_N`), and typed `(x : T)` (signature skipped).
  Same for top-level `parse-decl-let`. test.sh timeout extended from
  60s to 180s for the growing suite. 283/283 (+5).
- 2026-05-08 Phase 6 — extended stdlib slice (+13 tests, 278 total).
  Host primitives exposed via `_string_*`, `_char_*`, `_int_*`,
  `_string_of_*` underscore-prefixed builtins so the OCaml-side
  `lib/ocaml/runtime.sx` modules can wrap them: String (length, get,
  sub, concat, uppercase_ascii, lowercase_ascii, starts_with), Char
  (code, chr, lowercase_ascii, uppercase_ascii), Int (to_string,
  of_string, abs, max, min), Float.to_string, Printf stubs. Also added
  `print_string`/`print_endline`/`print_int` builtins.
- 2026-05-08 Phase 5 — Hindley-Milner type inference, paired-sequencing
  consumer of `lib/guest/hm.sx` (algebra) and `lib/guest/match.sx`
  (unify). `lib/ocaml/infer.sx` ships Algorithm W rules for OCaml AST:
  atoms, var (instantiate), fun (auto-curry through fresh-tv), app
  (unify against arrow), let (generalize over rhs), if (unify branches),
  neg/not, op (treat as app of builtin). Builtin env types `+`/`-`/etc.
  as monomorphic int->int->int and `=`/`<>` as polymorphic 'a->'a->bool.
  Tested: literals, +1, identity polymorphism `'a -> 'a`, let-poly so
  `let id = fun x -> x in id true : Bool`, `twice` infers
  `('a -> 'a) -> 'a -> 'a`. Mandate satisfied: OCaml-on-SX is the
  deferred second consumer for lib-guest Step 8. 265/265 (+14).
- 2026-05-08 Phase 2 — `let ... and ...` mutual recursion at top level.
  Parser collects all bindings into a list, emitting `(:def-rec-mut)` or
  `(:def-mut)` when there are 2+. Eval allocates a placeholder cell per
  recursive binding, builds an env with all of them visible, then fills
  the cells. Even/odd mutual-recursion test passes. 251/251 (+3).
- 2026-05-08 Phase 6 — `lib/ocaml/runtime.sx` minimal stdlib slice
  written entirely in OCaml syntax: List (length, rev, rev_append, map,
  filter, fold_left/right, append, iter, mem, for_all, exists, hd, tl,
  nth), Option (map, bind, value, get, is_none, is_some), Result (map,
  bind, is_ok, is_error). Loaded once via `ocaml-load-stdlib!`, cached
  in `ocaml-stdlib-env`; `ocaml-run` and `ocaml-run-program` layer user
  code on top via `ocaml-base-env`. The fact that these are written in
  OCaml (not SX) and parse + evaluate cleanly is a substrate-validation
  win: every parser, eval, match, ref, and module path proven by a
  single nontrivial Ocaml program. 248/248 (+23).
- 2026-05-08 Phase 4 — functors + module aliases (+5 tests, 225 total).
  Parser: `module F (M) = struct DECLS end` → `(:functor-def NAME PARAMS
  DECLS)`. `module N = expr` (where expr isn't `struct`) → `(:module-alias
  NAME BODY-SRC)`. Functor params accept `(P)` or `(P : Sig)` (signatures
  parsed-and-skipped). Eval: `ocaml-make-functor` builds a curried
  host-SX closure that takes module dicts and returns a module dict;
  `ocaml-resolve-module-path` extended for `:app` so `F(A)`, `F(A)(B)`,
  `Outer.Inner` all resolve to dicts. Tested: 1-arg functor, 2-arg
  curried `Pair(One)(Two)`, module alias, submodule alias, identity
  functor with include. Phase 4 LOC ~290 (still well under 2000).
- 2026-05-08 Phase 4 — `open M` / `include M` (+5 tests, 220 total).
  Parser: top-level `open Path` / `include Path` decls; path is `Ctor (.
  Ctor)*`. Eval resolves the path via `ocaml-resolve-module-path` (the
  same `:con`-as-module-lookup escape hatch used for `:field`); merges
  the dict bindings into the current env via `ocaml-env-merge-dict`.
  `include` inside a module also adds the bindings to the module's
  resulting dict, so `module Sphere = struct include Math let area r =
  ... end` exposes both Math's `pi` and Sphere's `area`. Phase 4 LOC
  cumulative: ~165.
- 2026-05-08 Phase 4 — modules + field access (+11 tests, 215 total). Parser:
  `module M = struct DECLS end` decl in `ocaml-parse-program`. Body parsed
  by sub-tokenising the source between `struct` and the matching `end`,
  tracking nesting via `struct`/`begin`/`sig`/`end`. Field access added
  as a postfix layer above `parse-atom`, binding tighter than application:
  `f r.x` → `(:app f (:field r "x"))`. Eval: `(:module-def NAME DECLS)`
  builds a dict via new `ocaml-eval-module` that runs decls in a sub-env;
  `(:field EXPR NAME)` looks up the field, with the special case that
  `(:con NAME)` heads are interpreted as module-name lookups instead of
  nullary ctors. Tested: simple module, multi-decl module, nested modules
  (`Outer.Inner.v`), `let rec` inside a module, module containing tuple
  pattern match. Phase 4 LOC: ~110 (well under 2000 budget).
- 2026-05-08 Phase 2 — `try`/`with` + `raise` builtin. Parser produces
  `(:try EXPR CLAUSES)`; eval delegates to SX `guard` with `else`
  matching the raised value against clause patterns and re-raising on
  no-match. `raise`/`failwith`/`invalid_arg` exposed as builtins;
  failwith builds `("Failure" msg)` so `Failure msg -> ...` patterns
  match. 204/204 (+6).
- 2026-05-08 Phase 2 — `function | pat -> body | …` parser + eval.
  Sugar for `fun x -> match x with | …`. AST: `(:function CLAUSES)`
  evaluated to a unary closure that runs `ocaml-match-clauses` on the
  argument. `let rec` knot also triggers when rhs is `:function`, so
  `let rec map f = function | [] -> [] | h::t -> f h :: map f t` works.
  ocaml-match-eval refactored to share `ocaml-match-clauses` with the
  function form. 198/198 (+4).
- 2026-05-08 Phase 2 — `for`/`while` loops. `(:for NAME LO HI DIR BODY)`
  with `:ascend`/`:descend` direction (`to`/`downto`); `(:while COND BODY)`.
  Both eval to unit and re-bind the loop var per iteration. 194/194 (+5).
- 2026-05-08 Phase 2 — references (`ref`/`!`/`:=`). `ref` is a builtin
  that boxes its argument in a one-element list (the mutable cell);
  prefix `!` parses to `(:deref EXPR)` and reads `(nth cell 0)`; `:=`
  joins the precedence table at the lowest binop level (right-assoc) and
  short-circuits in eval to mutate via `set-nth!`. Closures capture refs
  by sharing the underlying list. 189/189 (+6).
- 2026-05-08 Phase 3 — pattern matching evaluator + constructors (+18
  tests, 183 total). Constructor application: `(:app (:con NAME) arg)`
  builds a tagged list `(NAME …args)` with tuple args flattened (so
  `Pair (a, b)` → `("Pair" a b)` matches the parser's pattern flatten).
  Standalone ctor `(:con NAME)` → `(NAME)` (nullary). Pattern matcher:
  :pwild / :pvar / :plit (unboxed compare) / :pcon (head + arity match) /
  :pcons (cons-decompose) / :plist (length+items) / :ptuple (after `tuple`
  tag). Match drives clauses until first success; runtime error on
  exhaustion. Tested with option match, literal match, tuple match,
  recursive list functions (`len`, `sum`), nested ctor (`Pair(a,b)`).
  Note: arity flattening happens for any tuple-arg ctor — without ADT
  declarations there's no way to distinguish `Some (1,2)` (single tuple
  payload) from `Pair (1,2)` (two-arg ctor). All-flatten convention is
  consistent across parser + evaluator.
- 2026-05-08 Phase 2 — `lib/ocaml/eval.sx`: ocaml-eval + ocaml-run +
  ocaml-run-program. Coverage: atoms, var lookup, :app (curried),
  :op (arithmetic/comparison/boolean/^/mod/::/|>), :neg, :not, :if,
  :seq, :tuple, :list, :fun (auto-curried host-SX closures), :let,
  :let-rec (recursive knot via one-element-list mutable cell). Initial
  env exposes `not`/`succ`/`pred`/`abs`/`max`/`min`/`fst`/`snd`/`ignore`
  as host-SX functions. Tests: literals, arithmetic, comparison, boolean,
  string concat, conditionals, lambda + closures + recursion (fact 5,
  fib 10, sum 100), sequences, top-level program decls, |> pipe. 165/165
  passing (+42).
- 2026-05-07 Phase 1 — sequence operator `;`. Lowest-precedence binary;
  `e1; e2; e3` → `(:seq e1 e2 e3)`. Two-phase grammar: `parse-expr-no-seq`
  is the prior expression entry point; new `parse-expr` wraps it with
  `;` chaining. List-literal items still use `parse-expr-no-seq` so `;`
  retains its separator role inside `[…]`. Match-clause bodies use the
  seq variant and stop at `|`, matching real OCaml semantics. Trailing `;`
  before `end`/`)`/`|`/`in`/`then`/`else`/eof is permitted. 123/123 tests
  passing (+10).
- 2026-05-07 Phase 1 — `match`/`with` + pattern parser. Patterns: wildcard,
  literal, var, ctor (nullary + with arg, with tuple-arg flattening so
  `Pair (a, b)` → `(:pcon "Pair" PA PB)`), tuple, list literal, cons `::`
  (right-assoc), parens, unit. Match clauses: leading `|` optional, body
  parsed via `parse-expr`. AST: `(:match SCRUT CLAUSES)` where each clause
  is `(:case PAT BODY)`. 113/113 tests passing (+9). Note: parse-expr is
  used for case bodies, so a trailing `| pat -> body` after a complex body
  will be reached because `|` is not in the binop table for level 1.
- 2026-05-07 Phase 1 — top-level program parser `ocaml-parse-program`. Parses
  a sequence of `let [rec] name params* = expr` decls and bare expressions
  separated by `;;`. Output `(:program DECLS)` with each decl one of `(:def …)`,
  `(:def-rec …)`, `(:expr E)`. Decl bodies parsed by re-feeding the source
  slice through `ocaml-parse` (cheap stand-in until shared-state refactor).
  104/104 tests now passing (+9).
- 2026-05-07 Phase 1 — `lib/ocaml/parser.sx` expression parser consuming
  `lib/guest/pratt.sx` for binop precedence (29 operators across 8 levels,
  incl. keyword-spelled binops `mod`/`land`/`lor`/`lxor`/`lsl`/`lsr`/`asr`).
  Atoms (literals + var/con/unit/list), application (left-assoc), prefix
  `-`/`not`, tuples, parens, `if`/`then`/`else`, `fun x y -> body`,
  `let`/`let rec` with function shorthand. AST shapes match Haskell-on-SX
  conventions (`(:int N)` `(:op OP L R)` `(:fun PARAMS BODY)` etc.). Total
  95/95 tests now passing via `lib/ocaml/test.sh`.
- 2026-05-07 Phase 1 — `lib/ocaml/tokenizer.sx` consuming `lib/guest/lex.sx`
  via `prefix-rename`. Covers idents, ctors, 51 keywords, numbers (int / float
  / hex / exponent / underscored), strings (with escapes), chars (with escapes),
  type variables (`'a`), nested block comments, and 26 operator/punct tokens
  (incl. `->` `|>` `<-` `:=` `::` `;;` `@@` `<>` `&&` `||` `**` etc.). 58/58
  tokenizer tests pass via `lib/ocaml/test.sh` driving `sx_server.exe`.

## Blockers

_(none yet)_
