aiken_lang/
pretty.rs

1//! This module implements the functionality described in
2//! ["Strictly Pretty" (2000) by Christian Lindig][0], with a few
3//! extensions.
4//!
5//! This module is heavily influenced by Elixir's Inspect.Algebra and
6//! JavaScript's Prettier.
7//!
8//! [0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200
9//!
10//! ## Extensions
11//!
12//! - `ForcedBreak` from Elixir.
13#![allow(clippy::wrong_self_convention)]
14
15use itertools::Itertools;
16use std::collections::VecDeque;
17
18#[macro_export]
19macro_rules! docvec {
20    () => {
21        Document::Vec(Vec::new())
22    };
23
24    ($($x:expr),+ $(,)?) => {
25        Document::Vec(vec![$($x.to_doc()),+])
26    };
27}
28
29/// Coerce a value into a Document.
30/// Note we do not implement this for String as a slight pressure to favour str
31/// over String.
32pub trait Documentable<'a> {
33    fn to_doc(self) -> Document<'a>;
34}
35
36impl<'a> Documentable<'a> for char {
37    fn to_doc(self) -> Document<'a> {
38        Document::String(format!("{self}"))
39    }
40}
41
42impl<'a> Documentable<'a> for &'a str {
43    fn to_doc(self) -> Document<'a> {
44        Document::Str(self)
45    }
46}
47
48impl<'a> Documentable<'a> for isize {
49    fn to_doc(self) -> Document<'a> {
50        Document::String(format!("{self}"))
51    }
52}
53
54impl<'a> Documentable<'a> for i64 {
55    fn to_doc(self) -> Document<'a> {
56        Document::String(format!("{self}"))
57    }
58}
59
60impl<'a> Documentable<'a> for usize {
61    fn to_doc(self) -> Document<'a> {
62        Document::String(format!("{self}"))
63    }
64}
65
66impl<'a> Documentable<'a> for f64 {
67    fn to_doc(self) -> Document<'a> {
68        Document::String(format!("{self:?}"))
69    }
70}
71
72impl<'a> Documentable<'a> for u64 {
73    fn to_doc(self) -> Document<'a> {
74        Document::String(format!("{self:?}"))
75    }
76}
77
78impl<'a> Documentable<'a> for u32 {
79    fn to_doc(self) -> Document<'a> {
80        Document::String(format!("{self}"))
81    }
82}
83
84impl<'a> Documentable<'a> for u16 {
85    fn to_doc(self) -> Document<'a> {
86        Document::String(format!("{self}"))
87    }
88}
89
90impl<'a> Documentable<'a> for u8 {
91    fn to_doc(self) -> Document<'a> {
92        Document::String(format!("{self}"))
93    }
94}
95
96impl<'a> Documentable<'a> for Document<'a> {
97    fn to_doc(self) -> Document<'a> {
98        self
99    }
100}
101
102impl<'a> Documentable<'a> for Vec<Document<'a>> {
103    fn to_doc(self) -> Document<'a> {
104        Document::Vec(self)
105    }
106}
107
108impl<'a, D: Documentable<'a>> Documentable<'a> for Option<D> {
109    fn to_doc(self) -> Document<'a> {
110        self.map(Documentable::to_doc).unwrap_or_else(nil)
111    }
112}
113
114pub fn concat<'a>(docs: impl IntoIterator<Item = Document<'a>>) -> Document<'a> {
115    Document::Vec(docs.into_iter().collect())
116}
117
118pub fn join<'a>(
119    docs: impl IntoIterator<Item = Document<'a>>,
120    separator: Document<'a>,
121) -> Document<'a> {
122    concat(Itertools::intersperse(docs.into_iter(), separator))
123}
124
125#[derive(Debug, Clone, PartialEq, Eq)]
126pub enum Document<'a> {
127    /// A mandatory linebreak
128    Line(usize),
129
130    /// Forces contained groups to break
131    ForceBroken(Box<Self>),
132
133    /// Forces contained group to not break
134    ForceUnbroken(Box<Self>),
135
136    /// Renders `broken` if group is broken, `unbroken` otherwise
137    Break {
138        broken: &'a str,
139        unbroken: &'a str,
140        break_first: bool,
141        kind: BreakKind,
142    },
143
144    /// Join multiple documents together
145    Vec(Vec<Self>),
146
147    /// Nests the given document by the given indent
148    Nest(isize, Box<Self>),
149
150    /// Nests the given document to the current cursor position
151    Group(Box<Self>),
152
153    /// A string to render
154    String(String),
155
156    /// A str to render
157    Str(&'a str),
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161enum Mode {
162    Broken,
163    Unbroken,
164
165    //
166    // These are used for the Fits variant, taken from Elixir's
167    // Inspect.Algebra's `fits` extension.
168    //
169    /// Broken and forced to remain broken
170    ForcedBroken,
171    ForcedUnbroken,
172}
173
174impl Mode {
175    fn is_forced(&self) -> bool {
176        matches!(self, Mode::ForcedBroken | Mode::ForcedUnbroken)
177    }
178}
179
180fn fits(
181    mut limit: isize,
182    mut current_width: isize,
183    mut docs: VecDeque<(isize, Mode, &Document<'_>)>,
184) -> bool {
185    loop {
186        if current_width > limit {
187            return false;
188        };
189
190        let (indent, mode, document) = match docs.pop_front() {
191            Some(x) => x,
192            None => return true,
193        };
194
195        match document {
196            Document::ForceBroken(_) => {
197                return false;
198            }
199
200            Document::Line(_) => return true,
201
202            Document::Nest(i, doc) => docs.push_front((i + indent, mode, doc)),
203
204            Document::ForceUnbroken(doc) => docs.push_front((indent, mode, doc)),
205
206            Document::Group(doc) if mode.is_forced() => docs.push_front((indent, mode, doc)),
207
208            Document::Group(doc) => docs.push_front((indent, Mode::Unbroken, doc)),
209
210            Document::Str(s) => limit -= s.len() as isize,
211
212            Document::String(s) => limit -= s.len() as isize,
213
214            Document::Break { unbroken, .. } => match mode {
215                Mode::Broken | Mode::ForcedBroken => return true,
216                Mode::Unbroken | Mode::ForcedUnbroken => current_width += unbroken.len() as isize,
217            },
218
219            Document::Vec(vec) => {
220                for doc in vec.iter().rev() {
221                    docs.push_front((indent, mode, doc));
222                }
223            }
224        }
225    }
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
229pub enum BreakKind {
230    Flex,
231    Strict,
232}
233
234fn format(
235    writer: &mut String,
236    limit: isize,
237    mut width: isize,
238    mut docs: VecDeque<(isize, Mode, &Document<'_>)>,
239) {
240    while let Some((indent, mode, document)) = docs.pop_front() {
241        match document {
242            Document::Line(i) => {
243                for _ in 0..*i {
244                    writer.push('\n');
245                }
246
247                for _ in 0..indent {
248                    writer.push(' ');
249                }
250
251                width = indent;
252            }
253
254            // Flex breaks are NOT conditional to the mode
255            Document::Break {
256                broken,
257                unbroken,
258                break_first,
259                kind: BreakKind::Flex,
260            } => {
261                if mode == Mode::ForcedUnbroken {
262                    writer.push_str(unbroken);
263                    width += unbroken.len() as isize
264                } else {
265                    let unbroken_width = width + unbroken.len() as isize;
266
267                    if fits(limit, unbroken_width, docs.clone()) {
268                        writer.push_str(unbroken);
269                        width = unbroken_width;
270                        continue;
271                    }
272
273                    if *break_first {
274                        writer.push('\n');
275                        for _ in 0..indent {
276                            writer.push(' ');
277                        }
278                        writer.push_str(broken);
279                    } else {
280                        writer.push_str(broken);
281                        writer.push('\n');
282                        for _ in 0..indent {
283                            writer.push(' ');
284                        }
285                    }
286
287                    width = indent;
288                }
289            }
290
291            // Strict breaks are conditional to the mode
292            Document::Break {
293                broken,
294                unbroken,
295                break_first,
296                kind: BreakKind::Strict,
297            } => {
298                width = match mode {
299                    Mode::Unbroken | Mode::ForcedUnbroken => {
300                        writer.push_str(unbroken);
301
302                        width + unbroken.len() as isize
303                    }
304
305                    Mode::Broken | Mode::ForcedBroken if *break_first => {
306                        writer.push('\n');
307
308                        for _ in 0..indent {
309                            writer.push(' ');
310                        }
311
312                        writer.push_str(broken);
313
314                        indent
315                    }
316
317                    Mode::Broken | Mode::ForcedBroken => {
318                        writer.push_str(broken);
319
320                        writer.push('\n');
321
322                        for _ in 0..indent {
323                            writer.push(' ');
324                        }
325
326                        indent
327                    }
328                };
329            }
330
331            Document::String(s) => {
332                width += s.len() as isize;
333
334                writer.push_str(s);
335            }
336
337            Document::Str(s) => {
338                width += s.len() as isize;
339
340                writer.push_str(s);
341            }
342
343            Document::Vec(vec) => {
344                for doc in vec.iter().rev() {
345                    docs.push_front((indent, mode, doc));
346                }
347            }
348
349            Document::Nest(i, doc) => {
350                docs.push_front((indent + i, mode, doc));
351            }
352
353            Document::Group(doc) => {
354                let mut group_docs = VecDeque::new();
355
356                let inner_mode = if mode == Mode::ForcedUnbroken {
357                    Mode::ForcedUnbroken
358                } else {
359                    Mode::Unbroken
360                };
361
362                group_docs.push_front((indent, inner_mode, doc.as_ref()));
363
364                if fits(limit, width, group_docs) {
365                    docs.push_front((indent, inner_mode, doc));
366                } else {
367                    docs.push_front((indent, Mode::Broken, doc));
368                }
369            }
370
371            Document::ForceBroken(document) => {
372                docs.push_front((indent, Mode::ForcedBroken, document));
373            }
374
375            Document::ForceUnbroken(document) => {
376                docs.push_front((indent, Mode::ForcedUnbroken, document));
377            }
378        }
379    }
380}
381
382pub fn nil<'a>() -> Document<'a> {
383    Document::Vec(vec![])
384}
385
386pub fn line<'a>() -> Document<'a> {
387    Document::Line(1)
388}
389
390pub fn lines<'a>(i: usize) -> Document<'a> {
391    Document::Line(i)
392}
393
394pub fn break_<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> {
395    Document::Break {
396        broken,
397        unbroken,
398        kind: BreakKind::Strict,
399        break_first: false,
400    }
401}
402
403pub fn prebreak<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> {
404    Document::Break {
405        broken,
406        unbroken,
407        kind: BreakKind::Strict,
408        break_first: true,
409    }
410}
411
412pub fn flex_break<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> {
413    Document::Break {
414        broken,
415        unbroken,
416        kind: BreakKind::Flex,
417        break_first: false,
418    }
419}
420
421pub fn flex_prebreak<'a>(broken: &'a str, unbroken: &'a str) -> Document<'a> {
422    Document::Break {
423        broken,
424        unbroken,
425        kind: BreakKind::Flex,
426        break_first: true,
427    }
428}
429
430impl<'a> Document<'a> {
431    pub fn fits(&self, target: isize) -> bool {
432        let mut docs = VecDeque::new();
433        docs.push_front((0, Mode::Unbroken, self));
434        fits(target, 0, docs)
435    }
436
437    pub fn group(self) -> Self {
438        Self::Group(Box::new(self))
439    }
440
441    pub fn nest(self, indent: isize) -> Self {
442        Self::Nest(indent, Box::new(self))
443    }
444
445    pub fn force_break(self) -> Self {
446        Self::ForceBroken(Box::new(self))
447    }
448
449    pub fn force_unbroken(self) -> Self {
450        Self::ForceUnbroken(Box::new(self))
451    }
452
453    pub fn append(self, second: impl Documentable<'a>) -> Self {
454        match self {
455            Self::Vec(mut vec) => {
456                vec.push(second.to_doc());
457                Self::Vec(vec)
458            }
459            first => Self::Vec(vec![first, second.to_doc()]),
460        }
461    }
462
463    pub fn to_pretty_string(self, limit: isize) -> String {
464        let mut buffer = String::new();
465
466        self.pretty_print(limit, &mut buffer);
467
468        buffer
469    }
470
471    pub fn surround(self, open: impl Documentable<'a>, closed: impl Documentable<'a>) -> Self {
472        open.to_doc().append(self).append(closed)
473    }
474
475    pub fn pretty_print(&self, limit: isize, writer: &mut String) {
476        let mut docs = VecDeque::new();
477
478        docs.push_front((0, Mode::Unbroken, self));
479
480        format(writer, limit, 0, docs);
481    }
482
483    /// Returns true when the document contains no printable characters
484    /// (whitespace and newlines are considered printable characters).
485    pub fn is_empty(&self) -> bool {
486        use Document::*;
487        match self {
488            Line(n) => *n == 0,
489            String(s) => s.is_empty(),
490            Str(s) => s.is_empty(),
491            // assuming `broken` and `unbroken` are equivalent
492            Break { broken, .. } => broken.is_empty(),
493            ForceUnbroken(d) | ForceBroken(d) | Nest(_, d) | Group(d) => d.is_empty(),
494            Vec(docs) => docs.iter().all(|d| d.is_empty()),
495        }
496    }
497}