[OCaml] Org mode parser.
org
Table of Contents
A lightweight OCaml library for parsing Org Mode documents into a strongly-typed semantic abstract syntax tree.
Overview
org parses Org Mode documents covering approximately 80% of commonly used syntax. It produces a semantic AST suitable for downstream consumers such as HTML renderers, document analysis tools, and formatters.
The parser is permissive: malformed input degrades gracefully into plain text rather than raising exceptions.
Installation
opaminstallorg
Or add to your dune-project:
(depends (org (>= 0.1.0)))
Usage
let doc = Org.parse {|
,#+TITLE: Hello
,* TODO [#A] First heading :tag1:tag2:
Some /italic/ and *bold* text with a [[https://example.com][link]].
|}
(* doc : Org.Ast.document *)
This produces a fully typed AST. Here's what the result looks like:
{ directives = [ { dir_key = "TITLE"; dir_value = "Hello" } ];
preamble = [];
headings =
[ { level = 1;
todo = Some "TODO";
priority = Some 'A';
commented = false;
title = [ Plain "First heading" ];
tags = [ "tag1"; "tag2" ];
planning = None;
properties = [];
contents =
[ Paragraph
([ Italic [ Plain "italic" ];
Plain " and ";
Bold [ Plain "bold" ];
Plain " text with a ";
Link { target = Url "https://example.com";
description = Some [ Plain "link" ] };
Plain "." ],
[]) ];
children = [] } ] }
Every Org construct maps to a typed OCaml value — no stringly-typed property bags, no node.kind dispatching, no unchecked casts. Pattern-match on what you need:
(* Extract all TODO headings with their deadlines *)
let todos_with_deadlines doc =
let rec collect acc headings =
List.fold_left (fun acc h ->
let acc = match h.todo, h.planning with
| Some kw, Some { deadline = Some ts; _ } -> (kw, h.title, ts) :: acc
| _ -> acc
in
collect acc h.children
) acc headings
in
collect [] doc.headings
(* Find all source blocks with a given language *)
let find_src_blocks ~language doc =
let rec from_elements acc = function
| [] -> acc
| Block (Src_block src) :: rest when src.src_language = Some language ->
from_elements (src :: acc) rest
| _ :: rest -> from_elements acc rest
in
(* Search in headings recursively *)
let rec from_headings acc = function
| [] -> acc
| h :: rest ->
let acc = from_elements acc h.contents in
let acc = from_headings acc h.children in
from_headings acc rest
in
from_headings (from_elements [] doc.preamble) doc.headings
The public API is minimal:
- Org.parse : string -> Org.Ast.document
Supported Syntax
Structural
- Document preamble (zeroth section)
- Heading hierarchy with TODO states, priorities, tags
- Planning (SCHEDULED, DEADLINE, CLOSED)
- Property drawers
- Ordinary drawers
- Paragraphs
- Unordered, ordered, and descriptive lists
- Source, example, export, comment, quote, center, verse, and custom blocks
- Org tables
- Keywords and affiliated keywords
- Comments, fixed-width areas, horizontal rules
Inline
- Bold, italic, underline, strikethrough
- Code (~code~), verbatim (
=verbatim=) - Regular links ([[target][description]])
- Angle links (<proto:path>)
- Plain links (auto-detected https://...)
- Active and inactive timestamps, timestamp ranges
- Explicit line breaks
Deferred
The following are out of scope for the initial release:
- Citations, radio targets/links, macros
- Complex footnotes, inline Babel calls, inline source blocks
- Diary sexps, inlinetasks, table formulas
- Entity expansion, link resolution, Babel execution
- Incremental parsing, lossless round-tripping
Architecture
#+begin_example Input string │ ├─→ Prescan (collect #+TODO declarations) │ ├─→ Structural Lexer (handwritten, line-oriented) │ │ │ └─→ Structural Parser (Menhir grammar → Raw_ast) │ │ │ └─→ Normalization (heading hierarchy, list nesting, │ inline parsing, affiliated keyword attachment) │ │ │ └─→ Ast.document │ └─→ Inline Lexer (handwritten, pre-scans emphasis pairs) │ └─→ Inline Parser (Menhir grammar → Ast.inline list) #+end_example
Dependencies
| Dependency | Role | Type | |------------+-----------------+------------| | OCaml | Language | > 5.0 | | Dune | Build system | > 3.0 | | Menhir | Parser generator | >= 20231231 | | MenhirLib | Parser runtime | (with Menhir) | | Alcotest | Test framework | with-test |
All dependencies are pure OCaml — no C stubs, no system libraries, no external runtimes.
Development
Requires OCaml 5.5.0 and opam.
# Create local switch
opamswitchcreate.ocaml-base-compiler.5.5.0
# Install dependencies
opaminstallmenhiralcotest--yes
# Build
dunebuild
# Run tests
duneruntest
License
ISC