wdl_ast/
visitor.rs

1//! Implementation for AST visitation.
2//!
3//! An AST visitor is called when a WDL document is being visited (see
4//! [Document::visit]); callbacks correspond to specific nodes and tokens in the
5//! AST based on [SyntaxKind]. As `SyntaxKind` is the union of nodes and tokens
6//! from _every_ version of WDL, the `Visitor` trait is also the union of
7//! visitation callbacks.
8//!
9//! The [Visitor] trait is not WDL version-specific, meaning that the trait's
10//! methods currently receive V1 representation of AST nodes.
11//!
12//! In the future, a major version change to the WDL specification will
13//! introduce V2 representations for AST nodes that are either brand new or have
14//! changed since V1.
15//!
16//! When this occurs, the `Visitor` trait will be extended to support the new
17//! syntax; however, syntax that has not changed since V1 will continue to use
18//! the V1 AST types.
19//!
20//! That means it is possible to receive callbacks for V1 nodes and tokens when
21//! visiting a V2 document; the hope is that enables some visitors to be
22//! "shared" across different WDL versions.
23
24use rowan::WalkEvent;
25
26use crate::AstToken as _;
27use crate::Comment;
28use crate::Document;
29use crate::SupportedVersion;
30use crate::SyntaxKind;
31use crate::SyntaxNode;
32use crate::VersionStatement;
33use crate::VisitReason;
34use crate::Whitespace;
35use crate::v1::BoundDecl;
36use crate::v1::CallStatement;
37use crate::v1::CommandSection;
38use crate::v1::CommandText;
39use crate::v1::ConditionalStatement;
40use crate::v1::Expr;
41use crate::v1::ImportStatement;
42use crate::v1::InputSection;
43use crate::v1::MetadataArray;
44use crate::v1::MetadataObject;
45use crate::v1::MetadataObjectItem;
46use crate::v1::MetadataSection;
47use crate::v1::OutputSection;
48use crate::v1::ParameterMetadataSection;
49use crate::v1::Placeholder;
50use crate::v1::RequirementsSection;
51use crate::v1::RuntimeItem;
52use crate::v1::RuntimeSection;
53use crate::v1::ScatterStatement;
54use crate::v1::StringText;
55use crate::v1::StructDefinition;
56use crate::v1::TaskDefinition;
57use crate::v1::TaskHintsSection;
58use crate::v1::UnboundDecl;
59use crate::v1::WorkflowDefinition;
60use crate::v1::WorkflowHintsSection;
61
62/// A trait used to implement an AST visitor.
63///
64/// Each encountered node will receive a corresponding method call
65/// that receives both a [VisitReason::Enter] call and a
66/// matching [VisitReason::Exit] call.
67#[allow(unused_variables)]
68pub trait Visitor {
69    /// Represents the external visitation state.
70    type State;
71
72    /// Visits the root document node.
73    ///
74    /// A visitor must implement this method and response to
75    /// `VisitReason::Enter` with resetting any internal state so that a visitor
76    /// may be reused between documents.
77    fn document(
78        &mut self,
79        state: &mut Self::State,
80        reason: VisitReason,
81        doc: &Document,
82        version: SupportedVersion,
83    );
84
85    /// Visits a whitespace token.
86    fn whitespace(&mut self, state: &mut Self::State, whitespace: &Whitespace) {}
87
88    /// Visit a comment token.
89    fn comment(&mut self, state: &mut Self::State, comment: &Comment) {}
90
91    /// Visits a top-level version statement node.
92    fn version_statement(
93        &mut self,
94        state: &mut Self::State,
95        reason: VisitReason,
96        stmt: &VersionStatement,
97    ) {
98    }
99
100    /// Visits a top-level import statement node.
101    fn import_statement(
102        &mut self,
103        state: &mut Self::State,
104        reason: VisitReason,
105        stmt: &ImportStatement,
106    ) {
107    }
108
109    /// Visits a struct definition node.
110    fn struct_definition(
111        &mut self,
112        state: &mut Self::State,
113        reason: VisitReason,
114        def: &StructDefinition,
115    ) {
116    }
117
118    /// Visits a task definition node.
119    fn task_definition(
120        &mut self,
121        state: &mut Self::State,
122        reason: VisitReason,
123        task: &TaskDefinition,
124    ) {
125    }
126
127    /// Visits a workflow definition node.
128    fn workflow_definition(
129        &mut self,
130        state: &mut Self::State,
131        reason: VisitReason,
132        workflow: &WorkflowDefinition,
133    ) {
134    }
135
136    /// Visits an input section node.
137    fn input_section(
138        &mut self,
139        state: &mut Self::State,
140        reason: VisitReason,
141        section: &InputSection,
142    ) {
143    }
144
145    /// Visits an output section node.
146    fn output_section(
147        &mut self,
148        state: &mut Self::State,
149        reason: VisitReason,
150        section: &OutputSection,
151    ) {
152    }
153
154    /// Visits a command section node.
155    fn command_section(
156        &mut self,
157        state: &mut Self::State,
158        reason: VisitReason,
159        section: &CommandSection,
160    ) {
161    }
162
163    /// Visits a command text token in a command section node.
164    fn command_text(&mut self, state: &mut Self::State, text: &CommandText) {}
165
166    /// Visits a requirements section node.
167    fn requirements_section(
168        &mut self,
169        state: &mut Self::State,
170        reason: VisitReason,
171        section: &RequirementsSection,
172    ) {
173    }
174
175    /// Visits a task hints section node.
176    fn task_hints_section(
177        &mut self,
178        state: &mut Self::State,
179        reason: VisitReason,
180        section: &TaskHintsSection,
181    ) {
182    }
183
184    /// Visits a workflow hints section node.
185    fn workflow_hints_section(
186        &mut self,
187        state: &mut Self::State,
188        reason: VisitReason,
189        section: &WorkflowHintsSection,
190    ) {
191    }
192
193    /// Visits a runtime section node.
194    fn runtime_section(
195        &mut self,
196        state: &mut Self::State,
197        reason: VisitReason,
198        section: &RuntimeSection,
199    ) {
200    }
201
202    /// Visits a runtime item node.
203    fn runtime_item(&mut self, state: &mut Self::State, reason: VisitReason, item: &RuntimeItem) {}
204
205    /// Visits a metadata section node.
206    fn metadata_section(
207        &mut self,
208        state: &mut Self::State,
209        reason: VisitReason,
210        section: &MetadataSection,
211    ) {
212    }
213
214    /// Visits a parameter metadata section node.
215    fn parameter_metadata_section(
216        &mut self,
217        state: &mut Self::State,
218        reason: VisitReason,
219        section: &ParameterMetadataSection,
220    ) {
221    }
222
223    /// Visits a metadata object in a metadata or parameter metadata section.
224    fn metadata_object(
225        &mut self,
226        state: &mut Self::State,
227        reason: VisitReason,
228        object: &MetadataObject,
229    ) {
230    }
231
232    /// Visits a metadata object item in a metadata object.
233    fn metadata_object_item(
234        &mut self,
235        state: &mut Self::State,
236        reason: VisitReason,
237        item: &MetadataObjectItem,
238    ) {
239    }
240
241    /// Visits a metadata array node in a metadata or parameter metadata
242    /// section.
243    fn metadata_array(
244        &mut self,
245        state: &mut Self::State,
246        reason: VisitReason,
247        item: &MetadataArray,
248    ) {
249    }
250
251    /// Visits an unbound declaration node.
252    fn unbound_decl(&mut self, state: &mut Self::State, reason: VisitReason, decl: &UnboundDecl) {}
253
254    /// Visits a bound declaration node.
255    fn bound_decl(&mut self, state: &mut Self::State, reason: VisitReason, decl: &BoundDecl) {}
256
257    /// Visits an expression node.
258    fn expr(&mut self, state: &mut Self::State, reason: VisitReason, expr: &Expr) {}
259
260    /// Visits a string text token in a literal string node.
261    fn string_text(&mut self, state: &mut Self::State, text: &StringText) {}
262
263    /// Visits a placeholder node.
264    fn placeholder(
265        &mut self,
266        state: &mut Self::State,
267        reason: VisitReason,
268        placeholder: &Placeholder,
269    ) {
270    }
271
272    /// Visits a conditional statement node in a workflow.
273    fn conditional_statement(
274        &mut self,
275        state: &mut Self::State,
276        reason: VisitReason,
277        stmt: &ConditionalStatement,
278    ) {
279    }
280
281    /// Visits a scatter statement node in a workflow.
282    fn scatter_statement(
283        &mut self,
284        state: &mut Self::State,
285        reason: VisitReason,
286        stmt: &ScatterStatement,
287    ) {
288    }
289
290    /// Visits a call statement node in a workflow.
291    fn call_statement(
292        &mut self,
293        state: &mut Self::State,
294        reason: VisitReason,
295        stmt: &CallStatement,
296    ) {
297    }
298}
299
300/// Used to visit each descendant node of the given root in a preorder
301/// traversal.
302pub(crate) fn visit<V: Visitor>(root: &SyntaxNode, state: &mut V::State, visitor: &mut V) {
303    for event in root.preorder_with_tokens() {
304        let (reason, element) = match event {
305            WalkEvent::Enter(node) => (VisitReason::Enter, node),
306            WalkEvent::Leave(node) => (VisitReason::Exit, node),
307        };
308
309        match element.kind() {
310            SyntaxKind::RootNode => {
311                let document = Document(element.into_node().unwrap());
312
313                let version = document
314                    .version_statement()
315                    .and_then(|s| s.version().as_str().parse::<SupportedVersion>().ok())
316                    .expect("only WDL documents with supported versions can be visited");
317
318                visitor.document(state, reason, &document, version)
319            }
320            SyntaxKind::VersionStatementNode => visitor.version_statement(
321                state,
322                reason,
323                &VersionStatement(element.into_node().unwrap()),
324            ),
325            SyntaxKind::ImportStatementNode => visitor.import_statement(
326                state,
327                reason,
328                &ImportStatement(element.into_node().unwrap()),
329            ),
330            SyntaxKind::ImportAliasNode => {
331                // Skip these nodes as they're part of an import statement
332            }
333            SyntaxKind::StructDefinitionNode => visitor.struct_definition(
334                state,
335                reason,
336                &StructDefinition(element.into_node().unwrap()),
337            ),
338            SyntaxKind::TaskDefinitionNode => visitor.task_definition(
339                state,
340                reason,
341                &TaskDefinition(element.into_node().unwrap()),
342            ),
343            SyntaxKind::WorkflowDefinitionNode => visitor.workflow_definition(
344                state,
345                reason,
346                &WorkflowDefinition(element.into_node().unwrap()),
347            ),
348            SyntaxKind::UnboundDeclNode => {
349                visitor.unbound_decl(state, reason, &UnboundDecl(element.into_node().unwrap()))
350            }
351            SyntaxKind::BoundDeclNode => {
352                visitor.bound_decl(state, reason, &BoundDecl(element.into_node().unwrap()))
353            }
354            SyntaxKind::PrimitiveTypeNode
355            | SyntaxKind::MapTypeNode
356            | SyntaxKind::ArrayTypeNode
357            | SyntaxKind::PairTypeNode
358            | SyntaxKind::ObjectTypeNode
359            | SyntaxKind::TypeRefNode => {
360                // Skip these nodes as they're part of declarations
361            }
362            SyntaxKind::InputSectionNode => {
363                visitor.input_section(state, reason, &InputSection(element.into_node().unwrap()))
364            }
365            SyntaxKind::OutputSectionNode => {
366                visitor.output_section(state, reason, &OutputSection(element.into_node().unwrap()))
367            }
368            SyntaxKind::CommandSectionNode => visitor.command_section(
369                state,
370                reason,
371                &CommandSection(element.into_node().unwrap()),
372            ),
373            SyntaxKind::RequirementsSectionNode => visitor.requirements_section(
374                state,
375                reason,
376                &RequirementsSection(element.into_node().unwrap()),
377            ),
378            SyntaxKind::TaskHintsSectionNode => visitor.task_hints_section(
379                state,
380                reason,
381                &TaskHintsSection(element.into_node().unwrap()),
382            ),
383            SyntaxKind::WorkflowHintsSectionNode => visitor.workflow_hints_section(
384                state,
385                reason,
386                &WorkflowHintsSection(element.into_node().unwrap()),
387            ),
388            SyntaxKind::TaskHintsItemNode | SyntaxKind::WorkflowHintsItemNode => {
389                // Skip this node as it's part of a hints section
390            }
391            SyntaxKind::RequirementsItemNode => {
392                // Skip this node as it's part of a requirements section
393            }
394            SyntaxKind::RuntimeSectionNode => visitor.runtime_section(
395                state,
396                reason,
397                &RuntimeSection(element.into_node().unwrap()),
398            ),
399            SyntaxKind::RuntimeItemNode => {
400                visitor.runtime_item(state, reason, &RuntimeItem(element.into_node().unwrap()))
401            }
402            SyntaxKind::MetadataSectionNode => visitor.metadata_section(
403                state,
404                reason,
405                &MetadataSection(element.into_node().unwrap()),
406            ),
407            SyntaxKind::ParameterMetadataSectionNode => visitor.parameter_metadata_section(
408                state,
409                reason,
410                &ParameterMetadataSection(element.into_node().unwrap()),
411            ),
412            SyntaxKind::MetadataObjectNode => visitor.metadata_object(
413                state,
414                reason,
415                &MetadataObject(element.into_node().unwrap()),
416            ),
417            SyntaxKind::MetadataObjectItemNode => visitor.metadata_object_item(
418                state,
419                reason,
420                &MetadataObjectItem(element.into_node().unwrap()),
421            ),
422            SyntaxKind::MetadataArrayNode => {
423                visitor.metadata_array(state, reason, &MetadataArray(element.into_node().unwrap()))
424            }
425            SyntaxKind::LiteralNullNode => {
426                // Skip these nodes as they're part of a metadata section
427            }
428            k if Expr::can_cast(k) => {
429                visitor.expr(
430                    state,
431                    reason,
432                    &Expr::cast(element.into_node().expect(
433                        "any element that is able to be turned into an expr should be a node",
434                    ))
435                    .expect("expr should be built"),
436                )
437            }
438            SyntaxKind::LiteralMapItemNode
439            | SyntaxKind::LiteralObjectItemNode
440            | SyntaxKind::LiteralStructItemNode
441            | SyntaxKind::LiteralHintsItemNode
442            | SyntaxKind::LiteralInputItemNode
443            | SyntaxKind::LiteralOutputItemNode => {
444                // Skip these nodes as they're part of literal expressions
445            }
446            k @ (SyntaxKind::LiteralIntegerNode
447            | SyntaxKind::LiteralFloatNode
448            | SyntaxKind::LiteralBooleanNode
449            | SyntaxKind::LiteralNoneNode
450            | SyntaxKind::LiteralStringNode
451            | SyntaxKind::LiteralPairNode
452            | SyntaxKind::LiteralArrayNode
453            | SyntaxKind::LiteralMapNode
454            | SyntaxKind::LiteralObjectNode
455            | SyntaxKind::LiteralStructNode
456            | SyntaxKind::LiteralHintsNode
457            | SyntaxKind::LiteralInputNode
458            | SyntaxKind::LiteralOutputNode
459            | SyntaxKind::ParenthesizedExprNode
460            | SyntaxKind::NameRefNode
461            | SyntaxKind::IfExprNode
462            | SyntaxKind::LogicalNotExprNode
463            | SyntaxKind::NegationExprNode
464            | SyntaxKind::LogicalOrExprNode
465            | SyntaxKind::LogicalAndExprNode
466            | SyntaxKind::EqualityExprNode
467            | SyntaxKind::InequalityExprNode
468            | SyntaxKind::LessExprNode
469            | SyntaxKind::LessEqualExprNode
470            | SyntaxKind::GreaterExprNode
471            | SyntaxKind::GreaterEqualExprNode
472            | SyntaxKind::AdditionExprNode
473            | SyntaxKind::SubtractionExprNode
474            | SyntaxKind::MultiplicationExprNode
475            | SyntaxKind::DivisionExprNode
476            | SyntaxKind::ModuloExprNode
477            | SyntaxKind::CallExprNode
478            | SyntaxKind::IndexExprNode
479            | SyntaxKind::AccessExprNode) => {
480                unreachable!("`{k:?}` should be handled by `Expr::can_cast`")
481            }
482            SyntaxKind::PlaceholderNode => {
483                visitor.placeholder(state, reason, &Placeholder(element.into_node().unwrap()))
484            }
485            SyntaxKind::PlaceholderSepOptionNode
486            | SyntaxKind::PlaceholderDefaultOptionNode
487            | SyntaxKind::PlaceholderTrueFalseOptionNode => {
488                // Skip these nodes as they're part of a placeholder
489            }
490            SyntaxKind::ConditionalStatementNode => visitor.conditional_statement(
491                state,
492                reason,
493                &ConditionalStatement(element.into_node().unwrap()),
494            ),
495            SyntaxKind::ScatterStatementNode => visitor.scatter_statement(
496                state,
497                reason,
498                &ScatterStatement(element.into_node().unwrap()),
499            ),
500            SyntaxKind::CallStatementNode => {
501                visitor.call_statement(state, reason, &CallStatement(element.into_node().unwrap()))
502            }
503            SyntaxKind::CallTargetNode
504            | SyntaxKind::CallAliasNode
505            | SyntaxKind::CallAfterNode
506            | SyntaxKind::CallInputItemNode => {
507                // Skip these nodes as they're part of a call statement
508            }
509            SyntaxKind::Abandoned | SyntaxKind::MAX => {
510                unreachable!("node should not exist in the tree")
511            }
512            SyntaxKind::Whitespace if reason == VisitReason::Enter => {
513                visitor.whitespace(state, &Whitespace(element.into_token().unwrap()))
514            }
515            SyntaxKind::Comment if reason == VisitReason::Enter => {
516                visitor.comment(state, &Comment(element.into_token().unwrap()))
517            }
518            SyntaxKind::LiteralStringText if reason == VisitReason::Enter => {
519                visitor.string_text(state, &StringText(element.into_token().unwrap()))
520            }
521            SyntaxKind::LiteralCommandText if reason == VisitReason::Enter => {
522                visitor.command_text(state, &CommandText(element.into_token().unwrap()))
523            }
524            _ => {
525                // Skip remaining tokens
526            }
527        }
528    }
529}