1use std::collections::HashSet;
7use std::fmt::Display;
8use std::rc::Rc;
9
10use wdl_ast::SyntaxKind;
11
12use crate::Comment;
13use crate::Config;
14use crate::NEWLINE;
15use crate::PreToken;
16use crate::SPACE;
17use crate::Token;
18use crate::TokenStream;
19use crate::Trivia;
20use crate::TriviaBlankLineSpacingPolicy;
21
22const INLINE_COMMENT_PRECEDING_TOKENS: [PostToken; 2] = [PostToken::Space, PostToken::Space];
24
25#[derive(Clone, Eq, PartialEq)]
27pub enum PostToken {
28 Space,
30
31 Newline,
33
34 Indent,
36
37 TempIndent(Rc<String>),
42
43 Literal(Rc<String>),
45}
46
47impl std::fmt::Debug for PostToken {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 match self {
50 Self::Space => write!(f, "<SPACE>"),
51 Self::Newline => write!(f, "<NEWLINE>"),
52 Self::Indent => write!(f, "<INDENT>"),
53 Self::TempIndent(value) => write!(f, "<TEMP_INDENT@{value}>"),
54 Self::Literal(value) => write!(f, "<LITERAL@{value}>"),
55 }
56 }
57}
58
59impl Token for PostToken {
60 fn display<'a>(&'a self, config: &'a Config) -> impl Display + 'a {
62 struct Display<'a> {
64 token: &'a PostToken,
66 config: &'a Config,
68 }
69
70 impl std::fmt::Display for Display<'_> {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 match self.token {
73 PostToken::Space => write!(f, "{SPACE}"),
74 PostToken::Newline => write!(f, "{NEWLINE}"),
75 PostToken::Indent => {
76 write!(f, "{indent}", indent = self.config.indent().string())
77 }
78 PostToken::TempIndent(value) => write!(f, "{value}"),
79 PostToken::Literal(value) => write!(f, "{value}"),
80 }
81 }
82 }
83
84 Display {
85 token: self,
86 config,
87 }
88 }
89}
90
91impl PostToken {
92 fn width(&self, config: &crate::Config) -> usize {
98 match self {
99 Self::Space => SPACE.len(), Self::Newline => 0,
101 Self::Indent => config.indent().num(),
102 Self::TempIndent(value) => value.len(),
103 Self::Literal(value) => value.len(),
104 }
105 }
106}
107
108impl TokenStream<PostToken> {
109 fn max_width(&self, config: &Config) -> usize {
113 let mut max: usize = 0;
114 let mut cur_width: usize = 0;
115 for token in self.iter() {
116 cur_width += token.width(config);
117 if token == &PostToken::Newline {
118 max = max.max(cur_width);
119 cur_width = 0;
120 }
121 }
122 max.max(cur_width)
123 }
124
125 fn last_line_width(&self, config: &Config) -> usize {
127 let mut width = 0;
128 for token in self.iter().rev() {
129 if token == &PostToken::Newline {
130 break;
131 }
132 width += token.width(config);
133 }
134 width
135 }
136}
137
138enum LineBreak {
140 Before,
142 After,
144}
145
146fn can_be_line_broken(kind: SyntaxKind) -> Option<LineBreak> {
148 match kind {
149 SyntaxKind::CloseBrace
150 | SyntaxKind::CloseBracket
151 | SyntaxKind::CloseParen
152 | SyntaxKind::CloseHeredoc
153 | SyntaxKind::Assignment
154 | SyntaxKind::Plus
155 | SyntaxKind::Minus
156 | SyntaxKind::Asterisk
157 | SyntaxKind::Slash
158 | SyntaxKind::Percent
159 | SyntaxKind::Exponentiation
160 | SyntaxKind::Equal
161 | SyntaxKind::NotEqual
162 | SyntaxKind::Less
163 | SyntaxKind::LessEqual
164 | SyntaxKind::Greater
165 | SyntaxKind::GreaterEqual
166 | SyntaxKind::LogicalAnd
167 | SyntaxKind::LogicalOr
168 | SyntaxKind::AfterKeyword
169 | SyntaxKind::AsKeyword
170 | SyntaxKind::IfKeyword
171 | SyntaxKind::ElseKeyword
172 | SyntaxKind::ThenKeyword => Some(LineBreak::Before),
173 SyntaxKind::OpenBrace
174 | SyntaxKind::OpenBracket
175 | SyntaxKind::OpenParen
176 | SyntaxKind::OpenHeredoc
177 | SyntaxKind::Colon
178 | SyntaxKind::PlaceholderOpen
179 | SyntaxKind::Comma => Some(LineBreak::After),
180 _ => None,
181 }
182}
183
184fn split_except_directive_lines(value: &str, max_len: usize) -> Option<Vec<String>> {
191 let remainder = value.strip_prefix("#@")?;
192 let rules_text = remainder.trim_start().strip_prefix("except:")?;
193
194 if value.len() <= max_len {
196 return None;
197 };
198
199 let rules: Vec<&str> = rules_text
201 .split(',')
202 .map(|s| s.trim())
203 .filter(|s| !s.is_empty())
204 .collect();
205
206 if rules.is_empty() {
207 return None;
208 }
209
210 let prefix = "#@ except: ";
211 let mut lines = Vec::new();
212 let mut current_rules = Vec::new();
213
214 for rule in rules {
215 let mut test_rules = current_rules.clone();
217 test_rules.push(rule);
218 let test_line = format!("{}{}", prefix, test_rules.join(", "));
219
220 if test_line.len() <= max_len {
221 current_rules.push(rule);
223 } else {
224 if current_rules.is_empty() {
226 current_rules.push(rule);
229 } else {
230 lines.push(format!("{}{}", prefix, current_rules.join(", ")));
232 current_rules.clear();
233 current_rules.push(rule);
234 }
235 }
236 }
237
238 if !current_rules.is_empty() {
240 lines.push(format!("{}{}", prefix, current_rules.join(", ")));
241 }
242
243 Some(lines)
244}
245
246#[derive(Default, Eq, PartialEq)]
248enum LinePosition {
249 #[default]
251 StartOfLine,
252
253 MiddleOfLine,
255}
256
257#[derive(Default)]
259pub struct Postprocessor {
260 position: LinePosition,
262
263 indent_level: usize,
265
266 interrupted: bool,
268
269 line_spacing_policy: TriviaBlankLineSpacingPolicy,
271
272 temp_indent_needed: bool,
274
275 temp_indent: Rc<String>,
277}
278
279impl Postprocessor {
280 pub fn run(&mut self, input: TokenStream<PreToken>, config: &Config) -> TokenStream<PostToken> {
282 let mut output = TokenStream::<PostToken>::default();
283 let mut buffer = TokenStream::<PreToken>::default();
284
285 for token in input {
286 match token {
287 PreToken::LineEnd => {
288 self.flush(&buffer, &mut output, config);
289 self.trim_whitespace(&mut output);
290 output.push(PostToken::Newline);
291
292 buffer.clear();
293 self.interrupted = false;
294 self.position = LinePosition::StartOfLine;
295 }
296 _ => {
297 buffer.push(token);
298 }
299 }
300 }
301
302 output
303 }
304
305 fn step(
308 &mut self,
309 token: PreToken,
310 next: Option<&PreToken>,
311 stream: &mut TokenStream<PostToken>,
312 ) {
313 if stream.is_empty() {
314 self.interrupted = false;
315 self.position = LinePosition::StartOfLine;
316 self.indent(stream);
317 }
318 match token {
319 PreToken::BlankLine => {
320 self.blank_line(stream);
321 }
322 PreToken::LineEnd => {
323 self.interrupted = false;
324 self.end_line(stream);
325 }
326 PreToken::WordEnd => {
327 stream.trim_end(&PostToken::Space);
328
329 if self.position == LinePosition::MiddleOfLine {
330 stream.push(PostToken::Space);
331 } else {
332 }
335 }
336 PreToken::IndentStart => {
337 self.indent_level += 1;
338 self.end_line(stream);
339 }
340 PreToken::IndentEnd => {
341 self.indent_level = self.indent_level.saturating_sub(1);
342 self.end_line(stream);
343 }
344 PreToken::LineSpacingPolicy(policy) => {
345 self.line_spacing_policy = policy;
346 }
347 PreToken::Literal(value, kind) => {
348 assert!(!kind.is_trivia());
349
350 if value.is_empty() {
357 self.trim_last_line(stream);
358 stream.push(PostToken::Literal(value));
359 self.position = LinePosition::MiddleOfLine;
360 return;
361 }
362
363 if self.interrupted
364 && matches!(
365 kind,
366 SyntaxKind::OpenBrace
367 | SyntaxKind::OpenBracket
368 | SyntaxKind::OpenParen
369 | SyntaxKind::OpenHeredoc
370 )
371 && matches!(
372 stream.0.last(),
373 Some(&PostToken::Indent) | Some(&PostToken::TempIndent(_))
374 )
375 {
376 stream.0.pop();
377 }
378
379 if kind == SyntaxKind::LiteralCommandText {
380 self.temp_indent = Rc::new(
381 value
382 .chars()
383 .take_while(|c| matches!(c.to_string().as_str(), SPACE | crate::TAB))
384 .collect(),
385 );
386 }
387
388 stream.push(PostToken::Literal(value));
389 self.position = LinePosition::MiddleOfLine;
390 }
391 PreToken::Trivia(trivia) => match trivia {
392 Trivia::BlankLine => match self.line_spacing_policy {
393 TriviaBlankLineSpacingPolicy::Always => {
394 self.blank_line(stream);
395 }
396 TriviaBlankLineSpacingPolicy::RemoveTrailingBlanks => {
397 if matches!(next, Some(&PreToken::Trivia(Trivia::Comment(_)))) {
398 self.blank_line(stream);
399 }
400 }
401 },
402 Trivia::Comment(comment) => {
403 match comment {
404 Comment::Preceding(value) => {
405 if !matches!(
406 stream.0.last(),
407 Some(&PostToken::Newline)
408 | Some(&PostToken::Indent)
409 | Some(&PostToken::TempIndent(_))
410 | None
411 ) {
412 self.interrupted = true;
413 }
414 self.end_line(stream);
415 stream.push(PostToken::Literal(value));
416 self.position = LinePosition::MiddleOfLine;
417 }
418 Comment::Inline(value) => {
419 assert!(self.position == LinePosition::MiddleOfLine);
420 if let Some(next) = next
421 && next != &PreToken::LineEnd
422 {
423 self.interrupted = true;
424 }
425 self.trim_last_line(stream);
426 for token in INLINE_COMMENT_PRECEDING_TOKENS.iter() {
427 stream.push(token.clone());
428 }
429 stream.push(PostToken::Literal(value));
430 }
431 }
432 self.end_line(stream);
433 }
434 },
435 PreToken::TempIndentStart => {
436 self.temp_indent_needed = true;
437 }
438 PreToken::TempIndentEnd => {
439 self.temp_indent_needed = false;
440 }
441 }
442 }
443
444 fn flush(
446 &mut self,
447 in_stream: &TokenStream<PreToken>,
448 out_stream: &mut TokenStream<PostToken>,
449 config: &Config,
450 ) {
451 assert!(!self.interrupted);
452 assert!(self.position == LinePosition::StartOfLine);
453 let mut expanded_stream = TokenStream::<PreToken>::default();
455 let in_stream = if let Some(max_len) = config.max_line_length() {
456 for token in in_stream.iter() {
457 let PreToken::Trivia(Trivia::Comment(Comment::Preceding(value))) = token else {
458 expanded_stream.push(token.clone());
459 continue;
460 };
461
462 if !value.starts_with("#@") {
463 expanded_stream.push(token.clone());
464 continue;
465 }
466
467 if let Some(lines) = split_except_directive_lines(value, max_len) {
468 for line in lines {
469 expanded_stream.push(PreToken::Trivia(Trivia::Comment(
470 Comment::Preceding(Rc::new(line)),
471 )));
472 }
473 } else {
474 expanded_stream.push(token.clone());
475 }
476 }
477 &expanded_stream
478 } else {
479 in_stream
481 };
482 let mut post_buffer = TokenStream::<PostToken>::default();
483 let mut pre_buffer = in_stream.iter().peekable();
484 let starting_indent = self.indent_level;
485 while let Some(token) = pre_buffer.next() {
486 let next = pre_buffer.peek().copied();
487 self.step(token.clone(), next, &mut post_buffer);
488 }
489
490 if config.max_line_length().is_none()
493 || post_buffer.max_width(config) <= config.max_line_length().unwrap()
494 {
495 out_stream.extend(post_buffer);
496 return;
497 }
498
499 let max_length = config.max_line_length().unwrap();
505
506 let mut potential_line_breaks: HashSet<usize> = HashSet::new();
507 for (i, token) in in_stream.iter().enumerate() {
508 if let PreToken::Literal(_, kind) = token {
509 match can_be_line_broken(*kind) {
510 Some(LineBreak::Before) => {
511 potential_line_breaks.insert(i);
512 }
513 Some(LineBreak::After) => {
514 potential_line_breaks.insert(i + 1);
515 }
516 None => {}
517 }
518 }
519 }
520
521 if potential_line_breaks.is_empty() {
522 out_stream.extend(post_buffer);
524 return;
525 }
526
527 post_buffer.clear();
529 let mut pre_buffer = in_stream.iter().enumerate().peekable();
530
531 self.indent_level = starting_indent;
533
534 while let Some((i, token)) = pre_buffer.next() {
535 let mut cache = None;
536 if potential_line_breaks.contains(&i) {
537 if post_buffer.last_line_width(config) > max_length {
538 self.interrupted = true;
541 self.end_line(&mut post_buffer);
542 } else {
543 cache = Some(post_buffer.clone());
547 }
548 }
549 self.step(
550 token.clone(),
551 pre_buffer.peek().map(|(_, v)| &**v),
552 &mut post_buffer,
553 );
554
555 if let Some(cache) = cache
556 && post_buffer.last_line_width(config) > max_length
557 {
558 post_buffer = cache;
561 self.interrupted = true;
562 self.end_line(&mut post_buffer);
563 self.step(
564 token.clone(),
565 pre_buffer.peek().map(|(_, v)| &**v),
566 &mut post_buffer,
567 );
568 }
569 }
570
571 out_stream.extend(post_buffer);
572 }
573
574 fn trim_whitespace(&self, stream: &mut TokenStream<PostToken>) {
576 stream.trim_while(|token| {
577 matches!(
578 token,
579 PostToken::Space
580 | PostToken::Newline
581 | PostToken::Indent
582 | PostToken::TempIndent(_)
583 )
584 });
585 }
586
587 fn trim_last_line(&mut self, stream: &mut TokenStream<PostToken>) {
589 stream.trim_while(|token| {
590 matches!(
591 token,
592 PostToken::Space | PostToken::Indent | PostToken::TempIndent(_)
593 )
594 });
595 }
596
597 fn end_line(&mut self, stream: &mut TokenStream<PostToken>) {
604 self.trim_last_line(stream);
605 if self.position != LinePosition::StartOfLine {
606 stream.push(PostToken::Newline);
607 }
608 self.position = LinePosition::StartOfLine;
609 self.indent(stream);
610 }
611
612 fn indent(&self, stream: &mut TokenStream<PostToken>) {
616 assert!(self.position == LinePosition::StartOfLine);
617
618 let level = if self.interrupted {
619 self.indent_level + 1
620 } else {
621 self.indent_level
622 };
623
624 for _ in 0..level {
625 stream.push(PostToken::Indent);
626 }
627
628 if self.temp_indent_needed {
629 stream.push(PostToken::TempIndent(self.temp_indent.clone()));
630 }
631 }
632
633 fn blank_line(&mut self, stream: &mut TokenStream<PostToken>) {
635 self.trim_whitespace(stream);
636 if !stream.is_empty() {
637 stream.push(PostToken::Newline);
638 }
639 stream.push(PostToken::Newline);
640 self.position = LinePosition::StartOfLine;
641 self.indent(stream);
642 }
643}