View raw

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

opam install org

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:

Supported Syntax

Structural

Inline

Deferred

The following are out of scope for the initial release:

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
opam switch create . ocaml-base-compiler.5.5.0

# Install dependencies
opam install menhir alcotest --yes

# Build
dune build

# Run tests
dune runtest

License

ISC