1use 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
14fn normalize_except_directive(text: &str) -> String {
16 let Some(remainder) = text.trim_start().strip_prefix("#@") else {
17 return text.to_owned();
18 };
19
20 let Some(rules_text) = remainder.trim_start().strip_prefix("except:") else {
21 return text.to_owned();
22 };
23
24 let mut rules: Vec<String> = rules_text
26 .split(',')
27 .map(|s| s.trim().to_owned())
28 .filter(|s| !s.is_empty())
29 .collect();
30
31 rules.sort_by_key(|a| a.to_ascii_lowercase());
33
34 format!("#@ except: {}", rules.join(", "))
36}
37
38#[derive(Clone, Debug, Eq, PartialEq)]
47pub enum PreToken {
48 BlankLine,
53
54 LineEnd,
56
57 WordEnd,
59
60 IndentStart,
62
63 IndentEnd,
65
66 LineSpacingPolicy(TriviaBlankLineSpacingPolicy),
68
69 Literal(Rc<String>, SyntaxKind),
71
72 Trivia(Trivia),
74
75 TempIndentStart,
81
82 TempIndentEnd,
86}
87
88impl std::fmt::Display for PreToken {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 match self {
91 PreToken::BlankLine => write!(f, "<BlankLine>"),
92 PreToken::LineEnd => write!(f, "<EndOfLine>"),
93 PreToken::WordEnd => write!(f, "<WordEnd>"),
94 PreToken::IndentStart => write!(f, "<IndentStart>"),
95 PreToken::IndentEnd => write!(f, "<IndentEnd>"),
96 PreToken::LineSpacingPolicy(policy) => {
97 write!(f, "<LineSpacingPolicy@{policy:?}>")
98 }
99 PreToken::Literal(value, kind) => {
100 write!(f, "<Literal-{kind:?}@{value}>",)
101 }
102 PreToken::Trivia(trivia) => match trivia {
103 Trivia::BlankLine => {
104 write!(f, "<OptionalBlankLine>")
105 }
106 Trivia::Comment(comment) => match comment {
107 Comment::Preceding(value) => {
108 write!(f, "<Comment-Preceding@{value}>",)
109 }
110 Comment::Inline(value) => {
111 write!(f, "<Comment-Inline@{value}>",)
112 }
113 },
114 },
115 PreToken::TempIndentStart => write!(f, "<TempIndentStart>"),
116 PreToken::TempIndentEnd => write!(f, "<TempIndentEnd>"),
117 }
118 }
119}
120
121impl Token for PreToken {
122 fn display<'a>(&'a self, _config: &'a crate::Config) -> impl std::fmt::Display {
124 self
125 }
126}
127
128impl TokenStream<PreToken> {
129 pub fn blank_line(&mut self) {
133 self.trim_while(|t| matches!(t, PreToken::BlankLine | PreToken::Trivia(Trivia::BlankLine)));
134 self.0.push(PreToken::BlankLine);
135 }
136
137 pub fn end_line(&mut self) {
142 self.trim_while(|t| matches!(t, PreToken::WordEnd | PreToken::LineEnd));
143 self.0.push(PreToken::LineEnd);
144 }
145
146 pub fn end_word(&mut self) {
149 self.trim_end(&PreToken::WordEnd);
150 self.0.push(PreToken::WordEnd);
151 }
152
153 pub fn increment_indent(&mut self) {
156 self.end_line();
157 self.0.push(PreToken::IndentStart);
158 }
159
160 pub fn decrement_indent(&mut self) {
163 self.end_line();
164 self.0.push(PreToken::IndentEnd);
165 }
166
167 pub fn allow_blank_lines(&mut self) {
169 self.0.push(PreToken::LineSpacingPolicy(
170 TriviaBlankLineSpacingPolicy::Always,
171 ));
172 }
173
174 pub fn ignore_trailing_blank_lines(&mut self) {
177 self.0.push(PreToken::LineSpacingPolicy(
178 TriviaBlankLineSpacingPolicy::RemoveTrailingBlanks,
179 ));
180 }
181
182 fn push_preceding_trivia(&mut self, token: &wdl_ast::Token) {
189 assert!(!token.inner().kind().is_trivia());
190 let preceding_trivia = token.inner().preceding_trivia();
191 for token in preceding_trivia {
192 match token.kind() {
193 SyntaxKind::Whitespace => {
194 if !self.0.last().is_some_and(|t| {
195 matches!(t, PreToken::BlankLine | PreToken::Trivia(Trivia::BlankLine))
196 }) {
197 self.0.push(PreToken::Trivia(Trivia::BlankLine));
198 }
199 }
200 SyntaxKind::Comment => {
201 let normalized = normalize_except_directive(token.text().trim_end());
202 let comment =
203 PreToken::Trivia(Trivia::Comment(Comment::Preceding(Rc::new(normalized))));
204 self.0.push(comment);
205 }
206 _ => unreachable!("unexpected trivia: {:?}", token),
207 };
208 }
209 }
210
211 fn push_inline_trivia(&mut self, token: &wdl_ast::Token) {
218 assert!(!token.inner().kind().is_trivia());
219 if let Some(token) = token.inner().inline_comment() {
220 let inline_comment = PreToken::Trivia(Trivia::Comment(Comment::Inline(Rc::new(
221 token.text().trim_end().to_owned(),
222 ))));
223 self.0.push(inline_comment);
224 }
225 }
226
227 pub fn push_ast_token(&mut self, token: &wdl_ast::Token) {
237 self.push_preceding_trivia(token);
238 self.0.push(PreToken::Literal(
239 Rc::new(token.inner().text().to_owned()),
240 token.inner().kind(),
241 ));
242 self.push_inline_trivia(token);
243 }
244
245 pub fn push_literal_in_place_of_token(&mut self, token: &wdl_ast::Token, replacement: String) {
254 self.push_preceding_trivia(token);
255 self.0.push(PreToken::Literal(
256 Rc::new(replacement),
257 token.inner().kind(),
258 ));
259 self.push_inline_trivia(token);
260 }
261
262 pub fn push_literal(&mut self, value: String, kind: SyntaxKind) {
266 self.0.push(PreToken::Literal(Rc::new(value), kind));
267 }
268}