1#![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::collections::HashSet;
34use std::fmt;
35use std::str::FromStr;
36
37pub use rowan::Direction;
38use rowan::NodeOrToken;
39use v1::CloseBrace;
40use v1::CloseHeredoc;
41use v1::OpenBrace;
42use v1::OpenHeredoc;
43pub use wdl_grammar::Diagnostic;
44pub use wdl_grammar::Label;
45pub use wdl_grammar::Severity;
46pub use wdl_grammar::Span;
47pub use wdl_grammar::SupportedVersion;
48pub use wdl_grammar::SyntaxElement;
49pub use wdl_grammar::SyntaxKind;
50pub use wdl_grammar::SyntaxNode;
51pub use wdl_grammar::SyntaxToken;
52pub use wdl_grammar::SyntaxTokenExt;
53pub use wdl_grammar::SyntaxTree;
54pub use wdl_grammar::WorkflowDescriptionLanguage;
55pub use wdl_grammar::lexer;
56pub use wdl_grammar::version;
57
58pub mod v1;
59
60mod element;
61
62pub use element::*;
63
64pub trait Documented<N: TreeNode>: AstNode<N> {
66 fn doc_comments(&self) -> Option<Vec<Comment<N::Token>>>;
73}
74
75pub fn doc_comments<N: TreeNode>(
77 preceding_trivia: impl IntoIterator<Item = N::Token>,
78) -> impl Iterator<Item = Comment<N::Token>> {
79 preceding_trivia
80 .into_iter()
81 .take_while(|token| {
82 token.kind() == SyntaxKind::Whitespace || token.kind() == SyntaxKind::Comment
83 })
84 .filter_map(|token| {
85 if token.kind() == SyntaxKind::Comment && token.text().starts_with(DOC_COMMENT_PREFIX) {
86 Some(Comment::<N::Token>::cast(token).expect("should be a comment"))
87 } else {
88 None
89 }
90 })
91}
92
93pub trait TreeNode: Clone + fmt::Debug + PartialEq + Eq + std::hash::Hash {
97 type Token: TreeToken;
99
100 fn parent(&self) -> Option<Self>;
104
105 fn kind(&self) -> SyntaxKind;
107
108 fn text(&self) -> impl fmt::Display;
112
113 fn span(&self) -> Span;
115
116 fn children(&self) -> impl Iterator<Item = Self>;
118
119 fn children_with_tokens(&self) -> impl Iterator<Item = NodeOrToken<Self, Self::Token>>;
121
122 fn first_token(&self) -> Option<Self::Token>;
124
125 fn last_token(&self) -> Option<Self::Token>;
127
128 fn descendants(&self) -> impl Iterator<Item = Self>;
130
131 fn ancestors(&self) -> impl Iterator<Item = Self>;
133}
134
135pub trait TreeToken: Clone + fmt::Debug + PartialEq + Eq + std::hash::Hash {
137 type Node: TreeNode;
139
140 fn parent(&self) -> Self::Node;
142
143 fn kind(&self) -> SyntaxKind;
145
146 fn text(&self) -> &str;
148
149 fn span(&self) -> Span;
151}
152
153pub trait AstNode<N: TreeNode>: Sized {
155 fn can_cast(kind: SyntaxKind) -> bool;
157
158 fn cast(inner: N) -> Option<Self>;
160
161 fn inner(&self) -> &N;
163
164 fn kind(&self) -> SyntaxKind {
166 self.inner().kind()
167 }
168
169 fn text<'a>(&'a self) -> impl fmt::Display
174 where
175 N: 'a,
176 {
177 self.inner().text()
178 }
179
180 fn span(&self) -> Span {
182 self.inner().span()
183 }
184
185 fn token<C>(&self) -> Option<C>
187 where
188 C: AstToken<N::Token>,
189 {
190 self.inner()
191 .children_with_tokens()
192 .filter_map(|e| e.into_token())
193 .find_map(|t| C::cast(t))
194 }
195
196 fn tokens<'a, C>(&'a self) -> impl Iterator<Item = C>
198 where
199 C: AstToken<N::Token>,
200 N: 'a,
201 {
202 self.inner()
203 .children_with_tokens()
204 .filter_map(|e| e.into_token().and_then(C::cast))
205 }
206
207 fn last_token<C>(&self) -> Option<C>
213 where
214 C: AstToken<N::Token>,
215 {
216 self.inner().last_token().and_then(C::cast)
217 }
218
219 fn child<C>(&self) -> Option<C>
221 where
222 C: AstNode<N>,
223 {
224 self.inner().children().find_map(C::cast)
225 }
226
227 fn children<'a, C>(&'a self) -> impl Iterator<Item = C>
229 where
230 C: AstNode<N>,
231 N: 'a,
232 {
233 self.inner().children().filter_map(C::cast)
234 }
235
236 fn parent<'a, P>(&self) -> Option<P>
241 where
242 P: AstNode<N>,
243 N: 'a,
244 {
245 P::cast(self.inner().parent()?)
246 }
247
248 fn scope_span<O, C>(&self) -> Option<Span>
254 where
255 O: AstToken<N::Token>,
256 C: AstToken<N::Token>,
257 {
258 let open = self.token::<O>()?.span();
259 let close = self.last_token::<C>()?.span();
260
261 Some(Span::new(open.end(), close.end() - open.end()))
263 }
264
265 fn braced_scope_span(&self) -> Option<Span> {
273 self.scope_span::<OpenBrace<N::Token>, CloseBrace<N::Token>>()
274 }
275
276 fn heredoc_scope_span(&self) -> Option<Span> {
284 self.scope_span::<OpenHeredoc<N::Token>, CloseHeredoc<N::Token>>()
285 }
286
287 fn descendants<'a, D>(&'a self) -> impl Iterator<Item = D>
290 where
291 D: AstNode<N>,
292 N: 'a,
293 {
294 self.inner().descendants().filter_map(|d| D::cast(d))
295 }
296}
297
298pub trait AstToken<T: TreeToken>: Sized {
300 fn can_cast(kind: SyntaxKind) -> bool;
302
303 fn cast(inner: T) -> Option<Self>;
305
306 fn inner(&self) -> &T;
308
309 fn kind(&self) -> SyntaxKind {
311 self.inner().kind()
312 }
313
314 fn text<'a>(&'a self) -> &'a str
316 where
317 T: 'a,
318 {
319 self.inner().text()
320 }
321
322 fn span(&self) -> Span {
324 self.inner().span()
325 }
326
327 fn parent<'a, P>(&self) -> Option<P>
331 where
332 P: AstNode<T::Node>,
333 T: 'a,
334 {
335 P::cast(self.inner().parent())
336 }
337}
338
339pub trait NewRoot<N: TreeNode>: Sized {
342 fn new_root(root: N) -> Self;
345}
346
347impl TreeNode for SyntaxNode {
348 type Token = SyntaxToken;
349
350 fn parent(&self) -> Option<SyntaxNode> {
351 self.parent()
352 }
353
354 fn kind(&self) -> SyntaxKind {
355 self.kind()
356 }
357
358 fn children(&self) -> impl Iterator<Item = Self> {
359 self.children()
360 }
361
362 fn children_with_tokens(&self) -> impl Iterator<Item = NodeOrToken<Self, Self::Token>> {
363 self.children_with_tokens()
364 }
365
366 fn text(&self) -> impl fmt::Display {
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 fn first_token(&self) -> Option<Self::Token> {
377 self.first_token()
378 }
379
380 fn last_token(&self) -> Option<Self::Token> {
381 self.last_token()
382 }
383
384 fn descendants(&self) -> impl Iterator<Item = Self> {
385 self.descendants()
386 }
387
388 fn ancestors(&self) -> impl Iterator<Item = Self> {
389 self.ancestors()
390 }
391}
392
393impl TreeToken for SyntaxToken {
394 type Node = SyntaxNode;
395
396 fn parent(&self) -> SyntaxNode {
397 self.parent().expect("token should have a parent")
398 }
399
400 fn kind(&self) -> SyntaxKind {
401 self.kind()
402 }
403
404 fn text(&self) -> &str {
405 self.text()
406 }
407
408 fn span(&self) -> Span {
409 let range = self.text_range();
410 let start = usize::from(range.start());
411 Span::new(start, usize::from(range.end()) - start)
412 }
413}
414
415#[derive(Clone, Debug, PartialEq, Eq)]
419pub enum Ast<N: TreeNode = SyntaxNode> {
420 Unsupported,
422 V1(v1::Ast<N>),
424}
425
426impl<N: TreeNode> Ast<N> {
427 pub fn as_v1(&self) -> Option<&v1::Ast<N>> {
431 match self {
432 Self::V1(ast) => Some(ast),
433 _ => None,
434 }
435 }
436
437 pub fn into_v1(self) -> Option<v1::Ast<N>> {
439 match self {
440 Self::V1(ast) => Some(ast),
441 _ => None,
442 }
443 }
444
445 pub fn unwrap_v1(self) -> v1::Ast<N> {
451 self.into_v1().expect("the AST is not a V1 AST")
452 }
453}
454
455#[derive(Clone, PartialEq, Eq, Hash)]
460pub struct Document<N: TreeNode = SyntaxNode>(N);
461
462impl<N: TreeNode> AstNode<N> for Document<N> {
463 fn can_cast(kind: SyntaxKind) -> bool {
464 kind == SyntaxKind::RootNode
465 }
466
467 fn cast(inner: N) -> Option<Self> {
468 if Self::can_cast(inner.kind()) {
469 Some(Self(inner))
470 } else {
471 None
472 }
473 }
474
475 fn inner(&self) -> &N {
476 &self.0
477 }
478}
479
480impl Document {
481 pub fn parse(source: &str) -> (Self, Vec<Diagnostic>) {
509 let (tree, diagnostics) = SyntaxTree::parse(source);
510 (
511 Document::cast(tree.into_syntax()).expect("document should cast"),
512 diagnostics,
513 )
514 }
515}
516
517impl<N: TreeNode> Document<N> {
518 pub fn version_statement(&self) -> Option<VersionStatement<N>> {
525 self.child()
526 }
527
528 pub fn ast(&self) -> Ast<N> {
530 self.ast_with_version_fallback(None)
531 }
532
533 pub fn ast_with_version_fallback(&self, fallback_version: Option<SupportedVersion>) -> Ast<N> {
550 let Some(stmt) = self.version_statement() else {
551 return Ast::Unsupported;
552 };
553 let Some(version) = stmt
556 .version()
557 .text()
558 .parse::<SupportedVersion>()
559 .ok()
560 .or(fallback_version)
561 else {
562 return Ast::Unsupported;
563 };
564 match version {
565 SupportedVersion::V1(_) => Ast::V1(v1::Ast(self.0.clone())),
566 _ => Ast::Unsupported,
567 }
568 }
569
570 pub fn morph<U: TreeNode + NewRoot<N>>(self) -> Document<U> {
573 Document(U::new_root(self.0))
574 }
575}
576
577impl fmt::Debug for Document {
578 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579 self.0.fmt(f)
580 }
581}
582
583#[derive(Clone, Debug, PartialEq, Eq, Hash)]
585pub struct Whitespace<T: TreeToken = SyntaxToken>(T);
586
587impl<T: TreeToken> AstToken<T> for Whitespace<T> {
588 fn can_cast(kind: SyntaxKind) -> bool {
589 kind == SyntaxKind::Whitespace
590 }
591
592 fn cast(inner: T) -> Option<Self> {
593 match inner.kind() {
594 SyntaxKind::Whitespace => Some(Self(inner)),
595 _ => None,
596 }
597 }
598
599 fn inner(&self) -> &T {
600 &self.0
601 }
602}
603
604pub const DIRECTIVE_COMMENT_PREFIX: &str = "#@";
606pub const DIRECTIVE_DELIMITER: &str = ":";
608
609#[derive(Debug, PartialEq, Eq)]
611pub enum Directive {
612 Except(HashSet<String>),
614}
615
616impl FromStr for Directive {
617 type Err = ();
618
619 fn from_str(s: &str) -> Result<Self, Self::Err> {
620 let s = s.strip_prefix(DIRECTIVE_COMMENT_PREFIX).ok_or(())?;
621 let (directive, contents) = s.trim().split_once(DIRECTIVE_DELIMITER).ok_or(())?;
622 match directive.trim_end() {
623 "except" => Ok(Self::Except(HashSet::from_iter(
624 contents.split(',').map(|id| id.trim().to_string()),
625 ))),
626 _ => Err(()),
627 }
628 }
629}
630
631pub const DOC_COMMENT_PREFIX: &str = "##";
633
634#[derive(Debug, Clone, PartialEq, Eq, Hash)]
636pub struct Comment<T: TreeToken = SyntaxToken>(T);
637
638impl<T: TreeToken> AstToken<T> for Comment<T> {
639 fn can_cast(kind: SyntaxKind) -> bool {
640 kind == SyntaxKind::Comment
641 }
642
643 fn cast(inner: T) -> Option<Self> {
644 match inner.kind() {
645 SyntaxKind::Comment => Some(Self(inner)),
646 _ => None,
647 }
648 }
649
650 fn inner(&self) -> &T {
651 &self.0
652 }
653}
654
655impl Comment {
656 pub fn is_directive(&self) -> bool {
658 self.text().starts_with(DIRECTIVE_COMMENT_PREFIX)
659 }
660
661 pub fn directive(&self) -> Option<Directive> {
663 self.text().parse::<Directive>().ok()
664 }
665
666 pub fn is_doc_comment(&self) -> bool {
668 self.text().starts_with(DOC_COMMENT_PREFIX)
669 }
670
671 pub fn is_inline_comment(&self) -> bool {
673 if let Some(prev) = self.inner().prev_sibling_or_token() {
676 if prev.kind() == SyntaxKind::Whitespace {
677 !prev
678 .into_token()
679 .expect("SyntaxKind::Whitespace is a token")
680 .text()
681 .contains('\n')
682 } else {
683 true
684 }
685 } else {
686 false
687 }
688 }
689}
690
691#[derive(Debug, Clone, PartialEq, Eq, Hash)]
693pub struct VersionStatement<N: TreeNode = SyntaxNode>(N);
694
695impl<N: TreeNode> VersionStatement<N> {
696 pub fn version(&self) -> Version<N::Token> {
698 self.token()
699 .expect("version statement must have a version token")
700 }
701
702 pub fn keyword(&self) -> v1::VersionKeyword<N::Token> {
704 self.token()
705 .expect("version statement must have a version keyword")
706 }
707}
708
709impl<N: TreeNode> AstNode<N> for VersionStatement<N> {
710 fn can_cast(kind: SyntaxKind) -> bool {
711 kind == SyntaxKind::VersionStatementNode
712 }
713
714 fn cast(inner: N) -> Option<Self> {
715 match inner.kind() {
716 SyntaxKind::VersionStatementNode => Some(Self(inner)),
717 _ => None,
718 }
719 }
720
721 fn inner(&self) -> &N {
722 &self.0
723 }
724}
725
726#[derive(Clone, Debug, PartialEq, Eq, Hash)]
728pub struct Version<T: TreeToken = SyntaxToken>(T);
729
730impl<T: TreeToken> AstToken<T> for Version<T> {
731 fn can_cast(kind: SyntaxKind) -> bool {
732 kind == SyntaxKind::Version
733 }
734
735 fn cast(inner: T) -> Option<Self> {
736 match inner.kind() {
737 SyntaxKind::Version => Some(Self(inner)),
738 _ => None,
739 }
740 }
741
742 fn inner(&self) -> &T {
743 &self.0
744 }
745}
746
747#[derive(Debug, Clone, PartialEq, Eq, Hash)]
749pub struct Ident<T: TreeToken = SyntaxToken>(T);
750
751impl<T: TreeToken> Ident<T> {
752 pub fn hashable(&self) -> TokenText<T> {
754 TokenText(self.0.clone())
755 }
756}
757
758impl<T: TreeToken> AstToken<T> for Ident<T> {
759 fn can_cast(kind: SyntaxKind) -> bool {
760 kind == SyntaxKind::Ident
761 }
762
763 fn cast(inner: T) -> Option<Self> {
764 match inner.kind() {
765 SyntaxKind::Ident => Some(Self(inner)),
766 _ => None,
767 }
768 }
769
770 fn inner(&self) -> &T {
771 &self.0
772 }
773}
774
775#[derive(Debug, Clone)]
784pub struct TokenText<T: TreeToken = SyntaxToken>(T);
785
786impl TokenText {
787 pub fn text(&self) -> &str {
789 self.0.text()
790 }
791
792 pub fn span(&self) -> Span {
794 self.0.span()
795 }
796}
797
798impl<T: TreeToken> PartialEq for TokenText<T> {
799 fn eq(&self, other: &Self) -> bool {
800 self.0.text() == other.0.text()
801 }
802}
803
804impl<T: TreeToken> Eq for TokenText<T> {}
805
806impl<T: TreeToken> std::hash::Hash for TokenText<T> {
807 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
808 self.0.text().hash(state);
809 }
810}
811
812impl<T: TreeToken> std::borrow::Borrow<str> for TokenText<T> {
813 fn borrow(&self) -> &str {
814 self.0.text()
815 }
816}