Skip to content

Commit bcbdcdc

Browse files
committed
Bump 0.2.1
1 parent 5b0ac9b commit bcbdcdc

21 files changed

Lines changed: 580410 additions & 101984 deletions

File tree

README.md

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
# ![xeus-ocaml logo](https://raw.githubusercontent.com/davy39/xeus-ocaml/refs/heads/main/share/jupyter/kernels/xocaml/logo-64x64.png) xeus-ocaml
1+
2+
# ![xeus-logo](https://raw.githubusercontent.com/jupyter-xeus/xeus/refs/heads/main/docs/source/xeus.svg) OCAML ![xeus-ocaml logo](https://raw.githubusercontent.com/davy39/xeus-ocaml/refs/heads/main/share/jupyter/kernels/xocaml/logo-64x64.png)
23

34
[![CI and Auto-Tagging](https://github.com/davy39/xeus-ocaml/actions/workflows/ci.yml/badge.svg)](https://github.com/davy39/xeus-ocaml/actions/workflows/ci.yml)
45
[![Release and Deploy](https://github.com/davy39/xeus-ocaml/actions/workflows/release.yml/badge.svg)](https://github.com/davy39/xeus-ocaml/actions/workflows/release.yml)
5-
[![GitHub Pages](https://img.shields.io/badge/github--pages-deployed-success)](https://davy39.github.io/xeus-ocaml/)
6+
[![lite-badge](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://davy39.github.io/xeus-ocaml/)
67

78
`xeus-ocaml` is a Jupyter kernel for the OCaml programming language that runs entirely in the web browser through WebAssembly. It is built on the `xeus-lite` library, a lightweight C++ implementation of the Jupyter protocol for WASM environments.
89

@@ -19,7 +20,49 @@ Experience `xeus-ocaml` firsthand in your browser by visiting the JupyterLite de
1920
* **Fully Browser-Based**: Runs entirely in the browser with no server-side installation, powered by WebAssembly.
2021
* **Interactive OCaml Toplevel**: Execute OCaml code interactively, with persistent state between cells.
2122
* **Rich Language Intelligence**: Provides code completion and inspection (tooltips on hover/Shift+Tab) through an integrated Merlin engine.
22-
* **JupyterLite Integration**: Designed for seamless use within the JupyterLite environment.
23+
* **Rich Display Support**: Render HTML, Markdown, SVG, JSON, and even complex plots like Vega-Lite directly from your OCaml code.
24+
25+
## 📊 Rich Display and Visualization
26+
27+
The kernel comes with a built-in `Xlib` library that is **automatically opened** on startup, so its functions are immediately available in the global scope. This library provides a simple API for rendering a wide variety of rich outputs in your notebook cells.
28+
29+
Here are some of the key functions available:
30+
31+
| Function | Description |
32+
| ----------------------- | ---------------------------------------------------------- |
33+
| `output_html s` | Renders a raw HTML string `s`. |
34+
| `output_markdown s` | Renders a Markdown string `s`. |
35+
| `output_svg s` | Renders an SVG image from its XML string `s`. |
36+
| `output_json s` | Renders a JSON string `s` as a collapsible tree view. |
37+
| `output_vegalite s` | Renders an interactive Vega-Lite plot from a JSON spec `s`.|
38+
| `output_png_base64 s` | Displays a PNG image from a Base64-encoded string `s`. |
39+
| `output_jpeg_base64 s` | Displays a JPEG image from a Base64-encoded string `s`. |
40+
41+
#### Example Usage
42+
43+
You can call these functions directly in any cell to produce rich outputs.
44+
45+
```ocaml
46+
(* Render an interactive Vega-Lite chart *)
47+
let vega_spec = {|
48+
{
49+
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
50+
"description": "A simple bar chart with embedded data.",
51+
"data": {
52+
"values": [
53+
{"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43},
54+
{"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53}
55+
]
56+
},
57+
"mark": "bar",
58+
"encoding": {
59+
"x": {"field": "a", "type": "nominal", "axis": {"labelAngle": 0}},
60+
"y": {"field": "b", "type": "quantitative"}
61+
}
62+
}
63+
|} in
64+
output_vegalite vega_spec
65+
```
2366

2467
## 🏗️ Architecture
2568

@@ -64,17 +107,29 @@ This project uses the `pixi` package and environment manager to streamline devel
64107
```bash
65108
pixi run -e ocaml build
66109
```
110+
4. **Build the Kernel**
111+
This build the kernel and create a conda package.
112+
```bash
113+
pixi run build-kernel
114+
```
115+
5. **Install the kernel**
116+
This install the kernel to be used with Jupyterlite.
117+
```bash
118+
pixi run install-kernel
119+
```
120+
6. **Serve Jupyterlite**
121+
This build and serve the Jupyterlite interface.
122+
```bash
123+
pixi run install-kernel
124+
```
125+
You can now access the local JupyterLite instance in your browser, typically at `http://localhost:8000`.
67126

68-
4. **Build the WASM Kernel and Serve JupyterLite**
69-
This is a convenience command that performs all remaining steps:
70-
* Builds the C++ kernel to WASM using `rattler-build`.
71-
* Packages the kernel and `xocaml.js` into a conda package.
72-
* Installs the package into a local environment.
73-
* Builds and launches a local JupyterLite server.
127+
7. **All in one command**
128+
This is a convenience command that performs all steps:
74129
```bash
75130
pixi run build-all-serve
76131
```
77-
You can now access the local JupyterLite instance in your browser, typically at `http://localhost:8000`.
132+
78133

79134
## 🧪 Testing
80135

@@ -91,6 +146,7 @@ pixi run -e test test
91146
- [x] Interactive code execution via the `js_of_ocaml` toplevel.
92147
- [x] Code completion powered by an in-browser Merlin instance.
93148
- [x] Code inspection for tooltips (Shift+Tab) and the inspector panel.
149+
- [x] **Rich Outputs**: Display HTML, Markdown, SVG, JSON, and Vega-Lite plots directly from OCaml code using the auto-opened `Xlib` module.
94150

95151
### Future Work
96152
- [ ] **Library Management**: Implement a mechanism to dynamically fetch and load pre-compiled OCaml libraries from within a notebook session (e.g., via `#require`).

ocaml/dune

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,3 @@
77
(alias
88
(name js)
99
(deps xocaml.js))
10-

ocaml/src/protocol/protocol.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ type output =
3636
| Stdout of string
3737
| Stderr of string
3838
| Value of string
39+
| DisplayData of Yojson.Safe.t
40+
3941
[@@deriving yojson]
4042

4143
type completions = {

ocaml/src/xlib/dune

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
(library
2+
(name xlib)
3+
(public_name xocaml.lib)
4+
; (preprocess (pps ppx_deriving_yojson))
5+
(libraries
6+
xocaml.protocol
7+
yojson
8+
)
9+
(flags (:standard -bin-annot)))

ocaml/src/xlib/xlib.ml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
(* A mutable list to store outputs generated by user functions. *)
2+
let extra_outputs = ref []
3+
4+
(* Internal function to add an output to the list. *)
5+
let add_output output =
6+
extra_outputs := output :: !extra_outputs
7+
8+
(* Function to be called by the toplevel to retrieve and clear outputs for the last execution. *)
9+
let get_and_clear_outputs () =
10+
let result = List.rev !extra_outputs in
11+
extra_outputs := [];
12+
result
13+
14+
(**
15+
* Generic helper to create a single-MIME-type display data object and add it to the output list.
16+
*)
17+
let create_and_add_display_data mime_type content =
18+
let data = `Assoc [(mime_type, content)] in
19+
add_output (Protocol.DisplayData data)
20+
21+
(* The most generic function, exposed to the user. *)
22+
let output_display_data data =
23+
add_output (Protocol.DisplayData data)
24+
25+
(* --- Text-based formats --- *)
26+
27+
let output_html s =
28+
create_and_add_display_data "text/html" (`String s)
29+
30+
let output_markdown s =
31+
create_and_add_display_data "text/markdown" (`String s)
32+
33+
let output_latex s =
34+
create_and_add_display_data "text/latex" (`String s)
35+
36+
let output_svg s =
37+
create_and_add_display_data "image/svg+xml" (`String s)
38+
39+
(* --- JSON-based formats --- *)
40+
(* For these, the frontend expects a JSON object, not a string containing JSON.
41+
So we must parse the string into a Yojson.Safe.t value first. *)
42+
43+
let output_json s =
44+
try
45+
let json_val = Yojson.Safe.from_string s in
46+
create_and_add_display_data "application/json" json_val
47+
with Yojson.Json_error msg ->
48+
let err_msg = Printf.sprintf "JSON parsing error: %s" msg in
49+
add_output (Protocol.Stderr err_msg)
50+
51+
let output_vegalite s =
52+
try
53+
let json_val = Yojson.Safe.from_string s in
54+
create_and_add_display_data "application/vnd.vegalite.v5+json" json_val
55+
with Yojson.Json_error msg ->
56+
let err_msg = Printf.sprintf "Vega-Lite JSON parsing error: %s" msg in
57+
add_output (Protocol.Stderr err_msg)
58+
59+
let output_vega s =
60+
try
61+
let json_val = Yojson.Safe.from_string s in
62+
create_and_add_display_data "application/vnd.vega.v5+json" json_val
63+
with Yojson.Json_error msg ->
64+
let err_msg = Printf.sprintf "Vega JSON parsing error: %s" msg in
65+
add_output (Protocol.Stderr err_msg)
66+
67+
(* --- Binary formats (via Base64) --- *)
68+
69+
let output_png_base64 s =
70+
create_and_add_display_data "image/png" (`String s)
71+
72+
let output_jpeg_base64 s =
73+
create_and_add_display_data "image/jpeg" (`String s)
74+
75+
let output_gif_base64 s =
76+
create_and_add_display_data "image/gif" (`String s)
77+
78+
let output_pdf_base64 s =
79+
create_and_add_display_data "application/pdf" (`String s)

ocaml/src/xlib/xlib.mli

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
(**
2+
* Internal function for the toplevel to retrieve and clear outputs.
3+
* This is not intended for direct use by end-users.
4+
*)
5+
val get_and_clear_outputs : unit -> Protocol.output list
6+
7+
(**
8+
* Renders a full MIME bundle as a cell output. This is the most flexible
9+
* function for creating rich output.
10+
* The data should be a Yojson.Safe.t object, e.g.,
11+
* `Assoc [("text/plain", `String "plain"); ("text/html", `String "<b>html</b>")]
12+
*)
13+
val output_display_data : Yojson.Safe.t -> unit
14+
15+
(** Renders a raw HTML string as a cell output. *)
16+
val output_html : string -> unit
17+
18+
(** Renders a raw Markdown string as a cell output. *)
19+
val output_markdown : string -> unit
20+
21+
(** Renders a raw LaTeX string as a cell output (for equations). *)
22+
val output_latex : string -> unit
23+
24+
(** Renders a raw SVG image string as a cell output. *)
25+
val output_svg : string -> unit
26+
27+
(** Renders a JSON string as a collapsible tree view in the output. *)
28+
val output_json : string -> unit
29+
30+
(** Renders a PNG image from a Base64-encoded string. *)
31+
val output_png_base64 : string -> unit
32+
33+
(** Renders a JPEG image from a Base64-encoded string. *)
34+
val output_jpeg_base64 : string -> unit
35+
36+
(** Renders a GIF image from a Base64-encoded string. *)
37+
val output_gif_base64 : string -> unit
38+
39+
(** Renders an inline PDF document from a Base64-encoded string. *)
40+
val output_pdf_base64 : string -> unit
41+
42+
(** Renders an interactive Vega-Lite plot from a JSON spec string. *)
43+
val output_vegalite : string -> unit
44+
45+
(** Renders an interactive Vega plot from a JSON spec string. *)
46+
val output_vega : string -> unit

ocaml/src/xmerlin/dynamic/dune

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
(data_only_dirs stdlib)
2-
31
(rule
42
(target dynamic_modules.ml)
53
(deps
64
gen_dynamic.ml
5+
(alias ./stdlib/merlin_files)
76
(glob_files stdlib/*.cmi))
87
(action
98
(run ocaml %{dep:gen_dynamic.ml})))
109

1110
(library
1211
(name dynamic_modules)
1312
(public_name xocaml.xmerlin.dynamic_modules)
14-
(modules dynamic_modules))
13+
(modules dynamic_modules))
14+

ocaml/src/xmerlin/dynamic/gen_dynamic.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ let () =
1818
| ["stdlib"] -> "Stdlib" :: acc
1919
| ["stdlib"; ""; mod_part] when mod_part <> "" ->
2020
String.capitalize_ascii mod_part :: acc
21+
| ["xlib"] -> "Xlib" :: acc
2122
| _ -> acc
2223
else acc)
2324
[] files
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
(rule
3+
(alias merlin_files)
4+
(mode promote)
5+
(target xlib.cmi)
6+
(action
7+
(copy %{lib:xocaml.lib:xlib.cmi} %{target})))
8+
9+
(rule
10+
(alias merlin_files)
11+
(mode promote)
12+
(target xlib.cmt)
13+
(action
14+
(copy %{lib:xocaml.lib:xlib.cmt} %{target})))
15+
16+
(rule
17+
(alias merlin_files)
18+
(mode promote)
19+
(target xlib.cmti)
20+
(action
21+
(copy %{lib:xocaml.lib:xlib.cmti} %{target})))
2.53 KB
Binary file not shown.

0 commit comments

Comments
 (0)