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 ()

(Note: OCamlformat doesn't yet support the effect syntax?)

Integration

Just link the scripts with:

<script async
  src="https://cdn.jsdelivr.net/gh/art-w/x-ocaml.js@2/x-ocaml.js"
  src-worker="https://cdn.jsdelivr.net/gh/art-w/x-ocaml.js@2/x-ocaml.worker+effects.js"
></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@2/x-ocaml.js"
  src-worker="https://cdn.jsdelivr.net/gh/art-w/x-ocaml.js@2/x-ocaml.worker.js"
></script>

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 46px;
  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>

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)