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 );
160
161 assert!(diagnostics.is_empty());
162 let document = document.ast().into_v1().unwrap();
163
164 let format_element = Node::Ast(document).into_format_element();
165 let mut children = format_element.children().unwrap();
166
167 let version = children.next().expect("version statement element");
170 assert_eq!(
171 version.element().inner().kind(),
172 SyntaxKind::VersionStatementNode
173 );
174
175 let mut version_children = version.children().unwrap();
176 assert_eq!(
177 version_children.next().unwrap().element().kind(),
178 SyntaxKind::VersionKeyword
179 );
180 assert_eq!(
181 version_children.next().unwrap().element().kind(),
182 SyntaxKind::Version
183 );
184
185 let task = children.next().expect("task element");
188 assert_eq!(
189 task.element().inner().kind(),
190 SyntaxKind::TaskDefinitionNode
191 );
192
193 let mut task_children = task.children().unwrap();
196 assert_eq!(
197 task_children.next().unwrap().element().kind(),
198 SyntaxKind::TaskKeyword
199 );
200
201 let ident = task_children.next().unwrap();
202 assert_eq!(ident.element().kind(), SyntaxKind::Ident);
203
204 assert_eq!(
205 task_children.next().unwrap().element().kind(),
206 SyntaxKind::OpenBrace
207 );
208 assert_eq!(
209 task_children.next().unwrap().element().kind(),
210 SyntaxKind::CloseBrace
211 );
212
213 assert!(task_children.next().is_none());
214
215 let workflow = children.next().expect("workflow element");
218 assert_eq!(
219 workflow.element().inner().kind(),
220 SyntaxKind::WorkflowDefinitionNode
221 );
222
223 let mut workflow_children = workflow.children().unwrap();
226
227 assert_eq!(
228 workflow_children.next().unwrap().element().kind(),
229 SyntaxKind::WorkflowKeyword
230 );
231
232 let ident = workflow_children.next().unwrap();
233 assert_eq!(ident.element().kind(), SyntaxKind::Ident);
234
235 assert_eq!(
236 workflow_children.next().unwrap().element().kind(),
237 SyntaxKind::OpenBrace
238 );
239
240 let call = workflow_children.next().unwrap();
241 assert_eq!(call.element().kind(), SyntaxKind::CallStatementNode);
242
243 assert_eq!(
244 workflow_children.next().unwrap().element().kind(),
245 SyntaxKind::CloseBrace
246 );
247
248 assert!(workflow_children.next().is_none());
249 }
250
251 #[test]
252 #[should_panic]
253 fn unconsumed_children_nodes_panic() {
254 let (document, diagnostics) = Document::parse(
255 "## WDL
256version 1.2 # This is a comment attached to the version.
257
258# This is a comment attached to the task keyword.
259task foo # This is an inline comment on the task ident.
260{
261
262} # This is an inline comment on the task close brace.",
263 );
264
265 assert!(diagnostics.is_empty());
266 let document = document.ast().into_v1().unwrap();
267
268 let format_element = Node::Ast(document).into_format_element();
269 fn inner(format_element: &crate::element::FormatElement) {
270 let mut _children = format_element.children().unwrap();
271 }
272 inner(&format_element);
273 }
274}