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> =
141 exceptions.iter().cloned().map(|e| e.name).collect();
142 rules.sort();
143 write!(f, "{prefix}")?;
144 if let Some(max) = self.config.max_line_length.get() {
145 let indent_width = self.config.indent.num() * num_indents;
146 let start_width = indent_width + prefix.len();
147 let mut remaining = max.saturating_sub(start_width);
148 let mut written_to_cur_line = 0usize;
149 for rule in rules {
150 let cur_len = rule.len();
151 if written_to_cur_line == 0 {
152 write!(f, "{rule}")?;
153 remaining = remaining.saturating_sub(cur_len);
154 written_to_cur_line += 1;
155 } else if remaining.saturating_sub(cur_len + 2) > 0 {
156 write!(f, ", {rule}")?;
160 remaining = remaining.saturating_sub(cur_len + 2);
161 written_to_cur_line += 1;
162 } else {
163 write!(f, "{}", self.config.newline_style.as_str())?;
165 write_indents(f, &self.config.indent, *num_indents)?;
166 write!(f, "{prefix}{rule}")?;
167 written_to_cur_line = 1;
168 remaining = max.saturating_sub(start_width + cur_len);
169 }
170 }
171 Ok(())
172 } else {
173 write!(f, "{rules}", rules = rules.join(", "))
174 }
175 }
176 }
177 }
178 }
179 }
180 }
181
182 Display {
183 token: self,
184 config,
185 }
186 }
187}
188
189impl PostToken {
190 fn width(&self, config: &crate::Config) -> usize {
198 match self {
199 Self::Space => SPACE.len(), Self::Newline => 0,
201 Self::Indent => config.indent.num(),
202 Self::TempIndent(value) => value.len(),
203 Self::Literal(value) => value.len(),
204 Self::Directive { .. } => 0,
205 Self::Documentation { .. } => 0,
206 }
207 }
208}
209
210impl TokenStream<PostToken> {
211 fn max_width(&self, config: &Config) -> usize {
215 let mut max: usize = 0;
216 let mut cur_width: usize = 0;
217 for token in self.iter() {
218 cur_width += token.width(config);
219 if token == &PostToken::Newline {
220 max = max.max(cur_width);
221 cur_width = 0;
222 }
223 }
224 max.max(cur_width)
225 }
226
227 fn last_line_width(&self, config: &Config) -> usize {
229 let mut width = 0;
230 for token in self.iter().rev() {
231 if token == &PostToken::Newline {
232 break;
233 }
234 width += token.width(config);
235 }
236 width
237 }
238}
239
240enum LineBreak {
242 Before,
244 After,
246}
247
248fn can_be_line_broken(kind: SyntaxKind) -> Option<LineBreak> {
250 match kind {
251 SyntaxKind::CloseBrace
252 | SyntaxKind::CloseBracket
253 | SyntaxKind::CloseParen
254 | SyntaxKind::CloseHeredoc
255 | SyntaxKind::Assignment
256 | SyntaxKind::Plus
257 | SyntaxKind::Minus
258 | SyntaxKind::Asterisk
259 | SyntaxKind::Slash
260 | SyntaxKind::Percent
261 | SyntaxKind::Exponentiation
262 | SyntaxKind::Equal
263 | SyntaxKind::NotEqual
264 | SyntaxKind::Less
265 | SyntaxKind::LessEqual
266 | SyntaxKind::Greater
267 | SyntaxKind::GreaterEqual
268 | SyntaxKind::LogicalAnd
269 | SyntaxKind::LogicalOr
270 | SyntaxKind::AfterKeyword
271 | SyntaxKind::AsKeyword
272 | SyntaxKind::IfKeyword
273 | SyntaxKind::ElseKeyword
274 | SyntaxKind::ThenKeyword => Some(LineBreak::Before),
275 SyntaxKind::OpenBrace
276 | SyntaxKind::OpenBracket
277 | SyntaxKind::OpenParen
278 | SyntaxKind::OpenHeredoc
279 | SyntaxKind::Colon
280 | SyntaxKind::PlaceholderOpen
281 | SyntaxKind::Comma => Some(LineBreak::After),
282 _ => None,
283 }
284}
285
286fn tandem_line_break(kind: SyntaxKind) -> Option<SyntaxKind> {
289 match kind {
290 SyntaxKind::OpenBrace => Some(SyntaxKind::CloseBrace),
291 SyntaxKind::OpenBracket => Some(SyntaxKind::CloseBracket),
292 SyntaxKind::OpenParen => Some(SyntaxKind::CloseParen),
293 SyntaxKind::OpenHeredoc => Some(SyntaxKind::CloseHeredoc),
294 SyntaxKind::PlaceholderOpen => Some(SyntaxKind::CloseBrace),
295 _ => None,
296 }
297}
298
299fn should_deindent(kind: SyntaxKind) -> bool {
302 matches!(
303 kind,
304 SyntaxKind::OpenBrace
305 | SyntaxKind::OpenBracket
306 | SyntaxKind::OpenParen
307 | SyntaxKind::OpenHeredoc
308 | SyntaxKind::CloseBrace
309 | SyntaxKind::CloseBracket
310 | SyntaxKind::CloseParen
311 | SyntaxKind::CloseHeredoc
312 )
313}
314
315struct TandemBreak {
317 pub open: SyntaxKind,
319 pub close: SyntaxKind,
321 pub depth: usize,
328}
329
330#[derive(Default, Eq, PartialEq)]
332enum LinePosition {
333 #[default]
335 StartOfLine,
336
337 MiddleOfLine,
339}
340
341#[derive(Default)]
343pub struct Postprocessor {
344 position: LinePosition,
346
347 indent_level: usize,
349
350 interrupted: bool,
352
353 line_spacing_policy: TriviaBlankLineSpacingPolicy,
355
356 temp_indent: Option<Rc<String>>,
358}
359
360impl Postprocessor {
361 pub fn run(&mut self, input: TokenStream<PreToken>, config: &Config) -> TokenStream<PostToken> {
363 let mut output = TokenStream::<PostToken>::default();
364 let mut buffer = TokenStream::<PreToken>::default();
365
366 for token in input {
367 match token {
368 PreToken::LineEnd => {
369 self.flush(&buffer, &mut output, config);
370 self.trim_whitespace(&mut output);
371 output.push(PostToken::Newline);
372
373 buffer.clear();
374 self.interrupted = false;
375 self.position = LinePosition::StartOfLine;
376 }
377 _ => {
378 buffer.push(token);
379 }
380 }
381 }
382
383 output
384 }
385
386 fn step(
389 &mut self,
390 token: PreToken,
391 next: Option<&PreToken>,
392 stream: &mut TokenStream<PostToken>,
393 ) {
394 if stream.is_empty() {
395 self.interrupted = false;
396 self.position = LinePosition::StartOfLine;
397 self.indent(stream);
398 }
399 match token {
400 PreToken::BlankLine => {
401 self.blank_line(stream);
402 }
403 PreToken::LineEnd => {
404 self.interrupted = false;
405 self.end_line(stream);
406 }
407 PreToken::WordEnd => {
408 stream.trim_end(&PostToken::Space);
409
410 if self.position == LinePosition::MiddleOfLine {
411 stream.push(PostToken::Space);
412 } else {
413 }
416 }
417 PreToken::IndentStart => {
418 self.indent_level += 1;
419 self.end_line(stream);
420 }
421 PreToken::IndentEnd => {
422 self.indent_level = self.indent_level.saturating_sub(1);
423 self.end_line(stream);
424 }
425 PreToken::LineSpacingPolicy(policy) => {
426 self.line_spacing_policy = policy;
427 }
428 PreToken::Literal(value, kind) => {
429 assert!(!kind.is_trivia());
430
431 if value.is_empty() {
435 self.trim_last_line(stream);
436 }
437
438 if self.interrupted
439 && should_deindent(kind)
440 && matches!(
441 stream.0.last(),
442 Some(&PostToken::Indent) | Some(&PostToken::TempIndent(_))
443 )
444 {
445 let popped = stream.0.pop().unwrap();
446 if matches!(popped, PostToken::TempIndent(_)) {
449 stream.0.pop_if(|t| matches!(t, PostToken::Indent));
450 stream.0.push(popped);
452 }
453 }
454
455 stream.push(PostToken::Literal(value));
456 self.position = LinePosition::MiddleOfLine;
457 }
458 PreToken::Trivia(trivia) => match trivia {
459 Trivia::BlankLine => match self.line_spacing_policy {
460 TriviaBlankLineSpacingPolicy::Always => {
461 self.blank_line(stream);
462 }
463 TriviaBlankLineSpacingPolicy::RemoveTrailingBlanks => {
464 if matches!(next, Some(&PreToken::Trivia(Trivia::Comment(_)))) {
465 self.blank_line(stream);
466 }
467 }
468 },
469 Trivia::Comment(comment) => {
470 match comment {
471 Comment::Preceding(value) => {
472 if self.position == LinePosition::MiddleOfLine {
473 self.interrupted = true;
474 self.end_line(stream);
475 }
476 stream.push(PostToken::Literal(value));
477 }
478 Comment::Inline(value) => {
479 assert!(self.position == LinePosition::MiddleOfLine);
480 if let Some(next) = next
481 && next != &PreToken::LineEnd
482 {
483 self.interrupted = true;
484 }
485 self.trim_last_line(stream);
486 for token in INLINE_COMMENT_PRECEDING_TOKENS.iter() {
487 stream.push(token.clone());
488 }
489 stream.push(PostToken::Literal(value));
490 }
491 Comment::Documentation(contents) => {
492 if self.position == LinePosition::MiddleOfLine {
493 self.interrupted = true;
494 self.end_line(stream);
495 }
496 stream.push(PostToken::Documentation {
497 num_indents: self.indent_level,
498 contents,
499 });
500 }
501 Comment::Directive(directive) => {
502 if self.position == LinePosition::MiddleOfLine {
503 self.interrupted = true;
504 self.end_line(stream);
505 }
506 stream.push(PostToken::Directive {
507 num_indents: self.indent_level,
508 directive,
509 });
510 }
511 }
512 self.position = LinePosition::MiddleOfLine;
513 self.end_line(stream);
514 }
515 },
516 PreToken::TempIndentStart(bash_indent) => {
517 self.temp_indent = Some(bash_indent);
518 }
519 PreToken::TempIndentEnd => {
520 self.temp_indent = None;
521 }
522 }
523 }
524
525 fn flush(
527 &mut self,
528 in_stream: &TokenStream<PreToken>,
529 out_stream: &mut TokenStream<PostToken>,
530 config: &Config,
531 ) {
532 assert!(!self.interrupted);
533 assert!(self.position == LinePosition::StartOfLine);
534 let mut post_buffer = TokenStream::<PostToken>::default();
535 let mut pre_buffer = in_stream.iter().peekable();
536 let starting_indent = self.indent_level;
537 let starting_temp_indent = self.temp_indent.clone();
538 while let Some(token) = pre_buffer.next() {
539 let next = pre_buffer.peek().copied();
540 self.step(token.clone(), next, &mut post_buffer);
541 }
542
543 if config.max_line_length.get().is_none()
546 || post_buffer.max_width(config) <= config.max_line_length.get().unwrap()
547 {
548 out_stream.extend(post_buffer);
549 return;
550 }
551
552 let max_length = config.max_line_length.get().unwrap();
558
559 let mut potential_line_breaks: HashMap<usize, SyntaxKind> = HashMap::new();
560 for (i, token) in in_stream.iter().enumerate() {
561 if let PreToken::Literal(_, kind) = token {
562 match can_be_line_broken(*kind) {
563 Some(LineBreak::Before) => {
564 potential_line_breaks.insert(i, *kind);
565 }
566 Some(LineBreak::After) => {
567 potential_line_breaks.insert(i + 1, *kind);
568 }
569 None => {}
570 }
571 }
572 }
573
574 if potential_line_breaks.is_empty() {
575 out_stream.extend(post_buffer);
577 return;
578 }
579
580 post_buffer.clear();
582 let mut pre_buffer = in_stream.iter().enumerate().peekable();
583
584 self.interrupted = false;
586 self.position = LinePosition::StartOfLine;
587 self.temp_indent = starting_temp_indent;
588 self.indent_level = starting_indent;
589
590 let mut break_stack: Vec<TandemBreak> = Vec::new();
591
592 while let Some((i, token)) = pre_buffer.next() {
593 let mut cache = None;
594 if let Some(break_kind) = potential_line_breaks.get(&i) {
595 if let Some(top_of_stack) = break_stack.last_mut() {
597 if *break_kind == top_of_stack.close {
598 if top_of_stack.depth > 0 {
599 top_of_stack.depth -= 1;
600 } else {
601 break_stack.pop();
602 self.indent_level -= 1;
603 self.end_line(&mut post_buffer);
604 }
605 } else if *break_kind == top_of_stack.open {
606 top_of_stack.depth += 1;
607 }
608 }
609 cache = Some(post_buffer.clone());
612 }
613
614 self.step(
615 token.clone(),
616 pre_buffer.peek().map(|(_, v)| &**v),
617 &mut post_buffer,
618 );
619
620 if let Some(cache) = cache
621 && post_buffer.last_line_width(config) > max_length
622 {
623 post_buffer = cache;
626 self.interrupted = true;
627 self.end_line(&mut post_buffer);
628 self.step(
629 token.clone(),
630 pre_buffer.peek().map(|(_, v)| &**v),
631 &mut post_buffer,
632 );
633
634 let break_kind = potential_line_breaks.get(&i).unwrap();
637 if let Some(also_break_on) = tandem_line_break(*break_kind) {
638 let tandem_break = TandemBreak {
639 open: *break_kind,
640 close: also_break_on,
641 depth: 0,
642 };
643 break_stack.push(tandem_break);
644 self.indent_level += 1;
645 }
646 }
647 }
648
649 for _ in break_stack {
651 self.indent_level = self.indent_level.saturating_sub(1);
652 }
653 out_stream.extend(post_buffer);
654 }
655
656 fn trim_whitespace(&self, stream: &mut TokenStream<PostToken>) {
658 stream.trim_while(|token| {
659 matches!(
660 token,
661 PostToken::Space
662 | PostToken::Newline
663 | PostToken::Indent
664 | PostToken::TempIndent(_)
665 )
666 });
667 }
668
669 fn trim_last_line(&self, stream: &mut TokenStream<PostToken>) {
671 stream.trim_while(|token| {
672 matches!(
673 token,
674 PostToken::Space | PostToken::Indent | PostToken::TempIndent(_)
675 )
676 });
677 }
678
679 fn end_line(&mut self, stream: &mut TokenStream<PostToken>) {
686 self.trim_last_line(stream);
687 if self.position != LinePosition::StartOfLine {
688 stream.push(PostToken::Newline);
689 }
690 self.position = LinePosition::StartOfLine;
691 self.indent(stream);
692 }
693
694 fn indent(&self, stream: &mut TokenStream<PostToken>) {
700 assert!(self.position == LinePosition::StartOfLine);
701
702 self.trim_last_line(stream);
703
704 let level = if self.interrupted {
705 self.indent_level + 1
706 } else {
707 self.indent_level
708 };
709
710 for _ in 0..level {
711 stream.push(PostToken::Indent);
712 }
713
714 if let Some(ref temp_indent) = self.temp_indent {
715 stream.push(PostToken::TempIndent(temp_indent.clone()));
716 }
717 }
718
719 fn blank_line(&mut self, stream: &mut TokenStream<PostToken>) {
721 self.trim_whitespace(stream);
722 if !stream.is_empty() {
723 stream.push(PostToken::Newline);
724 }
725 stream.push(PostToken::Newline);
726 self.position = LinePosition::StartOfLine;
727 self.indent(stream);
728 }
729}