docs add README.org with usage, supported syntax, and architecture

Commit
effcd39532ac66559ecbd9a494ff8bcfd5da5865
Author
Kiro (high) <noreply@kiro.dev>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
README.org
index 00000000..685b2f53 000000..100644
@@ -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