1use rowan::NodeOrToken;
4use wdl_grammar::SyntaxTokenExt;
5
6use super::BoundDecl;
7use super::Decl;
8use super::Expr;
9use super::LiteralBoolean;
10use super::LiteralFloat;
11use super::LiteralInteger;
12use super::LiteralString;
13use super::OpenHeredoc;
14use super::Placeholder;
15use super::StructDefinition;
16use super::TaskKeyword;
17use super::WorkflowDefinition;
18use crate::AstNode;
19use crate::AstToken;
20use crate::Comment;
21use crate::Documented;
22use crate::Ident;
23use crate::SyntaxKind;
24use crate::SyntaxNode;
25use crate::SyntaxToken;
26use crate::TreeNode;
27use crate::TreeToken;
28use crate::v1::CommandKeyword;
29use crate::v1::MetaKeyword;
30use crate::v1::ParameterMetaKeyword;
31use crate::v1::RequirementsKeyword;
32
33pub mod common;
34pub mod requirements;
35pub mod runtime;
36
37pub const TASK_FIELDS: &[(&str, &str)] = &[
40 (TASK_FIELD_NAME, "The task name."),
41 (
42 TASK_FIELD_ID,
43 "A String with the unique ID of the task. The execution engine may choose the format for \
44 this ID, but it is suggested to include at least the following information:\nThe task \
45 name\nThe task alias, if it differs from the task name\nThe index of the task instance, \
46 if it is within a scatter statement",
47 ),
48 (
49 TASK_FIELD_CONTAINER,
50 "The URI String of the container in which the task is executing, or None if the task is \
51 being executed in the host environment.",
52 ),
53 (
54 TASK_FIELD_CPU,
55 "The allocated number of cpus as a Float. Must be greater than 0.",
56 ),
57 (
58 TASK_FIELD_MEMORY,
59 "The allocated memory in bytes as an Int. Must be greater than 0.",
60 ),
61 (
62 TASK_FIELD_GPU,
63 "An Array[String] with one specification per allocated GPU. The specification is \
64 execution engine-specific. If no GPUs were allocated, then the value must be an empty \
65 array.",
66 ),
67 (
68 TASK_FIELD_FPGA,
69 "An Array[String] with one specification per allocated FPGA. The specification is \
70 execution engine-specific. If no FPGAs were allocated, then the value must be an empty \
71 array.",
72 ),
73 (
74 TASK_FIELD_DISKS,
75 "A Map[String, Int] with one entry for each disk mount point. The key is the mount point \
76 and the value is the initial amount of disk space allocated, in bytes. The execution \
77 engine must, at a minimum, provide one entry for each disk mount point requested, but \
78 may provide more. The amount of disk space available for a given mount point may \
79 increase during the lifetime of the task (e.g., autoscaling volumes provided by some \
80 cloud services).",
81 ),
82 (
83 TASK_FIELD_ATTEMPT,
84 "The current task attempt. The value must be 0 the first time the task is executed, and \
85 incremented by 1 each time the task is retried (if any).",
86 ),
87 (
88 TASK_FIELD_PREVIOUS,
89 "An Object containing the resource requirements from the previous task attempt. Available \
90 in requirements, hints, runtime, output, and command sections. All constituent members \
91 are optional and `None` on the first attempt.",
92 ),
93 (
94 TASK_FIELD_END_TIME,
95 "An Int? whose value is the time by which the task must be completed, as a Unix time \
96 stamp. A value of 0 means that the execution engine does not impose a time limit. A \
97 value of None means that the execution engine cannot determine whether the runtime of \
98 the task is limited. A positive value is a guarantee that the task will be preempted at \
99 the specified time, but is not a guarantee that the task won't be preempted earlier.",
100 ),
101 (
102 TASK_FIELD_RETURN_CODE,
103 "An Int? whose value is initially None and is set to the value of the command's return \
104 code. The value is only guaranteed to be defined in the output section.",
105 ),
106 (
107 TASK_FIELD_META,
108 "An Object containing a copy of the task's meta section, or the empty Object if there is \
109 no meta section or if it is empty.",
110 ),
111 (
112 TASK_FIELD_PARAMETER_META,
113 "An Object containing a copy of the task's parameter_meta section, or the empty Object if \
114 there is no parameter_meta section or if it is empty.",
115 ),
116 (
117 TASK_FIELD_EXT,
118 "An Object containing execution engine-specific attributes, or the empty Object if there \
119 aren't any. Members of ext should be considered optional. It is recommended to only \
120 access a member of ext using string interpolation to avoid an error if it is not defined.",
121 ),
122];
123
124pub const RUNTIME_KEYS: &[(&str, &str)] = &[
126 (
127 TASK_REQUIREMENT_CONTAINER,
128 "Specifies the container image (e.g., Docker, Singularity) to use for the task.",
129 ),
130 (
131 TASK_REQUIREMENT_CPU,
132 "The number of CPU cores required for the task.",
133 ),
134 (
135 TASK_REQUIREMENT_MEMORY,
136 "The amount of memory required, specified as a string with units (e.g., '2 GiB').",
137 ),
138 (
139 TASK_REQUIREMENT_DISKS,
140 "Specifies the disk requirements for the task.",
141 ),
142 (TASK_REQUIREMENT_GPU, "Specifies GPU requirements."),
143];
144
145pub const REQUIREMENTS_KEY: &[(&str, &str)] = &[
147 (
148 TASK_REQUIREMENT_CONTAINER,
149 "Specifies a list of allowed container images. Use `*` to allow any POSIX environment.",
150 ),
151 (
152 TASK_REQUIREMENT_CPU,
153 "The minimum number of CPU cores required.",
154 ),
155 (
156 TASK_REQUIREMENT_MEMORY,
157 "The minimum amount of memory required.",
158 ),
159 (TASK_REQUIREMENT_GPU, "The minimum GPU requirements."),
160 (TASK_REQUIREMENT_FPGA, "The minimum FPGA requirements."),
161 (TASK_REQUIREMENT_DISKS, "The minimum disk requirements."),
162 (
163 TASK_REQUIREMENT_MAX_RETRIES,
164 "The maximum number of times the task can be retried.",
165 ),
166 (
167 TASK_REQUIREMENT_RETURN_CODES,
168 "A list of acceptable return codes from the command.",
169 ),
170];
171
172pub const TASK_HINT_KEYS: &[(&str, &str)] = &[
174 (
175 TASK_HINT_DISKS,
176 "A hint to the execution engine to mount disks with specific attributes. The value of \
177 this hint can be a String with a specification that applies to all mount points, or a \
178 Map with the key being the mount point and the value being a String with the \
179 specification for that mount point.",
180 ),
181 (
182 TASK_HINT_GPU,
183 "A hint to the execution engine to provision hardware accelerators with specific \
184 attributes. Accelerator specifications are left intentionally vague as they are \
185 primarily intended to be used in the context of a specific compute environment.",
186 ),
187 (
188 TASK_HINT_FPGA,
189 "A hint to the execution engine to provision hardware accelerators with specific \
190 attributes. Accelerator specifications are left intentionally vague as they are \
191 primarily intended to be used in the context of a specific compute environment.",
192 ),
193 (
194 TASK_HINT_INPUTS,
195 "Provides input-specific hints. Each key must refer to a parameter defined in the task's \
196 input section. A key may also used dotted notation to refer to a specific member of a \
197 struct input.",
198 ),
199 (
200 TASK_HINT_LOCALIZATION_OPTIONAL,
201 "A hint to the execution engine about whether the File inputs for this task need to be \
202 localized prior to executing the task. The value of this hint is a Boolean for which \
203 true indicates that the contents of the File inputs may be streamed on demand.",
204 ),
205 (
206 TASK_HINT_MAX_CPU,
207 "A hint to the execution engine that the task expects to use no more than the specified \
208 number of CPUs. The value of this hint has the same specification as requirements.cpu.",
209 ),
210 (
211 TASK_HINT_MAX_MEMORY,
212 "A hint to the execution engine that the task expects to use no more than the specified \
213 amount of memory. The value of this hint has the same specification as \
214 requirements.memory.",
215 ),
216 (
217 TASK_HINT_OUTPUTS,
218 "Provides output-specific hints. Each key must refer to a parameter defined in the task's \
219 output section. A key may also use dotted notation to refer to a specific member of a \
220 struct output.",
221 ),
222 (
223 TASK_HINT_SHORT_TASK,
224 "A hint to the execution engine about the expected duration of this task. The value of \
225 this hint is a Boolean for which true indicates that that this task is not expected to \
226 take long to execute, which the execution engine can interpret as permission to optimize \
227 the execution of the task.",
228 ),
229 (
230 TASK_HINT_CACHEABLE,
231 "A hint to the execution engine that the task's execution result is cacheable. The value \
232 of this hint is a Boolean for which true indicates that the execution result is \
233 cacheable and false indicates it is not. The default value of the hint depends on the \
234 engine's configuration.",
235 ),
236];
237
238pub const TASK_FIELD_NAME: &str = "name";
240pub const TASK_FIELD_ID: &str = "id";
242pub const TASK_FIELD_CONTAINER: &str = "container";
244pub const TASK_FIELD_CPU: &str = "cpu";
246pub const TASK_FIELD_MEMORY: &str = "memory";
248pub const TASK_FIELD_ATTEMPT: &str = "attempt";
250pub const TASK_FIELD_PREVIOUS: &str = "previous";
252pub const TASK_FIELD_GPU: &str = "gpu";
254pub const TASK_FIELD_FPGA: &str = "fpga";
256pub const TASK_FIELD_DISKS: &str = "disks";
258pub const TASK_FIELD_END_TIME: &str = "end_time";
260pub const TASK_FIELD_RETURN_CODE: &str = "return_code";
262pub const TASK_FIELD_META: &str = "meta";
264pub const TASK_FIELD_PARAMETER_META: &str = "parameter_meta";
266pub const TASK_FIELD_EXT: &str = "ext";
268pub const TASK_FIELD_MAX_RETRIES: &str = "max_retries";
270
271pub const TASK_REQUIREMENT_CONTAINER: &str = "container";
273pub const TASK_REQUIREMENT_CONTAINER_ALIAS: &str = "docker";
275pub const TASK_REQUIREMENT_CPU: &str = "cpu";
277pub const TASK_REQUIREMENT_DISKS: &str = "disks";
279pub const TASK_REQUIREMENT_GPU: &str = "gpu";
281pub const TASK_REQUIREMENT_FPGA: &str = "fpga";
283pub const TASK_REQUIREMENT_MAX_RETRIES: &str = "max_retries";
285pub const TASK_REQUIREMENT_MAX_RETRIES_ALIAS: &str = "maxRetries";
287pub const TASK_REQUIREMENT_MEMORY: &str = "memory";
289pub const TASK_REQUIREMENT_RETURN_CODES: &str = "return_codes";
291pub const TASK_REQUIREMENT_RETURN_CODES_ALIAS: &str = "returnCodes";
293
294pub const TASK_HINT_DISKS: &str = "disks";
296pub const TASK_HINT_GPU: &str = "gpu";
298pub const TASK_HINT_FPGA: &str = "fpga";
300pub const TASK_HINT_INPUTS: &str = "inputs";
302pub const TASK_HINT_LOCALIZATION_OPTIONAL: &str = "localization_optional";
304pub const TASK_HINT_LOCALIZATION_OPTIONAL_ALIAS: &str = "localizationOptional";
307pub const TASK_HINT_MAX_CPU: &str = "max_cpu";
309pub const TASK_HINT_MAX_CPU_ALIAS: &str = "maxCpu";
311pub const TASK_HINT_MAX_MEMORY: &str = "max_memory";
313pub const TASK_HINT_MAX_MEMORY_ALIAS: &str = "maxMemory";
315pub const TASK_HINT_OUTPUTS: &str = "outputs";
317pub const TASK_HINT_SHORT_TASK: &str = "short_task";
319pub const TASK_HINT_SHORT_TASK_ALIAS: &str = "shortTask";
321pub const TASK_HINT_CACHEABLE: &str = "cacheable";
323
324fn unescape_command_text(s: &str, heredoc: bool, buffer: &mut String) {
326 let mut chars = s.chars().peekable();
327 while let Some(c) = chars.next() {
328 match c {
329 '\\' => match chars.peek() {
330 Some('\\') | Some('~') => {
331 buffer.push(chars.next().unwrap());
332 }
333 Some('>') if heredoc => {
334 buffer.push(chars.next().unwrap());
335 }
336 Some('$') | Some('}') if !heredoc => {
337 buffer.push(chars.next().unwrap());
338 }
339 _ => {
340 buffer.push('\\');
341 }
342 },
343 _ => {
344 buffer.push(c);
345 }
346 }
347 }
348}
349
350#[derive(Clone, Debug, PartialEq, Eq)]
352pub struct TaskDefinition<N: TreeNode = SyntaxNode>(N);
353
354impl<N: TreeNode> TaskDefinition<N> {
355 pub fn name(&self) -> Ident<N::Token> {
357 self.token().expect("task should have a name")
358 }
359
360 pub fn keyword(&self) -> TaskKeyword<N::Token> {
362 self.token().expect("task should have a keyword")
363 }
364
365 pub fn items(&self) -> impl Iterator<Item = TaskItem<N>> + use<'_, N> {
367 TaskItem::children(&self.0)
368 }
369
370 pub fn input(&self) -> Option<InputSection<N>> {
372 self.child()
373 }
374
375 pub fn output(&self) -> Option<OutputSection<N>> {
377 self.child()
378 }
379
380 pub fn command(&self) -> Option<CommandSection<N>> {
382 self.child()
383 }
384
385 pub fn requirements(&self) -> Option<RequirementsSection<N>> {
387 self.child()
388 }
389
390 pub fn hints(&self) -> Option<TaskHintsSection<N>> {
392 self.child()
393 }
394
395 pub fn runtime(&self) -> Option<RuntimeSection<N>> {
397 self.child()
398 }
399
400 pub fn metadata(&self) -> Option<MetadataSection<N>> {
402 self.child()
403 }
404
405 pub fn parameter_metadata(&self) -> Option<ParameterMetadataSection<N>> {
407 self.child()
408 }
409
410 pub fn declarations(&self) -> impl Iterator<Item = BoundDecl<N>> + use<'_, N> {
412 self.children()
413 }
414}
415
416impl<N: TreeNode> AstNode<N> for TaskDefinition<N> {
417 fn can_cast(kind: SyntaxKind) -> bool {
418 kind == SyntaxKind::TaskDefinitionNode
419 }
420
421 fn cast(inner: N) -> Option<Self> {
422 match inner.kind() {
423 SyntaxKind::TaskDefinitionNode => Some(Self(inner)),
424 _ => None,
425 }
426 }
427
428 fn inner(&self) -> &N {
429 &self.0
430 }
431}
432
433impl Documented<SyntaxNode> for TaskDefinition<SyntaxNode> {
434 fn doc_comments(&self) -> Option<Vec<Comment<<SyntaxNode as TreeNode>::Token>>> {
435 Some(crate::doc_comments::<SyntaxNode>(self.keyword().inner().preceding_trivia()).collect())
436 }
437}
438
439#[derive(Clone, Debug, PartialEq, Eq)]
441pub enum TaskItem<N: TreeNode = SyntaxNode> {
442 Input(InputSection<N>),
444 Output(OutputSection<N>),
446 Command(CommandSection<N>),
448 Requirements(RequirementsSection<N>),
450 Hints(TaskHintsSection<N>),
452 Runtime(RuntimeSection<N>),
454 Metadata(MetadataSection<N>),
456 ParameterMetadata(ParameterMetadataSection<N>),
458 Declaration(BoundDecl<N>),
460}
461
462impl<N: TreeNode> TaskItem<N> {
463 pub fn can_cast(kind: SyntaxKind) -> bool {
466 matches!(
467 kind,
468 SyntaxKind::InputSectionNode
469 | SyntaxKind::OutputSectionNode
470 | SyntaxKind::CommandSectionNode
471 | SyntaxKind::RequirementsSectionNode
472 | SyntaxKind::TaskHintsSectionNode
473 | SyntaxKind::RuntimeSectionNode
474 | SyntaxKind::MetadataSectionNode
475 | SyntaxKind::ParameterMetadataSectionNode
476 | SyntaxKind::BoundDeclNode
477 )
478 }
479
480 pub fn cast(inner: N) -> Option<Self> {
484 match inner.kind() {
485 SyntaxKind::InputSectionNode => Some(Self::Input(
486 InputSection::cast(inner).expect("input section to cast"),
487 )),
488 SyntaxKind::OutputSectionNode => Some(Self::Output(
489 OutputSection::cast(inner).expect("output section to cast"),
490 )),
491 SyntaxKind::CommandSectionNode => Some(Self::Command(
492 CommandSection::cast(inner).expect("command section to cast"),
493 )),
494 SyntaxKind::RequirementsSectionNode => Some(Self::Requirements(
495 RequirementsSection::cast(inner).expect("requirements section to cast"),
496 )),
497 SyntaxKind::RuntimeSectionNode => Some(Self::Runtime(
498 RuntimeSection::cast(inner).expect("runtime section to cast"),
499 )),
500 SyntaxKind::MetadataSectionNode => Some(Self::Metadata(
501 MetadataSection::cast(inner).expect("metadata section to cast"),
502 )),
503 SyntaxKind::ParameterMetadataSectionNode => Some(Self::ParameterMetadata(
504 ParameterMetadataSection::cast(inner).expect("parameter metadata section to cast"),
505 )),
506 SyntaxKind::TaskHintsSectionNode => Some(Self::Hints(
507 TaskHintsSection::cast(inner).expect("task hints section to cast"),
508 )),
509 SyntaxKind::BoundDeclNode => Some(Self::Declaration(
510 BoundDecl::cast(inner).expect("bound decl to cast"),
511 )),
512 _ => None,
513 }
514 }
515
516 pub fn inner(&self) -> &N {
518 match self {
519 Self::Input(element) => element.inner(),
520 Self::Output(element) => element.inner(),
521 Self::Command(element) => element.inner(),
522 Self::Requirements(element) => element.inner(),
523 Self::Hints(element) => element.inner(),
524 Self::Runtime(element) => element.inner(),
525 Self::Metadata(element) => element.inner(),
526 Self::ParameterMetadata(element) => element.inner(),
527 Self::Declaration(element) => element.inner(),
528 }
529 }
530
531 pub fn as_input_section(&self) -> Option<&InputSection<N>> {
537 match self {
538 Self::Input(s) => Some(s),
539 _ => None,
540 }
541 }
542
543 pub fn into_input_section(self) -> Option<InputSection<N>> {
549 match self {
550 Self::Input(s) => Some(s),
551 _ => None,
552 }
553 }
554
555 pub fn as_output_section(&self) -> Option<&OutputSection<N>> {
561 match self {
562 Self::Output(s) => Some(s),
563 _ => None,
564 }
565 }
566
567 pub fn into_output_section(self) -> Option<OutputSection<N>> {
573 match self {
574 Self::Output(s) => Some(s),
575 _ => None,
576 }
577 }
578
579 pub fn as_command_section(&self) -> Option<&CommandSection<N>> {
585 match self {
586 Self::Command(s) => Some(s),
587 _ => None,
588 }
589 }
590
591 pub fn into_command_section(self) -> Option<CommandSection<N>> {
597 match self {
598 Self::Command(s) => Some(s),
599 _ => None,
600 }
601 }
602
603 pub fn as_requirements_section(&self) -> Option<&RequirementsSection<N>> {
609 match self {
610 Self::Requirements(s) => Some(s),
611 _ => None,
612 }
613 }
614
615 pub fn into_requirements_section(self) -> Option<RequirementsSection<N>> {
622 match self {
623 Self::Requirements(s) => Some(s),
624 _ => None,
625 }
626 }
627
628 pub fn as_hints_section(&self) -> Option<&TaskHintsSection<N>> {
634 match self {
635 Self::Hints(s) => Some(s),
636 _ => None,
637 }
638 }
639
640 pub fn into_hints_section(self) -> Option<TaskHintsSection<N>> {
646 match self {
647 Self::Hints(s) => Some(s),
648 _ => None,
649 }
650 }
651
652 pub fn as_runtime_section(&self) -> Option<&RuntimeSection<N>> {
658 match self {
659 Self::Runtime(s) => Some(s),
660 _ => None,
661 }
662 }
663
664 pub fn into_runtime_section(self) -> Option<RuntimeSection<N>> {
670 match self {
671 Self::Runtime(s) => Some(s),
672 _ => None,
673 }
674 }
675
676 pub fn as_metadata_section(&self) -> Option<&MetadataSection<N>> {
682 match self {
683 Self::Metadata(s) => Some(s),
684 _ => None,
685 }
686 }
687
688 pub fn into_metadata_section(self) -> Option<MetadataSection<N>> {
694 match self {
695 Self::Metadata(s) => Some(s),
696 _ => None,
697 }
698 }
699
700 pub fn as_parameter_metadata_section(&self) -> Option<&ParameterMetadataSection<N>> {
707 match self {
708 Self::ParameterMetadata(s) => Some(s),
709 _ => None,
710 }
711 }
712
713 pub fn into_parameter_metadata_section(self) -> Option<ParameterMetadataSection<N>> {
720 match self {
721 Self::ParameterMetadata(s) => Some(s),
722 _ => None,
723 }
724 }
725
726 pub fn as_declaration(&self) -> Option<&BoundDecl<N>> {
732 match self {
733 Self::Declaration(d) => Some(d),
734 _ => None,
735 }
736 }
737
738 pub fn into_declaration(self) -> Option<BoundDecl<N>> {
744 match self {
745 Self::Declaration(d) => Some(d),
746 _ => None,
747 }
748 }
749
750 pub fn child(node: &N) -> Option<Self> {
752 node.children().find_map(Self::cast)
753 }
754
755 pub fn children(node: &N) -> impl Iterator<Item = Self> + use<'_, N> {
757 node.children().filter_map(Self::cast)
758 }
759}
760
761#[derive(Clone, Debug, PartialEq, Eq)]
763pub enum SectionParent<N: TreeNode = SyntaxNode> {
764 Task(TaskDefinition<N>),
766 Workflow(WorkflowDefinition<N>),
768 Struct(StructDefinition<N>),
770}
771
772impl<N: TreeNode> SectionParent<N> {
773 pub fn can_cast(kind: SyntaxKind) -> bool {
776 matches!(
777 kind,
778 SyntaxKind::TaskDefinitionNode
779 | SyntaxKind::WorkflowDefinitionNode
780 | SyntaxKind::StructDefinitionNode
781 )
782 }
783
784 pub fn cast(inner: N) -> Option<Self> {
788 match inner.kind() {
789 SyntaxKind::TaskDefinitionNode => Some(Self::Task(
790 TaskDefinition::cast(inner).expect("task definition to cast"),
791 )),
792 SyntaxKind::WorkflowDefinitionNode => Some(Self::Workflow(
793 WorkflowDefinition::cast(inner).expect("workflow definition to cast"),
794 )),
795 SyntaxKind::StructDefinitionNode => Some(Self::Struct(
796 StructDefinition::cast(inner).expect("struct definition to cast"),
797 )),
798 _ => None,
799 }
800 }
801
802 pub fn inner(&self) -> &N {
804 match self {
805 Self::Task(element) => element.inner(),
806 Self::Workflow(element) => element.inner(),
807 Self::Struct(element) => element.inner(),
808 }
809 }
810
811 pub fn name(&self) -> Ident<N::Token> {
813 match self {
814 Self::Task(t) => t.name(),
815 Self::Workflow(w) => w.name(),
816 Self::Struct(s) => s.name(),
817 }
818 }
819
820 pub fn as_task(&self) -> Option<&TaskDefinition<N>> {
826 match self {
827 Self::Task(task) => Some(task),
828 _ => None,
829 }
830 }
831
832 pub fn into_task(self) -> Option<TaskDefinition<N>> {
838 match self {
839 Self::Task(task) => Some(task),
840 _ => None,
841 }
842 }
843
844 pub fn unwrap_task(self) -> TaskDefinition<N> {
850 match self {
851 Self::Task(task) => task,
852 _ => panic!("not a task definition"),
853 }
854 }
855
856 pub fn as_workflow(&self) -> Option<&WorkflowDefinition<N>> {
862 match self {
863 Self::Workflow(workflow) => Some(workflow),
864 _ => None,
865 }
866 }
867
868 pub fn into_workflow(self) -> Option<WorkflowDefinition<N>> {
874 match self {
875 Self::Workflow(workflow) => Some(workflow),
876 _ => None,
877 }
878 }
879
880 pub fn unwrap_workflow(self) -> WorkflowDefinition<N> {
886 match self {
887 Self::Workflow(workflow) => workflow,
888 _ => panic!("not a workflow definition"),
889 }
890 }
891
892 pub fn as_struct(&self) -> Option<&StructDefinition<N>> {
898 match self {
899 Self::Struct(r#struct) => Some(r#struct),
900 _ => None,
901 }
902 }
903
904 pub fn into_struct(self) -> Option<StructDefinition<N>> {
910 match self {
911 Self::Struct(r#struct) => Some(r#struct),
912 _ => None,
913 }
914 }
915
916 pub fn unwrap_struct(self) -> StructDefinition<N> {
922 match self {
923 Self::Struct(def) => def,
924 _ => panic!("not a struct definition"),
925 }
926 }
927
928 pub fn child(node: &N) -> Option<Self> {
930 node.children().find_map(Self::cast)
931 }
932
933 pub fn children(node: &N) -> impl Iterator<Item = Self> + use<'_, N> {
935 node.children().filter_map(Self::cast)
936 }
937}
938
939#[derive(Clone, Debug, PartialEq, Eq)]
941pub struct InputSection<N: TreeNode = SyntaxNode>(N);
942
943impl<N: TreeNode> InputSection<N> {
944 pub fn declarations(&self) -> impl Iterator<Item = Decl<N>> + use<'_, N> {
946 Decl::children(&self.0)
947 }
948
949 pub fn parent(&self) -> SectionParent<N> {
951 SectionParent::cast(self.0.parent().expect("should have a parent"))
952 .expect("parent should cast")
953 }
954}
955
956impl<N: TreeNode> AstNode<N> for InputSection<N> {
957 fn can_cast(kind: SyntaxKind) -> bool {
958 kind == SyntaxKind::InputSectionNode
959 }
960
961 fn cast(inner: N) -> Option<Self> {
962 match inner.kind() {
963 SyntaxKind::InputSectionNode => Some(Self(inner)),
964 _ => None,
965 }
966 }
967
968 fn inner(&self) -> &N {
969 &self.0
970 }
971}
972
973#[derive(Clone, Debug, PartialEq, Eq)]
975pub struct OutputSection<N: TreeNode = SyntaxNode>(N);
976
977impl<N: TreeNode> OutputSection<N> {
978 pub fn declarations(&self) -> impl Iterator<Item = BoundDecl<N>> + use<'_, N> {
980 self.children()
981 }
982
983 pub fn parent(&self) -> SectionParent<N> {
985 SectionParent::cast(self.0.parent().expect("should have a parent"))
986 .expect("parent should cast")
987 }
988}
989
990impl<N: TreeNode> AstNode<N> for OutputSection<N> {
991 fn can_cast(kind: SyntaxKind) -> bool {
992 kind == SyntaxKind::OutputSectionNode
993 }
994
995 fn cast(inner: N) -> Option<Self> {
996 match inner.kind() {
997 SyntaxKind::OutputSectionNode => Some(Self(inner)),
998 _ => None,
999 }
1000 }
1001
1002 fn inner(&self) -> &N {
1003 &self.0
1004 }
1005}
1006
1007#[derive(Clone, Debug, PartialEq, Eq)]
1011pub enum StrippedCommandPart<N: TreeNode = SyntaxNode> {
1012 Text(String),
1014 Placeholder(Placeholder<N>),
1016}
1017
1018#[derive(Clone, Debug, PartialEq, Eq)]
1020pub struct CommandSection<N: TreeNode = SyntaxNode>(N);
1021
1022impl<N: TreeNode> CommandSection<N> {
1023 pub fn is_heredoc(&self) -> bool {
1025 self.token::<OpenHeredoc<N::Token>>().is_some()
1026 }
1027
1028 pub fn parts(&self) -> impl Iterator<Item = CommandPart<N>> + use<'_, N> {
1030 self.0.children_with_tokens().filter_map(CommandPart::cast)
1031 }
1032
1033 pub fn count_whitespace(&self) -> Option<usize> {
1037 let mut min_leading_spaces = usize::MAX;
1038 let mut min_leading_tabs = usize::MAX;
1039 let mut parsing_leading_whitespace = false; let mut leading_spaces = 0;
1042 let mut leading_tabs = 0;
1043 for part in self.parts() {
1044 match part {
1045 CommandPart::Text(text) => {
1046 for c in text.text().chars() {
1047 match c {
1048 ' ' if parsing_leading_whitespace => {
1049 leading_spaces += 1;
1050 }
1051 '\t' if parsing_leading_whitespace => {
1052 leading_tabs += 1;
1053 }
1054 '\n' => {
1055 parsing_leading_whitespace = true;
1056 leading_spaces = 0;
1057 leading_tabs = 0;
1058 }
1059 '\r' => {}
1060 _ => {
1061 if parsing_leading_whitespace {
1062 parsing_leading_whitespace = false;
1063 if leading_spaces == 0 && leading_tabs == 0 {
1064 min_leading_spaces = 0;
1065 min_leading_tabs = 0;
1066 continue;
1067 }
1068 if leading_spaces < min_leading_spaces && leading_spaces > 0 {
1069 min_leading_spaces = leading_spaces;
1070 }
1071 if leading_tabs < min_leading_tabs && leading_tabs > 0 {
1072 min_leading_tabs = leading_tabs;
1073 }
1074 }
1075 }
1076 }
1077 }
1078 }
1080 CommandPart::Placeholder(_) => {
1081 if parsing_leading_whitespace {
1082 parsing_leading_whitespace = false;
1083 if leading_spaces == 0 && leading_tabs == 0 {
1084 min_leading_spaces = 0;
1085 min_leading_tabs = 0;
1086 continue;
1087 }
1088 if leading_spaces < min_leading_spaces && leading_spaces > 0 {
1089 min_leading_spaces = leading_spaces;
1090 }
1091 if leading_tabs < min_leading_tabs && leading_tabs > 0 {
1092 min_leading_tabs = leading_tabs;
1093 }
1094 }
1095 }
1096 }
1097 }
1098
1099 if (min_leading_spaces == 0 && min_leading_tabs == 0)
1101 || (min_leading_spaces == usize::MAX && min_leading_tabs == usize::MAX)
1102 {
1103 return Some(0);
1104 }
1105
1106 if (min_leading_spaces > 0 && min_leading_spaces != usize::MAX)
1108 && (min_leading_tabs > 0 && min_leading_tabs != usize::MAX)
1109 {
1110 return None;
1111 }
1112
1113 let final_leading_whitespace = if min_leading_spaces < min_leading_tabs {
1116 min_leading_spaces
1117 } else {
1118 min_leading_tabs
1119 };
1120
1121 Some(final_leading_whitespace)
1122 }
1123
1124 pub fn strip_whitespace(&self) -> Option<Vec<StrippedCommandPart<N>>> {
1128 let mut result = Vec::new();
1129 let heredoc = self.is_heredoc();
1130 for part in self.parts() {
1131 match part {
1132 CommandPart::Text(text) => {
1133 let mut s = String::new();
1134 unescape_command_text(text.text(), heredoc, &mut s);
1135 result.push(StrippedCommandPart::Text(s));
1136 }
1137 CommandPart::Placeholder(p) => {
1138 result.push(StrippedCommandPart::Placeholder(p));
1139 }
1140 }
1141 }
1142
1143 let mut whole_first_line_trimmed = false;
1145 if let Some(StrippedCommandPart::Text(text)) = result.first_mut() {
1146 let end_of_first_line = text.find('\n').map(|p| p + 1).unwrap_or(text.len());
1147 let line = &text[..end_of_first_line];
1148 let len = line.len() - line.trim_start().len();
1149 whole_first_line_trimmed = len == line.len();
1150 text.replace_range(..len, "");
1151 }
1152
1153 if let Some(StrippedCommandPart::Text(text)) = result.last_mut() {
1155 if let Some(index) = text.rfind(|c| !matches!(c, ' ' | '\t')) {
1156 text.truncate(index + 1);
1157 } else {
1158 text.clear();
1159 }
1160
1161 if text.ends_with('\n') {
1162 text.pop();
1163 }
1164
1165 if text.ends_with('\r') {
1166 text.pop();
1167 }
1168 }
1169
1170 let num_stripped_chars = self.count_whitespace()?;
1172
1173 if num_stripped_chars == 0 {
1175 return Some(result);
1176 }
1177
1178 let mut strip_leading_whitespace = whole_first_line_trimmed;
1182 for part in &mut result {
1183 match part {
1184 StrippedCommandPart::Text(text) => {
1185 let mut offset = 0;
1186 while let Some(next) = text[offset..].find('\n') {
1187 let next = next + offset;
1188 if offset > 0 {
1189 strip_leading_whitespace = true;
1190 }
1191
1192 if !strip_leading_whitespace {
1193 offset = next + 1;
1194 continue;
1195 }
1196
1197 let line = &text[offset..next];
1198 let line = line.strip_suffix('\r').unwrap_or(line);
1199 let len = line.len().min(num_stripped_chars);
1200 text.replace_range(offset..offset + len, "");
1201 offset = next + 1 - len;
1202 }
1203
1204 if strip_leading_whitespace || offset > 0 {
1206 let line = &text[offset..];
1207 let line = line.strip_suffix('\r').unwrap_or(line);
1208 let len = line.len().min(num_stripped_chars);
1209 text.replace_range(offset..offset + len, "");
1210 }
1211 }
1212 StrippedCommandPart::Placeholder(_) => {
1213 strip_leading_whitespace = false;
1214 }
1215 }
1216 }
1217
1218 Some(result)
1219 }
1220
1221 pub fn parent(&self) -> SectionParent<N> {
1223 SectionParent::cast(self.0.parent().expect("should have a parent"))
1224 .expect("parent should cast")
1225 }
1226
1227 pub fn keyword(&self) -> CommandKeyword<N::Token> {
1229 self.token()
1230 .expect("CommandSection must have CommandKeyword")
1231 }
1232}
1233
1234impl<N: TreeNode> AstNode<N> for CommandSection<N> {
1235 fn can_cast(kind: SyntaxKind) -> bool {
1236 kind == SyntaxKind::CommandSectionNode
1237 }
1238
1239 fn cast(inner: N) -> Option<Self> {
1240 match inner.kind() {
1241 SyntaxKind::CommandSectionNode => Some(Self(inner)),
1242 _ => None,
1243 }
1244 }
1245
1246 fn inner(&self) -> &N {
1247 &self.0
1248 }
1249}
1250
1251#[derive(Clone, Debug, PartialEq, Eq)]
1253pub struct CommandText<T: TreeToken = SyntaxToken>(T);
1254
1255impl<T: TreeToken> CommandText<T> {
1256 pub fn unescape_to(&self, heredoc: bool, buffer: &mut String) {
1262 unescape_command_text(self.text(), heredoc, buffer);
1263 }
1264}
1265
1266impl<T: TreeToken> AstToken<T> for CommandText<T> {
1267 fn can_cast(kind: SyntaxKind) -> bool {
1268 kind == SyntaxKind::LiteralCommandText
1269 }
1270
1271 fn cast(inner: T) -> Option<Self> {
1272 match inner.kind() {
1273 SyntaxKind::LiteralCommandText => Some(Self(inner)),
1274 _ => None,
1275 }
1276 }
1277
1278 fn inner(&self) -> &T {
1279 &self.0
1280 }
1281}
1282
1283#[derive(Clone, Debug, PartialEq, Eq)]
1285pub enum CommandPart<N: TreeNode = SyntaxNode> {
1286 Text(CommandText<N::Token>),
1288 Placeholder(Placeholder<N>),
1290}
1291
1292impl<N: TreeNode> CommandPart<N> {
1293 pub fn unwrap_text(self) -> CommandText<N::Token> {
1299 match self {
1300 Self::Text(text) => text,
1301 _ => panic!("not string text"),
1302 }
1303 }
1304
1305 pub fn unwrap_placeholder(self) -> Placeholder<N> {
1311 match self {
1312 Self::Placeholder(p) => p,
1313 _ => panic!("not a placeholder"),
1314 }
1315 }
1316
1317 fn cast(element: NodeOrToken<N, N::Token>) -> Option<Self> {
1321 match element {
1322 NodeOrToken::Node(n) => Some(Self::Placeholder(Placeholder::cast(n)?)),
1323 NodeOrToken::Token(t) => Some(Self::Text(CommandText::cast(t)?)),
1324 }
1325 }
1326}
1327
1328#[derive(Clone, Debug, PartialEq, Eq)]
1330pub struct RequirementsSection<N: TreeNode = SyntaxNode>(N);
1331
1332impl<N: TreeNode> RequirementsSection<N> {
1333 pub fn items(&self) -> impl Iterator<Item = RequirementsItem<N>> + use<'_, N> {
1335 self.children()
1336 }
1337
1338 pub fn parent(&self) -> SectionParent<N> {
1340 SectionParent::cast(self.0.parent().expect("should have a parent"))
1341 .expect("parent should cast")
1342 }
1343
1344 pub fn container(&self) -> Option<requirements::item::Container<N>> {
1347 self.child()
1350 }
1351
1352 pub fn keyword(&self) -> RequirementsKeyword<N::Token> {
1354 self.token()
1355 .expect("RequirementsSection must have RequirementsKeyword")
1356 }
1357}
1358
1359impl<N: TreeNode> AstNode<N> for RequirementsSection<N> {
1360 fn can_cast(kind: SyntaxKind) -> bool {
1361 kind == SyntaxKind::RequirementsSectionNode
1362 }
1363
1364 fn cast(inner: N) -> Option<Self> {
1365 match inner.kind() {
1366 SyntaxKind::RequirementsSectionNode => Some(Self(inner)),
1367 _ => None,
1368 }
1369 }
1370
1371 fn inner(&self) -> &N {
1372 &self.0
1373 }
1374}
1375
1376#[derive(Clone, Debug, PartialEq, Eq)]
1378pub struct RequirementsItem<N: TreeNode = SyntaxNode>(N);
1379
1380impl<N: TreeNode> RequirementsItem<N> {
1381 pub fn name(&self) -> Ident<N::Token> {
1383 self.token().expect("expected an item name")
1384 }
1385
1386 pub fn expr(&self) -> Expr<N> {
1388 Expr::child(&self.0).expect("expected an item expression")
1389 }
1390
1391 pub fn into_container(self) -> Option<requirements::item::Container<N>> {
1394 requirements::item::Container::try_from(self).ok()
1395 }
1396}
1397
1398impl<N: TreeNode> AstNode<N> for RequirementsItem<N> {
1399 fn can_cast(kind: SyntaxKind) -> bool {
1400 kind == SyntaxKind::RequirementsItemNode
1401 }
1402
1403 fn cast(inner: N) -> Option<Self> {
1404 match inner.kind() {
1405 SyntaxKind::RequirementsItemNode => Some(Self(inner)),
1406 _ => None,
1407 }
1408 }
1409
1410 fn inner(&self) -> &N {
1411 &self.0
1412 }
1413}
1414
1415#[derive(Clone, Debug, PartialEq, Eq)]
1417pub struct TaskHintsSection<N: TreeNode = SyntaxNode>(N);
1418
1419impl<N: TreeNode> TaskHintsSection<N> {
1420 pub fn items(&self) -> impl Iterator<Item = TaskHintsItem<N>> + use<'_, N> {
1422 self.children()
1423 }
1424
1425 pub fn parent(&self) -> TaskDefinition<N> {
1427 TaskDefinition::cast(self.0.parent().expect("should have a parent"))
1428 .expect("parent should cast")
1429 }
1430}
1431
1432impl<N: TreeNode> AstNode<N> for TaskHintsSection<N> {
1433 fn can_cast(kind: SyntaxKind) -> bool {
1434 kind == SyntaxKind::TaskHintsSectionNode
1435 }
1436
1437 fn cast(inner: N) -> Option<Self> {
1438 match inner.kind() {
1439 SyntaxKind::TaskHintsSectionNode => Some(Self(inner)),
1440 _ => None,
1441 }
1442 }
1443
1444 fn inner(&self) -> &N {
1445 &self.0
1446 }
1447}
1448
1449#[derive(Clone, Debug, PartialEq, Eq)]
1451pub struct TaskHintsItem<N: TreeNode = SyntaxNode>(N);
1452
1453impl<N: TreeNode> TaskHintsItem<N> {
1454 pub fn name(&self) -> Ident<N::Token> {
1456 self.token().expect("expected an item name")
1457 }
1458
1459 pub fn expr(&self) -> Expr<N> {
1461 Expr::child(&self.0).expect("expected an item expression")
1462 }
1463}
1464
1465impl<N: TreeNode> AstNode<N> for TaskHintsItem<N> {
1466 fn can_cast(kind: SyntaxKind) -> bool {
1467 kind == SyntaxKind::TaskHintsItemNode
1468 }
1469
1470 fn cast(inner: N) -> Option<Self> {
1471 match inner.kind() {
1472 SyntaxKind::TaskHintsItemNode => Some(Self(inner)),
1473 _ => None,
1474 }
1475 }
1476
1477 fn inner(&self) -> &N {
1478 &self.0
1479 }
1480}
1481
1482#[derive(Clone, Debug, PartialEq, Eq)]
1484pub struct RuntimeSection<N: TreeNode = SyntaxNode>(N);
1485
1486impl<N: TreeNode> RuntimeSection<N> {
1487 pub fn items(&self) -> impl Iterator<Item = RuntimeItem<N>> + use<'_, N> {
1489 self.children()
1490 }
1491
1492 pub fn parent(&self) -> SectionParent<N> {
1494 SectionParent::cast(self.0.parent().expect("should have a parent"))
1495 .expect("parent should cast")
1496 }
1497
1498 pub fn container(&self) -> Option<runtime::item::Container<N>> {
1501 self.child()
1504 }
1505}
1506
1507impl<N: TreeNode> AstNode<N> for RuntimeSection<N> {
1508 fn can_cast(kind: SyntaxKind) -> bool {
1509 kind == SyntaxKind::RuntimeSectionNode
1510 }
1511
1512 fn cast(inner: N) -> Option<Self> {
1513 match inner.kind() {
1514 SyntaxKind::RuntimeSectionNode => Some(Self(inner)),
1515 _ => None,
1516 }
1517 }
1518
1519 fn inner(&self) -> &N {
1520 &self.0
1521 }
1522}
1523
1524#[derive(Clone, Debug, PartialEq, Eq)]
1526pub struct RuntimeItem<N: TreeNode = SyntaxNode>(N);
1527
1528impl<N: TreeNode> RuntimeItem<N> {
1529 pub fn name(&self) -> Ident<N::Token> {
1531 self.token().expect("expected an item name")
1532 }
1533
1534 pub fn expr(&self) -> Expr<N> {
1536 Expr::child(&self.0).expect("expected an item expression")
1537 }
1538
1539 pub fn into_container(self) -> Option<runtime::item::Container<N>> {
1542 runtime::item::Container::try_from(self).ok()
1543 }
1544}
1545
1546impl<N: TreeNode> AstNode<N> for RuntimeItem<N> {
1547 fn can_cast(kind: SyntaxKind) -> bool {
1548 kind == SyntaxKind::RuntimeItemNode
1549 }
1550
1551 fn cast(inner: N) -> Option<Self> {
1552 match inner.kind() {
1553 SyntaxKind::RuntimeItemNode => Some(Self(inner)),
1554 _ => None,
1555 }
1556 }
1557
1558 fn inner(&self) -> &N {
1559 &self.0
1560 }
1561}
1562
1563#[derive(Clone, Debug, PartialEq, Eq)]
1565pub struct MetadataSection<N: TreeNode = SyntaxNode>(N);
1566
1567impl<N: TreeNode> MetadataSection<N> {
1568 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1570 self.children()
1571 }
1572
1573 pub fn parent(&self) -> SectionParent<N> {
1575 SectionParent::cast(self.0.parent().expect("should have a parent"))
1576 .expect("parent should cast")
1577 }
1578
1579 pub fn keyword(&self) -> MetaKeyword<N::Token> {
1581 self.token().expect("MetadataSection must have MetaKeyword")
1582 }
1583}
1584
1585impl<N: TreeNode> AstNode<N> for MetadataSection<N> {
1586 fn can_cast(kind: SyntaxKind) -> bool {
1587 kind == SyntaxKind::MetadataSectionNode
1588 }
1589
1590 fn cast(inner: N) -> Option<Self> {
1591 match inner.kind() {
1592 SyntaxKind::MetadataSectionNode => Some(Self(inner)),
1593 _ => None,
1594 }
1595 }
1596
1597 fn inner(&self) -> &N {
1598 &self.0
1599 }
1600}
1601
1602#[derive(Clone, Debug, PartialEq, Eq)]
1604pub struct MetadataObjectItem<N: TreeNode = SyntaxNode>(N);
1605
1606impl<N: TreeNode> MetadataObjectItem<N> {
1607 pub fn name(&self) -> Ident<N::Token> {
1609 self.token().expect("expected a name")
1610 }
1611
1612 pub fn value(&self) -> MetadataValue<N> {
1614 self.child().expect("expected a value")
1615 }
1616}
1617
1618impl<N: TreeNode> AstNode<N> for MetadataObjectItem<N> {
1619 fn can_cast(kind: SyntaxKind) -> bool {
1620 kind == SyntaxKind::MetadataObjectItemNode
1621 }
1622
1623 fn cast(inner: N) -> Option<Self> {
1624 match inner.kind() {
1625 SyntaxKind::MetadataObjectItemNode => Some(Self(inner)),
1626 _ => None,
1627 }
1628 }
1629
1630 fn inner(&self) -> &N {
1631 &self.0
1632 }
1633}
1634
1635#[derive(Clone, Debug, PartialEq, Eq)]
1637pub enum MetadataValue<N: TreeNode = SyntaxNode> {
1638 Boolean(LiteralBoolean<N>),
1640 Integer(LiteralInteger<N>),
1642 Float(LiteralFloat<N>),
1644 String(LiteralString<N>),
1646 Null(LiteralNull<N>),
1648 Object(MetadataObject<N>),
1650 Array(MetadataArray<N>),
1652}
1653
1654impl<N: TreeNode> MetadataValue<N> {
1655 pub fn unwrap_boolean(self) -> LiteralBoolean<N> {
1661 match self {
1662 Self::Boolean(b) => b,
1663 _ => panic!("not a boolean"),
1664 }
1665 }
1666
1667 pub fn unwrap_integer(self) -> LiteralInteger<N> {
1673 match self {
1674 Self::Integer(i) => i,
1675 _ => panic!("not an integer"),
1676 }
1677 }
1678
1679 pub fn unwrap_float(self) -> LiteralFloat<N> {
1685 match self {
1686 Self::Float(f) => f,
1687 _ => panic!("not a float"),
1688 }
1689 }
1690
1691 pub fn unwrap_string(self) -> LiteralString<N> {
1697 match self {
1698 Self::String(s) => s,
1699 _ => panic!("not a string"),
1700 }
1701 }
1702
1703 pub fn unwrap_null(self) -> LiteralNull<N> {
1709 match self {
1710 Self::Null(n) => n,
1711 _ => panic!("not a null"),
1712 }
1713 }
1714
1715 pub fn unwrap_object(self) -> MetadataObject<N> {
1721 match self {
1722 Self::Object(o) => o,
1723 _ => panic!("not an object"),
1724 }
1725 }
1726
1727 pub fn unwrap_array(self) -> MetadataArray<N> {
1733 match self {
1734 Self::Array(a) => a,
1735 _ => panic!("not an array"),
1736 }
1737 }
1738}
1739
1740impl<N: TreeNode> AstNode<N> for MetadataValue<N> {
1741 fn can_cast(kind: SyntaxKind) -> bool {
1742 matches!(
1743 kind,
1744 SyntaxKind::LiteralBooleanNode
1745 | SyntaxKind::LiteralIntegerNode
1746 | SyntaxKind::LiteralFloatNode
1747 | SyntaxKind::LiteralStringNode
1748 | SyntaxKind::LiteralNullNode
1749 | SyntaxKind::MetadataObjectNode
1750 | SyntaxKind::MetadataArrayNode
1751 )
1752 }
1753
1754 fn cast(inner: N) -> Option<Self> {
1755 match inner.kind() {
1756 SyntaxKind::LiteralBooleanNode => Some(Self::Boolean(LiteralBoolean(inner))),
1757 SyntaxKind::LiteralIntegerNode => Some(Self::Integer(LiteralInteger(inner))),
1758 SyntaxKind::LiteralFloatNode => Some(Self::Float(LiteralFloat(inner))),
1759 SyntaxKind::LiteralStringNode => Some(Self::String(LiteralString(inner))),
1760 SyntaxKind::LiteralNullNode => Some(Self::Null(LiteralNull(inner))),
1761 SyntaxKind::MetadataObjectNode => Some(Self::Object(MetadataObject(inner))),
1762 SyntaxKind::MetadataArrayNode => Some(Self::Array(MetadataArray(inner))),
1763 _ => None,
1764 }
1765 }
1766
1767 fn inner(&self) -> &N {
1768 match self {
1769 Self::Boolean(b) => &b.0,
1770 Self::Integer(i) => &i.0,
1771 Self::Float(f) => &f.0,
1772 Self::String(s) => &s.0,
1773 Self::Null(n) => &n.0,
1774 Self::Object(o) => &o.0,
1775 Self::Array(a) => &a.0,
1776 }
1777 }
1778}
1779
1780#[derive(Clone, Debug, PartialEq, Eq)]
1782pub struct LiteralNull<N: TreeNode = SyntaxNode>(N);
1783
1784impl<N: TreeNode> AstNode<N> for LiteralNull<N> {
1785 fn can_cast(kind: SyntaxKind) -> bool {
1786 kind == SyntaxKind::LiteralNullNode
1787 }
1788
1789 fn cast(inner: N) -> Option<Self> {
1790 match inner.kind() {
1791 SyntaxKind::LiteralNullNode => Some(Self(inner)),
1792 _ => None,
1793 }
1794 }
1795
1796 fn inner(&self) -> &N {
1797 &self.0
1798 }
1799}
1800
1801#[derive(Clone, Debug, PartialEq, Eq)]
1803pub struct MetadataObject<N: TreeNode = SyntaxNode>(N);
1804
1805impl<N: TreeNode> MetadataObject<N> {
1806 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1808 self.children()
1809 }
1810}
1811
1812impl<N: TreeNode> AstNode<N> for MetadataObject<N> {
1813 fn can_cast(kind: SyntaxKind) -> bool {
1814 kind == SyntaxKind::MetadataObjectNode
1815 }
1816
1817 fn cast(inner: N) -> Option<Self> {
1818 match inner.kind() {
1819 SyntaxKind::MetadataObjectNode => Some(Self(inner)),
1820 _ => None,
1821 }
1822 }
1823
1824 fn inner(&self) -> &N {
1825 &self.0
1826 }
1827}
1828
1829#[derive(Clone, Debug, PartialEq, Eq)]
1831pub struct MetadataArray<N: TreeNode = SyntaxNode>(N);
1832
1833impl<N: TreeNode> MetadataArray<N> {
1834 pub fn elements(&self) -> impl Iterator<Item = MetadataValue<N>> + use<'_, N> {
1836 self.children()
1837 }
1838}
1839
1840impl<N: TreeNode> AstNode<N> for MetadataArray<N> {
1841 fn can_cast(kind: SyntaxKind) -> bool {
1842 kind == SyntaxKind::MetadataArrayNode
1843 }
1844
1845 fn cast(inner: N) -> Option<Self> {
1846 match inner.kind() {
1847 SyntaxKind::MetadataArrayNode => Some(Self(inner)),
1848 _ => None,
1849 }
1850 }
1851
1852 fn inner(&self) -> &N {
1853 &self.0
1854 }
1855}
1856
1857#[derive(Clone, Debug, PartialEq, Eq)]
1859pub struct ParameterMetadataSection<N: TreeNode = SyntaxNode>(N);
1860
1861impl<N: TreeNode> ParameterMetadataSection<N> {
1862 pub fn items(&self) -> impl Iterator<Item = MetadataObjectItem<N>> + use<'_, N> {
1864 self.children()
1865 }
1866
1867 pub fn parent(&self) -> SectionParent<N> {
1869 SectionParent::cast(self.0.parent().expect("should have a parent"))
1870 .expect("parent should cast")
1871 }
1872
1873 pub fn keyword(&self) -> ParameterMetaKeyword<N::Token> {
1875 self.token()
1876 .expect("ParameterMetadataSection must have ParameterMetaKeyword")
1877 }
1878}
1879
1880impl<N: TreeNode> AstNode<N> for ParameterMetadataSection<N> {
1881 fn can_cast(kind: SyntaxKind) -> bool {
1882 kind == SyntaxKind::ParameterMetadataSectionNode
1883 }
1884
1885 fn cast(inner: N) -> Option<Self> {
1886 match inner.kind() {
1887 SyntaxKind::ParameterMetadataSectionNode => Some(Self(inner)),
1888 _ => None,
1889 }
1890 }
1891
1892 fn inner(&self) -> &N {
1893 &self.0
1894 }
1895}
1896
1897#[cfg(test)]
1898mod test {
1899 use pretty_assertions::assert_eq;
1900
1901 use super::*;
1902 use crate::Document;
1903
1904 #[test]
1905 fn tasks() {
1906 let (document, diagnostics) = Document::parse(
1907 r#"
1908version 1.2
1909
1910task test {
1911 input {
1912 String name
1913 }
1914
1915 output {
1916 String greeting = stdout()
1917 }
1918
1919 command <<<
1920 printf "hello, ~{name}!
1921 >>>
1922
1923 requirements {
1924 container: "baz/qux"
1925 }
1926
1927 hints {
1928 foo: "bar"
1929 }
1930
1931 runtime {
1932 container: "foo/bar"
1933 }
1934
1935 meta {
1936 description: "a test"
1937 foo: null
1938 }
1939
1940 parameter_meta {
1941 name: {
1942 help: "a name to greet"
1943 }
1944 }
1945
1946 String x = "private"
1947}
1948"#,
1949 None,
1950 );
1951
1952 assert!(diagnostics.is_empty());
1953 let ast = document.ast();
1954 let ast = ast.as_v1().expect("should be a V1 AST");
1955 let tasks: Vec<_> = ast.tasks().collect();
1956 assert_eq!(tasks.len(), 1);
1957 assert_eq!(tasks[0].name().text(), "test");
1958
1959 let input = tasks[0].input().expect("should have an input section");
1961 assert_eq!(input.parent().unwrap_task().name().text(), "test");
1962 let decls: Vec<_> = input.declarations().collect();
1963 assert_eq!(decls.len(), 1);
1964 assert_eq!(
1965 decls[0].clone().unwrap_unbound_decl().ty().to_string(),
1966 "String"
1967 );
1968 assert_eq!(decls[0].clone().unwrap_unbound_decl().name().text(), "name");
1969
1970 let output = tasks[0].output().expect("should have an output section");
1972 assert_eq!(output.parent().unwrap_task().name().text(), "test");
1973 let decls: Vec<_> = output.declarations().collect();
1974 assert_eq!(decls.len(), 1);
1975 assert_eq!(decls[0].ty().to_string(), "String");
1976 assert_eq!(decls[0].name().text(), "greeting");
1977 assert_eq!(decls[0].expr().unwrap_call().target().text(), "stdout");
1978
1979 let command = tasks[0].command().expect("should have a command section");
1981 assert_eq!(command.parent().name().text(), "test");
1982 assert!(command.is_heredoc());
1983 let parts: Vec<_> = command.parts().collect();
1984 assert_eq!(parts.len(), 3);
1985 assert_eq!(
1986 parts[0].clone().unwrap_text().text(),
1987 "\n printf \"hello, "
1988 );
1989 assert_eq!(
1990 parts[1]
1991 .clone()
1992 .unwrap_placeholder()
1993 .expr()
1994 .unwrap_name_ref()
1995 .name()
1996 .text(),
1997 "name"
1998 );
1999 assert_eq!(parts[2].clone().unwrap_text().text(), "!\n ");
2000
2001 let requirements = tasks[0]
2003 .requirements()
2004 .expect("should have a requirements section");
2005 assert_eq!(requirements.parent().name().text(), "test");
2006 let items: Vec<_> = requirements.items().collect();
2007 assert_eq!(items.len(), 1);
2008 assert_eq!(items[0].name().text(), TASK_REQUIREMENT_CONTAINER);
2009 assert_eq!(
2010 items[0]
2011 .expr()
2012 .unwrap_literal()
2013 .unwrap_string()
2014 .text()
2015 .unwrap()
2016 .text(),
2017 "baz/qux"
2018 );
2019
2020 let hints = tasks[0].hints().expect("should have a hints section");
2022 assert_eq!(hints.parent().name().text(), "test");
2023 let items: Vec<_> = hints.items().collect();
2024 assert_eq!(items.len(), 1);
2025 assert_eq!(items[0].name().text(), "foo");
2026 assert_eq!(
2027 items[0]
2028 .expr()
2029 .unwrap_literal()
2030 .unwrap_string()
2031 .text()
2032 .unwrap()
2033 .text(),
2034 "bar"
2035 );
2036
2037 let runtime = tasks[0].runtime().expect("should have a runtime section");
2039 assert_eq!(runtime.parent().name().text(), "test");
2040 let items: Vec<_> = runtime.items().collect();
2041 assert_eq!(items.len(), 1);
2042 assert_eq!(items[0].name().text(), TASK_REQUIREMENT_CONTAINER);
2043 assert_eq!(
2044 items[0]
2045 .expr()
2046 .unwrap_literal()
2047 .unwrap_string()
2048 .text()
2049 .unwrap()
2050 .text(),
2051 "foo/bar"
2052 );
2053
2054 let metadata = tasks[0].metadata().expect("should have a metadata section");
2056 assert_eq!(metadata.parent().unwrap_task().name().text(), "test");
2057 let items: Vec<_> = metadata.items().collect();
2058 assert_eq!(items.len(), 2);
2059 assert_eq!(items[0].name().text(), "description");
2060 assert_eq!(
2061 items[0].value().unwrap_string().text().unwrap().text(),
2062 "a test"
2063 );
2064
2065 assert_eq!(items[1].name().text(), "foo");
2067 items[1].value().unwrap_null();
2068
2069 let param_meta = tasks[0]
2071 .parameter_metadata()
2072 .expect("should have a parameter metadata section");
2073 assert_eq!(param_meta.parent().unwrap_task().name().text(), "test");
2074 let items: Vec<_> = param_meta.items().collect();
2075 assert_eq!(items.len(), 1);
2076 assert_eq!(items[0].name().text(), "name");
2077 let items: Vec<_> = items[0].value().unwrap_object().items().collect();
2078 assert_eq!(items.len(), 1);
2079 assert_eq!(items[0].name().text(), "help");
2080 assert_eq!(
2081 items[0].value().unwrap_string().text().unwrap().text(),
2082 "a name to greet"
2083 );
2084
2085 let decls: Vec<_> = tasks[0].declarations().collect();
2087 assert_eq!(decls.len(), 1);
2088
2089 assert_eq!(decls[0].ty().to_string(), "String");
2091 assert_eq!(decls[0].name().text(), "x");
2092 assert_eq!(
2093 decls[0]
2094 .expr()
2095 .unwrap_literal()
2096 .unwrap_string()
2097 .text()
2098 .unwrap()
2099 .text(),
2100 "private"
2101 );
2102 }
2103
2104 #[test]
2105 fn whitespace_stripping_without_interpolation() {
2106 let (document, diagnostics) = Document::parse(
2107 r#"
2108version 1.2
2109
2110task test {
2111 command <<<
2112 echo "hello"
2113 echo "world"
2114 echo \
2115 "goodbye"
2116 >>>
2117}
2118"#,
2119 None,
2120 );
2121
2122 assert!(diagnostics.is_empty());
2123 let ast = document.ast();
2124 let ast = ast.as_v1().expect("should be a V1 AST");
2125 let tasks: Vec<_> = ast.tasks().collect();
2126 assert_eq!(tasks.len(), 1);
2127
2128 let command = tasks[0].command().expect("should have a command section");
2129
2130 let stripped = command.strip_whitespace().unwrap();
2131
2132 assert_eq!(stripped.len(), 1);
2133 let text = match &stripped[0] {
2134 StrippedCommandPart::Text(text) => text,
2135 _ => panic!("expected text"),
2136 };
2137 assert_eq!(
2138 text,
2139 "echo \"hello\"\necho \"world\"\necho \\\n \"goodbye\""
2140 );
2141 }
2142
2143 #[test]
2144 fn whitespace_stripping_with_interpolation() {
2145 let (document, diagnostics) = Document::parse(
2146 r#"
2147version 1.2
2148
2149task test {
2150 input {
2151 String name
2152 Boolean flag
2153 }
2154
2155 command <<<
2156 echo "hello, ~{
2157if flag
2158then name
2159 else "Jerry"
2160 }!"
2161 >>>
2162}
2163 "#,
2164 None,
2165 );
2166
2167 assert!(diagnostics.is_empty());
2168 let ast = document.ast();
2169 let ast = ast.as_v1().expect("should be a V1 AST");
2170 let tasks: Vec<_> = ast.tasks().collect();
2171 assert_eq!(tasks.len(), 1);
2172
2173 let command = tasks[0].command().expect("should have a command section");
2174
2175 let stripped = command.strip_whitespace().unwrap();
2176 assert_eq!(stripped.len(), 3);
2177 let text = match &stripped[0] {
2178 StrippedCommandPart::Text(text) => text,
2179 _ => panic!("expected text"),
2180 };
2181 assert_eq!(text, "echo \"hello, ");
2182
2183 let _placeholder = match &stripped[1] {
2184 StrippedCommandPart::Placeholder(p) => p,
2185 _ => panic!("expected placeholder"),
2186 };
2187 let text = match &stripped[2] {
2190 StrippedCommandPart::Text(text) => text,
2191 _ => panic!("expected text"),
2192 };
2193 assert_eq!(text, "!\"");
2194 }
2195
2196 #[test]
2197 fn whitespace_stripping_when_interpolation_starts_line() {
2198 let (document, diagnostics) = Document::parse(
2199 r#"
2200version 1.2
2201
2202task test {
2203 input {
2204 Int placeholder
2205 }
2206
2207 command <<<
2208 # other weird whitespace
2209 ~{placeholder} "$trailing_pholder" ~{placeholder}
2210 ~{placeholder} somecommand.py "$leading_pholder"
2211 >>>
2212}
2213"#,
2214 None,
2215 );
2216
2217 assert!(diagnostics.is_empty());
2218 let ast = document.ast();
2219 let ast = ast.as_v1().expect("should be a V1 AST");
2220 let tasks: Vec<_> = ast.tasks().collect();
2221 assert_eq!(tasks.len(), 1);
2222
2223 let command = tasks[0].command().expect("should have a command section");
2224
2225 let stripped = command.strip_whitespace().unwrap();
2226 assert_eq!(stripped.len(), 7);
2227 let text = match &stripped[0] {
2228 StrippedCommandPart::Text(text) => text,
2229 _ => panic!("expected text"),
2230 };
2231 assert_eq!(text, " # other weird whitespace\n");
2232
2233 let _placeholder = match &stripped[1] {
2234 StrippedCommandPart::Placeholder(p) => p,
2235 _ => panic!("expected placeholder"),
2236 };
2237 let text = match &stripped[2] {
2240 StrippedCommandPart::Text(text) => text,
2241 _ => panic!("expected text"),
2242 };
2243 assert_eq!(text, " \"$trailing_pholder\" ");
2244
2245 let _placeholder = match &stripped[3] {
2246 StrippedCommandPart::Placeholder(p) => p,
2247 _ => panic!("expected placeholder"),
2248 };
2249 let text = match &stripped[4] {
2252 StrippedCommandPart::Text(text) => text,
2253 _ => panic!("expected text"),
2254 };
2255 assert_eq!(text, "\n");
2256
2257 let _placeholder = match &stripped[5] {
2258 StrippedCommandPart::Placeholder(p) => p,
2259 _ => panic!("expected placeholder"),
2260 };
2261 let text = match &stripped[6] {
2264 StrippedCommandPart::Text(text) => text,
2265 _ => panic!("expected text"),
2266 };
2267 assert_eq!(text, " somecommand.py \"$leading_pholder\"");
2268 }
2269
2270 #[test]
2271 fn whitespace_stripping_when_command_is_empty() {
2272 let (document, diagnostics) = Document::parse(
2273 r#"
2274version 1.2
2275
2276task test {
2277 command <<<>>>
2278}
2279 "#,
2280 None,
2281 );
2282
2283 assert!(diagnostics.is_empty());
2284 let ast = document.ast();
2285 let ast = ast.as_v1().expect("should be a V1 AST");
2286 let tasks: Vec<_> = ast.tasks().collect();
2287 assert_eq!(tasks.len(), 1);
2288
2289 let command = tasks[0].command().expect("should have a command section");
2290
2291 let stripped = command.strip_whitespace().unwrap();
2292 assert_eq!(stripped.len(), 0);
2293 }
2294
2295 #[test]
2296 fn whitespace_stripping_when_command_is_one_line_of_whitespace() {
2297 let (document, diagnostics) = Document::parse(
2298 r#"
2299version 1.2
2300
2301task test {
2302 command <<< >>>
2303}
2304 "#,
2305 None,
2306 );
2307
2308 assert!(diagnostics.is_empty());
2309 let ast = document.ast();
2310 let ast = ast.as_v1().expect("should be a V1 AST");
2311 let tasks: Vec<_> = ast.tasks().collect();
2312 assert_eq!(tasks.len(), 1);
2313
2314 let command = tasks[0].command().expect("should have a command section");
2315
2316 let stripped = command.strip_whitespace().unwrap();
2317 assert_eq!(stripped.len(), 1);
2318 let text = match &stripped[0] {
2319 StrippedCommandPart::Text(text) => text,
2320 _ => panic!("expected text"),
2321 };
2322 assert_eq!(text, "");
2323 }
2324
2325 #[test]
2326 fn whitespace_stripping_when_command_is_one_newline() {
2327 let (document, diagnostics) = Document::parse(
2328 r#"
2329version 1.2
2330
2331task test {
2332 command <<<
2333 >>>
2334}
2335 "#,
2336 None,
2337 );
2338
2339 assert!(diagnostics.is_empty());
2340 let ast = document.ast();
2341 let ast = ast.as_v1().expect("should be a V1 AST");
2342 let tasks: Vec<_> = ast.tasks().collect();
2343 assert_eq!(tasks.len(), 1);
2344
2345 let command = tasks[0].command().expect("should have a command section");
2346
2347 let stripped = command.strip_whitespace().unwrap();
2348 assert_eq!(stripped.len(), 1);
2349 let text = match &stripped[0] {
2350 StrippedCommandPart::Text(text) => text,
2351 _ => panic!("expected text"),
2352 };
2353 assert_eq!(text, "");
2354 }
2355
2356 #[test]
2357 fn whitespace_stripping_when_command_is_a_blank_line() {
2358 let (document, diagnostics) = Document::parse(
2359 r#"
2360version 1.2
2361
2362task test {
2363 command <<<
2364
2365 >>>
2366}
2367 "#,
2368 None,
2369 );
2370
2371 assert!(diagnostics.is_empty());
2372 let ast = document.ast();
2373 let ast = ast.as_v1().expect("should be a V1 AST");
2374 let tasks: Vec<_> = ast.tasks().collect();
2375 assert_eq!(tasks.len(), 1);
2376
2377 let command = tasks[0].command().expect("should have a command section");
2378
2379 let stripped = command.strip_whitespace().unwrap();
2380 assert_eq!(stripped.len(), 1);
2381 let text = match &stripped[0] {
2382 StrippedCommandPart::Text(text) => text,
2383 _ => panic!("expected text"),
2384 };
2385 assert_eq!(text, "");
2386 }
2387
2388 #[test]
2389 fn whitespace_stripping_when_command_is_a_blank_line_with_spaces() {
2390 let (document, diagnostics) = Document::parse(
2391 r#"
2392version 1.2
2393
2394task test {
2395 command <<<
2396
2397 >>>
2398}
2399 "#,
2400 None,
2401 );
2402
2403 assert!(diagnostics.is_empty());
2404 let ast = document.ast();
2405 let ast = ast.as_v1().expect("should be a V1 AST");
2406 let tasks: Vec<_> = ast.tasks().collect();
2407 assert_eq!(tasks.len(), 1);
2408
2409 let command = tasks[0].command().expect("should have a command section");
2410
2411 let stripped = command.strip_whitespace().unwrap();
2412 assert_eq!(stripped.len(), 1);
2413 let text = match &stripped[0] {
2414 StrippedCommandPart::Text(text) => text,
2415 _ => panic!("expected text"),
2416 };
2417 assert_eq!(text, " ");
2418 }
2419
2420 #[test]
2421 fn whitespace_stripping_with_mixed_indentation() {
2422 let (document, diagnostics) = Document::parse(
2423 r#"
2424version 1.2
2425
2426task test {
2427 command <<<
2428 echo "hello"
2429 echo "world"
2430 echo \
2431 "goodbye"
2432 >>>
2433 }"#,
2434 None,
2435 );
2436
2437 assert!(diagnostics.is_empty());
2438 let ast = document.ast();
2439 let ast = ast.as_v1().expect("should be a V1 AST");
2440 let tasks: Vec<_> = ast.tasks().collect();
2441 assert_eq!(tasks.len(), 1);
2442
2443 let command = tasks[0].command().expect("should have a command section");
2444
2445 let stripped = command.strip_whitespace();
2446 assert!(stripped.is_none());
2447 }
2448
2449 #[test]
2450 fn whitespace_stripping_with_funky_indentation() {
2451 let (document, diagnostics) = Document::parse(
2452 r#"
2453version 1.2
2454
2455task test {
2456 command <<<
2457 echo "hello"
2458 echo "world"
2459 echo \
2460 "goodbye"
2461 >>>
2462 }"#,
2463 None,
2464 );
2465
2466 assert!(diagnostics.is_empty());
2467 let ast = document.ast();
2468 let ast = ast.as_v1().expect("should be a V1 AST");
2469 let tasks: Vec<_> = ast.tasks().collect();
2470 assert_eq!(tasks.len(), 1);
2471
2472 let command = tasks[0].command().expect("should have a command section");
2473
2474 let stripped = command.strip_whitespace().unwrap();
2475 assert_eq!(stripped.len(), 1);
2476 let text = match &stripped[0] {
2477 StrippedCommandPart::Text(text) => text,
2478 _ => panic!("expected text"),
2479 };
2480 assert_eq!(
2481 text,
2482 "echo \"hello\"\n echo \"world\"\necho \\\n \"goodbye\""
2483 );
2484 }
2485
2486 #[test]
2488 fn whitespace_stripping_with_content_on_first_line() {
2489 let (document, diagnostics) = Document::parse(
2490 r#"
2491version 1.2
2492
2493task test {
2494 command <<< weird stuff $firstlinelint
2495 # other weird whitespace
2496 somecommand.py $line120 ~{placeholder}
2497 >>>
2498 }"#,
2499 None,
2500 );
2501
2502 assert!(diagnostics.is_empty());
2503 let ast = document.ast();
2504 let ast = ast.as_v1().expect("should be a V1 AST");
2505 let tasks: Vec<_> = ast.tasks().collect();
2506 assert_eq!(tasks.len(), 1);
2507
2508 let command = tasks[0].command().expect("should have a command section");
2509
2510 let stripped = command.strip_whitespace().unwrap();
2511 assert_eq!(stripped.len(), 3);
2512 let text = match &stripped[0] {
2513 StrippedCommandPart::Text(text) => text,
2514 _ => panic!("expected text"),
2515 };
2516 assert_eq!(
2517 text,
2518 "weird stuff $firstlinelint\n # other weird whitespace\nsomecommand.py $line120 "
2519 );
2520
2521 let _placeholder = match &stripped[1] {
2522 StrippedCommandPart::Placeholder(p) => p,
2523 _ => panic!("expected placeholder"),
2524 };
2525 let text = match &stripped[2] {
2528 StrippedCommandPart::Text(text) => text,
2529 _ => panic!("expected text"),
2530 };
2531 assert_eq!(text, "");
2532 }
2533
2534 #[test]
2535 fn whitespace_stripping_on_windows() {
2536 let (document, diagnostics) = Document::parse(
2537 "version 1.2\r\ntask test {\r\n command <<<\r\n echo \"hello\"\r\n \
2538 >>>\r\n}\r\n",
2539 None,
2540 );
2541
2542 assert!(diagnostics.is_empty());
2543 let ast = document.ast();
2544 let ast = ast.as_v1().expect("should be a V1 AST");
2545 let tasks: Vec<_> = ast.tasks().collect();
2546 assert_eq!(tasks.len(), 1);
2547
2548 let command = tasks[0].command().expect("should have a command section");
2549 let stripped = command.strip_whitespace().unwrap();
2550 assert_eq!(stripped.len(), 1);
2551 let text = match &stripped[0] {
2552 StrippedCommandPart::Text(text) => text,
2553 _ => panic!("expected text"),
2554 };
2555 assert_eq!(text, "echo \"hello\"");
2556 }
2557}