miden_formatting/prettier/
document.rs1use alloc::{
2 borrow::Cow,
3 rc::Rc,
4 string::{String, ToString},
5};
6use core::fmt;
7
8#[derive(Debug, Default, Clone)]
9pub enum Document {
10 #[default]
12 Empty,
13 Newline,
15 Char(char, u32),
20 Text(Cow<'static, str>, u32),
22 Flatten(Rc<Document>),
25 Indent(u32, Rc<Document>),
27 Concat(Rc<Document>, Rc<Document>),
29 Choice(Rc<Document>, Rc<Document>),
32}
33impl Document {
34 pub fn is_empty(&self) -> bool {
36 matches!(self, Self::Empty)
37 }
38
39 pub fn has_leading_newline(&self) -> bool {
43 match self {
44 Self::Empty => false,
45 Self::Newline => true,
46 Self::Char('\n' | '\r', _) => true,
47 Self::Char(..) => false,
48 Self::Text(ref text, _) => text.starts_with(['\n', '\r']),
49 Self::Flatten(doc) => doc.has_leading_newline(),
50 Self::Indent(_, doc) => doc.has_leading_newline(),
51 Self::Concat(a, b) if a.is_empty() => b.has_leading_newline(),
52 Self::Concat(a, _) => a.has_leading_newline(),
53 Self::Choice(..) => false,
56 }
57 }
58}
59impl From<char> for Document {
60 #[inline(always)]
61 fn from(c: char) -> Self {
62 character(c)
63 }
64}
65impl From<&'static str> for Document {
66 #[inline(always)]
67 fn from(s: &'static str) -> Self {
68 const_text(s)
69 }
70}
71impl From<String> for Document {
72 #[inline(always)]
73 fn from(s: String) -> Self {
74 text(s)
75 }
76}
77
78pub fn nl() -> Document {
80 Document::Newline
81}
82
83pub fn display(s: impl ToString) -> Document {
88 let string = Cow::<'static, str>::Owned(s.to_string());
89 text(string)
90}
91
92pub fn character(c: char) -> Document {
94 match c {
95 '\n' => Document::Newline,
96 c => {
97 let width = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0) as u32;
98 Document::Char(c, width)
99 },
100 }
101}
102
103pub fn text(s: impl ToString) -> Document {
108 let string = Cow::<'static, str>::Owned(s.to_string());
109 let mut chars = string.chars();
110 match chars.next() {
111 None => Document::Empty,
112 Some(c) if chars.next().is_none() => character(c),
113 Some(_) => {
114 drop(chars);
115 let width = unicode_width::UnicodeWidthStr::width(string.as_ref()) as u32;
116 Document::Text(string, width)
117 },
118 }
119}
120
121pub fn const_text(s: &'static str) -> Document {
123 let mut chars = s.chars();
124 match chars.next() {
125 None => Document::Empty,
126 Some(c) if chars.next().is_none() => character(c),
127 Some(_) => {
128 drop(chars);
129 let string = Cow::Borrowed(s);
130 let width = unicode_width::UnicodeWidthStr::width(string.as_ref()) as u32;
131 Document::Text(string, width)
132 },
133 }
134}
135
136pub fn split<S: AsRef<str>>(input: S) -> Document {
138 let input = input.as_ref();
139 input
140 .lines()
141 .map(text)
142 .reduce(|acc, doc| match acc {
143 Document::Empty => doc + nl(),
144 other => other + doc + nl(),
145 })
146 .unwrap_or(Document::Empty)
147}
148
149#[inline(always)]
151pub fn concat(left: Document, right: Document) -> Document {
152 left + right
153}
154
155pub fn flatten(doc: Document) -> Document {
161 if doc.is_empty() {
162 return doc;
163 }
164 Document::Flatten(Rc::new(doc))
165}
166
167pub fn indent(indent: u32, doc: Document) -> Document {
174 if doc.is_empty() {
175 return doc;
176 }
177 Document::Indent(indent, Rc::new(doc))
178}
179
180impl core::ops::Add for Document {
181 type Output = Document;
182
183 fn add(self: Document, other: Document) -> Self::Output {
185 if self.is_empty() {
186 return other;
187 }
188 if other.is_empty() {
189 return self;
190 }
191 Document::Concat(Rc::new(self), Rc::new(other))
192 }
193}
194
195impl core::ops::Add<char> for Document {
196 type Output = Document;
197
198 fn add(self: Document, other: char) -> Self::Output {
200 let other = character(other);
201 if self.is_empty() {
202 return other;
203 }
204 if other.is_empty() {
205 return self;
206 }
207 Document::Concat(Rc::new(self), Rc::new(other))
208 }
209}
210
211impl core::ops::Add<Document> for char {
212 type Output = Document;
213
214 fn add(self: char, other: Document) -> Self::Output {
216 let lhs = character(self);
217 if lhs.is_empty() {
218 return other;
219 }
220 if other.is_empty() {
221 return lhs;
222 }
223 Document::Concat(Rc::new(lhs), Rc::new(other))
224 }
225}
226
227impl core::ops::Add<&'static str> for Document {
228 type Output = Document;
229
230 fn add(self: Document, other: &'static str) -> Self::Output {
232 let other = const_text(other);
233 if self.is_empty() {
234 return other;
235 }
236 if other.is_empty() {
237 return self;
238 }
239 Document::Concat(Rc::new(self), Rc::new(other))
240 }
241}
242
243impl core::ops::Add<Document> for &'static str {
244 type Output = Document;
245
246 fn add(self: &'static str, other: Document) -> Self::Output {
248 let lhs = const_text(self);
249 if lhs.is_empty() {
250 return other;
251 }
252 if other.is_empty() {
253 return lhs;
254 }
255 Document::Concat(Rc::new(lhs), Rc::new(other))
256 }
257}
258
259impl core::ops::AddAssign for Document {
260 fn add_assign(&mut self, rhs: Document) {
262 if rhs.is_empty() {
263 return;
264 }
265 if self.is_empty() {
266 *self = rhs;
267 return;
268 }
269 let lhs = core::mem::take(self);
270 *self = Document::Concat(Rc::new(lhs), Rc::new(rhs));
271 }
272}
273
274impl core::ops::AddAssign<char> for Document {
275 fn add_assign(&mut self, rhs: char) {
277 let rhs = character(rhs);
278 if rhs.is_empty() {
279 return;
280 }
281 if self.is_empty() {
282 *self = rhs;
283 return;
284 }
285 let lhs = core::mem::take(self);
286 *self = Document::Concat(Rc::new(lhs), Rc::new(rhs));
287 }
288}
289
290impl core::ops::AddAssign<&'static str> for Document {
291 fn add_assign(&mut self, rhs: &'static str) {
293 let rhs = const_text(rhs);
294 if rhs.is_empty() {
295 return;
296 }
297 if self.is_empty() {
298 *self = rhs;
299 return;
300 }
301 let lhs = core::mem::take(self);
302 *self = Document::Concat(Rc::new(lhs), Rc::new(rhs));
303 }
304}
305
306impl core::ops::BitOr for Document {
307 type Output = Document;
308
309 fn bitor(self: Document, other: Document) -> Self::Output {
313 if self.is_empty() {
314 return other;
315 }
316 if other.is_empty() {
317 return self;
318 }
319 Document::Choice(Rc::new(self), Rc::new(other))
320 }
321}
322
323impl fmt::Display for Document {
324 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
325 use core::fmt::Write;
326 match self {
327 Self::Empty => Ok(()),
328 Self::Newline => f.write_char('\n'),
329 Self::Char(c, _) => f.write_char(*c),
330 doc => {
331 let width = f.width().unwrap_or(80);
332 super::print::pretty_print(doc, width, f)
333 },
334 }
335 }
336}