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}