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}