pretty_printing/
print.rs

1use crate::notation::{NRef, N};
2
3pub fn pretty_print(n_ref: NRef, max_width: u32) -> String {
4    let mut printer = PrettyPrinter::new(n_ref, max_width);
5    printer.print()
6}
7
8struct PrettyPrinter<'a> {
9    max_width: u32,
10    col: u32,
11    chunks: Vec<Chunk<'a>>,
12}
13
14#[derive(Debug, Clone, Copy)]
15struct Chunk<'a> {
16    n_ref: NRef<'a>,
17    indent: u32,
18    flat: bool,
19}
20
21impl<'a> Chunk<'a> {
22    fn with_n(self, n_ref: NRef<'a>) -> Self {
23        Chunk {
24            n_ref,
25            indent: self.indent,
26            flat: self.flat,
27        }
28    }
29
30    fn indented(self, indent: u32, n_ref: NRef<'a>) -> Self {
31        Chunk {
32            n_ref,
33            indent: self.indent + indent,
34            flat: self.flat,
35        }
36    }
37
38    fn flat(self, n_ref: NRef<'a>) -> Self {
39        Chunk {
40            n_ref,
41            indent: self.indent,
42            flat: true,
43        }
44    }
45}
46
47impl<'a> PrettyPrinter<'a> {
48    fn new(n_ref: NRef<'a>, width: u32) -> Self {
49        let chunk = Chunk {
50            n_ref,
51            indent: 0,
52            flat: false,
53        };
54
55        Self {
56            max_width: width,
57            col: 0,
58            chunks: vec![chunk],
59        }
60    }
61
62    fn print(&mut self) -> String {
63        let mut result = String::new();
64
65        while let Some(chunk) = self.chunks.pop() {
66            match chunk.n_ref {
67                N::Newline => {
68                    result.push('\n');
69                    for _ in 0..chunk.indent {
70                        result.push(' ');
71                    }
72                    self.col = chunk.indent;
73                }
74                N::Text(text, width) => {
75                    result.push_str(text);
76                    self.col += width;
77                }
78                N::Flat(x) => self.chunks.push(chunk.flat(x)),
79                N::Indent(i, x) => self.chunks.push(chunk.indented(*i, x)),
80                N::Concat(seq) => {
81                    for n in seq.iter().rev() {
82                        self.chunks.push(chunk.with_n(n));
83                    }
84                }
85                N::Choice(x, y) => {
86                    if chunk.flat || self.fits(chunk.with_n(x)) {
87                        self.chunks.push(chunk.with_n(x));
88                    } else {
89                        self.chunks.push(chunk.with_n(y));
90                    }
91                }
92            }
93        }
94        result
95    }
96
97    fn fits(&self, chunk: Chunk<'a>) -> bool {
98        let mut remaining_width = self.max_width.saturating_sub(self.col);
99        let mut stack = vec![chunk];
100        let mut chunks = &self.chunks as &[Chunk];
101
102        loop {
103            let chunk = if let Some(chunk) = stack.pop() {
104                chunk
105            } else if let Some((chunk, more_chunks)) = chunks.split_last() {
106                chunks = more_chunks;
107                *chunk
108            } else {
109                return true;
110            };
111
112            match chunk.n_ref {
113                N::Newline => return true,
114                N::Text(_, text_width) => {
115                    if *text_width <= remaining_width {
116                        remaining_width -= text_width;
117                    } else {
118                        return false;
119                    }
120                }
121                N::Flat(x) => stack.push(chunk.flat(x)),
122                N::Indent(i, x) => stack.push(chunk.indented(*i, x)),
123                N::Concat(seq) => {
124                    for n in seq.iter().rev() {
125                        stack.push(chunk.with_n(n));
126                    }
127                }
128                N::Choice(x, y) => {
129                    if chunk.flat {
130                        stack.push(chunk.with_n(x));
131                    } else {
132                        // With assumption: for every choice `x | y`,
133                        // the first line of `y` is no longer than the first line of `x`.
134                        stack.push(chunk.with_n(y));
135                    }
136                }
137            }
138        }
139    }
140}