# 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//`. - **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`, `!`, `:=`. - [ ] 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" }`. - [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: ``, `
children
`. - 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)`. - 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-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)_