lib/pretty/core/src/root.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 //! Pretty is a width-aware document layout and structured output package.
2 //!
3 //! ### Design rationale and problem model
4 //!
5 //! Pretty printing separates declarative document structure from geometric
6 //! layout to resolve formatting choices without output backtracking:
7 //! formatters cannot determine whether an expression fits on a single line
8 //! until both the expression and its trailing syntactic context are known.
9 //! An algebraic tree (`Doc`) defers line-break selection to the renderer.
10 //!
11 //! The package enforces strict separation between construction and rendering:
12 //!
13 //! - Construction: `Builder` allocates AST nodes using any caller-supplied
14 //! allocator, while text nodes borrow source slices. Callers bear the
15 //! obligation of ensuring both allocated nodes and borrowed slices remain
16 //! valid until rendering finishes.
17 //! - Traversal: The layout pass (`write`, `writeWithState`) performs zero
18 //! dynamic allocation, streaming directly to a caller-owned `*std.Io.Writer`.
19 //! Callers retain control over output buffering, flush timing, and I/O side
20 //! effects, trading automated memory management for zero-allocation
21 //! rendering. Where an owned slice is required, `renderAlloc` manages an
22 //! allocating writer.
23 //!
24 //! ### Continuation-sensitive layout and mode inheritance
25 //!
26 //! Line-break decisions require continuation lookahead: evaluating a group
27 //! in isolation is insufficient because trailing tokens on the same line
28 //! (such as closing delimiters or commas) could force an immediate overflow.
29 //! The renderer therefore tests whether the candidate and its inline sibling
30 //! continuation fit the saturating remaining width `width -| col`.
31 //!
32 //! Mode inheritance is deliberately asymmetric:
33 //!
34 //! - A flat parent forces all descendant groups into flat mode, because an
35 //! inner line break would contradict the parent's single-line commitment.
36 //! - A broken parent permits nested groups to choose independently, allowing
37 //! localized compaction of sub-expressions within an expanded structure.
38 //!
39 //! Lookahead revisits subtrees to measure flat width prior to emission, and
40 //! recursive descent operates without an explicit stack ceiling. Unconditional
41 //! breaks (`hardline`) emit a newline followed by the current nesting indentation
42 //! in all modes.
43 //!
44 //! ### Layout metrics, control breaks, and styling
45 //!
46 //! Column advancement strictly measures raw byte length (`bytes.len`),
47 //! omitting Unicode scalar, grapheme, and terminal display-width semantics.
48 //! Raw escape bytes inside text count as bytes. When indivisible text exceeds
49 //! the margin, it emits across it without error, prioritizing semantic data
50 //! preservation over rigid column boundaries.
51 //!
52 //! Only carriage returns and line feeds reset the column:
53 //!
54 //! - In text, `\r` and `\n` reset column tracking, where CR alone produces
55 //! zero line feeds. They do not insert structural indentation.
56 //! - In soft breaks, alternatives containing CR or LF force broken mode to
57 //! prevent broken control characters from polluting flat layout.
58 //!
59 //! Semantic styling (`styled`) emits ANSI sequences under `ColorMode.ansi`.
60 //! These escape sequences consume zero layout columns, ensuring monochrome
61 //! and styled outputs make identical line-break decisions.
62 //!
63 //! ### Worked example: Why continuation lookahead matters
64 //!
65 //! The trailing-continuation witness from `test.zig` demonstrates why groups
66 //! cannot evaluate fit in isolation:
67 //!
68 //! ```zig
69 //! const grouped = try builder.group(try builder.concat(&.{
70 //! builder.text("abc"),
71 //! pretty.softline,
72 //! builder.text("de"),
73 //! }));
74 //! const doc = try builder.concat(&.{ grouped, builder.text("XY") });
75 //! ```
76 //!
77 //! At width 6, `grouped` alone has a flat width of 6 (`"abc de"`).
78 //! Evaluated in isolation, it would fit flat. However, trailing sibling
79 //! `"XY"` shares the same output line without an intervening break,
80 //! requiring 8 columns total.
81 //!
82 //! Continuation lookahead inspects `"XY"`, detects that $6 + 2 > 6$, and
83 //! forces `grouped` to break, emitting `"abc\ndeXY"` instead of overflowing
84 //! to `"abc deXY"`.
85 //!
86 //! ### Verification boundary
87 //!
88 //! Evidence is partitioned across formal models and test suites:
89 //!
90 //! - Formal model: The Lean specification in `verification/pretty/` proves
91 //! compositionality and layout properties for finite trees over abstract
92 //! byte classes (`cell`, `LF`, `CR`) and unbounded natural numbers (`Nat`).
93 //! It assumes successful writes and omits continuation lookahead, machine
94 //! `usize` saturation, and display width. No formal theorem proves that the
95 //! Zig implementation refines the Lean specification.
96 //! - Empirical and capacity evidence: Automated unit tests in `test.zig` and
97 //! Hypothesis property tests in `src/properties/` test ANSI neutrality,
98 //! saturating arithmetic, and position composition, while bounded table
99 //! capacity contracts are declared in `compact/table.zig`.
100
101 const types = @import("types.zig");
102 const builder_product = @import("builder.zig");
103 const render = @import("render.zig");
104
105 pub const json = @import("json/root.zig");
106 pub const compact = @import("compact/root.zig");
107 pub const diagnostic = @import("diagnostic.zig");
108
109 pub const default_width = types.default_width;
110 pub const LayoutOptions = types.LayoutOptions;
111 pub const ColorMode = types.ColorMode;
112 pub const Style = types.Style;
113 pub const Doc = types.Doc;
114 pub const Nested = types.Nested;
115 pub const Styled = types.Styled;
116 pub const WriteState = types.WriteState;
117 pub const TextWriter = @import("stream.zig").TextWriter;
118 pub const softline = types.softline;
119 pub const softbreak = types.softbreak;
120 pub const hardline = types.hardline;
121 pub const Builder = builder_product.Builder;
122 pub const RenderError = render.Error;
123 pub const FlatFitWriter = render.FlatFitWriter;
124 pub const renderAlloc = render.renderAlloc;
125 pub const write = render.write;
126 pub const writeWithState = render.writeWithState;