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        );
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        // Version statement.
168
169        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        // Task Definition.
186
187        let task = children.next().expect("task element");
188        assert_eq!(
189            task.element().inner().kind(),
190            SyntaxKind::TaskDefinitionNode
191        );
192
193        // Children.
194
195        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        // Workflow Definition.
216
217        let workflow = children.next().expect("workflow element");
218        assert_eq!(
219            workflow.element().inner().kind(),
220            SyntaxKind::WorkflowDefinitionNode
221        );
222
223        // Children.
224
225        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}