1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! Multi-format interoperability for Lex documents
//!
//! This crate provides a uniform interface for converting between Lex AST and various document
//! formats (Markdown, HTML, Pandoc JSON, etc.).
//!
//! TLDR: For format authors:
//! - Babel never parses or serializes any format, but instead relies on the format's libraries
//! - The conversion should be by converting to the IR, running the common code in common if relevant (it usually is), then to the AST of the target format.
//! - We should use the testing harness (see lex-core/src/lex/testing.rs) to load documents and process them into ASTs.
//! - Each element should use the harness above and the available file for isolated element testing with unit tests (load with the lib, assert with AST / IR)
//! - Each format should have trifecta unit tested in from and to formats to lex.
//! - Each format should have a kitchensink unit tested in from and to formats to lex
//! - Read the README.lex for full details
//!
//! Architecture
//!
//! The goal here is to, as much as possible, split what is the common logic for multiple formats
//! conversions into a format agnostic layer. This is done by using the IR representation (./ir/mod.rs),
//! and having the common code in ./common/mod.rs. This allows for the format specific code to be focused on the data format transformations, while having a strong, focused core that can be well tested in isolation.
//!
//! This is a pure lib, that is, it powers the lexd CLI but is shell agnostic, that is no code
//! should be written that supposes a shell environment, be it to std print, env vars etc.
//!
//! The file structure:
//! .
//! ├── error.rs
//! ├── format.rs # Format trait definition
//! ├── registry.rs # FormatRegistry for discovery and selection
//! ├── formats
//! │ └── <format>
//! │ ├── parser.rs # Parser implementation
//! │ ├── serializer.rs # Serializer implementation
//! │ └── mod.rs
//! ├── lib.rs
//! ├── ir # Intermediate Representation
//! │ ├── nodes.rs # IR data model (DocNode, InlineContent, etc.)
//! │ ├── events.rs # Flat event stream representation
//! │ ├── from_lex.rs # Lex AST → IR conversion
//! │ └── to_lex.rs # IR → Lex AST conversion
//! └── common # Common mapping code
//! ├── flat_to_nested.rs # Events → IR tree (with auto-closing)
//! ├── nested_to_flat.rs # IR tree → Events
//! └── verbatim/ # Verbatim handler registry (doc.table, doc.image, etc.)
//!
//! Testing
//! tests
//! └── <format>
//! ├── <testname>.rs
//! └── fixtures
//! ├── <docname>.<format>
//! ├── kitchensink.html
//! ├── kitchensink.lex
//! └── kitchensink.md
//!
//! Note that rust does not by default discover tests in subdirectories, so we need to include these
//! in the mod.
//!
//!
//! Core Algorithms
//!
//! The most complex part of the work is reconstructing a nested representation from a flat document, followed by the reverse operations. For this reason we have a common IR (./ir/mod.rs) that is used for all formats.
//! Over this representation we implement both algorithms (see ./common/flat_to_nested.rs and ./common/nested_to_flat.rs).
//! This means that all the heavy lifting is done by a core, well tested and maintained module,
//! freeing format adaptations to be focused on the simpler data format transformations.
//!
//!
//! Formats
//!
//! Format specific capabilities are implemented with the Format trait. Formats should have a
//! parse() and serialize() method, a name and file extensions. See the trait def [./format.rs]
//! - Format trait: Uniform interface for all formats (parsing and/or serialization)
//! - FormatRegistry: Centralized discovery and selection of formats
//! - Format implementations: Concrete implementations for each supported format
//!
//!
//! The Lex Format
//!
//! The Lex format itself is implemented as a format, see ./formats/lex/mod.rs, which allows for
//! a homogeneous API where all formats have identical interfaces.
//!
//! Note that Lex is a more expressive format than most, which means that converting from Lex is
//! simple, but always lossy. In particular converting to Lex requires some consideration on how
//! to best represent the author's intent.
//!
//! This means that full format interop round tripping is not possible.
//!
//! v1 Interop Scope
//!
//! The full contributor-facing version of this lives in
//! `comms/docs/interop-scope.lex` (browse on GitHub:
//! <https://github.com/lex-fmt/comms/blob/main/docs/interop-scope.lex>).
//! The short version:
//!
//! | Tier | Format | Export | Import | Notes |
//! |-------------------|------------|--------|--------|------------------------------------------------------|
//! | Core | Markdown | ✓ | ✓ | Lingua franca. Round-trip is the bar. |
//! | Core | HTML | ✓ | — | Publishing target; PDF and editor previews consume. |
//! | Core | PDF | ✓ | — | Headless Chrome over the HTML output. |
//! | Core | PNG | ✓ | — | Headless Chrome screenshot of the HTML output. |
//! | Stretch | HTML | — | ✓ | After core lands. |
//! | Experimental | RFC XML | — | ✓ | Proof-of-concept; no bespoke investment. |
//! | Planned | Pandoc | — | — | Bridge to DOCX/EPUB/RST/Org/etc. Not started. |
//! | Planned | LaTeX | ✓ | — | Export only, via Pandoc once Pandoc lands. |
//! | Category error | PDF import | — | — | **Will not be implemented.** See below. |
//!
//! **PDF import is a category error, not a postponed task.** PDF is a
//! presentation format — paragraphs, headings, and lists are
//! reconstructed heuristically from layout (font size, indentation,
//! glyph positions), and no rule-based importer recovers that
//! reliably. Pandoc punts to `pdftotext` for the same reason. This
//! will not be implemented, ever, regardless of adoption. ML-based
//! extraction (Marker, Nougat, Mathpix, Grobid) is a different
//! product category and out of scope for lex-babel. Do not list,
//! advertise, or design around the possibility.
//!
//! Diagnostic outputs (`tag`, `treeviz`, `linetreeviz`) sit outside
//! this tiering — they're AST visualizers used by `lexd inspect`,
//! not interop targets.
//!
//! Library Choices
//!
//! This, not being Lex's core, means that we will offload as much as possible to better, specialized crates
//! for each format. The scope here is mainly to adapt the ASTs from Lex to the format or vice
//! versa. For example we never write the serializer for, say, Markdown, but pass the AST to the
//! Markdown library. To support a format inbound, we write the format AST → Lex AST adapter.
//! Likewise, for outbound formats we will do the reverse, converting from the Lex AST to the
//! format's.
//!
//! As much as possible, we will use Rust crates, and avoid shelling out and having outside dependencies.
//!
pub use FormatError;
pub use ;
pub use FormatRegistry;
/// Converts a lex document to the Intermediate Representation (IR).
///
/// # Information Loss
///
/// The IR is a simplified, semantic representation. The following
/// Lex information is lost during conversion:
/// - Blank line grouping (BlankLineGroup nodes)
/// - Source positions and token information
/// - Comment annotations at document level
///
/// For lossless Lex representation, use the AST directly.
///
/// Uses the shared default registry ([`default_registry()`]) for
/// verbatim-label `on_resolve` dispatch. Callers that need to plug
/// in third-party namespaces (lex-cli with `boot_registry`, lex-lsp,
/// embedders) should call [`to_ir_with_registry`] directly.
/// Converts a Lex AST to its IR representation, dispatching verbatim
/// labels through the supplied registry's `on_resolve` hooks.
///
/// This is the explicit-registry variant of [`to_ir`] — use it when
/// the caller has its own registry (with third-party namespaces
/// registered) and wants those handlers to participate in IR
/// construction.
/// Process-wide registry with the built-in `lex.*` schemas
/// registered. Used by `to_ir` and `to_lex_document` for callers
/// that don't supply their own. Constructed lazily on first call.
///
/// Filesystem access is plumbed through a no-op loader — this
/// registry doesn't resolve `lex.include` (the to_ir/to_lex paths
/// never invoke `on_resolve` for that label; includes are resolved
/// elsewhere in the pipeline).
/// Converts an IR document back to Lex AST.
///
/// This is useful for round-trip conversions: Format → IR → Lex.