1use crate::{
2 options::{LineBreak, PrintOptions},
3 Doc, IndentKind,
4};
5
6#[derive(Clone, Copy)]
7enum Mode {
8 Flat,
9 Break,
10}
11
12type Action<'a> = (usize, Mode, &'a Doc<'a>);
13
14pub fn print(doc: &Doc, options: &PrintOptions) -> String {
20 assert!(options.tab_size > 0);
21
22 let mut printer = Printer::new(options);
23 let mut out = String::with_capacity(1024);
24 printer.print_to((0, Mode::Break, doc), &mut out);
25 out
26}
27
28struct Printer<'a> {
29 options: &'a PrintOptions,
30 cols: usize,
31}
32
33impl<'a> Printer<'a> {
34 fn new(options: &'a PrintOptions) -> Self {
35 Self { options, cols: 0 }
36 }
37
38 fn print_to(&mut self, init_action: Action<'a>, out: &mut String) -> bool {
39 let line_break = match self.options.line_break {
40 LineBreak::Lf => "\n",
41 LineBreak::Crlf => "\r\n",
42 };
43
44 let mut actions = Vec::with_capacity(128);
45 actions.push(init_action);
46
47 let mut fits = true;
48
49 while let Some((indent, mode, doc)) = actions.pop() {
50 match doc {
51 Doc::Nil => {}
52 Doc::Alt(doc_flat, doc_break) => match mode {
53 Mode::Flat => actions.push((indent, mode, doc_flat)),
54 Mode::Break => actions.push((indent, mode, doc_break)),
55 },
56 Doc::Union(attempt, alternate) => {
57 let original_cols = self.cols;
58
59 let mut buf = String::new();
60 if self.print_to((indent, mode, attempt), &mut buf) {
61 unsafe {
63 out.as_mut_vec().append(buf.as_mut_vec());
64 }
65 } else {
66 self.cols = original_cols;
67 actions.push((indent, mode, alternate));
68 }
69 }
70 Doc::Nest(offset, doc) => {
71 actions.push((indent + offset, mode, doc));
72 }
73 Doc::Text(text) => {
74 self.cols += measure_text_width(text);
75 out.push_str(text);
76 fits &= self.cols <= self.options.width;
77 }
78 Doc::NewLine => {
79 self.cols = indent;
80 out.push_str(line_break);
81 match self.options.indent_kind {
82 IndentKind::Space => {
83 out.push_str(&" ".repeat(indent));
84 }
85 IndentKind::Tab => {
86 out.push_str(&"\t".repeat(indent / self.options.tab_size));
87 out.push_str(&" ".repeat(indent % self.options.tab_size));
88 }
89 }
90 fits &= self.cols <= self.options.width;
91 }
92 Doc::EmptyLine => {
93 out.push_str(line_break);
94 }
95 Doc::Break(spaces, offset) => {
96 match mode {
97 Mode::Flat => {
98 self.cols += spaces;
99 out.push_str(&" ".repeat(*spaces));
100 }
101 Mode::Break => {
102 self.cols = indent + offset;
103 out.push_str(line_break);
104 match self.options.indent_kind {
105 IndentKind::Space => {
106 out.push_str(&" ".repeat(self.cols));
107 }
108 IndentKind::Tab => {
109 out.push_str(&"\t".repeat(self.cols / self.options.tab_size));
110 out.push_str(&" ".repeat(self.cols % self.options.tab_size));
111 }
112 }
113 }
114 };
115 fits &= self.cols <= self.options.width;
116 }
117 Doc::Group(docs) => match mode {
118 Mode::Flat => {
119 actions.extend(docs.iter().map(|doc| (indent, Mode::Flat, doc)).rev());
120 }
121 Mode::Break => {
122 let fitting_actions = docs
123 .iter()
124 .map(|doc| (indent, Mode::Flat, doc))
125 .rev()
126 .collect();
127 let mode = if fitting(
128 fitting_actions,
129 actions.iter().rev(),
130 self.cols,
131 self.options.width,
132 ) {
133 Mode::Flat
134 } else {
135 Mode::Break
136 };
137 actions.extend(docs.iter().map(|doc| (indent, mode, doc)).rev());
138 }
139 },
140 Doc::List(docs) => {
141 actions.extend(docs.iter().map(|doc| (indent, mode, doc)).rev());
142 }
143 }
144 }
145
146 fits
147 }
148}
149
150fn fitting<'a>(
157 mut actions: Vec<Action<'a>>,
158 mut best_actions: impl Iterator<Item = &'a Action<'a>>,
159 mut cols: usize,
160 width: usize,
161) -> bool {
162 while let Some((indent, mode, doc)) = actions.pop().or_else(|| best_actions.next().copied()) {
163 match doc {
164 Doc::Nil => {}
165 Doc::Alt(doc_flat, doc_break) => match mode {
166 Mode::Flat => actions.push((indent, mode, doc_flat)),
167 Mode::Break => actions.push((indent, mode, doc_break)),
168 },
169 Doc::Union(attempt, alternate) => match mode {
170 Mode::Flat => actions.push((indent, mode, attempt)),
171 Mode::Break => actions.push((indent, mode, alternate)),
172 },
173 Doc::Nest(offset, doc) => {
174 actions.push((indent + offset, mode, doc));
175 }
176 Doc::Text(text) => {
177 cols += measure_text_width(text);
178 }
179 Doc::Break(spaces, _) => match mode {
180 Mode::Flat => cols += spaces,
181 Mode::Break => return true,
182 },
183 Doc::NewLine => {
184 return matches!(mode, Mode::Break);
186 }
187 Doc::EmptyLine => {}
188 Doc::Group(docs) | Doc::List(docs) => {
189 actions.extend(docs.iter().map(|doc| (indent, mode, doc)).rev());
190 }
191 }
192 if cols > width {
193 return false;
194 }
195 }
196 true
197}
198
199#[cfg(not(feature = "unicode-width"))]
200fn measure_text_width(text: &str) -> usize {
201 text.len()
202}
203
204#[cfg(feature = "unicode-width")]
205fn measure_text_width(text: &str) -> usize {
206 use unicode_width::UnicodeWidthStr;
207 text.width()
208}