OCaml notebooks

An attempt at making it simpler to embed the OCaml interpreter, Merlin and OCamlformat, in any webpage thanks to WebComponents:

let rec fib n = if n <= 1 then n else fib (n - 1) + fib (n - 2);;

The different editors follow each others and can reference previous definitions:

let v = fib 8 let () = Format.printf "fib 10 =@. %#i@." (fib 10)

And of course, effect support:

type _ Effect.t += A : unit Effect.t let v = match Effect.perform A with | () -> () | effect A, k -> Effect.Deep.continue k ()

There's also experimental support for outputing HTML visualizations, using the X_ocaml_lib.output_html function:

let () = X_ocaml_lib.output_html {|<svg height="210" width="500" xmlns="http://www.w3.org/2000/svg"> <polygon points="100,10 40,198 190,78 10,78 160,198" style="fill:yellow;stroke:darkblue;stroke-width:5"/></svg>|}

See a longer example on how to integrate x-ocaml with tyxml or graphviz.

To convert markdown files to html, the command-line tool xocmd by @patricoferris will automatically insert the <x-ocaml> tags!

Integration

Just link the scripts with:

<script async
  src="https://cdn.jsdelivr.net/gh/art-w/x-ocaml.js@6/x-ocaml.js"
  src-worker="https://cdn.jsdelivr.net/gh/art-w/x-ocaml.js@6/x-ocaml.worker+effects.js"
  integrity="sha256-3ITn2LRgP/8Rz6oqP5ZQTysesNaSi6/iEdbDvBfyCSE="
  crossorigin="anonymous"
></script>

Importing those scripts will define a new HTML element <x-ocaml> thanks to WebComponents, which can be used to add new editors in the page with: (also check the html source of this page):

<x-ocaml>let x = 42</x-ocaml>
let x = 42

The x-ocaml.js defines the webcomponent and code-mirror integration, while the x-ocaml.worker.js will spawn a webworker to run the OCaml interpreter, Merlin and OCamlformat in a separate thread.

The support for effect handlers adds a couple mb to the runtime and libraries, so instead you might want to use the version without effect handlers x-ocaml.worker.js (instead of x-ocaml.worker+effects.js):

<script async
  src="https://cdn.jsdelivr.net/gh/art-w/x-ocaml.js@6/x-ocaml.js"
  src-worker="https://cdn.jsdelivr.net/gh/art-w/x-ocaml.js@6/x-ocaml.worker.js"
  integrity="sha256-3ITn2LRgP/8Rz6oqP5ZQTysesNaSi6/iEdbDvBfyCSE="
  crossorigin="anonymous"
></script>

To avoid relying on a CDN to serve the javascript files, please check out the x-ocaml repository for instructions to reproduce them.

Loading libraries

There's also a shell utility to export OCaml libraries and PPX: (drop the --effects if using the worker without effects)

$ x-ocaml --effects re digestif.ocaml -o re_digestif.js
$ x-ocaml --effects re digestif.ocaml --ppx ppx_deriving.show -o re_digestif_show.js

Which can be linked in the html page with:

<script async src="..." src-worker="..." src-load="./re_digestif_show.js" ></script>

So that they become accessible in the notebook cells:

let re = Re.(compile (seq [any;any])) let hash = Digestif.MD5.(digest_string "hello" |> to_hex)

PPX support only works for the toplevel atm, as Merlin doesn't yet see that the ppx generates show: (wip!)

type t = A of int [@@deriving show] let v = show (A 42)

Style

To avoid the page layout shifting once CodeMirror loads, we recommend the following CSS:

x-ocaml:not(:defined) {
  display: block;
  white-space: pre-wrap;
  padding: 4px 0.3em 4px 48px;
  line-height: 1.4em;
  font-family: monospace;
  font-size: 1.2em;
}

You can also style the WebComponent when loading the script with the attribute inline-style="..." and/or load an external stylesheet with the tag src-style="./extra.css"

<script async src="..." src-worker="..."
  inline-style="font-size:2em"
  src-style="./extra.css"
></script>

If you opt for an external stylesheet, the WebComponent DOM root is called :host, for example:

:host { font-family: 2em }

OCamlformat

OCamlformat configuration can be passed with the x-ocamlformat="..." attribute:

<script async src="..." src-worker="..."
  x-ocamlformat="
    profile=janestreet
    margin=60
    ..."
></script>

As a special case, you can disable the pretty formatting with x-ocamlformat="disable".