[OCaml] Org mode parser.
docs add README.org with usage, supported syntax, and architecture
README.org
@@ -0,0 +1,201 @@
1
Added:
#+TITLE: ocaml-org
2
Added:
#+AUTHOR: Marius PETER
3
Added:
#+DATE: <2026-08-02 Sun>
4
Added:
5
Added:
A lightweight OCaml library for parsing [[https://orgmode.org][Org Mode]] documents into a
6
Added:
strongly-typed semantic abstract syntax tree.
7
Added:
8
Added:
* Overview
9
Added:
10
Added:
=ocaml-org= parses Org Mode documents covering approximately 80% of
11
Added:
commonly used syntax. It produces a semantic AST suitable for
12
Added:
downstream consumers such as HTML renderers, document analysis tools,
13
Added:
and formatters.
14
Added:
15
Added:
The parser is permissive: malformed input degrades gracefully into
16
Added:
plain text rather than raising exceptions.
17
Added:
18
Added:
* Installation
19
Added:
20
Added:
#+begin_src sh
21
Added:
opam install ocaml-org
22
Added:
#+end_src
23
Added:
24
Added:
Or add to your =dune-project=:
25
Added:
26
Added:
#+begin_src scheme
27
Added:
(depends (ocaml-org (>= 0.1.0)))
28
Added:
#+end_src
29
Added:
30
Added:
* Usage
31
Added:
32
Added:
#+begin_src ocaml
33
Added:
let doc = Ocaml_org.Org.parse {|
34
Added:
#+TITLE: Hello
35
Added:
* TODO [#A] First heading :tag1:tag2:
36
Added:
Some /italic/ and *bold* text with a [[https://example.com][link]].
37
Added:
|}
38
Added:
39
Added:
(* doc : Ocaml_org.Ast.document *)
40
Added:
#+end_src
41
Added:
42
Added:
This produces a fully typed AST. Here's what the result looks like:
43
Added:
44
Added:
#+begin_src ocaml
45
Added:
{ directives = [ { dir_key = "TITLE"; dir_value = "Hello" } ];
46
Added:
preamble = [];
47
Added:
headings =
48
Added:
[ { level = 1;
49
Added:
todo = Some "TODO";
50
Added:
priority = Some 'A';
51
Added:
commented = false;
52
Added:
title = [ Plain "First heading" ];
53
Added:
tags = [ "tag1"; "tag2" ];
54
Added:
planning = None;
55
Added:
properties = [];
56
Added:
heading_contents =
57
Added:
[ Paragraph
58
Added:
([ Italic [ Plain "italic" ];
59
Added:
Plain " and ";
60
Added:
Bold [ Plain "bold" ];
61
Added:
Plain " text with a ";
62
Added:
Link { target = Url "https://example.com";
63
Added:
description = Some [ Plain "link" ] };
64
Added:
Plain "." ],
65
Added:
[]) ];
66
Added:
children = [] } ] }
67
Added:
#+end_src
68
Added:
69
Added:
Every Org construct maps to a typed OCaml value — no stringly-typed
70
Added:
property bags, no =node.kind= dispatching, no unchecked casts.
71
Added:
Pattern-match on what you need:
72
Added:
73
Added:
#+begin_src ocaml
74
Added:
(* Extract all TODO headings with their deadlines *)
75
Added:
let todos_with_deadlines doc =
76
Added:
let rec collect acc headings =
77
Added:
List.fold_left (fun acc h ->
78
Added:
let acc = match h.todo, h.planning with
79
Added:
| Some kw, Some { deadline = Some ts; _ } -> (kw, h.title, ts) :: acc
80
Added:
| _ -> acc
81
Added:
in
82
Added:
collect acc h.children
83
Added:
) acc headings
84
Added:
in
85
Added:
collect [] doc.headings
86
Added:
87
Added:
(* Find all source blocks with a given language *)
88
Added:
let find_src_blocks ~language doc =
89
Added:
let rec from_elements acc = function
90
Added:
| [] -> acc
91
Added:
| Block (Src_block src) :: rest when src.src_language = Some language ->
92
Added:
from_elements (src :: acc) rest
93
Added:
| _ :: rest -> from_elements acc rest
94
Added:
in
95
Added:
(* Search in headings recursively *)
96
Added:
let rec from_headings acc = function
97
Added:
| [] -> acc
98
Added:
| h :: rest ->
99
Added:
let acc = from_elements acc h.heading_contents in
100
Added:
let acc = from_headings acc h.children in
101
Added:
from_headings acc rest
102
Added:
in
103
Added:
from_headings (from_elements [] doc.preamble) doc.headings
104
Added:
#+end_src
105
Added:
106
Added:
The public API is minimal:
107
Added:
108
Added:
- =Ocaml_org.Org.parse : string -> Ocaml_org.Ast.document=
109
Added:
- =Ocaml_org.Org.parse_with_diagnostics : string -> Ocaml_org.Ast.document * Ocaml_org.Diagnostic.t list=
110
Added:
111
Added:
* Supported Syntax
112
Added:
113
Added:
** Structural
114
Added:
115
Added:
- Document preamble (zeroth section)
116
Added:
- Heading hierarchy with TODO states, priorities, tags
117
Added:
- Planning (=SCHEDULED=, =DEADLINE=, =CLOSED=)
118
Added:
- Property drawers
119
Added:
- Ordinary drawers
120
Added:
- Paragraphs
121
Added:
- Unordered, ordered, and descriptive lists
122
Added:
- Source, example, export, comment, quote, center, verse, and custom blocks
123
Added:
- Org tables
124
Added:
- Keywords and affiliated keywords
125
Added:
- Comments, fixed-width areas, horizontal rules
126
Added:
127
Added:
** Inline
128
Added:
129
Added:
- Bold, italic, underline, strikethrough
130
Added:
- Code (=~code~=), verbatim (~=verbatim=~)
131
Added:
- Regular links (=[[target][description]]=)
132
Added:
- Angle links (=<proto:path>=)
133
Added:
- Plain links (auto-detected =https://...=)
134
Added:
- Active and inactive timestamps, timestamp ranges
135
Added:
- Explicit line breaks
136
Added:
137
Added:
** Deferred
138
Added:
139
Added:
The following are out of scope for the initial release:
140
Added:
141
Added:
- Citations, radio targets/links, macros
142
Added:
- Complex footnotes, inline Babel calls, inline source blocks
143
Added:
- Diary sexps, inlinetasks, table formulas
144
Added:
- Entity expansion, link resolution, Babel execution
145
Added:
- Incremental parsing, lossless round-tripping
146
Added:
147
Added:
* Architecture
148
Added:
149
Added:
#+begin_example
150
Added:
Input string
151
Added:
│
152
Added:
├─→ Prescan (collect #+TODO declarations)
153
Added:
│
154
Added:
├─→ Structural Lexer (handwritten, line-oriented)
155
Added:
│ │
156
Added:
│ └─→ Structural Parser (Menhir grammar → Raw_ast)
157
Added:
│ │
158
Added:
│ └─→ Normalization (heading hierarchy, list nesting,
159
Added:
│ inline parsing, affiliated keyword attachment)
160
Added:
│ │
161
Added:
│ └─→ Ast.document
162
Added:
│
163
Added:
└─→ Inline Lexer (handwritten, pre-scans emphasis pairs)
164
Added:
│
165
Added:
└─→ Inline Parser (Menhir grammar → Ast.inline list)
166
Added:
#+end_example
167
Added:
168
Added:
* Dependencies
169
Added:
170
Added:
| Dependency | Role | Type |
171
Added:
|------------+-----------------+------------|
172
Added:
| OCaml | Language | >= 5.0 |
173
Added:
| Dune | Build system | >= 3.0 |
174
Added:
| Menhir | Parser generator | >= 20231231 |
175
Added:
| MenhirLib | Parser runtime | (with Menhir) |
176
Added:
| Alcotest | Test framework | with-test |
177
Added:
178
Added:
All dependencies are pure OCaml — no C stubs, no system libraries,
179
Added:
no external runtimes.
180
Added:
181
Added:
* Development
182
Added:
183
Added:
Requires OCaml 5.5.0 and opam.
184
Added:
185
Added:
#+begin_src sh
186
Added:
# Create local switch
187
Added:
opam switch create . ocaml-base-compiler.5.5.0
188
Added:
189
Added:
# Install dependencies
190
Added:
opam install menhir alcotest --yes
191
Added:
192
Added:
# Build
193
Added:
dune build
194
Added:
195
Added:
# Run tests
196
Added:
dune runtest
197
Added:
#+end_src
198
Added:
199
Added:
* License
200
Added:
201
Added:
ISC