# Interactive OCaml Tutorials — User's Guide This guide is for tutorial authors and web developers who want to create interactive OCaml content — live code cells, exercises, and widgets — served as static HTML pages with no server-side component. ## How it works Authors write documentation in `.mld` files using odoc's tagged code block syntax. The odoc plugin translates `{@ocaml ...}` blocks into `` HTML elements. A WebComponent (`x-ocaml.js`) and a Web Worker (`worker.js`) handle all interactivity in the browser: editing, execution, autocompletion, and type feedback. ``` Author writes .mld odoc plugin x-ocaml + worker ───────────────────── ──> ──────────────── ──> ───────────────── {@ocaml exercise UI, sends code to ... worker for execution ``` There is no server-side component beyond serving static files over HTTP. ## Universes A **universe** is a self-consistent set of compiled OCaml packages. OCaml requires that all libraries in a universe are built with exactly the same versions of all transitive dependencies — you cannot mix libraries from different build environments. Each universe is identified by a content hash and has a single entry point: `findlib_index.json`. This file tells the runtime which compiler version to use and where to find every package's artifacts. ### Content sources **The ocaml.org multiverse** — the OCaml package documentation site hosts pre-built universes for published opam packages. Each package version's documentation page links to the correct universe for that package and its dependencies. **Self-hosted** — you can generate and host your own universes for custom package sets or private libraries. See the Admin Guide for instructions on using `jtw opam` or `day10` to produce these artifacts. Serve the output directory over HTTP with appropriate CORS headers if loading cross-origin. ### Directory layout ``` compiler/// worker.js -- the OCaml toplevel web worker lib/ocaml/ *.cmi, stdlib.cma.js -- stdlib artifacts p//// lib// META, *.cmi, *.cma.js -- package artifacts u// findlib_index.json -- entry point ``` All paths include a content hash, making them safe to cache indefinitely. ## Authoring tutorials ### Page-level configuration Custom tags at the top of the `.mld` file configure the page: ``` @x-ocaml.universe https://ocaml.org/universe/5.3.0 @x-ocaml.requires cmdliner, astring @x-ocaml.auto-execute false @x-ocaml.merlin false ``` | Tag | Default | Purpose | |-----|---------|---------| | `universe` | `./universe/` | URL where `findlib_index.json` lives | | `requires` | none | Packages to preload before any cells run | | `auto-execute` | `true` | Whether cells run automatically on page load | | `merlin` | `true` | Whether Merlin-based LSP feedback is enabled | ### Cell types Code blocks use odoc's tagged code block syntax: `{@ocaml [...code...]}`. | Attribute | Purpose | Editable? | Visible? | |---------------|---------------------------------|-----------|----------| | `interactive` | Demo or example cell | No | Yes | | `exercise` | Skeleton for the reader to edit | Yes | Yes | | `test` | Immutable test assertions | No | Yes | | `hidden` | Setup code, runs but not shown | No | No | ### Per-cell attributes | Attribute | Purpose | |------------|----------------------------------------| | `id=name` | Name this cell for explicit linking | | `for=name` | Link a test cell to a specific exercise | | `env=name` | Named execution environment | | `merlin` | Override page-level merlin setting | ### Execution environments Cells sharing an `env` attribute see each other's definitions. By default, all cells on a page share one environment. Named environments allow isolation when needed: ``` {@ocaml hidden env=greetings [ let greeting = "Hello" ]} {@ocaml interactive env=greetings [ Printf.printf "%s, world!\n" greeting ]} {@ocaml interactive env=math [ (* This cell cannot see 'greeting' *) let pi = Float.pi ]} ``` ### Exercise linking Test cells are linked to exercise cells by two mechanisms: - **Positional (default)** — a test cell applies to the nearest preceding exercise cell. - **Explicit** — use `id` and `for` attributes when the test is distant or ambiguous. ## Examples ### Interactive tutorial A step-by-step walkthrough where cells build on each other: ``` @x-ocaml.universe https://ocaml.org/universe/5.4.0 @x-ocaml.requires fmt {1 Working with Fmt} The [Fmt] library provides composable pretty-printing combinators. {@ocaml interactive [ let pp_greeting ppf name = Fmt.pf ppf "Hello, %s!" name ]} Try it: {@ocaml interactive [ Fmt.pr "%a@." pp_greeting "world" ]} ``` ### Assessment worksheet An exercise with hidden setup, editable skeleton, and visible tests: ``` {@ocaml hidden [ (* Setup code the student doesn't see *) let check_positive f = assert (f 0 = 1); assert (f 1 = 1) ]} Write an OCaml function [facr] to compute the factorial by recursion. {@ocaml exercise id=factorial [ let rec facr n = (* YOUR CODE HERE *) failwith "Not implemented" ]} {@ocaml test for=factorial [ assert (facr 10 = 3628800);; assert (facr 11 = 39916800);; ]} ``` ### Generated HTML The odoc plugin maps attributes directly to HTML data attributes: ```html (* Setup code the student doesn't see *) let check_positive f = ...

Write an OCaml function facr to compute the factorial by recursion.

let rec facr n = (* YOUR CODE HERE *) failwith "Not implemented" assert (facr 10 = 3628800);; assert (facr 11 = 39916800);; ``` The plugin also injects a ` let words = Str.split (Str.regexp " ") "hello world" let reverse lst = (* YOUR CODE HERE *) failwith "todo" assert (reverse [1;2;3] = [3;2;1]);; ``` This makes the system usable with other documentation generators, static site builders, or hand-written HTML. ## Client library (advanced) For custom integrations that need direct control over the Web Worker, the `OcamlWorker` JavaScript class provides a programmatic API. ### Creating a worker ```javascript import { OcamlWorker } from './ocaml-worker.js'; const indexUrl = './u/UNIVERSE_HASH/findlib_index.json'; const { worker, stdlib_dcs, findlib_index } = await OcamlWorker.fromIndex(indexUrl, '.', { timeout: 120000 }); await worker.init({ findlib_requires: ['fmt'], stdlib_dcs, findlib_index, }); ``` ### Evaluating code ```javascript const result = await worker.eval('List.map (fun x -> x * 2) [1;2;3];;'); console.log(result.caml_ppf); // val x : int list = [2; 4; 6] ``` The result object contains: | Field | Type | Description | |-------------|-------------|------------------------------------------------| | `caml_ppf` | `string` | Toplevel-style output (e.g. `val x : int = 3`) | | `stdout` | `string` | Anything printed to stdout | | `stderr` | `string` | Warnings and errors | | `mime_vals` | `MimeVal[]` | Rich output (HTML, SVG, images) | ### Other methods - **`complete(code, pos)`** — autocompletion suggestions at a cursor position - **`typeAt(code, pos)`** — type of the expression at a position - **`errors(code)`** — check code for errors without executing it - **`createEnv(name)` / `destroyEnv(name)`** — manage isolated execution environments - **`terminate()`** — shut down the web worker ### Rich output (MIME values) `eval()` results may include `mime_vals` — an array of objects with `mime_type` and `data`. Libraries that produce graphical output (plotting, diagrams) use this mechanism to display results in the browser. Common types: `text/html`, `image/svg+xml`, `image/png`. ### Loading libraries at runtime In addition to preloading via `findlib_requires`, users can load libraries dynamically: ```ocaml #require "str";; Str.split (Str.regexp " ") "hello world";; ``` This works for any library present in the universe's `findlib_index.json`. ## What's next - **Scrollycode tutorials** — scroll-driven code walkthroughs using `odoc-scrollycode-extension` (already prototyped) - **Interactive widgets** — reactive UI elements (sliders, plots, mini-apps) driven by an FRP library running in the Worker (experimental)