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 allow_interruption(kind: SyntaxKind) -> bool {
304 matches!(
305 kind,
306 SyntaxKind::OpenBrace
307 | SyntaxKind::OpenBracket
308 | SyntaxKind::OpenParen
309 | SyntaxKind::OpenHeredoc
310 | SyntaxKind::CloseBrace
311 | SyntaxKind::CloseBracket
312 | SyntaxKind::CloseParen
313 | SyntaxKind::CloseHeredoc
314 | SyntaxKind::IfKeyword
315 | SyntaxKind::ElseKeyword
316 )
317}
318
319struct TandemBreak {
321 pub open: SyntaxKind,
323 pub close: SyntaxKind,
325 pub depth: usize,
332}
333
334#[derive(Default, Eq, PartialEq)]
336enum LinePosition {
337 #[default]
339 StartOfLine,
340
341 MiddleOfLine,
343}
344
345#[derive(Default)]
347pub struct Postprocessor {
348 position: LinePosition,
350
351 indent_level: usize,
353
354 interrupted: bool,
356
357 line_spacing_policy: TriviaBlankLineSpacingPolicy,
359
360 temp_indent: Option<Rc<String>>,
362}
363
364impl Postprocessor {
365 pub fn run(&mut self, input: TokenStream<PreToken>, config: &Config) -> TokenStream<PostToken> {
367 let mut output = TokenStream::<PostToken>::default();
368 let mut buffer = TokenStream::<PreToken>::default();
369
370 for token in input {
371 match token {
372 PreToken::LineEnd => {
373 self.flush(&buffer, &mut output, config);
374 self.trim_whitespace(&mut output);
375 output.push(PostToken::Newline);
376
377 buffer.clear();
378 self.interrupted = false;
379 self.position = LinePosition::StartOfLine;
380 }
381 _ => {
382 buffer.push(token);
383 }
384 }
385 }
386
387 output
388 }
389
390 fn step(
393 &mut self,
394 token: PreToken,
395 next: Option<&PreToken>,
396 stream: &mut TokenStream<PostToken>,
397 ) {
398 if stream.is_empty() {
399 self.interrupted = false;
400 self.position = LinePosition::StartOfLine;
401 self.indent(stream);
402 }
403 match token {
404 PreToken::BlankLine => {
405 self.blank_line(stream);
406 }
407 PreToken::LineEnd => {
408 self.interrupted = false;
409 self.end_line(stream);
410 }
411 PreToken::WordEnd => {
412 stream.trim_end(&PostToken::Space);
413
414 if self.position == LinePosition::MiddleOfLine {
415 stream.push(PostToken::Space);
416 } else {
417 }
420 }
421 PreToken::IndentStart => {
422 self.indent_level += 1;
423 self.end_line(stream);
424 }
425 PreToken::IndentEnd => {
426 self.indent_level = self.indent_level.saturating_sub(1);
427 self.end_line(stream);
428 }
429 PreToken::LineSpacingPolicy(policy) => {
430 self.line_spacing_policy = policy;
431 }
432 PreToken::Literal(value, kind) => {
433 assert!(!kind.is_trivia());
434
435 if value.is_empty() {
439 self.trim_last_line(stream);
440 }
441
442 if self.interrupted && allow_interruption(kind) {
443 self.pop_indent(stream);
444 }
445
446 stream.push(PostToken::Literal(value));
447 self.position = LinePosition::MiddleOfLine;
448 }
449 PreToken::Trivia(trivia) => match trivia {
450 Trivia::BlankLine => match self.line_spacing_policy {
451 TriviaBlankLineSpacingPolicy::Always => {
452 self.blank_line(stream);
453 }
454 TriviaBlankLineSpacingPolicy::RemoveTrailingBlanks => {
455 if matches!(next, Some(&PreToken::Trivia(Trivia::Comment(_)))) {
456 self.blank_line(stream);
457 }
458 }
459 },
460 Trivia::Comment(comment) => {
461 match comment {
462 Comment::Preceding(value) => {
463 if self.position == LinePosition::MiddleOfLine {
464 self.interrupted = true;
465 self.end_line(stream);
466 if let Some(PreToken::Literal(_, next_kind)) = next
467 && allow_interruption(*next_kind)
468 {
469 self.pop_indent(stream);
470 }
471 }
472 stream.push(PostToken::Literal(value));
473 }
474 Comment::Inline(value) => {
475 assert!(self.position == LinePosition::MiddleOfLine);
476 if let Some(next) = next
477 && next != &PreToken::LineEnd
478 {
479 self.interrupted = true;
480 }
481 self.trim_last_line(stream);
482 for token in INLINE_COMMENT_PRECEDING_TOKENS.iter() {
483 stream.push(token.clone());
484 }
485 stream.push(PostToken::Literal(value));
486 }
487 Comment::Documentation(contents) => {
488 if self.position == LinePosition::MiddleOfLine {
489 self.interrupted = true;
490 self.end_line(stream);
491 }
492 stream.push(PostToken::Documentation {
493 num_indents: self.indent_level,
494 contents,
495 });
496 }
497 Comment::Directive(directive) => {
498 if self.position == LinePosition::MiddleOfLine {
499 self.interrupted = true;
500 self.end_line(stream);
501 }
502 stream.push(PostToken::Directive {
503 num_indents: self.indent_level,
504 directive,
505 });
506 }
507 }
508 self.position = LinePosition::MiddleOfLine;
509 self.end_line(stream);
510 }
511 },
512 PreToken::TempIndentStart(bash_indent) => {
513 self.temp_indent = Some(bash_indent);
514 }
515 PreToken::TempIndentEnd => {
516 self.temp_indent = None;
517 }
518 }
519 }
520
521 fn flush(
523 &mut self,
524 in_stream: &TokenStream<PreToken>,
525 out_stream: &mut TokenStream<PostToken>,
526 config: &Config,
527 ) {
528 assert!(!self.interrupted);
529 assert!(self.position == LinePosition::StartOfLine);
530 let mut post_buffer = TokenStream::<PostToken>::default();
531 let mut pre_buffer = in_stream.iter().peekable();
532 let starting_indent = self.indent_level;
533 let starting_temp_indent = self.temp_indent.clone();
534 while let Some(token) = pre_buffer.next() {
535 let next = pre_buffer.peek().copied();
536 self.step(token.clone(), next, &mut post_buffer);
537 }
538
539 if config.max_line_length.get().is_none()
542 || post_buffer.max_width(config) <= config.max_line_length.get().unwrap()
543 {
544 out_stream.extend(post_buffer);
545 return;
546 }
547
548 let max_length = config.max_line_length.get().unwrap();
554
555 let mut potential_line_breaks: HashMap<usize, SyntaxKind> = HashMap::new();
556 for (i, token) in in_stream.iter().enumerate() {
557 if let PreToken::Literal(_, kind) = token {
558 match can_be_line_broken(*kind) {
559 Some(LineBreak::Before) => {
560 potential_line_breaks.insert(i, *kind);
561 }
562 Some(LineBreak::After) => {
563 potential_line_breaks.insert(i + 1, *kind);
564 }
565 None => {}
566 }
567 }
568 }
569
570 if potential_line_breaks.is_empty() {
571 out_stream.extend(post_buffer);
573 return;
574 }
575
576 post_buffer.clear();
578 let mut pre_buffer = in_stream.iter().enumerate().peekable();
579
580 self.interrupted = false;
582 self.position = LinePosition::StartOfLine;
583 self.temp_indent = starting_temp_indent;
584 self.indent_level = starting_indent;
585
586 let mut break_stack: Vec<TandemBreak> = Vec::new();
587
588 while let Some((i, token)) = pre_buffer.next() {
589 let mut cache = None;
590 if let Some(break_kind) = potential_line_breaks.get(&i) {
591 if let Some(top_of_stack) = break_stack.last_mut() {
593 if *break_kind == top_of_stack.close {
594 if top_of_stack.depth > 0 {
595 top_of_stack.depth -= 1;
596 } else {
597 break_stack.pop();
598 self.indent_level -= 1;
599 self.end_line(&mut post_buffer);
600 }
601 } else if *break_kind == top_of_stack.open {
602 top_of_stack.depth += 1;
603 }
604 }
605 cache = Some(post_buffer.clone());
608 }
609
610 self.step(
611 token.clone(),
612 pre_buffer.peek().map(|(_, v)| &**v),
613 &mut post_buffer,
614 );
615
616 if let Some(cache) = cache
617 && post_buffer.last_line_width(config) > max_length
618 {
619 post_buffer = cache;
622 self.interrupted = true;
623 self.end_line(&mut post_buffer);
624 self.step(
625 token.clone(),
626 pre_buffer.peek().map(|(_, v)| &**v),
627 &mut post_buffer,
628 );
629
630 let break_kind = potential_line_breaks.get(&i).unwrap();
633 if let Some(also_break_on) = tandem_line_break(*break_kind) {
634 let tandem_break = TandemBreak {
635 open: *break_kind,
636 close: also_break_on,
637 depth: 0,
638 };
639 break_stack.push(tandem_break);
640 self.indent_level += 1;
641 }
642 }
643 }
644
645 for _ in break_stack {
647 self.indent_level = self.indent_level.saturating_sub(1);
648 }
649 out_stream.extend(post_buffer);
650 }
651
652 fn trim_whitespace(&self, stream: &mut TokenStream<PostToken>) {
654 stream.trim_while(|token| {
655 matches!(
656 token,
657 PostToken::Space
658 | PostToken::Newline
659 | PostToken::Indent
660 | PostToken::TempIndent(_)
661 )
662 });
663 }
664
665 fn trim_last_line(&self, stream: &mut TokenStream<PostToken>) {
667 stream.trim_while(|token| {
668 matches!(
669 token,
670 PostToken::Space | PostToken::Indent | PostToken::TempIndent(_)
671 )
672 });
673 }
674
675 fn end_line(&mut self, stream: &mut TokenStream<PostToken>) {
682 self.trim_last_line(stream);
683 if self.position != LinePosition::StartOfLine {
684 stream.push(PostToken::Newline);
685 }
686 self.position = LinePosition::StartOfLine;
687 self.indent(stream);
688 }
689
690 fn indent(&self, stream: &mut TokenStream<PostToken>) {
696 assert!(self.position == LinePosition::StartOfLine);
697
698 self.trim_last_line(stream);
699
700 let level = if self.interrupted {
701 self.indent_level + 1
702 } else {
703 self.indent_level
704 };
705
706 for _ in 0..level {
707 stream.push(PostToken::Indent);
708 }
709
710 if let Some(ref temp_indent) = self.temp_indent {
711 stream.push(PostToken::TempIndent(temp_indent.clone()));
712 }
713 }
714
715 fn blank_line(&mut self, stream: &mut TokenStream<PostToken>) {
717 self.trim_whitespace(stream);
718 if !stream.is_empty() {
719 stream.push(PostToken::Newline);
720 }
721 stream.push(PostToken::Newline);
722 self.position = LinePosition::StartOfLine;
723 self.indent(stream);
724 }
725
726 fn pop_indent(&mut self, stream: &mut TokenStream<PostToken>) {
731 if matches!(
732 stream.0.last(),
733 Some(&PostToken::Indent) | Some(&PostToken::TempIndent(_))
734 ) {
735 let popped = stream.0.pop().unwrap();
736 if matches!(popped, PostToken::TempIndent(_)) {
739 stream.0.pop_if(|t| matches!(t, PostToken::Indent));
740 stream.0.push(popped);
742 }
743 }
744 }
745}