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