Skip to main content

lutra_compiler/printer/
mod.rs

1#![allow(unused_variables)]
2//! Printer that converts AST to Lutra source code.
3//!
4//! Goal is to provide full code formatting, with indentation, wrapping to
5//! prevent long lines and preservation of whitespace and comments.
6//!
7//! The printer uses a recursive descent approach to traverse the AST and
8//! generate the corresponding source code. It defines a [PrintSource] trait
9//! that is implemented for most AST node types.
10//! The [PrintSource::print] method takes a [Printer] object which contains
11//! printing configuration (ident size, max line width), buffer for generated
12//! source code and constraints for further code printing.
13//!
14//! For example, when `max_width` is 80 and we insert `let a = ` (8 chars) into
15//! the buffer, constrains are updated to reflect that current line has only
16//! 72 remaining unused chars.
17//!
18//! When printing nodes, we first try to print them inline (without new lines).
19//! If that succeeds, it will return `Some(Printer)` that contains generated
20//! code and new constrains. This printer is then merged into parent printer.
21//! If that fails, it will return `None` and we will retry by splitting expr
22//! into multiple lines.
23
24mod common;
25mod defs;
26mod expr;
27mod test;
28mod types;
29
30use crate::codespan;
31use crate::parser::lexer::{Token, TokenKind};
32use crate::pr;
33
34pub fn print_ty(ty: &pr::Ty) -> String {
35    let mut p = Printer::new(&CONFIG_NO_WRAP, None);
36    ty.print(&mut p).unwrap();
37
38    assert!(p.edits.is_empty());
39    p.buffer
40}
41
42pub fn print_source(source: &pr::Source, trivia: Option<&[Token]>) -> Vec<codespan::TextEdit> {
43    let mut p = Printer::new(&CONFIG_PRETTY, trivia);
44    p.buffer_span.source_id = source.span.source_id;
45
46    source.print(&mut p).unwrap();
47
48    p.finish_edit();
49    p.edits
50}
51
52/// Print source code of an AST node, within constrains of a printer
53/// (e.g. remaining line width). If this is not possible, return `None`.
54trait PrintSource {
55    #[must_use]
56    fn print<'c>(&self, p: &mut Printer<'c>) -> Option<()>;
57
58    /// Return span of the AST node.
59    /// Used for emitting leading & following trivia (comments, new lines).
60    fn span(&self) -> Option<crate::Span>;
61}
62
63struct Printer<'c> {
64    /// Printer configuration (indent size, max line width)
65    config: &'c Config,
66
67    /// Buffer for generated code.
68    buffer: String,
69
70    /// Span of the original code that the buffer contains code for.
71    /// Note that this is not consistent at every step - only when *particular*
72    /// nodes are finished, we increase this value.
73    buffer_span: crate::Span,
74
75    /// Remaining width of the current line.
76    rem_width: u16,
77
78    /// Number of indentation steps the next line should be prefixed with.
79    indent: u16,
80
81    /// When true, generated code must not contain new lines.
82    single_line: bool,
83
84    /// Width of suffix that is needed to complete current node.
85    /// When using single_line mode, this width can be subtracted before
86    /// printing sub-nodes.
87    pending_suffix: u16,
88
89    /// Trivia tokens from original source (e.g. comments, new lines)
90    trivia: Option<&'c [Token]>,
91
92    /// Finished edits of preceding nodes, whose code might not be contingent to
93    /// the code of the nodes in the current [Self::buffer].
94    edits: Vec<codespan::TextEdit>,
95}
96
97struct Config {
98    indent_size: u16,
99    max_width: u16,
100}
101
102const CONFIG_PRETTY: Config = Config {
103    indent_size: 2,
104    max_width: 80,
105};
106
107const CONFIG_NO_WRAP: Config = Config {
108    indent_size: 2,
109    max_width: u16::MAX,
110};
111
112impl<'c> Printer<'c> {
113    fn new(config: &'c Config, trivia: Option<&'c [Token]>) -> Printer<'c> {
114        Printer {
115            config,
116
117            buffer: String::new(),
118            buffer_span: crate::Span {
119                source_id: 0, // this is incorrect, but it is overridden later
120                start: 0,
121                len: 0,
122            },
123            indent: 0,
124            rem_width: config.max_width,
125            single_line: false,
126            pending_suffix: 0,
127
128            trivia,
129            edits: Vec::new(),
130        }
131    }
132
133    /// Appends a code snippet to generated code buffer.
134    ///
135    /// It will calculate `rem_width` after the snippet is applied and return
136    /// `None` if the snippet overflows the line.
137    #[must_use]
138    fn push<S: AsRef<str>>(&mut self, snippet: S) -> Option<()> {
139        for c in snippet.as_ref().chars() {
140            if c == '\n' {
141                self.rem_width = self.config.max_width;
142                continue;
143            }
144            if self.rem_width == 0 {
145                return None;
146            }
147            self.rem_width -= 1;
148        }
149        self.buffer += snippet.as_ref();
150        Some(())
151    }
152
153    /// Appends a code snippet to generated code buffer, but does not consume
154    /// width for it. This is to be used only when width was already consumed in
155    /// advance.
156    fn push_unchecked<S: AsRef<str>>(&mut self, snippet: S) {
157        self.buffer += snippet.as_ref();
158    }
159
160    /// Mark a span as fully printed (its printed code is in [Self::buffer]).
161    /// This is used for producing [codespan::TextEdit].
162    fn mark_printed(&mut self, span: &Option<crate::Span>) {
163        if let Some(span) = span {
164            self.buffer_span.set_end_of(span);
165        }
166    }
167
168    /// Mark the buffer to contain code for nodes up to some offset of the
169    /// original code.
170    fn mark_printed_up_to(&mut self, offset: u32) {
171        self.buffer_span.set_end(offset);
172    }
173
174    /// Creates a new [Printer] for trying printing for a sub-node without
175    /// affecting the original [Printer].
176    /// When successful, the forked [Printer] has to be [Printer::merge]ed back.
177    fn fork(&self) -> Printer<'c> {
178        Printer {
179            config: self.config,
180            buffer: String::new(),
181            buffer_span: crate::Span {
182                source_id: self.buffer_span.source_id,
183                start: self.buffer_span.end(),
184                len: 0,
185            },
186            rem_width: self.rem_width,
187            indent: self.indent,
188            single_line: self.single_line,
189            pending_suffix: self.pending_suffix,
190
191            trivia: self.trivia,
192            edits: Vec::new(),
193        }
194    }
195
196    /// Merges a forked printer back into the parent.
197    fn merge(&mut self, forked: Printer<'c>) {
198        self.rem_width = forked.rem_width;
199        self.trivia = forked.trivia;
200        self.buffer += &forked.buffer;
201        self.buffer_span.set_end_of(&forked.buffer_span);
202        self.edits.extend(forked.edits);
203    }
204
205    /// Subtracts remaining widths. Returns `None` when there is not enough
206    /// space on the line.
207    #[must_use]
208    fn consume(&mut self, width: usize) -> Option<()> {
209        self.rem_width = self.rem_width.checked_sub(width as u16)?;
210        Some(())
211    }
212
213    /// Increase indentation level.
214    fn indent(&mut self) {
215        self.indent += 1;
216    }
217
218    /// Decrease indentation level.
219    fn dedent(&mut self) {
220        self.indent -= 1;
221    }
222
223    /// Resets rem_width and produces chars for a new line + indentation of the
224    /// next line.
225    fn new_line(&mut self) {
226        let indent_width = self.indent * self.config.indent_size;
227        self.rem_width = self.config.max_width.saturating_sub(indent_width);
228
229        // When we insert a new line, we can assume that pending suffix will be
230        // placed on another line and thus does need to be considered when
231        // producing single-line outputs.
232        self.pending_suffix = 0;
233
234        let len_without_trailing_whitespace = self.buffer.trim_end_matches(' ').len();
235        self.buffer.truncate(len_without_trailing_whitespace);
236
237        self.buffer.push('\n');
238        self.buffer.push_str(&" ".repeat(indent_width as usize));
239    }
240
241    /// Enables single line mode. Also consumes width of pending node suffix.
242    #[must_use]
243    fn require_single_line(&mut self, span: Option<crate::Span>) -> Option<()> {
244        self.single_line = true;
245        self.consume(self.pending_suffix as usize)?;
246        self.pending_suffix = 0;
247
248        self.validate_no_comments(span.map(|s| s.end()))?;
249        Some(())
250    }
251
252    fn take_trivia(&mut self, until: u32) -> Option<&'c Token> {
253        let trivia = self.trivia.as_mut()?;
254
255        let t = trivia.first().filter(|t| t.span.end() <= until)?;
256
257        *trivia = &trivia[1..];
258        Some(t)
259    }
260
261    fn take_trivia_comment(&mut self, until: u32) -> Option<&'c str> {
262        let trivia = self.trivia.as_mut()?;
263
264        let t = trivia.first().filter(|t| t.span.end() <= until)?;
265
266        let TokenKind::Comment(s) = &t.kind else {
267            return None;
268        };
269        *trivia = &trivia[1..];
270        Some(s)
271    }
272
273    fn take_trivia_new_line(&mut self, until: u32) -> Option<()> {
274        let trivia = self.trivia.as_mut()?;
275
276        let t = trivia.first().filter(|t| t.span.end() <= until)?;
277
278        let TokenKind::NewLine = &t.kind else {
279            return None;
280        };
281        *trivia = &trivia[1..];
282        Some(())
283    }
284
285    fn inject_trivia_leading(&mut self, until: Option<u32>) {
286        let Some(until) = until else { return };
287
288        let mut nl_count = 0;
289
290        while let Some(trivia) = self.take_trivia(until) {
291            match &trivia.kind {
292                TokenKind::NewLine => {
293                    // print double empty lines
294                    nl_count += 1;
295                    if nl_count == 2 {
296                        self.new_line();
297                    }
298                }
299                TokenKind::Comment(comment) => {
300                    self.push_unchecked("# ");
301                    self.push_unchecked(comment);
302                    self.new_line();
303                    nl_count = 0;
304                }
305
306                _ => (),
307            }
308        }
309    }
310
311    fn inject_trivia_prev_inline(&mut self, until: Option<u32>) {
312        let Some(until) = until else { return };
313
314        while let Some(comment) = self.take_trivia_comment(until) {
315            self.push_unchecked(" # ");
316            self.push_unchecked(comment);
317        }
318    }
319
320    fn inject_trivia_trailing(&mut self, until: Option<u32>) {
321        let Some(until) = until else { return };
322
323        let mut had_empty_line = false;
324        while let Some(token) = self.take_trivia(until) {
325            let TokenKind::Comment(comment) = &token.kind else {
326                continue;
327            };
328
329            self.new_line();
330            if !had_empty_line {
331                self.new_line();
332                had_empty_line = true;
333            }
334
335            self.push_unchecked("# ");
336            self.push_unchecked(comment);
337        }
338    }
339
340    fn validate_no_comments(&mut self, until: Option<u32>) -> Option<()> {
341        let Some(until) = until else {
342            return Some(());
343        };
344
345        while let Some(trivia) = self.take_trivia(until) {
346            if let TokenKind::Comment(comment) = &trivia.kind {
347                return None;
348            }
349        }
350        Some(())
351    }
352
353    /// Convert current buffer into an [codespan::TextEdit] and save it.
354    /// This is used to skip formatting of some nodes, i.e. not produce
355    /// any [codespan::TextEdit] that would change it.
356    fn finish_edit(&mut self) {
357        let code = std::mem::take(&mut self.buffer);
358        self.edits.push(codespan::TextEdit {
359            span: self.buffer_span,
360            new_text: code,
361        });
362        self.buffer_span.start = self.buffer_span.end();
363        self.buffer_span.len = 0;
364    }
365}
366
367impl std::fmt::Debug for Printer<'_> {
368    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369        f.debug_struct("Printer")
370            .field("rem_width", &self.rem_width)
371            .field("single_line", &self.single_line)
372            .field("pending_suffix", &self.pending_suffix)
373            .finish()
374    }
375}
376
377impl<T> PrintSource for &T
378where
379    T: PrintSource,
380{
381    fn print<'c>(&self, p: &mut Printer<'c>) -> Option<()> {
382        T::print(self, p)
383    }
384
385    fn span(&self) -> Option<crate::Span> {
386        T::span(self)
387    }
388}