1use rowan::WalkEvent;
25use wdl_ast::AstNode;
26use wdl_ast::AstToken;
27use wdl_ast::Comment;
28use wdl_ast::Document as AstDocument;
29use wdl_ast::SupportedVersion;
30use wdl_ast::SyntaxKind;
31use wdl_ast::SyntaxNode;
32use wdl_ast::VersionStatement;
33use wdl_ast::Whitespace;
34use wdl_ast::v1::BoundDecl;
35use wdl_ast::v1::CallStatement;
36use wdl_ast::v1::CommandSection;
37use wdl_ast::v1::CommandText;
38use wdl_ast::v1::ConditionalStatement;
39use wdl_ast::v1::Expr;
40use wdl_ast::v1::ImportStatement;
41use wdl_ast::v1::InputSection;
42use wdl_ast::v1::MetadataArray;
43use wdl_ast::v1::MetadataObject;
44use wdl_ast::v1::MetadataObjectItem;
45use wdl_ast::v1::MetadataSection;
46use wdl_ast::v1::OutputSection;
47use wdl_ast::v1::ParameterMetadataSection;
48use wdl_ast::v1::Placeholder;
49use wdl_ast::v1::RequirementsSection;
50use wdl_ast::v1::RuntimeItem;
51use wdl_ast::v1::RuntimeSection;
52use wdl_ast::v1::ScatterStatement;
53use wdl_ast::v1::StringText;
54use wdl_ast::v1::StructDefinition;
55use wdl_ast::v1::TaskDefinition;
56use wdl_ast::v1::TaskHintsSection;
57use wdl_ast::v1::UnboundDecl;
58use wdl_ast::v1::WorkflowDefinition;
59use wdl_ast::v1::WorkflowHintsSection;
60
61use crate::Diagnostics;
62use crate::document::Document as AnalysisDocument;
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
69pub enum VisitReason {
70 Enter,
72 Exit,
74}
75
76#[allow(unused_variables)]
82pub trait Visitor {
83 fn reset(&mut self);
88
89 fn document(
91 &mut self,
92 diagnostics: &mut Diagnostics,
93 reason: VisitReason,
94 doc: &AnalysisDocument,
95 version: SupportedVersion,
96 ) {
97 }
98
99 fn whitespace(&mut self, diagnostics: &mut Diagnostics, whitespace: &Whitespace) {}
101
102 fn comment(&mut self, diagnostics: &mut Diagnostics, comment: &Comment) {}
104
105 fn version_statement(
107 &mut self,
108 diagnostics: &mut Diagnostics,
109 reason: VisitReason,
110 stmt: &VersionStatement,
111 ) {
112 }
113
114 fn import_statement(
116 &mut self,
117 diagnostics: &mut Diagnostics,
118 reason: VisitReason,
119 stmt: &ImportStatement,
120 ) {
121 }
122
123 fn struct_definition(
125 &mut self,
126 diagnostics: &mut Diagnostics,
127 reason: VisitReason,
128 def: &StructDefinition,
129 ) {
130 }
131
132 fn task_definition(
134 &mut self,
135 diagnostics: &mut Diagnostics,
136 reason: VisitReason,
137 task: &TaskDefinition,
138 ) {
139 }
140
141 fn workflow_definition(
143 &mut self,
144 diagnostics: &mut Diagnostics,
145 reason: VisitReason,
146 workflow: &WorkflowDefinition,
147 ) {
148 }
149
150 fn input_section(
152 &mut self,
153 diagnostics: &mut Diagnostics,
154 reason: VisitReason,
155 section: &InputSection,
156 ) {
157 }
158
159 fn output_section(
161 &mut self,
162 diagnostics: &mut Diagnostics,
163 reason: VisitReason,
164 section: &OutputSection,
165 ) {
166 }
167
168 fn command_section(
170 &mut self,
171 diagnostics: &mut Diagnostics,
172 reason: VisitReason,
173 section: &CommandSection,
174 ) {
175 }
176
177 fn command_text(&mut self, diagnostics: &mut Diagnostics, text: &CommandText) {}
179
180 fn requirements_section(
182 &mut self,
183 diagnostics: &mut Diagnostics,
184 reason: VisitReason,
185 section: &RequirementsSection,
186 ) {
187 }
188
189 fn task_hints_section(
191 &mut self,
192 diagnostics: &mut Diagnostics,
193 reason: VisitReason,
194 section: &TaskHintsSection,
195 ) {
196 }
197
198 fn workflow_hints_section(
200 &mut self,
201 diagnostics: &mut Diagnostics,
202 reason: VisitReason,
203 section: &WorkflowHintsSection,
204 ) {
205 }
206
207 fn runtime_section(
209 &mut self,
210 diagnostics: &mut Diagnostics,
211 reason: VisitReason,
212 section: &RuntimeSection,
213 ) {
214 }
215
216 fn runtime_item(
218 &mut self,
219 diagnostics: &mut Diagnostics,
220 reason: VisitReason,
221 item: &RuntimeItem,
222 ) {
223 }
224
225 fn metadata_section(
227 &mut self,
228 diagnostics: &mut Diagnostics,
229 reason: VisitReason,
230 section: &MetadataSection,
231 ) {
232 }
233
234 fn parameter_metadata_section(
236 &mut self,
237 diagnostics: &mut Diagnostics,
238 reason: VisitReason,
239 section: &ParameterMetadataSection,
240 ) {
241 }
242
243 fn metadata_object(
245 &mut self,
246 diagnostics: &mut Diagnostics,
247 reason: VisitReason,
248 object: &MetadataObject,
249 ) {
250 }
251
252 fn metadata_object_item(
254 &mut self,
255 diagnostics: &mut Diagnostics,
256 reason: VisitReason,
257 item: &MetadataObjectItem,
258 ) {
259 }
260
261 fn metadata_array(
264 &mut self,
265 diagnostics: &mut Diagnostics,
266 reason: VisitReason,
267 item: &MetadataArray,
268 ) {
269 }
270
271 fn unbound_decl(
273 &mut self,
274 diagnostics: &mut Diagnostics,
275 reason: VisitReason,
276 decl: &UnboundDecl,
277 ) {
278 }
279
280 fn bound_decl(&mut self, diagnostics: &mut Diagnostics, reason: VisitReason, decl: &BoundDecl) {
282 }
283
284 fn expr(&mut self, diagnostics: &mut Diagnostics, reason: VisitReason, expr: &Expr) {}
286
287 fn string_text(&mut self, diagnostics: &mut Diagnostics, text: &StringText) {}
289
290 fn placeholder(
292 &mut self,
293 diagnostics: &mut Diagnostics,
294 reason: VisitReason,
295 placeholder: &Placeholder,
296 ) {
297 }
298
299 fn conditional_statement(
301 &mut self,
302 diagnostics: &mut Diagnostics,
303 reason: VisitReason,
304 stmt: &ConditionalStatement,
305 ) {
306 }
307
308 fn scatter_statement(
310 &mut self,
311 diagnostics: &mut Diagnostics,
312 reason: VisitReason,
313 stmt: &ScatterStatement,
314 ) {
315 }
316
317 fn call_statement(
319 &mut self,
320 diagnostics: &mut Diagnostics,
321 reason: VisitReason,
322 stmt: &CallStatement,
323 ) {
324 }
325}
326
327pub(crate) fn visit<V: Visitor>(
330 document: &AnalysisDocument,
331 diagnostics: &mut Diagnostics,
332 visitor: &mut V,
333) {
334 for event in document.root().inner().preorder_with_tokens() {
335 let (reason, element) = match event {
336 WalkEvent::Enter(node) => (VisitReason::Enter, node),
337 WalkEvent::Leave(node) => (VisitReason::Exit, node),
338 };
339
340 match element.kind() {
341 SyntaxKind::RootNode => {
342 let ast_document = AstDocument::cast(element.into_node().unwrap())
343 .expect("root node should be a document");
344
345 let version = ast_document
346 .version_statement()
347 .and_then(|s| s.version().text().parse::<SupportedVersion>().ok())
348 .expect("only WDL documents with supported versions can be visited");
349
350 visitor.document(diagnostics, reason, document, version)
351 }
352 SyntaxKind::VersionStatementNode => visitor.version_statement(
353 diagnostics,
354 reason,
355 &VersionStatement::cast(element.into_node().unwrap()).expect("should cast"),
356 ),
357 SyntaxKind::ImportStatementNode => visitor.import_statement(
358 diagnostics,
359 reason,
360 &ImportStatement::cast(element.into_node().unwrap()).expect("should cast"),
361 ),
362 SyntaxKind::ImportAliasNode => {
363 }
365 SyntaxKind::StructDefinitionNode => visitor.struct_definition(
366 diagnostics,
367 reason,
368 &StructDefinition::cast(element.into_node().unwrap()).expect("should cast"),
369 ),
370 SyntaxKind::TaskDefinitionNode => visitor.task_definition(
371 diagnostics,
372 reason,
373 &TaskDefinition::cast(element.into_node().unwrap()).expect("should cast"),
374 ),
375 SyntaxKind::WorkflowDefinitionNode => visitor.workflow_definition(
376 diagnostics,
377 reason,
378 &WorkflowDefinition::cast(element.into_node().unwrap()).expect("should cast"),
379 ),
380 SyntaxKind::UnboundDeclNode => visitor.unbound_decl(
381 diagnostics,
382 reason,
383 &UnboundDecl::cast(element.into_node().unwrap()).expect("should cast"),
384 ),
385 SyntaxKind::BoundDeclNode => visitor.bound_decl(
386 diagnostics,
387 reason,
388 &BoundDecl::cast(element.into_node().unwrap()).expect("should cast"),
389 ),
390 SyntaxKind::PrimitiveTypeNode
391 | SyntaxKind::MapTypeNode
392 | SyntaxKind::ArrayTypeNode
393 | SyntaxKind::PairTypeNode
394 | SyntaxKind::ObjectTypeNode
395 | SyntaxKind::TypeRefNode => {
396 }
398 SyntaxKind::InputSectionNode => visitor.input_section(
399 diagnostics,
400 reason,
401 &InputSection::cast(element.into_node().unwrap()).expect("should cast"),
402 ),
403 SyntaxKind::OutputSectionNode => visitor.output_section(
404 diagnostics,
405 reason,
406 &OutputSection::cast(element.into_node().unwrap()).expect("should cast"),
407 ),
408 SyntaxKind::CommandSectionNode => visitor.command_section(
409 diagnostics,
410 reason,
411 &CommandSection::cast(element.into_node().unwrap()).expect("should cast"),
412 ),
413 SyntaxKind::RequirementsSectionNode => visitor.requirements_section(
414 diagnostics,
415 reason,
416 &RequirementsSection::cast(element.into_node().unwrap()).expect("should cast"),
417 ),
418 SyntaxKind::TaskHintsSectionNode => visitor.task_hints_section(
419 diagnostics,
420 reason,
421 &TaskHintsSection::cast(element.into_node().unwrap()).expect("should cast"),
422 ),
423 SyntaxKind::WorkflowHintsSectionNode => visitor.workflow_hints_section(
424 diagnostics,
425 reason,
426 &WorkflowHintsSection::cast(element.into_node().unwrap()).expect("should cast"),
427 ),
428 SyntaxKind::TaskHintsItemNode | SyntaxKind::WorkflowHintsItemNode => {
429 }
431 SyntaxKind::RequirementsItemNode => {
432 }
434 SyntaxKind::RuntimeSectionNode => visitor.runtime_section(
435 diagnostics,
436 reason,
437 &RuntimeSection::cast(element.into_node().unwrap()).expect("should cast"),
438 ),
439 SyntaxKind::RuntimeItemNode => visitor.runtime_item(
440 diagnostics,
441 reason,
442 &RuntimeItem::cast(element.into_node().unwrap()).expect("should cast"),
443 ),
444 SyntaxKind::MetadataSectionNode => visitor.metadata_section(
445 diagnostics,
446 reason,
447 &MetadataSection::cast(element.into_node().unwrap()).expect("should cast"),
448 ),
449 SyntaxKind::ParameterMetadataSectionNode => visitor.parameter_metadata_section(
450 diagnostics,
451 reason,
452 &ParameterMetadataSection::cast(element.into_node().unwrap()).expect("should cast"),
453 ),
454 SyntaxKind::MetadataObjectNode => visitor.metadata_object(
455 diagnostics,
456 reason,
457 &MetadataObject::cast(element.into_node().unwrap()).expect("should cast"),
458 ),
459 SyntaxKind::MetadataObjectItemNode => visitor.metadata_object_item(
460 diagnostics,
461 reason,
462 &MetadataObjectItem::cast(element.into_node().unwrap()).expect("should cast"),
463 ),
464 SyntaxKind::MetadataArrayNode => visitor.metadata_array(
465 diagnostics,
466 reason,
467 &MetadataArray::cast(element.into_node().unwrap()).expect("should cast"),
468 ),
469 SyntaxKind::LiteralNullNode => {
470 }
472 k if Expr::<SyntaxNode>::can_cast(k) => {
473 visitor.expr(
474 diagnostics,
475 reason,
476 &Expr::cast(element.into_node().expect(
477 "any element that is able to be turned into an expr should be a node",
478 ))
479 .expect("expr should be built"),
480 )
481 }
482 SyntaxKind::LiteralMapItemNode
483 | SyntaxKind::LiteralObjectItemNode
484 | SyntaxKind::LiteralStructItemNode
485 | SyntaxKind::LiteralHintsItemNode
486 | SyntaxKind::LiteralInputItemNode
487 | SyntaxKind::LiteralOutputItemNode => {
488 }
490 k @ (SyntaxKind::LiteralIntegerNode
491 | SyntaxKind::LiteralFloatNode
492 | SyntaxKind::LiteralBooleanNode
493 | SyntaxKind::LiteralNoneNode
494 | SyntaxKind::LiteralStringNode
495 | SyntaxKind::LiteralPairNode
496 | SyntaxKind::LiteralArrayNode
497 | SyntaxKind::LiteralMapNode
498 | SyntaxKind::LiteralObjectNode
499 | SyntaxKind::LiteralStructNode
500 | SyntaxKind::LiteralHintsNode
501 | SyntaxKind::LiteralInputNode
502 | SyntaxKind::LiteralOutputNode
503 | SyntaxKind::ParenthesizedExprNode
504 | SyntaxKind::NameRefExprNode
505 | SyntaxKind::IfExprNode
506 | SyntaxKind::LogicalNotExprNode
507 | SyntaxKind::NegationExprNode
508 | SyntaxKind::LogicalOrExprNode
509 | SyntaxKind::LogicalAndExprNode
510 | SyntaxKind::EqualityExprNode
511 | SyntaxKind::InequalityExprNode
512 | SyntaxKind::LessExprNode
513 | SyntaxKind::LessEqualExprNode
514 | SyntaxKind::GreaterExprNode
515 | SyntaxKind::GreaterEqualExprNode
516 | SyntaxKind::AdditionExprNode
517 | SyntaxKind::SubtractionExprNode
518 | SyntaxKind::MultiplicationExprNode
519 | SyntaxKind::DivisionExprNode
520 | SyntaxKind::ModuloExprNode
521 | SyntaxKind::CallExprNode
522 | SyntaxKind::IndexExprNode
523 | SyntaxKind::AccessExprNode) => {
524 unreachable!("`{k:?}` should be handled by `Expr::can_cast`")
525 }
526 SyntaxKind::PlaceholderNode => visitor.placeholder(
527 diagnostics,
528 reason,
529 &Placeholder::cast(element.into_node().unwrap()).expect("should cast"),
530 ),
531 SyntaxKind::PlaceholderSepOptionNode
532 | SyntaxKind::PlaceholderDefaultOptionNode
533 | SyntaxKind::PlaceholderTrueFalseOptionNode => {
534 }
536 SyntaxKind::ConditionalStatementNode => visitor.conditional_statement(
537 diagnostics,
538 reason,
539 &ConditionalStatement::cast(element.into_node().unwrap()).expect("should cast"),
540 ),
541 SyntaxKind::ScatterStatementNode => visitor.scatter_statement(
542 diagnostics,
543 reason,
544 &ScatterStatement::cast(element.into_node().unwrap()).expect("should cast"),
545 ),
546 SyntaxKind::CallStatementNode => visitor.call_statement(
547 diagnostics,
548 reason,
549 &CallStatement::cast(element.into_node().unwrap()).expect("should cast"),
550 ),
551 SyntaxKind::CallTargetNode
552 | SyntaxKind::CallAliasNode
553 | SyntaxKind::CallAfterNode
554 | SyntaxKind::CallInputItemNode => {
555 }
557 SyntaxKind::Abandoned | SyntaxKind::MAX => {
558 unreachable!("node should not exist in the tree")
559 }
560 SyntaxKind::Whitespace if reason == VisitReason::Enter => visitor.whitespace(
561 diagnostics,
562 &Whitespace::cast(element.into_token().unwrap()).expect("should cast"),
563 ),
564 SyntaxKind::Comment if reason == VisitReason::Enter => visitor.comment(
565 diagnostics,
566 &Comment::cast(element.into_token().unwrap()).expect("should cast"),
567 ),
568 SyntaxKind::LiteralStringText if reason == VisitReason::Enter => visitor.string_text(
569 diagnostics,
570 &StringText::cast(element.into_token().unwrap()).expect("should cast"),
571 ),
572 SyntaxKind::LiteralCommandText if reason == VisitReason::Enter => visitor.command_text(
573 diagnostics,
574 &CommandText::cast(element.into_token().unwrap()).expect("should cast"),
575 ),
576 _ => {
577 }
579 }
580 }
581}