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