Skip to main content

wdl_format/token/
pre.rs

1//! Tokens emitted during the formatting of particular elements.
2
3use std::collections::HashSet;
4use std::rc::Rc;
5
6use wdl_ast::AstToken;
7use wdl_ast::Directive;
8use wdl_ast::SyntaxKind;
9use wdl_ast::SyntaxTokenExt;
10
11use crate::Comment;
12use crate::Token;
13use crate::TokenStream;
14use crate::Trivia;
15use crate::TriviaBlankLineSpacingPolicy;
16
17/// A token that can be written by elements.
18///
19/// These are tokens that are intended to be written directly by elements to a
20/// [`TokenStream`](super::TokenStream) consisting of [`PreToken`]s. Note that
21/// this will transformed into a [`TokenStream`](super::TokenStream) of
22/// [`PostToken`](super::PostToken)s by a
23/// [`Postprocessor`](super::Postprocessor) (authors of elements are never
24/// expected to write [`PostToken`](super::PostToken)s directly).
25#[derive(Clone, Debug, Eq, PartialEq)]
26pub enum PreToken {
27    /// A non-trivial blank line.
28    ///
29    /// This will not be ignored by the postprocessor (unlike
30    /// [`Trivia::BlankLine`] which is potentially ignored).
31    BlankLine,
32
33    /// The end of a line.
34    LineEnd,
35
36    /// The end of a word.
37    WordEnd,
38
39    /// The start of an indented block.
40    IndentStart,
41
42    /// The end of an indented block.
43    IndentEnd,
44
45    /// How to handle trivial blank lines from this point onwards.
46    LineSpacingPolicy(TriviaBlankLineSpacingPolicy),
47
48    /// Literal text.
49    Literal(Rc<String>, SyntaxKind),
50
51    /// Trivia.
52    Trivia(Trivia),
53
54    /// A temporary indent start. Used in command section formatting.
55    ///
56    /// Command sections must account for indentation from both the
57    /// WDL context and the embedded Bash context, so this is used to
58    /// add additional indentation from the Bash context.
59    TempIndentStart(Rc<String>),
60
61    /// A temporary indent end. Used in command section formatting.
62    ///
63    /// See [`PreToken::TempIndentStart`] for more information.
64    TempIndentEnd,
65
66    /// The start of a fit or split block.
67    FitOrSplitStart {
68        /// If the block will be "fit", insert this literal string at the
69        /// beginning.
70        fit_start: Rc<String>,
71        /// If the block will be "fit", insert this literal string between each
72        /// potential split.
73        fit_delimiter: Rc<String>,
74        /// If the block will be split, end the line immediately.
75        split_end_line: bool,
76    },
77
78    /// A potential split in a fit or split block.
79    PotentialSplit,
80
81    /// The end of a fit or split block.
82    FitOrSplitEnd {
83        /// If the block will be "fit", insert this literal string at the end.
84        fit_end: Rc<String>,
85        /// If the block will be split, insert this literal string at the end.
86        split_end: Rc<String>,
87        /// If the block will be split, end the line after inserting
88        /// `split_end`.
89        split_end_line: bool,
90    },
91}
92
93impl std::fmt::Display for PreToken {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        match self {
96            PreToken::BlankLine => write!(f, "<BlankLine>"),
97            PreToken::LineEnd => write!(f, "<EndOfLine>"),
98            PreToken::WordEnd => write!(f, "<WordEnd>"),
99            PreToken::IndentStart => write!(f, "<IndentStart>"),
100            PreToken::IndentEnd => write!(f, "<IndentEnd>"),
101            PreToken::LineSpacingPolicy(policy) => {
102                write!(f, "<LineSpacingPolicy@{policy:?}>")
103            }
104            PreToken::Literal(value, kind) => {
105                write!(f, "<Literal-{kind:?}@{value}>")
106            }
107            PreToken::Trivia(trivia) => match trivia {
108                Trivia::BlankLine => {
109                    write!(f, "<OptionalBlankLine>")
110                }
111                Trivia::Comment(comment) => match comment {
112                    Comment::Directive(directive) => {
113                        write!(f, "<Comment-Directive@{directive:?}>")
114                    }
115                    Comment::Documentation(documentation) => {
116                        write!(f, "<Comment-Documentation@{documentation}>")
117                    }
118                    Comment::Preceding(value) => {
119                        write!(f, "<Comment-Preceding@{value}>")
120                    }
121                    Comment::Inline(value) => {
122                        write!(f, "<Comment-Inline@{value}>")
123                    }
124                },
125            },
126            PreToken::TempIndentStart(value) => write!(f, "<TempIndentStart@{value}>"),
127            PreToken::TempIndentEnd => write!(f, "<TempIndentEnd>"),
128            PreToken::FitOrSplitStart { .. } => write!(f, "<FitOrSplitStart>"),
129            PreToken::PotentialSplit => write!(f, "<PotentialSplit>"),
130            PreToken::FitOrSplitEnd { .. } => write!(f, "<FitOrSplitEnd>"),
131        }
132    }
133}
134
135impl Token for PreToken {
136    /// Returns a displayable version of the token.
137    fn display<'a>(&'a self, _config: &'a crate::Config) -> impl std::fmt::Display {
138        self
139    }
140}
141
142impl TokenStream<PreToken> {
143    /// Inserts a blank line token to the stream if the stream does not already
144    /// end with a blank line. This will replace any [`Trivia::BlankLine`]
145    /// tokens with [`PreToken::BlankLine`].
146    pub fn blank_line(&mut self) {
147        self.trim_while(|t| matches!(t, PreToken::BlankLine | PreToken::Trivia(Trivia::BlankLine)));
148        self.0.push(PreToken::BlankLine);
149    }
150
151    /// Inserts an end of line token to the stream if the stream does not
152    /// already end with an end of line token.
153    ///
154    /// This will also trim any trailing [`PreToken::WordEnd`] tokens.
155    pub fn end_line(&mut self) {
156        self.trim_while(|t| matches!(t, PreToken::WordEnd | PreToken::LineEnd));
157        self.0.push(PreToken::LineEnd);
158    }
159
160    /// Inserts a word end token to the stream if the stream does not already
161    /// end with a word end token.
162    pub fn end_word(&mut self) {
163        self.trim_end(&PreToken::WordEnd);
164        self.0.push(PreToken::WordEnd);
165    }
166
167    /// Inserts an indent start token to the stream. This will **not** end the
168    /// current line.
169    ///
170    /// Callers that want the indent change to take effect on the next line must
171    /// call `end_line()` after this.
172    pub fn increment_indent(&mut self) {
173        self.0.push(PreToken::IndentStart);
174    }
175
176    /// Inserts an indent end token to the stream. This will **not** end the
177    /// current line.
178    ///
179    /// Callers that want the indent change to take effect on the next line must
180    /// call `end_line()` after this.
181    pub fn decrement_indent(&mut self) {
182        self.0.push(PreToken::IndentEnd);
183    }
184
185    /// Start a fit or split block.
186    pub fn fit_or_split_start(
187        &mut self,
188        fit_start: Rc<String>,
189        fit_delimiter: Rc<String>,
190        split_end_line: bool,
191    ) {
192        self.0.push(PreToken::FitOrSplitStart {
193            fit_start,
194            fit_delimiter,
195            split_end_line,
196        })
197    }
198
199    /// Insert a potential split in the middle of a fit or split block.
200    pub fn potential_split(&mut self) {
201        self.0.push(PreToken::PotentialSplit);
202    }
203
204    /// End a fit or split block.
205    pub fn fit_or_split_end(
206        &mut self,
207        fit_end: Rc<String>,
208        split_end: Rc<String>,
209        split_end_line: bool,
210    ) {
211        self.0.push(PreToken::FitOrSplitEnd {
212            fit_end,
213            split_end,
214            split_end_line,
215        })
216    }
217
218    /// Inserts a trivial blank lines "always allowed" context change.
219    pub fn allow_blank_lines(&mut self) {
220        self.0.push(PreToken::LineSpacingPolicy(
221            TriviaBlankLineSpacingPolicy::Always,
222        ));
223    }
224
225    /// Inserts a trivial blank lines "not allowed after comments" context
226    /// change.
227    pub fn ignore_trailing_blank_lines(&mut self) {
228        self.0.push(PreToken::LineSpacingPolicy(
229            TriviaBlankLineSpacingPolicy::RemoveTrailingBlanks,
230        ));
231    }
232
233    /// Inserts any preceding trivia into the stream.
234    ///
235    /// This will consolidate directive comments which
236    /// precede this token.
237    ///
238    /// # Panics
239    ///
240    /// This will panic if the provided token is itself trivia, as trivia
241    /// cannot have trivia.
242    fn push_preceding_trivia(&mut self, token: &wdl_ast::Token) {
243        assert!(!token.inner().kind().is_trivia());
244        let preceding_trivia = token.inner().preceding_trivia();
245        let mut trivia = Vec::new();
246        let mut exceptions = HashSet::new();
247        for token in preceding_trivia {
248            match token.kind() {
249                SyntaxKind::Whitespace => {
250                    if !self.0.last().is_some_and(|t| {
251                        matches!(t, PreToken::BlankLine | PreToken::Trivia(Trivia::BlankLine))
252                    }) {
253                        trivia.push(PreToken::Trivia(Trivia::BlankLine));
254                    }
255                }
256                SyntaxKind::Comment => {
257                    if let Some(comment) = wdl_ast::Comment::cast(token.clone())
258                        && let Some(directive) = comment.directive()
259                    {
260                        match directive {
261                            Directive::Except(e) => exceptions.extend(e),
262                        }
263                    } else {
264                        let comment = PreToken::Trivia(Trivia::Comment(Comment::Preceding(
265                            Rc::new(token.text().trim_end().to_string()),
266                        )));
267                        trivia.push(comment);
268                    }
269                }
270                _ => unreachable!("unexpected trivia: {:?}", token),
271            };
272        }
273
274        for token in trivia {
275            self.0.push(token);
276        }
277
278        if !exceptions.is_empty() {
279            let comment = PreToken::Trivia(Trivia::Comment(Comment::Directive(Rc::new(
280                Directive::Except(exceptions),
281            ))));
282            self.0.push(comment);
283        }
284    }
285
286    /// Inserts any inline trivia into the stream.
287    ///
288    /// # Panics
289    ///
290    /// This will panic if the provided token is itself trivia, as trivia
291    /// cannot have trivia.
292    fn push_inline_trivia(&mut self, token: &wdl_ast::Token) {
293        assert!(!token.inner().kind().is_trivia());
294        if let Some(token) = token.inner().inline_comment() {
295            let inline_comment = PreToken::Trivia(Trivia::Comment(Comment::Inline(Rc::new(
296                token.text().trim_end().to_owned(),
297            ))));
298            self.0.push(inline_comment);
299        }
300    }
301
302    /// Pushes an AST token into the stream.
303    ///
304    /// This will also push any preceding or inline trivia into the stream.
305    /// Any token may have preceding or inline trivia, unless that token is
306    /// itself trivia (i.e. trivia cannot have trivia).
307    ///
308    /// # Panics
309    ///
310    /// This will panic if the provided token is trivia.
311    pub fn push_ast_token(&mut self, token: &wdl_ast::Token) {
312        self.push_preceding_trivia(token);
313        self.0.push(PreToken::Literal(
314            Rc::new(token.inner().text().to_owned()),
315            token.inner().kind(),
316        ));
317        self.push_inline_trivia(token);
318    }
319
320    /// Pushes an AST token into the stream as another [`SyntaxKind`].
321    ///
322    /// This will insert any trivia that would have been inserted with the AST
323    /// token.
324    ///
325    /// # Panics
326    ///
327    /// This will panic if the provided token is trivia.
328    pub fn push_ast_token_as(&mut self, token: &wdl_ast::Token, kind: SyntaxKind) {
329        self.push_preceding_trivia(token);
330        self.0.push(PreToken::Literal(
331            Rc::new(token.inner().text().to_owned()),
332            kind,
333        ));
334        self.push_inline_trivia(token);
335    }
336
337    /// Pushes a literal string into the stream in place of an AST token.
338    ///
339    /// This will insert any trivia that would have been inserted with the AST
340    /// token.
341    ///
342    /// # Panics
343    ///
344    /// This will panic if the provided token is trivia.
345    pub fn push_literal_in_place_of_token(&mut self, token: &wdl_ast::Token, replacement: String) {
346        self.push_preceding_trivia(token);
347        self.0.push(PreToken::Literal(
348            Rc::new(replacement),
349            token.inner().kind(),
350        ));
351        self.push_inline_trivia(token);
352    }
353
354    /// Pushes a literal string into the stream.
355    ///
356    /// This will not insert any trivia.
357    pub fn push_literal(&mut self, value: String, kind: SyntaxKind) {
358        self.0.push(PreToken::Literal(Rc::new(value), kind));
359    }
360}