use std::collections::HashSet;
use std::rc::Rc;
use wdl_ast::DOC_COMMENT_PREFIX;
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,
}
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>"),
}
}
}
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.end_line();
self.0.push(PreToken::IndentStart);
}
pub fn decrement_indent(&mut self) {
self.end_line();
self.0.push(PreToken::IndentEnd);
}
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 documentation = String::new();
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(t) = token.text().strip_prefix(DOC_COMMENT_PREFIX) {
documentation.push_str(t);
documentation.push('\n');
} else if let Ok(directive) = token.text().parse::<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),
};
}
let mut trivia = trivia.into_iter().peekable();
if let Some(PreToken::Trivia(Trivia::BlankLine)) = trivia.peek() {
self.0.push(trivia.next().unwrap());
}
let mut docs_present = false;
if !documentation.is_empty() {
docs_present = true;
let comment = PreToken::Trivia(Trivia::Comment(Comment::Documentation(Rc::new(
documentation,
))));
self.0.push(comment);
if let Some(PreToken::Trivia(Trivia::BlankLine)) = trivia.peek() {
let _ = trivia.next();
}
}
for token in trivia {
self.0.push(token);
}
if docs_present && let Some(PreToken::Trivia(Trivia::BlankLine)) = self.0.last() {
self.0.pop();
}
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_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));
}
}