1pub mod dive;
4
5use std::borrow::Cow;
6use std::collections::VecDeque;
7use std::fmt;
8use std::iter;
9
10use rowan::GreenNodeBuilder;
11use rowan::GreenNodeData;
12use strum::VariantArray;
13
14use super::Diagnostic;
15use super::SupportedVersion;
16use super::grammar;
17use super::lexer::Lexer;
18use super::parser::Event;
19use crate::parser::Parser;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, VariantArray)]
30#[repr(u16)]
31#[cfg_attr(
32 feature = "unstable-python",
33 pyo3::pyclass(
34 module = "sprocket_bio.grammar",
35 frozen,
36 rename_all = "SCREAMING_SNAKE_CASE",
37 from_py_object,
38 eq,
39 ord,
40 hash
41 )
42)]
43pub enum SyntaxKind {
44 Unknown,
46 Unparsed,
50 Whitespace,
52 Comment,
54 Version,
56 Float,
58 Integer,
60 Ident,
62 SingleQuote,
64 DoubleQuote,
66 OpenHeredoc,
68 CloseHeredoc,
70 ArrayTypeKeyword,
72 BooleanTypeKeyword,
74 FileTypeKeyword,
76 FloatTypeKeyword,
78 IntTypeKeyword,
80 MapTypeKeyword,
82 ObjectTypeKeyword,
84 PairTypeKeyword,
86 StringTypeKeyword,
88 AfterKeyword,
90 AliasKeyword,
92 AsKeyword,
94 CallKeyword,
96 CommandKeyword,
98 ElseKeyword,
100 EnvKeyword,
102 FalseKeyword,
104 FromKeyword,
106 IfKeyword,
108 InKeyword,
110 ImportKeyword,
112 InputKeyword,
114 MetaKeyword,
116 NoneKeyword,
118 NullKeyword,
120 ObjectKeyword,
122 OutputKeyword,
124 ParameterMetaKeyword,
126 RuntimeKeyword,
128 ScatterKeyword,
130 StructKeyword,
132 EnumKeyword,
134 TaskKeyword,
136 ThenKeyword,
138 TrueKeyword,
140 VersionKeyword,
142 WorkflowKeyword,
144 DirectoryTypeKeyword,
146 HintsKeyword,
148 RequirementsKeyword,
150 OpenBrace,
152 CloseBrace,
154 OpenBracket,
156 CloseBracket,
158 Assignment,
160 Colon,
162 Comma,
164 OpenParen,
166 CloseParen,
168 QuestionMark,
170 Exclamation,
172 Plus,
174 Minus,
176 LogicalOr,
178 LogicalAnd,
180 Asterisk,
182 Exponentiation,
184 Slash,
186 Percent,
188 Equal,
190 NotEqual,
192 LessEqual,
194 GreaterEqual,
196 Less,
198 Greater,
200 Dot,
202 LiteralStringText,
204 LiteralCommandText,
206 PlaceholderOpen,
208
209 #[doc(hidden)]
217 Abandoned,
218 RootNode,
220 VersionStatementNode,
222 ImportStatementNode,
224 ImportMembersNode,
226 ImportMemberNode,
228 SymbolicModulePathNode,
230 ImportAliasNode,
232 StructDefinitionNode,
234 EnumDefinitionNode,
236 EnumTypeParameterNode,
238 EnumChoiceNode,
240 TaskDefinitionNode,
242 WorkflowDefinitionNode,
244 UnboundDeclNode,
246 BoundDeclNode,
248 InputSectionNode,
250 OutputSectionNode,
252 CommandSectionNode,
254 RequirementsSectionNode,
256 RequirementsItemNode,
258 TaskHintsSectionNode,
260 WorkflowHintsSectionNode,
262 TaskHintsItemNode,
264 WorkflowHintsItemNode,
266 WorkflowHintsObjectNode,
268 WorkflowHintsObjectItemNode,
270 WorkflowHintsArrayNode,
272 RuntimeSectionNode,
274 RuntimeItemNode,
276 PrimitiveTypeNode,
278 MapTypeNode,
280 ArrayTypeNode,
282 PairTypeNode,
284 ObjectTypeNode,
286 TypeRefNode,
288 MetadataSectionNode,
290 ParameterMetadataSectionNode,
292 MetadataObjectItemNode,
294 MetadataObjectNode,
296 MetadataArrayNode,
298 LiteralIntegerNode,
300 LiteralFloatNode,
302 LiteralBooleanNode,
304 LiteralNoneNode,
306 LiteralNullNode,
308 LiteralStringNode,
310 LiteralPairNode,
312 LiteralArrayNode,
314 LiteralMapNode,
316 LiteralMapItemNode,
318 LiteralObjectNode,
320 LiteralObjectItemNode,
322 LiteralStructNode,
324 LiteralStructItemNode,
326 LiteralHintsNode,
328 LiteralHintsItemNode,
330 LiteralInputNode,
332 LiteralInputItemNode,
334 LiteralOutputNode,
336 LiteralOutputItemNode,
338 ParenthesizedExprNode,
340 NameRefExprNode,
342 IfExprNode,
344 LogicalNotExprNode,
346 NegationExprNode,
348 LogicalOrExprNode,
350 LogicalAndExprNode,
352 EqualityExprNode,
354 InequalityExprNode,
356 LessExprNode,
358 LessEqualExprNode,
360 GreaterExprNode,
362 GreaterEqualExprNode,
364 AdditionExprNode,
366 SubtractionExprNode,
368 MultiplicationExprNode,
370 DivisionExprNode,
372 ModuloExprNode,
374 ExponentiationExprNode,
376 CallExprNode,
378 IndexExprNode,
380 AccessExprNode,
382 PlaceholderNode,
384 PlaceholderSepOptionNode,
386 PlaceholderDefaultOptionNode,
388 PlaceholderTrueFalseOptionNode,
390 ConditionalStatementNode,
392 ConditionalStatementClauseNode,
394 ScatterStatementNode,
396 CallStatementNode,
398 CallTargetNode,
400 CallAliasNode,
402 CallAfterNode,
404 CallInputItemNode,
406
407 MAX,
410}
411
412impl SyntaxKind {
413 pub fn is_symbolic(&self) -> bool {
419 matches!(
420 self,
421 Self::Abandoned | Self::Unknown | Self::Unparsed | Self::MAX
422 )
423 }
424
425 pub fn describe(&self) -> &'static str {
427 match self {
428 Self::Unknown => unreachable!(),
429 Self::Unparsed => unreachable!(),
430 Self::Whitespace => "whitespace",
431 Self::Comment => "comment",
432 Self::Version => "version",
433 Self::Float => "float",
434 Self::Integer => "integer",
435 Self::Ident => "identifier",
436 Self::SingleQuote => "single quote",
437 Self::DoubleQuote => "double quote",
438 Self::OpenHeredoc => "open heredoc",
439 Self::CloseHeredoc => "close heredoc",
440 Self::ArrayTypeKeyword => "`Array` type keyword",
441 Self::BooleanTypeKeyword => "`Boolean` type keyword",
442 Self::FileTypeKeyword => "`File` type keyword",
443 Self::FloatTypeKeyword => "`Float` type keyword",
444 Self::IntTypeKeyword => "`Int` type keyword",
445 Self::MapTypeKeyword => "`Map` type keyword",
446 Self::ObjectTypeKeyword => "`Object` type keyword",
447 Self::PairTypeKeyword => "`Pair` type keyword",
448 Self::StringTypeKeyword => "`String` type keyword",
449 Self::AfterKeyword => "`after` keyword",
450 Self::AliasKeyword => "`alias` keyword",
451 Self::AsKeyword => "`as` keyword",
452 Self::CallKeyword => "`call` keyword",
453 Self::CommandKeyword => "`command` keyword",
454 Self::ElseKeyword => "`else` keyword",
455 Self::EnvKeyword => "`env` keyword",
456 Self::FalseKeyword => "`false` keyword",
457 Self::FromKeyword => "`from` keyword",
458 Self::IfKeyword => "`if` keyword",
459 Self::InKeyword => "`in` keyword",
460 Self::ImportKeyword => "`import` keyword",
461 Self::InputKeyword => "`input` keyword",
462 Self::MetaKeyword => "`meta` keyword",
463 Self::NoneKeyword => "`None` keyword",
464 Self::NullKeyword => "`null` keyword",
465 Self::ObjectKeyword => "`object` keyword",
466 Self::OutputKeyword => "`output` keyword",
467 Self::ParameterMetaKeyword => "`parameter_meta` keyword",
468 Self::RuntimeKeyword => "`runtime` keyword",
469 Self::ScatterKeyword => "`scatter` keyword",
470 Self::StructKeyword => "`struct` keyword",
471 Self::EnumKeyword => "`enum` keyword",
472 Self::TaskKeyword => "`task` keyword",
473 Self::ThenKeyword => "`then` keyword",
474 Self::TrueKeyword => "`true` keyword",
475 Self::VersionKeyword => "`version` keyword",
476 Self::WorkflowKeyword => "`workflow` keyword",
477 Self::DirectoryTypeKeyword => "`Directory` type keyword",
478 Self::HintsKeyword => "`hints` keyword",
479 Self::RequirementsKeyword => "`requirements` keyword",
480 Self::OpenBrace => "`{` symbol",
481 Self::CloseBrace => "`}` symbol",
482 Self::OpenBracket => "`[` symbol",
483 Self::CloseBracket => "`]` symbol",
484 Self::Assignment => "`=` symbol",
485 Self::Colon => "`:` symbol",
486 Self::Comma => "`,` symbol",
487 Self::OpenParen => "`(` symbol",
488 Self::CloseParen => "`)` symbol",
489 Self::QuestionMark => "`?` symbol",
490 Self::Exclamation => "`!` symbol",
491 Self::Plus => "`+` symbol",
492 Self::Minus => "`-` symbol",
493 Self::LogicalOr => "`||` symbol",
494 Self::LogicalAnd => "`&&` symbol",
495 Self::Asterisk => "`*` symbol",
496 Self::Exponentiation => "`**` symbol",
497 Self::Slash => "`/` symbol",
498 Self::Percent => "`%` symbol",
499 Self::Equal => "`==` symbol",
500 Self::NotEqual => "`!=` symbol",
501 Self::LessEqual => "`<=` symbol",
502 Self::GreaterEqual => "`>=` symbol",
503 Self::Less => "`<` symbol",
504 Self::Greater => "`>` symbol",
505 Self::Dot => "`.` symbol",
506 Self::LiteralStringText => "literal string text",
507 Self::LiteralCommandText => "literal command text",
508 Self::PlaceholderOpen => "placeholder open",
509 Self::Abandoned => unreachable!(),
510 Self::RootNode => "root node",
511 Self::VersionStatementNode => "version statement",
512 Self::ImportStatementNode => "import statement",
513 Self::ImportMembersNode => "selected members clause",
514 Self::ImportMemberNode => "selected member",
515 Self::SymbolicModulePathNode => "symbolic module path",
516 Self::ImportAliasNode => "import alias",
517 Self::StructDefinitionNode => "struct definition",
518 Self::EnumDefinitionNode => "enum definition",
519 Self::EnumTypeParameterNode => "enum type parameter",
520 Self::EnumChoiceNode => "enum choice",
521 Self::TaskDefinitionNode => "task definition",
522 Self::WorkflowDefinitionNode => "workflow definition",
523 Self::UnboundDeclNode => "declaration without assignment",
524 Self::BoundDeclNode => "declaration with assignment",
525 Self::InputSectionNode => "input section",
526 Self::OutputSectionNode => "output section",
527 Self::CommandSectionNode => "command section",
528 Self::RequirementsSectionNode => "requirements section",
529 Self::RequirementsItemNode => "requirements item",
530 Self::TaskHintsSectionNode | Self::WorkflowHintsSectionNode => "hints section",
531 Self::TaskHintsItemNode | Self::WorkflowHintsItemNode => "hints item",
532 Self::WorkflowHintsObjectNode => "literal object",
533 Self::WorkflowHintsObjectItemNode => "literal object item",
534 Self::WorkflowHintsArrayNode => "literal array",
535 Self::RuntimeSectionNode => "runtime section",
536 Self::RuntimeItemNode => "runtime item",
537 Self::PrimitiveTypeNode => "primitive type",
538 Self::MapTypeNode => "map type",
539 Self::ArrayTypeNode => "array type",
540 Self::PairTypeNode => "pair type",
541 Self::ObjectTypeNode => "object type",
542 Self::TypeRefNode => "type reference",
543 Self::MetadataSectionNode => "metadata section",
544 Self::ParameterMetadataSectionNode => "parameter metadata section",
545 Self::MetadataObjectItemNode => "metadata object item",
546 Self::MetadataObjectNode => "metadata object",
547 Self::MetadataArrayNode => "metadata array",
548 Self::LiteralIntegerNode => "literal integer",
549 Self::LiteralFloatNode => "literal float",
550 Self::LiteralBooleanNode => "literal boolean",
551 Self::LiteralNoneNode => "literal `None`",
552 Self::LiteralNullNode => "literal null",
553 Self::LiteralStringNode => "literal string",
554 Self::LiteralPairNode => "literal pair",
555 Self::LiteralArrayNode => "literal array",
556 Self::LiteralMapNode => "literal map",
557 Self::LiteralMapItemNode => "literal map item",
558 Self::LiteralObjectNode => "literal object",
559 Self::LiteralObjectItemNode => "literal object item",
560 Self::LiteralStructNode => "literal struct",
561 Self::LiteralStructItemNode => "literal struct item",
562 Self::LiteralHintsNode => "literal hints",
563 Self::LiteralHintsItemNode => "literal hints item",
564 Self::LiteralInputNode => "literal input",
565 Self::LiteralInputItemNode => "literal input item",
566 Self::LiteralOutputNode => "literal output",
567 Self::LiteralOutputItemNode => "literal output item",
568 Self::ParenthesizedExprNode => "parenthesized expression",
569 Self::NameRefExprNode => "name reference expression",
570 Self::IfExprNode => "`if` expression",
571 Self::LogicalNotExprNode => "logical not expression",
572 Self::NegationExprNode => "negation expression",
573 Self::LogicalOrExprNode => "logical OR expression",
574 Self::LogicalAndExprNode => "logical AND expression",
575 Self::EqualityExprNode => "equality expression",
576 Self::InequalityExprNode => "inequality expression",
577 Self::LessExprNode => "less than expression",
578 Self::LessEqualExprNode => "less than or equal to expression",
579 Self::GreaterExprNode => "greater than expression",
580 Self::GreaterEqualExprNode => "greater than or equal to expression",
581 Self::AdditionExprNode => "addition expression",
582 Self::SubtractionExprNode => "subtraction expression",
583 Self::MultiplicationExprNode => "multiplication expression",
584 Self::DivisionExprNode => "division expression",
585 Self::ModuloExprNode => "modulo expression",
586 Self::ExponentiationExprNode => "exponentiation expression",
587 Self::CallExprNode => "call expression",
588 Self::IndexExprNode => "index expression",
589 Self::AccessExprNode => "access expression",
590 Self::PlaceholderNode => "placeholder",
591 Self::PlaceholderSepOptionNode => "placeholder `sep` option",
592 Self::PlaceholderDefaultOptionNode => "placeholder `default` option",
593 Self::PlaceholderTrueFalseOptionNode => "placeholder `true`/`false` option",
594 Self::ConditionalStatementNode => "conditional statement",
595 Self::ConditionalStatementClauseNode => "conditional statement clause",
596 Self::ScatterStatementNode => "scatter statement",
597 Self::CallStatementNode => "call statement",
598 Self::CallTargetNode => "call target",
599 Self::CallAliasNode => "call alias",
600 Self::CallAfterNode => "call `after` clause",
601 Self::CallInputItemNode => "call input item",
602 Self::MAX => unreachable!(),
603 }
604 }
605
606 pub fn is_trivia(&self) -> bool {
608 matches!(self, Self::Whitespace | Self::Comment)
609 }
610
611 pub fn is_keyword(&self) -> bool {
615 matches!(
616 self,
617 SyntaxKind::AfterKeyword
618 | SyntaxKind::AliasKeyword
619 | SyntaxKind::AsKeyword
620 | SyntaxKind::CallKeyword
621 | SyntaxKind::CommandKeyword
622 | SyntaxKind::ElseKeyword
623 | SyntaxKind::EnvKeyword
624 | SyntaxKind::FalseKeyword
625 | SyntaxKind::FromKeyword
626 | SyntaxKind::HintsKeyword
627 | SyntaxKind::IfKeyword
628 | SyntaxKind::ImportKeyword
629 | SyntaxKind::InKeyword
630 | SyntaxKind::InputKeyword
631 | SyntaxKind::MetaKeyword
632 | SyntaxKind::NoneKeyword
633 | SyntaxKind::NullKeyword
634 | SyntaxKind::ObjectKeyword
635 | SyntaxKind::OutputKeyword
636 | SyntaxKind::ParameterMetaKeyword
637 | SyntaxKind::RequirementsKeyword
638 | SyntaxKind::RuntimeKeyword
639 | SyntaxKind::ScatterKeyword
640 | SyntaxKind::StructKeyword
641 | SyntaxKind::EnumKeyword
642 | SyntaxKind::TaskKeyword
643 | SyntaxKind::ThenKeyword
644 | SyntaxKind::TrueKeyword
645 | SyntaxKind::VersionKeyword
646 | SyntaxKind::WorkflowKeyword
647 )
648 }
649
650 pub fn is_type(&self) -> bool {
652 matches!(
653 self,
654 SyntaxKind::ArrayTypeKeyword
655 | SyntaxKind::BooleanTypeKeyword
656 | SyntaxKind::DirectoryTypeKeyword
657 | SyntaxKind::FileTypeKeyword
658 | SyntaxKind::FloatTypeKeyword
659 | SyntaxKind::IntTypeKeyword
660 | SyntaxKind::MapTypeKeyword
661 | SyntaxKind::ObjectTypeKeyword
662 | SyntaxKind::PairTypeKeyword
663 | SyntaxKind::StringTypeKeyword
664 )
665 }
666
667 pub fn is_operator(&self) -> bool {
669 matches!(
670 self,
671 SyntaxKind::Plus
672 | SyntaxKind::Minus
673 | SyntaxKind::Slash
674 | SyntaxKind::Percent
675 | SyntaxKind::Asterisk
676 | SyntaxKind::Exponentiation
677 | SyntaxKind::Equal
678 | SyntaxKind::NotEqual
679 | SyntaxKind::Less
680 | SyntaxKind::LessEqual
681 | SyntaxKind::Greater
682 | SyntaxKind::GreaterEqual
683 | SyntaxKind::LogicalAnd
684 | SyntaxKind::LogicalOr
685 | SyntaxKind::Exclamation
686 | SyntaxKind::Assignment
687 | SyntaxKind::QuestionMark
688 | SyntaxKind::Dot
689 )
690 }
691}
692
693pub static ALL_SYNTAX_KIND: &[SyntaxKind] = SyntaxKind::VARIANTS;
695
696impl From<SyntaxKind> for rowan::SyntaxKind {
697 fn from(kind: SyntaxKind) -> Self {
698 rowan::SyntaxKind(kind as u16)
699 }
700}
701
702#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
704pub struct WorkflowDescriptionLanguage;
705
706impl rowan::Language for WorkflowDescriptionLanguage {
707 type Kind = SyntaxKind;
708
709 fn kind_from_raw(raw: rowan::SyntaxKind) -> Self::Kind {
710 assert!(raw.0 <= SyntaxKind::MAX as u16);
711 unsafe { std::mem::transmute::<u16, SyntaxKind>(raw.0) }
712 }
713
714 fn kind_to_raw(kind: Self::Kind) -> rowan::SyntaxKind {
715 kind.into()
716 }
717}
718
719pub type SyntaxNode = rowan::SyntaxNode<WorkflowDescriptionLanguage>;
721pub type SyntaxToken = rowan::SyntaxToken<WorkflowDescriptionLanguage>;
723pub type SyntaxElement = rowan::SyntaxElement<WorkflowDescriptionLanguage>;
725pub type SyntaxNodeChildren = rowan::SyntaxNodeChildren<WorkflowDescriptionLanguage>;
727
728pub fn construct_tree(source: &str, mut events: Vec<Event>) -> SyntaxNode {
730 let mut builder = GreenNodeBuilder::default();
731 let mut ancestors = Vec::new();
732
733 for i in 0..events.len() {
734 match std::mem::replace(&mut events[i], Event::abandoned()) {
735 Event::NodeStarted {
736 kind,
737 forward_parent,
738 } => {
739 ancestors.push(kind);
742 let mut idx = i;
743 let mut fp: Option<usize> = forward_parent;
744 while let Some(distance) = fp {
745 idx += distance;
746 fp = match std::mem::replace(&mut events[idx], Event::abandoned()) {
747 Event::NodeStarted {
748 kind,
749 forward_parent,
750 } => {
751 ancestors.push(kind);
752 forward_parent
753 }
754 _ => unreachable!(),
755 };
756 }
757
758 for kind in ancestors.drain(..).rev() {
761 if kind != SyntaxKind::Abandoned {
762 builder.start_node(kind.into());
763 }
764 }
765 }
766 Event::NodeFinished => builder.finish_node(),
767 Event::Token { kind, span } => {
768 builder.token(kind.into(), &source[span.start()..span.end()])
769 }
770 }
771 }
772
773 SyntaxNode::new_root(builder.finish())
774}
775
776#[derive(Clone, PartialEq, Eq, Hash)]
778pub struct SyntaxTree(SyntaxNode);
779
780impl SyntaxTree {
781 pub fn parse(
803 source: &str,
804 fallback_version: Option<SupportedVersion>,
805 ) -> (Self, Vec<Diagnostic>) {
806 let parser = Parser::new(Lexer::new(source));
807 let (events, mut diagnostics) = grammar::document(parser, fallback_version);
808 diagnostics.sort();
809 (Self(construct_tree(source, events)), diagnostics)
810 }
811
812 pub fn root(&self) -> &SyntaxNode {
814 &self.0
815 }
816
817 pub fn green(&self) -> Cow<'_, GreenNodeData> {
819 self.0.green()
820 }
821
822 pub fn into_syntax(self) -> SyntaxNode {
824 self.0
825 }
826}
827
828impl fmt::Display for SyntaxTree {
829 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
830 self.0.fmt(f)
831 }
832}
833
834impl fmt::Debug for SyntaxTree {
835 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
836 self.0.fmt(f)
837 }
838}
839
840pub trait SyntaxTokenExt {
842 fn preceding_trivia(&self) -> impl Iterator<Item = SyntaxToken>;
844
845 fn inline_comment(&self) -> Option<SyntaxToken>;
848}
849
850impl SyntaxTokenExt for SyntaxToken {
851 fn preceding_trivia(&self) -> impl Iterator<Item = SyntaxToken> {
852 let mut tokens = VecDeque::new();
853 let mut cur = self.prev_token();
854 while let Some(token) = cur {
855 cur = token.prev_token();
856 if !token.kind().is_trivia() {
858 break;
859 }
860 if token.kind() == SyntaxKind::Comment
862 && let Some(prev) = token.prev_token()
863 {
864 if prev.kind() == SyntaxKind::Whitespace {
865 let has_newlines = prev.text().chars().any(|c| c == '\n');
866 if !has_newlines && prev.prev_token().is_some() {
872 break;
873 }
874 } else {
875 break;
877 }
878 }
879 match token.kind() {
881 SyntaxKind::Whitespace
882 if token.text().chars().filter(|c| *c == '\n').count() > 1 =>
883 {
884 tokens.push_front(token);
885 }
886 SyntaxKind::Comment => {
887 tokens.push_front(token);
888 }
889 _ => {}
890 }
891 }
892 tokens.into_iter()
893 }
894
895 fn inline_comment(&self) -> Option<SyntaxToken> {
896 let mut next = self.next_token();
897 iter::from_fn(move || {
898 let cur = next.clone()?;
899 next = cur.next_token();
900 Some(cur)
901 })
902 .take_while(|t| {
903 if !t.kind().is_trivia() {
905 return false;
906 }
907 if t.kind() == SyntaxKind::Whitespace {
909 return !t.text().chars().any(|c| c == '\n');
910 }
911 true
912 })
913 .find(|t| t.kind() == SyntaxKind::Comment)
914 }
915}
916
917#[cfg(feature = "unstable-python")]
919mod python {
920 use pyo3::exceptions::PyValueError;
921 use pyo3::prelude::*;
922
923 use super::*;
924
925 #[pymethods]
926 impl SyntaxKind {
927 #[pyo3(name = "is_symbolic")]
933 fn py_is_symbolic(&self) -> bool {
934 self.is_symbolic()
935 }
936
937 #[pyo3(name = "describe")]
944 fn py_describe(&self) -> PyResult<&'static str> {
945 if self.is_symbolic() {
946 return Err(PyValueError::new_err(format!(
947 "cannot describe symbolic syntax kind: {}",
948 self.__pyo3__repr__()
949 )));
950 }
951
952 Ok(self.describe())
953 }
954
955 #[pyo3(name = "is_trivia")]
957 fn py_is_trivia(&self) -> bool {
958 self.is_trivia()
959 }
960
961 #[pyo3(name = "is_keyword")]
965 fn py_is_keyword(&self) -> bool {
966 self.is_keyword()
967 }
968
969 #[pyo3(name = "is_type")]
971 fn py_is_type(&self) -> bool {
972 self.is_type()
973 }
974
975 #[pyo3(name = "is_operator")]
977 fn py_is_operator(&self) -> bool {
978 self.is_operator()
979 }
980 }
981}
982
983#[cfg(test)]
984mod tests {
985 use super::*;
986 use crate::SyntaxTree;
987
988 #[test]
989 fn preceding_comments() {
990 let (tree, diagnostics) = SyntaxTree::parse(
991 "version 1.2
992
993# This comment should not be included
994task foo {} # This comment should not be included
995
996# Some
997# comments
998# are
999# long
1000
1001# Others are short
1002
1003# and, yet another
1004workflow foo {} # This should not be collected.
1005
1006# This comment should not be included either.",
1007 None,
1008 );
1009
1010 assert!(diagnostics.is_empty());
1011
1012 let workflow = tree.root().last_child().unwrap();
1013 assert_eq!(workflow.kind(), SyntaxKind::WorkflowDefinitionNode);
1014 let token = workflow.first_token().unwrap();
1015 let mut trivia = token.preceding_trivia();
1016 assert_eq!(trivia.next().unwrap().text(), "\n\n");
1017 assert_eq!(trivia.next().unwrap().text(), "# Some");
1018 assert_eq!(trivia.next().unwrap().text(), "# comments");
1019 assert_eq!(trivia.next().unwrap().text(), "# are");
1020 assert_eq!(trivia.next().unwrap().text(), "# long");
1021 assert_eq!(trivia.next().unwrap().text(), "\n \n");
1022 assert_eq!(trivia.next().unwrap().text(), "# Others are short");
1023 assert_eq!(trivia.next().unwrap().text(), "\n\n");
1024 assert_eq!(trivia.next().unwrap().text(), "# and, yet another");
1025 assert!(trivia.next().is_none());
1026 }
1027
1028 #[test]
1029 fn inline_comment() {
1030 let (tree, diagnostics) = SyntaxTree::parse(
1031 "version 1.2
1032
1033# This comment should not be included
1034task foo {}
1035
1036# This should not be collected.
1037workflow foo {} # Here is a comment that should be collected.
1038
1039# This comment should not be included either.",
1040 None,
1041 );
1042
1043 assert!(diagnostics.is_empty());
1044
1045 let workflow = tree.root().last_child().unwrap();
1046 assert_eq!(workflow.kind(), SyntaxKind::WorkflowDefinitionNode);
1047 let comment = workflow.last_token().unwrap().inline_comment().unwrap();
1048 assert_eq!(
1049 comment.text(),
1050 "# Here is a comment that should be collected."
1051 );
1052 }
1053}