use std::collections::HashSet;
use std::rc::Rc;
use wdl_ast::AstToken;
use wdl_ast::Directive;
use wdl_ast::SyntaxKind;
use wdl_ast::SyntaxTokenExt;
use crate::Comment;
use crate::Token;
use crate::TokenStream;
use crate::Trivia;
use crate::TriviaBlankLineSpacingPolicy;
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PreToken {
BlankLine,
LineEnd,
WordEnd,
IndentStart,
IndentEnd,
LineSpacingPolicy(TriviaBlankLineSpacingPolicy),
Literal(Rc<String>, SyntaxKind),
Trivia(Trivia),
TempIndentStart(Rc<String>),
TempIndentEnd,
FitOrSplitStart {
fit_start: Rc<String>,
fit_delimiter: Rc<String>,
split_end_line: bool,
},
PotentialSplit,
FitOrSplitEnd {
fit_end: Rc<String>,
split_end: Rc<String>,
split_end_line: bool,
},
}
impl std::fmt::Display for PreToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PreToken::BlankLine => write!(f, "<BlankLine>"),
PreToken::LineEnd => write!(f, "<EndOfLine>"),
PreToken::WordEnd => write!(f, "<WordEnd>"),
PreToken::IndentStart => write!(f, "<IndentStart>"),
PreToken::IndentEnd => write!(f, "<IndentEnd>"),
PreToken::LineSpacingPolicy(policy) => {
write!(f, "<LineSpacingPolicy@{policy:?}>")
}
PreToken::Literal(value, kind) => {
write!(f, "<Literal-{kind:?}@{value}>")
}
PreToken::Trivia(trivia) => match trivia {
Trivia::BlankLine => {
write!(f, "<OptionalBlankLine>")
}
Trivia::Comment(comment) => match comment {
Comment::Directive(directive) => {
write!(f, "<Comment-Directive@{directive:?}>")
}
Comment::Documentation(documentation) => {
write!(f, "<Comment-Documentation@{documentation}>")
}
Comment::Preceding(value) => {
write!(f, "<Comment-Preceding@{value}>")
}
Comment::Inline(value) => {
write!(f, "<Comment-Inline@{value}>")
}
},
},
PreToken::TempIndentStart(value) => write!(f, "<TempIndentStart@{value}>"),
PreToken::TempIndentEnd => write!(f, "<TempIndentEnd>"),
PreToken::FitOrSplitStart { .. } => write!(f, "<FitOrSplitStart>"),
PreToken::PotentialSplit => write!(f, "<PotentialSplit>"),
PreToken::FitOrSplitEnd { .. } => write!(f, "<FitOrSplitEnd>"),
}
}
}
impl Token for PreToken {
fn display<'a>(&'a self, _config: &'a crate::Config) -> impl std::fmt::Display {
self
}
}
impl TokenStream<PreToken> {
pub fn blank_line(&mut self) {
self.trim_while(|t| matches!(t, PreToken::BlankLine | PreToken::Trivia(Trivia::BlankLine)));
self.0.push(PreToken::BlankLine);
}
pub fn end_line(&mut self) {
self.trim_while(|t| matches!(t, PreToken::WordEnd | PreToken::LineEnd));
self.0.push(PreToken::LineEnd);
}
pub fn end_word(&mut self) {
self.trim_end(&PreToken::WordEnd);
self.0.push(PreToken::WordEnd);
}
pub fn increment_indent(&mut self) {
self.0.push(PreToken::IndentStart);
}
pub fn decrement_indent(&mut self) {
self.0.push(PreToken::IndentEnd);
}
pub fn fit_or_split_start(
&mut self,
fit_start: Rc<String>,
fit_delimiter: Rc<String>,
split_end_line: bool,
) {
self.0.push(PreToken::FitOrSplitStart {
fit_start,
fit_delimiter,
split_end_line,
})
}
pub fn potential_split(&mut self) {
self.0.push(PreToken::PotentialSplit);
}
pub fn fit_or_split_end(
&mut self,
fit_end: Rc<String>,
split_end: Rc<String>,
split_end_line: bool,
) {
self.0.push(PreToken::FitOrSplitEnd {
fit_end,
split_end,
split_end_line,
})
}
pub fn allow_blank_lines(&mut self) {
self.0.push(PreToken::LineSpacingPolicy(
TriviaBlankLineSpacingPolicy::Always,
));
}
pub fn ignore_trailing_blank_lines(&mut self) {
self.0.push(PreToken::LineSpacingPolicy(
TriviaBlankLineSpacingPolicy::RemoveTrailingBlanks,
));
}
fn push_preceding_trivia(&mut self, token: &wdl_ast::Token) {
assert!(!token.inner().kind().is_trivia());
let preceding_trivia = token.inner().preceding_trivia();
let mut trivia = Vec::new();
let mut exceptions = HashSet::new();
for token in preceding_trivia {
match token.kind() {
SyntaxKind::Whitespace => {
if !self.0.last().is_some_and(|t| {
matches!(t, PreToken::BlankLine | PreToken::Trivia(Trivia::BlankLine))
}) {
trivia.push(PreToken::Trivia(Trivia::BlankLine));
}
}
SyntaxKind::Comment => {
if let Some(comment) = wdl_ast::Comment::cast(token.clone())
&& let Some(directive) = comment.directive()
{
match directive {
Directive::Except(e) => exceptions.extend(e),
}
} else {
let comment = PreToken::Trivia(Trivia::Comment(Comment::Preceding(
Rc::new(token.text().trim_end().to_string()),
)));
trivia.push(comment);
}
}
_ => unreachable!("unexpected trivia: {:?}", token),
};
}
for token in trivia {
self.0.push(token);
}
if !exceptions.is_empty() {
let comment = PreToken::Trivia(Trivia::Comment(Comment::Directive(Rc::new(
Directive::Except(exceptions),
))));
self.0.push(comment);
}
}
fn push_inline_trivia(&mut self, token: &wdl_ast::Token) {
assert!(!token.inner().kind().is_trivia());
if let Some(token) = token.inner().inline_comment() {
let inline_comment = PreToken::Trivia(Trivia::Comment(Comment::Inline(Rc::new(
token.text().trim_end().to_owned(),
))));
self.0.push(inline_comment);
}
}
pub fn push_ast_token(&mut self, token: &wdl_ast::Token) {
self.push_preceding_trivia(token);
self.0.push(PreToken::Literal(
Rc::new(token.inner().text().to_owned()),
token.inner().kind(),
));
self.push_inline_trivia(token);
}
pub fn push_ast_token_as(&mut self, token: &wdl_ast::Token, kind: SyntaxKind) {
self.push_preceding_trivia(token);
self.0.push(PreToken::Literal(
Rc::new(token.inner().text().to_owned()),
kind,
));
self.push_inline_trivia(token);
}
pub fn push_literal_in_place_of_token(&mut self, token: &wdl_ast::Token, replacement: String) {
self.push_preceding_trivia(token);
self.0.push(PreToken::Literal(
Rc::new(replacement),
token.inner().kind(),
));
self.push_inline_trivia(token);
}
pub fn push_literal(&mut self, value: String, kind: SyntaxKind) {
self.0.push(PreToken::Literal(Rc::new(value), kind));
}
}