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}
128
129impl GenericType {
130 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
132 #[allow(clippy::missing_docs_in_private_items)]
133 struct Display<'a> {
134 params: &'a TypeParameters<'a>,
135 ty: &'a GenericType,
136 }
137
138 impl fmt::Display for Display<'_> {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 match self.ty {
141 GenericType::Parameter(name) | GenericType::UnqualifiedParameter(name) => {
142 let (_, ty) = self.params.get(name).expect("the name should be present");
143 match ty {
144 Some(ty) => {
145 if let GenericType::UnqualifiedParameter(_) = self.ty {
146 ty.require().fmt(f)
147 } else {
148 ty.fmt(f)
149 }
150 }
151 None => {
152 write!(f, "{name}")
153 }
154 }
155 }
156 GenericType::Array(ty) => ty.display(self.params).fmt(f),
157 GenericType::Pair(ty) => ty.display(self.params).fmt(f),
158 GenericType::Map(ty) => ty.display(self.params).fmt(f),
159 }
160 }
161 }
162
163 Display { params, ty: self }
164 }
165
166 fn infer_type_parameters(
168 &self,
169 ty: &Type,
170 params: &mut TypeParameters<'_>,
171 ignore_constraints: bool,
172 ) {
173 match self {
174 Self::Parameter(name) | Self::UnqualifiedParameter(name) => {
175 let (param, _) = params.get(name).expect("should have parameter");
177
178 if !ignore_constraints
179 && let Some(constraint) = param.constraint()
180 && !constraint.satisfied(ty)
181 {
182 return;
183 }
184
185 params.set_inferred_type(name, ty.clone());
186 }
187 Self::Array(array) => array.infer_type_parameters(ty, params, ignore_constraints),
188 Self::Pair(pair) => pair.infer_type_parameters(ty, params, ignore_constraints),
189 Self::Map(map) => map.infer_type_parameters(ty, params, ignore_constraints),
190 }
191 }
192
193 fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
195 match self {
196 Self::Parameter(name) => {
197 params
198 .get(name)
199 .expect("type parameter should be present")
200 .1
201 }
202 Self::UnqualifiedParameter(name) => params
203 .get(name)
204 .expect("type parameter should be present")
205 .1
206 .map(|ty| ty.require()),
207 Self::Array(ty) => ty.realize(params),
208 Self::Pair(ty) => ty.realize(params),
209 Self::Map(ty) => ty.realize(params),
210 }
211 }
212
213 fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
219 match self {
220 Self::Parameter(n) | Self::UnqualifiedParameter(n) => assert!(
221 parameters.iter().any(|p| p.name == *n),
222 "generic type references unknown type parameter `{n}`"
223 ),
224 Self::Array(a) => a.assert_type_parameters(parameters),
225 Self::Pair(p) => p.assert_type_parameters(parameters),
226 Self::Map(m) => m.assert_type_parameters(parameters),
227 }
228 }
229}
230
231impl From<GenericArrayType> for GenericType {
232 fn from(value: GenericArrayType) -> Self {
233 Self::Array(value)
234 }
235}
236
237impl From<GenericPairType> for GenericType {
238 fn from(value: GenericPairType) -> Self {
239 Self::Pair(value)
240 }
241}
242
243impl From<GenericMapType> for GenericType {
244 fn from(value: GenericMapType) -> Self {
245 Self::Map(value)
246 }
247}
248
249#[derive(Debug, Clone)]
251pub struct GenericArrayType {
252 element_type: Box<FunctionalType>,
254 non_empty: bool,
256}
257
258impl GenericArrayType {
259 pub fn new(element_type: impl Into<FunctionalType>) -> Self {
261 Self {
262 element_type: Box::new(element_type.into()),
263 non_empty: false,
264 }
265 }
266
267 pub fn non_empty(element_type: impl Into<FunctionalType>) -> Self {
269 Self {
270 element_type: Box::new(element_type.into()),
271 non_empty: true,
272 }
273 }
274
275 pub fn element_type(&self) -> &FunctionalType {
277 &self.element_type
278 }
279
280 pub fn is_non_empty(&self) -> bool {
282 self.non_empty
283 }
284
285 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
287 #[allow(clippy::missing_docs_in_private_items)]
288 struct Display<'a> {
289 params: &'a TypeParameters<'a>,
290 ty: &'a GenericArrayType,
291 }
292
293 impl fmt::Display for Display<'_> {
294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 write!(f, "Array[")?;
296 self.ty.element_type.display(self.params).fmt(f)?;
297 write!(f, "]")?;
298
299 if self.ty.is_non_empty() {
300 write!(f, "+")?;
301 }
302
303 Ok(())
304 }
305 }
306
307 Display { params, ty: self }
308 }
309
310 fn infer_type_parameters(
312 &self,
313 ty: &Type,
314 params: &mut TypeParameters<'_>,
315 ignore_constraints: bool,
316 ) {
317 match ty {
318 Type::Union => {
319 self.element_type
320 .infer_type_parameters(&Type::Union, params, ignore_constraints);
321 }
322 Type::Compound(CompoundType::Array(ty), false) => {
323 self.element_type.infer_type_parameters(
324 ty.element_type(),
325 params,
326 ignore_constraints,
327 );
328 }
329 _ => {}
330 }
331 }
332
333 fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
335 let ty = self.element_type.realize(params)?;
336 if self.non_empty {
337 Some(ArrayType::non_empty(ty).into())
338 } else {
339 Some(ArrayType::new(ty).into())
340 }
341 }
342
343 fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
349 self.element_type.assert_type_parameters(parameters);
350 }
351}
352
353#[derive(Debug, Clone)]
355pub struct GenericPairType {
356 left_type: Box<FunctionalType>,
358 right_type: Box<FunctionalType>,
360}
361
362impl GenericPairType {
363 pub fn new(
365 left_type: impl Into<FunctionalType>,
366 right_type: impl Into<FunctionalType>,
367 ) -> Self {
368 Self {
369 left_type: Box::new(left_type.into()),
370 right_type: Box::new(right_type.into()),
371 }
372 }
373
374 pub fn left_type(&self) -> &FunctionalType {
376 &self.left_type
377 }
378
379 pub fn right_type(&self) -> &FunctionalType {
381 &self.right_type
382 }
383
384 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
386 #[allow(clippy::missing_docs_in_private_items)]
387 struct Display<'a> {
388 params: &'a TypeParameters<'a>,
389 ty: &'a GenericPairType,
390 }
391
392 impl fmt::Display for Display<'_> {
393 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394 write!(f, "Pair[")?;
395 self.ty.left_type.display(self.params).fmt(f)?;
396 write!(f, ", ")?;
397 self.ty.right_type.display(self.params).fmt(f)?;
398 write!(f, "]")
399 }
400 }
401
402 Display { params, ty: self }
403 }
404
405 fn infer_type_parameters(
407 &self,
408 ty: &Type,
409 params: &mut TypeParameters<'_>,
410 ignore_constraints: bool,
411 ) {
412 match ty {
413 Type::Union => {
414 self.left_type
415 .infer_type_parameters(&Type::Union, params, ignore_constraints);
416 self.right_type
417 .infer_type_parameters(&Type::Union, params, ignore_constraints);
418 }
419 Type::Compound(CompoundType::Pair(ty), false) => {
420 self.left_type
421 .infer_type_parameters(ty.left_type(), params, ignore_constraints);
422 self.right_type
423 .infer_type_parameters(ty.right_type(), params, ignore_constraints);
424 }
425 _ => {}
426 }
427 }
428
429 fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
431 let left_type = self.left_type.realize(params)?;
432 let right_type = self.right_type.realize(params)?;
433 Some(PairType::new(left_type, right_type).into())
434 }
435
436 fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
442 self.left_type.assert_type_parameters(parameters);
443 self.right_type.assert_type_parameters(parameters);
444 }
445}
446
447#[derive(Debug, Clone)]
449pub struct GenericMapType {
450 key_type: Box<FunctionalType>,
452 value_type: Box<FunctionalType>,
454}
455
456impl GenericMapType {
457 pub fn new(key_type: impl Into<FunctionalType>, value_type: impl Into<FunctionalType>) -> Self {
459 Self {
460 key_type: Box::new(key_type.into()),
461 value_type: Box::new(value_type.into()),
462 }
463 }
464
465 pub fn key_type(&self) -> &FunctionalType {
467 &self.key_type
468 }
469
470 pub fn value_type(&self) -> &FunctionalType {
472 &self.value_type
473 }
474
475 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
477 #[allow(clippy::missing_docs_in_private_items)]
478 struct Display<'a> {
479 params: &'a TypeParameters<'a>,
480 ty: &'a GenericMapType,
481 }
482
483 impl fmt::Display for Display<'_> {
484 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485 write!(f, "Map[")?;
486 self.ty.key_type.display(self.params).fmt(f)?;
487 write!(f, ", ")?;
488 self.ty.value_type.display(self.params).fmt(f)?;
489 write!(f, "]")
490 }
491 }
492
493 Display { params, ty: self }
494 }
495
496 fn infer_type_parameters(
498 &self,
499 ty: &Type,
500 params: &mut TypeParameters<'_>,
501 ignore_constraints: bool,
502 ) {
503 match ty {
504 Type::Union => {
505 self.key_type
506 .infer_type_parameters(&Type::Union, params, ignore_constraints);
507 self.value_type
508 .infer_type_parameters(&Type::Union, params, ignore_constraints);
509 }
510 Type::Compound(CompoundType::Map(ty), false) => {
511 self.key_type
512 .infer_type_parameters(ty.key_type(), params, ignore_constraints);
513 self.value_type
514 .infer_type_parameters(ty.value_type(), params, ignore_constraints);
515 }
516 _ => {}
517 }
518 }
519
520 fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
522 let key_type = self.key_type.realize(params)?;
523 let value_type = self.value_type.realize(params)?;
524 Some(MapType::new(key_type, value_type).into())
525 }
526
527 fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
533 self.key_type.assert_type_parameters(parameters);
534 self.value_type.assert_type_parameters(parameters);
535 }
536}
537
538#[derive(Debug, Clone)]
540pub struct TypeParameters<'a> {
541 parameters: &'a [TypeParameter],
543 inferred_types: [Option<Type>; MAX_TYPE_PARAMETERS],
545 referenced: Cell<usize>,
548}
549
550impl<'a> TypeParameters<'a> {
551 pub fn new(parameters: &'a [TypeParameter]) -> Self {
559 assert!(
560 parameters.len() <= MAX_TYPE_PARAMETERS,
561 "no more than {MAX_TYPE_PARAMETERS} type parameters is supported"
562 );
563
564 Self {
565 parameters,
566 inferred_types: [const { None }; MAX_TYPE_PARAMETERS],
567 referenced: Cell::new(0),
568 }
569 }
570
571 pub fn get(&self, name: &str) -> Option<(&TypeParameter, Option<Type>)> {
577 let index = self.parameters.iter().position(|p| p.name == name)?;
578
579 self.referenced.set(self.referenced.get() | (1 << index));
581
582 Some((&self.parameters[index], self.inferred_types[index].clone()))
583 }
584
585 pub fn reset(&self) {
587 self.referenced.set(0);
588 }
589
590 pub fn referenced(&self) -> impl Iterator<Item = (&TypeParameter, Option<Type>)> + use<'_> {
593 let mut bits = self.referenced.get();
594 std::iter::from_fn(move || {
595 if bits == 0 {
596 return None;
597 }
598
599 let index = bits.trailing_zeros() as usize;
600 let parameter = &self.parameters[index];
601 let ty = self.inferred_types[index].clone();
602 bits ^= bits & bits.overflowing_neg().0;
603 Some((parameter, ty))
604 })
605 }
606
607 fn set_inferred_type(&mut self, name: &str, ty: Type) {
616 let index = self
617 .parameters
618 .iter()
619 .position(|p| p.name == name)
620 .unwrap_or_else(|| panic!("unknown type parameter `{name}`"));
621
622 self.inferred_types[index].get_or_insert(ty);
623 }
624}
625
626#[derive(Debug, Clone)]
628pub enum FunctionalType {
629 Concrete(Type),
631 Generic(GenericType),
633}
634
635impl FunctionalType {
636 pub fn is_generic(&self) -> bool {
638 matches!(self, Self::Generic(_))
639 }
640
641 pub fn concrete_type(&self) -> Option<&Type> {
645 match self {
646 Self::Concrete(ty) => Some(ty),
647 Self::Generic(_) => None,
648 }
649 }
650
651 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
653 #[allow(clippy::missing_docs_in_private_items)]
654 struct Display<'a> {
655 params: &'a TypeParameters<'a>,
656 ty: &'a FunctionalType,
657 }
658
659 impl fmt::Display for Display<'_> {
660 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
661 match self.ty {
662 FunctionalType::Concrete(ty) => ty.fmt(f),
663 FunctionalType::Generic(ty) => ty.display(self.params).fmt(f),
664 }
665 }
666 }
667
668 Display { params, ty: self }
669 }
670
671 fn infer_type_parameters(
673 &self,
674 ty: &Type,
675 params: &mut TypeParameters<'_>,
676 ignore_constraints: bool,
677 ) {
678 if let Self::Generic(generic) = self {
679 generic.infer_type_parameters(ty, params, ignore_constraints);
680 }
681 }
682
683 fn realize(&self, params: &TypeParameters<'_>) -> Option<Type> {
685 match self {
686 FunctionalType::Concrete(ty) => Some(ty.clone()),
687 FunctionalType::Generic(ty) => ty.realize(params),
688 }
689 }
690
691 fn assert_type_parameters(&self, parameters: &[TypeParameter]) {
697 if let FunctionalType::Generic(ty) = self {
698 ty.assert_type_parameters(parameters)
699 }
700 }
701}
702
703impl From<Type> for FunctionalType {
704 fn from(value: Type) -> Self {
705 Self::Concrete(value)
706 }
707}
708
709impl From<PrimitiveType> for FunctionalType {
710 fn from(value: PrimitiveType) -> Self {
711 Self::Concrete(value.into())
712 }
713}
714
715impl From<GenericType> for FunctionalType {
716 fn from(value: GenericType) -> Self {
717 Self::Generic(value)
718 }
719}
720
721impl From<GenericArrayType> for FunctionalType {
722 fn from(value: GenericArrayType) -> Self {
723 Self::Generic(GenericType::Array(value))
724 }
725}
726
727impl From<GenericPairType> for FunctionalType {
728 fn from(value: GenericPairType) -> Self {
729 Self::Generic(GenericType::Pair(value))
730 }
731}
732
733impl From<GenericMapType> for FunctionalType {
734 fn from(value: GenericMapType) -> Self {
735 Self::Generic(GenericType::Map(value))
736 }
737}
738
739#[derive(Debug)]
741pub struct TypeParameter {
742 name: &'static str,
744 constraint: Option<Box<dyn Constraint>>,
746}
747
748impl TypeParameter {
749 pub fn any(name: &'static str) -> Self {
751 Self {
752 name,
753 constraint: None,
754 }
755 }
756
757 pub fn new(name: &'static str, constraint: impl Constraint + 'static) -> Self {
759 Self {
760 name,
761 constraint: Some(Box::new(constraint)),
762 }
763 }
764
765 pub fn name(&self) -> &str {
767 self.name
768 }
769
770 pub fn constraint(&self) -> Option<&dyn Constraint> {
772 self.constraint.as_deref()
773 }
774}
775
776#[derive(Debug, Clone)]
778enum BindingKind {
779 Equivalence(Type),
784 Coercion(Type),
789}
790
791impl BindingKind {
792 pub fn ret(&self) -> &Type {
794 match self {
795 Self::Equivalence(ty) | Self::Coercion(ty) => ty,
796 }
797 }
798}
799
800#[derive(Debug)]
802pub struct FunctionParameter {
803 name: &'static str,
805 ty: FunctionalType,
807 description: &'static str,
809}
810
811impl FunctionParameter {
812 pub fn name(&self) -> &'static str {
814 self.name
815 }
816
817 pub fn ty(&self) -> &FunctionalType {
819 &self.ty
820 }
821
822 #[allow(dead_code)]
824 pub fn description(&self) -> &'static str {
825 self.description
826 }
827}
828
829#[derive(Debug)]
831pub struct FunctionSignature {
832 minimum_version: Option<SupportedVersion>,
834 type_parameters: Vec<TypeParameter>,
836 required: Option<usize>,
838 parameters: Vec<FunctionParameter>,
840 ret: FunctionalType,
842 definition: Option<&'static str>,
844}
845
846impl FunctionSignature {
847 pub fn builder() -> FunctionSignatureBuilder {
849 FunctionSignatureBuilder::new()
850 }
851
852 pub fn minimum_version(&self) -> SupportedVersion {
854 self.minimum_version
855 .unwrap_or(SupportedVersion::V1(V1::Zero))
856 }
857
858 pub fn type_parameters(&self) -> &[TypeParameter] {
860 &self.type_parameters
861 }
862
863 pub fn parameters(&self) -> &[FunctionParameter] {
865 &self.parameters
866 }
867
868 pub fn required(&self) -> usize {
873 self.required.unwrap_or(self.parameters.len())
874 }
875
876 pub fn ret(&self) -> &FunctionalType {
878 &self.ret
879 }
880
881 pub fn definition(&self) -> Option<&'static str> {
883 self.definition
884 }
885
886 pub fn is_generic(&self) -> bool {
888 self.generic_parameter_count() > 0 || self.ret.is_generic()
889 }
890
891 pub fn generic_parameter_count(&self) -> usize {
893 self.parameters.iter().filter(|p| p.ty.is_generic()).count()
894 }
895
896 pub fn display<'a>(&'a self, params: &'a TypeParameters<'a>) -> impl fmt::Display + 'a {
899 #[allow(clippy::missing_docs_in_private_items)]
900 struct Display<'a> {
901 params: &'a TypeParameters<'a>,
902 sig: &'a FunctionSignature,
903 }
904
905 impl fmt::Display for Display<'_> {
906 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
907 f.write_char('(')?;
908
909 self.params.reset();
910 let required = self.sig.required();
911 for (i, parameter) in self.sig.parameters.iter().enumerate() {
912 if i > 0 {
913 f.write_str(", ")?;
914 }
915
916 if i >= required {
917 f.write_char('<')?;
918 }
919
920 write!(
921 f,
922 "{name}: {ty}",
923 name = parameter.name(),
924 ty = parameter.ty().display(self.params)
925 )?;
926
927 if i >= required {
928 f.write_char('>')?;
929 }
930 }
931
932 write!(f, ") -> {ret}", ret = self.sig.ret.display(self.params))?;
933 write_uninferred_constraints(f, self.params)?;
934
935 Ok(())
936 }
937 }
938
939 Display { params, sig: self }
940 }
941
942 fn infer_type_parameters(
947 &self,
948 arguments: &[Type],
949 ignore_constraints: bool,
950 ) -> TypeParameters<'_> {
951 let mut parameters = TypeParameters::new(&self.type_parameters);
952 for (parameter, argument) in self.parameters.iter().zip(arguments.iter()) {
953 parameter
954 .ty
955 .infer_type_parameters(argument, &mut parameters, ignore_constraints);
956 }
957
958 parameters
959 }
960
961 fn insufficient_arguments(&self, arguments: &[Type]) -> bool {
964 arguments.len() < self.required() || arguments.len() > self.parameters.len()
965 }
966
967 fn bind(
977 &self,
978 version: SupportedVersion,
979 arguments: &[Type],
980 ) -> Result<BindingKind, FunctionBindError> {
981 if version < self.minimum_version() {
982 return Err(FunctionBindError::RequiresVersion(self.minimum_version()));
983 }
984
985 let required = self.required();
986 if arguments.len() < required {
987 return Err(FunctionBindError::TooFewArguments(required));
988 }
989
990 if arguments.len() > self.parameters.len() {
991 return Err(FunctionBindError::TooManyArguments(self.parameters.len()));
992 }
993
994 let mut coerced = false;
996 let type_parameters = self.infer_type_parameters(arguments, false);
997 for (i, (parameter, argument)) in self.parameters.iter().zip(arguments.iter()).enumerate() {
998 match parameter.ty.realize(&type_parameters) {
999 Some(ty) => {
1000 if !coerced && argument != &ty && argument != &ty.require() {
1004 coerced = true;
1005 }
1006
1007 if coerced && !argument.is_coercible_to(&ty) {
1008 return Err(FunctionBindError::ArgumentTypeMismatch {
1009 index: i,
1010 expected: format!("`{ty}`"),
1011 });
1012 }
1013 }
1014 None if argument.is_union() => {
1015 continue;
1017 }
1018 None => {
1019 type_parameters.reset();
1021
1022 let mut expected = String::new();
1023
1024 write!(
1025 &mut expected,
1026 "`{param}`",
1027 param = parameter.ty.display(&type_parameters)
1028 )
1029 .unwrap();
1030
1031 write_uninferred_constraints(&mut expected, &type_parameters).unwrap();
1032 return Err(FunctionBindError::ArgumentTypeMismatch { index: i, expected });
1033 }
1034 }
1035 }
1036
1037 let ret = self.ret().realize(&type_parameters).unwrap_or(Type::Union);
1041
1042 if coerced {
1043 Ok(BindingKind::Coercion(ret))
1044 } else {
1045 Ok(BindingKind::Equivalence(ret))
1046 }
1047 }
1048}
1049
1050impl Default for FunctionSignature {
1051 fn default() -> Self {
1052 Self {
1053 minimum_version: None,
1054 type_parameters: Default::default(),
1055 required: Default::default(),
1056 parameters: Default::default(),
1057 ret: FunctionalType::Concrete(Type::Union),
1058 definition: None,
1059 }
1060 }
1061}
1062
1063#[derive(Debug, Default)]
1065pub struct FunctionSignatureBuilder(FunctionSignature);
1066
1067impl FunctionSignatureBuilder {
1068 pub fn new() -> Self {
1070 Self(Default::default())
1071 }
1072
1073 pub fn min_version(mut self, version: SupportedVersion) -> Self {
1075 self.0.minimum_version = Some(version);
1076 self
1077 }
1078
1079 pub fn type_parameter(
1081 mut self,
1082 name: &'static str,
1083 constraint: impl Constraint + 'static,
1084 ) -> Self {
1085 self.0
1086 .type_parameters
1087 .push(TypeParameter::new(name, constraint));
1088 self
1089 }
1090
1091 pub fn any_type_parameter(mut self, name: &'static str) -> Self {
1093 self.0.type_parameters.push(TypeParameter::any(name));
1094 self
1095 }
1096
1097 pub fn parameter(
1099 mut self,
1100 name: &'static str,
1101 ty: impl Into<FunctionalType>,
1102 description: &'static str,
1103 ) -> Self {
1104 self.0.parameters.push(FunctionParameter {
1105 name,
1106 ty: ty.into(),
1107 description,
1108 });
1109 self
1110 }
1111
1112 pub fn ret(mut self, ret: impl Into<FunctionalType>) -> Self {
1117 self.0.ret = ret.into();
1118 self
1119 }
1120
1121 pub fn required(mut self, required: usize) -> Self {
1123 self.0.required = Some(required);
1124 self
1125 }
1126
1127 pub fn definition(mut self, definition: &'static str) -> Self {
1129 self.0.definition = Some(definition);
1130 self
1131 }
1132
1133 pub fn build(self) -> FunctionSignature {
1139 let sig = self.0;
1140
1141 if let Some(required) = sig.required
1144 && required > sig.parameters.len()
1145 {
1146 panic!("number of required parameters exceeds the number of parameters");
1147 }
1148
1149 assert!(
1150 sig.type_parameters.len() <= MAX_TYPE_PARAMETERS,
1151 "too many type parameters"
1152 );
1153
1154 assert!(
1155 sig.parameters.len() <= MAX_PARAMETERS,
1156 "too many parameters"
1157 );
1158
1159 for parameter in sig.parameters.iter() {
1161 parameter.ty.assert_type_parameters(&sig.type_parameters)
1162 }
1163
1164 sig.ret().assert_type_parameters(&sig.type_parameters);
1165
1166 assert!(sig.definition.is_some(), "functions should have definition");
1167
1168 sig
1169 }
1170}
1171
1172#[derive(Debug, Clone)]
1174pub struct Binding<'a> {
1175 return_type: Type,
1177 index: usize,
1181 signature: &'a FunctionSignature,
1183}
1184
1185impl Binding<'_> {
1186 pub fn return_type(&self) -> &Type {
1188 &self.return_type
1189 }
1190
1191 pub fn index(&self) -> usize {
1195 self.index
1196 }
1197
1198 pub fn signature(&self) -> &FunctionSignature {
1200 self.signature
1201 }
1202}
1203
1204#[derive(Debug)]
1206pub enum Function {
1207 Monomorphic(MonomorphicFunction),
1209 Polymorphic(PolymorphicFunction),
1211}
1212
1213impl Function {
1214 pub fn minimum_version(&self) -> SupportedVersion {
1216 match self {
1217 Self::Monomorphic(f) => f.minimum_version(),
1218 Self::Polymorphic(f) => f.minimum_version(),
1219 }
1220 }
1221
1222 pub fn param_min_max(&self, version: SupportedVersion) -> Option<(usize, usize)> {
1227 match self {
1228 Self::Monomorphic(f) => f.param_min_max(version),
1229 Self::Polymorphic(f) => f.param_min_max(version),
1230 }
1231 }
1232
1233 pub fn bind<'a>(
1235 &'a self,
1236 version: SupportedVersion,
1237 arguments: &[Type],
1238 ) -> Result<Binding<'a>, FunctionBindError> {
1239 match self {
1240 Self::Monomorphic(f) => f.bind(version, arguments),
1241 Self::Polymorphic(f) => f.bind(version, arguments),
1242 }
1243 }
1244
1245 pub fn realize_unconstrained_return_type(&self, arguments: &[Type]) -> Type {
1253 match self {
1254 Self::Monomorphic(f) => {
1255 let type_parameters = f.signature.infer_type_parameters(arguments, true);
1256 f.signature
1257 .ret()
1258 .realize(&type_parameters)
1259 .unwrap_or(Type::Union)
1260 }
1261 Self::Polymorphic(f) => {
1262 let mut ty = None;
1263
1264 for signature in &f.signatures {
1267 let type_parameters = signature.infer_type_parameters(arguments, true);
1268 let ret_ty = signature
1269 .ret()
1270 .realize(&type_parameters)
1271 .unwrap_or(Type::Union);
1272
1273 if ty.get_or_insert(ret_ty.clone()) != &ret_ty {
1274 return Type::Union;
1275 }
1276 }
1277
1278 ty.unwrap_or(Type::Union)
1279 }
1280 }
1281 }
1282}
1283
1284#[derive(Debug)]
1289pub struct MonomorphicFunction {
1290 signature: FunctionSignature,
1292}
1293
1294impl MonomorphicFunction {
1295 pub fn new(signature: FunctionSignature) -> Self {
1297 Self { signature }
1298 }
1299
1300 pub fn minimum_version(&self) -> SupportedVersion {
1302 self.signature.minimum_version()
1303 }
1304
1305 pub fn param_min_max(&self, version: SupportedVersion) -> Option<(usize, usize)> {
1310 if version < self.signature.minimum_version() {
1311 return None;
1312 }
1313
1314 Some((self.signature.required(), self.signature.parameters.len()))
1315 }
1316
1317 pub fn signature(&self) -> &FunctionSignature {
1319 &self.signature
1320 }
1321
1322 pub fn bind<'a>(
1324 &'a self,
1325 version: SupportedVersion,
1326 arguments: &[Type],
1327 ) -> Result<Binding<'a>, FunctionBindError> {
1328 let return_type = self.signature.bind(version, arguments)?.ret().clone();
1329 Ok(Binding {
1330 return_type,
1331 index: 0,
1332 signature: &self.signature,
1333 })
1334 }
1335}
1336
1337impl From<MonomorphicFunction> for Function {
1338 fn from(value: MonomorphicFunction) -> Self {
1339 Self::Monomorphic(value)
1340 }
1341}
1342
1343#[derive(Debug)]
1349pub struct PolymorphicFunction {
1350 signatures: Vec<FunctionSignature>,
1352}
1353
1354impl PolymorphicFunction {
1355 pub fn new(signatures: Vec<FunctionSignature>) -> Self {
1361 assert!(
1362 signatures.len() > 1,
1363 "a polymorphic function must have at least two signatures"
1364 );
1365
1366 Self { signatures }
1367 }
1368
1369 pub fn minimum_version(&self) -> SupportedVersion {
1371 self.signatures
1372 .iter()
1373 .fold(None, |v: Option<SupportedVersion>, s| {
1374 Some(
1375 v.map(|v| v.min(s.minimum_version()))
1376 .unwrap_or_else(|| s.minimum_version()),
1377 )
1378 })
1379 .expect("there should be at least one signature")
1380 }
1381
1382 pub fn param_min_max(&self, version: SupportedVersion) -> Option<(usize, usize)> {
1387 let mut min = usize::MAX;
1388 let mut max = 0;
1389 for sig in self
1390 .signatures
1391 .iter()
1392 .filter(|s| s.minimum_version() <= version)
1393 {
1394 min = std::cmp::min(min, sig.required());
1395 max = std::cmp::max(max, sig.parameters().len());
1396 }
1397
1398 if min == usize::MAX {
1399 return None;
1400 }
1401
1402 Some((min, max))
1403 }
1404
1405 pub fn signatures(&self) -> &[FunctionSignature] {
1407 &self.signatures
1408 }
1409
1410 pub fn bind<'a>(
1414 &'a self,
1415 version: SupportedVersion,
1416 arguments: &[Type],
1417 ) -> Result<Binding<'a>, FunctionBindError> {
1418 let min_version = self.minimum_version();
1420 if version < min_version {
1421 return Err(FunctionBindError::RequiresVersion(min_version));
1422 }
1423
1424 let (min, max) = self
1426 .param_min_max(version)
1427 .expect("should have at least one signature for the version");
1428 if arguments.len() < min {
1429 return Err(FunctionBindError::TooFewArguments(min));
1430 }
1431
1432 if arguments.len() > max {
1433 return Err(FunctionBindError::TooManyArguments(max));
1434 }
1435
1436 let mut max_mismatch_index = 0;
1443 let mut expected_types = IndexSet::new();
1444
1445 for generic in [false, true] {
1446 let mut exact: Option<(usize, Type)> = None;
1447 let mut coercion1: Option<(usize, Type)> = None;
1448 let mut coercion2 = None;
1449 for (index, signature) in self.signatures.iter().enumerate().filter(|(_, s)| {
1450 s.is_generic() == generic
1451 && s.minimum_version() <= version
1452 && !s.insufficient_arguments(arguments)
1453 }) {
1454 match signature.bind(version, arguments) {
1455 Ok(BindingKind::Equivalence(ty)) => {
1456 if let Some((previous, _)) = exact {
1458 return Err(FunctionBindError::Ambiguous {
1459 first: self.signatures[previous]
1460 .display(&TypeParameters::new(
1461 &self.signatures[previous].type_parameters,
1462 ))
1463 .to_string(),
1464 second: self.signatures[index]
1465 .display(&TypeParameters::new(
1466 &self.signatures[index].type_parameters,
1467 ))
1468 .to_string(),
1469 });
1470 }
1471
1472 exact = Some((index, ty));
1473 }
1474 Ok(BindingKind::Coercion(ty)) => {
1475 if coercion1.is_none() {
1479 coercion1 = Some((index, ty));
1480 } else {
1481 coercion2.get_or_insert(index);
1482 }
1483 }
1484 Err(FunctionBindError::ArgumentTypeMismatch { index, expected }) => {
1485 if index > max_mismatch_index {
1487 max_mismatch_index = index;
1488 expected_types.clear();
1489 }
1490
1491 if index == max_mismatch_index {
1492 expected_types.insert(expected);
1493 }
1494 }
1495 Err(
1496 FunctionBindError::RequiresVersion(_)
1497 | FunctionBindError::Ambiguous { .. }
1498 | FunctionBindError::TooFewArguments(_)
1499 | FunctionBindError::TooManyArguments(_),
1500 ) => unreachable!("should not encounter these errors due to above filter"),
1501 }
1502 }
1503
1504 if let Some((index, ty)) = exact {
1505 return Ok(Binding {
1506 return_type: ty,
1507 index,
1508 signature: &self.signatures[index],
1509 });
1510 }
1511
1512 if let Some(previous) = coercion2 {
1514 let index = coercion1.unwrap().0;
1515 return Err(FunctionBindError::Ambiguous {
1516 first: self.signatures[previous]
1517 .display(&TypeParameters::new(
1518 &self.signatures[previous].type_parameters,
1519 ))
1520 .to_string(),
1521 second: self.signatures[index]
1522 .display(&TypeParameters::new(
1523 &self.signatures[index].type_parameters,
1524 ))
1525 .to_string(),
1526 });
1527 }
1528
1529 if let Some((index, ty)) = coercion1 {
1530 return Ok(Binding {
1531 return_type: ty,
1532 index,
1533 signature: &self.signatures[index],
1534 });
1535 }
1536 }
1537
1538 assert!(!expected_types.is_empty());
1539
1540 let mut expected = String::new();
1541 for (i, ty) in expected_types.iter().enumerate() {
1542 if i > 0 {
1543 if expected_types.len() == 2 {
1544 expected.push_str(" or ");
1545 } else if i == expected_types.len() - 1 {
1546 expected.push_str(", or ");
1547 } else {
1548 expected.push_str(", ");
1549 }
1550 }
1551
1552 expected.push_str(ty);
1553 }
1554
1555 Err(FunctionBindError::ArgumentTypeMismatch {
1556 index: max_mismatch_index,
1557 expected,
1558 })
1559 }
1560}
1561
1562impl From<PolymorphicFunction> for Function {
1563 fn from(value: PolymorphicFunction) -> Self {
1564 Self::Polymorphic(value)
1565 }
1566}
1567
1568#[derive(Debug)]
1570pub struct StandardLibrary {
1571 functions: IndexMap<&'static str, Function>,
1573 array_int: Type,
1575 array_string: Type,
1577 array_file: Type,
1579 array_object: Type,
1581 array_string_non_empty: Type,
1583 array_array_string: Type,
1585 map_string_string: Type,
1587 map_string_int: Type,
1589}
1590
1591impl StandardLibrary {
1592 pub fn function(&self, name: &str) -> Option<&Function> {
1594 self.functions.get(name)
1595 }
1596
1597 pub fn functions(&self) -> impl ExactSizeIterator<Item = (&'static str, &Function)> {
1599 self.functions.iter().map(|(n, f)| (*n, f))
1600 }
1601
1602 pub fn array_int_type(&self) -> &Type {
1604 &self.array_int
1605 }
1606
1607 pub fn array_string_type(&self) -> &Type {
1609 &self.array_string
1610 }
1611
1612 pub fn array_file_type(&self) -> &Type {
1614 &self.array_file
1615 }
1616
1617 pub fn array_object_type(&self) -> &Type {
1619 &self.array_object
1620 }
1621
1622 pub fn array_string_non_empty_type(&self) -> &Type {
1624 &self.array_string_non_empty
1625 }
1626
1627 pub fn array_array_string_type(&self) -> &Type {
1629 &self.array_array_string
1630 }
1631
1632 pub fn map_string_string_type(&self) -> &Type {
1634 &self.map_string_string
1635 }
1636
1637 pub fn map_string_int_type(&self) -> &Type {
1639 &self.map_string_int
1640 }
1641}
1642
1643pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
1645 let array_int: Type = ArrayType::new(PrimitiveType::Integer).into();
1646 let array_string: Type = ArrayType::new(PrimitiveType::String).into();
1647 let array_file: Type = ArrayType::new(PrimitiveType::File).into();
1648 let array_object: Type = ArrayType::new(Type::Object).into();
1649 let array_string_non_empty: Type = ArrayType::non_empty(PrimitiveType::String).into();
1650 let array_array_string: Type = ArrayType::new(array_string.clone()).into();
1651 let map_string_string: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
1652 let map_string_int: Type = MapType::new(PrimitiveType::String, PrimitiveType::Integer).into();
1653 let mut functions = IndexMap::new();
1654
1655 assert!(
1657 functions
1658 .insert(
1659 "floor",
1660 MonomorphicFunction::new(
1661 FunctionSignature::builder()
1662 .parameter("value", PrimitiveType::Float, "The number to round.")
1663 .ret(PrimitiveType::Integer)
1664 .definition(
1665 r#"
1666Rounds a floating point number **down** to the next lower integer.
1667
1668**Parameters**:
1669
16701. `Float`: the number to round.
1671
1672**Returns**: An integer.
1673
1674Example: test_floor.wdl
1675
1676```wdl
1677version 1.2
1678
1679workflow test_floor {
1680 input {
1681 Int i1
1682 }
1683
1684 Int i2 = i1 - 1
1685 Float f1 = i1
1686 Float f2 = i1 - 0.1
1687
1688 output {
1689 Array[Boolean] all_true = [floor(f1) == i1, floor(f2) == i2]
1690 }
1691}
1692```"#
1693 )
1694 .build(),
1695 )
1696 .into(),
1697 )
1698 .is_none()
1699 );
1700
1701 assert!(
1703 functions
1704 .insert(
1705 "ceil",
1706 MonomorphicFunction::new(
1707 FunctionSignature::builder()
1708 .parameter("value", PrimitiveType::Float, "The number to round.")
1709 .ret(PrimitiveType::Integer)
1710 .definition(
1711 r#"
1712Rounds a floating point number **up** to the next higher integer.
1713
1714**Parameters**:
1715
17161. `Float`: the number to round.
1717
1718**Returns**: An integer.
1719
1720Example: test_ceil.wdl
1721
1722```wdl
1723version 1.2
1724
1725workflow test_ceil {
1726 input {
1727 Int i1
1728 }
1729
1730 Int i2 = i1 + 1
1731 Float f1 = i1
1732 Float f2 = i1 + 0.1
1733
1734 output {
1735 Array[Boolean] all_true = [ceil(f1) == i1, ceil(f2) == i2]
1736 }
1737}
1738```
1739"#
1740 )
1741 .build(),
1742 )
1743 .into(),
1744 )
1745 .is_none()
1746 );
1747
1748 assert!(
1750 functions
1751 .insert(
1752 "round",
1753 MonomorphicFunction::new(
1754 FunctionSignature::builder()
1755 .parameter("value", PrimitiveType::Float, "The number to round.")
1756 .ret(PrimitiveType::Integer)
1757 .definition(r#"
1758Rounds a floating point number to the nearest integer based on standard rounding rules ("round half up").
1759
1760**Parameters**:
1761
17621. `Float`: the number to round.
1763
1764**Returns**: An integer.
1765
1766Example: test_round.wdl
1767
1768```wdl
1769version 1.2
1770
1771workflow test_round {
1772 input {
1773 Int i1
1774 }
1775
1776 Int i2 = i1 + 1
1777 Float f1 = i1 + 0.49
1778 Float f2 = i1 + 0.50
1779
1780 output {
1781 Array[Boolean] all_true = [round(f1) == i1, round(f2) == i2]
1782 }
1783}
1784```
1785"#
1786 )
1787 .build(),
1788 )
1789 .into(),
1790 )
1791 .is_none()
1792 );
1793
1794 const MIN_DEFINITION: &str = r#"
1795Returns the smaller of two values. If both values are `Int`s, the return value is an `Int`, otherwise it is a `Float`.
1796
1797**Parameters**:
1798
17991. `Int|Float`: the first number to compare.
18002. `Int|Float`: the second number to compare.
1801
1802**Returns**: The smaller of the two arguments.
1803
1804Example: test_min.wdl
1805
1806```wdl
1807version 1.2
1808
1809workflow test_min {
1810 input {
1811 Int value1
1812 Float value2
1813 }
1814
1815 output {
1816 # these two expressions are equivalent
1817 Float min1 = if value1 < value2 then value1 else value2
1818 Float min2 = min(value1, value2)
1819 }
1820}
1821```
1822"#;
1823
1824 assert!(
1826 functions
1827 .insert(
1828 "min",
1829 PolymorphicFunction::new(vec![
1830 FunctionSignature::builder()
1831 .min_version(SupportedVersion::V1(V1::One))
1832 .parameter("a", PrimitiveType::Integer, "The first number to compare.",)
1833 .parameter("b", PrimitiveType::Integer, "The second number to compare.",)
1834 .ret(PrimitiveType::Integer)
1835 .definition(MIN_DEFINITION)
1836 .build(),
1837 FunctionSignature::builder()
1838 .min_version(SupportedVersion::V1(V1::One))
1839 .parameter("a", PrimitiveType::Integer, "The first number to compare.",)
1840 .parameter("b", PrimitiveType::Float, "The second number to compare.")
1841 .ret(PrimitiveType::Float)
1842 .definition(MIN_DEFINITION)
1843 .build(),
1844 FunctionSignature::builder()
1845 .min_version(SupportedVersion::V1(V1::One))
1846 .parameter("a", PrimitiveType::Float, "The first number to compare.")
1847 .parameter("b", PrimitiveType::Integer, "The second number to compare.",)
1848 .ret(PrimitiveType::Float)
1849 .definition(MIN_DEFINITION)
1850 .build(),
1851 FunctionSignature::builder()
1852 .min_version(SupportedVersion::V1(V1::One))
1853 .parameter("a", PrimitiveType::Float, "The first number to compare.")
1854 .parameter("b", PrimitiveType::Float, "The second number to compare.")
1855 .ret(PrimitiveType::Float)
1856 .definition(MIN_DEFINITION)
1857 .build(),
1858 ])
1859 .into(),
1860 )
1861 .is_none()
1862 );
1863
1864 const MAX_DEFINITION: &str = r#"
1865Returns the larger of two values. If both values are `Int`s, the return value is an `Int`, otherwise it is a `Float`.
1866
1867**Parameters**:
1868
18691. `Int|Float`: the first number to compare.
18702. `Int|Float`: the second number to compare.
1871
1872**Returns**: The larger of the two arguments.
1873
1874Example: test_max.wdl
1875
1876```wdl
1877version 1.2
1878
1879workflow test_max {
1880 input {
1881 Int value1
1882 Float value2
1883 }
1884
1885 output {
1886 # these two expressions are equivalent
1887 Float min1 = if value1 > value2 then value1 else value2
1888 Float min2 = max(value1, value2)
1889 }
1890}
1891```
1892"#;
1893
1894 assert!(
1896 functions
1897 .insert(
1898 "max",
1899 PolymorphicFunction::new(vec![
1900 FunctionSignature::builder()
1901 .min_version(SupportedVersion::V1(V1::One))
1902 .parameter("a", PrimitiveType::Integer, "The first number to compare.")
1903 .parameter("b", PrimitiveType::Integer, "The second number to compare.")
1904 .ret(PrimitiveType::Integer)
1905 .definition(MAX_DEFINITION)
1906 .build(),
1907 FunctionSignature::builder()
1908 .min_version(SupportedVersion::V1(V1::One))
1909 .parameter("a", PrimitiveType::Integer, "The first number to compare.")
1910 .parameter("b", PrimitiveType::Float, "The second number to compare.")
1911 .ret(PrimitiveType::Float)
1912 .definition(MAX_DEFINITION)
1913 .build(),
1914 FunctionSignature::builder()
1915 .min_version(SupportedVersion::V1(V1::One))
1916 .parameter("a", PrimitiveType::Float, "The first number to compare.")
1917 .parameter("b", PrimitiveType::Integer, "The second number to compare.",)
1918 .ret(PrimitiveType::Float)
1919 .definition(MAX_DEFINITION)
1920 .build(),
1921 FunctionSignature::builder()
1922 .min_version(SupportedVersion::V1(V1::One))
1923 .parameter("a", PrimitiveType::Float, "The first number to compare.")
1924 .parameter("b", PrimitiveType::Float, "The second number to compare.")
1925 .ret(PrimitiveType::Float)
1926 .definition(MAX_DEFINITION)
1927 .build(),
1928 ])
1929 .into(),
1930 )
1931 .is_none()
1932 );
1933
1934 assert!(
1936 functions
1937 .insert(
1938 "find",
1939 MonomorphicFunction::new(
1940 FunctionSignature::builder()
1941 .min_version(SupportedVersion::V1(V1::Two))
1942 .parameter("input", PrimitiveType::String, "The input string to search.")
1943 .parameter("pattern", PrimitiveType::String, "The pattern to search for.")
1944 .ret(Type::from(PrimitiveType::String).optional())
1945 .definition(
1946 r#"
1947Given 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).
1948
1949Note that regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped. For example:
1950
1951```wdl
1952String? first_match = find("hello\tBob", "\t")
1953```
1954
1955**Parameters**
1956
19571. `String`: the input string to search.
19582. `String`: the pattern to search for.
1959
1960**Returns**: The contents of the first match, or `None` if `pattern` does not match `input`.
1961
1962Example: test_find_task.wdl
1963
1964```wdl
1965version 1.2
1966workflow find_string {
1967 input {
1968 String in = "hello world"
1969 String pattern1 = "e..o"
1970 String pattern2 = "goodbye"
1971 }
1972 output {
1973 String? match1 = find(in, pattern1) # "ello"
1974 String? match2 = find(in, pattern2) # None
1975 }
1976}
1977```
1978"#
1979 )
1980 .build(),
1981 )
1982 .into(),
1983 )
1984 .is_none()
1985 );
1986
1987 assert!(
1989 functions
1990 .insert(
1991 "matches",
1992 MonomorphicFunction::new(
1993 FunctionSignature::builder()
1994 .min_version(SupportedVersion::V1(V1::Two))
1995 .parameter("input", PrimitiveType::String, "The input string to search.")
1996 .parameter("pattern", PrimitiveType::String, "The pattern to search for.")
1997 .ret(PrimitiveType::Boolean)
1998 .definition(
1999 r#"
2000Given 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).
2001
2002To test whether `pattern` matches the entire `input`, make sure to begin and end the pattern with anchors. For example:
2003
2004```wdl
2005Boolean full_match = matches("abc123", "^a.+3$")
2006```
2007
2008Note that regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped. For example:
2009
2010```wdl
2011Boolean has_tab = matches("hello\tBob", "\t")
2012```
2013
2014**Parameters**
2015
20161. `String`: the input string to search.
20172. `String`: the pattern to search for.
2018
2019**Returns**: `true` if `pattern` matches `input` at least once, otherwise `false`.
2020
2021Example: test_matches_task.wdl
2022
2023```wdl
2024version 1.2
2025workflow contains_string {
2026 input {
2027 File fastq
2028 }
2029 output {
2030 Boolean is_compressed = matches(basename(fastq), "\\.(gz|zip|zstd)")
2031 Boolean is_read1 = matches(basename(fastq), "_R1")
2032 }
2033}
2034```
2035"#
2036 )
2037 .build(),
2038 )
2039 .into(),
2040 )
2041 .is_none()
2042 );
2043
2044 assert!(
2046 functions
2047 .insert(
2048 "sub",
2049 MonomorphicFunction::new(
2050 FunctionSignature::builder()
2051 .parameter("input", PrimitiveType::String, "The input string.")
2052 .parameter("pattern", PrimitiveType::String, "The pattern to search for.")
2053 .parameter("replace", PrimitiveType::String, "The replacement string.")
2054 .ret(PrimitiveType::String)
2055 .definition(
2056 r#"
2057Given 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).
2058Regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped (e.g., "\t").
2059
2060🗑 The option for execution engines to allow other regular expression grammars besides POSIX ERE is deprecated.
2061
2062**Parameters**:
2063
20641. `String`: the input string.
20652. `String`: the pattern to search for.
20663. `String`: the replacement string.
2067
2068**Returns**: the input string, with all occurrences of the pattern replaced by the replacement string.
2069
2070Example: test_sub.wdl
2071
2072```wdl
2073version 1.2
2074
2075workflow test_sub {
2076 String chocolike = "I like chocolate when\nit's late"
2077
2078 output {
2079 String chocolove = sub(chocolike, "like", "love") # I love chocolate when\nit's late
2080 String chocoearly = sub(chocolike, "late", "early") # I like chocoearly when\nit's early
2081 String chocolate = sub(chocolike, "late$", "early") # I like chocolate when\nit's early
2082 String chocoearlylate = sub(chocolike, "[^ ]late", "early") # I like chocearly when\nit's late
2083 String choco4 = sub(chocolike, " [:alpha:]{4} ", " 4444 ") # I 4444 chocolate 4444\nit's late
2084 String no_newline = sub(chocolike, "\n", " ") # "I like chocolate when it's late"
2085 }
2086}
2087```
2088"#
2089 )
2090 .build(),
2091 )
2092 .into(),
2093 )
2094 .is_none()
2095 );
2096
2097 const BASENAME_DEFINITION: &str = r#"
2098Returns the "basename" of a file or directory - the name after the last directory separator in the path.
2099
2100The 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.
2101
2102**Parameters**
2103
21041. `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.
21052. `String`: (Optional) Suffix to remove from the file name.
2106
2107**Returns**: The file's basename as a `String`.
2108
2109Example: test_basename.wdl
2110
2111```wdl
2112version 1.2
2113
2114workflow test_basename {
2115 output {
2116 Boolean is_true1 = basename("/path/to/file.txt") == "file.txt"
2117 Boolean is_true2 = basename("/path/to/file.txt", ".txt") == "file"
2118 Boolean is_true3 = basename("/path/to/dir") == "dir"
2119 }
2120}
2121```
2122"#;
2123
2124 assert!(
2126 functions
2127 .insert(
2128 "basename",
2129 PolymorphicFunction::new(vec![
2130 FunctionSignature::builder()
2131 .required(1)
2132 .parameter(
2133 "path",
2134 PrimitiveType::File,
2135 "Path of the file or directory to read. If the argument is a \
2136 `String`, it is assumed to be a local file path relative to the \
2137 current working directory of the task.",
2138 )
2139 .parameter(
2140 "suffix",
2141 PrimitiveType::String,
2142 "(Optional) Suffix to remove from the file name.",
2143 )
2144 .ret(PrimitiveType::String)
2145 .definition(BASENAME_DEFINITION)
2146 .build(),
2147 FunctionSignature::builder()
2152 .min_version(SupportedVersion::V1(V1::Two))
2153 .required(1)
2154 .parameter(
2155 "path",
2156 PrimitiveType::String,
2157 "Path of the file or directory to read. If the argument is a \
2158 `String`, it is assumed to be a local file path relative to the \
2159 current working directory of the task."
2160 )
2161 .parameter(
2162 "suffix",
2163 PrimitiveType::String,
2164 "(Optional) Suffix to remove from the file name."
2165 )
2166 .ret(PrimitiveType::String)
2167 .definition(BASENAME_DEFINITION)
2168 .build(),
2169 FunctionSignature::builder()
2170 .min_version(SupportedVersion::V1(V1::Two))
2171 .required(1)
2172 .parameter(
2173 "path",
2174 PrimitiveType::Directory,
2175 "Path of the file or directory to read. If the argument is a \
2176 `String`, it is assumed to be a local file path relative to the \
2177 current working directory of the task.",
2178 )
2179 .parameter(
2180 "suffix",
2181 PrimitiveType::String,
2182 "(Optional) Suffix to remove from the file name.",
2183 )
2184 .ret(PrimitiveType::String)
2185 .definition(BASENAME_DEFINITION)
2186 .build(),
2187 ])
2188 .into(),
2189 )
2190 .is_none()
2191 );
2192
2193 const JOIN_PATHS_DEFINITION: &str = r#"
2194Joins together two or more paths into an absolute path in the host filesystem.
2195
2196There are three variants of this function:
2197
21981. `File join_paths(File, String)`: Joins together exactly two paths. The first path may be either absolute or relative and must specify a directory; the second path is relative to the first path and may specify a file or directory.
21992. `File join_paths(File, Array[String]+)`: Joins together any number of relative paths with a base path. The first argument may be either an absolute or a relative path and must specify a directory. The paths in the second array argument must all be relative. The *last* element may specify a file or directory; all other elements must specify a directory.
22003. `File 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.
2201
2202An 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.
2203
2204A 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.
2205
2206**Parameters**
2207
22081. `File|Array[String]+`: Either a path or an array of paths.
22092. `String|Array[String]+`: A relative path or paths; only allowed if the first argument is a `File`.
2210
2211**Returns**: A `File` 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.
2212
2213Example: join_paths_task.wdl
2214
2215```wdl
2216version 1.2
2217
2218task resolve_paths_task {
2219 input {
2220 File abs_file = "/usr"
2221 String abs_str = "/usr"
2222 String rel_dir_str = "bin"
2223 File rel_file = "echo"
2224 File rel_dir_file = "mydir"
2225 String rel_str = "mydata.txt"
2226 }
2227
2228 # these are all equivalent to '/usr/bin/echo'
2229 File bin1 = join_paths(abs_file, [rel_dir_str, rel_file])
2230 File bin2 = join_paths(abs_str, [rel_dir_str, rel_file])
2231 File bin3 = join_paths([abs_str, rel_dir_str, rel_file])
2232
2233 # the default behavior is that this resolves to
2234 # '<working dir>/mydir/mydata.txt'
2235 File data = join_paths(rel_dir_file, rel_str)
2236
2237 # this resolves to '<working dir>/bin/echo', which is non-existent
2238 File doesnt_exist = join_paths([rel_dir_str, rel_file])
2239 command <<<
2240 mkdir ~{rel_dir_file}
2241 ~{bin1} -n "hello" > ~{data}
2242 >>>
2243
2244 output {
2245 Boolean bins_equal = (bin1 == bin2) && (bin1 == bin3)
2246 String result = read_string(data)
2247 File? missing_file = doesnt_exist
2248 }
2249
2250 runtime {
2251 container: "ubuntu:latest"
2252 }
2253}
2254```
2255"#;
2256
2257 assert!(
2259 functions
2260 .insert(
2261 "join_paths",
2262 PolymorphicFunction::new(vec![
2263 FunctionSignature::builder()
2264 .min_version(SupportedVersion::V1(V1::Two))
2265 .parameter(
2266 "base",
2267 PrimitiveType::File,
2268 "Either a path or an array of paths.",
2269 )
2270 .parameter(
2271 "relative",
2272 PrimitiveType::String,
2273 "A relative path or paths; only allowed if the first argument is a \
2274 `File`.",
2275 )
2276 .ret(PrimitiveType::File)
2277 .definition(JOIN_PATHS_DEFINITION)
2278 .build(),
2279 FunctionSignature::builder()
2280 .min_version(SupportedVersion::V1(V1::Two))
2281 .parameter(
2282 "base",
2283 PrimitiveType::File,
2284 "Either a path or an array of paths."
2285 )
2286 .parameter(
2287 "relative",
2288 array_string_non_empty.clone(),
2289 "A relative path or paths; only allowed if the first argument is a \
2290 `File`."
2291 )
2292 .ret(PrimitiveType::File)
2293 .definition(JOIN_PATHS_DEFINITION)
2294 .build(),
2295 FunctionSignature::builder()
2296 .min_version(SupportedVersion::V1(V1::Two))
2297 .parameter(
2298 "paths",
2299 array_string_non_empty.clone(),
2300 "Either a path or an array of paths."
2301 )
2302 .ret(PrimitiveType::File)
2303 .definition(JOIN_PATHS_DEFINITION)
2304 .build(),
2305 ])
2306 .into(),
2307 )
2308 .is_none()
2309 );
2310
2311 assert!(
2313 functions
2314 .insert(
2315 "glob",
2316 MonomorphicFunction::new(
2317 FunctionSignature::builder()
2318 .parameter("pattern", PrimitiveType::String, "The glob string.")
2319 .ret(array_file.clone())
2320 .definition(
2321 r#"
2322Returns 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.
2323
2324`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.
2325
2326At least in standard Bash, glob expressions are not evaluated recursively, i.e., files in nested directories are not included.
2327
2328**Parameters**:
2329
23301. `String`: The glob string.
2331
2332**Returns**: A array of all files matched by the glob.
2333
2334Example: gen_files_task.wdl
2335
2336```wdl
2337version 1.2
2338
2339task gen_files {
2340 input {
2341 Int num_files
2342 }
2343
2344 command <<<
2345 for i in 1..~{num_files}; do
2346 printf ${i} > a_file_${i}.txt
2347 done
2348 mkdir a_dir
2349 touch a_dir/a_inner.txt
2350 >>>
2351
2352 output {
2353 Array[File] files = glob("a_*")
2354 Int glob_len = length(files)
2355 }
2356}
2357```
2358"#
2359 )
2360 .build(),
2361 )
2362 .into(),
2363 )
2364 .is_none()
2365 );
2366
2367 const SIZE_DEFINITION: &str = r#"
2368Determines 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)
2369
2370In the second variant of the `size` function, the parameter type `X` represents any compound type that contains `File` or `File?` nested at any depth.
2371
2372If 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.
2373
2374**Parameters**
2375
23761. `File|File?|Directory|Directory?|X|X?`: A file, directory, or a compound value containing files/directories, for which to determine the size.
23772. `String`: (Optional) The unit of storage; defaults to 'B'.
2378
2379**Returns**: The size of the files/directories as a `Float`.
2380
2381Example: file_sizes_task.wdl
2382
2383```wdl
2384version 1.2
2385
2386task file_sizes {
2387 command <<<
2388 printf "this file is 22 bytes\n" > created_file
2389 >>>
2390
2391 File? missing_file = None
2392
2393 output {
2394 File created_file = "created_file"
2395 Float missing_file_bytes = size(missing_file)
2396 Float created_file_bytes = size(created_file, "B")
2397 Float multi_file_kb = size([created_file, missing_file], "K")
2398
2399 Map[String, Pair[Int, File]] nested = {
2400 "a": (10, created_file),
2401 "b": (50, missing_file)
2402 }
2403 Float nested_bytes = size(nested)
2404 }
2405
2406 requirements {
2407 container: "ubuntu:latest"
2408 }
2409}
2410```
2411"#;
2412
2413 assert!(
2415 functions
2416 .insert(
2417 "size",
2418 PolymorphicFunction::new(vec![
2419 FunctionSignature::builder()
2422 .min_version(SupportedVersion::V1(V1::Two))
2423 .required(1)
2424 .parameter(
2425 "value",
2426 Type::None,
2427 "A file, directory, or a compound value containing files/directories, \
2428 for which to determine the size."
2429 )
2430 .parameter(
2431 "unit",
2432 PrimitiveType::String,
2433 "(Optional) The unit of storage; defaults to 'B'."
2434 )
2435 .ret(PrimitiveType::Float)
2436 .definition(SIZE_DEFINITION)
2437 .build(),
2438 FunctionSignature::builder()
2439 .required(1)
2440 .parameter(
2441 "value",
2442 Type::from(PrimitiveType::File).optional(),
2443 "A file, directory, or a compound value containing files/directories, \
2444 for which to determine the size."
2445 )
2446 .parameter(
2447 "unit",
2448 PrimitiveType::String,
2449 "(Optional) The unit of storage; defaults to 'B'."
2450 )
2451 .ret(PrimitiveType::Float)
2452 .definition(SIZE_DEFINITION)
2453 .build(),
2454 FunctionSignature::builder()
2459 .min_version(SupportedVersion::V1(V1::Two))
2460 .required(1)
2461 .parameter(
2462 "value",
2463 Type::from(PrimitiveType::String).optional(),
2464 "A file, directory, or a compound value containing files/directories, \
2465 for which to determine the size.",
2466 )
2467 .parameter(
2468 "unit",
2469 PrimitiveType::String,
2470 "(Optional) The unit of storage; defaults to 'B'.",
2471 )
2472 .ret(PrimitiveType::Float)
2473 .definition(SIZE_DEFINITION)
2474 .build(),
2475 FunctionSignature::builder()
2476 .min_version(SupportedVersion::V1(V1::Two))
2477 .required(1)
2478 .parameter(
2479 "value",
2480 Type::from(PrimitiveType::Directory).optional(),
2481 "A file, directory, or a compound value containing files/directories, \
2482 for which to determine the size."
2483 )
2484 .parameter(
2485 "unit",
2486 PrimitiveType::String,
2487 "(Optional) The unit of storage; defaults to 'B'."
2488 )
2489 .ret(PrimitiveType::Float)
2490 .definition(SIZE_DEFINITION)
2491 .build(),
2492 FunctionSignature::builder()
2493 .required(1)
2494 .type_parameter("X", SizeableConstraint)
2495 .parameter(
2496 "value",
2497 GenericType::Parameter("X"),
2498 "A file, directory, or a compound value containing files/directories, \
2499 for which to determine the size."
2500 )
2501 .parameter(
2502 "unit",
2503 PrimitiveType::String,
2504 "(Optional) The unit of storage; defaults to 'B'."
2505 )
2506 .ret(PrimitiveType::Float)
2507 .definition(SIZE_DEFINITION)
2508 .build(),
2509 ])
2510 .into(),
2511 )
2512 .is_none()
2513 );
2514
2515 assert!(
2517 functions
2518 .insert(
2519 "stdout",
2520 MonomorphicFunction::new(
2521 FunctionSignature::builder()
2522 .ret(PrimitiveType::File)
2523 .definition(
2524 r#"
2525Returns 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.
2526
2527**Parameters**: None
2528
2529**Returns**: A `File` whose contents are the stdout generated by the command of the task where the function is called.
2530
2531Example: echo_stdout.wdl
2532
2533```wdl
2534version 1.2
2535
2536task echo_stdout {
2537 command <<<
2538 printf "hello world"
2539 >>>
2540
2541 output {
2542 File message = read_string(stdout())
2543 }
2544}
2545```
2546"#
2547 )
2548 .build(),
2549 )
2550 .into(),
2551 )
2552 .is_none()
2553 );
2554
2555 assert!(
2557 functions
2558 .insert(
2559 "stderr",
2560 MonomorphicFunction::new(
2561 FunctionSignature::builder()
2562 .ret(PrimitiveType::File)
2563 .definition(
2564 r#"
2565Returns 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.
2566
2567**Parameters**: None
2568
2569**Returns**: A `File` whose contents are the stderr generated by the command of the task where the function is called.
2570
2571Example: echo_stderr.wdl
2572
2573```wdl
2574version 1.2
2575
2576task echo_stderr {
2577 command <<<
2578 >&2 printf "hello world"
2579 >>>
2580
2581 output {
2582 File message = read_string(stderr())
2583 }
2584}
2585```
2586"#
2587 )
2588 .build(),
2589 )
2590 .into(),
2591 )
2592 .is_none()
2593 );
2594
2595 assert!(
2597 functions
2598 .insert(
2599 "read_string",
2600 MonomorphicFunction::new(
2601 FunctionSignature::builder()
2602 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2603 .ret(PrimitiveType::String)
2604 .definition(
2605 r#"
2606Reads 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.
2607
2608If the file contains any internal newline characters, they are left in tact.
2609
2610**Parameters**
2611
26121. `File`: Path of the file to read.
2613
2614**Returns**: A `String`.
2615
2616Example: read_string_task.wdl
2617
2618```wdl
2619version 1.2
2620
2621task read_string {
2622 # this file will contain "this\nfile\nhas\nfive\nlines\n"
2623 File f = write_lines(["this", "file", "has", "five", "lines"])
2624
2625 command <<<
2626 cat ~{f}
2627 >>>
2628
2629 output {
2630 # s will contain "this\nfile\nhas\nfive\nlines"
2631 String s = read_string(stdout())
2632 }
2633}
2634```
2635"#
2636 )
2637 .build(),
2638 )
2639 .into(),
2640 )
2641 .is_none()
2642 );
2643
2644 assert!(
2646 functions
2647 .insert(
2648 "read_int",
2649 MonomorphicFunction::new(
2650 FunctionSignature::builder()
2651 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2652 .ret(PrimitiveType::Integer)
2653 .definition(
2654 r#"
2655Reads 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.
2656
2657**Parameters**
2658
26591. `File`: Path of the file to read.
2660
2661**Returns**: An `Int`.
2662
2663Example: read_int_task.wdl
2664
2665```wdl
2666version 1.2
2667
2668task read_int {
2669 command <<<
2670 printf " 1 \n" > int_file
2671 >>>
2672
2673 output {
2674 Int i = read_int("int_file")
2675 }
2676}
2677```
2678"#
2679 )
2680 .build(),
2681 )
2682 .into(),
2683 )
2684 .is_none()
2685 );
2686
2687 assert!(
2689 functions
2690 .insert(
2691 "read_float",
2692 MonomorphicFunction::new(
2693 FunctionSignature::builder()
2694 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2695 .ret(PrimitiveType::Float)
2696 .definition(
2697 r#"
2698Reads 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.
2699
2700**Parameters**
2701
27021. `File`: Path of the file to read.
2703
2704**Returns**: A `Float`.
2705
2706Example: read_float_task.wdl
2707
2708```wdl
2709version 1.2
2710
2711task read_float {
2712 command <<<
2713 printf " 1 \n" > int_file
2714 printf " 2.0 \n" > float_file
2715 >>>
2716
2717 output {
2718 Float f1 = read_float("int_file")
2719 Float f2 = read_float("float_file")
2720 }
2721}
2722```
2723"#
2724 )
2725 .build(),
2726 )
2727 .into(),
2728 )
2729 .is_none()
2730 );
2731
2732 assert!(
2734 functions
2735 .insert(
2736 "read_boolean",
2737 MonomorphicFunction::new(
2738 FunctionSignature::builder()
2739 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2740 .ret(PrimitiveType::Boolean)
2741 .definition(
2742 r#"
2743Reads 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.
2744
2745**Parameters**
2746
27471. `File`: Path of the file to read.
2748
2749**Returns**: A `Boolean`.
2750
2751Example: read_bool_task.wdl
2752
2753```wdl
2754version 1.2
2755
2756task read_bool {
2757 command <<<
2758 printf " true \n" > true_file
2759 printf " FALSE \n" > false_file
2760 >>>
2761
2762 output {
2763 Boolean b1 = read_boolean("true_file")
2764 Boolean b2 = read_boolean("false_file")
2765 }
2766}
2767```
2768"#
2769 )
2770 .build(),
2771 )
2772 .into(),
2773 )
2774 .is_none()
2775 );
2776
2777 assert!(
2779 functions
2780 .insert(
2781 "read_lines",
2782 MonomorphicFunction::new(
2783 FunctionSignature::builder()
2784 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2785 .ret(array_string.clone())
2786 .definition(
2787 r#"
2788Reads 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.
2789
2790The order of the lines in the returned `Array[String]` is the order in which the lines appear in the file.
2791
2792If the file is empty, an empty array is returned.
2793
2794**Parameters**
2795
27961. `File`: Path of the file to read.
2797
2798**Returns**: An `Array[String]` representation of the lines in the file.
2799
2800Example: grep_task.wdl
2801
2802```wdl
2803version 1.2
2804
2805task grep {
2806 input {
2807 String pattern
2808 File file
2809 }
2810
2811 command <<<
2812 grep '~{pattern}' ~{file}
2813 >>>
2814
2815 output {
2816 Array[String] matches = read_lines(stdout())
2817 }
2818
2819 requirements {
2820 container: "ubuntu:latest"
2821 }
2822}
2823```
2824"#
2825 )
2826 .build(),
2827 )
2828 .into(),
2829 )
2830 .is_none()
2831 );
2832
2833 assert!(
2835 functions
2836 .insert(
2837 "write_lines",
2838 MonomorphicFunction::new(
2839 FunctionSignature::builder()
2840 .parameter("array", array_string.clone(), "`Array` of strings to write.")
2841 .ret(PrimitiveType::File)
2842 .definition(
2843 r#"
2844Writes 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.
2845
2846**Parameters**
2847
28481. `Array[String]`: Array of strings to write.
2849
2850**Returns**: A `File`.
2851
2852Example: write_lines_task.wdl
2853
2854```wdl
2855version 1.2
2856
2857task write_lines {
2858 input {
2859 Array[String] array = ["first", "second", "third"]
2860 }
2861
2862 command <<<
2863 paste -s -d'\t' ~{write_lines(array)}
2864 >>>
2865
2866 output {
2867 String s = read_string(stdout())
2868 }
2869
2870 requirements {
2871 container: "ubuntu:latest"
2872 }
2873}
2874```
2875"#
2876 )
2877 .build(),
2878 )
2879 .into(),
2880 )
2881 .is_none()
2882 );
2883
2884 const READ_TSV_DEFINITION: &str = r#"
2885Reads 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.
2886
2887This function has three variants:
2888
28891. `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.
28902. `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.
28913. `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).
2892
2893If the file is empty, an empty array is returned.
2894
2895If 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.
2896
2897**Parameters**
2898
28991. `File`: The TSV file to read.
29002. `Boolean`: (Optional) Whether to treat the file's first line as a header.
29013. `Array[String]`: (Optional) An array of field names. If specified, then the second parameter is also required.
2902
2903**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.
2904
2905Example: read_tsv_task.wdl
2906
2907```wdl
2908version 1.2
2909
2910task read_tsv {
2911 command <<<
2912 {
2913 printf "row1\tvalue1\n"
2914 printf "row2\tvalue2\n"
2915 printf "row3\tvalue3\n"
2916 } >> data.no_headers.tsv
2917
2918 {
2919 printf "header1\theader2\n"
2920 printf "row1\tvalue1\n"
2921 printf "row2\tvalue2\n"
2922 printf "row3\tvalue3\n"
2923 } >> data.headers.tsv
2924 >>>
2925
2926 output {
2927 Array[Array[String]] output_table = read_tsv("data.no_headers.tsv")
2928 Array[Object] output_objs1 = read_tsv("data.no_headers.tsv", false, ["name", "value"])
2929 Array[Object] output_objs2 = read_tsv("data.headers.tsv", true)
2930 Array[Object] output_objs3 = read_tsv("data.headers.tsv", true, ["name", "value"])
2931 }
2932}
2933```
2934"#;
2935
2936 assert!(
2938 functions
2939 .insert(
2940 "read_tsv",
2941 PolymorphicFunction::new(vec![
2942 FunctionSignature::builder()
2943 .parameter("file", PrimitiveType::File, "The TSV file to read.")
2944 .ret(array_array_string.clone())
2945 .definition(READ_TSV_DEFINITION)
2946 .build(),
2947 FunctionSignature::builder()
2948 .min_version(SupportedVersion::V1(V1::Two))
2949 .parameter("file", PrimitiveType::File, "The TSV file to read.")
2950 .parameter(
2951 "header",
2952 PrimitiveType::Boolean,
2953 "(Optional) Whether to treat the file's first line as a header.",
2954 )
2955 .ret(array_object.clone())
2956 .definition(READ_TSV_DEFINITION)
2957 .build(),
2958 FunctionSignature::builder()
2959 .min_version(SupportedVersion::V1(V1::Two))
2960 .parameter("file", PrimitiveType::File, "The TSV file to read.")
2961 .parameter(
2962 "header",
2963 PrimitiveType::Boolean,
2964 "(Optional) Whether to treat the file's first line as a header.",
2965 )
2966 .parameter(
2967 "columns",
2968 array_string.clone(),
2969 "(Optional) An array of field names. If specified, then the second \
2970 parameter is also required.",
2971 )
2972 .ret(array_object.clone())
2973 .definition(READ_TSV_DEFINITION)
2974 .build(),
2975 ])
2976 .into(),
2977 )
2978 .is_none()
2979 );
2980
2981 const WRITE_TSV_DEFINITION: &str = r#"
2982Given an `Array` of elements, writes a tab-separated value (TSV) file with one line for each element.
2983
2984There are three variants of this function:
2985
29861. `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.
2987
29882. `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.
2989
29903. `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.
2991
2992Each line is terminated by the newline (`\n`) character.
2993
2994The 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.
2995
2996If 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.
2997
2998
2999**Parameters**
3000
30011. `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.
30022. `Boolean`: (Optional) Whether to write a header row.
30033. `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`.
3004
3005
3006**Returns**: A `File`.
3007
3008Example: write_tsv_task.wdl
3009
3010```wdl
3011version 1.2
3012
3013task write_tsv {
3014 input {
3015 Array[Array[String]] array = [["one", "two", "three"], ["un", "deux", "trois"]]
3016 Array[Numbers] structs = [
3017 Numbers {
3018 first: "one",
3019 second: "two",
3020 third: "three"
3021 },
3022 Numbers {
3023 first: "un",
3024 second: "deux",
3025 third: "trois"
3026 }
3027 ]
3028 }
3029
3030 command <<<
3031 cut -f 1 ~{write_tsv(array)} >> array_no_header.txt
3032 cut -f 1 ~{write_tsv(array, true, ["first", "second", "third"])} > array_header.txt
3033 cut -f 1 ~{write_tsv(structs)} >> structs_default.txt
3034 cut -f 2 ~{write_tsv(structs, false)} >> structs_no_header.txt
3035 cut -f 2 ~{write_tsv(structs, true)} >> structs_header.txt
3036 cut -f 3 ~{write_tsv(structs, true, ["no1", "no2", "no3"])} >> structs_user_header.txt
3037 >>>
3038
3039 output {
3040 Array[String] array_no_header = read_lines("array_no_header.txt")
3041 Array[String] array_header = read_lines("array_header.txt")
3042 Array[String] structs_default = read_lines("structs_default.txt")
3043 Array[String] structs_no_header = read_lines("structs_no_header.txt")
3044 Array[String] structs_header = read_lines("structs_header.txt")
3045 Array[String] structs_user_header = read_lines("structs_user_header.txt")
3046
3047 }
3048
3049 requirements {
3050 container: "ubuntu:latest"
3051 }
3052}
3053```
3054"#;
3055
3056 assert!(
3058 functions
3059 .insert(
3060 "write_tsv",
3061 PolymorphicFunction::new(vec![
3062 FunctionSignature::builder()
3063 .parameter(
3064 "data",
3065 array_array_string.clone(),
3066 "An array of rows, where each row is either an `Array` of column \
3067 values or a struct whose values are the column values.",
3068 )
3069 .ret(PrimitiveType::File)
3070 .definition(WRITE_TSV_DEFINITION)
3071 .build(),
3072 FunctionSignature::builder()
3073 .min_version(SupportedVersion::V1(V1::Two))
3074 .parameter(
3075 "data",
3076 array_array_string.clone(),
3077 "An array of rows, where each row is either an `Array` of column \
3078 values or a struct whose values are the column values.",
3079 )
3080 .parameter(
3081 "header",
3082 PrimitiveType::Boolean,
3083 "(Optional) Whether to write a header row.",
3084 )
3085 .parameter(
3086 "columns",
3087 array_string.clone(),
3088 "An array of column names. If the first argument is \
3089 `Array[Array[String]]` and the second argument is true then it is \
3090 required, otherwise it is optional. Ignored if the second argument \
3091 is false."
3092 )
3093 .ret(PrimitiveType::File)
3094 .definition(WRITE_TSV_DEFINITION)
3095 .build(),
3096 FunctionSignature::builder()
3097 .min_version(SupportedVersion::V1(V1::Two))
3098 .type_parameter("S", PrimitiveStructConstraint)
3099 .required(1)
3100 .parameter(
3101 "data",
3102 GenericArrayType::new(GenericType::Parameter("S")),
3103 "An array of rows, where each row is either an `Array` of column \
3104 values or a struct whose values are the column values.",
3105 )
3106 .parameter(
3107 "header",
3108 PrimitiveType::Boolean,
3109 "(Optional) Whether to write a header row.",
3110 )
3111 .parameter(
3112 "columns",
3113 array_string.clone(),
3114 "An array of column names. If the first argument is \
3115 `Array[Array[String]]` and the second argument is true then it is \
3116 required, otherwise it is optional. Ignored if the second argument \
3117 is false."
3118 )
3119 .ret(PrimitiveType::File)
3120 .definition(WRITE_TSV_DEFINITION)
3121 .build(),
3122 ])
3123 .into(),
3124 )
3125 .is_none()
3126 );
3127
3128 assert!(
3130 functions
3131 .insert(
3132 "read_map",
3133 MonomorphicFunction::new(
3134 FunctionSignature::builder()
3135 .parameter(
3136 "file",
3137 PrimitiveType::File,
3138 "Path of the two-column TSV file to read.",
3139 )
3140 .ret(map_string_string.clone())
3141 .definition(
3142 r#"
3143Reads 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.
3144
3145Each 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.
3146
3147If the file is empty, an empty map is returned.
3148
3149**Parameters**
3150
31511. `File`: Path of the two-column TSV file to read.
3152
3153**Returns**: A `Map[String, String]`, with one element for each row in the TSV file.
3154
3155Example: read_map_task.wdl
3156
3157```wdl
3158version 1.2
3159
3160task read_map {
3161 command <<<
3162 printf "key1\tvalue1\n" >> map_file
3163 printf "key2\tvalue2\n" >> map_file
3164 >>>
3165
3166 output {
3167 Map[String, String] mapping = read_map(stdout())
3168 }
3169}
3170```
3171"#
3172 )
3173 .build(),
3174 )
3175 .into(),
3176 )
3177 .is_none()
3178 );
3179
3180 assert!(
3182 functions
3183 .insert(
3184 "write_map",
3185 MonomorphicFunction::new(
3186 FunctionSignature::builder()
3187 .parameter(
3188 "map",
3189 map_string_string.clone(),
3190 "A `Map`, where each element will be a row in the generated file.",
3191 )
3192 .ret(PrimitiveType::File)
3193 .definition(
3194 r#"
3195Writes 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.
3196
3197Since `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`.
3198
3199**Parameters**
3200
32011. `Map[String, String]`: A `Map`, where each element will be a row in the generated file.
3202
3203**Returns**: A `File`.
3204
3205Example: write_map_task.wdl
3206
3207```wdl
3208version 1.2
3209
3210task write_map {
3211 input {
3212 Map[String, String] map = {"key1": "value1", "key2": "value2"}
3213 }
3214
3215 command <<<
3216 cut -f 1 ~{write_map(map)}
3217 >>>
3218
3219 output {
3220 Array[String] keys = read_lines(stdout())
3221 }
3222
3223 requirements {
3224 container: "ubuntu:latest"
3225 }
3226}
3227```
3228"#
3229 )
3230 .build(),
3231 )
3232 .into(),
3233 )
3234 .is_none()
3235 );
3236
3237 assert!(
3239 functions
3240 .insert(
3241 "read_json",
3242 MonomorphicFunction::new(
3243 FunctionSignature::builder()
3244 .parameter("file", PrimitiveType::File, "Path of the JSON file to read.")
3245 .ret(Type::Union)
3246 .definition(
3247 r#"
3248Reads a JSON file into a WDL value whose type depends on the file's contents. The mapping of JSON type to WDL type is:
3249
3250| JSON Type | WDL Type |
3251| --------- | ---------------- |
3252| object | `Object` |
3253| array | `Array[X]` |
3254| number | `Int` or `Float` |
3255| string | `String` |
3256| boolean | `Boolean` |
3257| null | `None` |
3258
3259The 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.
3260
3261If 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.
3262
3263The `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.
3264
3265Note that an empty file is not valid according to the JSON specification, and so calling `read_json` on an empty file raises an error.
3266
3267**Parameters**
3268
32691. `File`: Path of the JSON file to read.
3270
3271**Returns**: A value whose type is dependent on the contents of the JSON file.
3272
3273Example: read_person.wdl
3274
3275```wdl
3276version 1.2
3277
3278struct Person {
3279 String name
3280 Int age
3281}
3282
3283workflow read_person {
3284 input {
3285 File json_file
3286 }
3287
3288 output {
3289 Person p = read_json(json_file)
3290 }
3291}
3292```
3293"#
3294 )
3295 .build(),
3296 )
3297 .into(),
3298 )
3299 .is_none()
3300 );
3301
3302 assert!(
3304 functions
3305 .insert(
3306 "write_json",
3307 MonomorphicFunction::new(
3308 FunctionSignature::builder()
3309 .type_parameter("X", JsonSerializableConstraint)
3310 .parameter(
3311 "value",
3312 GenericType::Parameter("X"),
3313 "A WDL value of a supported type.",
3314 )
3315 .ret(PrimitiveType::File)
3316 .definition(
3317 r#"
3318Writes a JSON file with the serialized form of a WDL value. The following WDL types can be serialized:
3319
3320| WDL Type | JSON Type |
3321| ---------------- | --------- |
3322| `Struct` | object |
3323| `Object` | object |
3324| `Map[String, X]` | object |
3325| `Array[X]` | array |
3326| `Int` | number |
3327| `Float` | number |
3328| `String` | string |
3329| `File` | string |
3330| `Boolean` | boolean |
3331| `None` | null |
3332
3333When serializing compound types, all nested types must be serializable or an error is raised.
3334
3335**Parameters**
3336
33371. `X`: A WDL value of a supported type.
3338
3339**Returns**: A `File`.
3340
3341Example: write_json_fail.wdl
3342
3343```wdl
3344version 1.2
3345
3346workflow write_json_fail {
3347 Pair[Int, Map[Int, String]] x = (1, {2: "hello"})
3348 # this fails with an error - Map with Int keys is not serializable
3349 File f = write_json(x)
3350}
3351```
3352"#
3353 )
3354 .build(),
3355 )
3356 .into(),
3357 )
3358 .is_none()
3359 );
3360
3361 assert!(
3363 functions
3364 .insert(
3365 "read_object",
3366 MonomorphicFunction::new(
3367 FunctionSignature::builder()
3368 .parameter(
3369 "file",
3370 PrimitiveType::File,
3371 "Path of the two-row TSV file to read.",
3372 )
3373 .ret(Type::Object)
3374 .definition(
3375 r#"
3376Reads 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.
3377
3378The 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.
3379
3380The 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`.
3381
3382**Parameters**
3383
33841. `File`: Path of the two-row TSV file to read.
3385
3386**Returns**: An `Object`, with as many members as there are unique names in the TSV.
3387
3388Example: read_object_task.wdl
3389
3390```wdl
3391version 1.2
3392
3393task read_object {
3394 command <<<
3395 python <<CODE
3396 print('\t'.join(["key_{}".format(i) for i in range(3)]))
3397 print('\t'.join(["value_{}".format(i) for i in range(3)]))
3398 CODE
3399 >>>
3400
3401 output {
3402 Object my_obj = read_object(stdout())
3403 }
3404
3405 requirements {
3406 container: "python:latest"
3407 }
3408}
3409```
3410"#
3411 )
3412 .build(),
3413 )
3414 .into(),
3415 )
3416 .is_none()
3417 );
3418
3419 assert!(
3421 functions
3422 .insert(
3423 "read_objects",
3424 MonomorphicFunction::new(
3425 FunctionSignature::builder()
3426 .parameter("file", PrimitiveType::File, "The file to read.")
3427 .ret(array_object.clone())
3428 .definition(
3429 r#"
3430Reads 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.
3431
3432The 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.
3433
3434There 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`.
3435
3436If the file is empty or contains only a header line, an empty array is returned.
3437
3438**Parameters**
3439
34401. `File`: Path of the TSV file to read.
3441
3442**Returns**: An `Array[Object]`, with `N-1` elements, where `N` is the number of rows in the file.
3443
3444Example: read_objects_task.wdl
3445
3446```wdl
3447version 1.2
3448
3449task read_objects {
3450 command <<<
3451 python <<CODE
3452 print('\t'.join(["key_{}".format(i) for i in range(3)]))
3453 print('\t'.join(["value_A{}".format(i) for i in range(3)]))
3454 print('\t'.join(["value_B{}".format(i) for i in range(3)]))
3455 print('\t'.join(["value_C{}".format(i) for i in range(3)]))
3456 CODE
3457 >>>
3458
3459 output {
3460 Array[Object] my_obj = read_objects(stdout())
3461 }
3462"#
3463 )
3464 .build(),
3465 )
3466 .into(),
3467 )
3468 .is_none()
3469 );
3470
3471 const WRITE_OBJECT_DEFINITION: &str = r#"
3472Writes 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.
3473
3474Each line is terminated by the newline (`\n`) character.
3475
3476The 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.
3477
3478If 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.
3479
3480**Parameters**
3481
34821. `Object`: An `Object` whose members will be written to the file.
3483
3484**Returns**: A `File`.
3485
3486Example: write_object_task.wdl
3487
3488```wdl
3489version 1.2
3490
3491task write_object {
3492 input {
3493 Object my_obj = {"key_0": "value_A0", "key_1": "value_A1", "key_2": "value_A2"}
3494 }
3495
3496 command <<<
3497 cat ~{write_object(my_obj)}
3498 >>>
3499
3500 output {
3501 Object new_obj = read_object(stdout())
3502 }
3503}
3504```
3505"#;
3506
3507 assert!(
3509 functions
3510 .insert(
3511 "write_object",
3512 PolymorphicFunction::new(vec![
3513 FunctionSignature::builder()
3514 .parameter("object", Type::Object, "An object to write.")
3515 .ret(PrimitiveType::File)
3516 .definition(WRITE_OBJECT_DEFINITION)
3517 .build(),
3518 FunctionSignature::builder()
3519 .min_version(SupportedVersion::V1(V1::One))
3520 .type_parameter("S", PrimitiveStructConstraint)
3521 .parameter("object", GenericType::Parameter("S"), "An object to write.")
3522 .ret(PrimitiveType::File)
3523 .definition(WRITE_OBJECT_DEFINITION)
3524 .build(),
3525 ])
3526 .into(),
3527 )
3528 .is_none()
3529 );
3530
3531 const WRITE_OBJECTS_DEFINITION: &str = r#"
3532Writes 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.
3533
3534Each line is terminated by the newline (`\n`) character.
3535
3536The 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.
3537
3538If 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.
3539
3540**Parameters**
3541
35421. `Array[Object]`: An `Array[Object]` whose elements will be written to the file.
3543
3544**Returns**: A `File`.
3545
3546Example: write_objects_task.wdl
3547
3548```wdl
3549version 1.2
3550
3551task write_objects {
3552 input {
3553 Array[Object] my_objs = [
3554 {"key_0": "value_A0", "key_1": "value_A1", "key_2": "value_A2"},
3555 {"key_0": "value_B0", "key_1": "value_B1", "key_2": "value_B2"},
3556 {"key_0": "value_C0", "key_1": "value_C1", "key_2": "value_C2"}
3557 ]
3558 }
3559
3560 command <<<
3561 cat ~{write_objects(my_objs)}
3562 >>>
3563
3564 output {
3565 Array[Object] new_objs = read_objects(stdout())
3566 }
3567}
3568```
3569"#;
3570
3571 assert!(
3573 functions
3574 .insert(
3575 "write_objects",
3576 PolymorphicFunction::new(vec![
3577 FunctionSignature::builder()
3578 .parameter("objects", array_object.clone(), "The objects to write.")
3579 .ret(PrimitiveType::File)
3580 .definition(WRITE_OBJECTS_DEFINITION)
3581 .build(),
3582 FunctionSignature::builder()
3583 .min_version(SupportedVersion::V1(V1::One))
3584 .type_parameter("S", PrimitiveStructConstraint)
3585 .parameter(
3586 "objects",
3587 GenericArrayType::new(GenericType::Parameter("S")),
3588 "The objects to write."
3589 )
3590 .ret(PrimitiveType::File)
3591 .definition(WRITE_OBJECTS_DEFINITION)
3592 .build(),
3593 ])
3594 .into(),
3595 )
3596 .is_none()
3597 );
3598
3599 assert!(
3601 functions
3602 .insert(
3603 "prefix",
3604 MonomorphicFunction::new(
3605 FunctionSignature::builder()
3606 .type_parameter("P", PrimitiveTypeConstraint)
3607 .parameter(
3608 "prefix",
3609 PrimitiveType::String,
3610 "The prefix to prepend to each element in the array.",
3611 )
3612 .parameter(
3613 "array",
3614 GenericArrayType::new(GenericType::Parameter("P")),
3615 "Array with a primitive element type.",
3616 )
3617 .ret(array_string.clone())
3618 .definition(
3619 r#"
3620Given 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.
3621
3622**Parameters**
3623
36241. `String`: The string to prepend.
36252. `Array[X]`: The array whose elements will be prepended.
3626
3627**Returns**: A new `Array[String]` with the prepended elements.
3628
3629Example: prefix_task.wdl
3630
3631```wdl
3632version 1.2
3633
3634task prefix {
3635 input {
3636 Array[Int] ints = [1, 2, 3]
3637 }
3638
3639 output {
3640 Array[String] prefixed_ints = prefix("file_", ints) # ["file_1", "file_2", "file_3"]
3641 }
3642}
3643```
3644"#
3645 )
3646 .build(),
3647 )
3648 .into(),
3649 )
3650 .is_none()
3651 );
3652
3653 assert!(
3655 functions
3656 .insert(
3657 "suffix",
3658 MonomorphicFunction::new(
3659 FunctionSignature::builder()
3660 .min_version(SupportedVersion::V1(V1::One))
3661 .type_parameter("P", PrimitiveTypeConstraint)
3662 .parameter(
3663 "suffix",
3664 PrimitiveType::String,
3665 "The suffix to append to each element in the array.",
3666 )
3667 .parameter(
3668 "array",
3669 GenericArrayType::new(GenericType::Parameter("P")),
3670 "Array with a primitive element type.",
3671 )
3672 .ret(array_string.clone())
3673 .definition(
3674 r#"
3675Given 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.
3676
3677**Parameters**
3678
36791. `String`: The string to append.
36802. `Array[X]`: The array whose elements will be appended.
3681
3682**Returns**: A new `Array[String]` with the appended elements.
3683
3684Example: suffix_task.wdl
3685
3686```wdl
3687version 1.2
3688
3689task suffix {
3690 input {
3691 Array[Int] ints = [1, 2, 3]
3692 }
3693
3694 output {
3695 Array[String] suffixed_ints = suffix(".txt", ints) # ["1.txt", "2.txt", "3.txt"]
3696 }
3697}
3698```
3699"#
3700 )
3701 .build(),
3702 )
3703 .into(),
3704 )
3705 .is_none()
3706 );
3707
3708 assert!(
3710 functions
3711 .insert(
3712 "quote",
3713 MonomorphicFunction::new(
3714 FunctionSignature::builder()
3715 .min_version(SupportedVersion::V1(V1::One))
3716 .type_parameter("P", PrimitiveTypeConstraint)
3717 .parameter(
3718 "array",
3719 GenericArrayType::new(GenericType::Parameter("P")),
3720 "Array with a primitive element type.",
3721 )
3722 .ret(array_string.clone())
3723 .definition(
3724 r#"
3725Given 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.
3726
3727**Parameters**
3728
37291. `Array[X]`: The array whose elements will be quoted.
3730
3731**Returns**: A new `Array[String]` with the quoted elements.
3732
3733Example: quote_task.wdl
3734
3735```wdl
3736version 1.2
3737
3738task quote {
3739 input {
3740 Array[String] strings = ["hello", "world"]
3741 }
3742
3743 output {
3744 Array[String] quoted_strings = quote(strings) # ["\"hello\"", "\"world\""]
3745 }
3746}
3747```
3748"#
3749 )
3750 .build(),
3751 )
3752 .into(),
3753 )
3754 .is_none()
3755 );
3756
3757 assert!(
3759 functions
3760 .insert(
3761 "squote",
3762 MonomorphicFunction::new(
3763 FunctionSignature::builder()
3764 .min_version(SupportedVersion::V1(V1::One))
3765 .type_parameter("P", PrimitiveTypeConstraint)
3766 .parameter("array", GenericArrayType::new(GenericType::Parameter("P")), "The array of values.") .ret(array_string.clone())
3767 .definition(
3768 r#"
3769Given 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.
3770
3771**Parameters**
3772
37731. `Array[X]`: The array whose elements will be single-quoted.
3774
3775**Returns**: A new `Array[String]` with the single-quoted elements.
3776
3777Example: squote_task.wdl
3778
3779```wdl
3780version 1.2
3781
3782task squote {
3783 input {
3784 Array[String] strings = ["hello", "world"]
3785 }
3786
3787 output {
3788 Array[String] squoted_strings = squote(strings) # ["'hello'", "'world'"]
3789 }
3790}
3791```
3792"#
3793 )
3794 .build(),
3795 )
3796 .into(),
3797 )
3798 .is_none()
3799 );
3800
3801 assert!(
3803 functions
3804 .insert(
3805 "sep",
3806 MonomorphicFunction::new(
3807 FunctionSignature::builder()
3808 .min_version(SupportedVersion::V1(V1::One))
3809 .type_parameter("P", PrimitiveTypeConstraint)
3810 .parameter("separator", PrimitiveType::String, "Separator string.")
3811 .parameter(
3812 "array",
3813 GenericArrayType::new(GenericType::Parameter("P")),
3814 "`Array` of strings to concatenate.",
3815 )
3816 .ret(PrimitiveType::String)
3817 .definition(
3818 r#"
3819Given 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.
3820
3821**Parameters**
3822
38231. `String`: The string to use as a separator.
38242. `Array[X]`: The array whose elements will be joined.
3825
3826**Returns**: A new `String` with the joined elements.
3827
3828Example: sep_task.wdl
3829
3830```wdl
3831version 1.2
3832
3833task sep {
3834 input {
3835 Array[Int] ints = [1, 2, 3]
3836 }
3837
3838 output {
3839 String joined_ints = sep(",", ints) # "1,2,3"
3840 }
3841}
3842```
3843"#
3844 )
3845 .build(),
3846 )
3847 .into(),
3848 )
3849 .is_none()
3850 );
3851
3852 assert!(
3854 functions
3855 .insert(
3856 "range",
3857 MonomorphicFunction::new(
3858 FunctionSignature::builder()
3859 .parameter("n", PrimitiveType::Integer, "The length of array to create.")
3860 .ret(array_int.clone())
3861 .definition(
3862 r#"
3863Returns 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.
3864
3865**Parameters**
3866
38671. `Int`: The upper bound (exclusive) of the range.
3868
3869**Returns**: An `Array[Int]` of integers.
3870
3871Example: range_task.wdl
3872
3873```wdl
3874version 1.2
3875
3876task range {
3877 input {
3878 Int n = 5
3879 }
3880
3881 output {
3882 Array[Int] r = range(n) # [0, 1, 2, 3, 4]
3883 }
3884}
3885```
3886"#
3887 )
3888 .build(),
3889 )
3890 .into(),
3891 )
3892 .is_none()
3893 );
3894
3895 assert!(
3897 functions
3898 .insert(
3899 "transpose",
3900 MonomorphicFunction::new(
3901 FunctionSignature::builder()
3902 .any_type_parameter("X")
3903 .parameter(
3904 "array",
3905 GenericArrayType::new(GenericArrayType::new(
3906 GenericType::Parameter("X"),
3907 )),
3908 "A M*N two-dimensional array.",
3909 )
3910 .ret(GenericArrayType::new(GenericArrayType::new(
3911 GenericType::Parameter("X"),
3912 )))
3913 .definition(
3914 r#"
3915Given 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.
3916
3917If the inner arrays are not all the same length, an error is raised.
3918
3919**Parameters**
3920
39211. `Array[Array[X]]`: The array to transpose.
3922
3923**Returns**: A new `Array[Array[X]]` with the rows and columns swapped.
3924
3925Example: transpose_task.wdl
3926
3927```wdl
3928version 1.2
3929
3930task transpose {
3931 input {
3932 Array[Array[Int]] matrix = [[1, 2, 3], [4, 5, 6]]
3933 }
3934
3935 output {
3936 Array[Array[Int]] transposed_matrix = transpose(matrix) # [[1, 4], [2, 5], [3, 6]]
3937 }
3938}
3939```
3940"#
3941 )
3942 .build(),
3943 )
3944 .into(),
3945 )
3946 .is_none()
3947 );
3948
3949 assert!(
3951 functions
3952 .insert(
3953 "cross",
3954 MonomorphicFunction::new(
3955 FunctionSignature::builder()
3956 .any_type_parameter("X")
3957 .any_type_parameter("Y")
3958 .parameter("a", GenericArrayType::new(GenericType::Parameter("X")), "The first array of length M.")
3959 .parameter("b", GenericArrayType::new(GenericType::Parameter("Y")), "The second array of length N.")
3960 .ret(GenericArrayType::new(GenericPairType::new(
3961 GenericType::Parameter("X"),
3962 GenericType::Parameter("Y"),
3963 )))
3964 .definition(
3965 r#"
3966Given 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.
3967
3968If either `a` or `b` is empty, an empty array is returned.
3969
3970**Parameters**
3971
39721. `Array[X]`: The first array.
39732. `Array[Y]`: The second array.
3974
3975**Returns**: A new `Array[Pair[X, Y]]` with the cross product of the two arrays.
3976
3977Example: cross_task.wdl
3978
3979```wdl
3980version 1.2
3981
3982task cross {
3983 input {
3984 Array[Int] ints = [1, 2]
3985 Array[String] strings = ["a", "b"]
3986 }
3987
3988 output {
3989 Array[Pair[Int, String]] crossed = cross(ints, strings) # [(1, "a"), (1, "b"), (2, "a"), (2, "b")]
3990 }
3991}
3992```
3993"#
3994 )
3995 .build(),
3996 )
3997 .into(),
3998 )
3999 .is_none()
4000 );
4001
4002 assert!(
4004 functions
4005 .insert(
4006 "zip",
4007 MonomorphicFunction::new(
4008 FunctionSignature::builder()
4009 .any_type_parameter("X")
4010 .any_type_parameter("Y")
4011 .parameter("a", GenericArrayType::new(GenericType::Parameter("X")), "The first array of length M.")
4012 .parameter("b", GenericArrayType::new(GenericType::Parameter("Y")), "The second array of length N.")
4013 .ret(GenericArrayType::new(GenericPairType::new(
4014 GenericType::Parameter("X"),
4015 GenericType::Parameter("Y"),
4016 )))
4017 .definition(
4018 r#"
4019Given 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`.
4020
4021If either `a` or `b` is empty, an empty array is returned.
4022
4023**Parameters**
4024
40251. `Array[X]`: The first array.
40262. `Array[Y]`: The second array.
4027
4028**Returns**: A new `Array[Pair[X, Y]]` with the zipped elements.
4029
4030Example: zip_task.wdl
4031
4032```wdl
4033version 1.2
4034
4035task zip {
4036 input {
4037 Array[Int] ints = [1, 2, 3]
4038 Array[String] strings = ["a", "b"]
4039 }
4040
4041 output {
4042 Array[Pair[Int, String]] zipped = zip(ints, strings) # [(1, "a"), (2, "b")]
4043 }
4044}
4045```
4046"#
4047 )
4048 .build(),
4049 )
4050 .into(),
4051 )
4052 .is_none()
4053 );
4054
4055 assert!(
4057 functions
4058 .insert(
4059 "unzip",
4060 MonomorphicFunction::new(
4061 FunctionSignature::builder()
4062 .min_version(SupportedVersion::V1(V1::One))
4063 .any_type_parameter("X")
4064 .any_type_parameter("Y")
4065 .parameter(
4066 "array",
4067 GenericArrayType::new(GenericPairType::new(
4068 GenericType::Parameter("X"),
4069 GenericType::Parameter("Y"),
4070 )),
4071 "The `Array` of `Pairs` of length N to unzip.",
4072 )
4073 .ret(GenericPairType::new(
4074 GenericArrayType::new(GenericType::Parameter("X")),
4075 GenericArrayType::new(GenericType::Parameter("Y")),
4076 ))
4077 .definition(
4078 r#"
4079Given 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`.
4080
4081If `a` is empty, a `Pair` of two empty arrays is returned.
4082
4083**Parameters**
4084
40851. `Array[Pair[X, Y]]`: The array of pairs to unzip.
4086
4087**Returns**: A new `Pair[Array[X], Array[Y]]` with the unzipped elements.
4088
4089Example: unzip_task.wdl
4090
4091```wdl
4092version 1.2
4093
4094task unzip {
4095 input {
4096 Array[Pair[Int, String]] zipped = [(1, "a"), (2, "b")]
4097 }
4098
4099 output {
4100 Pair[Array[Int], Array[String]] unzipped = unzip(zipped) # ([1, 2], ["a", "b"])
4101 }
4102}
4103```
4104"#
4105 )
4106 .build(),
4107 )
4108 .into(),
4109 )
4110 .is_none()
4111 );
4112
4113 assert!(
4115 functions
4116 .insert(
4117 "contains",
4118 MonomorphicFunction::new(
4119 FunctionSignature::builder()
4120 .min_version(SupportedVersion::V1(V1::Two))
4121 .type_parameter("P", PrimitiveTypeConstraint)
4122 .parameter(
4123 "array",
4124 GenericArrayType::new(GenericType::Parameter("P")),
4125 "An array of any primitive type.",
4126 )
4127 .parameter(
4128 "value",
4129 GenericType::Parameter("P"),
4130 "A primitive value of the same type as the array. If the array's \
4131 type is optional, then the value may also be optional.",
4132 )
4133 .ret(PrimitiveType::Boolean)
4134 .definition(
4135 r#"
4136Given an `Array[X]` `a` and a value `v` of type `X`, returns `true` if `v` is present in `a`, otherwise `false`.
4137
4138**Parameters**
4139
41401. `Array[X]`: The array to search.
41412. `X`: The value to search for.
4142
4143**Returns**: `true` if `v` is present in `a`, otherwise `false`.
4144
4145Example: contains_task.wdl
4146
4147```wdl
4148version 1.2
4149
4150task contains {
4151 input {
4152 Array[Int] ints = [1, 2, 3]
4153 }
4154
4155 output {
4156 Boolean contains_2 = contains(ints, 2) # true
4157 Boolean contains_4 = contains(ints, 4) # false
4158 }
4159}
4160```
4161"#
4162 )
4163 .build(),
4164 )
4165 .into(),
4166 )
4167 .is_none()
4168 );
4169
4170 assert!(
4172 functions
4173 .insert(
4174 "chunk",
4175 MonomorphicFunction::new(
4176 FunctionSignature::builder()
4177 .min_version(SupportedVersion::V1(V1::Two))
4178 .any_type_parameter("X")
4179 .parameter(
4180 "array",
4181 GenericArrayType::new(GenericType::Parameter("X")),
4182 "The array to split. May be empty.",
4183 )
4184 .parameter("size", PrimitiveType::Integer, "The desired length of the sub-arrays. Must be > 0.")
4185 .ret(GenericArrayType::new(GenericArrayType::new(
4186 GenericType::Parameter("X"),
4187 )))
4188 .definition(
4189 r#"
4190Given 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.
4191
4192If `size` is less than or equal to `0`, an error is raised.
4193
4194**Parameters**
4195
41961. `Array[X]`: The array to chunk.
41972. `Int`: The maximum size of each chunk.
4198
4199**Returns**: A new `Array[Array[X]]` with the chunked elements.
4200
4201Example: chunk_task.wdl
4202
4203```wdl
4204version 1.2
4205
4206task chunk {
4207 input {
4208 Array[Int] ints = [1, 2, 3, 4, 5]
4209 }
4210
4211 output {
4212 Array[Array[Int]] chunked = chunk(ints, 2) # [[1, 2], [3, 4], [5]]
4213 }
4214}
4215```
4216"#
4217 )
4218 .build(),
4219 )
4220 .into(),
4221 )
4222 .is_none()
4223 );
4224
4225 assert!(
4227 functions
4228 .insert(
4229 "flatten",
4230 MonomorphicFunction::new(
4231 FunctionSignature::builder()
4232 .any_type_parameter("X")
4233 .parameter(
4234 "array",
4235 GenericArrayType::new(GenericArrayType::new(
4236 GenericType::Parameter("X"),
4237 )),
4238 "A nested array to flatten.",
4239 )
4240 .ret(GenericArrayType::new(GenericType::Parameter("X")))
4241 .definition(
4242 r#"
4243Given 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.
4244
4245**Parameters**
4246
42471. `Array[Array[X]]`: The array to flatten.
4248
4249**Returns**: A new `Array[X]` with the flattened elements.
4250
4251Example: flatten_task.wdl
4252
4253```wdl
4254version 1.2
4255
4256task flatten {
4257 input {
4258 Array[Array[Int]] nested_ints = [[1, 2], [3, 4], [5]]
4259 }
4260
4261 output {
4262 Array[Int] flattened_ints = flatten(nested_ints) # [1, 2, 3, 4, 5]
4263 }
4264}
4265```
4266"#
4267 )
4268 .build(),
4269 )
4270 .into(),
4271 )
4272 .is_none()
4273 );
4274
4275 assert!(
4277 functions
4278 .insert(
4279 "select_first",
4280 MonomorphicFunction::new(
4283 FunctionSignature::builder()
4284 .any_type_parameter("X")
4285 .required(1)
4286 .parameter(
4287 "array",
4288 GenericArrayType::new(GenericType::Parameter("X")),
4289 "Non-empty `Array` of optional values.",
4290 )
4291 .parameter("default", GenericType::UnqualifiedParameter("X"), "(Optional) The default value.")
4292 .ret(GenericType::UnqualifiedParameter("X"))
4293 .definition(
4294 r#"
4295Given an `Array[X?]` `a`, returns the first non-`None` element in `a`. If all elements are `None`, an error is raised.
4296
4297**Parameters**
4298
42991. `Array[X?]`: The array to search.
4300
4301**Returns**: The first non-`None` element.
4302
4303Example: select_first_task.wdl
4304
4305```wdl
4306version 1.2
4307
4308task select_first {
4309 input {
4310 Array[Int?] ints = [None, 1, None, 2]
4311 }
4312
4313 output {
4314 Int first_int = select_first(ints) # 1
4315 }
4316}
4317```
4318"#
4319 )
4320 .build(),
4321 )
4322 .into(),
4323 )
4324 .is_none()
4325 );
4326
4327 assert!(
4329 functions
4330 .insert(
4331 "select_all",
4332 MonomorphicFunction::new(
4333 FunctionSignature::builder()
4334 .any_type_parameter("X")
4335 .parameter(
4336 "array",
4337 GenericArrayType::new(GenericType::Parameter("X")),
4338 "`Array` of optional values.",
4339 )
4340 .ret(GenericArrayType::new(GenericType::UnqualifiedParameter(
4341 "X"
4342 )))
4343 .definition(
4344 r#"
4345Given 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.
4346
4347**Parameters**
4348
43491. `Array[X?]`: The array to filter.
4350
4351**Returns**: A new `Array[X]` with all the non-`None` elements.
4352
4353Example: select_all_task.wdl
4354
4355```wdl
4356version 1.2
4357
4358task select_all {
4359 input {
4360 Array[Int?] ints = [None, 1, None, 2]
4361 }
4362
4363 output {
4364 Array[Int] all_ints = select_all(ints) # [1, 2]
4365 }
4366}
4367```
4368"#
4369 )
4370 .build(),
4371 )
4372 .into(),
4373 )
4374 .is_none()
4375 );
4376
4377 assert!(
4379 functions
4380 .insert(
4381 "as_pairs",
4382 MonomorphicFunction::new(
4383 FunctionSignature::builder()
4384 .min_version(SupportedVersion::V1(V1::One))
4385 .type_parameter("K", PrimitiveTypeConstraint)
4386 .any_type_parameter("V")
4387 .parameter(
4388 "map",
4389 GenericMapType::new(
4390 GenericType::Parameter("K"),
4391 GenericType::Parameter("V"),
4392 ),
4393 "`Map` to convert to `Pairs`.",
4394 )
4395 .ret(GenericArrayType::new(GenericPairType::new(
4396 GenericType::Parameter("K"),
4397 GenericType::Parameter("V")
4398 )))
4399 .definition(
4400 r#"
4401Given 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`.
4402
4403If `m` is empty, an empty array is returned.
4404
4405**Parameters**
4406
44071. `Map[K, V]`: The map to convert.
4408
4409**Returns**: A new `Array[Pair[K, V]]` with the key-value pairs.
4410
4411Example: as_pairs_task.wdl
4412
4413```wdl
4414version 1.2
4415
4416task as_pairs {
4417 input {
4418 Map[String, Int] map = {"a": 1, "b": 2}
4419 }
4420
4421 output {
4422 Array[Pair[String, Int]] pairs = as_pairs(map) # [("a", 1), ("b", 2)]
4423 }
4424}
4425```
4426"#
4427 )
4428 .build(),
4429 )
4430 .into(),
4431 )
4432 .is_none()
4433 );
4434
4435 assert!(
4437 functions
4438 .insert(
4439 "as_map",
4440 MonomorphicFunction::new(
4441 FunctionSignature::builder()
4442 .min_version(SupportedVersion::V1(V1::One))
4443 .type_parameter("K", PrimitiveTypeConstraint)
4444 .any_type_parameter("V")
4445 .parameter(
4446 "pairs",
4447 GenericArrayType::new(GenericPairType::new(
4448 GenericType::Parameter("K"),
4449 GenericType::Parameter("V"),
4450 )),
4451 "`Array` of `Pairs` to convert to a `Map`.",
4452 )
4453 .ret(GenericMapType::new(
4454 GenericType::Parameter("K"),
4455 GenericType::Parameter("V")
4456 ))
4457 .definition(
4458 r#"
4459Given 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.
4460
4461If there are any duplicate keys in `a`, an error is raised.
4462
4463**Parameters**
4464
44651. `Array[Pair[K, V]]`: The array of pairs to convert.
4466
4467**Returns**: A new `Map[K, V]` with the key-value pairs.
4468
4469Example: as_map_task.wdl
4470
4471```wdl
4472version 1.2
4473
4474task as_map {
4475 input {
4476 Array[Pair[String, Int]] pairs = [("a", 1), ("b", 2)]
4477 }
4478
4479 output {
4480 Map[String, Int] map = as_map(pairs) # {"a": 1, "b": 2}
4481 }
4482}
4483```
4484"#
4485 )
4486 .build(),
4487 )
4488 .into(),
4489 )
4490 .is_none()
4491 );
4492
4493 const KEYS_DEFINITION: &str = r#"
4494Given 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`.
4495
4496If `m` is empty, an empty array is returned.
4497
4498**Parameters**
4499
45001. `Map[K, V]`: The map to get the keys from.
4501
4502**Returns**: A new `Array[K]` with the keys.
4503
4504Example: keys_map_task.wdl
4505
4506```wdl
4507version 1.2
4508
4509task keys_map {
4510 input {
4511 Map[String, Int] map = {"a": 1, "b": 2}
4512 }
4513
4514 output {
4515 Array[String] keys = keys(map) # ["a", "b"]
4516 }
4517}
4518```
4519"#;
4520
4521 assert!(
4523 functions
4524 .insert(
4525 "keys",
4526 PolymorphicFunction::new(vec![
4527 FunctionSignature::builder()
4528 .min_version(SupportedVersion::V1(V1::One))
4529 .type_parameter("K", PrimitiveTypeConstraint)
4530 .any_type_parameter("V")
4531 .parameter(
4532 "map",
4533 GenericMapType::new(
4534 GenericType::Parameter("K"),
4535 GenericType::Parameter("V"),
4536 ),
4537 "Collection from which to extract keys.",
4538 )
4539 .ret(GenericArrayType::new(GenericType::Parameter("K")))
4540 .definition(KEYS_DEFINITION)
4541 .build(),
4542 FunctionSignature::builder()
4543 .min_version(SupportedVersion::V1(V1::Two))
4544 .type_parameter("S", StructConstraint)
4545 .parameter(
4546 "struct",
4547 GenericType::Parameter("S"),
4548 "Collection from which to extract keys.",
4549 )
4550 .ret(array_string.clone())
4551 .definition(KEYS_DEFINITION)
4552 .build(),
4553 FunctionSignature::builder()
4554 .min_version(SupportedVersion::V1(V1::Two))
4555 .parameter(
4556 "object",
4557 Type::Object,
4558 "Collection from which to extract keys.",
4559 )
4560 .ret(array_string.clone())
4561 .definition(KEYS_DEFINITION)
4562 .build(),
4563 ])
4564 .into(),
4565 )
4566 .is_none()
4567 );
4568
4569 const CONTAINS_KEY_DEFINITION: &str = r#"
4570Given a `Map[K, V]` `m` and a key `k` of type `K`, returns `true` if `k` is present in `m`, otherwise `false`.
4571
4572**Parameters**
4573
45741. `Map[K, V]`: The map to search.
45752. `K`: The key to search for.
4576
4577**Returns**: `true` if `k` is present in `m`, otherwise `false`.
4578
4579Example: contains_key_map_task.wdl
4580
4581```wdl
4582version 1.2
4583
4584task contains_key_map {
4585 input {
4586 Map[String, Int] map = {"a": 1, "b": 2}
4587 }
4588
4589 output {
4590 Boolean contains_a = contains_key(map, "a") # true
4591 Boolean contains_c = contains_key(map, "c") # false
4592 }
4593}
4594```
4595"#;
4596
4597 assert!(
4599 functions
4600 .insert(
4601 "contains_key",
4602 PolymorphicFunction::new(vec![
4603 FunctionSignature::builder()
4604 .min_version(SupportedVersion::V1(V1::Two))
4605 .type_parameter("K", PrimitiveTypeConstraint)
4606 .any_type_parameter("V")
4607 .parameter(
4608 "map",
4609 GenericMapType::new(
4610 GenericType::Parameter("K"),
4611 GenericType::Parameter("V"),
4612 ),
4613 "Collection to search for the key.",
4614 )
4615 .parameter(
4616 "key",
4617 GenericType::Parameter("K"),
4618 "The key to search for. If the first argument is a `Map`, then \
4619 the key must be of the same type as the `Map`'s key type. If the \
4620 `Map`'s key type is optional then the key may also be optional. \
4621 If the first argument is a `Map[String, Y]`, `Struct`, or \
4622 `Object`, then the key may be either a `String` or \
4623 `Array[String]`."
4624 )
4625 .ret(PrimitiveType::Boolean)
4626 .definition(CONTAINS_KEY_DEFINITION)
4627 .build(),
4628 FunctionSignature::builder()
4629 .min_version(SupportedVersion::V1(V1::Two))
4630 .parameter("object", Type::Object, "Collection to search for the key.")
4631 .parameter(
4632 "key",
4633 PrimitiveType::String,
4634 "The key to search for. If the first argument is a `Map`, then \
4635 the key must be of the same type as the `Map`'s key type. If the \
4636 `Map`'s key type is optional then the key may also be optional. \
4637 If the first argument is a `Map[String, Y]`, `Struct`, or \
4638 `Object`, then the key may be either a `String` or \
4639 `Array[String]`."
4640 )
4641 .ret(PrimitiveType::Boolean)
4642 .definition(CONTAINS_KEY_DEFINITION)
4643 .build(),
4644 FunctionSignature::builder()
4645 .min_version(SupportedVersion::V1(V1::Two))
4646 .any_type_parameter("V")
4647 .parameter(
4648 "map",
4649 GenericMapType::new(
4650 PrimitiveType::String,
4651 GenericType::Parameter("V"),
4652 ),
4653 "Collection to search for the key.",
4654 )
4655 .parameter(
4656 "keys",
4657 array_string.clone(),
4658 "The key to search for. If the first argument is a `Map`, then \
4659 the key must be of the same type as the `Map`'s key type. If the \
4660 `Map`'s key type is optional then the key may also be optional. \
4661 If the first argument is a `Map[String, Y]`, `Struct`, or \
4662 `Object`, then the key may be either a `String` or \
4663 `Array[String]`."
4664 )
4665 .ret(PrimitiveType::Boolean)
4666 .definition(CONTAINS_KEY_DEFINITION)
4667 .build(),
4668 FunctionSignature::builder()
4669 .min_version(SupportedVersion::V1(V1::Two))
4670 .type_parameter("S", StructConstraint)
4671 .parameter(
4672 "struct",
4673 GenericType::Parameter("S"),
4674 "Collection to search for the key.",
4675 )
4676 .parameter(
4677 "keys",
4678 array_string.clone(),
4679 "The key to search for. If the first argument is a `Map`, then \
4680 the key must be of the same type as the `Map`'s key type. If the \
4681 `Map`'s key type is optional then the key may also be optional. \
4682 If the first argument is a `Map[String, Y]`, `Struct`, or \
4683 `Object`, then the key may be either a `String` or \
4684 `Array[String]`."
4685 )
4686 .ret(PrimitiveType::Boolean)
4687 .definition(CONTAINS_KEY_DEFINITION)
4688 .build(),
4689 FunctionSignature::builder()
4690 .min_version(SupportedVersion::V1(V1::Two))
4691 .parameter("object", Type::Object, "Collection to search for the key.")
4692 .parameter(
4693 "keys",
4694 array_string.clone(),
4695 "The key to search for. If the first argument is a `Map`, then \
4696 the key must be of the same type as the `Map`'s key type. If the \
4697 `Map`'s key type is optional then the key may also be optional. \
4698 If the first argument is a `Map[String, Y]`, `Struct`, or \
4699 `Object`, then the key may be either a `String` or \
4700 `Array[String]`."
4701 )
4702 .ret(PrimitiveType::Boolean)
4703 .definition(CONTAINS_KEY_DEFINITION)
4704 .build(),
4705 ])
4706 .into(),
4707 )
4708 .is_none()
4709 );
4710
4711 assert!(
4713 functions
4714 .insert(
4715 "values",
4716 MonomorphicFunction::new(
4717 FunctionSignature::builder()
4718 .min_version(SupportedVersion::V1(V1::Two))
4719 .type_parameter("K", PrimitiveTypeConstraint)
4720 .any_type_parameter("V")
4721 .parameter(
4722 "map",
4723 GenericMapType::new(
4724 GenericType::Parameter("K"),
4725 GenericType::Parameter("V"),
4726 ),
4727 "`Map` from which to extract values.",
4728 )
4729 .ret(GenericArrayType::new(GenericType::Parameter("V")))
4730 .definition(
4731 r#"
4732Given 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`.
4733
4734If `m` is empty, an empty array is returned.
4735
4736**Parameters**
4737
47381. `Map[K, V]`: The map to get the values from.
4739
4740**Returns**: A new `Array[V]` with the values.
4741
4742Example: values_map_task.wdl
4743
4744```wdl
4745version 1.2
4746
4747task values_map {
4748 input {
4749 Map[String, Int] map = {"a": 1, "b": 2}
4750 }
4751
4752 output {
4753 Array[Int] values = values(map) # [1, 2]
4754 }
4755}
4756```
4757"#
4758 )
4759 .build(),
4760 )
4761 .into(),
4762 )
4763 .is_none()
4764 );
4765
4766 assert!(
4768 functions
4769 .insert(
4770 "collect_by_key",
4771 MonomorphicFunction::new(
4772 FunctionSignature::builder()
4773 .min_version(SupportedVersion::V1(V1::One))
4774 .type_parameter("K", PrimitiveTypeConstraint)
4775 .any_type_parameter("V")
4776 .parameter(
4777 "pairs",
4778 GenericArrayType::new(GenericPairType::new(
4779 GenericType::Parameter("K"),
4780 GenericType::Parameter("V"),
4781 )),
4782 "`Array` of `Pairs` to group.",
4783 )
4784 .ret(GenericMapType::new(
4785 GenericType::Parameter("K"),
4786 GenericArrayType::new(GenericType::Parameter("V"))
4787 ))
4788 .definition(
4789 r#"
4790Given 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`.
4791
4792If `a` is empty, an empty map is returned.
4793
4794**Parameters**
4795
47961. `Array[Pair[K, V]]`: The array of pairs to collect.
4797
4798**Returns**: A new `Map[K, Array[V]]` with the collected values.
4799
4800Example: collect_by_key_task.wdl
4801
4802```wdl
4803version 1.2
4804
4805task collect_by_key {
4806 input {
4807 Array[Pair[String, Int]] pairs = [("a", 1), ("b", 2), ("a", 3)]
4808 }
4809
4810 output {
4811 Map[String, Array[Int]] collected = collect_by_key(pairs) # {"a": [1, 3], "b": 2}
4812 }
4813}
4814```
4815"#
4816 )
4817 .build(),
4818 )
4819 .into(),
4820 )
4821 .is_none()
4822 );
4823
4824 assert!(
4826 functions
4827 .insert(
4828 "defined",
4829 MonomorphicFunction::new(
4830 FunctionSignature::builder()
4831 .any_type_parameter("X")
4832 .parameter(
4833 "value",
4834 GenericType::Parameter("X"),
4835 "Optional value of any type."
4836 )
4837 .ret(PrimitiveType::Boolean)
4838 .definition(
4839 r#"
4840Given an optional value `x`, returns `true` if `x` is defined (i.e., not `None`), otherwise `false`.
4841
4842**Parameters**
4843
48441. `X?`: The optional value to check.
4845
4846**Returns**: `true` if `x` is defined, otherwise `false`.
4847
4848Example: defined_task.wdl
4849
4850```wdl
4851version 1.2
4852
4853task defined {
4854 input {
4855 Int? x = 1
4856 Int? y = None
4857 }
4858
4859 output {
4860 Boolean x_defined = defined(x) # true
4861 Boolean y_defined = defined(y) # false
4862 }
4863}
4864```
4865"#
4866 )
4867 .build(),
4868 )
4869 .into(),
4870 )
4871 .is_none()
4872 );
4873
4874 const LENGTH_DEFINITION: &str = r#"
4875Given an `Array[X]` `a`, returns the number of elements in `a`. If `a` is empty, `0` is returned.
4876
4877**Parameters**
4878
48791. `Array[X]`: The array to get the length from.
4880
4881**Returns**: The number of elements in the array as an `Int`.
4882
4883Example: length_array_task.wdl
4884
4885```wdl
4886version 1.2
4887
4888task length_array {
4889 input {
4890 Array[Int] ints = [1, 2, 3]
4891 }
4892
4893 output {
4894 Int len = length(ints) # 3
4895 }
4896}
4897```
4898"#;
4899
4900 assert!(
4902 functions
4903 .insert(
4904 "length",
4905 PolymorphicFunction::new(vec![
4906 FunctionSignature::builder()
4907 .any_type_parameter("X")
4908 .parameter(
4909 "array",
4910 GenericArrayType::new(GenericType::Parameter("X")),
4911 "A collection or string whose elements are to be counted.",
4912 )
4913 .ret(PrimitiveType::Integer)
4914 .definition(LENGTH_DEFINITION)
4915 .build(),
4916 FunctionSignature::builder()
4917 .any_type_parameter("K")
4918 .any_type_parameter("V")
4919 .parameter(
4920 "map",
4921 GenericMapType::new(
4922 GenericType::Parameter("K"),
4923 GenericType::Parameter("V"),
4924 ),
4925 "A collection or string whose elements are to be counted.",
4926 )
4927 .ret(PrimitiveType::Integer)
4928 .definition(LENGTH_DEFINITION)
4929 .build(),
4930 FunctionSignature::builder()
4931 .parameter(
4932 "object",
4933 Type::Object,
4934 "A collection or string whose elements are to be counted.",
4935 )
4936 .ret(PrimitiveType::Integer)
4937 .definition(LENGTH_DEFINITION)
4938 .build(),
4939 FunctionSignature::builder()
4940 .parameter(
4941 "string",
4942 PrimitiveType::String,
4943 "A collection or string whose elements are to be counted.",
4944 )
4945 .ret(PrimitiveType::Integer)
4946 .definition(LENGTH_DEFINITION)
4947 .build(),
4948 ])
4949 .into(),
4950 )
4951 .is_none()
4952 );
4953
4954 StandardLibrary {
4955 functions,
4956 array_int,
4957 array_string,
4958 array_file,
4959 array_object,
4960 array_string_non_empty,
4961 array_array_string,
4962 map_string_string,
4963 map_string_int,
4964 }
4965});
4966
4967#[cfg(test)]
4968mod test {
4969 use pretty_assertions::assert_eq;
4970
4971 use super::*;
4972
4973 #[test]
4974 fn verify_stdlib_signatures() {
4975 let mut signatures = Vec::new();
4976 for (name, f) in STDLIB.functions() {
4977 match f {
4978 Function::Monomorphic(f) => {
4979 let params = TypeParameters::new(&f.signature.type_parameters);
4980 signatures.push(format!("{name}{sig}", sig = f.signature.display(¶ms)));
4981 }
4982 Function::Polymorphic(f) => {
4983 for signature in &f.signatures {
4984 let params = TypeParameters::new(&signature.type_parameters);
4985 signatures.push(format!("{name}{sig}", sig = signature.display(¶ms)));
4986 }
4987 }
4988 }
4989 }
4990
4991 assert_eq!(
4992 signatures,
4993 [
4994 "floor(value: Float) -> Int",
4995 "ceil(value: Float) -> Int",
4996 "round(value: Float) -> Int",
4997 "min(a: Int, b: Int) -> Int",
4998 "min(a: Int, b: Float) -> Float",
4999 "min(a: Float, b: Int) -> Float",
5000 "min(a: Float, b: Float) -> Float",
5001 "max(a: Int, b: Int) -> Int",
5002 "max(a: Int, b: Float) -> Float",
5003 "max(a: Float, b: Int) -> Float",
5004 "max(a: Float, b: Float) -> Float",
5005 "find(input: String, pattern: String) -> String?",
5006 "matches(input: String, pattern: String) -> Boolean",
5007 "sub(input: String, pattern: String, replace: String) -> String",
5008 "basename(path: File, <suffix: String>) -> String",
5009 "basename(path: String, <suffix: String>) -> String",
5010 "basename(path: Directory, <suffix: String>) -> String",
5011 "join_paths(base: File, relative: String) -> File",
5012 "join_paths(base: File, relative: Array[String]+) -> File",
5013 "join_paths(paths: Array[String]+) -> File",
5014 "glob(pattern: String) -> Array[File]",
5015 "size(value: None, <unit: String>) -> Float",
5016 "size(value: File?, <unit: String>) -> Float",
5017 "size(value: String?, <unit: String>) -> Float",
5018 "size(value: Directory?, <unit: String>) -> Float",
5019 "size(value: X, <unit: String>) -> Float where `X`: any compound type that \
5020 recursively contains a `File` or `Directory`",
5021 "stdout() -> File",
5022 "stderr() -> File",
5023 "read_string(file: File) -> String",
5024 "read_int(file: File) -> Int",
5025 "read_float(file: File) -> Float",
5026 "read_boolean(file: File) -> Boolean",
5027 "read_lines(file: File) -> Array[String]",
5028 "write_lines(array: Array[String]) -> File",
5029 "read_tsv(file: File) -> Array[Array[String]]",
5030 "read_tsv(file: File, header: Boolean) -> Array[Object]",
5031 "read_tsv(file: File, header: Boolean, columns: Array[String]) -> Array[Object]",
5032 "write_tsv(data: Array[Array[String]]) -> File",
5033 "write_tsv(data: Array[Array[String]], header: Boolean, columns: Array[String]) \
5034 -> File",
5035 "write_tsv(data: Array[S], <header: Boolean>, <columns: Array[String]>) -> File \
5036 where `S`: any structure containing only primitive types",
5037 "read_map(file: File) -> Map[String, String]",
5038 "write_map(map: Map[String, String]) -> File",
5039 "read_json(file: File) -> Union",
5040 "write_json(value: X) -> File where `X`: any JSON-serializable type",
5041 "read_object(file: File) -> Object",
5042 "read_objects(file: File) -> Array[Object]",
5043 "write_object(object: Object) -> File",
5044 "write_object(object: S) -> File where `S`: any structure containing only \
5045 primitive types",
5046 "write_objects(objects: Array[Object]) -> File",
5047 "write_objects(objects: Array[S]) -> File where `S`: any structure containing \
5048 only primitive types",
5049 "prefix(prefix: String, array: Array[P]) -> Array[String] where `P`: any \
5050 primitive type",
5051 "suffix(suffix: String, array: Array[P]) -> Array[String] where `P`: any \
5052 primitive type",
5053 "quote(array: Array[P]) -> Array[String] where `P`: any primitive type",
5054 "squote(array: Array[P]) -> Array[String] where `P`: any primitive type",
5055 "sep(separator: String, array: Array[P]) -> String where `P`: any primitive type",
5056 "range(n: Int) -> Array[Int]",
5057 "transpose(array: Array[Array[X]]) -> Array[Array[X]]",
5058 "cross(a: Array[X], b: Array[Y]) -> Array[Pair[X, Y]]",
5059 "zip(a: Array[X], b: Array[Y]) -> Array[Pair[X, Y]]",
5060 "unzip(array: Array[Pair[X, Y]]) -> Pair[Array[X], Array[Y]]",
5061 "contains(array: Array[P], value: P) -> Boolean where `P`: any primitive type",
5062 "chunk(array: Array[X], size: Int) -> Array[Array[X]]",
5063 "flatten(array: Array[Array[X]]) -> Array[X]",
5064 "select_first(array: Array[X], <default: X>) -> X",
5065 "select_all(array: Array[X]) -> Array[X]",
5066 "as_pairs(map: Map[K, V]) -> Array[Pair[K, V]] where `K`: any primitive type",
5067 "as_map(pairs: Array[Pair[K, V]]) -> Map[K, V] where `K`: any primitive type",
5068 "keys(map: Map[K, V]) -> Array[K] where `K`: any primitive type",
5069 "keys(struct: S) -> Array[String] where `S`: any structure",
5070 "keys(object: Object) -> Array[String]",
5071 "contains_key(map: Map[K, V], key: K) -> Boolean where `K`: any primitive type",
5072 "contains_key(object: Object, key: String) -> Boolean",
5073 "contains_key(map: Map[String, V], keys: Array[String]) -> Boolean",
5074 "contains_key(struct: S, keys: Array[String]) -> Boolean where `S`: any structure",
5075 "contains_key(object: Object, keys: Array[String]) -> Boolean",
5076 "values(map: Map[K, V]) -> Array[V] where `K`: any primitive type",
5077 "collect_by_key(pairs: Array[Pair[K, V]]) -> Map[K, Array[V]] where `K`: any \
5078 primitive type",
5079 "defined(value: X) -> Boolean",
5080 "length(array: Array[X]) -> Int",
5081 "length(map: Map[K, V]) -> Int",
5082 "length(object: Object) -> Int",
5083 "length(string: String) -> Int",
5084 ]
5085 );
5086 }
5087
5088 #[test]
5089 fn it_binds_a_simple_function() {
5090 let f = STDLIB.function("floor").expect("should have function");
5091 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5092
5093 let e = f
5094 .bind(SupportedVersion::V1(V1::Zero), &[])
5095 .expect_err("bind should fail");
5096 assert_eq!(e, FunctionBindError::TooFewArguments(1));
5097
5098 let e = f
5099 .bind(
5100 SupportedVersion::V1(V1::One),
5101 &[PrimitiveType::String.into(), PrimitiveType::Boolean.into()],
5102 )
5103 .expect_err("bind should fail");
5104 assert_eq!(e, FunctionBindError::TooManyArguments(1));
5105
5106 let e = f
5108 .bind(
5109 SupportedVersion::V1(V1::Two),
5110 &[PrimitiveType::String.into()],
5111 )
5112 .expect_err("bind should fail");
5113 assert_eq!(
5114 e,
5115 FunctionBindError::ArgumentTypeMismatch {
5116 index: 0,
5117 expected: "`Float`".into()
5118 }
5119 );
5120
5121 let binding = f
5123 .bind(SupportedVersion::V1(V1::Zero), &[Type::Union])
5124 .expect("bind should succeed");
5125 assert_eq!(binding.index(), 0);
5126 assert_eq!(binding.return_type().to_string(), "Int");
5127
5128 let binding = f
5130 .bind(
5131 SupportedVersion::V1(V1::One),
5132 &[PrimitiveType::Float.into()],
5133 )
5134 .expect("bind should succeed");
5135 assert_eq!(binding.index(), 0);
5136 assert_eq!(binding.return_type().to_string(), "Int");
5137
5138 let binding = f
5140 .bind(
5141 SupportedVersion::V1(V1::Two),
5142 &[PrimitiveType::Integer.into()],
5143 )
5144 .expect("bind should succeed");
5145 assert_eq!(binding.index(), 0);
5146 assert_eq!(binding.return_type().to_string(), "Int");
5147 }
5148
5149 #[test]
5150 fn it_binds_a_generic_function() {
5151 let f = STDLIB.function("values").expect("should have function");
5152 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Two));
5153
5154 let e = f
5155 .bind(SupportedVersion::V1(V1::Zero), &[])
5156 .expect_err("bind should fail");
5157 assert_eq!(
5158 e,
5159 FunctionBindError::RequiresVersion(SupportedVersion::V1(V1::Two))
5160 );
5161
5162 let e = f
5163 .bind(SupportedVersion::V1(V1::Two), &[])
5164 .expect_err("bind should fail");
5165 assert_eq!(e, FunctionBindError::TooFewArguments(1));
5166
5167 let e = f
5168 .bind(
5169 SupportedVersion::V1(V1::Two),
5170 &[PrimitiveType::String.into(), PrimitiveType::Boolean.into()],
5171 )
5172 .expect_err("bind should fail");
5173 assert_eq!(e, FunctionBindError::TooManyArguments(1));
5174
5175 let e = f
5177 .bind(
5178 SupportedVersion::V1(V1::Two),
5179 &[PrimitiveType::String.into()],
5180 )
5181 .expect_err("bind should fail");
5182 assert_eq!(
5183 e,
5184 FunctionBindError::ArgumentTypeMismatch {
5185 index: 0,
5186 expected: "`Map[K, V]` where `K`: any primitive type".into()
5187 }
5188 );
5189
5190 let binding = f
5192 .bind(SupportedVersion::V1(V1::Two), &[Type::Union])
5193 .expect("bind should succeed");
5194 assert_eq!(binding.index(), 0);
5195 assert_eq!(binding.return_type().to_string(), "Array[Union]");
5196
5197 let ty: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
5199 let binding = f
5200 .bind(SupportedVersion::V1(V1::Two), &[ty])
5201 .expect("bind should succeed");
5202 assert_eq!(binding.index(), 0);
5203 assert_eq!(binding.return_type().to_string(), "Array[String]");
5204
5205 let ty: Type = MapType::new(PrimitiveType::String, Type::Object).into();
5207 let binding = f
5208 .bind(SupportedVersion::V1(V1::Two), &[ty])
5209 .expect("bind should succeed");
5210 assert_eq!(binding.index(), 0);
5211 assert_eq!(binding.return_type().to_string(), "Array[Object]");
5212
5213 let ty: Type = MapType::new(
5215 Type::from(PrimitiveType::String).optional(),
5216 PrimitiveType::Boolean,
5217 )
5218 .into();
5219 let binding = f
5220 .bind(SupportedVersion::V1(V1::Two), &[ty])
5221 .expect("bind should succeed");
5222 assert_eq!(binding.index(), 0);
5223 assert_eq!(binding.return_type().to_string(), "Array[Boolean]");
5224 }
5225
5226 #[test]
5227 fn it_removes_qualifiers() {
5228 let f = STDLIB.function("select_all").expect("should have function");
5229 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5230
5231 let array_string: Type = ArrayType::new(PrimitiveType::String).into();
5233 let binding = f
5234 .bind(SupportedVersion::V1(V1::One), &[array_string])
5235 .expect("bind should succeed");
5236 assert_eq!(binding.index(), 0);
5237 assert_eq!(binding.return_type().to_string(), "Array[String]");
5238
5239 let array_optional_string: Type =
5241 ArrayType::new(Type::from(PrimitiveType::String).optional()).into();
5242 let binding = f
5243 .bind(SupportedVersion::V1(V1::One), &[array_optional_string])
5244 .expect("bind should succeed");
5245 assert_eq!(binding.index(), 0);
5246 assert_eq!(binding.return_type().to_string(), "Array[String]");
5247
5248 let binding = f
5250 .bind(SupportedVersion::V1(V1::Two), &[Type::Union])
5251 .expect("bind should succeed");
5252 assert_eq!(binding.index(), 0);
5253 assert_eq!(binding.return_type().to_string(), "Array[Union]");
5254
5255 let array_string = Type::from(ArrayType::new(PrimitiveType::String)).optional();
5257 let array_array_string = ArrayType::new(array_string).into();
5258 let binding = f
5259 .bind(SupportedVersion::V1(V1::Zero), &[array_array_string])
5260 .expect("bind should succeed");
5261 assert_eq!(binding.index(), 0);
5262 assert_eq!(binding.return_type().to_string(), "Array[Array[String]]");
5263 }
5264
5265 #[test]
5266 fn it_binds_concrete_overloads() {
5267 let f = STDLIB.function("max").expect("should have function");
5268 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::One));
5269
5270 let e = f
5271 .bind(SupportedVersion::V1(V1::One), &[])
5272 .expect_err("bind should fail");
5273 assert_eq!(e, FunctionBindError::TooFewArguments(2));
5274
5275 let e = f
5276 .bind(
5277 SupportedVersion::V1(V1::Two),
5278 &[
5279 PrimitiveType::String.into(),
5280 PrimitiveType::Boolean.into(),
5281 PrimitiveType::File.into(),
5282 ],
5283 )
5284 .expect_err("bind should fail");
5285 assert_eq!(e, FunctionBindError::TooManyArguments(2));
5286
5287 let binding = f
5289 .bind(
5290 SupportedVersion::V1(V1::One),
5291 &[PrimitiveType::Integer.into(), PrimitiveType::Integer.into()],
5292 )
5293 .expect("binding should succeed");
5294 assert_eq!(binding.index(), 0);
5295 assert_eq!(binding.return_type().to_string(), "Int");
5296
5297 let binding = f
5299 .bind(
5300 SupportedVersion::V1(V1::Two),
5301 &[PrimitiveType::Integer.into(), PrimitiveType::Float.into()],
5302 )
5303 .expect("binding should succeed");
5304 assert_eq!(binding.index(), 1);
5305 assert_eq!(binding.return_type().to_string(), "Float");
5306
5307 let binding = f
5309 .bind(
5310 SupportedVersion::V1(V1::One),
5311 &[PrimitiveType::Float.into(), PrimitiveType::Integer.into()],
5312 )
5313 .expect("binding should succeed");
5314 assert_eq!(binding.index(), 2);
5315 assert_eq!(binding.return_type().to_string(), "Float");
5316
5317 let binding = f
5319 .bind(
5320 SupportedVersion::V1(V1::Two),
5321 &[PrimitiveType::Float.into(), PrimitiveType::Float.into()],
5322 )
5323 .expect("binding should succeed");
5324 assert_eq!(binding.index(), 3);
5325 assert_eq!(binding.return_type().to_string(), "Float");
5326
5327 let e = f
5329 .bind(
5330 SupportedVersion::V1(V1::One),
5331 &[PrimitiveType::String.into(), PrimitiveType::Integer.into()],
5332 )
5333 .expect_err("binding should fail");
5334 assert_eq!(
5335 e,
5336 FunctionBindError::ArgumentTypeMismatch {
5337 index: 0,
5338 expected: "`Int` or `Float`".into()
5339 }
5340 );
5341
5342 let e = f
5344 .bind(
5345 SupportedVersion::V1(V1::Two),
5346 &[PrimitiveType::Integer.into(), PrimitiveType::String.into()],
5347 )
5348 .expect_err("binding should fail");
5349 assert_eq!(
5350 e,
5351 FunctionBindError::ArgumentTypeMismatch {
5352 index: 1,
5353 expected: "`Int` or `Float`".into()
5354 }
5355 );
5356
5357 let e = f
5359 .bind(
5360 SupportedVersion::V1(V1::One),
5361 &[PrimitiveType::String.into(), PrimitiveType::Float.into()],
5362 )
5363 .expect_err("binding should fail");
5364 assert_eq!(
5365 e,
5366 FunctionBindError::ArgumentTypeMismatch {
5367 index: 0,
5368 expected: "`Int` or `Float`".into()
5369 }
5370 );
5371
5372 let e = f
5374 .bind(
5375 SupportedVersion::V1(V1::Two),
5376 &[PrimitiveType::Float.into(), PrimitiveType::String.into()],
5377 )
5378 .expect_err("binding should fail");
5379 assert_eq!(
5380 e,
5381 FunctionBindError::ArgumentTypeMismatch {
5382 index: 1,
5383 expected: "`Int` or `Float`".into()
5384 }
5385 );
5386 }
5387
5388 #[test]
5389 fn it_binds_generic_overloads() {
5390 let f = STDLIB
5391 .function("select_first")
5392 .expect("should have function");
5393 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5394
5395 let e = f
5396 .bind(SupportedVersion::V1(V1::Zero), &[])
5397 .expect_err("bind should fail");
5398 assert_eq!(e, FunctionBindError::TooFewArguments(1));
5399
5400 let e = f
5401 .bind(
5402 SupportedVersion::V1(V1::One),
5403 &[
5404 PrimitiveType::String.into(),
5405 PrimitiveType::Boolean.into(),
5406 PrimitiveType::File.into(),
5407 ],
5408 )
5409 .expect_err("bind should fail");
5410 assert_eq!(e, FunctionBindError::TooManyArguments(2));
5411
5412 let e = f
5414 .bind(
5415 SupportedVersion::V1(V1::Two),
5416 &[PrimitiveType::Integer.into()],
5417 )
5418 .expect_err("binding should fail");
5419 assert_eq!(
5420 e,
5421 FunctionBindError::ArgumentTypeMismatch {
5422 index: 0,
5423 expected: "`Array[X]`".into()
5424 }
5425 );
5426
5427 let array: Type = ArrayType::non_empty(Type::from(PrimitiveType::String).optional()).into();
5429 let binding = f
5430 .bind(SupportedVersion::V1(V1::Zero), std::slice::from_ref(&array))
5431 .expect("binding should succeed");
5432 assert_eq!(binding.index(), 0);
5433 assert_eq!(binding.return_type().to_string(), "String");
5434
5435 let binding = f
5437 .bind(
5438 SupportedVersion::V1(V1::One),
5439 &[array.clone(), PrimitiveType::String.into()],
5440 )
5441 .expect("binding should succeed");
5442 assert_eq!(binding.index(), 0);
5443 assert_eq!(binding.return_type().to_string(), "String");
5444
5445 let e = f
5447 .bind(
5448 SupportedVersion::V1(V1::Two),
5449 &[array.clone(), PrimitiveType::Integer.into()],
5450 )
5451 .expect_err("binding should fail");
5452 assert_eq!(
5453 e,
5454 FunctionBindError::ArgumentTypeMismatch {
5455 index: 1,
5456 expected: "`String`".into()
5457 }
5458 );
5459
5460 let array: Type = ArrayType::new(Type::from(PrimitiveType::String).optional()).into();
5462 let binding = f
5463 .bind(SupportedVersion::V1(V1::Zero), std::slice::from_ref(&array))
5464 .expect("binding should succeed");
5465 assert_eq!(binding.index(), 0);
5466 assert_eq!(binding.return_type().to_string(), "String");
5467
5468 let binding = f
5470 .bind(
5471 SupportedVersion::V1(V1::One),
5472 &[array.clone(), PrimitiveType::String.into()],
5473 )
5474 .expect("binding should succeed");
5475 assert_eq!(binding.index(), 0);
5476 assert_eq!(binding.return_type().to_string(), "String");
5477
5478 let e = f
5480 .bind(
5481 SupportedVersion::V1(V1::Two),
5482 &[array, PrimitiveType::Integer.into()],
5483 )
5484 .expect_err("binding should fail");
5485 assert_eq!(
5486 e,
5487 FunctionBindError::ArgumentTypeMismatch {
5488 index: 1,
5489 expected: "`String`".into()
5490 }
5491 );
5492 }
5493}