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 stack.push(chunk.with_n(y));
135 }
136 }
137 }
138 }
139 }
140}