1use std::cell::Cell;
4use std::fmt;
5use std::fmt::Write;
6use std::sync::LazyLock;
7
8use indexmap::IndexMap;
9use indexmap::IndexSet;
10use wdl_ast::SupportedVersion;
11use wdl_ast::version::V1;
12
13use crate::types::ArrayType;
14use crate::types::Coercible;
15use crate::types::CompoundType;
16use crate::types::MapType;
17use crate::types::Optional;
18use crate::types::PairType;
19use crate::types::PrimitiveType;
20use crate::types::Type;
21
22mod constraints;
23
24pub use constraints::*;
25
26pub const MAX_TYPE_PARAMETERS: usize = 4;
34
35#[allow(clippy::missing_docs_in_private_items)]
36const _: () = assert!(
37 MAX_TYPE_PARAMETERS < usize::BITS as usize,
38 "the maximum number of type parameters cannot exceed the number of bits in usize"
39);
40
41pub const MAX_PARAMETERS: usize = 4;
51
52fn write_uninferred_constraints(
55 s: &mut impl fmt::Write,
56 params: &TypeParameters<'_>,
57) -> Result<(), fmt::Error> {
58 for (i, (name, constraint)) in params
59 .referenced()
60 .filter_map(|(p, ty)| {
61 if ty.is_some() {
63 return None;
64 }
65
66 Some((p.name, p.constraint()?))
67 })
68 .enumerate()
69 {
70 if i == 0 {
71 s.write_str(" where ")?;
72 } else if i > 1 {
73 s.write_str(", ")?;
74 }
75
76 write!(s, "`{name}`: {desc}", desc = constraint.description())?;
77 }
78
79 Ok(())
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum FunctionBindError {
86 RequiresVersion(SupportedVersion),
88 TooFewArguments(usize),
92 TooManyArguments(usize),
96 ArgumentTypeMismatch {
98 index: usize,
100 expected: String,
102 },
103 Ambiguous {
105 first: String,
107 second: String,
109 },
110}
111
112#[derive(Debug, Clone)]
114pub enum GenericType {
115 Parameter(&'static str),
117 UnqualifiedParameter(&'static str),
121 Array(GenericArrayType),
123 Pair(GenericPairType),
125 Map(GenericMapType),
127 EnumInnerValue(GenericEnumInnerValueType),
129}
130
131impl GenericType {
132 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
134 #[allow(clippy::missing_docs_in_private_items)]
135 struct Display<'a> {
136 params: &'a TypeParameters<'a>,
137 ty: &'a GenericType,
138 }
139
140 impl fmt::Display for Display<'_> {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 match self.ty {
143 GenericType::Parameter(name) | GenericType::UnqualifiedParameter(name) => {
144 let (_, ty) = self.params.get(name).expect("the name should be present");
145 match ty {
146 Some(ty) => {
147 if let GenericType::UnqualifiedParameter(_) = self.ty {
148 ty.require().fmt(f)
149 } else {
150 ty.fmt(f)
151 }
152 }
153 None => {
154 write!(
155 f,
156 "{prefix}{name}{suffix}",
157 prefix = if f.alternate() { "generic type `" } else { "" },
158 suffix = if f.alternate() { "`" } else { "" },
159 )
160 }
161 }
162 }
163 GenericType::Array(ty) => ty.display(self.params).fmt(f),
164 GenericType::Pair(ty) => ty.display(self.params).fmt(f),
165 GenericType::Map(ty) => ty.display(self.params).fmt(f),
166 GenericType::EnumInnerValue(ty) => ty.display(self.params).fmt(f),
167 }
168 }
169 }
170
171 Display { params, ty: self }
172 }
173
174 fn infer_type_parameters(
176 &self,
177 ty: &Type,
178 params: &mut TypeParameters<'_>,
179 ignore_constraints: bool,
180 ) {
181 match self {
182 Self::Parameter(name) | Self::UnqualifiedParameter(name) => {
183 let (param, _) = params.get(name).expect("should have parameter");
185
186 if !ignore_constraints
187 && let Some(constraint) = param.constraint()
188 && !constraint.satisfied(ty)
189 {
190 return;
191 }
192
193 params.set_inferred_type(name, ty.clone());
194 }
195 Self::Array(array) => array.infer_type_parameters(ty, params, ignore_constraints),
196 Self::Pair(pair) => pair.infer_type_parameters(ty, params, ignore_constraints),
197 Self::Map(map) => map.infer_type_parameters(ty, params, ignore_constraints),
198 Self::EnumInnerValue(_) => {
199 }
202 }
203 }
204
205 fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
207 match self {
208 Self::Parameter(name) => {
209 params
210 .get(name)
211 .expect("type parameter should be present")
212 .1
213 }
214 Self::UnqualifiedParameter(name) => params
215 .get(name)
216 .expect("type parameter should be present")
217 .1
218 .map(|ty| ty.require()),
219 Self::Array(ty) => ty.realize(params),
220 Self::Pair(ty) => ty.realize(params),
221 Self::Map(ty) => ty.realize(params),
222 Self::EnumInnerValue(ty) => ty.realize(params),
223 }
224 }
225
226 fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
232 match self {
233 Self::Parameter(n) | Self::UnqualifiedParameter(n) => assert!(
234 parameters.iter().any(|p| p.name == *n),
235 "generic type references unknown type parameter `{n}`"
236 ),
237 Self::Array(a) => a.assert_type_parameters(parameters),
238 Self::Pair(p) => p.assert_type_parameters(parameters),
239 Self::Map(m) => m.assert_type_parameters(parameters),
240 Self::EnumInnerValue(e) => e.assert_type_parameters(parameters),
241 }
242 }
243}
244
245impl From<GenericArrayType> for GenericType {
246 fn from(value: GenericArrayType) -> Self {
247 Self::Array(value)
248 }
249}
250
251impl From<GenericPairType> for GenericType {
252 fn from(value: GenericPairType) -> Self {
253 Self::Pair(value)
254 }
255}
256
257impl From<GenericMapType> for GenericType {
258 fn from(value: GenericMapType) -> Self {
259 Self::Map(value)
260 }
261}
262
263impl From<GenericEnumInnerValueType> for GenericType {
264 fn from(value: GenericEnumInnerValueType) -> Self {
265 Self::EnumInnerValue(value)
266 }
267}
268
269#[derive(Debug, Clone)]
271pub struct GenericArrayType {
272 element_type: Box<FunctionalType>,
274 non_empty: bool,
276}
277
278impl GenericArrayType {
279 pub fn new(element_type: impl Into<FunctionalType>) -> Self {
281 Self {
282 element_type: Box::new(element_type.into()),
283 non_empty: false,
284 }
285 }
286
287 pub fn non_empty(element_type: impl Into<FunctionalType>) -> Self {
289 Self {
290 element_type: Box::new(element_type.into()),
291 non_empty: true,
292 }
293 }
294
295 pub fn element_type(&self) -> &FunctionalType {
297 &self.element_type
298 }
299
300 pub fn is_non_empty(&self) -> bool {
302 self.non_empty
303 }
304
305 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
307 #[allow(clippy::missing_docs_in_private_items)]
308 struct Display<'a> {
309 params: &'a TypeParameters<'a>,
310 ty: &'a GenericArrayType,
311 }
312
313 impl fmt::Display for Display<'_> {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 write!(
316 f,
317 "{prefix}Array[{ty}]{plus}{suffix}",
318 prefix = if f.alternate() { "generic type `" } else { "" },
319 ty = self.ty.element_type.display(self.params),
320 plus = if self.ty.is_non_empty() { "+" } else { "" },
321 suffix = if f.alternate() { "`" } else { "" },
322 )
323 }
324 }
325
326 Display { params, ty: self }
327 }
328
329 fn infer_type_parameters(
331 &self,
332 ty: &Type,
333 params: &mut TypeParameters<'_>,
334 ignore_constraints: bool,
335 ) {
336 match ty {
337 Type::Union => {
338 self.element_type
339 .infer_type_parameters(&Type::Union, params, ignore_constraints);
340 }
341 Type::Compound(CompoundType::Array(ty), false) => {
342 self.element_type.infer_type_parameters(
343 ty.element_type(),
344 params,
345 ignore_constraints,
346 );
347 }
348 _ => {}
349 }
350 }
351
352 fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
354 let ty = self.element_type.realize(params)?;
355 if self.non_empty {
356 Some(ArrayType::non_empty(ty).into())
357 } else {
358 Some(ArrayType::new(ty).into())
359 }
360 }
361
362 fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
368 self.element_type.assert_type_parameters(parameters);
369 }
370}
371
372#[derive(Debug, Clone)]
374pub struct GenericPairType {
375 left_type: Box<FunctionalType>,
377 right_type: Box<FunctionalType>,
379}
380
381impl GenericPairType {
382 pub fn new(
384 left_type: impl Into<FunctionalType>,
385 right_type: impl Into<FunctionalType>,
386 ) -> Self {
387 Self {
388 left_type: Box::new(left_type.into()),
389 right_type: Box::new(right_type.into()),
390 }
391 }
392
393 pub fn left_type(&self) -> &FunctionalType {
395 &self.left_type
396 }
397
398 pub fn right_type(&self) -> &FunctionalType {
400 &self.right_type
401 }
402
403 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
405 #[allow(clippy::missing_docs_in_private_items)]
406 struct Display<'a> {
407 params: &'a TypeParameters<'a>,
408 ty: &'a GenericPairType,
409 }
410
411 impl fmt::Display for Display<'_> {
412 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413 write!(
414 f,
415 "{prefix}Pair[{left}, {right}]{suffix}",
416 prefix = if f.alternate() { "generic type `" } else { "" },
417 left = self.ty.left_type.display(self.params),
418 right = self.ty.right_type.display(self.params),
419 suffix = if f.alternate() { "`" } else { "" },
420 )
421 }
422 }
423
424 Display { params, ty: self }
425 }
426
427 fn infer_type_parameters(
429 &self,
430 ty: &Type,
431 params: &mut TypeParameters<'_>,
432 ignore_constraints: bool,
433 ) {
434 match ty {
435 Type::Union => {
436 self.left_type
437 .infer_type_parameters(&Type::Union, params, ignore_constraints);
438 self.right_type
439 .infer_type_parameters(&Type::Union, params, ignore_constraints);
440 }
441 Type::Compound(CompoundType::Pair(ty), false) => {
442 self.left_type
443 .infer_type_parameters(ty.left_type(), params, ignore_constraints);
444 self.right_type
445 .infer_type_parameters(ty.right_type(), params, ignore_constraints);
446 }
447 _ => {}
448 }
449 }
450
451 fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
453 let left_type = self.left_type.realize(params)?;
454 let right_type = self.right_type.realize(params)?;
455 Some(PairType::new(left_type, right_type).into())
456 }
457
458 fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
464 self.left_type.assert_type_parameters(parameters);
465 self.right_type.assert_type_parameters(parameters);
466 }
467}
468
469#[derive(Debug, Clone)]
471pub struct GenericMapType {
472 key_type: Box<FunctionalType>,
474 value_type: Box<FunctionalType>,
476}
477
478impl GenericMapType {
479 pub fn new(key_type: impl Into<FunctionalType>, value_type: impl Into<FunctionalType>) -> Self {
481 Self {
482 key_type: Box::new(key_type.into()),
483 value_type: Box::new(value_type.into()),
484 }
485 }
486
487 pub fn key_type(&self) -> &FunctionalType {
489 &self.key_type
490 }
491
492 pub fn value_type(&self) -> &FunctionalType {
494 &self.value_type
495 }
496
497 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
499 #[allow(clippy::missing_docs_in_private_items)]
500 struct Display<'a> {
501 params: &'a TypeParameters<'a>,
502 ty: &'a GenericMapType,
503 }
504
505 impl fmt::Display for Display<'_> {
506 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
507 write!(
508 f,
509 "{prefix}Map[{key}, {value}]{suffix}",
510 prefix = if f.alternate() { "generic type `" } else { "" },
511 key = self.ty.key_type.display(self.params),
512 value = self.ty.value_type.display(self.params),
513 suffix = if f.alternate() { "`" } else { "" },
514 )
515 }
516 }
517
518 Display { params, ty: self }
519 }
520
521 fn infer_type_parameters(
523 &self,
524 ty: &Type,
525 params: &mut TypeParameters<'_>,
526 ignore_constraints: bool,
527 ) {
528 match ty {
529 Type::Union => {
530 self.key_type
531 .infer_type_parameters(&Type::Union, params, ignore_constraints);
532 self.value_type
533 .infer_type_parameters(&Type::Union, params, ignore_constraints);
534 }
535 Type::Compound(CompoundType::Map(ty), false) => {
536 self.key_type
537 .infer_type_parameters(ty.key_type(), params, ignore_constraints);
538 self.value_type
539 .infer_type_parameters(ty.value_type(), params, ignore_constraints);
540 }
541 _ => {}
542 }
543 }
544
545 fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
547 let key_type = self.key_type.realize(params)?;
548 let value_type = self.value_type.realize(params)?;
549 Some(MapType::new(key_type, value_type).into())
550 }
551
552 fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
558 self.key_type.assert_type_parameters(parameters);
559 self.value_type.assert_type_parameters(parameters);
560 }
561}
562
563#[derive(Debug, Clone)]
565pub struct GenericEnumInnerValueType {
566 param: &'static str,
568}
569
570impl GenericEnumInnerValueType {
571 pub fn new(param: &'static str) -> Self {
573 Self { param }
574 }
575
576 pub fn param(&self) -> &'static str {
578 self.param
579 }
580
581 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
583 #[allow(clippy::missing_docs_in_private_items)]
584 struct Display<'a> {
585 params: &'a TypeParameters<'a>,
586 ty: &'a GenericEnumInnerValueType,
587 }
588
589 impl fmt::Display for Display<'_> {
590 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591 let (_, variant_ty) = self
592 .params
593 .get(self.ty.param)
594 .expect("variant parameter should be present");
595
596 match variant_ty.as_ref().and_then(|t| t.as_enum()) {
597 Some(enum_ty) => write!(f, "{}", enum_ty.inner_value_type()),
598 _ => write!(f, "{}", self.ty.param),
600 }
601 }
602 }
603
604 Display { params, ty: self }
605 }
606
607 fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
609 let (_, variant_ty) = params
610 .get(self.param)
611 .expect("variant parameter should be present");
612
613 variant_ty
615 .as_ref()
616 .and_then(|t| t.as_enum())
617 .map(|enum_ty| enum_ty.inner_value_type().clone())
618 }
619
620 fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
622 assert!(
623 parameters.iter().any(|p| p.name == self.param),
624 "generic enum variant type references unknown type parameter `{}`",
625 self.param
626 );
627 }
628}
629
630#[derive(Debug, Clone)]
632pub struct TypeParameters<'a> {
633 parameters: &'a [TypeParameter],
635 inferred_types: [Option<Type>; MAX_TYPE_PARAMETERS],
637 referenced: Cell<usize>,
640}
641
642impl<'a> TypeParameters<'a> {
643 pub fn new(parameters: &'a [TypeParameter]) -> Self {
651 assert!(
652 parameters.len() <= MAX_TYPE_PARAMETERS,
653 "no more than {MAX_TYPE_PARAMETERS} type parameters is supported"
654 );
655
656 Self {
657 parameters,
658 inferred_types: [const { None }; MAX_TYPE_PARAMETERS],
659 referenced: Cell::new(0),
660 }
661 }
662
663 pub fn get(&self, name: &str) -> Option<(&TypeParameter, Option<Type>)> {
669 let index = self.parameters.iter().position(|p| p.name == name)?;
670
671 self.referenced.set(self.referenced.get() | (1 << index));
673
674 Some((&self.parameters[index], self.inferred_types[index].clone()))
675 }
676
677 pub fn reset(&self) {
679 self.referenced.set(0);
680 }
681
682 pub fn referenced(&self) -> impl Iterator<Item = (&TypeParameter, Option<Type>)> + use<'_> {
685 let mut bits = self.referenced.get();
686 std::iter::from_fn(move || {
687 if bits == 0 {
688 return None;
689 }
690
691 let index = bits.trailing_zeros() as usize;
692 let parameter = &self.parameters[index];
693 let ty = self.inferred_types[index].clone();
694 bits ^= bits & bits.overflowing_neg().0;
695 Some((parameter, ty))
696 })
697 }
698
699 fn set_inferred_type(&mut self, name: &str, ty: Type) {
708 let index = self
709 .parameters
710 .iter()
711 .position(|p| p.name == name)
712 .unwrap_or_else(|| panic!("unknown type parameter `{name}`"));
713
714 self.inferred_types[index].get_or_insert(ty);
715 }
716}
717
718#[derive(Debug, Clone)]
720pub enum FunctionalType {
721 Concrete(Type),
723 Generic(GenericType),
725}
726
727impl FunctionalType {
728 pub fn is_generic(&self) -> bool {
730 matches!(self, Self::Generic(_))
731 }
732
733 pub fn concrete_type(&self) -> Option<&Type> {
737 match self {
738 Self::Concrete(ty) => Some(ty),
739 Self::Generic(_) => None,
740 }
741 }
742
743 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
745 #[allow(clippy::missing_docs_in_private_items)]
746 struct Display<'a> {
747 params: &'a TypeParameters<'a>,
748 ty: &'a FunctionalType,
749 }
750
751 impl fmt::Display for Display<'_> {
752 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
753 match self.ty {
754 FunctionalType::Concrete(ty) => ty.fmt(f),
755 FunctionalType::Generic(ty) => ty.display(self.params).fmt(f),
756 }
757 }
758 }
759
760 Display { params, ty: self }
761 }
762
763 fn infer_type_parameters(
765 &self,
766 ty: &Type,
767 params: &mut TypeParameters<'_>,
768 ignore_constraints: bool,
769 ) {
770 if let Self::Generic(generic) = self {
771 generic.infer_type_parameters(ty, params, ignore_constraints);
772 }
773 }
774
775 fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
777 match self {
778 FunctionalType::Concrete(ty) => Some(ty.clone()),
779 FunctionalType::Generic(ty) => ty.realize(params),
780 }
781 }
782
783 fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
789 if let FunctionalType::Generic(ty) = self {
790 ty.assert_type_parameters(parameters)
791 }
792 }
793}
794
795impl From<Type> for FunctionalType {
796 fn from(value: Type) -> Self {
797 Self::Concrete(value)
798 }
799}
800
801impl From<PrimitiveType> for FunctionalType {
802 fn from(value: PrimitiveType) -> Self {
803 Self::Concrete(value.into())
804 }
805}
806
807impl From<ArrayType> for FunctionalType {
808 fn from(value: ArrayType) -> Self {
809 Self::Concrete(value.into())
810 }
811}
812
813impl From<MapType> for FunctionalType {
814 fn from(value: MapType) -> Self {
815 Self::Concrete(value.into())
816 }
817}
818
819impl From<GenericType> for FunctionalType {
820 fn from(value: GenericType) -> Self {
821 Self::Generic(value)
822 }
823}
824
825impl From<GenericArrayType> for FunctionalType {
826 fn from(value: GenericArrayType) -> Self {
827 Self::Generic(GenericType::Array(value))
828 }
829}
830
831impl From<GenericPairType> for FunctionalType {
832 fn from(value: GenericPairType) -> Self {
833 Self::Generic(GenericType::Pair(value))
834 }
835}
836
837impl From<GenericMapType> for FunctionalType {
838 fn from(value: GenericMapType) -> Self {
839 Self::Generic(GenericType::Map(value))
840 }
841}
842
843impl From<GenericEnumInnerValueType> for FunctionalType {
844 fn from(value: GenericEnumInnerValueType) -> Self {
845 Self::Generic(GenericType::EnumInnerValue(value))
846 }
847}
848
849#[derive(Debug)]
851pub struct TypeParameter {
852 name: &'static str,
854 constraint: Option<Box<dyn Constraint>>,
856}
857
858impl TypeParameter {
859 pub fn any(name: &'static str) -> Self {
861 Self {
862 name,
863 constraint: None,
864 }
865 }
866
867 pub fn new(name: &'static str, constraint: impl Constraint + 'static) -> Self {
869 Self {
870 name,
871 constraint: Some(Box::new(constraint)),
872 }
873 }
874
875 pub fn name(&self) -> &str {
877 self.name
878 }
879
880 pub fn constraint(&self) -> Option<&dyn Constraint> {
882 self.constraint.as_deref()
883 }
884}
885
886#[derive(Debug, Clone)]
888enum BindingKind {
889 Equivalence(Type),
894 Coercion(Type),
899}
900
901impl BindingKind {
902 pub fn ret(&self) -> &Type {
904 match self {
905 Self::Equivalence(ty) | Self::Coercion(ty) => ty,
906 }
907 }
908}
909
910#[derive(Debug)]
912pub struct FunctionParameter {
913 name: &'static str,
915 ty: FunctionalType,
917 description: &'static str,
919}
920
921impl FunctionParameter {
922 pub fn name(&self) -> &'static str {
924 self.name
925 }
926
927 pub fn ty(&self) -> &FunctionalType {
929 &self.ty
930 }
931
932 #[allow(dead_code)]
934 pub fn description(&self) -> &'static str {
935 self.description
936 }
937}
938
939#[derive(Debug)]
941pub struct FunctionSignature {
942 minimum_version: Option<SupportedVersion>,
944 type_parameters: Vec<TypeParameter>,
946 required: Option<usize>,
948 parameters: Vec<FunctionParameter>,
950 ret: FunctionalType,
952 definition: Option<&'static str>,
954}
955
956impl FunctionSignature {
957 pub fn builder() -> FunctionSignatureBuilder {
959 FunctionSignatureBuilder::new()
960 }
961
962 pub fn minimum_version(&self) -> SupportedVersion {
964 self.minimum_version
965 .unwrap_or(SupportedVersion::V1(V1::Zero))
966 }
967
968 pub fn type_parameters(&self) -> &[TypeParameter] {
970 &self.type_parameters
971 }
972
973 pub fn parameters(&self) -> &[FunctionParameter] {
975 &self.parameters
976 }
977
978 pub fn required(&self) -> usize {
983 self.required.unwrap_or(self.parameters.len())
984 }
985
986 pub fn ret(&self) -> &FunctionalType {
988 &self.ret
989 }
990
991 pub fn definition(&self) -> Option<&'static str> {
993 self.definition
994 }
995
996 pub fn is_generic(&self) -> bool {
998 self.generic_parameter_count() > 0 || self.ret.is_generic()
999 }
1000
1001 pub fn generic_parameter_count(&self) -> usize {
1003 self.parameters.iter().filter(|p| p.ty.is_generic()).count()
1004 }
1005
1006 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
1009 #[allow(clippy::missing_docs_in_private_items)]
1010 struct Display<'a> {
1011 params: &'a TypeParameters<'a>,
1012 sig: &'a FunctionSignature,
1013 }
1014
1015 impl fmt::Display for Display<'_> {
1016 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1017 f.write_char('(')?;
1018
1019 self.params.reset();
1020 let required = self.sig.required();
1021 for (i, parameter) in self.sig.parameters.iter().enumerate() {
1022 if i > 0 {
1023 f.write_str(", ")?;
1024 }
1025
1026 if i >= required {
1027 f.write_char('<')?;
1028 }
1029
1030 write!(
1031 f,
1032 "{name}: {ty}",
1033 name = parameter.name(),
1034 ty = parameter.ty().display(self.params)
1035 )?;
1036
1037 if i >= required {
1038 f.write_char('>')?;
1039 }
1040 }
1041
1042 write!(f, ") -> {ret}", ret = self.sig.ret.display(self.params))?;
1043 write_uninferred_constraints(f, self.params)?;
1044
1045 Ok(())
1046 }
1047 }
1048
1049 Display { params, sig: self }
1050 }
1051
1052 fn infer_type_parameters(
1057 &self,
1058 arguments: &[Type],
1059 ignore_constraints: bool,
1060 ) -> TypeParameters<'_> {
1061 let mut parameters = TypeParameters::new(&self.type_parameters);
1062 for (parameter, argument) in self.parameters.iter().zip(arguments.iter()) {
1063 parameter
1064 .ty
1065 .infer_type_parameters(argument, &mut parameters, ignore_constraints);
1066 }
1067
1068 parameters
1069 }
1070
1071 fn insufficient_arguments(&self, arguments: &[Type]) -> bool {
1074 arguments.len() < self.required() || arguments.len() > self.parameters.len()
1075 }
1076
1077 fn bind(
1087 &self,
1088 version: SupportedVersion,
1089 arguments: &[Type],
1090 ) -> Result<BindingKind, FunctionBindError> {
1091 if version < self.minimum_version() {
1092 return Err(FunctionBindError::RequiresVersion(self.minimum_version()));
1093 }
1094
1095 let required = self.required();
1096 if arguments.len() < required {
1097 return Err(FunctionBindError::TooFewArguments(required));
1098 }
1099
1100 if arguments.len() > self.parameters.len() {
1101 return Err(FunctionBindError::TooManyArguments(self.parameters.len()));
1102 }
1103
1104 let mut coerced = false;
1106 let type_parameters = self.infer_type_parameters(arguments, false);
1107 for (i, (parameter, argument)) in self.parameters.iter().zip(arguments.iter()).enumerate() {
1108 match parameter.ty.realize(&type_parameters) {
1109 Some(ty) => {
1110 if !coerced && argument != &ty && argument != &ty.require() {
1114 coerced = true;
1115 }
1116
1117 if coerced && !argument.is_coercible_to(&ty) {
1118 return Err(FunctionBindError::ArgumentTypeMismatch {
1119 index: i,
1120 expected: format!("{ty:#}"),
1121 });
1122 }
1123 }
1124 None if argument.is_union() => {
1125 continue;
1127 }
1128 None => {
1129 type_parameters.reset();
1131
1132 let mut expected = String::new();
1133
1134 write!(
1135 &mut expected,
1136 "{param:#}",
1137 param = parameter.ty.display(&type_parameters)
1138 )
1139 .unwrap();
1140
1141 write_uninferred_constraints(&mut expected, &type_parameters).unwrap();
1142 return Err(FunctionBindError::ArgumentTypeMismatch { index: i, expected });
1143 }
1144 }
1145 }
1146
1147 let ret = self.ret().realize(&type_parameters).unwrap_or(Type::Union);
1151
1152 if coerced {
1153 Ok(BindingKind::Coercion(ret))
1154 } else {
1155 Ok(BindingKind::Equivalence(ret))
1156 }
1157 }
1158}
1159
1160impl Default for FunctionSignature {
1161 fn default() -> Self {
1162 Self {
1163 minimum_version: None,
1164 type_parameters: Default::default(),
1165 required: Default::default(),
1166 parameters: Default::default(),
1167 ret: FunctionalType::Concrete(Type::Union),
1168 definition: None,
1169 }
1170 }
1171}
1172
1173#[derive(Debug, Default)]
1175pub struct FunctionSignatureBuilder(FunctionSignature);
1176
1177impl FunctionSignatureBuilder {
1178 pub fn new() -> Self {
1180 Self(Default::default())
1181 }
1182
1183 pub fn min_version(mut self, version: SupportedVersion) -> Self {
1185 self.0.minimum_version = Some(version);
1186 self
1187 }
1188
1189 pub fn type_parameter(
1191 mut self,
1192 name: &'static str,
1193 constraint: impl Constraint + 'static,
1194 ) -> Self {
1195 self.0
1196 .type_parameters
1197 .push(TypeParameter::new(name, constraint));
1198 self
1199 }
1200
1201 pub fn any_type_parameter(mut self, name: &'static str) -> Self {
1203 self.0.type_parameters.push(TypeParameter::any(name));
1204 self
1205 }
1206
1207 pub fn parameter(
1209 mut self,
1210 name: &'static str,
1211 ty: impl Into<FunctionalType>,
1212 description: &'static str,
1213 ) -> Self {
1214 self.0.parameters.push(FunctionParameter {
1215 name,
1216 ty: ty.into(),
1217 description,
1218 });
1219 self
1220 }
1221
1222 pub fn ret(mut self, ret: impl Into<FunctionalType>) -> Self {
1227 self.0.ret = ret.into();
1228 self
1229 }
1230
1231 pub fn required(mut self, required: usize) -> Self {
1233 self.0.required = Some(required);
1234 self
1235 }
1236
1237 pub fn definition(mut self, definition: &'static str) -> Self {
1239 self.0.definition = Some(definition);
1240 self
1241 }
1242
1243 pub fn build(self) -> FunctionSignature {
1249 let sig = self.0;
1250
1251 if let Some(required) = sig.required
1254 && required > sig.parameters.len()
1255 {
1256 panic!("number of required parameters exceeds the number of parameters");
1257 }
1258
1259 assert!(
1260 sig.type_parameters.len() <= MAX_TYPE_PARAMETERS,
1261 "too many type parameters"
1262 );
1263
1264 assert!(
1265 sig.parameters.len() <= MAX_PARAMETERS,
1266 "too many parameters"
1267 );
1268
1269 for parameter in sig.parameters.iter() {
1271 parameter.ty.assert_type_parameters(&sig.type_parameters)
1272 }
1273
1274 sig.ret().assert_type_parameters(&sig.type_parameters);
1275
1276 assert!(sig.definition.is_some(), "functions should have definition");
1277
1278 sig
1279 }
1280}
1281
1282#[derive(Debug, Clone)]
1284pub struct Binding<'a> {
1285 return_type: Type,
1287 index: usize,
1291 signature: &'a FunctionSignature,
1293}
1294
1295impl Binding<'_> {
1296 pub fn return_type(&self) -> &Type {
1298 &self.return_type
1299 }
1300
1301 pub fn index(&self) -> usize {
1305 self.index
1306 }
1307
1308 pub fn signature(&self) -> &FunctionSignature {
1310 self.signature
1311 }
1312}
1313
1314#[derive(Debug)]
1316pub enum Function {
1317 Monomorphic(MonomorphicFunction),
1319 Polymorphic(PolymorphicFunction),
1321}
1322
1323impl Function {
1324 pub fn minimum_version(&self) -> SupportedVersion {
1326 match self {
1327 Self::Monomorphic(f) => f.minimum_version(),
1328 Self::Polymorphic(f) => f.minimum_version(),
1329 }
1330 }
1331
1332 pub fn param_min_max(&self, version: SupportedVersion) -> Option<(usize, usize)> {
1337 match self {
1338 Self::Monomorphic(f) => f.param_min_max(version),
1339 Self::Polymorphic(f) => f.param_min_max(version),
1340 }
1341 }
1342
1343 pub fn bind<'a>(
1345 &'a self,
1346 version: SupportedVersion,
1347 arguments: &[Type],
1348 ) -> Result<Binding<'a>, FunctionBindError> {
1349 match self {
1350 Self::Monomorphic(f) => f.bind(version, arguments),
1351 Self::Polymorphic(f) => f.bind(version, arguments),
1352 }
1353 }
1354
1355 pub fn realize_unconstrained_return_type(&self, arguments: &[Type]) -> Type {
1363 match self {
1364 Self::Monomorphic(f) => {
1365 let type_parameters = f.signature.infer_type_parameters(arguments, true);
1366 f.signature
1367 .ret()
1368 .realize(&type_parameters)
1369 .unwrap_or(Type::Union)
1370 }
1371 Self::Polymorphic(f) => {
1372 let mut ty = None;
1373
1374 for signature in &f.signatures {
1377 let type_parameters = signature.infer_type_parameters(arguments, true);
1378 let ret_ty = signature
1379 .ret()
1380 .realize(&type_parameters)
1381 .unwrap_or(Type::Union);
1382
1383 if ty.get_or_insert(ret_ty.clone()) != &ret_ty {
1384 return Type::Union;
1385 }
1386 }
1387
1388 ty.unwrap_or(Type::Union)
1389 }
1390 }
1391 }
1392}
1393
1394#[derive(Debug)]
1399pub struct MonomorphicFunction {
1400 signature: FunctionSignature,
1402}
1403
1404impl MonomorphicFunction {
1405 pub fn new(signature: FunctionSignature) -> Self {
1407 Self { signature }
1408 }
1409
1410 pub fn minimum_version(&self) -> SupportedVersion {
1412 self.signature.minimum_version()
1413 }
1414
1415 pub fn param_min_max(&self, version: SupportedVersion) -> Option<(usize, usize)> {
1420 if version < self.signature.minimum_version() {
1421 return None;
1422 }
1423
1424 Some((self.signature.required(), self.signature.parameters.len()))
1425 }
1426
1427 pub fn signature(&self) -> &FunctionSignature {
1429 &self.signature
1430 }
1431
1432 pub fn bind<'a>(
1434 &'a self,
1435 version: SupportedVersion,
1436 arguments: &[Type],
1437 ) -> Result<Binding<'a>, FunctionBindError> {
1438 let return_type = self.signature.bind(version, arguments)?.ret().clone();
1439 Ok(Binding {
1440 return_type,
1441 index: 0,
1442 signature: &self.signature,
1443 })
1444 }
1445}
1446
1447impl From<MonomorphicFunction> for Function {
1448 fn from(value: MonomorphicFunction) -> Self {
1449 Self::Monomorphic(value)
1450 }
1451}
1452
1453#[derive(Debug)]
1459pub struct PolymorphicFunction {
1460 signatures: Vec<FunctionSignature>,
1462}
1463
1464impl PolymorphicFunction {
1465 pub fn new(signatures: Vec<FunctionSignature>) -> Self {
1471 assert!(
1472 signatures.len() > 1,
1473 "a polymorphic function must have at least two signatures"
1474 );
1475
1476 Self { signatures }
1477 }
1478
1479 pub fn minimum_version(&self) -> SupportedVersion {
1481 self.signatures
1482 .iter()
1483 .fold(None, |v: Option<SupportedVersion>, s| {
1484 Some(
1485 v.map(|v| v.min(s.minimum_version()))
1486 .unwrap_or_else(|| s.minimum_version()),
1487 )
1488 })
1489 .expect("there should be at least one signature")
1490 }
1491
1492 pub fn param_min_max(&self, version: SupportedVersion) -> Option<(usize, usize)> {
1497 let mut min = usize::MAX;
1498 let mut max = 0;
1499 for sig in self
1500 .signatures
1501 .iter()
1502 .filter(|s| s.minimum_version() <= version)
1503 {
1504 min = std::cmp::min(min, sig.required());
1505 max = std::cmp::max(max, sig.parameters().len());
1506 }
1507
1508 if min == usize::MAX {
1509 return None;
1510 }
1511
1512 Some((min, max))
1513 }
1514
1515 pub fn signatures(&self) -> &[FunctionSignature] {
1517 &self.signatures
1518 }
1519
1520 pub fn bind<'a>(
1524 &'a self,
1525 version: SupportedVersion,
1526 arguments: &[Type],
1527 ) -> Result<Binding<'a>, FunctionBindError> {
1528 let min_version = self.minimum_version();
1530 if version < min_version {
1531 return Err(FunctionBindError::RequiresVersion(min_version));
1532 }
1533
1534 let (min, max) = self
1536 .param_min_max(version)
1537 .expect("should have at least one signature for the version");
1538 if arguments.len() < min {
1539 return Err(FunctionBindError::TooFewArguments(min));
1540 }
1541
1542 if arguments.len() > max {
1543 return Err(FunctionBindError::TooManyArguments(max));
1544 }
1545
1546 let mut max_mismatch_index = 0;
1553 let mut expected_types = IndexSet::new();
1554
1555 for generic in [false, true] {
1556 let mut exact: Option<(usize, Type)> = None;
1557 let mut coercion1: Option<(usize, Type)> = None;
1558 let mut coercion2 = None;
1559 for (index, signature) in self.signatures.iter().enumerate().filter(|(_, s)| {
1560 s.is_generic() == generic
1561 && s.minimum_version() <= version
1562 && !s.insufficient_arguments(arguments)
1563 }) {
1564 match signature.bind(version, arguments) {
1565 Ok(BindingKind::Equivalence(ty)) => {
1566 if let Some((previous, _)) = exact {
1568 return Err(FunctionBindError::Ambiguous {
1569 first: self.signatures[previous]
1570 .display(&TypeParameters::new(
1571 &self.signatures[previous].type_parameters,
1572 ))
1573 .to_string(),
1574 second: self.signatures[index]
1575 .display(&TypeParameters::new(
1576 &self.signatures[index].type_parameters,
1577 ))
1578 .to_string(),
1579 });
1580 }
1581
1582 exact = Some((index, ty));
1583 }
1584 Ok(BindingKind::Coercion(ty)) => {
1585 if coercion1.is_none() {
1589 coercion1 = Some((index, ty));
1590 } else {
1591 coercion2.get_or_insert(index);
1592 }
1593 }
1594 Err(FunctionBindError::ArgumentTypeMismatch { index, expected }) => {
1595 if index > max_mismatch_index {
1597 max_mismatch_index = index;
1598 expected_types.clear();
1599 }
1600
1601 if index == max_mismatch_index {
1602 expected_types.insert(expected);
1603 }
1604 }
1605 Err(
1606 FunctionBindError::RequiresVersion(_)
1607 | FunctionBindError::Ambiguous { .. }
1608 | FunctionBindError::TooFewArguments(_)
1609 | FunctionBindError::TooManyArguments(_),
1610 ) => unreachable!("should not encounter these errors due to above filter"),
1611 }
1612 }
1613
1614 if let Some((index, ty)) = exact {
1615 return Ok(Binding {
1616 return_type: ty,
1617 index,
1618 signature: &self.signatures[index],
1619 });
1620 }
1621
1622 if let Some(previous) = coercion2 {
1624 let index = coercion1.unwrap().0;
1625 return Err(FunctionBindError::Ambiguous {
1626 first: self.signatures[previous]
1627 .display(&TypeParameters::new(
1628 &self.signatures[previous].type_parameters,
1629 ))
1630 .to_string(),
1631 second: self.signatures[index]
1632 .display(&TypeParameters::new(
1633 &self.signatures[index].type_parameters,
1634 ))
1635 .to_string(),
1636 });
1637 }
1638
1639 if let Some((index, ty)) = coercion1 {
1640 return Ok(Binding {
1641 return_type: ty,
1642 index,
1643 signature: &self.signatures[index],
1644 });
1645 }
1646 }
1647
1648 assert!(!expected_types.is_empty());
1649
1650 let mut expected = String::new();
1651 for (i, ty) in expected_types.iter().enumerate() {
1652 if i > 0 {
1653 if expected_types.len() == 2 {
1654 expected.push_str(" or ");
1655 } else if i == expected_types.len() - 1 {
1656 expected.push_str(", or ");
1657 } else {
1658 expected.push_str(", ");
1659 }
1660 }
1661
1662 expected.push_str(ty);
1663 }
1664
1665 Err(FunctionBindError::ArgumentTypeMismatch {
1666 index: max_mismatch_index,
1667 expected,
1668 })
1669 }
1670}
1671
1672impl From<PolymorphicFunction> for Function {
1673 fn from(value: PolymorphicFunction) -> Self {
1674 Self::Polymorphic(value)
1675 }
1676}
1677
1678#[derive(Debug)]
1680pub struct StandardLibrary {
1681 functions: IndexMap<&'static str, Function>,
1683 array_int: ArrayType,
1685 array_string: ArrayType,
1687 array_file: ArrayType,
1689 array_object: ArrayType,
1691 array_string_non_empty: ArrayType,
1693 array_array_string: ArrayType,
1695 map_string_string: MapType,
1697 map_string_int: MapType,
1699}
1700
1701impl StandardLibrary {
1702 pub fn function(&self, name: &str) -> Option<&Function> {
1704 self.functions.get(name)
1705 }
1706
1707 pub fn functions(&self) -> impl ExactSizeIterator<Item = (&'static str, &Function)> {
1709 self.functions.iter().map(|(n, f)| (*n, f))
1710 }
1711
1712 pub fn array_int_type(&self) -> &ArrayType {
1714 &self.array_int
1715 }
1716
1717 pub fn array_string_type(&self) -> &ArrayType {
1719 &self.array_string
1720 }
1721
1722 pub fn array_file_type(&self) -> &ArrayType {
1724 &self.array_file
1725 }
1726
1727 pub fn array_object_type(&self) -> &ArrayType {
1729 &self.array_object
1730 }
1731
1732 pub fn array_string_non_empty_type(&self) -> &ArrayType {
1734 &self.array_string_non_empty
1735 }
1736
1737 pub fn array_array_string_type(&self) -> &ArrayType {
1739 &self.array_array_string
1740 }
1741
1742 pub fn map_string_string_type(&self) -> &MapType {
1744 &self.map_string_string
1745 }
1746
1747 pub fn map_string_int_type(&self) -> &MapType {
1749 &self.map_string_int
1750 }
1751}
1752
1753pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
1755 let array_int = ArrayType::new(PrimitiveType::Integer);
1756 let array_string = ArrayType::new(PrimitiveType::String);
1757 let array_file = ArrayType::new(PrimitiveType::File);
1758 let array_object = ArrayType::new(Type::Object);
1759 let array_string_non_empty = ArrayType::non_empty(PrimitiveType::String);
1760 let array_array_string = ArrayType::new(array_string.clone());
1761 let map_string_string = MapType::new(PrimitiveType::String, PrimitiveType::String);
1762 let map_string_int = MapType::new(PrimitiveType::String, PrimitiveType::Integer);
1763 let mut functions = IndexMap::new();
1764
1765 assert!(
1767 functions
1768 .insert(
1769 "floor",
1770 MonomorphicFunction::new(
1771 FunctionSignature::builder()
1772 .parameter("value", PrimitiveType::Float, "The number to round.")
1773 .ret(PrimitiveType::Integer)
1774 .definition(
1775 r#"
1776Rounds a floating point number **down** to the next lower integer.
1777
1778**Parameters**:
1779
17801. `Float`: the number to round.
1781
1782**Returns**: An integer.
1783
1784Example: test_floor.wdl
1785
1786```wdl
1787version 1.2
1788
1789workflow test_floor {
1790 input {
1791 Int i1
1792 }
1793
1794 Int i2 = i1 - 1
1795 Float f1 = i1
1796 Float f2 = i1 - 0.1
1797
1798 output {
1799 Array[Boolean] all_true = [floor(f1) == i1, floor(f2) == i2]
1800 }
1801}
1802```"#
1803 )
1804 .build(),
1805 )
1806 .into(),
1807 )
1808 .is_none()
1809 );
1810
1811 assert!(
1813 functions
1814 .insert(
1815 "ceil",
1816 MonomorphicFunction::new(
1817 FunctionSignature::builder()
1818 .parameter("value", PrimitiveType::Float, "The number to round.")
1819 .ret(PrimitiveType::Integer)
1820 .definition(
1821 r#"
1822Rounds a floating point number **up** to the next higher integer.
1823
1824**Parameters**:
1825
18261. `Float`: the number to round.
1827
1828**Returns**: An integer.
1829
1830Example: test_ceil.wdl
1831
1832```wdl
1833version 1.2
1834
1835workflow test_ceil {
1836 input {
1837 Int i1
1838 }
1839
1840 Int i2 = i1 + 1
1841 Float f1 = i1
1842 Float f2 = i1 + 0.1
1843
1844 output {
1845 Array[Boolean] all_true = [ceil(f1) == i1, ceil(f2) == i2]
1846 }
1847}
1848```
1849"#
1850 )
1851 .build(),
1852 )
1853 .into(),
1854 )
1855 .is_none()
1856 );
1857
1858 assert!(
1860 functions
1861 .insert(
1862 "round",
1863 MonomorphicFunction::new(
1864 FunctionSignature::builder()
1865 .parameter("value", PrimitiveType::Float, "The number to round.")
1866 .ret(PrimitiveType::Integer)
1867 .definition(r#"
1868Rounds a floating point number to the nearest integer based on standard rounding rules ("round half up").
1869
1870**Parameters**:
1871
18721. `Float`: the number to round.
1873
1874**Returns**: An integer.
1875
1876Example: test_round.wdl
1877
1878```wdl
1879version 1.2
1880
1881workflow test_round {
1882 input {
1883 Int i1
1884 }
1885
1886 Int i2 = i1 + 1
1887 Float f1 = i1 + 0.49
1888 Float f2 = i1 + 0.50
1889
1890 output {
1891 Array[Boolean] all_true = [round(f1) == i1, round(f2) == i2]
1892 }
1893}
1894```
1895"#
1896 )
1897 .build(),
1898 )
1899 .into(),
1900 )
1901 .is_none()
1902 );
1903
1904 const MIN_DEFINITION: &str = r#"
1905Returns the smaller of two values. If both values are `Int`s, the return value is an `Int`, otherwise it is a `Float`.
1906
1907**Parameters**:
1908
19091. `Int|Float`: the first number to compare.
19102. `Int|Float`: the second number to compare.
1911
1912**Returns**: The smaller of the two arguments.
1913
1914Example: test_min.wdl
1915
1916```wdl
1917version 1.2
1918
1919workflow test_min {
1920 input {
1921 Int value1
1922 Float value2
1923 }
1924
1925 output {
1926 # these two expressions are equivalent
1927 Float min1 = if value1 < value2 then value1 else value2
1928 Float min2 = min(value1, value2)
1929 }
1930}
1931```
1932"#;
1933
1934 assert!(
1936 functions
1937 .insert(
1938 "min",
1939 PolymorphicFunction::new(vec![
1940 FunctionSignature::builder()
1941 .min_version(SupportedVersion::V1(V1::One))
1942 .parameter("a", PrimitiveType::Integer, "The first number to compare.",)
1943 .parameter("b", PrimitiveType::Integer, "The second number to compare.",)
1944 .ret(PrimitiveType::Integer)
1945 .definition(MIN_DEFINITION)
1946 .build(),
1947 FunctionSignature::builder()
1948 .min_version(SupportedVersion::V1(V1::One))
1949 .parameter("a", PrimitiveType::Integer, "The first number to compare.",)
1950 .parameter("b", PrimitiveType::Float, "The second number to compare.")
1951 .ret(PrimitiveType::Float)
1952 .definition(MIN_DEFINITION)
1953 .build(),
1954 FunctionSignature::builder()
1955 .min_version(SupportedVersion::V1(V1::One))
1956 .parameter("a", PrimitiveType::Float, "The first number to compare.")
1957 .parameter("b", PrimitiveType::Integer, "The second number to compare.",)
1958 .ret(PrimitiveType::Float)
1959 .definition(MIN_DEFINITION)
1960 .build(),
1961 FunctionSignature::builder()
1962 .min_version(SupportedVersion::V1(V1::One))
1963 .parameter("a", PrimitiveType::Float, "The first number to compare.")
1964 .parameter("b", PrimitiveType::Float, "The second number to compare.")
1965 .ret(PrimitiveType::Float)
1966 .definition(MIN_DEFINITION)
1967 .build(),
1968 ])
1969 .into(),
1970 )
1971 .is_none()
1972 );
1973
1974 const MAX_DEFINITION: &str = r#"
1975Returns the larger of two values. If both values are `Int`s, the return value is an `Int`, otherwise it is a `Float`.
1976
1977**Parameters**:
1978
19791. `Int|Float`: the first number to compare.
19802. `Int|Float`: the second number to compare.
1981
1982**Returns**: The larger of the two arguments.
1983
1984Example: test_max.wdl
1985
1986```wdl
1987version 1.2
1988
1989workflow test_max {
1990 input {
1991 Int value1
1992 Float value2
1993 }
1994
1995 output {
1996 # these two expressions are equivalent
1997 Float min1 = if value1 > value2 then value1 else value2
1998 Float min2 = max(value1, value2)
1999 }
2000}
2001```
2002"#;
2003
2004 assert!(
2006 functions
2007 .insert(
2008 "max",
2009 PolymorphicFunction::new(vec![
2010 FunctionSignature::builder()
2011 .min_version(SupportedVersion::V1(V1::One))
2012 .parameter("a", PrimitiveType::Integer, "The first number to compare.")
2013 .parameter("b", PrimitiveType::Integer, "The second number to compare.")
2014 .ret(PrimitiveType::Integer)
2015 .definition(MAX_DEFINITION)
2016 .build(),
2017 FunctionSignature::builder()
2018 .min_version(SupportedVersion::V1(V1::One))
2019 .parameter("a", PrimitiveType::Integer, "The first number to compare.")
2020 .parameter("b", PrimitiveType::Float, "The second number to compare.")
2021 .ret(PrimitiveType::Float)
2022 .definition(MAX_DEFINITION)
2023 .build(),
2024 FunctionSignature::builder()
2025 .min_version(SupportedVersion::V1(V1::One))
2026 .parameter("a", PrimitiveType::Float, "The first number to compare.")
2027 .parameter("b", PrimitiveType::Integer, "The second number to compare.",)
2028 .ret(PrimitiveType::Float)
2029 .definition(MAX_DEFINITION)
2030 .build(),
2031 FunctionSignature::builder()
2032 .min_version(SupportedVersion::V1(V1::One))
2033 .parameter("a", PrimitiveType::Float, "The first number to compare.")
2034 .parameter("b", PrimitiveType::Float, "The second number to compare.")
2035 .ret(PrimitiveType::Float)
2036 .definition(MAX_DEFINITION)
2037 .build(),
2038 ])
2039 .into(),
2040 )
2041 .is_none()
2042 );
2043
2044 assert!(
2046 functions
2047 .insert(
2048 "find",
2049 MonomorphicFunction::new(
2050 FunctionSignature::builder()
2051 .min_version(SupportedVersion::V1(V1::Two))
2052 .parameter("input", PrimitiveType::String, "The input string to search.")
2053 .parameter("pattern", PrimitiveType::String, "The pattern to search for.")
2054 .ret(Type::from(PrimitiveType::String).optional())
2055 .definition(
2056 r#"
2057Given two `String` parameters `input` and `pattern`, searches for the occurrence of `pattern` within `input` and returns the first match or `None` if there are no matches. `pattern` is a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) and is evaluated as a [POSIX Extended Regular Expression (ERE)](https://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended).
2058
2059Note that regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped. For example:
2060
2061```wdl
2062String? first_match = find("hello\tBob", "\t")
2063```
2064
2065**Parameters**
2066
20671. `String`: the input string to search.
20682. `String`: the pattern to search for.
2069
2070**Returns**: The contents of the first match, or `None` if `pattern` does not match `input`.
2071
2072Example: test_find_task.wdl
2073
2074```wdl
2075version 1.2
2076workflow find_string {
2077 input {
2078 String in = "hello world"
2079 String pattern1 = "e..o"
2080 String pattern2 = "goodbye"
2081 }
2082 output {
2083 String? match1 = find(in, pattern1) # "ello"
2084 String? match2 = find(in, pattern2) # None
2085 }
2086}
2087```
2088"#
2089 )
2090 .build(),
2091 )
2092 .into(),
2093 )
2094 .is_none()
2095 );
2096
2097 assert!(
2099 functions
2100 .insert(
2101 "matches",
2102 MonomorphicFunction::new(
2103 FunctionSignature::builder()
2104 .min_version(SupportedVersion::V1(V1::Two))
2105 .parameter("input", PrimitiveType::String, "The input string to search.")
2106 .parameter("pattern", PrimitiveType::String, "The pattern to search for.")
2107 .ret(PrimitiveType::Boolean)
2108 .definition(
2109 r#"
2110Given two `String` parameters `input` and `pattern`, tests whether `pattern` matches `input` at least once. `pattern` is a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) and is evaluated as a [POSIX Extended Regular Expression (ERE)](https://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended).
2111
2112To test whether `pattern` matches the entire `input`, make sure to begin and end the pattern with anchors. For example:
2113
2114```wdl
2115Boolean full_match = matches("abc123", "^a.+3$")
2116```
2117
2118Note that regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped. For example:
2119
2120```wdl
2121Boolean has_tab = matches("hello\tBob", "\t")
2122```
2123
2124**Parameters**
2125
21261. `String`: the input string to search.
21272. `String`: the pattern to search for.
2128
2129**Returns**: `true` if `pattern` matches `input` at least once, otherwise `false`.
2130
2131Example: test_matches_task.wdl
2132
2133```wdl
2134version 1.2
2135workflow contains_string {
2136 input {
2137 File fastq
2138 }
2139 output {
2140 Boolean is_compressed = matches(basename(fastq), "\\.(gz|zip|zstd)")
2141 Boolean is_read1 = matches(basename(fastq), "_R1")
2142 }
2143}
2144```
2145"#
2146 )
2147 .build(),
2148 )
2149 .into(),
2150 )
2151 .is_none()
2152 );
2153
2154 assert!(
2156 functions
2157 .insert(
2158 "sub",
2159 MonomorphicFunction::new(
2160 FunctionSignature::builder()
2161 .parameter("input", PrimitiveType::String, "The input string.")
2162 .parameter("pattern", PrimitiveType::String, "The pattern to search for.")
2163 .parameter("replace", PrimitiveType::String, "The replacement string.")
2164 .ret(PrimitiveType::String)
2165 .definition(
2166 r#"
2167Given three `String` parameters `input`, `pattern`, `replace`, this function replaces all non-overlapping occurrences of `pattern` in `input` by `replace`. `pattern` is a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) and is evaluated as a [POSIX Extended Regular Expression (ERE)](https://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended).
2168Regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped (e.g., "\t").
2169
2170đź—‘ The option for execution engines to allow other regular expression grammars besides POSIX ERE is deprecated.
2171
2172**Parameters**:
2173
21741. `String`: the input string.
21752. `String`: the pattern to search for.
21763. `String`: the replacement string.
2177
2178**Returns**: the input string, with all occurrences of the pattern replaced by the replacement string.
2179
2180Example: test_sub.wdl
2181
2182```wdl
2183version 1.2
2184
2185workflow test_sub {
2186 String chocolike = "I like chocolate when\nit's late"
2187
2188 output {
2189 String chocolove = sub(chocolike, "like", "love") # I love chocolate when\nit's late
2190 String chocoearly = sub(chocolike, "late", "early") # I like chocoearly when\nit's early
2191 String chocolate = sub(chocolike, "late$", "early") # I like chocolate when\nit's early
2192 String chocoearlylate = sub(chocolike, "[^ ]late", "early") # I like chocearly when\nit's late
2193 String choco4 = sub(chocolike, " [:alpha:]{4} ", " 4444 ") # I 4444 chocolate 4444\nit's late
2194 String no_newline = sub(chocolike, "\n", " ") # "I like chocolate when it's late"
2195 }
2196}
2197```
2198"#
2199 )
2200 .build(),
2201 )
2202 .into(),
2203 )
2204 .is_none()
2205 );
2206
2207 assert!(
2209 functions
2210 .insert(
2211 "split",
2212 MonomorphicFunction::new(
2213 FunctionSignature::builder()
2214 .min_version(SupportedVersion::V1(V1::Three))
2215 .parameter("input", PrimitiveType::String, "The input string.")
2216 .parameter("delimiter", PrimitiveType::String, "The delimiter to split on as a regular expression.")
2217 .ret(array_string.clone())
2218 .definition(
2219 r#"
2220Given the two `String` parameters `input` and `delimiter`, this function splits the input string on the provided delimiter and stores the results in a `Array[String]`. `delimiter` is a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) and is evaluated as a [POSIX Extended Regular Expression (ERE)](https://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended).
2221Regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped (e.g., `"\\t"`).
2222
2223**Parameters**:
2224
22251. `String`: the input string.
22262. `String`: the delimiter to split on as a regular expression.
2227
2228**Returns**: the parts of the input string split by the delimiter. If the input delimiter does not match anything in the input string, an array containing a single entry of the input string is returned.
2229
2230<details>
2231<summary>
2232Example: test_split.wdl
2233
2234```wdl
2235version 1.3
2236
2237workflow test_split {
2238 String in = "Here's an example\nthat takes up multiple lines"
2239
2240 output {
2241 Array[String] split_by_word = split(in, " ")
2242 Array[String] split_by_newline = split(in, "\\n")
2243 Array[String] split_by_both = split(in, "\s")
2244 }
2245}
2246```
2247"#
2248 )
2249 .build(),
2250 )
2251 .into(),
2252 )
2253 .is_none()
2254 );
2255
2256 const BASENAME_DEFINITION: &str = r#"
2257Returns the "basename" of a file or directory - the name after the last directory separator in the path.
2258
2259The optional second parameter specifies a literal suffix to remove from the file name. If the file name does not end with the specified suffix then it is ignored.
2260
2261**Parameters**
2262
22631. `File|Directory`: Path of the file or directory to read. If the argument is a `String`, it is assumed to be a local file path relative to the current working directory of the task.
22642. `String`: (Optional) Suffix to remove from the file name.
2265
2266**Returns**: The file's basename as a `String`.
2267
2268Example: test_basename.wdl
2269
2270```wdl
2271version 1.2
2272
2273workflow test_basename {
2274 output {
2275 Boolean is_true1 = basename("/path/to/file.txt") == "file.txt"
2276 Boolean is_true2 = basename("/path/to/file.txt", ".txt") == "file"
2277 Boolean is_true3 = basename("/path/to/dir") == "dir"
2278 }
2279}
2280```
2281"#;
2282
2283 assert!(
2285 functions
2286 .insert(
2287 "basename",
2288 PolymorphicFunction::new(vec![
2289 FunctionSignature::builder()
2290 .required(1)
2291 .parameter(
2292 "path",
2293 PrimitiveType::File,
2294 "Path of the file or directory to read. If the argument is a \
2295 `String`, it is assumed to be a local file path relative to the \
2296 current working directory of the task.",
2297 )
2298 .parameter(
2299 "suffix",
2300 PrimitiveType::String,
2301 "(Optional) Suffix to remove from the file name.",
2302 )
2303 .ret(PrimitiveType::String)
2304 .definition(BASENAME_DEFINITION)
2305 .build(),
2306 FunctionSignature::builder()
2311 .min_version(SupportedVersion::V1(V1::Two))
2312 .required(1)
2313 .parameter(
2314 "path",
2315 PrimitiveType::String,
2316 "Path of the file or directory to read. If the argument is a \
2317 `String`, it is assumed to be a local file path relative to the \
2318 current working directory of the task."
2319 )
2320 .parameter(
2321 "suffix",
2322 PrimitiveType::String,
2323 "(Optional) Suffix to remove from the file name."
2324 )
2325 .ret(PrimitiveType::String)
2326 .definition(BASENAME_DEFINITION)
2327 .build(),
2328 FunctionSignature::builder()
2329 .min_version(SupportedVersion::V1(V1::Two))
2330 .required(1)
2331 .parameter(
2332 "path",
2333 PrimitiveType::Directory,
2334 "Path of the file or directory to read. If the argument is a \
2335 `String`, it is assumed to be a local file path relative to the \
2336 current working directory of the task.",
2337 )
2338 .parameter(
2339 "suffix",
2340 PrimitiveType::String,
2341 "(Optional) Suffix to remove from the file name.",
2342 )
2343 .ret(PrimitiveType::String)
2344 .definition(BASENAME_DEFINITION)
2345 .build(),
2346 ])
2347 .into(),
2348 )
2349 .is_none()
2350 );
2351
2352 const JOIN_PATHS_DEFINITION: &str = r#"
2353Joins together two or more paths into an absolute path in the execution environment's filesystem.
2354
2355There are three variants of this function:
2356
23571. `String join_paths(Directory, String)`: Joins together exactly two paths. The second path is relative to the first directory and may specify a file or directory.
23582. `String join_paths(Directory, Array[String]+)`: Joins together any number of relative paths with a base directory. The paths in the array argument must all be relative. The *last* element may specify a file or directory; all other elements must specify a directory.
23593. `String join_paths(Array[String]+)`: Joins together any number of paths. The array must not be empty. The *first* element of the array may be either absolute or relative; subsequent path(s) must be relative. The *last* element may specify a file or directory; all other elements must specify a directory.
2360
2361An absolute path starts with `/` and indicates that the path is relative to the root of the environment in which the task is executed. Only the first path may be absolute. If any subsequent paths are absolute, it is an error.
2362
2363A relative path does not start with `/` and indicates the path is relative to its parent directory. It is up to the execution engine to determine which directory to use as the parent when resolving relative paths; by default it is the working directory in which the task is executed.
2364
2365**Parameters**
2366
23671. `Directory|Array[String]+`: Either a directory path or an array of paths.
23682. `String|Array[String]+`: A relative path or paths; only allowed if the first argument is a `Directory`.
2369
2370**Returns**: A `String` representing an absolute path that results from joining all the paths in order (left-to-right), and resolving the resulting path against the default parent directory if it is relative.
2371
2372Example: join_paths_task.wdl
2373
2374```wdl
2375version 1.2
2376
2377task join_paths {
2378 input {
2379 Directory abs_dir = "/usr"
2380 String abs_str = "/usr"
2381 String rel_dir_str = "bin"
2382 String rel_file = "echo"
2383 }
2384
2385 # these are all equivalent to '/usr/bin/echo'
2386 String bin1 = join_paths(abs_dir, [rel_dir_str, rel_file])
2387 String bin2 = join_paths(abs_str, [rel_dir_str, rel_file])
2388 String bin3 = join_paths([abs_str, rel_dir_str, rel_file])
2389
2390 command <<<
2391 ~{bin1} -n "hello" > output.txt
2392 >>>
2393
2394 output {
2395 Boolean bins_equal = (bin1 == bin2) && (bin1 == bin3)
2396 String result = read_string("output.txt")
2397 }
2398
2399 runtime {
2400 container: "ubuntu:latest"
2401 }
2402}
2403```
2404"#;
2405
2406 assert!(
2408 functions
2409 .insert(
2410 "join_paths",
2411 PolymorphicFunction::new(vec![
2412 FunctionSignature::builder()
2413 .min_version(SupportedVersion::V1(V1::Two))
2414 .parameter(
2415 "base",
2416 PrimitiveType::Directory,
2417 "Either a path or an array of paths.",
2418 )
2419 .parameter(
2420 "relative",
2421 PrimitiveType::String,
2422 "A relative path or paths; only allowed if the first argument is a \
2423 `Directory`.",
2424 )
2425 .ret(PrimitiveType::String)
2426 .definition(JOIN_PATHS_DEFINITION)
2427 .build(),
2428 FunctionSignature::builder()
2429 .min_version(SupportedVersion::V1(V1::Two))
2430 .parameter(
2431 "base",
2432 PrimitiveType::Directory,
2433 "Either a path or an array of paths."
2434 )
2435 .parameter(
2436 "relative",
2437 array_string_non_empty.clone(),
2438 "A relative path or paths; only allowed if the first argument is a \
2439 `Directory`."
2440 )
2441 .ret(PrimitiveType::String)
2442 .definition(JOIN_PATHS_DEFINITION)
2443 .build(),
2444 FunctionSignature::builder()
2445 .min_version(SupportedVersion::V1(V1::Two))
2446 .parameter(
2447 "paths",
2448 array_string_non_empty.clone(),
2449 "Either a path or an array of paths."
2450 )
2451 .ret(PrimitiveType::String)
2452 .definition(JOIN_PATHS_DEFINITION)
2453 .build(),
2454 ])
2455 .into(),
2456 )
2457 .is_none()
2458 );
2459
2460 assert!(
2462 functions
2463 .insert(
2464 "glob",
2465 MonomorphicFunction::new(
2466 FunctionSignature::builder()
2467 .parameter("pattern", PrimitiveType::String, "The glob string.")
2468 .ret(array_file.clone())
2469 .definition(
2470 r#"
2471Returns the Bash expansion of the [glob string](https://en.wikipedia.org/wiki/Glob_(programming)) relative to the task's execution directory, and in the same order.
2472
2473`glob` finds all of the files (but not the directories) in the same order as would be matched by running `echo <glob>` in Bash from the task's execution directory.
2474
2475At least in standard Bash, glob expressions are not evaluated recursively, i.e., files in nested directories are not included.
2476
2477**Parameters**:
2478
24791. `String`: The glob string.
2480
2481**Returns**: A array of all files matched by the glob.
2482
2483Example: gen_files_task.wdl
2484
2485```wdl
2486version 1.2
2487
2488task gen_files {
2489 input {
2490 Int num_files
2491 }
2492
2493 command <<<
2494 for i in 1..~{num_files}; do
2495 printf ${i} > a_file_${i}.txt
2496 done
2497 mkdir a_dir
2498 touch a_dir/a_inner.txt
2499 >>>
2500
2501 output {
2502 Array[File] files = glob("a_*")
2503 Int glob_len = length(files)
2504 }
2505}
2506```
2507"#
2508 )
2509 .build(),
2510 )
2511 .into(),
2512 )
2513 .is_none()
2514 );
2515
2516 const SIZE_DEFINITION: &str = r#"
2517Determines the size of a file, directory, or the sum total sizes of the files/directories contained within a compound value. The files may be optional values; `None` values have a size of `0.0`. By default, the size is returned in bytes unless the optional second argument is specified with a [unit](#units-of-storage)
2518
2519In the second variant of the `size` function, the parameter type `X` represents any compound type that contains `File` or `File?` nested at any depth.
2520
2521If the size cannot be represented in the specified unit because the resulting value is too large to fit in a `Float`, an error is raised. It is recommended to use a unit that will always be large enough to handle any expected inputs without numerical overflow.
2522
2523**Parameters**
2524
25251. `File|File?|Directory|Directory?|X|X?`: A file, directory, or a compound value containing files/directories, for which to determine the size.
25262. `String`: (Optional) The unit of storage; defaults to 'B'.
2527
2528**Returns**: The size of the files/directories as a `Float`.
2529
2530Example: file_sizes_task.wdl
2531
2532```wdl
2533version 1.2
2534
2535task file_sizes {
2536 command <<<
2537 printf "this file is 22 bytes\n" > created_file
2538 >>>
2539
2540 File? missing_file = None
2541
2542 output {
2543 File created_file = "created_file"
2544 Float missing_file_bytes = size(missing_file)
2545 Float created_file_bytes = size(created_file, "B")
2546 Float multi_file_kb = size([created_file, missing_file], "K")
2547
2548 Map[String, Pair[Int, File]] nested = {
2549 "a": (10, created_file),
2550 "b": (50, missing_file)
2551 }
2552 Float nested_bytes = size(nested)
2553 }
2554
2555 requirements {
2556 container: "ubuntu:latest"
2557 }
2558}
2559```
2560"#;
2561
2562 assert!(
2564 functions
2565 .insert(
2566 "size",
2567 PolymorphicFunction::new(vec![
2568 FunctionSignature::builder()
2571 .min_version(SupportedVersion::V1(V1::Two))
2572 .required(1)
2573 .parameter(
2574 "value",
2575 Type::None,
2576 "A file, directory, or a compound value containing files/directories, \
2577 for which to determine the size."
2578 )
2579 .parameter(
2580 "unit",
2581 PrimitiveType::String,
2582 "(Optional) The unit of storage; defaults to 'B'."
2583 )
2584 .ret(PrimitiveType::Float)
2585 .definition(SIZE_DEFINITION)
2586 .build(),
2587 FunctionSignature::builder()
2588 .required(1)
2589 .parameter(
2590 "value",
2591 Type::from(PrimitiveType::File).optional(),
2592 "A file, directory, or a compound value containing files/directories, \
2593 for which to determine the size."
2594 )
2595 .parameter(
2596 "unit",
2597 PrimitiveType::String,
2598 "(Optional) The unit of storage; defaults to 'B'."
2599 )
2600 .ret(PrimitiveType::Float)
2601 .definition(SIZE_DEFINITION)
2602 .build(),
2603 FunctionSignature::builder()
2608 .min_version(SupportedVersion::V1(V1::Two))
2609 .required(1)
2610 .parameter(
2611 "value",
2612 Type::from(PrimitiveType::String).optional(),
2613 "A file, directory, or a compound value containing files/directories, \
2614 for which to determine the size.",
2615 )
2616 .parameter(
2617 "unit",
2618 PrimitiveType::String,
2619 "(Optional) The unit of storage; defaults to 'B'.",
2620 )
2621 .ret(PrimitiveType::Float)
2622 .definition(SIZE_DEFINITION)
2623 .build(),
2624 FunctionSignature::builder()
2625 .min_version(SupportedVersion::V1(V1::Two))
2626 .required(1)
2627 .parameter(
2628 "value",
2629 Type::from(PrimitiveType::Directory).optional(),
2630 "A file, directory, or a compound value containing files/directories, \
2631 for which to determine the size."
2632 )
2633 .parameter(
2634 "unit",
2635 PrimitiveType::String,
2636 "(Optional) The unit of storage; defaults to 'B'."
2637 )
2638 .ret(PrimitiveType::Float)
2639 .definition(SIZE_DEFINITION)
2640 .build(),
2641 FunctionSignature::builder()
2642 .required(1)
2643 .type_parameter("X", SizeableConstraint)
2644 .parameter(
2645 "value",
2646 GenericType::Parameter("X"),
2647 "A file, directory, or a compound value containing files/directories, \
2648 for which to determine the size."
2649 )
2650 .parameter(
2651 "unit",
2652 PrimitiveType::String,
2653 "(Optional) The unit of storage; defaults to 'B'."
2654 )
2655 .ret(PrimitiveType::Float)
2656 .definition(SIZE_DEFINITION)
2657 .build(),
2658 ])
2659 .into(),
2660 )
2661 .is_none()
2662 );
2663
2664 assert!(
2666 functions
2667 .insert(
2668 "stdout",
2669 MonomorphicFunction::new(
2670 FunctionSignature::builder()
2671 .ret(PrimitiveType::File)
2672 .definition(
2673 r#"
2674Returns the value of the executed command's standard output (stdout) as a `File`. The engine should give the file a random name and write it in a temporary directory, so as not to conflict with any other task output files.
2675
2676**Parameters**: None
2677
2678**Returns**: A `File` whose contents are the stdout generated by the command of the task where the function is called.
2679
2680Example: echo_stdout.wdl
2681
2682```wdl
2683version 1.2
2684
2685task echo_stdout {
2686 command <<<
2687 printf "hello world"
2688 >>>
2689
2690 output {
2691 File message = read_string(stdout())
2692 }
2693}
2694```
2695"#
2696 )
2697 .build(),
2698 )
2699 .into(),
2700 )
2701 .is_none()
2702 );
2703
2704 assert!(
2706 functions
2707 .insert(
2708 "stderr",
2709 MonomorphicFunction::new(
2710 FunctionSignature::builder()
2711 .ret(PrimitiveType::File)
2712 .definition(
2713 r#"
2714Returns the value of the executed command's standard error (stderr) as a `File`. The file should be given a random name and written in a temporary directory, so as not to conflict with any other task output files.
2715
2716**Parameters**: None
2717
2718**Returns**: A `File` whose contents are the stderr generated by the command of the task where the function is called.
2719
2720Example: echo_stderr.wdl
2721
2722```wdl
2723version 1.2
2724
2725task echo_stderr {
2726 command <<<
2727 >&2 printf "hello world"
2728 >>>
2729
2730 output {
2731 File message = read_string(stderr())
2732 }
2733}
2734```
2735"#
2736 )
2737 .build(),
2738 )
2739 .into(),
2740 )
2741 .is_none()
2742 );
2743
2744 assert!(
2746 functions
2747 .insert(
2748 "read_string",
2749 MonomorphicFunction::new(
2750 FunctionSignature::builder()
2751 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2752 .ret(PrimitiveType::String)
2753 .definition(
2754 r#"
2755Reads an entire file as a `String`, with any trailing end-of-line characters (`` and `\n`) stripped off. If the file is empty, an empty string is returned.
2756
2757If the file contains any internal newline characters, they are left in tact.
2758
2759**Parameters**
2760
27611. `File`: Path of the file to read.
2762
2763**Returns**: A `String`.
2764
2765Example: read_string_task.wdl
2766
2767```wdl
2768version 1.2
2769
2770task read_string {
2771 # this file will contain "this\nfile\nhas\nfive\nlines\n"
2772 File f = write_lines(["this", "file", "has", "five", "lines"])
2773
2774 command <<<
2775 cat ~{f}
2776 >>>
2777
2778 output {
2779 # s will contain "this\nfile\nhas\nfive\nlines"
2780 String s = read_string(stdout())
2781 }
2782}
2783```
2784"#
2785 )
2786 .build(),
2787 )
2788 .into(),
2789 )
2790 .is_none()
2791 );
2792
2793 assert!(
2795 functions
2796 .insert(
2797 "read_int",
2798 MonomorphicFunction::new(
2799 FunctionSignature::builder()
2800 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2801 .ret(PrimitiveType::Integer)
2802 .definition(
2803 r#"
2804Reads a file that contains a single line containing only an integer and (optional) whitespace. If the line contains a valid integer, that value is returned as an `Int`. If the file is empty or does not contain a single integer, an error is raised.
2805
2806**Parameters**
2807
28081. `File`: Path of the file to read.
2809
2810**Returns**: An `Int`.
2811
2812Example: read_int_task.wdl
2813
2814```wdl
2815version 1.2
2816
2817task read_int {
2818 command <<<
2819 printf " 1 \n" > int_file
2820 >>>
2821
2822 output {
2823 Int i = read_int("int_file")
2824 }
2825}
2826```
2827"#
2828 )
2829 .build(),
2830 )
2831 .into(),
2832 )
2833 .is_none()
2834 );
2835
2836 assert!(
2838 functions
2839 .insert(
2840 "read_float",
2841 MonomorphicFunction::new(
2842 FunctionSignature::builder()
2843 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2844 .ret(PrimitiveType::Float)
2845 .definition(
2846 r#"
2847Reads a file that contains only a numeric value and (optional) whitespace. If the line contains a valid floating point number, that value is returned as a `Float`. If the file is empty or does not contain a single float, an error is raised.
2848
2849**Parameters**
2850
28511. `File`: Path of the file to read.
2852
2853**Returns**: A `Float`.
2854
2855Example: read_float_task.wdl
2856
2857```wdl
2858version 1.2
2859
2860task read_float {
2861 command <<<
2862 printf " 1 \n" > int_file
2863 printf " 2.0 \n" > float_file
2864 >>>
2865
2866 output {
2867 Float f1 = read_float("int_file")
2868 Float f2 = read_float("float_file")
2869 }
2870}
2871```
2872"#
2873 )
2874 .build(),
2875 )
2876 .into(),
2877 )
2878 .is_none()
2879 );
2880
2881 assert!(
2883 functions
2884 .insert(
2885 "read_boolean",
2886 MonomorphicFunction::new(
2887 FunctionSignature::builder()
2888 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2889 .ret(PrimitiveType::Boolean)
2890 .definition(
2891 r#"
2892Reads a file that contains a single line containing only a boolean value and (optional) whitespace. If the non-whitespace content of the line is "true" or "false", that value is returned as a `Boolean`. If the file is empty or does not contain a single boolean, an error is raised. The comparison is case- and whitespace-insensitive.
2893
2894**Parameters**
2895
28961. `File`: Path of the file to read.
2897
2898**Returns**: A `Boolean`.
2899
2900Example: read_bool_task.wdl
2901
2902```wdl
2903version 1.2
2904
2905task read_bool {
2906 command <<<
2907 printf " true \n" > true_file
2908 printf " FALSE \n" > false_file
2909 >>>
2910
2911 output {
2912 Boolean b1 = read_boolean("true_file")
2913 Boolean b2 = read_boolean("false_file")
2914 }
2915}
2916```
2917"#
2918 )
2919 .build(),
2920 )
2921 .into(),
2922 )
2923 .is_none()
2924 );
2925
2926 assert!(
2928 functions
2929 .insert(
2930 "read_lines",
2931 MonomorphicFunction::new(
2932 FunctionSignature::builder()
2933 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2934 .ret(array_string.clone())
2935 .definition(
2936 r#"
2937Reads each line of a file as a `String`, and returns all lines in the file as an `Array[String]`. Trailing end-of-line characters (`` and `\n`) are removed from each line.
2938
2939The order of the lines in the returned `Array[String]` is the order in which the lines appear in the file.
2940
2941If the file is empty, an empty array is returned.
2942
2943**Parameters**
2944
29451. `File`: Path of the file to read.
2946
2947**Returns**: An `Array[String]` representation of the lines in the file.
2948
2949Example: grep_task.wdl
2950
2951```wdl
2952version 1.2
2953
2954task grep {
2955 input {
2956 String pattern
2957 File file
2958 }
2959
2960 command <<<
2961 grep '~{pattern}' ~{file}
2962 >>>
2963
2964 output {
2965 Array[String] matches = read_lines(stdout())
2966 }
2967
2968 requirements {
2969 container: "ubuntu:latest"
2970 }
2971}
2972```
2973"#
2974 )
2975 .build(),
2976 )
2977 .into(),
2978 )
2979 .is_none()
2980 );
2981
2982 assert!(
2984 functions
2985 .insert(
2986 "write_lines",
2987 MonomorphicFunction::new(
2988 FunctionSignature::builder()
2989 .parameter("array", array_string.clone(), "`Array` of strings to write.")
2990 .ret(PrimitiveType::File)
2991 .definition(
2992 r#"
2993Writes a file with one line for each element in a `Array[String]`. All lines are terminated by the newline (`\n`) character (following the [POSIX standard](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_206)). If the `Array` is empty, an empty file is written.
2994
2995**Parameters**
2996
29971. `Array[String]`: Array of strings to write.
2998
2999**Returns**: A `File`.
3000
3001Example: write_lines_task.wdl
3002
3003```wdl
3004version 1.2
3005
3006task write_lines {
3007 input {
3008 Array[String] array = ["first", "second", "third"]
3009 }
3010
3011 command <<<
3012 paste -s -d'\t' ~{write_lines(array)}
3013 >>>
3014
3015 output {
3016 String s = read_string(stdout())
3017 }
3018
3019 requirements {
3020 container: "ubuntu:latest"
3021 }
3022}
3023```
3024"#
3025 )
3026 .build(),
3027 )
3028 .into(),
3029 )
3030 .is_none()
3031 );
3032
3033 const READ_TSV_DEFINITION: &str = r#"
3034Reads a tab-separated value (TSV) file as an `Array[Array[String]]` representing a table of values. Trailing end-of-line characters (`` and `\n`) are removed from each line.
3035
3036This function has three variants:
3037
30381. `Array[Array[String]] read_tsv(File, [false])`: Returns each row of the table as an `Array[String]`. There is no requirement that the rows of the table are all the same length.
30392. `Array[Object] read_tsv(File, true)`: The second parameter must be `true` and specifies that the TSV file contains a header line. Each row is returned as an `Object` with its keys determined by the header (the first line in the file) and its values as `String`s. All rows in the file must be the same length and the field names in the header row must be valid `Object` field names, or an error is raised.
30403. `Array[Object] read_tsv(File, Boolean, Array[String])`: The second parameter specifies whether the TSV file contains a header line, and the third parameter is an array of field names that is used to specify the field names to use for the returned `Object`s. If the second parameter is `true`, the specified field names override those in the file's header (i.e., the header line is ignored).
3041
3042If the file is empty, an empty array is returned.
3043
3044If the entire contents of the file can not be read for any reason, the calling task or workflow fails with an error. Examples of failure include, but are not limited to, not having access to the file, resource limitations (e.g. memory) when reading the file, and implementation-imposed file size limits.
3045
3046**Parameters**
3047
30481. `File`: The TSV file to read.
30492. `Boolean`: (Optional) Whether to treat the file's first line as a header.
30503. `Array[String]`: (Optional) An array of field names. If specified, then the second parameter is also required.
3051
3052**Returns**: An `Array` of rows in the TSV file, where each row is an `Array[String]` of fields or an `Object` with keys determined by the second and third parameters and `String` values.
3053
3054Example: read_tsv_task.wdl
3055
3056```wdl
3057version 1.2
3058
3059task read_tsv {
3060 command <<<
3061 {
3062 printf "row1\tvalue1\n"
3063 printf "row2\tvalue2\n"
3064 printf "row3\tvalue3\n"
3065 } >> data.no_headers.tsv
3066
3067 {
3068 printf "header1\theader2\n"
3069 printf "row1\tvalue1\n"
3070 printf "row2\tvalue2\n"
3071 printf "row3\tvalue3\n"
3072 } >> data.headers.tsv
3073 >>>
3074
3075 output {
3076 Array[Array[String]] output_table = read_tsv("data.no_headers.tsv")
3077 Array[Object] output_objs1 = read_tsv("data.no_headers.tsv", false, ["name", "value"])
3078 Array[Object] output_objs2 = read_tsv("data.headers.tsv", true)
3079 Array[Object] output_objs3 = read_tsv("data.headers.tsv", true, ["name", "value"])
3080 }
3081}
3082```
3083"#;
3084
3085 assert!(
3087 functions
3088 .insert(
3089 "read_tsv",
3090 PolymorphicFunction::new(vec![
3091 FunctionSignature::builder()
3092 .parameter("file", PrimitiveType::File, "The TSV file to read.")
3093 .ret(array_array_string.clone())
3094 .definition(READ_TSV_DEFINITION)
3095 .build(),
3096 FunctionSignature::builder()
3097 .min_version(SupportedVersion::V1(V1::Two))
3098 .parameter("file", PrimitiveType::File, "The TSV file to read.")
3099 .parameter(
3100 "header",
3101 PrimitiveType::Boolean,
3102 "(Optional) Whether to treat the file's first line as a header.",
3103 )
3104 .ret(array_object.clone())
3105 .definition(READ_TSV_DEFINITION)
3106 .build(),
3107 FunctionSignature::builder()
3108 .min_version(SupportedVersion::V1(V1::Two))
3109 .parameter("file", PrimitiveType::File, "The TSV file to read.")
3110 .parameter(
3111 "header",
3112 PrimitiveType::Boolean,
3113 "(Optional) Whether to treat the file's first line as a header.",
3114 )
3115 .parameter(
3116 "columns",
3117 array_string.clone(),
3118 "(Optional) An array of field names. If specified, then the second \
3119 parameter is also required.",
3120 )
3121 .ret(array_object.clone())
3122 .definition(READ_TSV_DEFINITION)
3123 .build(),
3124 ])
3125 .into(),
3126 )
3127 .is_none()
3128 );
3129
3130 const WRITE_TSV_DEFINITION: &str = r#"
3131Given an `Array` of elements, writes a tab-separated value (TSV) file with one line for each element.
3132
3133There are three variants of this function:
3134
31351. `File write_tsv(Array[Array[String]])`: Each element is concatenated using a tab ('\t') delimiter and written as a row in the file. There is no header row.
3136
31372. `File write_tsv(Array[Array[String]], true, Array[String])`: The second argument must be `true` and the third argument provides an `Array` of column names. The column names are concatenated to create a header that is written as the first row of the file. All elements must be the same length as the header array.
3138
31393. `File write_tsv(Array[Struct], [Boolean, [Array[String]]])`: Each element is a struct whose field values are concatenated in the order the fields are defined. The optional second argument specifies whether to write a header row. If it is `true`, then the header is created from the struct field names. If the second argument is `true`, then the optional third argument may be used to specify column names to use instead of the struct field names.
3140
3141Each line is terminated by the newline (`\n`) character.
3142
3143The generated file should be given a random name and written in a temporary directory, so as not to conflict with any other task output files.
3144
3145If the entire contents of the file can not be written for any reason, the calling task or workflow fails with an error. Examples of failure include, but are not limited to, insufficient disk space to write the file.
3146
3147
3148**Parameters**
3149
31501. `Array[Array[String]] | Array[Struct]`: An array of rows, where each row is either an `Array` of column values or a struct whose values are the column values.
31512. `Boolean`: (Optional) Whether to write a header row.
31523. `Array[String]`: An array of column names. If the first argument is `Array[Array[String]]` and the second argument is `true` then it is required, otherwise it is optional. Ignored if the second argument is `false`.
3153
3154
3155**Returns**: A `File`.
3156
3157Example: write_tsv_task.wdl
3158
3159```wdl
3160version 1.2
3161
3162task write_tsv {
3163 input {
3164 Array[Array[String]] array = [["one", "two", "three"], ["un", "deux", "trois"]]
3165 Array[Numbers] structs = [
3166 Numbers {
3167 first: "one",
3168 second: "two",
3169 third: "three"
3170 },
3171 Numbers {
3172 first: "un",
3173 second: "deux",
3174 third: "trois"
3175 }
3176 ]
3177 }
3178
3179 command <<<
3180 cut -f 1 ~{write_tsv(array)} >> array_no_header.txt
3181 cut -f 1 ~{write_tsv(array, true, ["first", "second", "third"])} > array_header.txt
3182 cut -f 1 ~{write_tsv(structs)} >> structs_default.txt
3183 cut -f 2 ~{write_tsv(structs, false)} >> structs_no_header.txt
3184 cut -f 2 ~{write_tsv(structs, true)} >> structs_header.txt
3185 cut -f 3 ~{write_tsv(structs, true, ["no1", "no2", "no3"])} >> structs_user_header.txt
3186 >>>
3187
3188 output {
3189 Array[String] array_no_header = read_lines("array_no_header.txt")
3190 Array[String] array_header = read_lines("array_header.txt")
3191 Array[String] structs_default = read_lines("structs_default.txt")
3192 Array[String] structs_no_header = read_lines("structs_no_header.txt")
3193 Array[String] structs_header = read_lines("structs_header.txt")
3194 Array[String] structs_user_header = read_lines("structs_user_header.txt")
3195
3196 }
3197
3198 requirements {
3199 container: "ubuntu:latest"
3200 }
3201}
3202```
3203"#;
3204
3205 assert!(
3207 functions
3208 .insert(
3209 "write_tsv",
3210 PolymorphicFunction::new(vec![
3211 FunctionSignature::builder()
3212 .parameter(
3213 "data",
3214 array_array_string.clone(),
3215 "An array of rows, where each row is either an `Array` of column \
3216 values or a struct whose values are the column values.",
3217 )
3218 .ret(PrimitiveType::File)
3219 .definition(WRITE_TSV_DEFINITION)
3220 .build(),
3221 FunctionSignature::builder()
3222 .min_version(SupportedVersion::V1(V1::Two))
3223 .parameter(
3224 "data",
3225 array_array_string.clone(),
3226 "An array of rows, where each row is either an `Array` of column \
3227 values or a struct whose values are the column values.",
3228 )
3229 .parameter(
3230 "header",
3231 PrimitiveType::Boolean,
3232 "(Optional) Whether to write a header row.",
3233 )
3234 .parameter(
3235 "columns",
3236 array_string.clone(),
3237 "An array of column names. If the first argument is \
3238 `Array[Array[String]]` and the second argument is true then it is \
3239 required, otherwise it is optional. Ignored if the second argument \
3240 is false."
3241 )
3242 .ret(PrimitiveType::File)
3243 .definition(WRITE_TSV_DEFINITION)
3244 .build(),
3245 FunctionSignature::builder()
3246 .min_version(SupportedVersion::V1(V1::Two))
3247 .type_parameter("S", PrimitiveStructConstraint)
3248 .required(1)
3249 .parameter(
3250 "data",
3251 GenericArrayType::new(GenericType::Parameter("S")),
3252 "An array of rows, where each row is either an `Array` of column \
3253 values or a struct whose values are the column values.",
3254 )
3255 .parameter(
3256 "header",
3257 PrimitiveType::Boolean,
3258 "(Optional) Whether to write a header row.",
3259 )
3260 .parameter(
3261 "columns",
3262 array_string.clone(),
3263 "An array of column names. If the first argument is \
3264 `Array[Array[String]]` and the second argument is true then it is \
3265 required, otherwise it is optional. Ignored if the second argument \
3266 is false."
3267 )
3268 .ret(PrimitiveType::File)
3269 .definition(WRITE_TSV_DEFINITION)
3270 .build(),
3271 ])
3272 .into(),
3273 )
3274 .is_none()
3275 );
3276
3277 assert!(
3279 functions
3280 .insert(
3281 "read_map",
3282 MonomorphicFunction::new(
3283 FunctionSignature::builder()
3284 .parameter(
3285 "file",
3286 PrimitiveType::File,
3287 "Path of the two-column TSV file to read.",
3288 )
3289 .ret(map_string_string.clone())
3290 .definition(
3291 r#"
3292Reads a tab-separated value (TSV) file representing a set of pairs. Each row must have exactly two columns, e.g., `col1\tcol2`. Trailing end-of-line characters (`` and `\n`) are removed from each line.
3293
3294Each pair is added to a `Map[String, String]` in order. The values in the first column must be unique; if there are any duplicate keys, an error is raised.
3295
3296If the file is empty, an empty map is returned.
3297
3298**Parameters**
3299
33001. `File`: Path of the two-column TSV file to read.
3301
3302**Returns**: A `Map[String, String]`, with one element for each row in the TSV file.
3303
3304Example: read_map_task.wdl
3305
3306```wdl
3307version 1.2
3308
3309task read_map {
3310 command <<<
3311 printf "key1\tvalue1\n" >> map_file
3312 printf "key2\tvalue2\n" >> map_file
3313 >>>
3314
3315 output {
3316 Map[String, String] mapping = read_map(stdout())
3317 }
3318}
3319```
3320"#
3321 )
3322 .build(),
3323 )
3324 .into(),
3325 )
3326 .is_none()
3327 );
3328
3329 assert!(
3331 functions
3332 .insert(
3333 "write_map",
3334 MonomorphicFunction::new(
3335 FunctionSignature::builder()
3336 .parameter(
3337 "map",
3338 map_string_string.clone(),
3339 "A `Map`, where each element will be a row in the generated file.",
3340 )
3341 .ret(PrimitiveType::File)
3342 .definition(
3343 r#"
3344Writes a tab-separated value (TSV) file with one line for each element in a `Map[String, String]`. Each element is concatenated into a single tab-delimited string of the format `~{key}\t~{value}`. Each line is terminated by the newline (`\n`) character. If the `Map` is empty, an empty file is written.
3345
3346Since `Map`s are ordered, the order of the lines in the file is guaranteed to be the same order that the elements were added to the `Map`.
3347
3348**Parameters**
3349
33501. `Map[String, String]`: A `Map`, where each element will be a row in the generated file.
3351
3352**Returns**: A `File`.
3353
3354Example: write_map_task.wdl
3355
3356```wdl
3357version 1.2
3358
3359task write_map {
3360 input {
3361 Map[String, String] map = {"key1": "value1", "key2": "value2"}
3362 }
3363
3364 command <<<
3365 cut -f 1 ~{write_map(map)}
3366 >>>
3367
3368 output {
3369 Array[String] keys = read_lines(stdout())
3370 }
3371
3372 requirements {
3373 container: "ubuntu:latest"
3374 }
3375}
3376```
3377"#
3378 )
3379 .build(),
3380 )
3381 .into(),
3382 )
3383 .is_none()
3384 );
3385
3386 assert!(
3388 functions
3389 .insert(
3390 "read_json",
3391 MonomorphicFunction::new(
3392 FunctionSignature::builder()
3393 .parameter("file", PrimitiveType::File, "Path of the JSON file to read.")
3394 .ret(Type::Union)
3395 .definition(
3396 r#"
3397Reads a JSON file into a WDL value whose type depends on the file's contents. The mapping of JSON type to WDL type is:
3398
3399| JSON Type | WDL Type |
3400| --------- | ---------------- |
3401| object | `Object` |
3402| array | `Array[X]` |
3403| number | `Int` or `Float` |
3404| string | `String` |
3405| boolean | `Boolean` |
3406| null | `None` |
3407
3408The return value is of type [`Union`](#union-hidden-type) and must be used in a context where it can be coerced to the expected type, or an error is raised. For example, if the JSON file contains `null`, then the return value will be `None`, meaning the value can only be used in a context where an optional type is expected.
3409
3410If the JSON file contains an array, then all the elements of the array must be coercible to the same type, or an error is raised.
3411
3412The `read_json` function does not have access to any WDL type information, so it cannot return an instance of a specific `Struct` type. Instead, it returns a generic `Object` value that must be coerced to the desired `Struct` type.
3413
3414Note that an empty file is not valid according to the JSON specification, and so calling `read_json` on an empty file raises an error.
3415
3416**Parameters**
3417
34181. `File`: Path of the JSON file to read.
3419
3420**Returns**: A value whose type is dependent on the contents of the JSON file.
3421
3422Example: read_person.wdl
3423
3424```wdl
3425version 1.2
3426
3427struct Person {
3428 String name
3429 Int age
3430}
3431
3432workflow read_person {
3433 input {
3434 File json_file
3435 }
3436
3437 output {
3438 Person p = read_json(json_file)
3439 }
3440}
3441```
3442"#
3443 )
3444 .build(),
3445 )
3446 .into(),
3447 )
3448 .is_none()
3449 );
3450
3451 assert!(
3453 functions
3454 .insert(
3455 "write_json",
3456 MonomorphicFunction::new(
3457 FunctionSignature::builder()
3458 .type_parameter("X", JsonSerializableConstraint)
3459 .parameter(
3460 "value",
3461 GenericType::Parameter("X"),
3462 "A WDL value of a supported type.",
3463 )
3464 .ret(PrimitiveType::File)
3465 .definition(
3466 r#"
3467Writes a JSON file with the serialized form of a WDL value. The following WDL types can be serialized:
3468
3469| WDL Type | JSON Type |
3470| ---------------- | --------- |
3471| `Struct` | object |
3472| `Object` | object |
3473| `Map[String, X]` | object |
3474| `Array[X]` | array |
3475| `Int` | number |
3476| `Float` | number |
3477| `String` | string |
3478| `File` | string |
3479| `Boolean` | boolean |
3480| `None` | null |
3481
3482When serializing compound types, all nested types must be serializable or an error is raised.
3483
3484**Parameters**
3485
34861. `X`: A WDL value of a supported type.
3487
3488**Returns**: A `File`.
3489
3490Example: write_json_fail.wdl
3491
3492```wdl
3493version 1.2
3494
3495workflow write_json_fail {
3496 Pair[Int, Map[Int, String]] x = (1, {2: "hello"})
3497 # this fails with an error - Map with Int keys is not serializable
3498 File f = write_json(x)
3499}
3500```
3501"#
3502 )
3503 .build(),
3504 )
3505 .into(),
3506 )
3507 .is_none()
3508 );
3509
3510 assert!(
3512 functions
3513 .insert(
3514 "read_object",
3515 MonomorphicFunction::new(
3516 FunctionSignature::builder()
3517 .parameter(
3518 "file",
3519 PrimitiveType::File,
3520 "Path of the two-row TSV file to read.",
3521 )
3522 .ret(Type::Object)
3523 .definition(
3524 r#"
3525Reads a tab-separated value (TSV) file representing the names and values of the members of an `Object`. There must be exactly two rows, and each row must have the same number of elements, otherwise an error is raised. Trailing end-of-line characters (`` and `\n`) are removed from each line.
3526
3527The first row specifies the object member names. The names in the first row must be unique; if there are any duplicate names, an error is raised.
3528
3529The second row specifies the object member values corresponding to the names in the first row. All of the `Object`'s values are of type `String`.
3530
3531**Parameters**
3532
35331. `File`: Path of the two-row TSV file to read.
3534
3535**Returns**: An `Object`, with as many members as there are unique names in the TSV.
3536
3537Example: read_object_task.wdl
3538
3539```wdl
3540version 1.2
3541
3542task read_object {
3543 command <<<
3544 python <<CODE
3545 print('\t'.join(["key_{}".format(i) for i in range(3)]))
3546 print('\t'.join(["value_{}".format(i) for i in range(3)]))
3547 CODE
3548 >>>
3549
3550 output {
3551 Object my_obj = read_object(stdout())
3552 }
3553
3554 requirements {
3555 container: "python:latest"
3556 }
3557}
3558```
3559"#
3560 )
3561 .build(),
3562 )
3563 .into(),
3564 )
3565 .is_none()
3566 );
3567
3568 assert!(
3570 functions
3571 .insert(
3572 "read_objects",
3573 MonomorphicFunction::new(
3574 FunctionSignature::builder()
3575 .parameter("file", PrimitiveType::File, "The file to read.")
3576 .ret(array_object.clone())
3577 .definition(
3578 r#"
3579Reads a tab-separated value (TSV) file representing the names and values of the members of any number of `Object`s. Trailing end-of-line characters (`` and `\n`) are removed from each line.
3580
3581The first line of the file must be a header row with the names of the object members. The names in the first row must be unique; if there are any duplicate names, an error is raised.
3582
3583There are any number of additional rows, where each additional row contains the values of an object corresponding to the member names. Each row in the file must have the same number of fields as the header row. All of the `Object`'s values are of type `String`.
3584
3585If the file is empty or contains only a header line, an empty array is returned.
3586
3587**Parameters**
3588
35891. `File`: Path of the TSV file to read.
3590
3591**Returns**: An `Array[Object]`, with `N-1` elements, where `N` is the number of rows in the file.
3592
3593Example: read_objects_task.wdl
3594
3595```wdl
3596version 1.2
3597
3598task read_objects {
3599 command <<<
3600 python <<CODE
3601 print('\t'.join(["key_{}".format(i) for i in range(3)]))
3602 print('\t'.join(["value_A{}".format(i) for i in range(3)]))
3603 print('\t'.join(["value_B{}".format(i) for i in range(3)]))
3604 print('\t'.join(["value_C{}".format(i) for i in range(3)]))
3605 CODE
3606 >>>
3607
3608 output {
3609 Array[Object] my_obj = read_objects(stdout())
3610 }
3611"#
3612 )
3613 .build(),
3614 )
3615 .into(),
3616 )
3617 .is_none()
3618 );
3619
3620 const WRITE_OBJECT_DEFINITION: &str = r#"
3621Writes a tab-separated value (TSV) file representing the names and values of the members of an `Object`. The file will contain exactly two rows. The first row specifies the object member names. The second row specifies the object member values corresponding to the names in the first row.
3622
3623Each line is terminated by the newline (`\n`) character.
3624
3625The generated file should be given a random name and written in a temporary directory, so as not to conflict with any other task output files.
3626
3627If the entire contents of the file can not be written for any reason, the calling task or workflow fails with an error. Examples of failure include, but are not limited to, insufficient disk space to write the file.
3628
3629**Parameters**
3630
36311. `Object`: An `Object` whose members will be written to the file.
3632
3633**Returns**: A `File`.
3634
3635Example: write_object_task.wdl
3636
3637```wdl
3638version 1.2
3639
3640task write_object {
3641 input {
3642 Object my_obj = {"key_0": "value_A0", "key_1": "value_A1", "key_2": "value_A2"}
3643 }
3644
3645 command <<<
3646 cat ~{write_object(my_obj)}
3647 >>>
3648
3649 output {
3650 Object new_obj = read_object(stdout())
3651 }
3652}
3653```
3654"#;
3655
3656 assert!(
3658 functions
3659 .insert(
3660 "write_object",
3661 PolymorphicFunction::new(vec![
3662 FunctionSignature::builder()
3663 .parameter("object", Type::Object, "An object to write.")
3664 .ret(PrimitiveType::File)
3665 .definition(WRITE_OBJECT_DEFINITION)
3666 .build(),
3667 FunctionSignature::builder()
3668 .min_version(SupportedVersion::V1(V1::One))
3669 .type_parameter("S", PrimitiveStructConstraint)
3670 .parameter("object", GenericType::Parameter("S"), "An object to write.")
3671 .ret(PrimitiveType::File)
3672 .definition(WRITE_OBJECT_DEFINITION)
3673 .build(),
3674 ])
3675 .into(),
3676 )
3677 .is_none()
3678 );
3679
3680 const WRITE_OBJECTS_DEFINITION: &str = r#"
3681Writes a tab-separated value (TSV) file representing the names and values of the members of any number of `Object`s. The first line of the file will be a header row with the names of the object members. There will be one additional row for each element in the input array, where each additional row contains the values of an object corresponding to the member names.
3682
3683Each line is terminated by the newline (`\n`) character.
3684
3685The generated file should be given a random name and written in a temporary directory, so as not to conflict with any other task output files.
3686
3687If the entire contents of the file can not be written for any reason, the calling task or workflow fails with an error. Examples of failure include, but are not limited to, insufficient disk space to write the file.
3688
3689**Parameters**
3690
36911. `Array[Object]`: An `Array[Object]` whose elements will be written to the file.
3692
3693**Returns**: A `File`.
3694
3695Example: write_objects_task.wdl
3696
3697```wdl
3698version 1.2
3699
3700task write_objects {
3701 input {
3702 Array[Object] my_objs = [
3703 {"key_0": "value_A0", "key_1": "value_A1", "key_2": "value_A2"},
3704 {"key_0": "value_B0", "key_1": "value_B1", "key_2": "value_B2"},
3705 {"key_0": "value_C0", "key_1": "value_C1", "key_2": "value_C2"}
3706 ]
3707 }
3708
3709 command <<<
3710 cat ~{write_objects(my_objs)}
3711 >>>
3712
3713 output {
3714 Array[Object] new_objs = read_objects(stdout())
3715 }
3716}
3717```
3718"#;
3719
3720 assert!(
3722 functions
3723 .insert(
3724 "write_objects",
3725 PolymorphicFunction::new(vec![
3726 FunctionSignature::builder()
3727 .parameter("objects", array_object.clone(), "The objects to write.")
3728 .ret(PrimitiveType::File)
3729 .definition(WRITE_OBJECTS_DEFINITION)
3730 .build(),
3731 FunctionSignature::builder()
3732 .min_version(SupportedVersion::V1(V1::One))
3733 .type_parameter("S", PrimitiveStructConstraint)
3734 .parameter(
3735 "objects",
3736 GenericArrayType::new(GenericType::Parameter("S")),
3737 "The objects to write."
3738 )
3739 .ret(PrimitiveType::File)
3740 .definition(WRITE_OBJECTS_DEFINITION)
3741 .build(),
3742 ])
3743 .into(),
3744 )
3745 .is_none()
3746 );
3747
3748 assert!(
3750 functions
3751 .insert(
3752 "prefix",
3753 MonomorphicFunction::new(
3754 FunctionSignature::builder()
3755 .type_parameter("P", PrimitiveTypeConstraint)
3756 .parameter(
3757 "prefix",
3758 PrimitiveType::String,
3759 "The prefix to prepend to each element in the array.",
3760 )
3761 .parameter(
3762 "array",
3763 GenericArrayType::new(GenericType::Parameter("P")),
3764 "Array with a primitive element type.",
3765 )
3766 .ret(array_string.clone())
3767 .definition(
3768 r#"
3769Given a `String` `prefix` and an `Array[X]` `a`, returns a new `Array[String]` where each element `x` of `a` is prepended with `prefix`. The elements of `a` are converted to `String`s before being prepended. If `a` is empty, an empty array is returned.
3770
3771**Parameters**
3772
37731. `String`: The string to prepend.
37742. `Array[X]`: The array whose elements will be prepended.
3775
3776**Returns**: A new `Array[String]` with the prepended elements.
3777
3778Example: prefix_task.wdl
3779
3780```wdl
3781version 1.2
3782
3783task prefix {
3784 input {
3785 Array[Int] ints = [1, 2, 3]
3786 }
3787
3788 output {
3789 Array[String] prefixed_ints = prefix("file_", ints) # ["file_1", "file_2", "file_3"]
3790 }
3791}
3792```
3793"#
3794 )
3795 .build(),
3796 )
3797 .into(),
3798 )
3799 .is_none()
3800 );
3801
3802 assert!(
3804 functions
3805 .insert(
3806 "suffix",
3807 MonomorphicFunction::new(
3808 FunctionSignature::builder()
3809 .min_version(SupportedVersion::V1(V1::One))
3810 .type_parameter("P", PrimitiveTypeConstraint)
3811 .parameter(
3812 "suffix",
3813 PrimitiveType::String,
3814 "The suffix to append to each element in the array.",
3815 )
3816 .parameter(
3817 "array",
3818 GenericArrayType::new(GenericType::Parameter("P")),
3819 "Array with a primitive element type.",
3820 )
3821 .ret(array_string.clone())
3822 .definition(
3823 r#"
3824Given a `String` `suffix` and an `Array[X]` `a`, returns a new `Array[String]` where each element `x` of `a` is appended with `suffix`. The elements of `a` are converted to `String`s before being appended. If `a` is empty, an empty array is returned.
3825
3826**Parameters**
3827
38281. `String`: The string to append.
38292. `Array[X]`: The array whose elements will be appended.
3830
3831**Returns**: A new `Array[String]` with the appended elements.
3832
3833Example: suffix_task.wdl
3834
3835```wdl
3836version 1.2
3837
3838task suffix {
3839 input {
3840 Array[Int] ints = [1, 2, 3]
3841 }
3842
3843 output {
3844 Array[String] suffixed_ints = suffix(".txt", ints) # ["1.txt", "2.txt", "3.txt"]
3845 }
3846}
3847```
3848"#
3849 )
3850 .build(),
3851 )
3852 .into(),
3853 )
3854 .is_none()
3855 );
3856
3857 assert!(
3859 functions
3860 .insert(
3861 "quote",
3862 MonomorphicFunction::new(
3863 FunctionSignature::builder()
3864 .min_version(SupportedVersion::V1(V1::One))
3865 .type_parameter("P", PrimitiveTypeConstraint)
3866 .parameter(
3867 "array",
3868 GenericArrayType::new(GenericType::Parameter("P")),
3869 "Array with a primitive element type.",
3870 )
3871 .ret(array_string.clone())
3872 .definition(
3873 r#"
3874Given an `Array[X]` `a`, returns a new `Array[String]` where each element `x` of `a` is converted to a `String` and then surrounded by double quotes (`"`). If `a` is empty, an empty array is returned.
3875
3876**Parameters**
3877
38781. `Array[X]`: The array whose elements will be quoted.
3879
3880**Returns**: A new `Array[String]` with the quoted elements.
3881
3882Example: quote_task.wdl
3883
3884```wdl
3885version 1.2
3886
3887task quote {
3888 input {
3889 Array[String] strings = ["hello", "world"]
3890 }
3891
3892 output {
3893 Array[String] quoted_strings = quote(strings) # ["\"hello\"", "\"world\""]
3894 }
3895}
3896```
3897"#
3898 )
3899 .build(),
3900 )
3901 .into(),
3902 )
3903 .is_none()
3904 );
3905
3906 assert!(
3908 functions
3909 .insert(
3910 "squote",
3911 MonomorphicFunction::new(
3912 FunctionSignature::builder()
3913 .min_version(SupportedVersion::V1(V1::One))
3914 .type_parameter("P", PrimitiveTypeConstraint)
3915 .parameter("array", GenericArrayType::new(GenericType::Parameter("P")), "The array of values.") .ret(array_string.clone())
3916 .definition(
3917 r#"
3918Given an `Array[X]` `a`, returns a new `Array[String]` where each element `x` of `a` is converted to a `String` and then surrounded by single quotes (`'`). If `a` is empty, an empty array is returned.
3919
3920**Parameters**
3921
39221. `Array[X]`: The array whose elements will be single-quoted.
3923
3924**Returns**: A new `Array[String]` with the single-quoted elements.
3925
3926Example: squote_task.wdl
3927
3928```wdl
3929version 1.2
3930
3931task squote {
3932 input {
3933 Array[String] strings = ["hello", "world"]
3934 }
3935
3936 output {
3937 Array[String] squoted_strings = squote(strings) # ["'hello'", "'world'"]
3938 }
3939}
3940```
3941"#
3942 )
3943 .build(),
3944 )
3945 .into(),
3946 )
3947 .is_none()
3948 );
3949
3950 assert!(
3952 functions
3953 .insert(
3954 "sep",
3955 MonomorphicFunction::new(
3956 FunctionSignature::builder()
3957 .min_version(SupportedVersion::V1(V1::One))
3958 .type_parameter("P", PrimitiveTypeConstraint)
3959 .parameter("separator", PrimitiveType::String, "Separator string.")
3960 .parameter(
3961 "array",
3962 GenericArrayType::new(GenericType::Parameter("P")),
3963 "`Array` of strings to concatenate.",
3964 )
3965 .ret(PrimitiveType::String)
3966 .definition(
3967 r#"
3968Given a `String` `separator` and an `Array[X]` `a`, returns a new `String` where each element `x` of `a` is converted to a `String` and then joined by `separator`. If `a` is empty, an empty string is returned.
3969
3970**Parameters**
3971
39721. `String`: The string to use as a separator.
39732. `Array[X]`: The array whose elements will be joined.
3974
3975**Returns**: A new `String` with the joined elements.
3976
3977Example: sep_task.wdl
3978
3979```wdl
3980version 1.2
3981
3982task sep {
3983 input {
3984 Array[Int] ints = [1, 2, 3]
3985 }
3986
3987 output {
3988 String joined_ints = sep(",", ints) # "1,2,3"
3989 }
3990}
3991```
3992"#
3993 )
3994 .build(),
3995 )
3996 .into(),
3997 )
3998 .is_none()
3999 );
4000
4001 assert!(
4003 functions
4004 .insert(
4005 "range",
4006 MonomorphicFunction::new(
4007 FunctionSignature::builder()
4008 .parameter("n", PrimitiveType::Integer, "The length of array to create.")
4009 .ret(array_int.clone())
4010 .definition(
4011 r#"
4012Returns an `Array[Int]` of integers from `0` up to (but not including) the given `Int` `n`. If `n` is less than or equal to `0`, an empty array is returned.
4013
4014**Parameters**
4015
40161. `Int`: The upper bound (exclusive) of the range.
4017
4018**Returns**: An `Array[Int]` of integers.
4019
4020Example: range_task.wdl
4021
4022```wdl
4023version 1.2
4024
4025task range {
4026 input {
4027 Int n = 5
4028 }
4029
4030 output {
4031 Array[Int] r = range(n) # [0, 1, 2, 3, 4]
4032 }
4033}
4034```
4035"#
4036 )
4037 .build(),
4038 )
4039 .into(),
4040 )
4041 .is_none()
4042 );
4043
4044 assert!(
4046 functions
4047 .insert(
4048 "transpose",
4049 MonomorphicFunction::new(
4050 FunctionSignature::builder()
4051 .any_type_parameter("X")
4052 .parameter(
4053 "array",
4054 GenericArrayType::new(GenericArrayType::new(
4055 GenericType::Parameter("X"),
4056 )),
4057 "A M*N two-dimensional array.",
4058 )
4059 .ret(GenericArrayType::new(GenericArrayType::new(
4060 GenericType::Parameter("X"),
4061 )))
4062 .definition(
4063 r#"
4064Given an `Array[Array[X]]` `a`, returns a new `Array[Array[X]]` where the rows and columns of `a` are swapped. If `a` is empty, an empty array is returned.
4065
4066If the inner arrays are not all the same length, an error is raised.
4067
4068**Parameters**
4069
40701. `Array[Array[X]]`: The array to transpose.
4071
4072**Returns**: A new `Array[Array[X]]` with the rows and columns swapped.
4073
4074Example: transpose_task.wdl
4075
4076```wdl
4077version 1.2
4078
4079task transpose {
4080 input {
4081 Array[Array[Int]] matrix = [[1, 2, 3], [4, 5, 6]]
4082 }
4083
4084 output {
4085 Array[Array[Int]] transposed_matrix = transpose(matrix) # [[1, 4], [2, 5], [3, 6]]
4086 }
4087}
4088```
4089"#
4090 )
4091 .build(),
4092 )
4093 .into(),
4094 )
4095 .is_none()
4096 );
4097
4098 assert!(
4100 functions
4101 .insert(
4102 "cross",
4103 MonomorphicFunction::new(
4104 FunctionSignature::builder()
4105 .any_type_parameter("X")
4106 .any_type_parameter("Y")
4107 .parameter("a", GenericArrayType::new(GenericType::Parameter("X")), "The first array of length M.")
4108 .parameter("b", GenericArrayType::new(GenericType::Parameter("Y")), "The second array of length N.")
4109 .ret(GenericArrayType::new(GenericPairType::new(
4110 GenericType::Parameter("X"),
4111 GenericType::Parameter("Y"),
4112 )))
4113 .definition(
4114 r#"
4115Given two `Array`s `a` and `b`, returns a new `Array[Pair[X, Y]]` where each element is a `Pair` of an element from `a` and an element from `b`. The order of the elements in the returned array is such that all elements from `b` are paired with the first element of `a`, then all elements from `b` are paired with the second element of `a`, and so on.
4116
4117If either `a` or `b` is empty, an empty array is returned.
4118
4119**Parameters**
4120
41211. `Array[X]`: The first array.
41222. `Array[Y]`: The second array.
4123
4124**Returns**: A new `Array[Pair[X, Y]]` with the cross product of the two arrays.
4125
4126Example: cross_task.wdl
4127
4128```wdl
4129version 1.2
4130
4131task cross {
4132 input {
4133 Array[Int] ints = [1, 2]
4134 Array[String] strings = ["a", "b"]
4135 }
4136
4137 output {
4138 Array[Pair[Int, String]] crossed = cross(ints, strings) # [(1, "a"), (1, "b"), (2, "a"), (2, "b")]
4139 }
4140}
4141```
4142"#
4143 )
4144 .build(),
4145 )
4146 .into(),
4147 )
4148 .is_none()
4149 );
4150
4151 assert!(
4153 functions
4154 .insert(
4155 "zip",
4156 MonomorphicFunction::new(
4157 FunctionSignature::builder()
4158 .any_type_parameter("X")
4159 .any_type_parameter("Y")
4160 .parameter("a", GenericArrayType::new(GenericType::Parameter("X")), "The first array of length M.")
4161 .parameter("b", GenericArrayType::new(GenericType::Parameter("Y")), "The second array of length N.")
4162 .ret(GenericArrayType::new(GenericPairType::new(
4163 GenericType::Parameter("X"),
4164 GenericType::Parameter("Y"),
4165 )))
4166 .definition(
4167 r#"
4168Given two `Array`s `a` and `b`, returns a new `Array[Pair[X, Y]]` where each element is a `Pair` of an element from `a` and an element from `b` at the same index. The length of the returned array is the minimum of the lengths of `a` and `b`.
4169
4170If either `a` or `b` is empty, an empty array is returned.
4171
4172**Parameters**
4173
41741. `Array[X]`: The first array.
41752. `Array[Y]`: The second array.
4176
4177**Returns**: A new `Array[Pair[X, Y]]` with the zipped elements.
4178
4179Example: zip_task.wdl
4180
4181```wdl
4182version 1.2
4183
4184task zip {
4185 input {
4186 Array[Int] ints = [1, 2, 3]
4187 Array[String] strings = ["a", "b"]
4188 }
4189
4190 output {
4191 Array[Pair[Int, String]] zipped = zip(ints, strings) # [(1, "a"), (2, "b")]
4192 }
4193}
4194```
4195"#
4196 )
4197 .build(),
4198 )
4199 .into(),
4200 )
4201 .is_none()
4202 );
4203
4204 assert!(
4206 functions
4207 .insert(
4208 "unzip",
4209 MonomorphicFunction::new(
4210 FunctionSignature::builder()
4211 .min_version(SupportedVersion::V1(V1::One))
4212 .any_type_parameter("X")
4213 .any_type_parameter("Y")
4214 .parameter(
4215 "array",
4216 GenericArrayType::new(GenericPairType::new(
4217 GenericType::Parameter("X"),
4218 GenericType::Parameter("Y"),
4219 )),
4220 "The `Array` of `Pairs` of length N to unzip.",
4221 )
4222 .ret(GenericPairType::new(
4223 GenericArrayType::new(GenericType::Parameter("X")),
4224 GenericArrayType::new(GenericType::Parameter("Y")),
4225 ))
4226 .definition(
4227 r#"
4228Given an `Array[Pair[X, Y]]` `a`, returns a new `Pair[Array[X], Array[Y]]` where the first element of the `Pair` is an `Array` of all the first elements of the `Pair`s in `a`, and the second element of the `Pair` is an `Array` of all the second elements of the `Pair`s in `a`.
4229
4230If `a` is empty, a `Pair` of two empty arrays is returned.
4231
4232**Parameters**
4233
42341. `Array[Pair[X, Y]]`: The array of pairs to unzip.
4235
4236**Returns**: A new `Pair[Array[X], Array[Y]]` with the unzipped elements.
4237
4238Example: unzip_task.wdl
4239
4240```wdl
4241version 1.2
4242
4243task unzip {
4244 input {
4245 Array[Pair[Int, String]] zipped = [(1, "a"), (2, "b")]
4246 }
4247
4248 output {
4249 Pair[Array[Int], Array[String]] unzipped = unzip(zipped) # ([1, 2], ["a", "b"])
4250 }
4251}
4252```
4253"#
4254 )
4255 .build(),
4256 )
4257 .into(),
4258 )
4259 .is_none()
4260 );
4261
4262 assert!(
4264 functions
4265 .insert(
4266 "contains",
4267 MonomorphicFunction::new(
4268 FunctionSignature::builder()
4269 .min_version(SupportedVersion::V1(V1::Two))
4270 .type_parameter("P", PrimitiveTypeConstraint)
4271 .parameter(
4272 "array",
4273 GenericArrayType::new(GenericType::Parameter("P")),
4274 "An array of any primitive type.",
4275 )
4276 .parameter(
4277 "value",
4278 GenericType::Parameter("P"),
4279 "A primitive value of the same type as the array. If the array's \
4280 type is optional, then the value may also be optional.",
4281 )
4282 .ret(PrimitiveType::Boolean)
4283 .definition(
4284 r#"
4285Given an `Array[X]` `a` and a value `v` of type `X`, returns `true` if `v` is present in `a`, otherwise `false`.
4286
4287**Parameters**
4288
42891. `Array[X]`: The array to search.
42902. `X`: The value to search for.
4291
4292**Returns**: `true` if `v` is present in `a`, otherwise `false`.
4293
4294Example: contains_task.wdl
4295
4296```wdl
4297version 1.2
4298
4299task contains {
4300 input {
4301 Array[Int] ints = [1, 2, 3]
4302 }
4303
4304 output {
4305 Boolean contains_2 = contains(ints, 2) # true
4306 Boolean contains_4 = contains(ints, 4) # false
4307 }
4308}
4309```
4310"#
4311 )
4312 .build(),
4313 )
4314 .into(),
4315 )
4316 .is_none()
4317 );
4318
4319 assert!(
4321 functions
4322 .insert(
4323 "chunk",
4324 MonomorphicFunction::new(
4325 FunctionSignature::builder()
4326 .min_version(SupportedVersion::V1(V1::Two))
4327 .any_type_parameter("X")
4328 .parameter(
4329 "array",
4330 GenericArrayType::new(GenericType::Parameter("X")),
4331 "The array to split. May be empty.",
4332 )
4333 .parameter("size", PrimitiveType::Integer, "The desired length of the sub-arrays. Must be > 0.")
4334 .ret(GenericArrayType::new(GenericArrayType::new(
4335 GenericType::Parameter("X"),
4336 )))
4337 .definition(
4338 r#"
4339Given an `Array[X]` `a` and an `Int` `size`, returns a new `Array[Array[X]]` where each inner array has at most `size` elements. The last inner array may have fewer than `size` elements. If `a` is empty, an empty array is returned.
4340
4341If `size` is less than or equal to `0`, an error is raised.
4342
4343**Parameters**
4344
43451. `Array[X]`: The array to chunk.
43462. `Int`: The maximum size of each chunk.
4347
4348**Returns**: A new `Array[Array[X]]` with the chunked elements.
4349
4350Example: chunk_task.wdl
4351
4352```wdl
4353version 1.2
4354
4355task chunk {
4356 input {
4357 Array[Int] ints = [1, 2, 3, 4, 5]
4358 }
4359
4360 output {
4361 Array[Array[Int]] chunked = chunk(ints, 2) # [[1, 2], [3, 4], [5]]
4362 }
4363}
4364```
4365"#
4366 )
4367 .build(),
4368 )
4369 .into(),
4370 )
4371 .is_none()
4372 );
4373
4374 assert!(
4376 functions
4377 .insert(
4378 "flatten",
4379 MonomorphicFunction::new(
4380 FunctionSignature::builder()
4381 .any_type_parameter("X")
4382 .parameter(
4383 "array",
4384 GenericArrayType::new(GenericArrayType::new(
4385 GenericType::Parameter("X"),
4386 )),
4387 "A nested array to flatten.",
4388 )
4389 .ret(GenericArrayType::new(GenericType::Parameter("X")))
4390 .definition(
4391 r#"
4392Given an `Array[Array[X]]` `a`, returns a new `Array[X]` where all the elements of the inner arrays are concatenated into a single array. If `a` is empty, an empty array is returned.
4393
4394**Parameters**
4395
43961. `Array[Array[X]]`: The array to flatten.
4397
4398**Returns**: A new `Array[X]` with the flattened elements.
4399
4400Example: flatten_task.wdl
4401
4402```wdl
4403version 1.2
4404
4405task flatten {
4406 input {
4407 Array[Array[Int]] nested_ints = [[1, 2], [3, 4], [5]]
4408 }
4409
4410 output {
4411 Array[Int] flattened_ints = flatten(nested_ints) # [1, 2, 3, 4, 5]
4412 }
4413}
4414```
4415"#
4416 )
4417 .build(),
4418 )
4419 .into(),
4420 )
4421 .is_none()
4422 );
4423
4424 assert!(
4426 functions
4427 .insert(
4428 "select_first",
4429 MonomorphicFunction::new(
4432 FunctionSignature::builder()
4433 .any_type_parameter("X")
4434 .required(1)
4435 .parameter(
4436 "array",
4437 GenericArrayType::new(GenericType::Parameter("X")),
4438 "Non-empty `Array` of optional values.",
4439 )
4440 .parameter("default", GenericType::UnqualifiedParameter("X"), "(Optional) The default value.")
4441 .ret(GenericType::UnqualifiedParameter("X"))
4442 .definition(
4443 r#"
4444Given an `Array[X?]` `a`, returns the first non-`None` element in `a`. If all elements are `None`, an error is raised.
4445
4446**Parameters**
4447
44481. `Array[X?]`: The array to search.
4449
4450**Returns**: The first non-`None` element.
4451
4452Example: select_first_task.wdl
4453
4454```wdl
4455version 1.2
4456
4457task select_first {
4458 input {
4459 Array[Int?] ints = [None, 1, None, 2]
4460 }
4461
4462 output {
4463 Int first_int = select_first(ints) # 1
4464 }
4465}
4466```
4467"#
4468 )
4469 .build(),
4470 )
4471 .into(),
4472 )
4473 .is_none()
4474 );
4475
4476 assert!(
4478 functions
4479 .insert(
4480 "select_all",
4481 MonomorphicFunction::new(
4482 FunctionSignature::builder()
4483 .any_type_parameter("X")
4484 .parameter(
4485 "array",
4486 GenericArrayType::new(GenericType::Parameter("X")),
4487 "`Array` of optional values.",
4488 )
4489 .ret(GenericArrayType::new(GenericType::UnqualifiedParameter(
4490 "X"
4491 )))
4492 .definition(
4493 r#"
4494Given an `Array[X?]` `a`, returns a new `Array[X]` containing all the non-`None` elements in `a`. If all elements are `None`, an empty array is returned.
4495
4496**Parameters**
4497
44981. `Array[X?]`: The array to filter.
4499
4500**Returns**: A new `Array[X]` with all the non-`None` elements.
4501
4502Example: select_all_task.wdl
4503
4504```wdl
4505version 1.2
4506
4507task select_all {
4508 input {
4509 Array[Int?] ints = [None, 1, None, 2]
4510 }
4511
4512 output {
4513 Array[Int] all_ints = select_all(ints) # [1, 2]
4514 }
4515}
4516```
4517"#
4518 )
4519 .build(),
4520 )
4521 .into(),
4522 )
4523 .is_none()
4524 );
4525
4526 assert!(
4528 functions
4529 .insert(
4530 "as_pairs",
4531 MonomorphicFunction::new(
4532 FunctionSignature::builder()
4533 .min_version(SupportedVersion::V1(V1::One))
4534 .type_parameter("K", MapKeyConstraint)
4535 .any_type_parameter("V")
4536 .parameter(
4537 "map",
4538 GenericMapType::new(
4539 GenericType::Parameter("K"),
4540 GenericType::Parameter("V"),
4541 ),
4542 "`Map` to convert to `Pairs`.",
4543 )
4544 .ret(GenericArrayType::new(GenericPairType::new(
4545 GenericType::Parameter("K"),
4546 GenericType::Parameter("V")
4547 )))
4548 .definition(
4549 r#"
4550Given a `Map[K, V]` `m`, returns a new `Array[Pair[K, V]]` where each element is a `Pair` of a key and its corresponding value from `m`. The order of the elements in the returned array is the same as the order in which the elements were added to the `Map`.
4551
4552If `m` is empty, an empty array is returned.
4553
4554**Parameters**
4555
45561. `Map[K, V]`: The map to convert.
4557
4558**Returns**: A new `Array[Pair[K, V]]` with the key-value pairs.
4559
4560Example: as_pairs_task.wdl
4561
4562```wdl
4563version 1.2
4564
4565task as_pairs {
4566 input {
4567 Map[String, Int] map = {"a": 1, "b": 2}
4568 }
4569
4570 output {
4571 Array[Pair[String, Int]] pairs = as_pairs(map) # [("a", 1), ("b", 2)]
4572 }
4573}
4574```
4575"#
4576 )
4577 .build(),
4578 )
4579 .into(),
4580 )
4581 .is_none()
4582 );
4583
4584 assert!(
4586 functions
4587 .insert(
4588 "as_map",
4589 MonomorphicFunction::new(
4590 FunctionSignature::builder()
4591 .min_version(SupportedVersion::V1(V1::One))
4592 .type_parameter("K", MapKeyConstraint)
4593 .any_type_parameter("V")
4594 .parameter(
4595 "pairs",
4596 GenericArrayType::new(GenericPairType::new(
4597 GenericType::Parameter("K"),
4598 GenericType::Parameter("V"),
4599 )),
4600 "`Array` of `Pairs` to convert to a `Map`.",
4601 )
4602 .ret(GenericMapType::new(
4603 GenericType::Parameter("K"),
4604 GenericType::Parameter("V")
4605 ))
4606 .definition(
4607 r#"
4608Given an `Array[Pair[K, V]]` `a`, returns a new `Map[K, V]` where each `Pair` is converted to a key-value pair in the `Map`. If `a` is empty, an empty map is returned.
4609
4610If there are any duplicate keys in `a`, an error is raised.
4611
4612**Parameters**
4613
46141. `Array[Pair[K, V]]`: The array of pairs to convert.
4615
4616**Returns**: A new `Map[K, V]` with the key-value pairs.
4617
4618Example: as_map_task.wdl
4619
4620```wdl
4621version 1.2
4622
4623task as_map {
4624 input {
4625 Array[Pair[String, Int]] pairs = [("a", 1), ("b", 2)]
4626 }
4627
4628 output {
4629 Map[String, Int] map = as_map(pairs) # {"a": 1, "b": 2}
4630 }
4631}
4632```
4633"#
4634 )
4635 .build(),
4636 )
4637 .into(),
4638 )
4639 .is_none()
4640 );
4641
4642 const KEYS_DEFINITION: &str = r#"
4643Given a `Map[K, V]` `m`, returns a new `Array[K]` containing all the keys in `m`. The order of the keys in the returned array is the same as the order in which the elements were added to the `Map`.
4644
4645If `m` is empty, an empty array is returned.
4646
4647**Parameters**
4648
46491. `Map[K, V]`: The map to get the keys from.
4650
4651**Returns**: A new `Array[K]` with the keys.
4652
4653Example: keys_map_task.wdl
4654
4655```wdl
4656version 1.2
4657
4658task keys_map {
4659 input {
4660 Map[String, Int] map = {"a": 1, "b": 2}
4661 }
4662
4663 output {
4664 Array[String] keys = keys(map) # ["a", "b"]
4665 }
4666}
4667```
4668"#;
4669
4670 assert!(
4672 functions
4673 .insert(
4674 "keys",
4675 PolymorphicFunction::new(vec![
4676 FunctionSignature::builder()
4677 .min_version(SupportedVersion::V1(V1::One))
4678 .type_parameter("K", MapKeyConstraint)
4679 .any_type_parameter("V")
4680 .parameter(
4681 "map",
4682 GenericMapType::new(
4683 GenericType::Parameter("K"),
4684 GenericType::Parameter("V"),
4685 ),
4686 "Collection from which to extract keys.",
4687 )
4688 .ret(GenericArrayType::new(GenericType::Parameter("K")))
4689 .definition(KEYS_DEFINITION)
4690 .build(),
4691 FunctionSignature::builder()
4692 .min_version(SupportedVersion::V1(V1::Two))
4693 .type_parameter("S", StructConstraint)
4694 .parameter(
4695 "struct",
4696 GenericType::Parameter("S"),
4697 "Collection from which to extract keys.",
4698 )
4699 .ret(array_string.clone())
4700 .definition(KEYS_DEFINITION)
4701 .build(),
4702 FunctionSignature::builder()
4703 .min_version(SupportedVersion::V1(V1::Two))
4704 .parameter(
4705 "object",
4706 Type::Object,
4707 "Collection from which to extract keys.",
4708 )
4709 .ret(array_string.clone())
4710 .definition(KEYS_DEFINITION)
4711 .build(),
4712 ])
4713 .into(),
4714 )
4715 .is_none()
4716 );
4717
4718 const CONTAINS_KEY_DEFINITION: &str = r#"
4719Given a `Map[K, V]` `m` and a key `k` of type `K`, returns `true` if `k` is present in `m`, otherwise `false`.
4720
4721**Parameters**
4722
47231. `Map[K, V]`: The map to search.
47242. `K`: The key to search for.
4725
4726**Returns**: `true` if `k` is present in `m`, otherwise `false`.
4727
4728Example: contains_key_map_task.wdl
4729
4730```wdl
4731version 1.2
4732
4733task contains_key_map {
4734 input {
4735 Map[String, Int] map = {"a": 1, "b": 2}
4736 }
4737
4738 output {
4739 Boolean contains_a = contains_key(map, "a") # true
4740 Boolean contains_c = contains_key(map, "c") # false
4741 }
4742}
4743```
4744"#;
4745
4746 assert!(
4748 functions
4749 .insert(
4750 "contains_key",
4751 PolymorphicFunction::new(vec![
4752 FunctionSignature::builder()
4753 .min_version(SupportedVersion::V1(V1::Two))
4754 .type_parameter("K", MapKeyConstraint)
4755 .any_type_parameter("V")
4756 .parameter(
4757 "map",
4758 GenericMapType::new(
4759 GenericType::Parameter("K"),
4760 GenericType::Parameter("V"),
4761 ),
4762 "Collection to search for the key.",
4763 )
4764 .parameter(
4765 "key",
4766 GenericType::Parameter("K"),
4767 "The key to search for. If the first argument is a `Map`, then \
4768 the key must be of the same type as the `Map`'s key type. If the \
4769 `Map`'s key type is optional then the key may also be optional. \
4770 If the first argument is a `Map[String, Y]`, `Struct`, or \
4771 `Object`, then the key may be either a `String` or \
4772 `Array[String]`."
4773 )
4774 .ret(PrimitiveType::Boolean)
4775 .definition(CONTAINS_KEY_DEFINITION)
4776 .build(),
4777 FunctionSignature::builder()
4778 .min_version(SupportedVersion::V1(V1::Two))
4779 .parameter("object", Type::Object, "Collection to search for the key.")
4780 .parameter(
4781 "key",
4782 PrimitiveType::String,
4783 "The key to search for. If the first argument is a `Map`, then \
4784 the key must be of the same type as the `Map`'s key type. If the \
4785 `Map`'s key type is optional then the key may also be optional. \
4786 If the first argument is a `Map[String, Y]`, `Struct`, or \
4787 `Object`, then the key may be either a `String` or \
4788 `Array[String]`."
4789 )
4790 .ret(PrimitiveType::Boolean)
4791 .definition(CONTAINS_KEY_DEFINITION)
4792 .build(),
4793 FunctionSignature::builder()
4794 .min_version(SupportedVersion::V1(V1::Two))
4795 .any_type_parameter("V")
4796 .parameter(
4797 "map",
4798 GenericMapType::new(
4799 PrimitiveType::String,
4800 GenericType::Parameter("V"),
4801 ),
4802 "Collection to search for the key.",
4803 )
4804 .parameter(
4805 "keys",
4806 array_string.clone(),
4807 "The key to search for. If the first argument is a `Map`, then \
4808 the key must be of the same type as the `Map`'s key type. If the \
4809 `Map`'s key type is optional then the key may also be optional. \
4810 If the first argument is a `Map[String, Y]`, `Struct`, or \
4811 `Object`, then the key may be either a `String` or \
4812 `Array[String]`."
4813 )
4814 .ret(PrimitiveType::Boolean)
4815 .definition(CONTAINS_KEY_DEFINITION)
4816 .build(),
4817 FunctionSignature::builder()
4818 .min_version(SupportedVersion::V1(V1::Two))
4819 .type_parameter("S", StructConstraint)
4820 .parameter(
4821 "struct",
4822 GenericType::Parameter("S"),
4823 "Collection to search for the key.",
4824 )
4825 .parameter(
4826 "keys",
4827 array_string.clone(),
4828 "The key to search for. If the first argument is a `Map`, then \
4829 the key must be of the same type as the `Map`'s key type. If the \
4830 `Map`'s key type is optional then the key may also be optional. \
4831 If the first argument is a `Map[String, Y]`, `Struct`, or \
4832 `Object`, then the key may be either a `String` or \
4833 `Array[String]`."
4834 )
4835 .ret(PrimitiveType::Boolean)
4836 .definition(CONTAINS_KEY_DEFINITION)
4837 .build(),
4838 FunctionSignature::builder()
4839 .min_version(SupportedVersion::V1(V1::Two))
4840 .parameter("object", Type::Object, "Collection to search for the key.")
4841 .parameter(
4842 "keys",
4843 array_string.clone(),
4844 "The key to search for. If the first argument is a `Map`, then \
4845 the key must be of the same type as the `Map`'s key type. If the \
4846 `Map`'s key type is optional then the key may also be optional. \
4847 If the first argument is a `Map[String, Y]`, `Struct`, or \
4848 `Object`, then the key may be either a `String` or \
4849 `Array[String]`."
4850 )
4851 .ret(PrimitiveType::Boolean)
4852 .definition(CONTAINS_KEY_DEFINITION)
4853 .build(),
4854 ])
4855 .into(),
4856 )
4857 .is_none()
4858 );
4859
4860 assert!(
4862 functions
4863 .insert(
4864 "values",
4865 MonomorphicFunction::new(
4866 FunctionSignature::builder()
4867 .min_version(SupportedVersion::V1(V1::Two))
4868 .type_parameter("K", MapKeyConstraint)
4869 .any_type_parameter("V")
4870 .parameter(
4871 "map",
4872 GenericMapType::new(
4873 GenericType::Parameter("K"),
4874 GenericType::Parameter("V"),
4875 ),
4876 "`Map` from which to extract values.",
4877 )
4878 .ret(GenericArrayType::new(GenericType::Parameter("V")))
4879 .definition(
4880 r#"
4881Given a `Map[K, V]` `m`, returns a new `Array[V]` containing all the values in `m`. The order of the values in the returned array is the same as the order in which the elements were added to the `Map`.
4882
4883If `m` is empty, an empty array is returned.
4884
4885**Parameters**
4886
48871. `Map[K, V]`: The map to get the values from.
4888
4889**Returns**: A new `Array[V]` with the values.
4890
4891Example: values_map_task.wdl
4892
4893```wdl
4894version 1.2
4895
4896task values_map {
4897 input {
4898 Map[String, Int] map = {"a": 1, "b": 2}
4899 }
4900
4901 output {
4902 Array[Int] values = values(map) # [1, 2]
4903 }
4904}
4905```
4906"#
4907 )
4908 .build(),
4909 )
4910 .into(),
4911 )
4912 .is_none()
4913 );
4914
4915 assert!(
4917 functions
4918 .insert(
4919 "collect_by_key",
4920 MonomorphicFunction::new(
4921 FunctionSignature::builder()
4922 .min_version(SupportedVersion::V1(V1::One))
4923 .type_parameter("K", MapKeyConstraint)
4924 .any_type_parameter("V")
4925 .parameter(
4926 "pairs",
4927 GenericArrayType::new(GenericPairType::new(
4928 GenericType::Parameter("K"),
4929 GenericType::Parameter("V"),
4930 )),
4931 "`Array` of `Pairs` to group.",
4932 )
4933 .ret(GenericMapType::new(
4934 GenericType::Parameter("K"),
4935 GenericArrayType::new(GenericType::Parameter("V"))
4936 ))
4937 .definition(
4938 r#"
4939Given an `Array[Pair[K, V]]` `a`, returns a new `Map[K, Array[V]]` where each key `K` maps to an `Array` of all the values `V` that were paired with `K` in `a`. The order of the values in the inner arrays is the same as the order in which they appeared in `a`.
4940
4941If `a` is empty, an empty map is returned.
4942
4943**Parameters**
4944
49451. `Array[Pair[K, V]]`: The array of pairs to collect.
4946
4947**Returns**: A new `Map[K, Array[V]]` with the collected values.
4948
4949Example: collect_by_key_task.wdl
4950
4951```wdl
4952version 1.2
4953
4954task collect_by_key {
4955 input {
4956 Array[Pair[String, Int]] pairs = [("a", 1), ("b", 2), ("a", 3)]
4957 }
4958
4959 output {
4960 Map[String, Array[Int]] collected = collect_by_key(pairs) # {"a": [1, 3], "b": 2}
4961 }
4962}
4963```
4964"#
4965 )
4966 .build(),
4967 )
4968 .into(),
4969 )
4970 .is_none()
4971 );
4972
4973 assert!(
4975 functions
4976 .insert(
4977 "value",
4978 MonomorphicFunction::new(
4979 FunctionSignature::builder()
4980 .min_version(SupportedVersion::V1(V1::Three))
4981 .any_type_parameter("T")
4982 .type_parameter("V", EnumVariantConstraint)
4983 .parameter(
4984 "variant",
4985 GenericType::Parameter("V"),
4986 "An enum variant of any enum type.",
4987 )
4988 .ret(GenericEnumInnerValueType::new("T"))
4989 .definition(
4990 r##"
4991Returns the underlying value associated with an enum variant.
4992
4993**Parameters**
4994
49951. `Enum`: an enum variant of any enum type.
4996
4997**Returns**: The variant's associated value.
4998
4999Example: test_enum_value.wdl
5000
5001```wdl
5002version 1.3
5003
5004enum Color {
5005 Red = "#FF0000",
5006 Green = "#00FF00",
5007 Blue = "#0000FF"
5008}
5009
5010enum Priority {
5011 Low = 1,
5012 Medium = 5,
5013 High = 10
5014}
5015
5016workflow test_enum_value {
5017 input {
5018 Color color = Color.Red
5019 Priority priority = Priority.High
5020 }
5021
5022 output {
5023 String variant_name = "~{color}" # "Red"
5024 String hex_value = value(color) # "#FF0000"
5025 Int priority_num = value(priority) # 10
5026 Boolean values_equal = value(Color.Red) == value(Color.Red) # true
5027 Boolean variants_equal = Color.Red == Color.Red # true
5028 }
5029}
5030```
5031"##
5032 )
5033 .build(),
5034 )
5035 .into(),
5036 )
5037 .is_none()
5038 );
5039
5040 assert!(
5042 functions
5043 .insert(
5044 "defined",
5045 MonomorphicFunction::new(
5046 FunctionSignature::builder()
5047 .any_type_parameter("X")
5048 .parameter(
5049 "value",
5050 GenericType::Parameter("X"),
5051 "Optional value of any type."
5052 )
5053 .ret(PrimitiveType::Boolean)
5054 .definition(
5055 r#"
5056Given an optional value `x`, returns `true` if `x` is defined (i.e., not `None`), otherwise `false`.
5057
5058**Parameters**
5059
50601. `X?`: The optional value to check.
5061
5062**Returns**: `true` if `x` is defined, otherwise `false`.
5063
5064Example: defined_task.wdl
5065
5066```wdl
5067version 1.2
5068
5069task defined {
5070 input {
5071 Int? x = 1
5072 Int? y = None
5073 }
5074
5075 output {
5076 Boolean x_defined = defined(x) # true
5077 Boolean y_defined = defined(y) # false
5078 }
5079}
5080```
5081"#
5082 )
5083 .build(),
5084 )
5085 .into(),
5086 )
5087 .is_none()
5088 );
5089
5090 const LENGTH_DEFINITION: &str = r#"
5091Given an `Array[X]` `a`, returns the number of elements in `a`. If `a` is empty, `0` is returned.
5092
5093**Parameters**
5094
50951. `Array[X]`: The array to get the length from.
5096
5097**Returns**: The number of elements in the array as an `Int`.
5098
5099Example: length_array_task.wdl
5100
5101```wdl
5102version 1.2
5103
5104task length_array {
5105 input {
5106 Array[Int] ints = [1, 2, 3]
5107 }
5108
5109 output {
5110 Int len = length(ints) # 3
5111 }
5112}
5113```
5114"#;
5115
5116 assert!(
5118 functions
5119 .insert(
5120 "length",
5121 PolymorphicFunction::new(vec![
5122 FunctionSignature::builder()
5123 .any_type_parameter("X")
5124 .parameter(
5125 "array",
5126 GenericArrayType::new(GenericType::Parameter("X")),
5127 "A collection or string whose elements are to be counted.",
5128 )
5129 .ret(PrimitiveType::Integer)
5130 .definition(LENGTH_DEFINITION)
5131 .build(),
5132 FunctionSignature::builder()
5133 .any_type_parameter("K")
5134 .any_type_parameter("V")
5135 .parameter(
5136 "map",
5137 GenericMapType::new(
5138 GenericType::Parameter("K"),
5139 GenericType::Parameter("V"),
5140 ),
5141 "A collection or string whose elements are to be counted.",
5142 )
5143 .ret(PrimitiveType::Integer)
5144 .definition(LENGTH_DEFINITION)
5145 .build(),
5146 FunctionSignature::builder()
5147 .parameter(
5148 "object",
5149 Type::Object,
5150 "A collection or string whose elements are to be counted.",
5151 )
5152 .ret(PrimitiveType::Integer)
5153 .definition(LENGTH_DEFINITION)
5154 .build(),
5155 FunctionSignature::builder()
5156 .parameter(
5157 "string",
5158 PrimitiveType::String,
5159 "A collection or string whose elements are to be counted.",
5160 )
5161 .ret(PrimitiveType::Integer)
5162 .definition(LENGTH_DEFINITION)
5163 .build(),
5164 ])
5165 .into(),
5166 )
5167 .is_none()
5168 );
5169
5170 StandardLibrary {
5171 functions,
5172 array_int,
5173 array_string,
5174 array_file,
5175 array_object,
5176 array_string_non_empty,
5177 array_array_string,
5178 map_string_string,
5179 map_string_int,
5180 }
5181});
5182
5183#[cfg(test)]
5184mod test {
5185 use pretty_assertions::assert_eq;
5186
5187 use super::*;
5188
5189 #[test]
5190 fn verify_stdlib_signatures() {
5191 let mut signatures = Vec::new();
5192 for (name, f) in STDLIB.functions() {
5193 match f {
5194 Function::Monomorphic(f) => {
5195 let params = TypeParameters::new(&f.signature.type_parameters);
5196 signatures.push(format!("{name}{sig}", sig = f.signature.display(¶ms)));
5197 }
5198 Function::Polymorphic(f) => {
5199 for signature in &f.signatures {
5200 let params = TypeParameters::new(&signature.type_parameters);
5201 signatures.push(format!("{name}{sig}", sig = signature.display(¶ms)));
5202 }
5203 }
5204 }
5205 }
5206
5207 assert_eq!(
5208 signatures,
5209 [
5210 "floor(value: Float) -> Int",
5211 "ceil(value: Float) -> Int",
5212 "round(value: Float) -> Int",
5213 "min(a: Int, b: Int) -> Int",
5214 "min(a: Int, b: Float) -> Float",
5215 "min(a: Float, b: Int) -> Float",
5216 "min(a: Float, b: Float) -> Float",
5217 "max(a: Int, b: Int) -> Int",
5218 "max(a: Int, b: Float) -> Float",
5219 "max(a: Float, b: Int) -> Float",
5220 "max(a: Float, b: Float) -> Float",
5221 "find(input: String, pattern: String) -> String?",
5222 "matches(input: String, pattern: String) -> Boolean",
5223 "sub(input: String, pattern: String, replace: String) -> String",
5224 "split(input: String, delimiter: String) -> Array[String]",
5225 "basename(path: File, <suffix: String>) -> String",
5226 "basename(path: String, <suffix: String>) -> String",
5227 "basename(path: Directory, <suffix: String>) -> String",
5228 "join_paths(base: Directory, relative: String) -> String",
5229 "join_paths(base: Directory, relative: Array[String]+) -> String",
5230 "join_paths(paths: Array[String]+) -> String",
5231 "glob(pattern: String) -> Array[File]",
5232 "size(value: None, <unit: String>) -> Float",
5233 "size(value: File?, <unit: String>) -> Float",
5234 "size(value: String?, <unit: String>) -> Float",
5235 "size(value: Directory?, <unit: String>) -> Float",
5236 "size(value: X, <unit: String>) -> Float where `X`: any compound type that \
5237 recursively contains a `File` or `Directory`",
5238 "stdout() -> File",
5239 "stderr() -> File",
5240 "read_string(file: File) -> String",
5241 "read_int(file: File) -> Int",
5242 "read_float(file: File) -> Float",
5243 "read_boolean(file: File) -> Boolean",
5244 "read_lines(file: File) -> Array[String]",
5245 "write_lines(array: Array[String]) -> File",
5246 "read_tsv(file: File) -> Array[Array[String]]",
5247 "read_tsv(file: File, header: Boolean) -> Array[Object]",
5248 "read_tsv(file: File, header: Boolean, columns: Array[String]) -> Array[Object]",
5249 "write_tsv(data: Array[Array[String]]) -> File",
5250 "write_tsv(data: Array[Array[String]], header: Boolean, columns: Array[String]) \
5251 -> File",
5252 "write_tsv(data: Array[S], <header: Boolean>, <columns: Array[String]>) -> File \
5253 where `S`: any structure containing only primitive types",
5254 "read_map(file: File) -> Map[String, String]",
5255 "write_map(map: Map[String, String]) -> File",
5256 "read_json(file: File) -> Union",
5257 "write_json(value: X) -> File where `X`: any JSON-serializable type",
5258 "read_object(file: File) -> Object",
5259 "read_objects(file: File) -> Array[Object]",
5260 "write_object(object: Object) -> File",
5261 "write_object(object: S) -> File where `S`: any structure containing only \
5262 primitive types",
5263 "write_objects(objects: Array[Object]) -> File",
5264 "write_objects(objects: Array[S]) -> File where `S`: any structure containing \
5265 only primitive types",
5266 "prefix(prefix: String, array: Array[P]) -> Array[String] where `P`: any \
5267 primitive type",
5268 "suffix(suffix: String, array: Array[P]) -> Array[String] where `P`: any \
5269 primitive type",
5270 "quote(array: Array[P]) -> Array[String] where `P`: any primitive type",
5271 "squote(array: Array[P]) -> Array[String] where `P`: any primitive type",
5272 "sep(separator: String, array: Array[P]) -> String where `P`: any primitive type",
5273 "range(n: Int) -> Array[Int]",
5274 "transpose(array: Array[Array[X]]) -> Array[Array[X]]",
5275 "cross(a: Array[X], b: Array[Y]) -> Array[Pair[X, Y]]",
5276 "zip(a: Array[X], b: Array[Y]) -> Array[Pair[X, Y]]",
5277 "unzip(array: Array[Pair[X, Y]]) -> Pair[Array[X], Array[Y]]",
5278 "contains(array: Array[P], value: P) -> Boolean where `P`: any primitive type",
5279 "chunk(array: Array[X], size: Int) -> Array[Array[X]]",
5280 "flatten(array: Array[Array[X]]) -> Array[X]",
5281 "select_first(array: Array[X], <default: X>) -> X",
5282 "select_all(array: Array[X]) -> Array[X]",
5283 "as_pairs(map: Map[K, V]) -> Array[Pair[K, V]] where `K`: any non-optional \
5284 primitive type",
5285 "as_map(pairs: Array[Pair[K, V]]) -> Map[K, V] where `K`: any non-optional \
5286 primitive type",
5287 "keys(map: Map[K, V]) -> Array[K] where `K`: any non-optional primitive type",
5288 "keys(struct: S) -> Array[String] where `S`: any structure",
5289 "keys(object: Object) -> Array[String]",
5290 "contains_key(map: Map[K, V], key: K) -> Boolean where `K`: any non-optional \
5291 primitive type",
5292 "contains_key(object: Object, key: String) -> Boolean",
5293 "contains_key(map: Map[String, V], keys: Array[String]) -> Boolean",
5294 "contains_key(struct: S, keys: Array[String]) -> Boolean where `S`: any structure",
5295 "contains_key(object: Object, keys: Array[String]) -> Boolean",
5296 "values(map: Map[K, V]) -> Array[V] where `K`: any non-optional primitive type",
5297 "collect_by_key(pairs: Array[Pair[K, V]]) -> Map[K, Array[V]] where `K`: any \
5298 non-optional primitive type",
5299 "value(variant: V) -> T where `V`: any enum variant",
5300 "defined(value: X) -> Boolean",
5301 "length(array: Array[X]) -> Int",
5302 "length(map: Map[K, V]) -> Int",
5303 "length(object: Object) -> Int",
5304 "length(string: String) -> Int",
5305 ]
5306 );
5307 }
5308
5309 #[test]
5310 fn it_binds_a_simple_function() {
5311 let f = STDLIB.function("floor").expect("should have function");
5312 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5313
5314 let e = f
5315 .bind(SupportedVersion::V1(V1::Zero), &[])
5316 .expect_err("bind should fail");
5317 assert_eq!(e, FunctionBindError::TooFewArguments(1));
5318
5319 let e = f
5320 .bind(
5321 SupportedVersion::V1(V1::One),
5322 &[PrimitiveType::String.into(), PrimitiveType::Boolean.into()],
5323 )
5324 .expect_err("bind should fail");
5325 assert_eq!(e, FunctionBindError::TooManyArguments(1));
5326
5327 let e = f
5329 .bind(
5330 SupportedVersion::V1(V1::Two),
5331 &[PrimitiveType::String.into()],
5332 )
5333 .expect_err("bind should fail");
5334 assert_eq!(
5335 e,
5336 FunctionBindError::ArgumentTypeMismatch {
5337 index: 0,
5338 expected: "type `Float`".into()
5339 }
5340 );
5341
5342 let binding = f
5344 .bind(SupportedVersion::V1(V1::Zero), &[Type::Union])
5345 .expect("bind should succeed");
5346 assert_eq!(binding.index(), 0);
5347 assert_eq!(binding.return_type().to_string(), "Int");
5348
5349 let binding = f
5351 .bind(
5352 SupportedVersion::V1(V1::One),
5353 &[PrimitiveType::Float.into()],
5354 )
5355 .expect("bind should succeed");
5356 assert_eq!(binding.index(), 0);
5357 assert_eq!(binding.return_type().to_string(), "Int");
5358
5359 let binding = f
5361 .bind(
5362 SupportedVersion::V1(V1::Two),
5363 &[PrimitiveType::Integer.into()],
5364 )
5365 .expect("bind should succeed");
5366 assert_eq!(binding.index(), 0);
5367 assert_eq!(binding.return_type().to_string(), "Int");
5368 }
5369
5370 #[test]
5371 fn it_binds_a_generic_function() {
5372 let f = STDLIB.function("values").expect("should have function");
5373 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Two));
5374
5375 let e = f
5376 .bind(SupportedVersion::V1(V1::Zero), &[])
5377 .expect_err("bind should fail");
5378 assert_eq!(
5379 e,
5380 FunctionBindError::RequiresVersion(SupportedVersion::V1(V1::Two))
5381 );
5382
5383 let e = f
5384 .bind(SupportedVersion::V1(V1::Two), &[])
5385 .expect_err("bind should fail");
5386 assert_eq!(e, FunctionBindError::TooFewArguments(1));
5387
5388 let e = f
5389 .bind(
5390 SupportedVersion::V1(V1::Two),
5391 &[PrimitiveType::String.into(), PrimitiveType::Boolean.into()],
5392 )
5393 .expect_err("bind should fail");
5394 assert_eq!(e, FunctionBindError::TooManyArguments(1));
5395
5396 let e = f
5398 .bind(
5399 SupportedVersion::V1(V1::Two),
5400 &[PrimitiveType::String.into()],
5401 )
5402 .expect_err("bind should fail");
5403 assert_eq!(
5404 e,
5405 FunctionBindError::ArgumentTypeMismatch {
5406 index: 0,
5407 expected: "generic type `Map[K, V]` where `K`: any non-optional primitive type"
5408 .into()
5409 }
5410 );
5411
5412 let binding = f
5414 .bind(SupportedVersion::V1(V1::Two), &[Type::Union])
5415 .expect("bind should succeed");
5416 assert_eq!(binding.index(), 0);
5417 assert_eq!(binding.return_type().to_string(), "Array[Union]");
5418
5419 let ty: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
5421 let binding = f
5422 .bind(SupportedVersion::V1(V1::Two), &[ty])
5423 .expect("bind should succeed");
5424 assert_eq!(binding.index(), 0);
5425 assert_eq!(binding.return_type().to_string(), "Array[String]");
5426
5427 let ty: Type = MapType::new(PrimitiveType::String, Type::Object).into();
5429 let binding = f
5430 .bind(SupportedVersion::V1(V1::Two), &[ty])
5431 .expect("bind should succeed");
5432 assert_eq!(binding.index(), 0);
5433 assert_eq!(binding.return_type().to_string(), "Array[Object]");
5434 }
5435
5436 #[test]
5437 fn it_removes_qualifiers() {
5438 let f = STDLIB.function("select_all").expect("should have function");
5439 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5440
5441 let array_string: Type = ArrayType::new(PrimitiveType::String).into();
5443 let binding = f
5444 .bind(SupportedVersion::V1(V1::One), &[array_string])
5445 .expect("bind should succeed");
5446 assert_eq!(binding.index(), 0);
5447 assert_eq!(binding.return_type().to_string(), "Array[String]");
5448
5449 let array_optional_string: Type =
5451 ArrayType::new(Type::from(PrimitiveType::String).optional()).into();
5452 let binding = f
5453 .bind(SupportedVersion::V1(V1::One), &[array_optional_string])
5454 .expect("bind should succeed");
5455 assert_eq!(binding.index(), 0);
5456 assert_eq!(binding.return_type().to_string(), "Array[String]");
5457
5458 let binding = f
5460 .bind(SupportedVersion::V1(V1::Two), &[Type::Union])
5461 .expect("bind should succeed");
5462 assert_eq!(binding.index(), 0);
5463 assert_eq!(binding.return_type().to_string(), "Array[Union]");
5464
5465 let array_string = Type::from(ArrayType::new(PrimitiveType::String)).optional();
5467 let array_array_string = ArrayType::new(array_string).into();
5468 let binding = f
5469 .bind(SupportedVersion::V1(V1::Zero), &[array_array_string])
5470 .expect("bind should succeed");
5471 assert_eq!(binding.index(), 0);
5472 assert_eq!(binding.return_type().to_string(), "Array[Array[String]]");
5473 }
5474
5475 #[test]
5476 fn it_binds_concrete_overloads() {
5477 let f = STDLIB.function("max").expect("should have function");
5478 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::One));
5479
5480 let e = f
5481 .bind(SupportedVersion::V1(V1::One), &[])
5482 .expect_err("bind should fail");
5483 assert_eq!(e, FunctionBindError::TooFewArguments(2));
5484
5485 let e = f
5486 .bind(
5487 SupportedVersion::V1(V1::Two),
5488 &[
5489 PrimitiveType::String.into(),
5490 PrimitiveType::Boolean.into(),
5491 PrimitiveType::File.into(),
5492 ],
5493 )
5494 .expect_err("bind should fail");
5495 assert_eq!(e, FunctionBindError::TooManyArguments(2));
5496
5497 let binding = f
5499 .bind(
5500 SupportedVersion::V1(V1::One),
5501 &[PrimitiveType::Integer.into(), PrimitiveType::Integer.into()],
5502 )
5503 .expect("binding should succeed");
5504 assert_eq!(binding.index(), 0);
5505 assert_eq!(binding.return_type().to_string(), "Int");
5506
5507 let binding = f
5509 .bind(
5510 SupportedVersion::V1(V1::Two),
5511 &[PrimitiveType::Integer.into(), PrimitiveType::Float.into()],
5512 )
5513 .expect("binding should succeed");
5514 assert_eq!(binding.index(), 1);
5515 assert_eq!(binding.return_type().to_string(), "Float");
5516
5517 let binding = f
5519 .bind(
5520 SupportedVersion::V1(V1::One),
5521 &[PrimitiveType::Float.into(), PrimitiveType::Integer.into()],
5522 )
5523 .expect("binding should succeed");
5524 assert_eq!(binding.index(), 2);
5525 assert_eq!(binding.return_type().to_string(), "Float");
5526
5527 let binding = f
5529 .bind(
5530 SupportedVersion::V1(V1::Two),
5531 &[PrimitiveType::Float.into(), PrimitiveType::Float.into()],
5532 )
5533 .expect("binding should succeed");
5534 assert_eq!(binding.index(), 3);
5535 assert_eq!(binding.return_type().to_string(), "Float");
5536
5537 let e = f
5539 .bind(
5540 SupportedVersion::V1(V1::One),
5541 &[PrimitiveType::String.into(), PrimitiveType::Integer.into()],
5542 )
5543 .expect_err("binding should fail");
5544 assert_eq!(
5545 e,
5546 FunctionBindError::ArgumentTypeMismatch {
5547 index: 0,
5548 expected: "type `Int` or type `Float`".into()
5549 }
5550 );
5551
5552 let e = f
5554 .bind(
5555 SupportedVersion::V1(V1::Two),
5556 &[PrimitiveType::Integer.into(), PrimitiveType::String.into()],
5557 )
5558 .expect_err("binding should fail");
5559 assert_eq!(
5560 e,
5561 FunctionBindError::ArgumentTypeMismatch {
5562 index: 1,
5563 expected: "type `Int` or type `Float`".into()
5564 }
5565 );
5566
5567 let e = f
5569 .bind(
5570 SupportedVersion::V1(V1::One),
5571 &[PrimitiveType::String.into(), PrimitiveType::Float.into()],
5572 )
5573 .expect_err("binding should fail");
5574 assert_eq!(
5575 e,
5576 FunctionBindError::ArgumentTypeMismatch {
5577 index: 0,
5578 expected: "type `Int` or type `Float`".into()
5579 }
5580 );
5581
5582 let e = f
5584 .bind(
5585 SupportedVersion::V1(V1::Two),
5586 &[PrimitiveType::Float.into(), PrimitiveType::String.into()],
5587 )
5588 .expect_err("binding should fail");
5589 assert_eq!(
5590 e,
5591 FunctionBindError::ArgumentTypeMismatch {
5592 index: 1,
5593 expected: "type `Int` or type `Float`".into()
5594 }
5595 );
5596 }
5597
5598 #[test]
5599 fn it_binds_generic_overloads() {
5600 let f = STDLIB
5601 .function("select_first")
5602 .expect("should have function");
5603 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5604
5605 let e = f
5606 .bind(SupportedVersion::V1(V1::Zero), &[])
5607 .expect_err("bind should fail");
5608 assert_eq!(e, FunctionBindError::TooFewArguments(1));
5609
5610 let e = f
5611 .bind(
5612 SupportedVersion::V1(V1::One),
5613 &[
5614 PrimitiveType::String.into(),
5615 PrimitiveType::Boolean.into(),
5616 PrimitiveType::File.into(),
5617 ],
5618 )
5619 .expect_err("bind should fail");
5620 assert_eq!(e, FunctionBindError::TooManyArguments(2));
5621
5622 let e = f
5624 .bind(
5625 SupportedVersion::V1(V1::Two),
5626 &[PrimitiveType::Integer.into()],
5627 )
5628 .expect_err("binding should fail");
5629 assert_eq!(
5630 e,
5631 FunctionBindError::ArgumentTypeMismatch {
5632 index: 0,
5633 expected: "generic type `Array[X]`".into()
5634 }
5635 );
5636
5637 let array: Type = ArrayType::non_empty(Type::from(PrimitiveType::String).optional()).into();
5639 let binding = f
5640 .bind(SupportedVersion::V1(V1::Zero), std::slice::from_ref(&array))
5641 .expect("binding should succeed");
5642 assert_eq!(binding.index(), 0);
5643 assert_eq!(binding.return_type().to_string(), "String");
5644
5645 let binding = f
5647 .bind(
5648 SupportedVersion::V1(V1::One),
5649 &[array.clone(), PrimitiveType::String.into()],
5650 )
5651 .expect("binding should succeed");
5652 assert_eq!(binding.index(), 0);
5653 assert_eq!(binding.return_type().to_string(), "String");
5654
5655 let e = f
5657 .bind(
5658 SupportedVersion::V1(V1::Two),
5659 &[array.clone(), PrimitiveType::Integer.into()],
5660 )
5661 .expect_err("binding should fail");
5662 assert_eq!(
5663 e,
5664 FunctionBindError::ArgumentTypeMismatch {
5665 index: 1,
5666 expected: "type `String`".into()
5667 }
5668 );
5669
5670 let array: Type = ArrayType::new(Type::from(PrimitiveType::String).optional()).into();
5672 let binding = f
5673 .bind(SupportedVersion::V1(V1::Zero), std::slice::from_ref(&array))
5674 .expect("binding should succeed");
5675 assert_eq!(binding.index(), 0);
5676 assert_eq!(binding.return_type().to_string(), "String");
5677
5678 let binding = f
5680 .bind(
5681 SupportedVersion::V1(V1::One),
5682 &[array.clone(), PrimitiveType::String.into()],
5683 )
5684 .expect("binding should succeed");
5685 assert_eq!(binding.index(), 0);
5686 assert_eq!(binding.return_type().to_string(), "String");
5687
5688 let e = f
5690 .bind(
5691 SupportedVersion::V1(V1::Two),
5692 &[array, PrimitiveType::Integer.into()],
5693 )
5694 .expect_err("binding should fail");
5695 assert_eq!(
5696 e,
5697 FunctionBindError::ArgumentTypeMismatch {
5698 index: 1,
5699 expected: "type `String`".into()
5700 }
5701 );
5702 }
5703}