rune/fmt/
output.rs

1use core::mem::take;
2
3use crate::alloc::prelude::*;
4use crate::alloc::{self, VecDeque};
5use crate::ast::{Kind, Span};
6use crate::compile::{Error, ErrorKind, FmtOptions, Result, WithSpan};
7use crate::grammar::{Ignore, Node, Tree};
8use crate::{Diagnostics, SourceId};
9
10use super::{INDENT, NL, NL_CHAR, WS};
11
12/// Hint for how comments may be laid out.
13pub(super) enum Comments {
14    /// Any kind of comment can be inserted and should be line-separated.
15    Line,
16    /// Comments may be inserted in a whitespace prefix position, like `(<comment>args`.
17    Prefix,
18    /// Comments may be inserted in a whitespace suffix position, like `args<comment>)`.
19    Suffix,
20    /// An infix comment hint, like `(<comment>)` where there is no preceeding
21    /// or succeeding whitespace.
22    Infix,
23}
24
25#[derive(Clone, Copy, Debug)]
26struct Comment {
27    span: Span,
28    before: usize,
29    line: bool,
30}
31
32/// A source of text.
33#[repr(transparent)]
34pub(super) struct Source(str);
35
36impl Source {
37    fn new(source: &str) -> &Self {
38        // Safety: Source is repr transparent over str.
39        unsafe { &*(source as *const str as *const Self) }
40    }
41
42    /// Get a checked span from the source.
43    pub(super) fn get(&self, span: Span) -> Result<&str> {
44        let Some(source) = self.0.get(span.range()) else {
45            return Err(Error::new(span, ErrorKind::BadSpan { len: self.0.len() }));
46        };
47
48        Ok(source)
49    }
50
51    /// Perform a whitespace-insensitive count and check if it's more than
52    /// `count`.
53    pub(super) fn is_at_least(&self, span: Span, mut count: usize) -> Result<bool> {
54        let source = self.get(span)?;
55
56        for c in source.chars() {
57            if c.is_whitespace() {
58                continue;
59            }
60
61            let Some(c) = count.checked_sub(1) else {
62                return Ok(true);
63            };
64
65            count = c;
66        }
67
68        Ok(false)
69    }
70}
71
72/// The output buffer.
73#[repr(transparent)]
74pub(super) struct Buffer(String);
75
76impl Buffer {
77    fn new(output: &mut String) -> &mut Self {
78        // Safety: Source is repr transparent over str.
79        unsafe { &mut *(output as *mut String as *mut Self) }
80    }
81
82    #[inline]
83    fn is_empty(&self) -> bool {
84        self.0.is_empty()
85    }
86
87    #[inline]
88    fn str(&mut self, s: &str) -> alloc::Result<()> {
89        self.0.try_push_str(s)
90    }
91
92    fn lines(&mut self, indent: usize, lines: usize) -> alloc::Result<()> {
93        if lines == 0 {
94            return Ok(());
95        }
96
97        for _ in 0..lines {
98            self.0.try_push_str(NL)?;
99        }
100
101        for _ in 0..indent {
102            self.0.try_push_str(INDENT)?;
103        }
104
105        Ok(())
106    }
107}
108
109/// A constructed syntax tree.
110pub(crate) struct Formatter<'a> {
111    span: Span,
112    pub(super) source: &'a Source,
113    source_id: SourceId,
114    o: &'a mut Buffer,
115    pub(super) options: &'a FmtOptions,
116    diagnostics: &'a mut Diagnostics,
117    comments: VecDeque<Comment>,
118    lines: usize,
119    use_lines: bool,
120    ws: bool,
121    indent: usize,
122}
123
124impl<'a> Formatter<'a> {
125    /// Construct a new tree.
126    pub(super) fn new(
127        span: Span,
128        source: &'a str,
129        source_id: SourceId,
130        o: &'a mut String,
131        options: &'a FmtOptions,
132        diagnostics: &'a mut Diagnostics,
133    ) -> Self {
134        Self {
135            span,
136            source: Source::new(source),
137            source_id,
138            o: Buffer::new(o),
139            options,
140            diagnostics,
141            comments: VecDeque::new(),
142            lines: 0,
143            use_lines: false,
144            ws: false,
145            indent: 0,
146        }
147    }
148
149    /// Ignore the given node.
150    pub(crate) fn ignore(&mut self, node: Node<'a>) -> Result<()> {
151        self.process_comments(node.walk_from())?;
152        Ok(())
153    }
154
155    /// Write the give node to output.
156    pub(crate) fn write_owned(&mut self, node: Node<'a>) -> Result<()> {
157        self.flush_whitespace(false)?;
158        self.write_node(&node)?;
159        self.process_comments(node.walk_from())?;
160        Ok(())
161    }
162
163    /// Write the give node to output without comment or whitespace processing.
164    pub(crate) fn write_raw(&mut self, node: Node<'a>) -> Result<()> {
165        self.write_node(&node)?;
166        self.process_comments(node.walk_from())?;
167        Ok(())
168    }
169
170    /// Buffer literal to output.
171    pub(crate) fn lit(&mut self, s: &str) -> Result<()> {
172        // We want whitespace to be preserved *unless* it was written out, since
173        // a literal is a synthetic token.
174        self.flush_whitespace(true)?;
175        self.o.str(s).with_span(self.span)?;
176        Ok(())
177    }
178
179    /// Flush any remaining whitespace.
180    pub(super) fn comments(&mut self, comments: Comments) -> Result<()> {
181        if self.comments.is_empty() {
182            return Ok(());
183        }
184
185        match comments {
186            Comments::Line => {
187                self.comments_line(false)?;
188            }
189            Comments::Prefix | Comments::Suffix => {
190                // Confusingly, the comment hint determines the location of the
191                // comment relative to any relevant token, so it *looks* like
192                // they are flipped here. But the writer function is simply used
193                // to determine where the whitespace should be located.
194                self.comments_ws(
195                    matches!(comments, Comments::Suffix),
196                    matches!(comments, Comments::Prefix),
197                )?;
198            }
199            Comments::Infix => {
200                self.comments_ws(false, false)?;
201            }
202        }
203
204        Ok(())
205    }
206
207    /// Indent the output.
208    pub(super) fn indent(&mut self, indent: isize) -> Result<()> {
209        if indent != 0 {
210            self.indent = self.checked_indent(indent)?;
211        }
212
213        Ok(())
214    }
215
216    /// Emit a line hint, indicating that the next write should be on a new line
217    /// separated by at least `nl` lines.
218    ///
219    /// The value of `nl` is clamped to the range `[0, 2]`.
220    ///
221    /// This will write any pending line comments which are on the same line as
222    /// the previously written nodes.
223    pub(crate) fn nl(&mut self, lines: usize) -> Result<()> {
224        if lines == 0 {
225            return Ok(());
226        }
227
228        self.comments_line(true)?;
229
230        // If we don't already have line heuristics, adopt the proposed one.
231        if self.lines == 0 {
232            self.lines = lines;
233        }
234
235        // At this point, we will use lines for the next flush.
236        self.use_lines = true;
237        Ok(())
238    }
239
240    /// Emit a whitespace hint, indicating that the next node write should
241    /// happen with preceeding whitespace.
242    ///
243    /// This emits a `Comments::Suffix` hint by default, since we *expect*
244    /// whitespace to be followed by tokens which will add any additional
245    /// whitespace.
246    pub(super) fn ws(&mut self) -> Result<()> {
247        self.comments_ws(true, false)?;
248        self.ws = true;
249        Ok(())
250    }
251
252    /// Write leading comments.
253    pub(super) fn flush_prefix_comments(&mut self, tree: &'a Tree) -> Result<()> {
254        self.process_comments(tree.walk())?;
255        self.comments(Comments::Line)?;
256        self.use_lines = self.lines > 0;
257        Ok(())
258    }
259
260    /// Smuggle in line comments when we receive a line hint.
261    fn comments_line(&mut self, same_line: bool) -> Result<()> {
262        while let Some(c) = self.comments.front() {
263            if same_line && c.before != 0 {
264                break;
265            }
266
267            if !self.o.is_empty() {
268                if c.before == 0 {
269                    self.o.str(WS).with_span(c.span)?;
270                } else {
271                    self.o
272                        .lines(self.indent, c.before.min(2))
273                        .with_span(c.span)?;
274                }
275            }
276
277            let source = self.source.get(c.span)?;
278            let source = if c.line { source.trim_end() } else { source };
279            self.o.str(source).with_span(c.span)?;
280
281            _ = self.comments.pop_front();
282        }
283
284        Ok(())
285    }
286
287    /// Smuggle in whitespace comments when we receive a whitespace hint.
288    fn comments_ws(&mut self, prefix: bool, suffix: bool) -> Result<()> {
289        if self.comments.is_empty() {
290            return Ok(());
291        }
292
293        let mut any = false;
294
295        while let Some(c) = self.comments.front() {
296            if c.line {
297                break;
298            }
299
300            if (prefix || any) && !self.o.is_empty() {
301                self.o.str(WS).with_span(c.span)?;
302            }
303
304            let source = self.source.get(c.span)?;
305            self.o.str(source).with_span(c.span)?;
306
307            any = true;
308
309            _ = self.comments.pop_front();
310        }
311
312        if suffix && any {
313            self.o.str(WS).with_span(self.span)?;
314        }
315
316        Ok(())
317    }
318
319    fn process_comments<I>(&mut self, iter: I) -> Result<()>
320    where
321        I: IntoIterator<Item = Node<'a>>,
322    {
323        for node in iter {
324            if !node.has_children() && !self.write_comment(node)? {
325                break;
326            }
327        }
328
329        Ok(())
330    }
331
332    fn write_comment(&mut self, node: Node<'a>) -> Result<bool> {
333        let span = node.span();
334
335        match node.kind() {
336            Kind::Comment | Kind::MultilineComment(..) => {
337                self.comments
338                    .try_push_back(Comment {
339                        span,
340                        before: take(&mut self.lines),
341                        line: matches!(node.kind(), Kind::Comment),
342                    })
343                    .with_span(span)?;
344
345                Ok(true)
346            }
347            Kind::Whitespace => {
348                let source = self.source.get(span)?;
349                let count = source.chars().filter(|c| *c == NL_CHAR).count();
350
351                if self.lines == 0 {
352                    self.lines = count;
353                }
354
355                Ok(true)
356            }
357            _ => Ok(false),
358        }
359    }
360
361    fn checked_indent(&mut self, level: isize) -> Result<usize> {
362        let Some(indent) = self.indent.checked_add_signed(level) else {
363            return Err(Error::new(
364                self.span,
365                ErrorKind::BadIndent {
366                    level,
367                    indent: self.indent,
368                },
369            ));
370        };
371
372        Ok(indent)
373    }
374
375    fn write_node(&mut self, node: &Node<'_>) -> Result<()> {
376        let source = self.source.get(node.span())?;
377        self.span = node.span();
378        self.o.str(source).with_span(self.span)?;
379        Ok(())
380    }
381
382    pub(crate) fn flush_whitespace(&mut self, preserve: bool) -> Result<()> {
383        if self.use_lines && self.lines > 0 {
384            self.o.lines(self.indent, self.lines.min(2))?;
385            self.ws = false;
386            self.use_lines = false;
387            self.lines = 0;
388        }
389
390        if self.ws {
391            self.o.str(WS).with_span(self.span)?;
392            self.ws = false;
393        }
394
395        if !preserve {
396            self.lines = 0;
397            self.use_lines = false;
398        }
399
400        Ok(())
401    }
402}
403
404impl<'a> Ignore<'a> for Formatter<'a> {
405    fn error(&mut self, error: Error) -> alloc::Result<()> {
406        self.diagnostics.error(self.source_id, error)
407    }
408
409    fn ignore(&mut self, node: Node<'a>) -> Result<()> {
410        Formatter::ignore(self, node)
411    }
412}