Skip to main content

shuck_format/
printer.rs

1use crate::format_element::{Document, FormatElement, InternedDocument, LineMode, TextMetrics};
2
3#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
4pub enum IndentStyle {
5    #[default]
6    Tab,
7    Space,
8}
9
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
11pub enum LineEnding {
12    #[default]
13    Lf,
14    CrLf,
15}
16
17impl LineEnding {
18    const fn as_str(self) -> &'static str {
19        match self {
20            Self::Lf => "\n",
21            Self::CrLf => "\r\n",
22        }
23    }
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct PrinterOptions {
28    pub indent_style: IndentStyle,
29    pub indent_width: u8,
30    pub line_width: u16,
31    pub line_ending: LineEnding,
32}
33
34impl Default for PrinterOptions {
35    fn default() -> Self {
36        Self {
37            indent_style: IndentStyle::Tab,
38            indent_width: 4,
39            line_width: 80,
40            line_ending: LineEnding::Lf,
41        }
42    }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct Printed {
47    code: String,
48}
49
50impl Printed {
51    #[must_use]
52    pub fn as_code(&self) -> &str {
53        &self.code
54    }
55
56    #[must_use]
57    pub fn into_code(self) -> String {
58        self.code
59    }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct PrintError {
64    message: String,
65}
66
67impl std::fmt::Display for PrintError {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        f.write_str(&self.message)
70    }
71}
72
73impl std::error::Error for PrintError {}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76enum PrintMode {
77    Flat,
78    Expanded,
79}
80
81#[derive(Debug, Clone)]
82pub struct Printer {
83    options: PrinterOptions,
84}
85
86impl Printer {
87    #[must_use]
88    pub fn new(options: PrinterOptions) -> Self {
89        Self { options }
90    }
91
92    pub fn print(&self, document: &Document) -> Result<Printed, PrintError> {
93        self.print_with_capacity(document, 0)
94    }
95
96    pub fn print_with_capacity(
97        &self,
98        document: &Document,
99        output_capacity: usize,
100    ) -> Result<Printed, PrintError> {
101        let mut output = String::with_capacity(output_capacity);
102        let mut state = PrinterState::new(self.options);
103        let mut queue = PrintQueue::new(document.as_slice());
104        let mut mode_stack = Vec::new();
105
106        while let Some(element) = queue.next(&mut state, &mut mode_stack) {
107            match element {
108                FormatElement::Token(text) => state.push_token(text, &mut output),
109                FormatElement::Text(text) => {
110                    state.push_text(text.as_str(), text.metrics(), &mut output);
111                }
112                FormatElement::Space => state.push_token(" ", &mut output),
113                FormatElement::Line(line_mode) => {
114                    let mode = mode_stack.last().copied().unwrap_or(PrintMode::Expanded);
115                    match (mode, line_mode) {
116                        (PrintMode::Flat, LineMode::Soft) => {}
117                        (PrintMode::Flat, LineMode::SoftOrSpace) => {
118                            state.push_token(" ", &mut output);
119                        }
120                        (_, LineMode::Hard) | (PrintMode::Expanded, _) => {
121                            state.push_newline(&mut output);
122                        }
123                    }
124                }
125                FormatElement::Indent(document) => {
126                    queue.push(document.as_slice(), None, 1, &mut state, &mut mode_stack);
127                }
128                FormatElement::Group(document) => {
129                    let mode = if self.fits(document.as_slice(), PrintMode::Flat, state.column) {
130                        PrintMode::Flat
131                    } else {
132                        PrintMode::Expanded
133                    };
134                    queue.push(
135                        document.as_slice(),
136                        Some(mode),
137                        0,
138                        &mut state,
139                        &mut mode_stack,
140                    );
141                }
142                FormatElement::BestFit { flat, expanded } => {
143                    let (variant, mode) =
144                        if self.fits(flat.as_slice(), PrintMode::Flat, state.column) {
145                            (flat.as_slice(), PrintMode::Flat)
146                        } else {
147                            (expanded.as_slice(), PrintMode::Expanded)
148                        };
149                    queue.push(variant, Some(mode), 0, &mut state, &mut mode_stack);
150                }
151                FormatElement::Verbatim(text) => {
152                    state.push_text(text.as_str(), text.metrics(), &mut output);
153                }
154            }
155        }
156
157        Ok(Printed { code: output })
158    }
159
160    fn fits(&self, document: &[FormatElement], mode: PrintMode, column: usize) -> bool {
161        let mut width = column;
162        let mut queue = MeasureQueue::new(document, mode);
163        let line_width = usize::from(self.options.line_width);
164
165        while let Some((element, mode)) = queue.next() {
166            match element {
167                FormatElement::Token(text) => width += text.len(),
168                FormatElement::Text(text) => {
169                    if let Some(single_line_width) = text.metrics().single_line_width() {
170                        width += single_line_width;
171                    } else {
172                        width += text.metrics().first_line_width();
173                        return width <= line_width;
174                    }
175                }
176                FormatElement::Space => width += 1,
177                FormatElement::Line(line_mode) => match (mode, line_mode) {
178                    (PrintMode::Flat, LineMode::Soft) => {}
179                    (PrintMode::Flat, LineMode::SoftOrSpace) => width += 1,
180                    (_, LineMode::Hard) | (PrintMode::Expanded, _) => return width <= line_width,
181                },
182                FormatElement::Indent(document) => queue.push(document, None),
183                FormatElement::Group(document) => queue.push(document, Some(PrintMode::Flat)),
184                FormatElement::BestFit { flat, .. } => queue.push(flat, Some(PrintMode::Flat)),
185                FormatElement::Verbatim(text) => {
186                    if let Some(single_line_width) = text.metrics().single_line_width() {
187                        width += single_line_width;
188                    } else {
189                        width += text.metrics().first_line_width();
190                        return width <= line_width;
191                    }
192                }
193            }
194
195            if width > line_width {
196                return false;
197            }
198        }
199
200        true
201    }
202}
203
204#[derive(Debug, Clone)]
205struct PrintFrame<'a> {
206    elements: &'a [FormatElement],
207    index: usize,
208    mode: Option<PrintMode>,
209    indent_delta: usize,
210}
211
212#[derive(Debug, Clone)]
213struct PrintQueue<'a> {
214    frames: Vec<PrintFrame<'a>>,
215}
216
217impl<'a> PrintQueue<'a> {
218    fn new(elements: &'a [FormatElement]) -> Self {
219        Self {
220            frames: vec![PrintFrame {
221                elements,
222                index: 0,
223                mode: None,
224                indent_delta: 0,
225            }],
226        }
227    }
228
229    fn push(
230        &mut self,
231        elements: &'a [FormatElement],
232        mode: Option<PrintMode>,
233        indent_delta: usize,
234        state: &mut PrinterState,
235        mode_stack: &mut Vec<PrintMode>,
236    ) {
237        if indent_delta > 0 {
238            state.indent_level += indent_delta;
239        }
240        if let Some(mode) = mode {
241            mode_stack.push(mode);
242        }
243        self.frames.push(PrintFrame {
244            elements,
245            index: 0,
246            mode,
247            indent_delta,
248        });
249    }
250
251    fn next(
252        &mut self,
253        state: &mut PrinterState,
254        mode_stack: &mut Vec<PrintMode>,
255    ) -> Option<&'a FormatElement> {
256        loop {
257            let frame = self.frames.last_mut()?;
258            if frame.index < frame.elements.len() {
259                let element = &frame.elements[frame.index];
260                frame.index += 1;
261                return Some(element);
262            }
263
264            let frame = self.frames.pop()?;
265            state.indent_level = state.indent_level.saturating_sub(frame.indent_delta);
266            if frame.mode.is_some() {
267                mode_stack.pop();
268            }
269        }
270    }
271}
272
273#[derive(Debug, Clone)]
274struct MeasureFrame<'a> {
275    elements: &'a [FormatElement],
276    index: usize,
277    mode: Option<PrintMode>,
278}
279
280#[derive(Debug, Clone)]
281struct MeasureQueue<'a> {
282    frames: Vec<MeasureFrame<'a>>,
283    mode_stack: Vec<PrintMode>,
284}
285
286impl<'a> MeasureQueue<'a> {
287    fn new(elements: &'a [FormatElement], mode: PrintMode) -> Self {
288        Self {
289            frames: vec![MeasureFrame {
290                elements,
291                index: 0,
292                mode: Some(mode),
293            }],
294            mode_stack: vec![mode],
295        }
296    }
297
298    fn push(&mut self, document: &'a InternedDocument, mode: Option<PrintMode>) {
299        if let Some(mode) = mode {
300            self.mode_stack.push(mode);
301        }
302        self.frames.push(MeasureFrame {
303            elements: document.as_slice(),
304            index: 0,
305            mode,
306        });
307    }
308
309    fn next(&mut self) -> Option<(&'a FormatElement, PrintMode)> {
310        loop {
311            let frame = self.frames.last_mut()?;
312            if frame.index < frame.elements.len() {
313                let element = &frame.elements[frame.index];
314                frame.index += 1;
315                let mode = self
316                    .mode_stack
317                    .last()
318                    .copied()
319                    .unwrap_or(PrintMode::Expanded);
320                return Some((element, mode));
321            }
322
323            let frame = self.frames.pop()?;
324            if frame.mode.is_some() {
325                self.mode_stack.pop();
326            }
327        }
328    }
329}
330
331#[derive(Debug, Clone, Copy)]
332struct PrinterState {
333    options: PrinterOptions,
334    indent_level: usize,
335    column: usize,
336    line_has_content: bool,
337}
338
339impl PrinterState {
340    fn new(options: PrinterOptions) -> Self {
341        Self {
342            options,
343            indent_level: 0,
344            column: 0,
345            line_has_content: false,
346        }
347    }
348
349    fn push_token(&mut self, text: &str, output: &mut String) {
350        if !self.line_has_content {
351            self.push_indent(output);
352        }
353        output.push_str(text);
354        self.column += text.len();
355        self.line_has_content = true;
356    }
357
358    fn push_text(&mut self, text: &str, metrics: TextMetrics, output: &mut String) {
359        if !self.line_has_content {
360            self.push_indent(output);
361        }
362
363        output.push_str(text);
364        if let Some(width) = metrics.single_line_width() {
365            self.column += width;
366            self.line_has_content = true;
367        } else {
368            self.column = if metrics.ends_with_newline() {
369                0
370            } else {
371                metrics.last_line_width()
372            };
373            self.line_has_content = !metrics.ends_with_newline();
374        }
375    }
376
377    fn push_newline(&mut self, output: &mut String) {
378        output.push_str(self.options.line_ending.as_str());
379        self.column = 0;
380        self.line_has_content = false;
381    }
382
383    fn push_indent(&mut self, output: &mut String) {
384        if self.line_has_content || self.indent_level == 0 {
385            return;
386        }
387
388        match self.options.indent_style {
389            IndentStyle::Tab => {
390                for _ in 0..self.indent_level {
391                    output.push('\t');
392                }
393            }
394            IndentStyle::Space => {
395                for _ in 0..(self.indent_level * usize::from(self.options.indent_width)) {
396                    output.push(' ');
397                }
398            }
399        }
400
401        self.column += self.indent_width();
402        self.line_has_content = true;
403    }
404
405    fn indent_width(&self) -> usize {
406        self.indent_level * usize::from(self.options.indent_width)
407    }
408}