1use std::iter::Peekable;
4
5use nonempty::NonEmpty;
6use wdl_ast::Element;
7use wdl_ast::Node;
8
9pub mod node;
10
11pub struct AssertConsumedIter<I: Iterator>(Peekable<I>);
13
14impl<I> AssertConsumedIter<I>
15where
16 I: Iterator,
17{
18 pub fn new(iter: I) -> Self {
20 Self(iter.peekable())
21 }
22}
23
24impl<I> Iterator for AssertConsumedIter<I>
25where
26 I: Iterator,
27{
28 type Item = I::Item;
29
30 fn next(&mut self) -> Option<Self::Item> {
31 self.0.next()
32 }
33}
34
35impl<I> Drop for AssertConsumedIter<I>
36where
37 I: Iterator,
38{
39 fn drop(&mut self) {
40 assert!(
41 self.0.peek().is_none(),
42 "not all iterator items were consumed!"
43 );
44 }
45}
46
47#[derive(Clone, Debug)]
49pub struct FormatElement {
50 element: Element,
52
53 children: Option<NonEmpty<Box<FormatElement>>>,
55}
56
57impl FormatElement {
58 pub fn new(element: Element, children: Option<NonEmpty<Box<FormatElement>>>) -> Self {
60 Self { element, children }
61 }
62
63 pub fn element(&self) -> &Element {
65 &self.element
66 }
67
68 pub fn children(&self) -> Option<AssertConsumedIter<impl Iterator<Item = &FormatElement>>> {
70 self.children
71 .as_ref()
72 .map(|children| AssertConsumedIter::new(children.iter().map(|c| c.as_ref())))
76 }
77}
78
79pub trait AstElementFormatExt {
81 fn into_format_element(self) -> FormatElement;
83}
84
85impl AstElementFormatExt for Element {
86 fn into_format_element(self) -> FormatElement
87 where
88 Self: Sized,
89 {
90 let children = match &self {
91 Element::Node(node) => collate(node),
92 Element::Token(_) => None,
93 };
94
95 FormatElement::new(self, children)
96 }
97}
98
99fn collate(node: &Node) -> Option<NonEmpty<Box<FormatElement>>> {
103 let mut results = Vec::new();
104 let stream = node.inner().children_with_tokens().filter_map(|syntax| {
105 if syntax.kind().is_trivia() {
106 None
107 } else {
108 Some(Element::cast(syntax))
109 }
110 });
111
112 for element in stream {
113 let children = match element {
114 Element::Node(ref node) => collate(node),
115 Element::Token(_) => None,
116 };
117
118 results.push(Box::new(FormatElement { element, children }));
119 }
120
121 if !results.is_empty() {
122 let mut results = results.into_iter();
123 let mut children = NonEmpty::new(results.next().unwrap());
126 children.extend(results);
127 Some(children)
128 } else {
129 None
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use wdl_ast::Document;
136 use wdl_ast::Node;
137 use wdl_ast::SyntaxKind;
138
139 use crate::element::node::AstNodeFormatExt;
140
141 #[test]
142 fn smoke() {
143 let (document, diagnostics) = Document::parse(
144 "## WDL
145version 1.2 # This is a comment attached to the version.
146
147# This is a comment attached to the task keyword.
148task foo # This is an inline comment on the task ident.
149{
150
151} # This is an inline comment on the task close brace.
152
153# This is a comment attached to the workflow keyword.
154workflow bar # This is an inline comment on the workflow ident.
155{
156 # This is attached to the call keyword.
157 call foo {}
158} # This is an inline comment on the workflow close brace.",
159 None,
160 );
161
162 assert!(diagnostics.is_empty());
163 let document = document.ast().into_v1().unwrap();
164
165 let format_element = Node::Ast(document).into_format_element();
166 let mut children = format_element.children().unwrap();
167
168 let version = children.next().expect("version statement element");
171 assert_eq!(
172 version.element().inner().kind(),
173 SyntaxKind::VersionStatementNode
174 );
175
176 let mut version_children = version.children().unwrap();
177 assert_eq!(
178 version_children.next().unwrap().element().kind(),
179 SyntaxKind::VersionKeyword
180 );
181 assert_eq!(
182 version_children.next().unwrap().element().kind(),
183 SyntaxKind::Version
184 );
185
186 let task = children.next().expect("task element");
189 assert_eq!(
190 task.element().inner().kind(),
191 SyntaxKind::TaskDefinitionNode
192 );
193
194 let mut task_children = task.children().unwrap();
197 assert_eq!(
198 task_children.next().unwrap().element().kind(),
199 SyntaxKind::TaskKeyword
200 );
201
202 let ident = task_children.next().unwrap();
203 assert_eq!(ident.element().kind(), SyntaxKind::Ident);
204
205 assert_eq!(
206 task_children.next().unwrap().element().kind(),
207 SyntaxKind::OpenBrace
208 );
209 assert_eq!(
210 task_children.next().unwrap().element().kind(),
211 SyntaxKind::CloseBrace
212 );
213
214 assert!(task_children.next().is_none());
215
216 let workflow = children.next().expect("workflow element");
219 assert_eq!(
220 workflow.element().inner().kind(),
221 SyntaxKind::WorkflowDefinitionNode
222 );
223
224 let mut workflow_children = workflow.children().unwrap();
227
228 assert_eq!(
229 workflow_children.next().unwrap().element().kind(),
230 SyntaxKind::WorkflowKeyword
231 );
232
233 let ident = workflow_children.next().unwrap();
234 assert_eq!(ident.element().kind(), SyntaxKind::Ident);
235
236 assert_eq!(
237 workflow_children.next().unwrap().element().kind(),
238 SyntaxKind::OpenBrace
239 );
240
241 let call = workflow_children.next().unwrap();
242 assert_eq!(call.element().kind(), SyntaxKind::CallStatementNode);
243
244 assert_eq!(
245 workflow_children.next().unwrap().element().kind(),
246 SyntaxKind::CloseBrace
247 );
248
249 assert!(workflow_children.next().is_none());
250 }
251
252 #[test]
253 #[should_panic]
254 fn unconsumed_children_nodes_panic() {
255 let (document, diagnostics) = Document::parse(
256 "## WDL
257version 1.2 # This is a comment attached to the version.
258
259# This is a comment attached to the task keyword.
260task foo # This is an inline comment on the task ident.
261{
262
263} # This is an inline comment on the task close brace.",
264 None,
265 );
266
267 assert!(diagnostics.is_empty());
268 let document = document.ast().into_v1().unwrap();
269
270 let format_element = Node::Ast(document).into_format_element();
271 fn inner(format_element: &crate::element::FormatElement) {
272 let mut _children = format_element.children().unwrap();
273 }
274 inner(&format_element);
275 }
276}