wdl_ast/
lib.rs

1//! An abstract syntax tree for Workflow Description Language (WDL) documents.
2//!
3//! The AST implementation is effectively a facade over the concrete syntax tree
4//! (CST) implemented by [SyntaxTree] from `wdl-grammar`.
5//!
6//! An AST is cheap to construct and may be cheaply cloned at any level.
7//!
8//! However, an AST (and the underlying CST) are immutable; updating the tree
9//! requires replacing a node in the tree to produce a new tree. The unaffected
10//! nodes of the replacement are reused from the old tree to the new tree.
11//!
12//! # Examples
13//!
14//! An example of parsing a WDL document into an AST and validating it:
15//!
16//! ```rust
17//! # let source = "version 1.1\nworkflow test {}";
18//! use wdl_ast::Document;
19//!
20//! let (document, diagnostics) = Document::parse(source);
21//! if !diagnostics.is_empty() {
22//!     // Handle the failure to parse
23//! }
24//! ```
25
26#![warn(missing_docs)]
27#![warn(rust_2018_idioms)]
28#![warn(rust_2021_compatibility)]
29#![warn(missing_debug_implementations)]
30#![warn(clippy::missing_docs_in_private_items)]
31#![warn(rustdoc::broken_intra_doc_links)]
32
33use std::fmt;
34
35pub use rowan::Direction;
36use rowan::NodeOrToken;
37use v1::CloseBrace;
38use v1::CloseHeredoc;
39use v1::OpenBrace;
40use v1::OpenHeredoc;
41pub use wdl_grammar::Diagnostic;
42pub use wdl_grammar::Label;
43pub use wdl_grammar::Severity;
44pub use wdl_grammar::Span;
45pub use wdl_grammar::SupportedVersion;
46pub use wdl_grammar::SyntaxElement;
47pub use wdl_grammar::SyntaxExt;
48pub use wdl_grammar::SyntaxKind;
49pub use wdl_grammar::SyntaxNode;
50pub use wdl_grammar::SyntaxToken;
51pub use wdl_grammar::SyntaxTokenExt;
52pub use wdl_grammar::SyntaxTree;
53pub use wdl_grammar::WorkflowDescriptionLanguage;
54pub use wdl_grammar::lexer;
55pub use wdl_grammar::version;
56
57pub mod v1;
58
59mod element;
60
61pub use element::*;
62
63/// A trait that abstracts the underlying representation of a syntax tree node.
64///
65/// The default node type is `SyntaxNode` for all AST nodes.
66pub trait TreeNode: Clone + fmt::Debug + PartialEq + Eq + std::hash::Hash {
67    /// The associated token type for the tree node.
68    type Token: TreeToken;
69
70    /// Gets the parent node of the node.
71    ///
72    /// Returns `None` if the node is a root.
73    fn parent(&self) -> Option<Self>;
74
75    /// Gets the syntax kind of the node.
76    fn kind(&self) -> SyntaxKind;
77
78    /// Gets the text of the node.
79    ///
80    /// Node text is not contiguous, so the returned value implements `Display`.
81    fn text(&self) -> impl fmt::Display;
82
83    /// Gets the span of the node.
84    fn span(&self) -> Span;
85
86    /// Gets the children nodes of the node.
87    fn children(&self) -> impl Iterator<Item = Self>;
88
89    /// Gets all the children of the node, including tokens.
90    fn children_with_tokens(&self) -> impl Iterator<Item = NodeOrToken<Self, Self::Token>>;
91
92    /// Gets the last token of the node.
93    fn last_token(&self) -> Option<Self::Token>;
94
95    /// Gets the node descendants of the node.
96    fn descendants(&self) -> impl Iterator<Item = Self>;
97
98    /// Gets the ancestors of the node.
99    fn ancestors(&self) -> impl Iterator<Item = Self>;
100}
101
102/// A trait that abstracts the underlying representation of a syntax token.
103pub trait TreeToken: Clone + fmt::Debug + PartialEq + Eq + std::hash::Hash {
104    /// The associated node type for the token.
105    type Node: TreeNode;
106
107    /// Gets the parent node of the token.
108    fn parent(&self) -> Self::Node;
109
110    /// Gets the syntax kind for the token.
111    fn kind(&self) -> SyntaxKind;
112
113    /// Gets the text of the token.
114    fn text(&self) -> &str;
115
116    /// Gets the span of the token.
117    fn span(&self) -> Span;
118}
119
120/// A trait implemented by AST nodes.
121pub trait AstNode<N: TreeNode>: Sized {
122    /// Determines if the kind can be cast to this representation.
123    fn can_cast(kind: SyntaxKind) -> bool;
124
125    /// Casts the given inner type to the this representation.
126    fn cast(inner: N) -> Option<Self>;
127
128    /// Gets the inner type from this representation.
129    fn inner(&self) -> &N;
130
131    /// Gets the syntax kind of the node.
132    fn kind(&self) -> SyntaxKind {
133        self.inner().kind()
134    }
135
136    /// Gets the text of the node.
137    ///
138    /// As node text is not contiguous, this returns a type that implements
139    /// `Display`.
140    fn text<'a>(&'a self) -> impl fmt::Display
141    where
142        N: 'a,
143    {
144        self.inner().text()
145    }
146
147    /// Gets the span of the node.
148    fn span(&self) -> Span {
149        self.inner().span()
150    }
151
152    /// Gets the first token child that can cast to an expected type.
153    fn token<C>(&self) -> Option<C>
154    where
155        C: AstToken<N::Token>,
156    {
157        self.inner()
158            .children_with_tokens()
159            .filter_map(|e| e.into_token())
160            .find_map(|t| C::cast(t))
161    }
162
163    /// Gets all the token children that can cast to an expected type.
164    fn tokens<'a, C>(&'a self) -> impl Iterator<Item = C>
165    where
166        C: AstToken<N::Token>,
167        N: 'a,
168    {
169        self.inner()
170            .children_with_tokens()
171            .filter_map(|e| e.into_token().and_then(C::cast))
172    }
173
174    /// Gets the last token of the node and attempts to cast it to an expected
175    /// type.
176    ///
177    /// Returns `None` if there is no last token or if it cannot be casted to
178    /// the expected type.
179    fn last_token<C>(&self) -> Option<C>
180    where
181        C: AstToken<N::Token>,
182    {
183        self.inner().last_token().and_then(C::cast)
184    }
185
186    /// Gets the first node child that can cast to an expected type.
187    fn child<C>(&self) -> Option<C>
188    where
189        C: AstNode<N>,
190    {
191        self.inner().children().find_map(C::cast)
192    }
193
194    /// Gets all node children that can cast to an expected type.
195    fn children<'a, C>(&'a self) -> impl Iterator<Item = C>
196    where
197        C: AstNode<N>,
198        N: 'a,
199    {
200        self.inner().children().filter_map(C::cast)
201    }
202
203    /// Gets the parent of the node if the underlying tree node has a parent.
204    ///
205    /// Returns `None` if the node has no parent or if the parent node is not of
206    /// the expected type.
207    fn parent<'a, P>(&self) -> Option<P>
208    where
209        P: AstNode<N>,
210        N: 'a,
211    {
212        P::cast(self.inner().parent()?)
213    }
214
215    /// Calculates the span of a scope given the node where the scope is
216    /// visible.
217    ///
218    /// Returns `None` if the node does not contain the open and close tokens as
219    /// children.
220    fn scope_span<O, C>(&self) -> Option<Span>
221    where
222        O: AstToken<N::Token>,
223        C: AstToken<N::Token>,
224    {
225        let open = self.token::<O>()?.span();
226        let close = self.last_token::<C>()?.span();
227
228        // The span starts after the opening brace and before the closing brace
229        Some(Span::new(open.end(), close.start() - open.end()))
230    }
231
232    /// Gets the interior span of child opening and closing brace tokens for the
233    /// node.
234    ///
235    /// The span starts from immediately after the opening brace token and ends
236    /// immediately before the closing brace token.
237    ///
238    /// Returns `None` if the node does not contain child brace tokens.
239    fn braced_scope_span(&self) -> Option<Span> {
240        self.scope_span::<OpenBrace<N::Token>, CloseBrace<N::Token>>()
241    }
242
243    /// Gets the interior span of child opening and closing heredoc tokens for
244    /// the node.
245    ///
246    /// The span starts from immediately after the opening heredoc token and
247    /// ends immediately before the closing heredoc token.
248    ///
249    /// Returns `None` if the node does not contain child heredoc tokens.
250    fn heredoc_scope_span(&self) -> Option<Span> {
251        self.scope_span::<OpenHeredoc<N::Token>, CloseHeredoc<N::Token>>()
252    }
253
254    /// Gets the node descendants (including self) from this node that can be
255    /// cast to the expected type.
256    fn descendants<'a, D>(&'a self) -> impl Iterator<Item = D>
257    where
258        D: AstNode<N>,
259        N: 'a,
260    {
261        self.inner().descendants().filter_map(|d| D::cast(d))
262    }
263}
264
265/// A trait implemented by AST tokens.
266pub trait AstToken<T: TreeToken>: Sized {
267    /// Determines if the kind can be cast to this representation.
268    fn can_cast(kind: SyntaxKind) -> bool;
269
270    /// Casts the given inner type to the this representation.
271    fn cast(inner: T) -> Option<Self>;
272
273    /// Gets the inner type from this representation.
274    fn inner(&self) -> &T;
275
276    /// Gets the syntax kind of the token.
277    fn kind(&self) -> SyntaxKind {
278        self.inner().kind()
279    }
280
281    /// Gets the text of the token.
282    fn text<'a>(&'a self) -> &'a str
283    where
284        T: 'a,
285    {
286        self.inner().text()
287    }
288
289    /// Gets the span of the token.
290    fn span(&self) -> Span {
291        self.inner().span()
292    }
293
294    /// Gets the parent of the token.
295    ///
296    /// Returns `None` if the parent node cannot be cast to the expected type.
297    fn parent<'a, P>(&self) -> Option<P>
298    where
299        P: AstNode<T::Node>,
300        T: 'a,
301    {
302        P::cast(self.inner().parent())
303    }
304}
305
306/// Implemented by nodes that can create a new root from a different tree node
307/// type.
308pub trait NewRoot<N: TreeNode>: Sized {
309    /// Constructs a new root node from the give root node of a different tree
310    /// node type.
311    fn new_root(root: N) -> Self;
312}
313
314impl TreeNode for SyntaxNode {
315    type Token = SyntaxToken;
316
317    fn parent(&self) -> Option<SyntaxNode> {
318        self.parent()
319    }
320
321    fn kind(&self) -> SyntaxKind {
322        self.kind()
323    }
324
325    fn children(&self) -> impl Iterator<Item = Self> {
326        self.children()
327    }
328
329    fn children_with_tokens(&self) -> impl Iterator<Item = NodeOrToken<Self, Self::Token>> {
330        self.children_with_tokens()
331    }
332
333    fn text(&self) -> impl fmt::Display {
334        self.text()
335    }
336
337    fn span(&self) -> Span {
338        let range = self.text_range();
339        let start = usize::from(range.start());
340        Span::new(start, usize::from(range.end()) - start)
341    }
342
343    fn last_token(&self) -> Option<Self::Token> {
344        self.last_token()
345    }
346
347    fn descendants(&self) -> impl Iterator<Item = Self> {
348        self.descendants()
349    }
350
351    fn ancestors(&self) -> impl Iterator<Item = Self> {
352        self.ancestors()
353    }
354}
355
356impl TreeToken for SyntaxToken {
357    type Node = SyntaxNode;
358
359    fn parent(&self) -> SyntaxNode {
360        self.parent().expect("token should have a parent")
361    }
362
363    fn kind(&self) -> SyntaxKind {
364        self.kind()
365    }
366
367    fn text(&self) -> &str {
368        self.text()
369    }
370
371    fn span(&self) -> Span {
372        let range = self.text_range();
373        let start = usize::from(range.start());
374        Span::new(start, usize::from(range.end()) - start)
375    }
376}
377
378/// Represents the AST of a [Document].
379///
380/// See [Document::ast].
381#[derive(Clone, Debug, PartialEq, Eq)]
382pub enum Ast<N: TreeNode = SyntaxNode> {
383    /// The WDL document specifies an unsupported version.
384    Unsupported,
385    /// The WDL document is V1.
386    V1(v1::Ast<N>),
387}
388
389impl<N: TreeNode> Ast<N> {
390    /// Gets the AST as a V1 AST.
391    ///
392    /// Returns `None` if the AST is not a V1 AST.
393    pub fn as_v1(&self) -> Option<&v1::Ast<N>> {
394        match self {
395            Self::V1(ast) => Some(ast),
396            _ => None,
397        }
398    }
399
400    /// Consumes `self` and attempts to return the V1 AST.
401    pub fn into_v1(self) -> Option<v1::Ast<N>> {
402        match self {
403            Self::V1(ast) => Some(ast),
404            _ => None,
405        }
406    }
407
408    /// Consumes `self` and attempts to return the V1 AST.
409    ///
410    /// # Panics
411    ///
412    /// Panics if the AST is not a V1 AST.
413    pub fn unwrap_v1(self) -> v1::Ast<N> {
414        self.into_v1().expect("the AST is not a V1 AST")
415    }
416}
417
418/// Represents a single WDL document.
419///
420/// See [Document::ast] for getting a version-specific Abstract
421/// Syntax Tree.
422#[derive(Clone, PartialEq, Eq, Hash)]
423pub struct Document<N: TreeNode = SyntaxNode>(N);
424
425impl<N: TreeNode> AstNode<N> for Document<N> {
426    fn can_cast(kind: SyntaxKind) -> bool {
427        kind == SyntaxKind::RootNode
428    }
429
430    fn cast(inner: N) -> Option<Self> {
431        if Self::can_cast(inner.kind()) {
432            Some(Self(inner))
433        } else {
434            None
435        }
436    }
437
438    fn inner(&self) -> &N {
439        &self.0
440    }
441}
442
443impl Document {
444    /// Parses a document from the given source.
445    ///
446    /// A document and its AST elements are trivially cloned.
447    ///
448    /// # Example
449    ///
450    /// ```rust
451    /// # use wdl_ast::{Document, AstToken, Ast};
452    /// let (document, diagnostics) = Document::parse("version 1.1");
453    /// assert!(diagnostics.is_empty());
454    ///
455    /// assert_eq!(
456    ///     document
457    ///         .version_statement()
458    ///         .expect("should have version statement")
459    ///         .version()
460    ///         .text(),
461    ///     "1.1"
462    /// );
463    ///
464    /// match document.ast() {
465    ///     Ast::V1(ast) => {
466    ///         assert_eq!(ast.items().count(), 0);
467    ///     }
468    ///     Ast::Unsupported => panic!("should be a V1 AST"),
469    /// }
470    /// ```
471    pub fn parse(source: &str) -> (Self, Vec<Diagnostic>) {
472        let (tree, diagnostics) = SyntaxTree::parse(source);
473        (
474            Document::cast(tree.into_syntax()).expect("document should cast"),
475            diagnostics,
476        )
477    }
478}
479
480impl<N: TreeNode> Document<N> {
481    /// Gets the version statement of the document.
482    ///
483    /// This can be used to determine the version of the document that was
484    /// parsed.
485    ///
486    /// A return value of `None` signifies a missing version statement.
487    pub fn version_statement(&self) -> Option<VersionStatement<N>> {
488        self.child()
489    }
490
491    /// Gets the AST representation of the document.
492    pub fn ast(&self) -> Ast<N> {
493        self.version_statement()
494            .as_ref()
495            .and_then(|s| s.version().text().parse::<SupportedVersion>().ok())
496            .map(|v| match v {
497                SupportedVersion::V1(_) => Ast::V1(v1::Ast(self.0.clone())),
498                _ => Ast::Unsupported,
499            })
500            .unwrap_or(Ast::Unsupported)
501    }
502
503    /// Morphs a document of one node type to a document of a different node
504    /// type.
505    pub fn morph<U: TreeNode + NewRoot<N>>(self) -> Document<U> {
506        Document(U::new_root(self.0))
507    }
508}
509
510impl fmt::Debug for Document {
511    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512        self.0.fmt(f)
513    }
514}
515
516/// Represents a whitespace token in the AST.
517#[derive(Clone, Debug, PartialEq, Eq, Hash)]
518pub struct Whitespace<T: TreeToken = SyntaxToken>(T);
519
520impl<T: TreeToken> AstToken<T> for Whitespace<T> {
521    fn can_cast(kind: SyntaxKind) -> bool {
522        kind == SyntaxKind::Whitespace
523    }
524
525    fn cast(inner: T) -> Option<Self> {
526        match inner.kind() {
527            SyntaxKind::Whitespace => Some(Self(inner)),
528            _ => None,
529        }
530    }
531
532    fn inner(&self) -> &T {
533        &self.0
534    }
535}
536
537/// Represents a comment token in the AST.
538#[derive(Debug, Clone, PartialEq, Eq, Hash)]
539pub struct Comment<T: TreeToken = SyntaxToken>(T);
540
541impl<T: TreeToken> AstToken<T> for Comment<T> {
542    fn can_cast(kind: SyntaxKind) -> bool {
543        kind == SyntaxKind::Comment
544    }
545
546    fn cast(inner: T) -> Option<Self> {
547        match inner.kind() {
548            SyntaxKind::Comment => Some(Self(inner)),
549            _ => None,
550        }
551    }
552
553    fn inner(&self) -> &T {
554        &self.0
555    }
556}
557
558/// Represents a version statement in a WDL AST.
559#[derive(Debug, Clone, PartialEq, Eq, Hash)]
560pub struct VersionStatement<N: TreeNode = SyntaxNode>(N);
561
562impl<N: TreeNode> VersionStatement<N> {
563    /// Gets the version of the version statement.
564    pub fn version(&self) -> Version<N::Token> {
565        self.token()
566            .expect("version statement must have a version token")
567    }
568
569    /// Gets the version keyword of the version statement.
570    pub fn keyword(&self) -> v1::VersionKeyword<N::Token> {
571        self.token()
572            .expect("version statement must have a version keyword")
573    }
574}
575
576impl<N: TreeNode> AstNode<N> for VersionStatement<N> {
577    fn can_cast(kind: SyntaxKind) -> bool {
578        kind == SyntaxKind::VersionStatementNode
579    }
580
581    fn cast(inner: N) -> Option<Self> {
582        match inner.kind() {
583            SyntaxKind::VersionStatementNode => Some(Self(inner)),
584            _ => None,
585        }
586    }
587
588    fn inner(&self) -> &N {
589        &self.0
590    }
591}
592
593/// Represents a version in the AST.
594#[derive(Clone, Debug, PartialEq, Eq, Hash)]
595pub struct Version<T: TreeToken = SyntaxToken>(T);
596
597impl<T: TreeToken> AstToken<T> for Version<T> {
598    fn can_cast(kind: SyntaxKind) -> bool {
599        kind == SyntaxKind::Version
600    }
601
602    fn cast(inner: T) -> Option<Self> {
603        match inner.kind() {
604            SyntaxKind::Version => Some(Self(inner)),
605            _ => None,
606        }
607    }
608
609    fn inner(&self) -> &T {
610        &self.0
611    }
612}
613
614/// Represents an identifier token.
615#[derive(Debug, Clone, PartialEq, Eq, Hash)]
616pub struct Ident<T: TreeToken = SyntaxToken>(T);
617
618impl<T: TreeToken> Ident<T> {
619    /// Gets a hashable representation of the identifier.
620    pub fn hashable(&self) -> TokenText<T> {
621        TokenText(self.0.clone())
622    }
623}
624
625impl<T: TreeToken> AstToken<T> for Ident<T> {
626    fn can_cast(kind: SyntaxKind) -> bool {
627        kind == SyntaxKind::Ident
628    }
629
630    fn cast(inner: T) -> Option<Self> {
631        match inner.kind() {
632            SyntaxKind::Ident => Some(Self(inner)),
633            _ => None,
634        }
635    }
636
637    fn inner(&self) -> &T {
638        &self.0
639    }
640}
641
642/// Helper for hashing tokens by their text.
643///
644/// Normally a token's equality and hash implementation work by comparing
645/// the token's element in the tree; thus, two tokens with the same text
646/// but different positions in the tree will compare and hash differently.
647///
648/// With this hash implementation, two tokens compare and hash identically if
649/// their text is identical.
650#[derive(Debug, Clone)]
651pub struct TokenText<T: TreeToken = SyntaxToken>(T);
652
653impl TokenText {
654    /// Gets the text of the underlying token.
655    pub fn text(&self) -> &str {
656        self.0.text()
657    }
658
659    /// Gets the span of the underlying token.
660    pub fn span(&self) -> Span {
661        self.0.span()
662    }
663}
664
665impl<T: TreeToken> PartialEq for TokenText<T> {
666    fn eq(&self, other: &Self) -> bool {
667        self.0.text() == other.0.text()
668    }
669}
670
671impl<T: TreeToken> Eq for TokenText<T> {}
672
673impl<T: TreeToken> std::hash::Hash for TokenText<T> {
674    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
675        self.0.text().hash(state);
676    }
677}
678
679impl<T: TreeToken> std::borrow::Borrow<str> for TokenText<T> {
680    fn borrow(&self) -> &str {
681        self.0.text()
682    }
683}