wdl_format/token/
pre.rs

1//! Tokens emitted during the formatting of particular elements.
2
3use std::rc::Rc;
4
5use wdl_ast::SyntaxKind;
6use wdl_ast::SyntaxTokenExt;
7
8use crate::Comment;
9use crate::Token;
10use crate::TokenStream;
11use crate::Trivia;
12use crate::TriviaBlankLineSpacingPolicy;
13
14/// A token that can be written by elements.
15///
16/// These are tokens that are intended to be written directly by elements to a
17/// [`TokenStream`](super::TokenStream) consisting of [`PreToken`]s. Note that
18/// this will transformed into a [`TokenStream`](super::TokenStream) of
19/// [`PostToken`](super::PostToken)s by a
20/// [`Postprocessor`](super::Postprocessor) (authors of elements are never
21/// expected to write [`PostToken`](super::PostToken)s directly).
22#[derive(Clone, Debug, Eq, PartialEq)]
23pub enum PreToken {
24    /// A non-trivial blank line.
25    ///
26    /// This will not be ignored by the postprocessor (unlike
27    /// [`Trivia::BlankLine`] which is potentially ignored).
28    BlankLine,
29
30    /// The end of a line.
31    LineEnd,
32
33    /// The end of a word.
34    WordEnd,
35
36    /// The start of an indented block.
37    IndentStart,
38
39    /// The end of an indented block.
40    IndentEnd,
41
42    /// How to handle trivial blank lines from this point onwards.
43    LineSpacingPolicy(TriviaBlankLineSpacingPolicy),
44
45    /// Literal text.
46    Literal(Rc<String>, SyntaxKind),
47
48    /// Trivia.
49    Trivia(Trivia),
50
51    /// A temporary indent start. Used in command section formatting.
52    ///
53    /// Command sections must account for indentation from both the
54    /// WDL context and the embedded Bash context, so this is used to
55    /// add additional indentation from the Bash context.
56    TempIndentStart,
57
58    /// A temporary indent end. Used in command section formatting.
59    ///
60    /// See [`PreToken::TempIndentStart`] for more information.
61    TempIndentEnd,
62}
63
64impl std::fmt::Display for PreToken {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        match self {
67            PreToken::BlankLine => write!(f, "<BlankLine>"),
68            PreToken::LineEnd => write!(f, "<EndOfLine>"),
69            PreToken::WordEnd => write!(f, "<WordEnd>"),
70            PreToken::IndentStart => write!(f, "<IndentStart>"),
71            PreToken::IndentEnd => write!(f, "<IndentEnd>"),
72            PreToken::LineSpacingPolicy(policy) => {
73                write!(f, "<LineSpacingPolicy@{policy:?}>")
74            }
75            PreToken::Literal(value, kind) => {
76                write!(f, "<Literal-{kind:?}@{value}>",)
77            }
78            PreToken::Trivia(trivia) => match trivia {
79                Trivia::BlankLine => {
80                    write!(f, "<OptionalBlankLine>")
81                }
82                Trivia::Comment(comment) => match comment {
83                    Comment::Preceding(value) => {
84                        write!(f, "<Comment-Preceding@{value}>",)
85                    }
86                    Comment::Inline(value) => {
87                        write!(f, "<Comment-Inline@{value}>",)
88                    }
89                },
90            },
91            PreToken::TempIndentStart => write!(f, "<TempIndentStart>"),
92            PreToken::TempIndentEnd => write!(f, "<TempIndentEnd>"),
93        }
94    }
95}
96
97impl Token for PreToken {
98    /// Returns a displayable version of the token.
99    fn display<'a>(&'a self, _config: &'a crate::Config) -> impl std::fmt::Display {
100        self
101    }
102}
103
104impl TokenStream<PreToken> {
105    /// Inserts a blank line token to the stream if the stream does not already
106    /// end with a blank line. This will replace any [`Trivia::BlankLine`]
107    /// tokens with [`PreToken::BlankLine`].
108    pub fn blank_line(&mut self) {
109        self.trim_while(|t| matches!(t, PreToken::BlankLine | PreToken::Trivia(Trivia::BlankLine)));
110        self.0.push(PreToken::BlankLine);
111    }
112
113    /// Inserts an end of line token to the stream if the stream does not
114    /// already end with an end of line token.
115    ///
116    /// This will also trim any trailing [`PreToken::WordEnd`] tokens.
117    pub fn end_line(&mut self) {
118        self.trim_while(|t| matches!(t, PreToken::WordEnd | PreToken::LineEnd));
119        self.0.push(PreToken::LineEnd);
120    }
121
122    /// Inserts a word end token to the stream if the stream does not already
123    /// end with a word end token.
124    pub fn end_word(&mut self) {
125        self.trim_end(&PreToken::WordEnd);
126        self.0.push(PreToken::WordEnd);
127    }
128
129    /// Inserts an indent start token to the stream. This will also end the
130    /// current line.
131    pub fn increment_indent(&mut self) {
132        self.end_line();
133        self.0.push(PreToken::IndentStart);
134    }
135
136    /// Inserts an indent end token to the stream. This will also end the
137    /// current line.
138    pub fn decrement_indent(&mut self) {
139        self.end_line();
140        self.0.push(PreToken::IndentEnd);
141    }
142
143    /// Inserts a trivial blank lines "always allowed" context change.
144    pub fn allow_blank_lines(&mut self) {
145        self.0.push(PreToken::LineSpacingPolicy(
146            TriviaBlankLineSpacingPolicy::Always,
147        ));
148    }
149
150    /// Inserts a trivial blank lines "not allowed after comments" context
151    /// change.
152    pub fn ignore_trailing_blank_lines(&mut self) {
153        self.0.push(PreToken::LineSpacingPolicy(
154            TriviaBlankLineSpacingPolicy::RemoveTrailingBlanks,
155        ));
156    }
157
158    /// Inserts any preceding trivia into the stream.
159    ///
160    /// # Panics
161    ///
162    /// This will panic if the provided token is itself trivia, as trivia
163    /// cannot have trivia.
164    fn push_preceding_trivia(&mut self, token: &wdl_ast::Token) {
165        assert!(!token.inner().kind().is_trivia());
166        let preceding_trivia = token.inner().preceding_trivia();
167        for token in preceding_trivia {
168            match token.kind() {
169                SyntaxKind::Whitespace => {
170                    if !self.0.last().is_some_and(|t| {
171                        matches!(t, PreToken::BlankLine | PreToken::Trivia(Trivia::BlankLine))
172                    }) {
173                        self.0.push(PreToken::Trivia(Trivia::BlankLine));
174                    }
175                }
176                SyntaxKind::Comment => {
177                    let comment = PreToken::Trivia(Trivia::Comment(Comment::Preceding(Rc::new(
178                        token.text().trim_end().to_owned(),
179                    ))));
180                    self.0.push(comment);
181                }
182                _ => unreachable!("unexpected trivia: {:?}", token),
183            };
184        }
185    }
186
187    /// Inserts any inline trivia into the stream.
188    ///
189    /// # Panics
190    ///
191    /// This will panic if the provided token is itself trivia, as trivia
192    /// cannot have trivia.
193    fn push_inline_trivia(&mut self, token: &wdl_ast::Token) {
194        assert!(!token.inner().kind().is_trivia());
195        if let Some(token) = token.inner().inline_comment() {
196            let inline_comment = PreToken::Trivia(Trivia::Comment(Comment::Inline(Rc::new(
197                token.text().trim_end().to_owned(),
198            ))));
199            self.0.push(inline_comment);
200        }
201    }
202
203    /// Pushes an AST token into the stream.
204    ///
205    /// This will also push any preceding or inline trivia into the stream.
206    /// Any token may have preceding or inline trivia, unless that token is
207    /// itself trivia (i.e. trivia cannot have trivia).
208    ///
209    /// # Panics
210    ///
211    /// This will panic if the provided token is trivia.
212    pub fn push_ast_token(&mut self, token: &wdl_ast::Token) {
213        self.push_preceding_trivia(token);
214        self.0.push(PreToken::Literal(
215            Rc::new(token.inner().text().to_owned()),
216            token.inner().kind(),
217        ));
218        self.push_inline_trivia(token);
219    }
220
221    /// Pushes a literal string into the stream in place of an AST token.
222    ///
223    /// This will insert any trivia that would have been inserted with the AST
224    /// token.
225    ///
226    /// # Panics
227    ///
228    /// This will panic if the provided token is trivia.
229    pub fn push_literal_in_place_of_token(&mut self, token: &wdl_ast::Token, replacement: String) {
230        self.push_preceding_trivia(token);
231        self.0.push(PreToken::Literal(
232            Rc::new(replacement),
233            token.inner().kind(),
234        ));
235        self.push_inline_trivia(token);
236    }
237
238    /// Pushes a literal string into the stream.
239    ///
240    /// This will not insert any trivia.
241    pub fn push_literal(&mut self, value: String, kind: SyntaxKind) {
242        self.0.push(PreToken::Literal(Rc::new(value), kind));
243    }
244
245    /// Returns the kind of the last literal token in the stream.
246    pub fn last_literal_kind(&self) -> Option<SyntaxKind> {
247        match self.0.last_chunk::<3>() {
248            Some([_, _, PreToken::Literal(_, kind)]) => Some(*kind),
249            Some([_, PreToken::Literal(_, kind), _]) => Some(*kind),
250            Some([PreToken::Literal(_, kind), _, _]) => Some(*kind),
251            _ => None,
252        }
253    }
254}