1use std::collections::HashMap;
7use std::fmt::Display;
8use std::rc::Rc;
9
10use wdl_ast::DIRECTIVE_COMMENT_PREFIX;
11use wdl_ast::DIRECTIVE_DELIMITER;
12use wdl_ast::DOC_COMMENT_PREFIX;
13use wdl_ast::Directive;
14use wdl_ast::SyntaxKind;
15
16use crate::Comment;
17use crate::Config;
18use crate::Indent;
19use crate::PreToken;
20use crate::SPACE;
21use crate::Token;
22use crate::TokenStream;
23use crate::Trivia;
24use crate::TriviaBlankLineSpacingPolicy;
25
26const INLINE_COMMENT_PRECEDING_TOKENS: [PostToken; 2] = [PostToken::Space, PostToken::Space];
28
29#[derive(Clone, Eq, PartialEq)]
31pub enum PostToken {
32 Space,
34
35 Newline,
37
38 Indent,
40
41 TempIndent(Rc<String>),
46
47 Literal(Rc<String>),
49
50 Documentation {
52 num_indents: usize,
54 contents: Rc<String>,
56 },
57
58 Directive {
60 num_indents: usize,
62 directive: Rc<Directive>,
64 },
65}
66
67impl std::fmt::Debug for PostToken {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 match self {
70 Self::Space => write!(f, "<SPACE>"),
71 Self::Newline => write!(f, "<NEWLINE>"),
72 Self::Indent => write!(f, "<INDENT>"),
73 Self::TempIndent(value) => write!(f, "<TEMP_INDENT@{value}>"),
74 Self::Literal(value) => write!(f, "<LITERAL@{value}>"),
75 Self::Directive { directive, .. } => write!(f, "<DIRECTIVE@{directive:?}>"),
76 Self::Documentation { contents, .. } => write!(f, "<DOCUMENTATION@{contents}>"),
77 }
78 }
79}
80
81impl Token for PostToken {
82 fn display<'a>(&'a self, config: &'a Config) -> impl Display + 'a {
84 struct Display<'a> {
86 token: &'a PostToken,
88 config: &'a Config,
90 }
91
92 fn write_indents(
93 f: &mut std::fmt::Formatter<'_>,
94 indent: &Indent,
95 num_indents: usize,
96 ) -> std::fmt::Result {
97 for _ in 0usize..num_indents {
98 write!(f, "{indent}")?;
99 }
100 Ok(())
101 }
102
103 impl std::fmt::Display for Display<'_> {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 match self.token {
106 PostToken::Space => write!(f, "{SPACE}"),
107 PostToken::Newline => write!(f, "{}", self.config.newline_style.as_str()),
108 PostToken::Indent => {
109 write!(f, "{indent}", indent = self.config.indent)
110 }
111 PostToken::TempIndent(value) => write!(f, "{value}"),
112 PostToken::Literal(value) => write!(f, "{value}"),
113 PostToken::Documentation {
114 num_indents,
115 contents: markdown,
116 } => {
117 let prefix = DOC_COMMENT_PREFIX;
118 write!(f, "{prefix}")?;
119 let mut lines = markdown.lines().peekable();
120 while let Some(cur) = lines.next() {
121 write!(f, "{cur}")?;
122 if lines.peek().is_some() {
123 write!(f, "{}", self.config.newline_style.as_str())?;
124 write_indents(f, &self.config.indent, *num_indents)?;
125 write!(f, "{prefix}")?;
126 }
127 }
128 Ok(())
129 }
130 PostToken::Directive {
131 num_indents,
132 directive,
133 } => {
134 let mut prefix = format!("{} ", DIRECTIVE_COMMENT_PREFIX);
135 match &**directive {
136 Directive::Except(exceptions) => {
137 prefix.push_str("except");
138 prefix.push_str(DIRECTIVE_DELIMITER);
139 prefix.push(' ');
140 let mut rules: Vec<String> = exceptions.iter().cloned().collect();
141 rules.sort();
142 write!(f, "{prefix}")?;
143 if let Some(max) = self.config.max_line_length.get() {
144 let indent_width = self.config.indent.num() * num_indents;
145 let start_width = indent_width + prefix.len();
146 let mut remaining = max.saturating_sub(start_width);
147 let mut written_to_cur_line = 0usize;
148 for rule in rules {
149 let cur_len = rule.len();
150 if written_to_cur_line == 0 {
151 write!(f, "{rule}")?;
152 remaining = remaining.saturating_sub(cur_len);
153 written_to_cur_line += 1;
154 } else if remaining.saturating_sub(cur_len + 2) > 0 {
155 write!(f, ", {rule}")?;
159 remaining = remaining.saturating_sub(cur_len + 2);
160 written_to_cur_line += 1;
161 } else {
162 write!(f, "{}", self.config.newline_style.as_str())?;
164 write_indents(f, &self.config.indent, *num_indents)?;
165 write!(f, "{prefix}{rule}")?;
166 written_to_cur_line = 1;
167 remaining = max.saturating_sub(start_width + cur_len);
168 }
169 }
170 Ok(())
171 } else {
172 write!(f, "{rules}", rules = rules.join(", "))
173 }
174 }
175 }
176 }
177 }
178 }
179 }
180
181 Display {
182 token: self,
183 config,
184 }
185 }
186}
187
188impl PostToken {
189 fn width(&self, config: &crate::Config) -> usize {
197 match self {
198 Self::Space => SPACE.len(), Self::Newline => 0,
200 Self::Indent => config.indent.num(),
201 Self::TempIndent(value) => value.len(),
202 Self::Literal(value) => value.len(),
203 Self::Directive { .. } => 0,
204 Self::Documentation { .. } => 0,
205 }
206 }
207}
208
209impl TokenStream<PostToken> {
210 fn max_width(&self, config: &Config) -> usize {
214 let mut max: usize = 0;
215 let mut cur_width: usize = 0;
216 for token in self.iter() {
217 cur_width += token.width(config);
218 if token == &PostToken::Newline {
219 max = max.max(cur_width);
220 cur_width = 0;
221 }
222 }
223 max.max(cur_width)
224 }
225
226 fn last_line_width(&self, config: &Config) -> usize {
228 let mut width = 0;
229 for token in self.iter().rev() {
230 if token == &PostToken::Newline {
231 break;
232 }
233 width += token.width(config);
234 }
235 width
236 }
237}
238
239enum LineBreak {
241 Before,
243 After,
245}
246
247fn can_be_line_broken(kind: SyntaxKind) -> Option<LineBreak> {
249 match kind {
250 SyntaxKind::CloseBrace
251 | SyntaxKind::CloseBracket
252 | SyntaxKind::CloseParen
253 | SyntaxKind::CloseHeredoc
254 | SyntaxKind::Assignment
255 | SyntaxKind::Plus
256 | SyntaxKind::Minus
257 | SyntaxKind::Asterisk
258 | SyntaxKind::Slash
259 | SyntaxKind::Percent
260 | SyntaxKind::Exponentiation
261 | SyntaxKind::Equal
262 | SyntaxKind::NotEqual
263 | SyntaxKind::Less
264 | SyntaxKind::LessEqual
265 | SyntaxKind::Greater
266 | SyntaxKind::GreaterEqual
267 | SyntaxKind::LogicalAnd
268 | SyntaxKind::LogicalOr
269 | SyntaxKind::AfterKeyword
270 | SyntaxKind::AsKeyword
271 | SyntaxKind::IfKeyword
272 | SyntaxKind::ElseKeyword
273 | SyntaxKind::ThenKeyword => Some(LineBreak::Before),
274 SyntaxKind::OpenBrace
275 | SyntaxKind::OpenBracket
276 | SyntaxKind::OpenParen
277 | SyntaxKind::OpenHeredoc
278 | SyntaxKind::Colon
279 | SyntaxKind::PlaceholderOpen
280 | SyntaxKind::Comma => Some(LineBreak::After),
281 _ => None,
282 }
283}
284
285fn tandem_line_break(kind: SyntaxKind) -> Option<SyntaxKind> {
288 match kind {
289 SyntaxKind::OpenBrace => Some(SyntaxKind::CloseBrace),
290 SyntaxKind::OpenBracket => Some(SyntaxKind::CloseBracket),
291 SyntaxKind::OpenParen => Some(SyntaxKind::CloseParen),
292 SyntaxKind::OpenHeredoc => Some(SyntaxKind::CloseHeredoc),
293 SyntaxKind::PlaceholderOpen => Some(SyntaxKind::CloseBrace),
294 _ => None,
295 }
296}
297
298fn should_deindent(kind: SyntaxKind) -> bool {
301 matches!(
302 kind,
303 SyntaxKind::OpenBrace
304 | SyntaxKind::OpenBracket
305 | SyntaxKind::OpenParen
306 | SyntaxKind::OpenHeredoc
307 | SyntaxKind::CloseBrace
308 | SyntaxKind::CloseBracket
309 | SyntaxKind::CloseParen
310 | SyntaxKind::CloseHeredoc
311 )
312}
313
314struct TandemBreak {
316 pub open: SyntaxKind,
318 pub close: SyntaxKind,
320 pub depth: usize,
327}
328
329#[derive(Default, Eq, PartialEq)]
331enum LinePosition {
332 #[default]
334 StartOfLine,
335
336 MiddleOfLine,
338}
339
340#[derive(Default)]
342pub struct Postprocessor {
343 position: LinePosition,
345
346 indent_level: usize,
348
349 interrupted: bool,
351
352 line_spacing_policy: TriviaBlankLineSpacingPolicy,
354
355 temp_indent: Option<Rc<String>>,
357}
358
359impl Postprocessor {
360 pub fn run(&mut self, input: TokenStream<PreToken>, config: &Config) -> TokenStream<PostToken> {
362 let mut output = TokenStream::<PostToken>::default();
363 let mut buffer = TokenStream::<PreToken>::default();
364
365 for token in input {
366 match token {
367 PreToken::LineEnd => {
368 self.flush(&buffer, &mut output, config);
369 self.trim_whitespace(&mut output);
370 output.push(PostToken::Newline);
371
372 buffer.clear();
373 self.interrupted = false;
374 self.position = LinePosition::StartOfLine;
375 }
376 _ => {
377 buffer.push(token);
378 }
379 }
380 }
381
382 output
383 }
384
385 fn step(
388 &mut self,
389 token: PreToken,
390 next: Option<&PreToken>,
391 stream: &mut TokenStream<PostToken>,
392 ) {
393 if stream.is_empty() {
394 self.interrupted = false;
395 self.position = LinePosition::StartOfLine;
396 self.indent(stream);
397 }
398 match token {
399 PreToken::BlankLine => {
400 self.blank_line(stream);
401 }
402 PreToken::LineEnd => {
403 self.interrupted = false;
404 self.end_line(stream);
405 }
406 PreToken::WordEnd => {
407 stream.trim_end(&PostToken::Space);
408
409 if self.position == LinePosition::MiddleOfLine {
410 stream.push(PostToken::Space);
411 } else {
412 }
415 }
416 PreToken::IndentStart => {
417 self.indent_level += 1;
418 self.end_line(stream);
419 }
420 PreToken::IndentEnd => {
421 self.indent_level = self.indent_level.saturating_sub(1);
422 self.end_line(stream);
423 }
424 PreToken::LineSpacingPolicy(policy) => {
425 self.line_spacing_policy = policy;
426 }
427 PreToken::Literal(value, kind) => {
428 assert!(!kind.is_trivia());
429
430 if value.is_empty() {
434 self.trim_last_line(stream);
435 }
436
437 if self.interrupted
438 && should_deindent(kind)
439 && matches!(
440 stream.0.last(),
441 Some(&PostToken::Indent) | Some(&PostToken::TempIndent(_))
442 )
443 {
444 let popped = stream.0.pop().unwrap();
445 if matches!(popped, PostToken::TempIndent(_)) {
448 stream.0.pop_if(|t| matches!(t, PostToken::Indent));
449 stream.0.push(popped);
451 }
452 }
453
454 stream.push(PostToken::Literal(value));
455 self.position = LinePosition::MiddleOfLine;
456 }
457 PreToken::Trivia(trivia) => match trivia {
458 Trivia::BlankLine => match self.line_spacing_policy {
459 TriviaBlankLineSpacingPolicy::Always => {
460 self.blank_line(stream);
461 }
462 TriviaBlankLineSpacingPolicy::RemoveTrailingBlanks => {
463 if matches!(next, Some(&PreToken::Trivia(Trivia::Comment(_)))) {
464 self.blank_line(stream);
465 }
466 }
467 },
468 Trivia::Comment(comment) => {
469 match comment {
470 Comment::Preceding(value) => {
471 if self.position == LinePosition::MiddleOfLine {
472 self.interrupted = true;
473 self.end_line(stream);
474 }
475 stream.push(PostToken::Literal(value));
476 }
477 Comment::Inline(value) => {
478 assert!(self.position == LinePosition::MiddleOfLine);
479 if let Some(next) = next
480 && next != &PreToken::LineEnd
481 {
482 self.interrupted = true;
483 }
484 self.trim_last_line(stream);
485 for token in INLINE_COMMENT_PRECEDING_TOKENS.iter() {
486 stream.push(token.clone());
487 }
488 stream.push(PostToken::Literal(value));
489 }
490 Comment::Documentation(contents) => {
491 if self.position == LinePosition::MiddleOfLine {
492 self.interrupted = true;
493 self.end_line(stream);
494 }
495 stream.push(PostToken::Documentation {
496 num_indents: self.indent_level,
497 contents,
498 });
499 }
500 Comment::Directive(directive) => {
501 if self.position == LinePosition::MiddleOfLine {
502 self.interrupted = true;
503 self.end_line(stream);
504 }
505 stream.push(PostToken::Directive {
506 num_indents: self.indent_level,
507 directive,
508 });
509 }
510 }
511 self.position = LinePosition::MiddleOfLine;
512 self.end_line(stream);
513 }
514 },
515 PreToken::TempIndentStart(bash_indent) => {
516 self.temp_indent = Some(bash_indent);
517 }
518 PreToken::TempIndentEnd => {
519 self.temp_indent = None;
520 }
521 }
522 }
523
524 fn flush(
526 &mut self,
527 in_stream: &TokenStream<PreToken>,
528 out_stream: &mut TokenStream<PostToken>,
529 config: &Config,
530 ) {
531 assert!(!self.interrupted);
532 assert!(self.position == LinePosition::StartOfLine);
533 let mut post_buffer = TokenStream::<PostToken>::default();
534 let mut pre_buffer = in_stream.iter().peekable();
535 let starting_indent = self.indent_level;
536 let starting_temp_indent = self.temp_indent.clone();
537 while let Some(token) = pre_buffer.next() {
538 let next = pre_buffer.peek().copied();
539 self.step(token.clone(), next, &mut post_buffer);
540 }
541
542 if config.max_line_length.get().is_none()
545 || post_buffer.max_width(config) <= config.max_line_length.get().unwrap()
546 {
547 out_stream.extend(post_buffer);
548 return;
549 }
550
551 let max_length = config.max_line_length.get().unwrap();
557
558 let mut potential_line_breaks: HashMap<usize, SyntaxKind> = HashMap::new();
559 for (i, token) in in_stream.iter().enumerate() {
560 if let PreToken::Literal(_, kind) = token {
561 match can_be_line_broken(*kind) {
562 Some(LineBreak::Before) => {
563 potential_line_breaks.insert(i, *kind);
564 }
565 Some(LineBreak::After) => {
566 potential_line_breaks.insert(i + 1, *kind);
567 }
568 None => {}
569 }
570 }
571 }
572
573 if potential_line_breaks.is_empty() {
574 out_stream.extend(post_buffer);
576 return;
577 }
578
579 post_buffer.clear();
581 let mut pre_buffer = in_stream.iter().enumerate().peekable();
582
583 self.interrupted = false;
585 self.position = LinePosition::StartOfLine;
586 self.temp_indent = starting_temp_indent;
587 self.indent_level = starting_indent;
588
589 let mut break_stack: Vec<TandemBreak> = Vec::new();
590
591 while let Some((i, token)) = pre_buffer.next() {
592 let mut cache = None;
593 if let Some(break_kind) = potential_line_breaks.get(&i) {
594 if let Some(top_of_stack) = break_stack.last_mut() {
596 if *break_kind == top_of_stack.close {
597 if top_of_stack.depth > 0 {
598 top_of_stack.depth -= 1;
599 } else {
600 break_stack.pop();
601 self.indent_level -= 1;
602 self.end_line(&mut post_buffer);
603 }
604 } else if *break_kind == top_of_stack.open {
605 top_of_stack.depth += 1;
606 }
607 }
608 cache = Some(post_buffer.clone());
611 }
612
613 self.step(
614 token.clone(),
615 pre_buffer.peek().map(|(_, v)| &**v),
616 &mut post_buffer,
617 );
618
619 if let Some(cache) = cache
620 && post_buffer.last_line_width(config) > max_length
621 {
622 post_buffer = cache;
625 self.interrupted = true;
626 self.end_line(&mut post_buffer);
627 self.step(
628 token.clone(),
629 pre_buffer.peek().map(|(_, v)| &**v),
630 &mut post_buffer,
631 );
632
633 let break_kind = potential_line_breaks.get(&i).unwrap();
636 if let Some(also_break_on) = tandem_line_break(*break_kind) {
637 let tandem_break = TandemBreak {
638 open: *break_kind,
639 close: also_break_on,
640 depth: 0,
641 };
642 break_stack.push(tandem_break);
643 self.indent_level += 1;
644 }
645 }
646 }
647
648 for _ in break_stack {
650 self.indent_level = self.indent_level.saturating_sub(1);
651 }
652 out_stream.extend(post_buffer);
653 }
654
655 fn trim_whitespace(&self, stream: &mut TokenStream<PostToken>) {
657 stream.trim_while(|token| {
658 matches!(
659 token,
660 PostToken::Space
661 | PostToken::Newline
662 | PostToken::Indent
663 | PostToken::TempIndent(_)
664 )
665 });
666 }
667
668 fn trim_last_line(&self, stream: &mut TokenStream<PostToken>) {
670 stream.trim_while(|token| {
671 matches!(
672 token,
673 PostToken::Space | PostToken::Indent | PostToken::TempIndent(_)
674 )
675 });
676 }
677
678 fn end_line(&mut self, stream: &mut TokenStream<PostToken>) {
685 self.trim_last_line(stream);
686 if self.position != LinePosition::StartOfLine {
687 stream.push(PostToken::Newline);
688 }
689 self.position = LinePosition::StartOfLine;
690 self.indent(stream);
691 }
692
693 fn indent(&self, stream: &mut TokenStream<PostToken>) {
699 assert!(self.position == LinePosition::StartOfLine);
700
701 self.trim_last_line(stream);
702
703 let level = if self.interrupted {
704 self.indent_level + 1
705 } else {
706 self.indent_level
707 };
708
709 for _ in 0..level {
710 stream.push(PostToken::Indent);
711 }
712
713 if let Some(ref temp_indent) = self.temp_indent {
714 stream.push(PostToken::TempIndent(temp_indent.clone()));
715 }
716 }
717
718 fn blank_line(&mut self, stream: &mut TokenStream<PostToken>) {
720 self.trim_whitespace(stream);
721 if !stream.is_empty() {
722 stream.push(PostToken::Newline);
723 }
724 stream.push(PostToken::Newline);
725 self.position = LinePosition::StartOfLine;
726 self.indent(stream);
727 }
728}