1#[derive(Debug, Clone)]
8pub enum Doc {
9 Nil,
10 Text(String),
11 Line,
14 HardLine,
16 Concat(Vec<Doc>),
17 Nest(i32, Box<Doc>),
19 Group(Box<Doc>),
21}
22
23impl Doc {
24 pub fn nil() -> Self {
25 Doc::Nil
26 }
27
28 pub fn text(s: impl Into<String>) -> Self {
29 Doc::Text(s.into())
30 }
31
32 pub fn concat(docs: Vec<Doc>) -> Self {
33 let flat: Vec<Doc> = docs
34 .into_iter()
35 .flat_map(|d| match d {
36 Doc::Concat(inner) => inner,
37 Doc::Nil => vec![],
38 other => vec![other],
39 })
40 .collect();
41 match flat.len() {
42 0 => Doc::Nil,
43 1 => flat.into_iter().next().unwrap(),
44 _ => Doc::Concat(flat),
45 }
46 }
47
48 pub fn nest(indent: i32, doc: Doc) -> Self {
49 Doc::Nest(indent, Box::new(doc))
50 }
51
52 pub fn group(doc: Doc) -> Self {
53 Doc::Group(Box::new(doc))
54 }
55}
56
57#[derive(Clone, Copy, PartialEq, Eq)]
58enum Mode {
59 Flat,
60 Break,
61}
62
63fn fits(mut width: isize, docs: &[(&Doc, i32, Mode)]) -> bool {
66 let mut i = docs.len();
67 loop {
68 if width < 0 {
69 return false;
70 }
71 if i == 0 {
72 return true;
73 }
74 i -= 1;
75 let (doc, indent, mode) = docs[i];
76 match doc {
77 Doc::Nil => {}
78 Doc::Text(s) => width -= s.len() as isize,
79 Doc::Line => {
80 if mode == Mode::Flat {
81 width -= 1; } else {
83 return true; }
85 }
86 Doc::HardLine => return true,
87 Doc::Concat(ds) => {
88 let mut extended: Vec<(&Doc, i32, Mode)> = docs[..i].to_vec();
94 for d in ds.iter().rev() {
95 extended.push((d, indent, mode));
96 }
97 return fits(width, &extended);
98 }
99 Doc::Nest(n, inner) => {
100 let mut extended: Vec<(&Doc, i32, Mode)> = docs[..i].to_vec();
101 extended.push((inner, indent + n, mode));
102 return fits(width, &extended);
103 }
104 Doc::Group(inner) => {
105 let mut extended: Vec<(&Doc, i32, Mode)> = docs[..i].to_vec();
106 extended.push((inner, indent, Mode::Flat));
107 return fits(width, &extended);
108 }
109 }
110 }
111}
112
113pub fn render(doc: &Doc, width: usize) -> String {
115 let mut out = String::new();
116 let mut col: i32 = 0;
117
118 let mut work: Vec<(&Doc, i32, Mode)> = vec![(doc, 0, Mode::Break)];
120
121 let mut should_ident = false;
122
123 while let Some((doc, indent, mode)) = work.pop() {
124 match doc {
125 Doc::Nil => {}
126 Doc::Text(s) => {
127 if should_ident {
128 for _ in 0..indent {
129 out.push(' ');
130 }
132 should_ident = false;
133 }
134 out.push_str(s);
135 col += s.len() as i32;
136 }
137 Doc::Line => {
138 if mode == Mode::Flat {
139 out.push(' ');
140 col += 1;
141 } else {
142 out.push('\n');
143 col = 0;
144 should_ident = true;
145 }
146 }
147 Doc::HardLine => {
148 out.push('\n');
149 col = 0;
150 should_ident = true;
151 }
152 Doc::Concat(ds) => {
153 for d in ds.iter().rev() {
155 work.push((d, indent, mode));
156 }
157 }
158 Doc::Nest(n, inner) => {
159 work.push((inner, indent + n, mode));
160 }
161 Doc::Group(inner) => {
162 let remaining = if should_ident {
164 width as isize - (indent + col) as isize
165 } else {
166 width as isize - col as isize
167 };
168 let mut probe: Vec<(&Doc, i32, Mode)> = work.clone();
171 probe.push((inner, indent, Mode::Flat));
172 let chosen = if fits(remaining, &probe) {
173 Mode::Flat
174 } else {
175 Mode::Break
176 };
177 work.push((inner, indent, chosen));
178 }
179 }
180 }
181
182 out
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn text_renders_literally() {
191 let doc = Doc::text("hello");
192 assert_eq!(render(&doc, 80), "hello");
193 }
194
195 #[test]
196 fn group_fits_on_one_line() {
197 let doc = Doc::group(Doc::concat(vec![
199 Doc::text("a"),
200 Doc::Line,
201 Doc::text("b"),
202 Doc::Line,
203 Doc::text("c"),
204 ]));
205 assert_eq!(render(&doc, 80), "a b c");
206 }
207
208 #[test]
209 fn group_breaks_when_too_wide() {
210 let doc = Doc::group(Doc::concat(vec![
212 Doc::text("a"),
213 Doc::Line,
214 Doc::text("b"),
215 Doc::Line,
216 Doc::text("c"),
217 ]));
218 assert_eq!(render(&doc, 4), "a\nb\nc");
219 }
220
221 #[test]
222 fn nest_indents_broken_lines() {
223 let doc = Doc::group(Doc::concat(vec![
224 Doc::text("{"),
225 Doc::nest(
226 2,
227 Doc::concat(vec![Doc::Line, Doc::text("x"), Doc::Line, Doc::text("y")]),
228 ),
229 Doc::Line,
230 Doc::text("}"),
231 ]));
232 assert_eq!(render(&doc, 80), "{ x y }");
234 assert_eq!(render(&doc, 5), "{\n x\n y\n}");
236 }
237
238 #[test]
239 fn hardline_always_breaks() {
240 let doc = Doc::group(Doc::concat(vec![
241 Doc::text("a"),
242 Doc::HardLine,
243 Doc::text("b"),
244 ]));
245 assert_eq!(render(&doc, 999), "a\nb");
247 }
248
249 #[test]
250 fn concat_flattens_nils() {
251 let doc = Doc::concat(vec![Doc::Nil, Doc::text("x"), Doc::Nil]);
252 assert_eq!(render(&doc, 80), "x");
253 }
254
255 #[test]
256 fn nest_propagates_to_all_lines_in_concat() {
257 let inner = Doc::concat(vec![
260 Doc::Line,
261 Doc::text("a"),
262 Doc::text(","),
263 Doc::Line,
264 Doc::text("b"),
265 ]);
266 let doc = Doc::group(Doc::concat(vec![
267 Doc::text("{"),
268 Doc::nest(2, inner),
269 Doc::Line,
270 Doc::text("}"),
271 ]));
272 assert_eq!(render(&doc, 5), "{\n a,\n b\n}");
274 }
275}