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
//! Intermediate Representation (IR) for lex documents.
//!
//! This module defines a format-agnostic representation of a lex document,
//! designed to facilitate conversion to various output formats like HTML,
//! Markdown, etc.
//!
//! # Round-trip contract
//!
//! `from_lex(to_lex(ir))` and `to_lex(from_lex(ast))` are structurally
//! lossless for the v1 element set with the explicit exceptions listed
//! below. Per-`DocNode` IR round-trip proptests live in
//! `crates/lex-babel/tests/ir_round_trip_proptest/mod.rs` (the
//! `tests/round_trip_proptest/` suite is the older AST round-trip
//! coverage) and are the executable form of this contract — they pin
//! the behaviour so changes here can't drift silently.
//!
//! ## Accepted losses (v1)
//!
//! These are documented, deliberate gaps where the IR doesn't faithfully
//! mirror the lex-core AST. Each is paired with a proptest pinning the
//! actual behaviour.
//!
//! - **Heading levels.** [`nodes::Heading`] carries a `level: usize`
//! field, but `to_lex_heading` reconstructs nesting from parent
//! context rather than the level itself — a heading rendered out of
//! its session context can shift level. See `to_lex.rs`'s
//! `to_lex_heading` for the reconstruction logic.
//! - **Inline-format nesting.** `Bold([Italic([Text("x")])])` flattens
//! to the text `*_x_*` on the way back through
//! `to_lex_inline_content` (see `to_lex.rs`). Round-tripping nested
//! inline formatting strictly is out of scope for v1; the proptests
//! assert text equivalence rather than structural equivalence inside
//! inline nesting.
//! - **Video / Audio inline.** [`nodes::DocNode::Video`] and
//! [`nodes::DocNode::Audio`] exist as block-level nodes; there are
//! no `InlineContent::Video` / `InlineContent::Audio` variants.
//! Inline-positioned video/audio (rare in practice) are unrepresentable
//! in v1.
//! - **Table caption + fullwidth.** [`nodes::Table`] carries `caption`
//! and `fullwidth` fields, but the pipe-table verbatim form emitted
//! by `to_lex_table` (see `common/verbatim/table.rs`) does not encode
//! them. A `Table { caption: Some(_), … }` round-trips with
//! `caption: None`. Footnotes survive via nested `lex.footnote.*`
//! annotations.
//! - **Anchored references resolve to `Link`.** An inline
//! `InlineContent::Reference { kind: Url|File|Session|General, .. }`
//! that lex-core resolved a word anchor for (references-general.lex
//! §2.3.1) is rewritten to `InlineContent::Link { text, href }` by
//! `ir/anchoring.rs::apply_word_anchors` on every lex → IR conversion,
//! and a reference line's whole-element / self-link anchor likewise
//! produces a `Link`. This is a deliberate shape change (anchor
//! resolution), not information loss in user-visible terms: the
//! reference target survives as `Link.href`. But the typed
//! `ReferenceType` does *not* survive — `Link` carries only strings,
//! and consumers that want the classification back must re-infer it
//! from `href`. Round-trip tests asserting "same shape both sides"
//! must account for the rewrite.
//!
//! Heading, inline-format nesting, table caption / fullwidth, and the
//! Reference → Link rewrite are the only *content-shape* divergences.
//! Everything else — reference sub-type classification (#614), verbatim
//! closing-data parameters (#614 follow-up), annotation
//! [`nodes::LabelForm`] (#593), table footnotes, document annotations
//! (#570 Phase 3b) — round-trips losslessly as of the #613 symmetry
//! work-stream.