use std::iter::Peekable;
use nonempty::NonEmpty;
use wdl_ast::Element;
use wdl_ast::Node;
pub mod node;
pub struct AssertConsumedIter<I: Iterator>(Peekable<I>);
impl<I> AssertConsumedIter<I>
where
I: Iterator,
{
pub fn new(iter: I) -> Self {
Self(iter.peekable())
}
}
impl<I> Iterator for AssertConsumedIter<I>
where
I: Iterator,
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl<I> Drop for AssertConsumedIter<I>
where
I: Iterator,
{
fn drop(&mut self) {
assert!(
self.0.peek().is_none(),
"not all iterator items were consumed!"
);
}
}
#[derive(Clone, Debug)]
pub struct FormatElement {
element: Element,
children: Option<NonEmpty<Box<FormatElement>>>,
}
impl FormatElement {
pub fn new(element: Element, children: Option<NonEmpty<Box<FormatElement>>>) -> Self {
Self { element, children }
}
pub fn element(&self) -> &Element {
&self.element
}
pub fn children(&self) -> Option<AssertConsumedIter<impl Iterator<Item = &FormatElement>>> {
self.children
.as_ref()
.map(|children| AssertConsumedIter::new(children.iter().map(|c| c.as_ref())))
}
}
pub trait AstElementFormatExt {
fn into_format_element(self) -> FormatElement;
}
impl AstElementFormatExt for Element {
fn into_format_element(self) -> FormatElement
where
Self: Sized,
{
let children = match &self {
Element::Node(node) => collate(node),
Element::Token(_) => None,
};
FormatElement::new(self, children)
}
}
fn collate(node: &Node) -> Option<NonEmpty<Box<FormatElement>>> {
let mut results = Vec::new();
let stream = node.syntax().children_with_tokens().filter_map(|syntax| {
if syntax.kind().is_trivia() {
None
} else {
Some(Element::cast(syntax))
}
});
for element in stream {
let children = match element {
Element::Node(ref node) => collate(node),
Element::Token(_) => None,
};
results.push(Box::new(FormatElement { element, children }));
}
if !results.is_empty() {
let mut results = results.into_iter();
let mut children = NonEmpty::new(results.next().unwrap());
children.extend(results);
Some(children)
} else {
None
}
}
#[cfg(test)]
mod tests {
use wdl_ast::Document;
use wdl_ast::Node;
use wdl_ast::SyntaxKind;
use crate::element::node::AstNodeFormatExt;
#[test]
fn smoke() {
let (document, diagnostics) = Document::parse(
"## WDL
version 1.2 # This is a comment attached to the version.
# This is a comment attached to the task keyword.
task foo # This is an inline comment on the task ident.
{
} # This is an inline comment on the task close brace.
# This is a comment attached to the workflow keyword.
workflow bar # This is an inline comment on the workflow ident.
{
# This is attached to the call keyword.
call foo {}
} # This is an inline comment on the workflow close brace.",
);
assert!(diagnostics.is_empty());
let document = document.ast().into_v1().unwrap();
let format_element = Node::Ast(document).into_format_element();
let mut children = format_element.children().unwrap();
let version = children.next().expect("version statement element");
assert_eq!(
version.element().syntax().kind(),
SyntaxKind::VersionStatementNode
);
let mut version_children = version.children().unwrap();
assert_eq!(
version_children.next().unwrap().element().kind(),
SyntaxKind::VersionKeyword
);
assert_eq!(
version_children.next().unwrap().element().kind(),
SyntaxKind::Version
);
let task = children.next().expect("task element");
assert_eq!(
task.element().syntax().kind(),
SyntaxKind::TaskDefinitionNode
);
let mut task_children = task.children().unwrap();
assert_eq!(
task_children.next().unwrap().element().kind(),
SyntaxKind::TaskKeyword
);
let ident = task_children.next().unwrap();
assert_eq!(ident.element().kind(), SyntaxKind::Ident);
assert_eq!(
task_children.next().unwrap().element().kind(),
SyntaxKind::OpenBrace
);
assert_eq!(
task_children.next().unwrap().element().kind(),
SyntaxKind::CloseBrace
);
assert!(task_children.next().is_none());
let workflow = children.next().expect("workflow element");
assert_eq!(
workflow.element().syntax().kind(),
SyntaxKind::WorkflowDefinitionNode
);
let mut workflow_children = workflow.children().unwrap();
assert_eq!(
workflow_children.next().unwrap().element().kind(),
SyntaxKind::WorkflowKeyword
);
let ident = workflow_children.next().unwrap();
assert_eq!(ident.element().kind(), SyntaxKind::Ident);
assert_eq!(
workflow_children.next().unwrap().element().kind(),
SyntaxKind::OpenBrace
);
let call = workflow_children.next().unwrap();
assert_eq!(call.element().kind(), SyntaxKind::CallStatementNode);
assert_eq!(
workflow_children.next().unwrap().element().kind(),
SyntaxKind::CloseBrace
);
assert!(workflow_children.next().is_none());
}
#[test]
#[should_panic]
fn unconsumed_children_nodes_panic() {
let (document, diagnostics) = Document::parse(
"## WDL
version 1.2 # This is a comment attached to the version.
# This is a comment attached to the task keyword.
task foo # This is an inline comment on the task ident.
{
} # This is an inline comment on the task close brace.",
);
assert!(diagnostics.is_empty());
let document = document.ast().into_v1().unwrap();
let format_element = Node::Ast(document).into_format_element();
fn inner(format_element: &crate::element::FormatElement) {
let mut _children = format_element.children().unwrap();
}
inner(&format_element);
}
}