Skip to content

Commit 5a6a13f

Browse files
authored
Merge pull request #9 from mbarbin/improve-intro-book
Improve intro book
2 parents ddacf79 + 48a122b commit 5a6a13f

25 files changed

Lines changed: 1954 additions & 217 deletions

.vscode/settings.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,32 @@
88
"Barbin",
99
"cmxs",
1010
"decr",
11+
"dedented",
12+
"dunolint",
13+
"EDSL",
1114
"endline",
1215
"Fpath",
16+
"iteri",
1317
"ltrim",
1418
"Mathieu",
19+
"mathjax",
1520
"mbarbin",
1621
"mdbook",
1722
"mdexp",
1823
"noprompt",
24+
"opam",
25+
"prec",
1926
"rtrim",
2027
"runtest",
2128
"sexp",
29+
"slipshow",
2230
"sprintf",
2331
"stdlib",
2432
"toplevel",
33+
"Typst",
2534
"unflushed",
26-
"Yojson"
35+
"windtrap",
36+
"Yojson",
37+
"zstd"
2738
]
2839
}

doc/book/introduction-to-mdexp/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@
66
- [Snapshots](snapshots.md)
77
- [ppx_expect](ppx_expect_snapshots.md)
88
- [Expect tests without ppx](windtrap_snapshots.md)
9+
- [EDSL](edsl.md)
10+
- [Broadening the View](broadening_the_view.md)

doc/book/introduction-to-mdexp/book.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ default-theme = "light"
1313
preferred-dark-theme = "navy"
1414
git-repository-url = "https://github.com/mbarbin/mdexp"
1515
theme = "../shared-theme"
16+
mathjax-support = true
1617
additional-js = ["../shared-theme/ansi-plugin.js"]
1718

1819
[output.html.fold]
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Broadening the View
2+
3+
Now that you have a feel for how mdexp works, let's step back and
4+
consider where the approach leads and where you might take it.
5+
6+
The previous chapters focused on mechanics: directives, snapshots,
7+
and the EDSL pattern. Here we broaden the view to explore
8+
additional use cases, discuss design choices, and invite you to
9+
think about how this pattern of *compilable, verified documentation*
10+
might apply to your own projects, whether or not you use mdexp
11+
itself.
12+
13+
## Cram-style tests in OCaml
14+
15+
OCaml's ecosystem uses `.t` cram files to test command-line tools:
16+
you write a shell session with expected output and the test runner
17+
checks it. mdexp offers a path to express the same tests in OCaml
18+
and we are experimenting with a `Mdexp_cram` library (wip, unpublished).
19+
The "background" part of the process becomes the evaluation environment
20+
(setting up state, running commands, capturing output) while the visible
21+
part reads as a tutorial showing the session step by step.
22+
23+
This means the same file can serve as both a functional test and a
24+
user-facing walkthrough: the test runner verifies correctness, and
25+
mdexp extracts the narrative.
26+
27+
## Client/server application testing
28+
29+
The pattern extends naturally to networked applications. The
30+
background spins up a running server, and the visible part scripts a
31+
series of CLI client calls that exercise the service. Each call and
32+
its output become a snapshot in the document; the full session builds
33+
up an end-to-end functional test.
34+
35+
The reader sees a coherent tutorial --- "first create a resource, then
36+
query it, then update it" --- while the build system verifies that
37+
every response matches expectations. The documentation becomes a live
38+
integration test.
39+
40+
## Your own EDSLs
41+
42+
The [EDSL chapter](edsl.md) showed a math expression language and a
43+
toy proof assistant. But there is nothing special about math --- the
44+
same pattern applies wherever you want to both **render** and **verify**
45+
structured content.
46+
47+
The key point is that mdexp does not provide or prescribe any of
48+
these EDSLs. It provides the infrastructure --- directives, snapshot
49+
capture, the `pp` pipeline --- and you build whatever domain-specific
50+
layer makes sense for your project. It is just OCaml; there is no
51+
plugin architecture to learn. Your EDSL lives in your codebase, uses
52+
your types, and evolves with your project.
53+
54+
## Host and output languages
55+
56+
Neither the host language nor the output format is a hard constraint
57+
of the system. mdexp's directive syntax is designed to work with
58+
any language that has block comments, and we have prepared support
59+
for host-language expect-test frameworks written in **Zig** and
60+
**Rust** alongside OCaml. Similarly, the output is not limited to
61+
Markdown --- it could just as well be Typst, reStructuredText, or
62+
any other text format.
63+
64+
That said, we have focused our effort on the **OCaml + Markdown**
65+
combination for practical reasons:
66+
67+
- **OCaml** is well suited to the kind of work mdexp encourages.
68+
The examples in this book --- symbolic computation, algebraic
69+
rewriting, proof construction --- benefit from a language where
70+
you can manipulate structured data at a high level without
71+
worrying about memory management. Rust and Zig are equally
72+
appealing here --- each handles memory in its own way without
73+
burdening documentation-oriented code. Which language fits best
74+
may depend on the libraries you need to pull into your document.
75+
We would not be surprised if mdexp found a place in
76+
multi-language codebases, with different chapters written in
77+
different host languages.
78+
- **Markdown** integrates well with the rest of our tooling: static
79+
site generators, mdbook, slipshow, and the broader ecosystem of
80+
tools that consume Markdown as input.
81+
82+
Other combinations are possible and welcome; the architecture does
83+
not privilege any particular pairing.
84+
85+
## Closing thoughts
86+
87+
Beyond the specific tool, the underlying pattern is worth
88+
reflecting on: write documentation as a compilable source file,
89+
use the type system and test framework to keep it honest, and let
90+
a thin preprocessor extract the readable output. This is not a new
91+
idea --- literate programming is decades old --- but the combination
92+
with snapshot testing and embedded DSLs gives it a practical edge
93+
that we believe makes it click in interesting ways.
94+
95+
mdexp is one implementation of this pattern, intentionally minimal.
96+
It reads directives, extracts content, and produces output.
97+
Everything else --- the types, the tests, the rendering logic, the
98+
EDSLs --- is ordinary code in your project. The tool stays small
99+
and predictable; the power comes from what you build on top of it.
100+
101+
We hope this introductory book has given you both a working
102+
understanding of mdexp and a sense of the broader possibilities.
103+
For deeper coverage --- reference material, how-to guides, and
104+
additional examples --- see the main documentation site.
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
(*********************************************************************************)
2+
(* mdexp - Literate Programming with Embedded Snapshots *)
3+
(* SPDX-FileCopyrightText: 2025-2026 Mathieu Barbin <mathieu.barbin@gmail.com> *)
4+
(* SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception *)
5+
(*********************************************************************************)
6+
7+
(* @mdexp
8+
9+
# Broadening the View
10+
11+
Now that you have a feel for how mdexp works, let's step back and
12+
consider where the approach leads and where you might take it.
13+
14+
The previous chapters focused on mechanics: directives, snapshots,
15+
and the EDSL pattern. Here we broaden the view to explore
16+
additional use cases, discuss design choices, and invite you to
17+
think about how this pattern of *compilable, verified documentation*
18+
might apply to your own projects, whether or not you use mdexp
19+
itself.
20+
21+
## Cram-style tests in OCaml
22+
23+
OCaml's ecosystem uses `.t` cram files to test command-line tools:
24+
you write a shell session with expected output and the test runner
25+
checks it. mdexp offers a path to express the same tests in OCaml
26+
and we are experimenting with a `Mdexp_cram` library (wip, unpublished).
27+
The "background" part of the process becomes the evaluation environment
28+
(setting up state, running commands, capturing output) while the visible
29+
part reads as a tutorial showing the session step by step.
30+
31+
This means the same file can serve as both a functional test and a
32+
user-facing walkthrough: the test runner verifies correctness, and
33+
mdexp extracts the narrative.
34+
35+
## Client/server application testing
36+
37+
The pattern extends naturally to networked applications. The
38+
background spins up a running server, and the visible part scripts a
39+
series of CLI client calls that exercise the service. Each call and
40+
its output become a snapshot in the document; the full session builds
41+
up an end-to-end functional test.
42+
43+
The reader sees a coherent tutorial --- "first create a resource, then
44+
query it, then update it" --- while the build system verifies that
45+
every response matches expectations. The documentation becomes a live
46+
integration test.
47+
48+
## Your own EDSLs
49+
50+
The [EDSL chapter](edsl.md) showed a math expression language and a
51+
toy proof assistant. But there is nothing special about math --- the
52+
same pattern applies wherever you want to both **render** and **verify**
53+
structured content.
54+
55+
The key point is that mdexp does not provide or prescribe any of
56+
these EDSLs. It provides the infrastructure --- directives, snapshot
57+
capture, the `pp` pipeline --- and you build whatever domain-specific
58+
layer makes sense for your project. It is just OCaml; there is no
59+
plugin architecture to learn. Your EDSL lives in your codebase, uses
60+
your types, and evolves with your project.
61+
62+
## Host and output languages
63+
64+
Neither the host language nor the output format is a hard constraint
65+
of the system. mdexp's directive syntax is designed to work with
66+
any language that has block comments, and we have prepared support
67+
for host-language expect-test frameworks written in **Zig** and
68+
**Rust** alongside OCaml. Similarly, the output is not limited to
69+
Markdown --- it could just as well be Typst, reStructuredText, or
70+
any other text format.
71+
72+
That said, we have focused our effort on the **OCaml + Markdown**
73+
combination for practical reasons:
74+
75+
- **OCaml** is well suited to the kind of work mdexp encourages.
76+
The examples in this book --- symbolic computation, algebraic
77+
rewriting, proof construction --- benefit from a language where
78+
you can manipulate structured data at a high level without
79+
worrying about memory management. Rust and Zig are equally
80+
appealing here --- each handles memory in its own way without
81+
burdening documentation-oriented code. Which language fits best
82+
may depend on the libraries you need to pull into your document.
83+
We would not be surprised if mdexp found a place in
84+
multi-language codebases, with different chapters written in
85+
different host languages.
86+
- **Markdown** integrates well with the rest of our tooling: static
87+
site generators, mdbook, slipshow, and the broader ecosystem of
88+
tools that consume Markdown as input.
89+
90+
Other combinations are possible and welcome; the architecture does
91+
not privilege any particular pairing.
92+
93+
## Closing thoughts
94+
95+
Beyond the specific tool, the underlying pattern is worth
96+
reflecting on: write documentation as a compilable source file,
97+
use the type system and test framework to keep it honest, and let
98+
a thin preprocessor extract the readable output. This is not a new
99+
idea --- literate programming is decades old --- but the combination
100+
with snapshot testing and embedded DSLs gives it a practical edge
101+
that we believe makes it click in interesting ways.
102+
103+
mdexp is one implementation of this pattern, intentionally minimal.
104+
It reads directives, extracts content, and produces output.
105+
Everything else --- the types, the tests, the rendering logic, the
106+
EDSLs --- is ordinary code in your project. The tool stays small
107+
and predictable; the power comes from what you build on top of it.
108+
109+
We hope this introductory book has given you both a working
110+
understanding of mdexp and a sense of the broader possibilities.
111+
For deeper coverage --- reference material, how-to guides, and
112+
additional examples --- see the main documentation site. *)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
(*_********************************************************************************)
2+
(*_ mdexp - Literate Programming with Embedded Snapshots *)
3+
(*_ SPDX-FileCopyrightText: 2025-2026 Mathieu Barbin <mathieu.barbin@gmail.com> *)
4+
(*_ SPDX-License-Identifier: LGPL-3.0-or-later WITH LGPL-3.0-linking-exception *)
5+
(*_********************************************************************************)
Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,61 @@
11
# Code Blocks
22

3-
Use `@mdexp.code` to include code examples in your documentation.
4-
The extracted code is wrapped in a fenced code block, with `ocaml`
5-
as the default language.
3+
The `@mdexp.code` directive marks a region of code for inclusion in
4+
the documentation. The extracted code is wrapped in a fenced code
5+
block, tagged with the host language detected from the file
6+
extension (e.g. `.ml``ocaml`).
67

7-
## Code in Comments
8+
## Including compilable code
89

9-
Write code inside comment wrappers. mdexp strips the markers:
10+
Code between `@mdexp.code` and the next directive is real,
11+
compilable OCaml. The compiler type-checks it along with the
12+
rest of the file. Here is what the source looks like:
1013

1114
```ocaml
1215
(* @mdexp.code *)
13-
(* let greeting = "Hello, World!" *)
14-
(* let () = print_endline greeting *)
16+
let answer = 42
17+
let question_of_life () = Printf.printf "The answer is %d\n" answer
1518
(* @mdexp.end *)
1619
```
1720

18-
This produces:
21+
And here is the markdown that mdexp produces from it:
1922

2023
```ocaml
21-
let greeting = "Hello, World!"
22-
let () = print_endline greeting
24+
let answer = 42
25+
let question_of_life () = Printf.printf "The answer is %d\n" answer
2326
```
2427

25-
## Actual Code
28+
Code outside the directives is invisible to the document but still
29+
compiled and tested. For instance, the function above is
30+
exercised in an expect test that does not appear in the output.
2631

27-
Code between the directives can also be real, compilable OCaml ---
28-
not wrapped in comments. This is useful when you want the compiler
29-
to type-check your documentation examples:
32+
## Prose to code transition
33+
34+
A block comment can transition directly from prose into a code
35+
block by placing `@mdexp.code` before the closing comment marker.
36+
This avoids the need for a separate comment:
3037

3138
```ocaml
32-
(* @mdexp.code *)
33-
let answer = 42
34-
let question_of_life () = Printf.printf "The answer is %d\n" answer
39+
(* @mdexp
40+
Here is an example:
41+
@mdexp.code *)
42+
let greet name = Printf.printf "Hello, %s!\n" name
3543
(* @mdexp.end *)
3644
```
3745

38-
The code is included as-is:
46+
This produces the prose paragraph followed by the code block in a
47+
single, natural flow:
48+
49+
Here is an example:
3950

4051
```ocaml
41-
let answer = 42
42-
let question_of_life () = Printf.printf "The answer is %d\n" answer
52+
let greet name = Printf.printf "Hello, %s!\n" name
4353
```
4454

45-
## Explicit Language
55+
## Explicit language
4656

47-
Override the default language tag with `@mdexp.code { lang: "<lang>" }`:
57+
The language tag is inferred from the file extension. You can override it
58+
with `@mdexp.code { lang: "<lang>" }`:
4859

4960
```ocaml
5061
(* @mdexp.code { lang: "bash" } *)
@@ -58,22 +69,6 @@ This produces a `bash`-tagged code fence:
5869
opam install mdexp
5970
```
6071

61-
## Prose to Code Transition
62-
63-
A block comment can transition directly from prose into a code block
64-
using `@mdexp.code` before the block comment closes:
65-
66-
```ocaml
67-
(* @mdexp
68-
Here is an example:
69-
@mdexp.code *)
70-
let hello () = print_endline "Hello!"
71-
(* @mdexp.end *)
72-
```
73-
74-
This outputs the prose followed by the code block, without needing
75-
separate comments:
76-
77-
```ocaml
78-
let (_ : string) = "Hello!"
79-
```
72+
Note that the bash command is written inside OCaml comments. This is
73+
one case where commented code makes sense, since the content is not
74+
OCaml and cannot be compiled.

0 commit comments

Comments
 (0)