Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_text/src/fmt/doc.rs

5.3 KiB, 3 runs

created by r1870400018:11624, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Layout algebra for code formatting.
2//!
3//! Based on Wadler's "A Prettier Printer" (2003) with extensions for
4//! column alignment. The algebra has a small number of constructors
5//! that can express any formatting pattern:
6//!
7//! - `Text` — literal text, never broken.
8//! - `Line` — a potential line break (rendered as a space when the
9//! enclosing `Group` fits on one line, a newline otherwise).
10//! - `HardLine` — an unconditional line break.
11//! - `Nest` — increase indentation for the nested document.
12//! - `Group` — try to fit the contents on one line; break if too wide.
13//! - `Concat` — sequential composition.
14//! - `Align` — align continuation lines to the current column.
15//! - `IfBreak` — choose between two documents depending on whether
16//! the enclosing group was broken.
17//!
18
19/// A layout document. Constructed with the free functions below,
20/// then rendered to a string by the renderer.
21#[derive(Clone, Debug)]
22pub enum Doc {
23 /// Nothing.
24 Empty,
25 /// Literal text (must not contain newlines).
26 Text(String),
27 /// A potential line break. Rendered as a single space when the
28 /// enclosing `Group` fits, or a newline + indentation otherwise.
29 Line,
30 /// An unconditional line break.
31 HardLine,
32 /// Increase indentation by `n` spaces for the nested document.
33 Nest(u16, Box<Doc>),
34 /// Try to fit everything on one line. If it doesn't fit within
35 /// the remaining width, break at every `Line` inside.
36 Group(Box<Doc>),
37 /// Sequential composition.
38 Concat(Vec<Doc>),
39 /// Align continuation lines to the current column position.
40 Align(Box<Doc>),
41 /// Emit `flat` when the enclosing group is flat (fits on one
42 /// line), `broken` when the group was broken across lines.
43 IfBreak {
44 flat: Box<Doc>,
45 broken: Box<Doc>,
46 },
47}
48
49// ── Constructors ─────────────────────────────────────────────────
50
51/// Empty document.
52pub fn empty() -> Doc { Doc::Empty }
53
54/// Literal text (must not contain newlines).
55pub fn text<S: Into<String>>(s: S) -> Doc { Doc::Text(s.into()) }
56
57/// A potential line break (space when flat, newline when broken).
58pub fn line() -> Doc { Doc::Line }
59
60/// An unconditional line break.
61pub fn hardline() -> Doc { Doc::HardLine }
62
63/// A soft line break: nothing when the group is flat, a newline
64/// when the group breaks. Use inside brackets: `(softline body softline)`.
65pub fn softline() -> Doc {
66 Doc::IfBreak {
67 flat: Box::new(Doc::Empty),
68 broken: Box::new(Doc::Line),
69 }
70}
71
72/// Increase indentation for the nested document.
73pub fn nest(indent: u16, doc: Doc) -> Doc {
74 Doc::Nest(indent, Box::new(doc))
75}
76
77/// Try to fit the document on one line.
78pub fn group(doc: Doc) -> Doc {
79 Doc::Group(Box::new(doc))
80}
81
82/// Align continuation lines to the current column.
83pub fn align(doc: Doc) -> Doc {
84 Doc::Align(Box::new(doc))
85}
86
87/// Emit different documents depending on whether the enclosing
88/// group was broken.
89pub fn if_break(flat: Doc, broken: Doc) -> Doc {
90 Doc::IfBreak {
91 flat: Box::new(flat),
92 broken: Box::new(broken),
93 }
94}
95
96/// Concatenate a sequence of documents.
97pub fn concat(docs: Vec<Doc>) -> Doc {
98 // Flatten nested concats and remove empties.
99 let mut flat = Vec::new();
100 for d in docs {
101 match d {
102 Doc::Empty => {}
103 Doc::Concat(inner) => flat.extend(inner),
104 other => flat.push(other),
105 }
106 }
107 match flat.len() {
108 0 => Doc::Empty,
109 1 => flat.into_iter().next().unwrap_or(Doc::Empty),
110 _ => Doc::Concat(flat),
111 }
112}
113
114/// Concatenate two documents.
115pub fn cat(a: Doc, b: Doc) -> Doc {
116 concat(vec![a, b])
117}
118
119// ── Convenience combinators ──────────────────────────────────────
120
121/// Join documents with a separator between each pair.
122pub fn join(sep: Doc, docs: Vec<Doc>) -> Doc {
123 let mut parts = Vec::with_capacity(docs.len() * 2);
124 let mut first = true;
125 for d in docs {
126 if !first {
127 parts.push(sep.clone());
128 }
129 parts.push(d);
130 first = false;
131 }
132 concat(parts)
133}
134
135/// Join documents with `line()` between each pair (soft breaks).
136pub fn join_lines(docs: Vec<Doc>) -> Doc {
137 join(line(), docs)
138}
139
140/// Text followed by a space.
141pub fn texts<S: Into<String>>(s: S) -> Doc {
142 cat(text(s), text(" "))
143}
144
145/// Surround a document with left and right text, indenting the body.
146/// Typically used for bracketed constructs: `surround("(", ")", 4, body)`.
147pub fn surround(
148 left: &str,
149 right: &str,
150 indent: u16,
151 body: Doc,
152) -> Doc {
153 group(concat(vec![
154 text(left),
155 nest(indent, concat(vec![line(), body])),
156 line(),
157 text(right),
158 ]))
159}
160
161/// Like `surround` but with a hardline before the closing bracket,
162/// producing the "tall" layout even when the group fits.
163pub fn surround_hard(
164 left: &str,
165 right: &str,
166 indent: u16,
167 body: Doc,
168) -> Doc {
169 concat(vec![
170 text(left),
171 nest(indent, concat(vec![hardline(), body])),
172 hardline(),
173 text(right),
174 ])
175}
176
177/// A trailing comma: present when broken, absent when flat.
178pub fn trailing_comma() -> Doc {
179 if_break(empty(), text(","))
180}