Skip to main content

wdl_format/
element.rs

1//! Elements used during formatting.
2
3use std::iter::Peekable;
4
5use nonempty::NonEmpty;
6use wdl_ast::Element;
7use wdl_ast::Node;
8
9pub mod node;
10
11/// An iterator that asserts that all items have been consumed when dropped.
12pub struct AssertConsumedIter<I: Iterator>(Peekable<I>);
13
14impl<I> AssertConsumedIter<I>
15where
16    I: Iterator,
17{
18    /// Creates a new [`AssertConsumedIter`].
19    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/// A formattable element.
48#[derive(Clone, Debug)]
49pub struct FormatElement {
50    /// The inner element.
51    element: Element,
52
53    /// Children as format elements.
54    children: Option<NonEmpty<Box<FormatElement>>>,
55}
56
57impl FormatElement {
58    /// Creates a new [`FormatElement`].
59    pub fn new(element: Element, children: Option<NonEmpty<Box<FormatElement>>>) -> Self {
60        Self { element, children }
61    }
62
63    /// Gets the inner element.
64    pub fn element(&self) -> &Element {
65        &self.element
66    }
67
68    /// Gets the children for this node.
69    pub fn children(&self) -> Option<AssertConsumedIter<impl Iterator<Item = &FormatElement>>> {
70        self.children
71            .as_ref()
72            // NOTE: we wrap the iterator in an [`AssertConsumedIter`] to ensure
73            // that no children are ever forgotten to be formatted (they must be
74            // explicitly consumed and dropped).
75            .map(|children| AssertConsumedIter::new(children.iter().map(|c| c.as_ref())))
76    }
77}
78
79/// An extension trait for formatting [`Element`]s.
80pub trait AstElementFormatExt {
81    /// Consumes `self` and returns the [`Element`] as a [`FormatElement`].
82    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
99/// Collates the children of a particular node.
100///
101/// This function ignores trivia.
102fn 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        // SAFETY: we just checked to ensure that `results` wasn't empty, so
124        // this will always unwrap.
125        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        // Version statement.
169
170        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        // Task Definition.
187
188        let task = children.next().expect("task element");
189        assert_eq!(
190            task.element().inner().kind(),
191            SyntaxKind::TaskDefinitionNode
192        );
193
194        // Children.
195
196        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        // Workflow Definition.
217
218        let workflow = children.next().expect("workflow element");
219        assert_eq!(
220            workflow.element().inner().kind(),
221            SyntaxKind::WorkflowDefinitionNode
222        );
223
224        // Children.
225
226        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}