pretty/
block.rs

1//! Document formatting of "blocks" such as where some number of prefixes and suffixes would
2//! ideally be layed out onto a single line instead of breaking them up into multiple lines. See
3//! `BlockDoc` for an example
4
5use crate::{docs, Doc, DocAllocator, DocBuilder};
6
7pub struct Affixes<'doc, D, A>
8where
9    D: DocAllocator<'doc, A>,
10{
11    prefix: DocBuilder<'doc, D, A>,
12    suffix: DocBuilder<'doc, D, A>,
13    nest: bool,
14}
15
16impl<'a, D, A> Clone for Affixes<'a, D, A>
17where
18    A: Clone,
19    D: DocAllocator<'a, A> + 'a,
20    D::Doc: Clone,
21{
22    fn clone(&self) -> Self {
23        Affixes {
24            prefix: self.prefix.clone(),
25            suffix: self.suffix.clone(),
26            nest: self.nest,
27        }
28    }
29}
30
31impl<'doc, D, A> Affixes<'doc, D, A>
32where
33    D: DocAllocator<'doc, A>,
34{
35    pub fn new(prefix: DocBuilder<'doc, D, A>, suffix: DocBuilder<'doc, D, A>) -> Self {
36        Affixes {
37            prefix,
38            suffix,
39            nest: false,
40        }
41    }
42
43    pub fn nest(mut self) -> Self {
44        self.nest = true;
45        self
46    }
47}
48
49/// Formats a set of `prefix` and `suffix` documents around a `body`
50///
51/// The following document split into the prefixes [\x y ->, \z ->, {], suffixes [nil, nil, }] and
52/// body [result: x + y - z] will try to be formatted
53///
54/// ```gluon
55/// \x y -> \z -> { result: x + y - z }
56/// ```
57///
58/// ```gluon
59/// \x y -> \z -> {
60///     result: x + y - z
61/// }
62/// ```
63///
64/// ```gluon
65/// \x y -> \z ->
66///     {
67///         result: x + y - z
68///     }
69/// ```
70///
71/// ```gluon
72/// \x y ->
73///     \z ->
74///         {
75///             result: x + y - z
76///         }
77/// ```
78pub struct BlockDoc<'doc, D, A>
79where
80    D: DocAllocator<'doc, A>,
81{
82    pub affixes: Vec<Affixes<'doc, D, A>>,
83    pub body: DocBuilder<'doc, D, A>,
84}
85
86impl<'doc, D, A> BlockDoc<'doc, D, A>
87where
88    D: DocAllocator<'doc, A>,
89    D::Doc: Clone,
90    A: Clone,
91{
92    pub fn format(self, nest: isize) -> DocBuilder<'doc, D, A> {
93        let arena = self.body.0;
94
95        let fail_on_multi_line = arena.fail().flat_alt(arena.nil());
96
97        (1..self.affixes.len() + 1)
98            .rev()
99            .map(|split| {
100                let (before, after) = self.affixes.split_at(split);
101                let last = before.len() == 1;
102                docs![
103                    arena,
104                    docs![
105                        arena,
106                        arena.concat(before.iter().map(|affixes| affixes.prefix.clone())),
107                        if last {
108                            arena.nil()
109                        } else {
110                            fail_on_multi_line.clone()
111                        }
112                    ]
113                    .group(),
114                    docs![
115                        arena,
116                        after.iter().rev().cloned().fold(
117                            docs![
118                                arena,
119                                self.body.clone(),
120                                // If there is no prefix then we must not allow the body to laid out on multiple
121                                // lines without nesting
122                                if !last
123                                    && before
124                                        .iter()
125                                        .all(|affixes| matches!(&*affixes.prefix.1, Doc::Nil))
126                                {
127                                    fail_on_multi_line.clone()
128                                } else {
129                                    arena.nil()
130                                },
131                            ]
132                            .nest(nest)
133                            .append(
134                                arena.concat(after.iter().map(|affixes| affixes.suffix.clone()))
135                            ),
136                            |acc, affixes| {
137                                let mut doc = affixes.prefix.append(acc);
138                                if affixes.nest {
139                                    doc = doc.nest(nest);
140                                }
141                                doc.group()
142                            },
143                        ),
144                        arena.concat(before.iter().map(|affixes| affixes.suffix.clone())),
145                    ]
146                    .group(),
147                ]
148            })
149            .fold(None::<DocBuilder<_, _>>, |acc, doc| {
150                Some(match acc {
151                    None => doc,
152                    Some(acc) => acc.union(doc),
153                })
154            })
155            .unwrap_or(self.body)
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    use crate::Arena;
164
165    #[test]
166    fn format_block() {
167        let arena = &Arena::<()>::new();
168        let mk_doc = || BlockDoc {
169            affixes: vec![
170                Affixes::new(docs![arena, "\\x y ->"], arena.nil()).nest(),
171                Affixes::new(docs![arena, arena.line(), "\\z ->"], arena.nil()).nest(),
172                Affixes::new(
173                    docs![arena, arena.line(), "{"],
174                    docs![arena, arena.line(), "}"],
175                )
176                .nest(),
177            ],
178            body: docs![arena, arena.line(), "result"],
179        };
180        expect_test::expect![[r#"\x y -> \z -> { result }"#]]
181            .assert_eq(&mk_doc().format(4).1.pretty(40).to_string());
182        expect_test::expect![[r#"
183\x y -> \z -> {
184    result
185}"#]]
186        .assert_eq(&mk_doc().format(4).1.pretty(15).to_string());
187        expect_test::expect![[r#"
188\x y -> \z ->
189    {
190        result
191    }"#]]
192        .assert_eq(&mk_doc().format(4).1.pretty(14).to_string());
193        expect_test::expect![[r#"
194\x y ->
195    \z ->
196        {
197            result
198        }"#]]
199        .assert_eq(&mk_doc().format(4).1.pretty(12).to_string());
200    }
201}