#+TITLE: org #+AUTHOR: Marius PETER #+DATE: <2026-08-02 Sun> A lightweight OCaml library for parsing [[https://orgmode.org][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 #+begin_src sh opam install org #+end_src Or add to your =dune-project=: #+begin_src scheme (depends (org (>= 0.1.0))) #+end_src * Usage #+begin_src ocaml 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 *) #+end_src This produces a fully typed AST. Here's what the result looks like: #+begin_src ocaml { 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 = [] } ] } #+end_src 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: #+begin_src ocaml (* 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 #+end_src 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 (==) - 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. #+begin_src sh # 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 #+end_src * License ISC