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 assert!(
2099 functions
2100 .insert(
2101 "split",
2102 MonomorphicFunction::new(
2103 FunctionSignature::builder()
2104 .min_version(SupportedVersion::V1(V1::Three))
2105 .parameter("input", PrimitiveType::String, "The input string.")
2106 .parameter("delimiter", PrimitiveType::String, "The delimiter to split on as a regular expression.")
2107 .ret(array_string.clone())
2108 .definition(
2109 r#"
2110Given the two `String` parameters `input` and `delimiter`, this function splits the input string on the provided delimiter and stores the results in a `Array[String]`. `delimiter` is a [regular expression](https://en.wikipedia.org/wiki/Regular_expression) and is evaluated as a [POSIX Extended Regular Expression (ERE)](https://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended).
2111Regular expressions are written using regular WDL strings, so backslash characters need to be double-escaped (e.g., `"\\t"`).
2112
2113**Parameters**:
2114
21151. `String`: the input string.
21162. `String`: the delimiter to split on as a regular expression.
2117
2118**Returns**: the parts of the input string split by the delimiter. If the input delimiter does not match anything in the input string, an array containing a single entry of the input string is returned.
2119
2120<details>
2121<summary>
2122Example: test_split.wdl
2123
2124```wdl
2125version 1.3
2126
2127workflow test_split {
2128 String in = "Here's an example\nthat takes up multiple lines"
2129
2130 output {
2131 Array[String] split_by_word = split(in, " ")
2132 Array[String] split_by_newline = split(in, "\\n")
2133 Array[String] split_by_both = split(in, "\s")
2134 }
2135}
2136```
2137"#
2138 )
2139 .build(),
2140 )
2141 .into(),
2142 )
2143 .is_none()
2144 );
2145
2146 const BASENAME_DEFINITION: &str = r#"
2147Returns the "basename" of a file or directory - the name after the last directory separator in the path.
2148
2149The 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.
2150
2151**Parameters**
2152
21531. `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.
21542. `String`: (Optional) Suffix to remove from the file name.
2155
2156**Returns**: The file's basename as a `String`.
2157
2158Example: test_basename.wdl
2159
2160```wdl
2161version 1.2
2162
2163workflow test_basename {
2164 output {
2165 Boolean is_true1 = basename("/path/to/file.txt") == "file.txt"
2166 Boolean is_true2 = basename("/path/to/file.txt", ".txt") == "file"
2167 Boolean is_true3 = basename("/path/to/dir") == "dir"
2168 }
2169}
2170```
2171"#;
2172
2173 assert!(
2175 functions
2176 .insert(
2177 "basename",
2178 PolymorphicFunction::new(vec![
2179 FunctionSignature::builder()
2180 .required(1)
2181 .parameter(
2182 "path",
2183 PrimitiveType::File,
2184 "Path of the file or directory to read. If the argument is a \
2185 `String`, it is assumed to be a local file path relative to the \
2186 current working directory of the task.",
2187 )
2188 .parameter(
2189 "suffix",
2190 PrimitiveType::String,
2191 "(Optional) Suffix to remove from the file name.",
2192 )
2193 .ret(PrimitiveType::String)
2194 .definition(BASENAME_DEFINITION)
2195 .build(),
2196 FunctionSignature::builder()
2201 .min_version(SupportedVersion::V1(V1::Two))
2202 .required(1)
2203 .parameter(
2204 "path",
2205 PrimitiveType::String,
2206 "Path of the file or directory to read. If the argument is a \
2207 `String`, it is assumed to be a local file path relative to the \
2208 current working directory of the task."
2209 )
2210 .parameter(
2211 "suffix",
2212 PrimitiveType::String,
2213 "(Optional) Suffix to remove from the file name."
2214 )
2215 .ret(PrimitiveType::String)
2216 .definition(BASENAME_DEFINITION)
2217 .build(),
2218 FunctionSignature::builder()
2219 .min_version(SupportedVersion::V1(V1::Two))
2220 .required(1)
2221 .parameter(
2222 "path",
2223 PrimitiveType::Directory,
2224 "Path of the file or directory to read. If the argument is a \
2225 `String`, it is assumed to be a local file path relative to the \
2226 current working directory of the task.",
2227 )
2228 .parameter(
2229 "suffix",
2230 PrimitiveType::String,
2231 "(Optional) Suffix to remove from the file name.",
2232 )
2233 .ret(PrimitiveType::String)
2234 .definition(BASENAME_DEFINITION)
2235 .build(),
2236 ])
2237 .into(),
2238 )
2239 .is_none()
2240 );
2241
2242 const JOIN_PATHS_DEFINITION: &str = r#"
2243Joins together two or more paths into an absolute path in the host filesystem.
2244
2245There are three variants of this function:
2246
22471. `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.
22482. `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.
22493. `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.
2250
2251An 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.
2252
2253A 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.
2254
2255**Parameters**
2256
22571. `File|Array[String]+`: Either a path or an array of paths.
22582. `String|Array[String]+`: A relative path or paths; only allowed if the first argument is a `File`.
2259
2260**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.
2261
2262Example: join_paths_task.wdl
2263
2264```wdl
2265version 1.2
2266
2267task resolve_paths_task {
2268 input {
2269 File abs_file = "/usr"
2270 String abs_str = "/usr"
2271 String rel_dir_str = "bin"
2272 File rel_file = "echo"
2273 File rel_dir_file = "mydir"
2274 String rel_str = "mydata.txt"
2275 }
2276
2277 # these are all equivalent to '/usr/bin/echo'
2278 File bin1 = join_paths(abs_file, [rel_dir_str, rel_file])
2279 File bin2 = join_paths(abs_str, [rel_dir_str, rel_file])
2280 File bin3 = join_paths([abs_str, rel_dir_str, rel_file])
2281
2282 # the default behavior is that this resolves to
2283 # '<working dir>/mydir/mydata.txt'
2284 File data = join_paths(rel_dir_file, rel_str)
2285
2286 # this resolves to '<working dir>/bin/echo', which is non-existent
2287 File doesnt_exist = join_paths([rel_dir_str, rel_file])
2288 command <<<
2289 mkdir ~{rel_dir_file}
2290 ~{bin1} -n "hello" > ~{data}
2291 >>>
2292
2293 output {
2294 Boolean bins_equal = (bin1 == bin2) && (bin1 == bin3)
2295 String result = read_string(data)
2296 File? missing_file = doesnt_exist
2297 }
2298
2299 runtime {
2300 container: "ubuntu:latest"
2301 }
2302}
2303```
2304"#;
2305
2306 assert!(
2308 functions
2309 .insert(
2310 "join_paths",
2311 PolymorphicFunction::new(vec![
2312 FunctionSignature::builder()
2313 .min_version(SupportedVersion::V1(V1::Two))
2314 .parameter(
2315 "base",
2316 PrimitiveType::File,
2317 "Either a path or an array of paths.",
2318 )
2319 .parameter(
2320 "relative",
2321 PrimitiveType::String,
2322 "A relative path or paths; only allowed if the first argument is a \
2323 `File`.",
2324 )
2325 .ret(PrimitiveType::File)
2326 .definition(JOIN_PATHS_DEFINITION)
2327 .build(),
2328 FunctionSignature::builder()
2329 .min_version(SupportedVersion::V1(V1::Two))
2330 .parameter(
2331 "base",
2332 PrimitiveType::File,
2333 "Either a path or an array of paths."
2334 )
2335 .parameter(
2336 "relative",
2337 array_string_non_empty.clone(),
2338 "A relative path or paths; only allowed if the first argument is a \
2339 `File`."
2340 )
2341 .ret(PrimitiveType::File)
2342 .definition(JOIN_PATHS_DEFINITION)
2343 .build(),
2344 FunctionSignature::builder()
2345 .min_version(SupportedVersion::V1(V1::Two))
2346 .parameter(
2347 "paths",
2348 array_string_non_empty.clone(),
2349 "Either a path or an array of paths."
2350 )
2351 .ret(PrimitiveType::File)
2352 .definition(JOIN_PATHS_DEFINITION)
2353 .build(),
2354 ])
2355 .into(),
2356 )
2357 .is_none()
2358 );
2359
2360 assert!(
2362 functions
2363 .insert(
2364 "glob",
2365 MonomorphicFunction::new(
2366 FunctionSignature::builder()
2367 .parameter("pattern", PrimitiveType::String, "The glob string.")
2368 .ret(array_file.clone())
2369 .definition(
2370 r#"
2371Returns 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.
2372
2373`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.
2374
2375At least in standard Bash, glob expressions are not evaluated recursively, i.e., files in nested directories are not included.
2376
2377**Parameters**:
2378
23791. `String`: The glob string.
2380
2381**Returns**: A array of all files matched by the glob.
2382
2383Example: gen_files_task.wdl
2384
2385```wdl
2386version 1.2
2387
2388task gen_files {
2389 input {
2390 Int num_files
2391 }
2392
2393 command <<<
2394 for i in 1..~{num_files}; do
2395 printf ${i} > a_file_${i}.txt
2396 done
2397 mkdir a_dir
2398 touch a_dir/a_inner.txt
2399 >>>
2400
2401 output {
2402 Array[File] files = glob("a_*")
2403 Int glob_len = length(files)
2404 }
2405}
2406```
2407"#
2408 )
2409 .build(),
2410 )
2411 .into(),
2412 )
2413 .is_none()
2414 );
2415
2416 const SIZE_DEFINITION: &str = r#"
2417Determines 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)
2418
2419In the second variant of the `size` function, the parameter type `X` represents any compound type that contains `File` or `File?` nested at any depth.
2420
2421If 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.
2422
2423**Parameters**
2424
24251. `File|File?|Directory|Directory?|X|X?`: A file, directory, or a compound value containing files/directories, for which to determine the size.
24262. `String`: (Optional) The unit of storage; defaults to 'B'.
2427
2428**Returns**: The size of the files/directories as a `Float`.
2429
2430Example: file_sizes_task.wdl
2431
2432```wdl
2433version 1.2
2434
2435task file_sizes {
2436 command <<<
2437 printf "this file is 22 bytes\n" > created_file
2438 >>>
2439
2440 File? missing_file = None
2441
2442 output {
2443 File created_file = "created_file"
2444 Float missing_file_bytes = size(missing_file)
2445 Float created_file_bytes = size(created_file, "B")
2446 Float multi_file_kb = size([created_file, missing_file], "K")
2447
2448 Map[String, Pair[Int, File]] nested = {
2449 "a": (10, created_file),
2450 "b": (50, missing_file)
2451 }
2452 Float nested_bytes = size(nested)
2453 }
2454
2455 requirements {
2456 container: "ubuntu:latest"
2457 }
2458}
2459```
2460"#;
2461
2462 assert!(
2464 functions
2465 .insert(
2466 "size",
2467 PolymorphicFunction::new(vec![
2468 FunctionSignature::builder()
2471 .min_version(SupportedVersion::V1(V1::Two))
2472 .required(1)
2473 .parameter(
2474 "value",
2475 Type::None,
2476 "A file, directory, or a compound value containing files/directories, \
2477 for which to determine the size."
2478 )
2479 .parameter(
2480 "unit",
2481 PrimitiveType::String,
2482 "(Optional) The unit of storage; defaults to 'B'."
2483 )
2484 .ret(PrimitiveType::Float)
2485 .definition(SIZE_DEFINITION)
2486 .build(),
2487 FunctionSignature::builder()
2488 .required(1)
2489 .parameter(
2490 "value",
2491 Type::from(PrimitiveType::File).optional(),
2492 "A file, directory, or a compound value containing files/directories, \
2493 for which to determine the size."
2494 )
2495 .parameter(
2496 "unit",
2497 PrimitiveType::String,
2498 "(Optional) The unit of storage; defaults to 'B'."
2499 )
2500 .ret(PrimitiveType::Float)
2501 .definition(SIZE_DEFINITION)
2502 .build(),
2503 FunctionSignature::builder()
2508 .min_version(SupportedVersion::V1(V1::Two))
2509 .required(1)
2510 .parameter(
2511 "value",
2512 Type::from(PrimitiveType::String).optional(),
2513 "A file, directory, or a compound value containing files/directories, \
2514 for which to determine the size.",
2515 )
2516 .parameter(
2517 "unit",
2518 PrimitiveType::String,
2519 "(Optional) The unit of storage; defaults to 'B'.",
2520 )
2521 .ret(PrimitiveType::Float)
2522 .definition(SIZE_DEFINITION)
2523 .build(),
2524 FunctionSignature::builder()
2525 .min_version(SupportedVersion::V1(V1::Two))
2526 .required(1)
2527 .parameter(
2528 "value",
2529 Type::from(PrimitiveType::Directory).optional(),
2530 "A file, directory, or a compound value containing files/directories, \
2531 for which to determine the size."
2532 )
2533 .parameter(
2534 "unit",
2535 PrimitiveType::String,
2536 "(Optional) The unit of storage; defaults to 'B'."
2537 )
2538 .ret(PrimitiveType::Float)
2539 .definition(SIZE_DEFINITION)
2540 .build(),
2541 FunctionSignature::builder()
2542 .required(1)
2543 .type_parameter("X", SizeableConstraint)
2544 .parameter(
2545 "value",
2546 GenericType::Parameter("X"),
2547 "A file, directory, or a compound value containing files/directories, \
2548 for which to determine the size."
2549 )
2550 .parameter(
2551 "unit",
2552 PrimitiveType::String,
2553 "(Optional) The unit of storage; defaults to 'B'."
2554 )
2555 .ret(PrimitiveType::Float)
2556 .definition(SIZE_DEFINITION)
2557 .build(),
2558 ])
2559 .into(),
2560 )
2561 .is_none()
2562 );
2563
2564 assert!(
2566 functions
2567 .insert(
2568 "stdout",
2569 MonomorphicFunction::new(
2570 FunctionSignature::builder()
2571 .ret(PrimitiveType::File)
2572 .definition(
2573 r#"
2574Returns 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.
2575
2576**Parameters**: None
2577
2578**Returns**: A `File` whose contents are the stdout generated by the command of the task where the function is called.
2579
2580Example: echo_stdout.wdl
2581
2582```wdl
2583version 1.2
2584
2585task echo_stdout {
2586 command <<<
2587 printf "hello world"
2588 >>>
2589
2590 output {
2591 File message = read_string(stdout())
2592 }
2593}
2594```
2595"#
2596 )
2597 .build(),
2598 )
2599 .into(),
2600 )
2601 .is_none()
2602 );
2603
2604 assert!(
2606 functions
2607 .insert(
2608 "stderr",
2609 MonomorphicFunction::new(
2610 FunctionSignature::builder()
2611 .ret(PrimitiveType::File)
2612 .definition(
2613 r#"
2614Returns 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.
2615
2616**Parameters**: None
2617
2618**Returns**: A `File` whose contents are the stderr generated by the command of the task where the function is called.
2619
2620Example: echo_stderr.wdl
2621
2622```wdl
2623version 1.2
2624
2625task echo_stderr {
2626 command <<<
2627 >&2 printf "hello world"
2628 >>>
2629
2630 output {
2631 File message = read_string(stderr())
2632 }
2633}
2634```
2635"#
2636 )
2637 .build(),
2638 )
2639 .into(),
2640 )
2641 .is_none()
2642 );
2643
2644 assert!(
2646 functions
2647 .insert(
2648 "read_string",
2649 MonomorphicFunction::new(
2650 FunctionSignature::builder()
2651 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2652 .ret(PrimitiveType::String)
2653 .definition(
2654 r#"
2655Reads 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.
2656
2657If the file contains any internal newline characters, they are left in tact.
2658
2659**Parameters**
2660
26611. `File`: Path of the file to read.
2662
2663**Returns**: A `String`.
2664
2665Example: read_string_task.wdl
2666
2667```wdl
2668version 1.2
2669
2670task read_string {
2671 # this file will contain "this\nfile\nhas\nfive\nlines\n"
2672 File f = write_lines(["this", "file", "has", "five", "lines"])
2673
2674 command <<<
2675 cat ~{f}
2676 >>>
2677
2678 output {
2679 # s will contain "this\nfile\nhas\nfive\nlines"
2680 String s = read_string(stdout())
2681 }
2682}
2683```
2684"#
2685 )
2686 .build(),
2687 )
2688 .into(),
2689 )
2690 .is_none()
2691 );
2692
2693 assert!(
2695 functions
2696 .insert(
2697 "read_int",
2698 MonomorphicFunction::new(
2699 FunctionSignature::builder()
2700 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2701 .ret(PrimitiveType::Integer)
2702 .definition(
2703 r#"
2704Reads 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.
2705
2706**Parameters**
2707
27081. `File`: Path of the file to read.
2709
2710**Returns**: An `Int`.
2711
2712Example: read_int_task.wdl
2713
2714```wdl
2715version 1.2
2716
2717task read_int {
2718 command <<<
2719 printf " 1 \n" > int_file
2720 >>>
2721
2722 output {
2723 Int i = read_int("int_file")
2724 }
2725}
2726```
2727"#
2728 )
2729 .build(),
2730 )
2731 .into(),
2732 )
2733 .is_none()
2734 );
2735
2736 assert!(
2738 functions
2739 .insert(
2740 "read_float",
2741 MonomorphicFunction::new(
2742 FunctionSignature::builder()
2743 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2744 .ret(PrimitiveType::Float)
2745 .definition(
2746 r#"
2747Reads 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.
2748
2749**Parameters**
2750
27511. `File`: Path of the file to read.
2752
2753**Returns**: A `Float`.
2754
2755Example: read_float_task.wdl
2756
2757```wdl
2758version 1.2
2759
2760task read_float {
2761 command <<<
2762 printf " 1 \n" > int_file
2763 printf " 2.0 \n" > float_file
2764 >>>
2765
2766 output {
2767 Float f1 = read_float("int_file")
2768 Float f2 = read_float("float_file")
2769 }
2770}
2771```
2772"#
2773 )
2774 .build(),
2775 )
2776 .into(),
2777 )
2778 .is_none()
2779 );
2780
2781 assert!(
2783 functions
2784 .insert(
2785 "read_boolean",
2786 MonomorphicFunction::new(
2787 FunctionSignature::builder()
2788 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2789 .ret(PrimitiveType::Boolean)
2790 .definition(
2791 r#"
2792Reads 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.
2793
2794**Parameters**
2795
27961. `File`: Path of the file to read.
2797
2798**Returns**: A `Boolean`.
2799
2800Example: read_bool_task.wdl
2801
2802```wdl
2803version 1.2
2804
2805task read_bool {
2806 command <<<
2807 printf " true \n" > true_file
2808 printf " FALSE \n" > false_file
2809 >>>
2810
2811 output {
2812 Boolean b1 = read_boolean("true_file")
2813 Boolean b2 = read_boolean("false_file")
2814 }
2815}
2816```
2817"#
2818 )
2819 .build(),
2820 )
2821 .into(),
2822 )
2823 .is_none()
2824 );
2825
2826 assert!(
2828 functions
2829 .insert(
2830 "read_lines",
2831 MonomorphicFunction::new(
2832 FunctionSignature::builder()
2833 .parameter("file", PrimitiveType::File, "Path of the file to read.")
2834 .ret(array_string.clone())
2835 .definition(
2836 r#"
2837Reads 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.
2838
2839The order of the lines in the returned `Array[String]` is the order in which the lines appear in the file.
2840
2841If the file is empty, an empty array is returned.
2842
2843**Parameters**
2844
28451. `File`: Path of the file to read.
2846
2847**Returns**: An `Array[String]` representation of the lines in the file.
2848
2849Example: grep_task.wdl
2850
2851```wdl
2852version 1.2
2853
2854task grep {
2855 input {
2856 String pattern
2857 File file
2858 }
2859
2860 command <<<
2861 grep '~{pattern}' ~{file}
2862 >>>
2863
2864 output {
2865 Array[String] matches = read_lines(stdout())
2866 }
2867
2868 requirements {
2869 container: "ubuntu:latest"
2870 }
2871}
2872```
2873"#
2874 )
2875 .build(),
2876 )
2877 .into(),
2878 )
2879 .is_none()
2880 );
2881
2882 assert!(
2884 functions
2885 .insert(
2886 "write_lines",
2887 MonomorphicFunction::new(
2888 FunctionSignature::builder()
2889 .parameter("array", array_string.clone(), "`Array` of strings to write.")
2890 .ret(PrimitiveType::File)
2891 .definition(
2892 r#"
2893Writes 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.
2894
2895**Parameters**
2896
28971. `Array[String]`: Array of strings to write.
2898
2899**Returns**: A `File`.
2900
2901Example: write_lines_task.wdl
2902
2903```wdl
2904version 1.2
2905
2906task write_lines {
2907 input {
2908 Array[String] array = ["first", "second", "third"]
2909 }
2910
2911 command <<<
2912 paste -s -d'\t' ~{write_lines(array)}
2913 >>>
2914
2915 output {
2916 String s = read_string(stdout())
2917 }
2918
2919 requirements {
2920 container: "ubuntu:latest"
2921 }
2922}
2923```
2924"#
2925 )
2926 .build(),
2927 )
2928 .into(),
2929 )
2930 .is_none()
2931 );
2932
2933 const READ_TSV_DEFINITION: &str = r#"
2934Reads 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.
2935
2936This function has three variants:
2937
29381. `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.
29392. `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.
29403. `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).
2941
2942If the file is empty, an empty array is returned.
2943
2944If 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.
2945
2946**Parameters**
2947
29481. `File`: The TSV file to read.
29492. `Boolean`: (Optional) Whether to treat the file's first line as a header.
29503. `Array[String]`: (Optional) An array of field names. If specified, then the second parameter is also required.
2951
2952**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.
2953
2954Example: read_tsv_task.wdl
2955
2956```wdl
2957version 1.2
2958
2959task read_tsv {
2960 command <<<
2961 {
2962 printf "row1\tvalue1\n"
2963 printf "row2\tvalue2\n"
2964 printf "row3\tvalue3\n"
2965 } >> data.no_headers.tsv
2966
2967 {
2968 printf "header1\theader2\n"
2969 printf "row1\tvalue1\n"
2970 printf "row2\tvalue2\n"
2971 printf "row3\tvalue3\n"
2972 } >> data.headers.tsv
2973 >>>
2974
2975 output {
2976 Array[Array[String]] output_table = read_tsv("data.no_headers.tsv")
2977 Array[Object] output_objs1 = read_tsv("data.no_headers.tsv", false, ["name", "value"])
2978 Array[Object] output_objs2 = read_tsv("data.headers.tsv", true)
2979 Array[Object] output_objs3 = read_tsv("data.headers.tsv", true, ["name", "value"])
2980 }
2981}
2982```
2983"#;
2984
2985 assert!(
2987 functions
2988 .insert(
2989 "read_tsv",
2990 PolymorphicFunction::new(vec![
2991 FunctionSignature::builder()
2992 .parameter("file", PrimitiveType::File, "The TSV file to read.")
2993 .ret(array_array_string.clone())
2994 .definition(READ_TSV_DEFINITION)
2995 .build(),
2996 FunctionSignature::builder()
2997 .min_version(SupportedVersion::V1(V1::Two))
2998 .parameter("file", PrimitiveType::File, "The TSV file to read.")
2999 .parameter(
3000 "header",
3001 PrimitiveType::Boolean,
3002 "(Optional) Whether to treat the file's first line as a header.",
3003 )
3004 .ret(array_object.clone())
3005 .definition(READ_TSV_DEFINITION)
3006 .build(),
3007 FunctionSignature::builder()
3008 .min_version(SupportedVersion::V1(V1::Two))
3009 .parameter("file", PrimitiveType::File, "The TSV file to read.")
3010 .parameter(
3011 "header",
3012 PrimitiveType::Boolean,
3013 "(Optional) Whether to treat the file's first line as a header.",
3014 )
3015 .parameter(
3016 "columns",
3017 array_string.clone(),
3018 "(Optional) An array of field names. If specified, then the second \
3019 parameter is also required.",
3020 )
3021 .ret(array_object.clone())
3022 .definition(READ_TSV_DEFINITION)
3023 .build(),
3024 ])
3025 .into(),
3026 )
3027 .is_none()
3028 );
3029
3030 const WRITE_TSV_DEFINITION: &str = r#"
3031Given an `Array` of elements, writes a tab-separated value (TSV) file with one line for each element.
3032
3033There are three variants of this function:
3034
30351. `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.
3036
30372. `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.
3038
30393. `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.
3040
3041Each line is terminated by the newline (`\n`) character.
3042
3043The 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.
3044
3045If 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.
3046
3047
3048**Parameters**
3049
30501. `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.
30512. `Boolean`: (Optional) Whether to write a header row.
30523. `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`.
3053
3054
3055**Returns**: A `File`.
3056
3057Example: write_tsv_task.wdl
3058
3059```wdl
3060version 1.2
3061
3062task write_tsv {
3063 input {
3064 Array[Array[String]] array = [["one", "two", "three"], ["un", "deux", "trois"]]
3065 Array[Numbers] structs = [
3066 Numbers {
3067 first: "one",
3068 second: "two",
3069 third: "three"
3070 },
3071 Numbers {
3072 first: "un",
3073 second: "deux",
3074 third: "trois"
3075 }
3076 ]
3077 }
3078
3079 command <<<
3080 cut -f 1 ~{write_tsv(array)} >> array_no_header.txt
3081 cut -f 1 ~{write_tsv(array, true, ["first", "second", "third"])} > array_header.txt
3082 cut -f 1 ~{write_tsv(structs)} >> structs_default.txt
3083 cut -f 2 ~{write_tsv(structs, false)} >> structs_no_header.txt
3084 cut -f 2 ~{write_tsv(structs, true)} >> structs_header.txt
3085 cut -f 3 ~{write_tsv(structs, true, ["no1", "no2", "no3"])} >> structs_user_header.txt
3086 >>>
3087
3088 output {
3089 Array[String] array_no_header = read_lines("array_no_header.txt")
3090 Array[String] array_header = read_lines("array_header.txt")
3091 Array[String] structs_default = read_lines("structs_default.txt")
3092 Array[String] structs_no_header = read_lines("structs_no_header.txt")
3093 Array[String] structs_header = read_lines("structs_header.txt")
3094 Array[String] structs_user_header = read_lines("structs_user_header.txt")
3095
3096 }
3097
3098 requirements {
3099 container: "ubuntu:latest"
3100 }
3101}
3102```
3103"#;
3104
3105 assert!(
3107 functions
3108 .insert(
3109 "write_tsv",
3110 PolymorphicFunction::new(vec![
3111 FunctionSignature::builder()
3112 .parameter(
3113 "data",
3114 array_array_string.clone(),
3115 "An array of rows, where each row is either an `Array` of column \
3116 values or a struct whose values are the column values.",
3117 )
3118 .ret(PrimitiveType::File)
3119 .definition(WRITE_TSV_DEFINITION)
3120 .build(),
3121 FunctionSignature::builder()
3122 .min_version(SupportedVersion::V1(V1::Two))
3123 .parameter(
3124 "data",
3125 array_array_string.clone(),
3126 "An array of rows, where each row is either an `Array` of column \
3127 values or a struct whose values are the column values.",
3128 )
3129 .parameter(
3130 "header",
3131 PrimitiveType::Boolean,
3132 "(Optional) Whether to write a header row.",
3133 )
3134 .parameter(
3135 "columns",
3136 array_string.clone(),
3137 "An array of column names. If the first argument is \
3138 `Array[Array[String]]` and the second argument is true then it is \
3139 required, otherwise it is optional. Ignored if the second argument \
3140 is false."
3141 )
3142 .ret(PrimitiveType::File)
3143 .definition(WRITE_TSV_DEFINITION)
3144 .build(),
3145 FunctionSignature::builder()
3146 .min_version(SupportedVersion::V1(V1::Two))
3147 .type_parameter("S", PrimitiveStructConstraint)
3148 .required(1)
3149 .parameter(
3150 "data",
3151 GenericArrayType::new(GenericType::Parameter("S")),
3152 "An array of rows, where each row is either an `Array` of column \
3153 values or a struct whose values are the column values.",
3154 )
3155 .parameter(
3156 "header",
3157 PrimitiveType::Boolean,
3158 "(Optional) Whether to write a header row.",
3159 )
3160 .parameter(
3161 "columns",
3162 array_string.clone(),
3163 "An array of column names. If the first argument is \
3164 `Array[Array[String]]` and the second argument is true then it is \
3165 required, otherwise it is optional. Ignored if the second argument \
3166 is false."
3167 )
3168 .ret(PrimitiveType::File)
3169 .definition(WRITE_TSV_DEFINITION)
3170 .build(),
3171 ])
3172 .into(),
3173 )
3174 .is_none()
3175 );
3176
3177 assert!(
3179 functions
3180 .insert(
3181 "read_map",
3182 MonomorphicFunction::new(
3183 FunctionSignature::builder()
3184 .parameter(
3185 "file",
3186 PrimitiveType::File,
3187 "Path of the two-column TSV file to read.",
3188 )
3189 .ret(map_string_string.clone())
3190 .definition(
3191 r#"
3192Reads 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.
3193
3194Each 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.
3195
3196If the file is empty, an empty map is returned.
3197
3198**Parameters**
3199
32001. `File`: Path of the two-column TSV file to read.
3201
3202**Returns**: A `Map[String, String]`, with one element for each row in the TSV file.
3203
3204Example: read_map_task.wdl
3205
3206```wdl
3207version 1.2
3208
3209task read_map {
3210 command <<<
3211 printf "key1\tvalue1\n" >> map_file
3212 printf "key2\tvalue2\n" >> map_file
3213 >>>
3214
3215 output {
3216 Map[String, String] mapping = read_map(stdout())
3217 }
3218}
3219```
3220"#
3221 )
3222 .build(),
3223 )
3224 .into(),
3225 )
3226 .is_none()
3227 );
3228
3229 assert!(
3231 functions
3232 .insert(
3233 "write_map",
3234 MonomorphicFunction::new(
3235 FunctionSignature::builder()
3236 .parameter(
3237 "map",
3238 map_string_string.clone(),
3239 "A `Map`, where each element will be a row in the generated file.",
3240 )
3241 .ret(PrimitiveType::File)
3242 .definition(
3243 r#"
3244Writes 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.
3245
3246Since `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`.
3247
3248**Parameters**
3249
32501. `Map[String, String]`: A `Map`, where each element will be a row in the generated file.
3251
3252**Returns**: A `File`.
3253
3254Example: write_map_task.wdl
3255
3256```wdl
3257version 1.2
3258
3259task write_map {
3260 input {
3261 Map[String, String] map = {"key1": "value1", "key2": "value2"}
3262 }
3263
3264 command <<<
3265 cut -f 1 ~{write_map(map)}
3266 >>>
3267
3268 output {
3269 Array[String] keys = read_lines(stdout())
3270 }
3271
3272 requirements {
3273 container: "ubuntu:latest"
3274 }
3275}
3276```
3277"#
3278 )
3279 .build(),
3280 )
3281 .into(),
3282 )
3283 .is_none()
3284 );
3285
3286 assert!(
3288 functions
3289 .insert(
3290 "read_json",
3291 MonomorphicFunction::new(
3292 FunctionSignature::builder()
3293 .parameter("file", PrimitiveType::File, "Path of the JSON file to read.")
3294 .ret(Type::Union)
3295 .definition(
3296 r#"
3297Reads a JSON file into a WDL value whose type depends on the file's contents. The mapping of JSON type to WDL type is:
3298
3299| JSON Type | WDL Type |
3300| --------- | ---------------- |
3301| object | `Object` |
3302| array | `Array[X]` |
3303| number | `Int` or `Float` |
3304| string | `String` |
3305| boolean | `Boolean` |
3306| null | `None` |
3307
3308The 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.
3309
3310If 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.
3311
3312The `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.
3313
3314Note that an empty file is not valid according to the JSON specification, and so calling `read_json` on an empty file raises an error.
3315
3316**Parameters**
3317
33181. `File`: Path of the JSON file to read.
3319
3320**Returns**: A value whose type is dependent on the contents of the JSON file.
3321
3322Example: read_person.wdl
3323
3324```wdl
3325version 1.2
3326
3327struct Person {
3328 String name
3329 Int age
3330}
3331
3332workflow read_person {
3333 input {
3334 File json_file
3335 }
3336
3337 output {
3338 Person p = read_json(json_file)
3339 }
3340}
3341```
3342"#
3343 )
3344 .build(),
3345 )
3346 .into(),
3347 )
3348 .is_none()
3349 );
3350
3351 assert!(
3353 functions
3354 .insert(
3355 "write_json",
3356 MonomorphicFunction::new(
3357 FunctionSignature::builder()
3358 .type_parameter("X", JsonSerializableConstraint)
3359 .parameter(
3360 "value",
3361 GenericType::Parameter("X"),
3362 "A WDL value of a supported type.",
3363 )
3364 .ret(PrimitiveType::File)
3365 .definition(
3366 r#"
3367Writes a JSON file with the serialized form of a WDL value. The following WDL types can be serialized:
3368
3369| WDL Type | JSON Type |
3370| ---------------- | --------- |
3371| `Struct` | object |
3372| `Object` | object |
3373| `Map[String, X]` | object |
3374| `Array[X]` | array |
3375| `Int` | number |
3376| `Float` | number |
3377| `String` | string |
3378| `File` | string |
3379| `Boolean` | boolean |
3380| `None` | null |
3381
3382When serializing compound types, all nested types must be serializable or an error is raised.
3383
3384**Parameters**
3385
33861. `X`: A WDL value of a supported type.
3387
3388**Returns**: A `File`.
3389
3390Example: write_json_fail.wdl
3391
3392```wdl
3393version 1.2
3394
3395workflow write_json_fail {
3396 Pair[Int, Map[Int, String]] x = (1, {2: "hello"})
3397 # this fails with an error - Map with Int keys is not serializable
3398 File f = write_json(x)
3399}
3400```
3401"#
3402 )
3403 .build(),
3404 )
3405 .into(),
3406 )
3407 .is_none()
3408 );
3409
3410 assert!(
3412 functions
3413 .insert(
3414 "read_object",
3415 MonomorphicFunction::new(
3416 FunctionSignature::builder()
3417 .parameter(
3418 "file",
3419 PrimitiveType::File,
3420 "Path of the two-row TSV file to read.",
3421 )
3422 .ret(Type::Object)
3423 .definition(
3424 r#"
3425Reads 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.
3426
3427The 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.
3428
3429The 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`.
3430
3431**Parameters**
3432
34331. `File`: Path of the two-row TSV file to read.
3434
3435**Returns**: An `Object`, with as many members as there are unique names in the TSV.
3436
3437Example: read_object_task.wdl
3438
3439```wdl
3440version 1.2
3441
3442task read_object {
3443 command <<<
3444 python <<CODE
3445 print('\t'.join(["key_{}".format(i) for i in range(3)]))
3446 print('\t'.join(["value_{}".format(i) for i in range(3)]))
3447 CODE
3448 >>>
3449
3450 output {
3451 Object my_obj = read_object(stdout())
3452 }
3453
3454 requirements {
3455 container: "python:latest"
3456 }
3457}
3458```
3459"#
3460 )
3461 .build(),
3462 )
3463 .into(),
3464 )
3465 .is_none()
3466 );
3467
3468 assert!(
3470 functions
3471 .insert(
3472 "read_objects",
3473 MonomorphicFunction::new(
3474 FunctionSignature::builder()
3475 .parameter("file", PrimitiveType::File, "The file to read.")
3476 .ret(array_object.clone())
3477 .definition(
3478 r#"
3479Reads 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.
3480
3481The 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.
3482
3483There 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`.
3484
3485If the file is empty or contains only a header line, an empty array is returned.
3486
3487**Parameters**
3488
34891. `File`: Path of the TSV file to read.
3490
3491**Returns**: An `Array[Object]`, with `N-1` elements, where `N` is the number of rows in the file.
3492
3493Example: read_objects_task.wdl
3494
3495```wdl
3496version 1.2
3497
3498task read_objects {
3499 command <<<
3500 python <<CODE
3501 print('\t'.join(["key_{}".format(i) for i in range(3)]))
3502 print('\t'.join(["value_A{}".format(i) for i in range(3)]))
3503 print('\t'.join(["value_B{}".format(i) for i in range(3)]))
3504 print('\t'.join(["value_C{}".format(i) for i in range(3)]))
3505 CODE
3506 >>>
3507
3508 output {
3509 Array[Object] my_obj = read_objects(stdout())
3510 }
3511"#
3512 )
3513 .build(),
3514 )
3515 .into(),
3516 )
3517 .is_none()
3518 );
3519
3520 const WRITE_OBJECT_DEFINITION: &str = r#"
3521Writes 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.
3522
3523Each line is terminated by the newline (`\n`) character.
3524
3525The 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.
3526
3527If 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.
3528
3529**Parameters**
3530
35311. `Object`: An `Object` whose members will be written to the file.
3532
3533**Returns**: A `File`.
3534
3535Example: write_object_task.wdl
3536
3537```wdl
3538version 1.2
3539
3540task write_object {
3541 input {
3542 Object my_obj = {"key_0": "value_A0", "key_1": "value_A1", "key_2": "value_A2"}
3543 }
3544
3545 command <<<
3546 cat ~{write_object(my_obj)}
3547 >>>
3548
3549 output {
3550 Object new_obj = read_object(stdout())
3551 }
3552}
3553```
3554"#;
3555
3556 assert!(
3558 functions
3559 .insert(
3560 "write_object",
3561 PolymorphicFunction::new(vec![
3562 FunctionSignature::builder()
3563 .parameter("object", Type::Object, "An object to write.")
3564 .ret(PrimitiveType::File)
3565 .definition(WRITE_OBJECT_DEFINITION)
3566 .build(),
3567 FunctionSignature::builder()
3568 .min_version(SupportedVersion::V1(V1::One))
3569 .type_parameter("S", PrimitiveStructConstraint)
3570 .parameter("object", GenericType::Parameter("S"), "An object to write.")
3571 .ret(PrimitiveType::File)
3572 .definition(WRITE_OBJECT_DEFINITION)
3573 .build(),
3574 ])
3575 .into(),
3576 )
3577 .is_none()
3578 );
3579
3580 const WRITE_OBJECTS_DEFINITION: &str = r#"
3581Writes 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.
3582
3583Each line is terminated by the newline (`\n`) character.
3584
3585The 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.
3586
3587If 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.
3588
3589**Parameters**
3590
35911. `Array[Object]`: An `Array[Object]` whose elements will be written to the file.
3592
3593**Returns**: A `File`.
3594
3595Example: write_objects_task.wdl
3596
3597```wdl
3598version 1.2
3599
3600task write_objects {
3601 input {
3602 Array[Object] my_objs = [
3603 {"key_0": "value_A0", "key_1": "value_A1", "key_2": "value_A2"},
3604 {"key_0": "value_B0", "key_1": "value_B1", "key_2": "value_B2"},
3605 {"key_0": "value_C0", "key_1": "value_C1", "key_2": "value_C2"}
3606 ]
3607 }
3608
3609 command <<<
3610 cat ~{write_objects(my_objs)}
3611 >>>
3612
3613 output {
3614 Array[Object] new_objs = read_objects(stdout())
3615 }
3616}
3617```
3618"#;
3619
3620 assert!(
3622 functions
3623 .insert(
3624 "write_objects",
3625 PolymorphicFunction::new(vec![
3626 FunctionSignature::builder()
3627 .parameter("objects", array_object.clone(), "The objects to write.")
3628 .ret(PrimitiveType::File)
3629 .definition(WRITE_OBJECTS_DEFINITION)
3630 .build(),
3631 FunctionSignature::builder()
3632 .min_version(SupportedVersion::V1(V1::One))
3633 .type_parameter("S", PrimitiveStructConstraint)
3634 .parameter(
3635 "objects",
3636 GenericArrayType::new(GenericType::Parameter("S")),
3637 "The objects to write."
3638 )
3639 .ret(PrimitiveType::File)
3640 .definition(WRITE_OBJECTS_DEFINITION)
3641 .build(),
3642 ])
3643 .into(),
3644 )
3645 .is_none()
3646 );
3647
3648 assert!(
3650 functions
3651 .insert(
3652 "prefix",
3653 MonomorphicFunction::new(
3654 FunctionSignature::builder()
3655 .type_parameter("P", PrimitiveTypeConstraint)
3656 .parameter(
3657 "prefix",
3658 PrimitiveType::String,
3659 "The prefix to prepend to each element in the array.",
3660 )
3661 .parameter(
3662 "array",
3663 GenericArrayType::new(GenericType::Parameter("P")),
3664 "Array with a primitive element type.",
3665 )
3666 .ret(array_string.clone())
3667 .definition(
3668 r#"
3669Given 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.
3670
3671**Parameters**
3672
36731. `String`: The string to prepend.
36742. `Array[X]`: The array whose elements will be prepended.
3675
3676**Returns**: A new `Array[String]` with the prepended elements.
3677
3678Example: prefix_task.wdl
3679
3680```wdl
3681version 1.2
3682
3683task prefix {
3684 input {
3685 Array[Int] ints = [1, 2, 3]
3686 }
3687
3688 output {
3689 Array[String] prefixed_ints = prefix("file_", ints) # ["file_1", "file_2", "file_3"]
3690 }
3691}
3692```
3693"#
3694 )
3695 .build(),
3696 )
3697 .into(),
3698 )
3699 .is_none()
3700 );
3701
3702 assert!(
3704 functions
3705 .insert(
3706 "suffix",
3707 MonomorphicFunction::new(
3708 FunctionSignature::builder()
3709 .min_version(SupportedVersion::V1(V1::One))
3710 .type_parameter("P", PrimitiveTypeConstraint)
3711 .parameter(
3712 "suffix",
3713 PrimitiveType::String,
3714 "The suffix to append to each element in the array.",
3715 )
3716 .parameter(
3717 "array",
3718 GenericArrayType::new(GenericType::Parameter("P")),
3719 "Array with a primitive element type.",
3720 )
3721 .ret(array_string.clone())
3722 .definition(
3723 r#"
3724Given 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.
3725
3726**Parameters**
3727
37281. `String`: The string to append.
37292. `Array[X]`: The array whose elements will be appended.
3730
3731**Returns**: A new `Array[String]` with the appended elements.
3732
3733Example: suffix_task.wdl
3734
3735```wdl
3736version 1.2
3737
3738task suffix {
3739 input {
3740 Array[Int] ints = [1, 2, 3]
3741 }
3742
3743 output {
3744 Array[String] suffixed_ints = suffix(".txt", ints) # ["1.txt", "2.txt", "3.txt"]
3745 }
3746}
3747```
3748"#
3749 )
3750 .build(),
3751 )
3752 .into(),
3753 )
3754 .is_none()
3755 );
3756
3757 assert!(
3759 functions
3760 .insert(
3761 "quote",
3762 MonomorphicFunction::new(
3763 FunctionSignature::builder()
3764 .min_version(SupportedVersion::V1(V1::One))
3765 .type_parameter("P", PrimitiveTypeConstraint)
3766 .parameter(
3767 "array",
3768 GenericArrayType::new(GenericType::Parameter("P")),
3769 "Array with a primitive element type.",
3770 )
3771 .ret(array_string.clone())
3772 .definition(
3773 r#"
3774Given 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.
3775
3776**Parameters**
3777
37781. `Array[X]`: The array whose elements will be quoted.
3779
3780**Returns**: A new `Array[String]` with the quoted elements.
3781
3782Example: quote_task.wdl
3783
3784```wdl
3785version 1.2
3786
3787task quote {
3788 input {
3789 Array[String] strings = ["hello", "world"]
3790 }
3791
3792 output {
3793 Array[String] quoted_strings = quote(strings) # ["\"hello\"", "\"world\""]
3794 }
3795}
3796```
3797"#
3798 )
3799 .build(),
3800 )
3801 .into(),
3802 )
3803 .is_none()
3804 );
3805
3806 assert!(
3808 functions
3809 .insert(
3810 "squote",
3811 MonomorphicFunction::new(
3812 FunctionSignature::builder()
3813 .min_version(SupportedVersion::V1(V1::One))
3814 .type_parameter("P", PrimitiveTypeConstraint)
3815 .parameter("array", GenericArrayType::new(GenericType::Parameter("P")), "The array of values.") .ret(array_string.clone())
3816 .definition(
3817 r#"
3818Given 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.
3819
3820**Parameters**
3821
38221. `Array[X]`: The array whose elements will be single-quoted.
3823
3824**Returns**: A new `Array[String]` with the single-quoted elements.
3825
3826Example: squote_task.wdl
3827
3828```wdl
3829version 1.2
3830
3831task squote {
3832 input {
3833 Array[String] strings = ["hello", "world"]
3834 }
3835
3836 output {
3837 Array[String] squoted_strings = squote(strings) # ["'hello'", "'world'"]
3838 }
3839}
3840```
3841"#
3842 )
3843 .build(),
3844 )
3845 .into(),
3846 )
3847 .is_none()
3848 );
3849
3850 assert!(
3852 functions
3853 .insert(
3854 "sep",
3855 MonomorphicFunction::new(
3856 FunctionSignature::builder()
3857 .min_version(SupportedVersion::V1(V1::One))
3858 .type_parameter("P", PrimitiveTypeConstraint)
3859 .parameter("separator", PrimitiveType::String, "Separator string.")
3860 .parameter(
3861 "array",
3862 GenericArrayType::new(GenericType::Parameter("P")),
3863 "`Array` of strings to concatenate.",
3864 )
3865 .ret(PrimitiveType::String)
3866 .definition(
3867 r#"
3868Given 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.
3869
3870**Parameters**
3871
38721. `String`: The string to use as a separator.
38732. `Array[X]`: The array whose elements will be joined.
3874
3875**Returns**: A new `String` with the joined elements.
3876
3877Example: sep_task.wdl
3878
3879```wdl
3880version 1.2
3881
3882task sep {
3883 input {
3884 Array[Int] ints = [1, 2, 3]
3885 }
3886
3887 output {
3888 String joined_ints = sep(",", ints) # "1,2,3"
3889 }
3890}
3891```
3892"#
3893 )
3894 .build(),
3895 )
3896 .into(),
3897 )
3898 .is_none()
3899 );
3900
3901 assert!(
3903 functions
3904 .insert(
3905 "range",
3906 MonomorphicFunction::new(
3907 FunctionSignature::builder()
3908 .parameter("n", PrimitiveType::Integer, "The length of array to create.")
3909 .ret(array_int.clone())
3910 .definition(
3911 r#"
3912Returns 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.
3913
3914**Parameters**
3915
39161. `Int`: The upper bound (exclusive) of the range.
3917
3918**Returns**: An `Array[Int]` of integers.
3919
3920Example: range_task.wdl
3921
3922```wdl
3923version 1.2
3924
3925task range {
3926 input {
3927 Int n = 5
3928 }
3929
3930 output {
3931 Array[Int] r = range(n) # [0, 1, 2, 3, 4]
3932 }
3933}
3934```
3935"#
3936 )
3937 .build(),
3938 )
3939 .into(),
3940 )
3941 .is_none()
3942 );
3943
3944 assert!(
3946 functions
3947 .insert(
3948 "transpose",
3949 MonomorphicFunction::new(
3950 FunctionSignature::builder()
3951 .any_type_parameter("X")
3952 .parameter(
3953 "array",
3954 GenericArrayType::new(GenericArrayType::new(
3955 GenericType::Parameter("X"),
3956 )),
3957 "A M*N two-dimensional array.",
3958 )
3959 .ret(GenericArrayType::new(GenericArrayType::new(
3960 GenericType::Parameter("X"),
3961 )))
3962 .definition(
3963 r#"
3964Given 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.
3965
3966If the inner arrays are not all the same length, an error is raised.
3967
3968**Parameters**
3969
39701. `Array[Array[X]]`: The array to transpose.
3971
3972**Returns**: A new `Array[Array[X]]` with the rows and columns swapped.
3973
3974Example: transpose_task.wdl
3975
3976```wdl
3977version 1.2
3978
3979task transpose {
3980 input {
3981 Array[Array[Int]] matrix = [[1, 2, 3], [4, 5, 6]]
3982 }
3983
3984 output {
3985 Array[Array[Int]] transposed_matrix = transpose(matrix) # [[1, 4], [2, 5], [3, 6]]
3986 }
3987}
3988```
3989"#
3990 )
3991 .build(),
3992 )
3993 .into(),
3994 )
3995 .is_none()
3996 );
3997
3998 assert!(
4000 functions
4001 .insert(
4002 "cross",
4003 MonomorphicFunction::new(
4004 FunctionSignature::builder()
4005 .any_type_parameter("X")
4006 .any_type_parameter("Y")
4007 .parameter("a", GenericArrayType::new(GenericType::Parameter("X")), "The first array of length M.")
4008 .parameter("b", GenericArrayType::new(GenericType::Parameter("Y")), "The second array of length N.")
4009 .ret(GenericArrayType::new(GenericPairType::new(
4010 GenericType::Parameter("X"),
4011 GenericType::Parameter("Y"),
4012 )))
4013 .definition(
4014 r#"
4015Given 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.
4016
4017If either `a` or `b` is empty, an empty array is returned.
4018
4019**Parameters**
4020
40211. `Array[X]`: The first array.
40222. `Array[Y]`: The second array.
4023
4024**Returns**: A new `Array[Pair[X, Y]]` with the cross product of the two arrays.
4025
4026Example: cross_task.wdl
4027
4028```wdl
4029version 1.2
4030
4031task cross {
4032 input {
4033 Array[Int] ints = [1, 2]
4034 Array[String] strings = ["a", "b"]
4035 }
4036
4037 output {
4038 Array[Pair[Int, String]] crossed = cross(ints, strings) # [(1, "a"), (1, "b"), (2, "a"), (2, "b")]
4039 }
4040}
4041```
4042"#
4043 )
4044 .build(),
4045 )
4046 .into(),
4047 )
4048 .is_none()
4049 );
4050
4051 assert!(
4053 functions
4054 .insert(
4055 "zip",
4056 MonomorphicFunction::new(
4057 FunctionSignature::builder()
4058 .any_type_parameter("X")
4059 .any_type_parameter("Y")
4060 .parameter("a", GenericArrayType::new(GenericType::Parameter("X")), "The first array of length M.")
4061 .parameter("b", GenericArrayType::new(GenericType::Parameter("Y")), "The second array of length N.")
4062 .ret(GenericArrayType::new(GenericPairType::new(
4063 GenericType::Parameter("X"),
4064 GenericType::Parameter("Y"),
4065 )))
4066 .definition(
4067 r#"
4068Given 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`.
4069
4070If either `a` or `b` is empty, an empty array is returned.
4071
4072**Parameters**
4073
40741. `Array[X]`: The first array.
40752. `Array[Y]`: The second array.
4076
4077**Returns**: A new `Array[Pair[X, Y]]` with the zipped elements.
4078
4079Example: zip_task.wdl
4080
4081```wdl
4082version 1.2
4083
4084task zip {
4085 input {
4086 Array[Int] ints = [1, 2, 3]
4087 Array[String] strings = ["a", "b"]
4088 }
4089
4090 output {
4091 Array[Pair[Int, String]] zipped = zip(ints, strings) # [(1, "a"), (2, "b")]
4092 }
4093}
4094```
4095"#
4096 )
4097 .build(),
4098 )
4099 .into(),
4100 )
4101 .is_none()
4102 );
4103
4104 assert!(
4106 functions
4107 .insert(
4108 "unzip",
4109 MonomorphicFunction::new(
4110 FunctionSignature::builder()
4111 .min_version(SupportedVersion::V1(V1::One))
4112 .any_type_parameter("X")
4113 .any_type_parameter("Y")
4114 .parameter(
4115 "array",
4116 GenericArrayType::new(GenericPairType::new(
4117 GenericType::Parameter("X"),
4118 GenericType::Parameter("Y"),
4119 )),
4120 "The `Array` of `Pairs` of length N to unzip.",
4121 )
4122 .ret(GenericPairType::new(
4123 GenericArrayType::new(GenericType::Parameter("X")),
4124 GenericArrayType::new(GenericType::Parameter("Y")),
4125 ))
4126 .definition(
4127 r#"
4128Given 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`.
4129
4130If `a` is empty, a `Pair` of two empty arrays is returned.
4131
4132**Parameters**
4133
41341. `Array[Pair[X, Y]]`: The array of pairs to unzip.
4135
4136**Returns**: A new `Pair[Array[X], Array[Y]]` with the unzipped elements.
4137
4138Example: unzip_task.wdl
4139
4140```wdl
4141version 1.2
4142
4143task unzip {
4144 input {
4145 Array[Pair[Int, String]] zipped = [(1, "a"), (2, "b")]
4146 }
4147
4148 output {
4149 Pair[Array[Int], Array[String]] unzipped = unzip(zipped) # ([1, 2], ["a", "b"])
4150 }
4151}
4152```
4153"#
4154 )
4155 .build(),
4156 )
4157 .into(),
4158 )
4159 .is_none()
4160 );
4161
4162 assert!(
4164 functions
4165 .insert(
4166 "contains",
4167 MonomorphicFunction::new(
4168 FunctionSignature::builder()
4169 .min_version(SupportedVersion::V1(V1::Two))
4170 .type_parameter("P", PrimitiveTypeConstraint)
4171 .parameter(
4172 "array",
4173 GenericArrayType::new(GenericType::Parameter("P")),
4174 "An array of any primitive type.",
4175 )
4176 .parameter(
4177 "value",
4178 GenericType::Parameter("P"),
4179 "A primitive value of the same type as the array. If the array's \
4180 type is optional, then the value may also be optional.",
4181 )
4182 .ret(PrimitiveType::Boolean)
4183 .definition(
4184 r#"
4185Given an `Array[X]` `a` and a value `v` of type `X`, returns `true` if `v` is present in `a`, otherwise `false`.
4186
4187**Parameters**
4188
41891. `Array[X]`: The array to search.
41902. `X`: The value to search for.
4191
4192**Returns**: `true` if `v` is present in `a`, otherwise `false`.
4193
4194Example: contains_task.wdl
4195
4196```wdl
4197version 1.2
4198
4199task contains {
4200 input {
4201 Array[Int] ints = [1, 2, 3]
4202 }
4203
4204 output {
4205 Boolean contains_2 = contains(ints, 2) # true
4206 Boolean contains_4 = contains(ints, 4) # false
4207 }
4208}
4209```
4210"#
4211 )
4212 .build(),
4213 )
4214 .into(),
4215 )
4216 .is_none()
4217 );
4218
4219 assert!(
4221 functions
4222 .insert(
4223 "chunk",
4224 MonomorphicFunction::new(
4225 FunctionSignature::builder()
4226 .min_version(SupportedVersion::V1(V1::Two))
4227 .any_type_parameter("X")
4228 .parameter(
4229 "array",
4230 GenericArrayType::new(GenericType::Parameter("X")),
4231 "The array to split. May be empty.",
4232 )
4233 .parameter("size", PrimitiveType::Integer, "The desired length of the sub-arrays. Must be > 0.")
4234 .ret(GenericArrayType::new(GenericArrayType::new(
4235 GenericType::Parameter("X"),
4236 )))
4237 .definition(
4238 r#"
4239Given 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.
4240
4241If `size` is less than or equal to `0`, an error is raised.
4242
4243**Parameters**
4244
42451. `Array[X]`: The array to chunk.
42462. `Int`: The maximum size of each chunk.
4247
4248**Returns**: A new `Array[Array[X]]` with the chunked elements.
4249
4250Example: chunk_task.wdl
4251
4252```wdl
4253version 1.2
4254
4255task chunk {
4256 input {
4257 Array[Int] ints = [1, 2, 3, 4, 5]
4258 }
4259
4260 output {
4261 Array[Array[Int]] chunked = chunk(ints, 2) # [[1, 2], [3, 4], [5]]
4262 }
4263}
4264```
4265"#
4266 )
4267 .build(),
4268 )
4269 .into(),
4270 )
4271 .is_none()
4272 );
4273
4274 assert!(
4276 functions
4277 .insert(
4278 "flatten",
4279 MonomorphicFunction::new(
4280 FunctionSignature::builder()
4281 .any_type_parameter("X")
4282 .parameter(
4283 "array",
4284 GenericArrayType::new(GenericArrayType::new(
4285 GenericType::Parameter("X"),
4286 )),
4287 "A nested array to flatten.",
4288 )
4289 .ret(GenericArrayType::new(GenericType::Parameter("X")))
4290 .definition(
4291 r#"
4292Given 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.
4293
4294**Parameters**
4295
42961. `Array[Array[X]]`: The array to flatten.
4297
4298**Returns**: A new `Array[X]` with the flattened elements.
4299
4300Example: flatten_task.wdl
4301
4302```wdl
4303version 1.2
4304
4305task flatten {
4306 input {
4307 Array[Array[Int]] nested_ints = [[1, 2], [3, 4], [5]]
4308 }
4309
4310 output {
4311 Array[Int] flattened_ints = flatten(nested_ints) # [1, 2, 3, 4, 5]
4312 }
4313}
4314```
4315"#
4316 )
4317 .build(),
4318 )
4319 .into(),
4320 )
4321 .is_none()
4322 );
4323
4324 assert!(
4326 functions
4327 .insert(
4328 "select_first",
4329 MonomorphicFunction::new(
4332 FunctionSignature::builder()
4333 .any_type_parameter("X")
4334 .required(1)
4335 .parameter(
4336 "array",
4337 GenericArrayType::new(GenericType::Parameter("X")),
4338 "Non-empty `Array` of optional values.",
4339 )
4340 .parameter("default", GenericType::UnqualifiedParameter("X"), "(Optional) The default value.")
4341 .ret(GenericType::UnqualifiedParameter("X"))
4342 .definition(
4343 r#"
4344Given an `Array[X?]` `a`, returns the first non-`None` element in `a`. If all elements are `None`, an error is raised.
4345
4346**Parameters**
4347
43481. `Array[X?]`: The array to search.
4349
4350**Returns**: The first non-`None` element.
4351
4352Example: select_first_task.wdl
4353
4354```wdl
4355version 1.2
4356
4357task select_first {
4358 input {
4359 Array[Int?] ints = [None, 1, None, 2]
4360 }
4361
4362 output {
4363 Int first_int = select_first(ints) # 1
4364 }
4365}
4366```
4367"#
4368 )
4369 .build(),
4370 )
4371 .into(),
4372 )
4373 .is_none()
4374 );
4375
4376 assert!(
4378 functions
4379 .insert(
4380 "select_all",
4381 MonomorphicFunction::new(
4382 FunctionSignature::builder()
4383 .any_type_parameter("X")
4384 .parameter(
4385 "array",
4386 GenericArrayType::new(GenericType::Parameter("X")),
4387 "`Array` of optional values.",
4388 )
4389 .ret(GenericArrayType::new(GenericType::UnqualifiedParameter(
4390 "X"
4391 )))
4392 .definition(
4393 r#"
4394Given 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.
4395
4396**Parameters**
4397
43981. `Array[X?]`: The array to filter.
4399
4400**Returns**: A new `Array[X]` with all the non-`None` elements.
4401
4402Example: select_all_task.wdl
4403
4404```wdl
4405version 1.2
4406
4407task select_all {
4408 input {
4409 Array[Int?] ints = [None, 1, None, 2]
4410 }
4411
4412 output {
4413 Array[Int] all_ints = select_all(ints) # [1, 2]
4414 }
4415}
4416```
4417"#
4418 )
4419 .build(),
4420 )
4421 .into(),
4422 )
4423 .is_none()
4424 );
4425
4426 assert!(
4428 functions
4429 .insert(
4430 "as_pairs",
4431 MonomorphicFunction::new(
4432 FunctionSignature::builder()
4433 .min_version(SupportedVersion::V1(V1::One))
4434 .type_parameter("K", PrimitiveTypeConstraint)
4435 .any_type_parameter("V")
4436 .parameter(
4437 "map",
4438 GenericMapType::new(
4439 GenericType::Parameter("K"),
4440 GenericType::Parameter("V"),
4441 ),
4442 "`Map` to convert to `Pairs`.",
4443 )
4444 .ret(GenericArrayType::new(GenericPairType::new(
4445 GenericType::Parameter("K"),
4446 GenericType::Parameter("V")
4447 )))
4448 .definition(
4449 r#"
4450Given 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`.
4451
4452If `m` is empty, an empty array is returned.
4453
4454**Parameters**
4455
44561. `Map[K, V]`: The map to convert.
4457
4458**Returns**: A new `Array[Pair[K, V]]` with the key-value pairs.
4459
4460Example: as_pairs_task.wdl
4461
4462```wdl
4463version 1.2
4464
4465task as_pairs {
4466 input {
4467 Map[String, Int] map = {"a": 1, "b": 2}
4468 }
4469
4470 output {
4471 Array[Pair[String, Int]] pairs = as_pairs(map) # [("a", 1), ("b", 2)]
4472 }
4473}
4474```
4475"#
4476 )
4477 .build(),
4478 )
4479 .into(),
4480 )
4481 .is_none()
4482 );
4483
4484 assert!(
4486 functions
4487 .insert(
4488 "as_map",
4489 MonomorphicFunction::new(
4490 FunctionSignature::builder()
4491 .min_version(SupportedVersion::V1(V1::One))
4492 .type_parameter("K", PrimitiveTypeConstraint)
4493 .any_type_parameter("V")
4494 .parameter(
4495 "pairs",
4496 GenericArrayType::new(GenericPairType::new(
4497 GenericType::Parameter("K"),
4498 GenericType::Parameter("V"),
4499 )),
4500 "`Array` of `Pairs` to convert to a `Map`.",
4501 )
4502 .ret(GenericMapType::new(
4503 GenericType::Parameter("K"),
4504 GenericType::Parameter("V")
4505 ))
4506 .definition(
4507 r#"
4508Given 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.
4509
4510If there are any duplicate keys in `a`, an error is raised.
4511
4512**Parameters**
4513
45141. `Array[Pair[K, V]]`: The array of pairs to convert.
4515
4516**Returns**: A new `Map[K, V]` with the key-value pairs.
4517
4518Example: as_map_task.wdl
4519
4520```wdl
4521version 1.2
4522
4523task as_map {
4524 input {
4525 Array[Pair[String, Int]] pairs = [("a", 1), ("b", 2)]
4526 }
4527
4528 output {
4529 Map[String, Int] map = as_map(pairs) # {"a": 1, "b": 2}
4530 }
4531}
4532```
4533"#
4534 )
4535 .build(),
4536 )
4537 .into(),
4538 )
4539 .is_none()
4540 );
4541
4542 const KEYS_DEFINITION: &str = r#"
4543Given 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`.
4544
4545If `m` is empty, an empty array is returned.
4546
4547**Parameters**
4548
45491. `Map[K, V]`: The map to get the keys from.
4550
4551**Returns**: A new `Array[K]` with the keys.
4552
4553Example: keys_map_task.wdl
4554
4555```wdl
4556version 1.2
4557
4558task keys_map {
4559 input {
4560 Map[String, Int] map = {"a": 1, "b": 2}
4561 }
4562
4563 output {
4564 Array[String] keys = keys(map) # ["a", "b"]
4565 }
4566}
4567```
4568"#;
4569
4570 assert!(
4572 functions
4573 .insert(
4574 "keys",
4575 PolymorphicFunction::new(vec![
4576 FunctionSignature::builder()
4577 .min_version(SupportedVersion::V1(V1::One))
4578 .type_parameter("K", PrimitiveTypeConstraint)
4579 .any_type_parameter("V")
4580 .parameter(
4581 "map",
4582 GenericMapType::new(
4583 GenericType::Parameter("K"),
4584 GenericType::Parameter("V"),
4585 ),
4586 "Collection from which to extract keys.",
4587 )
4588 .ret(GenericArrayType::new(GenericType::Parameter("K")))
4589 .definition(KEYS_DEFINITION)
4590 .build(),
4591 FunctionSignature::builder()
4592 .min_version(SupportedVersion::V1(V1::Two))
4593 .type_parameter("S", StructConstraint)
4594 .parameter(
4595 "struct",
4596 GenericType::Parameter("S"),
4597 "Collection from which to extract keys.",
4598 )
4599 .ret(array_string.clone())
4600 .definition(KEYS_DEFINITION)
4601 .build(),
4602 FunctionSignature::builder()
4603 .min_version(SupportedVersion::V1(V1::Two))
4604 .parameter(
4605 "object",
4606 Type::Object,
4607 "Collection from which to extract keys.",
4608 )
4609 .ret(array_string.clone())
4610 .definition(KEYS_DEFINITION)
4611 .build(),
4612 ])
4613 .into(),
4614 )
4615 .is_none()
4616 );
4617
4618 const CONTAINS_KEY_DEFINITION: &str = r#"
4619Given a `Map[K, V]` `m` and a key `k` of type `K`, returns `true` if `k` is present in `m`, otherwise `false`.
4620
4621**Parameters**
4622
46231. `Map[K, V]`: The map to search.
46242. `K`: The key to search for.
4625
4626**Returns**: `true` if `k` is present in `m`, otherwise `false`.
4627
4628Example: contains_key_map_task.wdl
4629
4630```wdl
4631version 1.2
4632
4633task contains_key_map {
4634 input {
4635 Map[String, Int] map = {"a": 1, "b": 2}
4636 }
4637
4638 output {
4639 Boolean contains_a = contains_key(map, "a") # true
4640 Boolean contains_c = contains_key(map, "c") # false
4641 }
4642}
4643```
4644"#;
4645
4646 assert!(
4648 functions
4649 .insert(
4650 "contains_key",
4651 PolymorphicFunction::new(vec![
4652 FunctionSignature::builder()
4653 .min_version(SupportedVersion::V1(V1::Two))
4654 .type_parameter("K", PrimitiveTypeConstraint)
4655 .any_type_parameter("V")
4656 .parameter(
4657 "map",
4658 GenericMapType::new(
4659 GenericType::Parameter("K"),
4660 GenericType::Parameter("V"),
4661 ),
4662 "Collection to search for the key.",
4663 )
4664 .parameter(
4665 "key",
4666 GenericType::Parameter("K"),
4667 "The key to search for. If the first argument is a `Map`, then \
4668 the key must be of the same type as the `Map`'s key type. If the \
4669 `Map`'s key type is optional then the key may also be optional. \
4670 If the first argument is a `Map[String, Y]`, `Struct`, or \
4671 `Object`, then the key may be either a `String` or \
4672 `Array[String]`."
4673 )
4674 .ret(PrimitiveType::Boolean)
4675 .definition(CONTAINS_KEY_DEFINITION)
4676 .build(),
4677 FunctionSignature::builder()
4678 .min_version(SupportedVersion::V1(V1::Two))
4679 .parameter("object", Type::Object, "Collection to search for the key.")
4680 .parameter(
4681 "key",
4682 PrimitiveType::String,
4683 "The key to search for. If the first argument is a `Map`, then \
4684 the key must be of the same type as the `Map`'s key type. If the \
4685 `Map`'s key type is optional then the key may also be optional. \
4686 If the first argument is a `Map[String, Y]`, `Struct`, or \
4687 `Object`, then the key may be either a `String` or \
4688 `Array[String]`."
4689 )
4690 .ret(PrimitiveType::Boolean)
4691 .definition(CONTAINS_KEY_DEFINITION)
4692 .build(),
4693 FunctionSignature::builder()
4694 .min_version(SupportedVersion::V1(V1::Two))
4695 .any_type_parameter("V")
4696 .parameter(
4697 "map",
4698 GenericMapType::new(
4699 PrimitiveType::String,
4700 GenericType::Parameter("V"),
4701 ),
4702 "Collection to search for the key.",
4703 )
4704 .parameter(
4705 "keys",
4706 array_string.clone(),
4707 "The key to search for. If the first argument is a `Map`, then \
4708 the key must be of the same type as the `Map`'s key type. If the \
4709 `Map`'s key type is optional then the key may also be optional. \
4710 If the first argument is a `Map[String, Y]`, `Struct`, or \
4711 `Object`, then the key may be either a `String` or \
4712 `Array[String]`."
4713 )
4714 .ret(PrimitiveType::Boolean)
4715 .definition(CONTAINS_KEY_DEFINITION)
4716 .build(),
4717 FunctionSignature::builder()
4718 .min_version(SupportedVersion::V1(V1::Two))
4719 .type_parameter("S", StructConstraint)
4720 .parameter(
4721 "struct",
4722 GenericType::Parameter("S"),
4723 "Collection to search for the key.",
4724 )
4725 .parameter(
4726 "keys",
4727 array_string.clone(),
4728 "The key to search for. If the first argument is a `Map`, then \
4729 the key must be of the same type as the `Map`'s key type. If the \
4730 `Map`'s key type is optional then the key may also be optional. \
4731 If the first argument is a `Map[String, Y]`, `Struct`, or \
4732 `Object`, then the key may be either a `String` or \
4733 `Array[String]`."
4734 )
4735 .ret(PrimitiveType::Boolean)
4736 .definition(CONTAINS_KEY_DEFINITION)
4737 .build(),
4738 FunctionSignature::builder()
4739 .min_version(SupportedVersion::V1(V1::Two))
4740 .parameter("object", Type::Object, "Collection to search for the key.")
4741 .parameter(
4742 "keys",
4743 array_string.clone(),
4744 "The key to search for. If the first argument is a `Map`, then \
4745 the key must be of the same type as the `Map`'s key type. If the \
4746 `Map`'s key type is optional then the key may also be optional. \
4747 If the first argument is a `Map[String, Y]`, `Struct`, or \
4748 `Object`, then the key may be either a `String` or \
4749 `Array[String]`."
4750 )
4751 .ret(PrimitiveType::Boolean)
4752 .definition(CONTAINS_KEY_DEFINITION)
4753 .build(),
4754 ])
4755 .into(),
4756 )
4757 .is_none()
4758 );
4759
4760 assert!(
4762 functions
4763 .insert(
4764 "values",
4765 MonomorphicFunction::new(
4766 FunctionSignature::builder()
4767 .min_version(SupportedVersion::V1(V1::Two))
4768 .type_parameter("K", PrimitiveTypeConstraint)
4769 .any_type_parameter("V")
4770 .parameter(
4771 "map",
4772 GenericMapType::new(
4773 GenericType::Parameter("K"),
4774 GenericType::Parameter("V"),
4775 ),
4776 "`Map` from which to extract values.",
4777 )
4778 .ret(GenericArrayType::new(GenericType::Parameter("V")))
4779 .definition(
4780 r#"
4781Given 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`.
4782
4783If `m` is empty, an empty array is returned.
4784
4785**Parameters**
4786
47871. `Map[K, V]`: The map to get the values from.
4788
4789**Returns**: A new `Array[V]` with the values.
4790
4791Example: values_map_task.wdl
4792
4793```wdl
4794version 1.2
4795
4796task values_map {
4797 input {
4798 Map[String, Int] map = {"a": 1, "b": 2}
4799 }
4800
4801 output {
4802 Array[Int] values = values(map) # [1, 2]
4803 }
4804}
4805```
4806"#
4807 )
4808 .build(),
4809 )
4810 .into(),
4811 )
4812 .is_none()
4813 );
4814
4815 assert!(
4817 functions
4818 .insert(
4819 "collect_by_key",
4820 MonomorphicFunction::new(
4821 FunctionSignature::builder()
4822 .min_version(SupportedVersion::V1(V1::One))
4823 .type_parameter("K", PrimitiveTypeConstraint)
4824 .any_type_parameter("V")
4825 .parameter(
4826 "pairs",
4827 GenericArrayType::new(GenericPairType::new(
4828 GenericType::Parameter("K"),
4829 GenericType::Parameter("V"),
4830 )),
4831 "`Array` of `Pairs` to group.",
4832 )
4833 .ret(GenericMapType::new(
4834 GenericType::Parameter("K"),
4835 GenericArrayType::new(GenericType::Parameter("V"))
4836 ))
4837 .definition(
4838 r#"
4839Given 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`.
4840
4841If `a` is empty, an empty map is returned.
4842
4843**Parameters**
4844
48451. `Array[Pair[K, V]]`: The array of pairs to collect.
4846
4847**Returns**: A new `Map[K, Array[V]]` with the collected values.
4848
4849Example: collect_by_key_task.wdl
4850
4851```wdl
4852version 1.2
4853
4854task collect_by_key {
4855 input {
4856 Array[Pair[String, Int]] pairs = [("a", 1), ("b", 2), ("a", 3)]
4857 }
4858
4859 output {
4860 Map[String, Array[Int]] collected = collect_by_key(pairs) # {"a": [1, 3], "b": 2}
4861 }
4862}
4863```
4864"#
4865 )
4866 .build(),
4867 )
4868 .into(),
4869 )
4870 .is_none()
4871 );
4872
4873 assert!(
4875 functions
4876 .insert(
4877 "defined",
4878 MonomorphicFunction::new(
4879 FunctionSignature::builder()
4880 .any_type_parameter("X")
4881 .parameter(
4882 "value",
4883 GenericType::Parameter("X"),
4884 "Optional value of any type."
4885 )
4886 .ret(PrimitiveType::Boolean)
4887 .definition(
4888 r#"
4889Given an optional value `x`, returns `true` if `x` is defined (i.e., not `None`), otherwise `false`.
4890
4891**Parameters**
4892
48931. `X?`: The optional value to check.
4894
4895**Returns**: `true` if `x` is defined, otherwise `false`.
4896
4897Example: defined_task.wdl
4898
4899```wdl
4900version 1.2
4901
4902task defined {
4903 input {
4904 Int? x = 1
4905 Int? y = None
4906 }
4907
4908 output {
4909 Boolean x_defined = defined(x) # true
4910 Boolean y_defined = defined(y) # false
4911 }
4912}
4913```
4914"#
4915 )
4916 .build(),
4917 )
4918 .into(),
4919 )
4920 .is_none()
4921 );
4922
4923 const LENGTH_DEFINITION: &str = r#"
4924Given an `Array[X]` `a`, returns the number of elements in `a`. If `a` is empty, `0` is returned.
4925
4926**Parameters**
4927
49281. `Array[X]`: The array to get the length from.
4929
4930**Returns**: The number of elements in the array as an `Int`.
4931
4932Example: length_array_task.wdl
4933
4934```wdl
4935version 1.2
4936
4937task length_array {
4938 input {
4939 Array[Int] ints = [1, 2, 3]
4940 }
4941
4942 output {
4943 Int len = length(ints) # 3
4944 }
4945}
4946```
4947"#;
4948
4949 assert!(
4951 functions
4952 .insert(
4953 "length",
4954 PolymorphicFunction::new(vec![
4955 FunctionSignature::builder()
4956 .any_type_parameter("X")
4957 .parameter(
4958 "array",
4959 GenericArrayType::new(GenericType::Parameter("X")),
4960 "A collection or string whose elements are to be counted.",
4961 )
4962 .ret(PrimitiveType::Integer)
4963 .definition(LENGTH_DEFINITION)
4964 .build(),
4965 FunctionSignature::builder()
4966 .any_type_parameter("K")
4967 .any_type_parameter("V")
4968 .parameter(
4969 "map",
4970 GenericMapType::new(
4971 GenericType::Parameter("K"),
4972 GenericType::Parameter("V"),
4973 ),
4974 "A collection or string whose elements are to be counted.",
4975 )
4976 .ret(PrimitiveType::Integer)
4977 .definition(LENGTH_DEFINITION)
4978 .build(),
4979 FunctionSignature::builder()
4980 .parameter(
4981 "object",
4982 Type::Object,
4983 "A collection or string whose elements are to be counted.",
4984 )
4985 .ret(PrimitiveType::Integer)
4986 .definition(LENGTH_DEFINITION)
4987 .build(),
4988 FunctionSignature::builder()
4989 .parameter(
4990 "string",
4991 PrimitiveType::String,
4992 "A collection or string whose elements are to be counted.",
4993 )
4994 .ret(PrimitiveType::Integer)
4995 .definition(LENGTH_DEFINITION)
4996 .build(),
4997 ])
4998 .into(),
4999 )
5000 .is_none()
5001 );
5002
5003 StandardLibrary {
5004 functions,
5005 array_int,
5006 array_string,
5007 array_file,
5008 array_object,
5009 array_string_non_empty,
5010 array_array_string,
5011 map_string_string,
5012 map_string_int,
5013 }
5014});
5015
5016#[cfg(test)]
5017mod test {
5018 use pretty_assertions::assert_eq;
5019
5020 use super::*;
5021
5022 #[test]
5023 fn verify_stdlib_signatures() {
5024 let mut signatures = Vec::new();
5025 for (name, f) in STDLIB.functions() {
5026 match f {
5027 Function::Monomorphic(f) => {
5028 let params = TypeParameters::new(&f.signature.type_parameters);
5029 signatures.push(format!("{name}{sig}", sig = f.signature.display(¶ms)));
5030 }
5031 Function::Polymorphic(f) => {
5032 for signature in &f.signatures {
5033 let params = TypeParameters::new(&signature.type_parameters);
5034 signatures.push(format!("{name}{sig}", sig = signature.display(¶ms)));
5035 }
5036 }
5037 }
5038 }
5039
5040 assert_eq!(
5041 signatures,
5042 [
5043 "floor(value: Float) -> Int",
5044 "ceil(value: Float) -> Int",
5045 "round(value: Float) -> Int",
5046 "min(a: Int, b: Int) -> Int",
5047 "min(a: Int, b: Float) -> Float",
5048 "min(a: Float, b: Int) -> Float",
5049 "min(a: Float, b: Float) -> Float",
5050 "max(a: Int, b: Int) -> Int",
5051 "max(a: Int, b: Float) -> Float",
5052 "max(a: Float, b: Int) -> Float",
5053 "max(a: Float, b: Float) -> Float",
5054 "find(input: String, pattern: String) -> String?",
5055 "matches(input: String, pattern: String) -> Boolean",
5056 "sub(input: String, pattern: String, replace: String) -> String",
5057 "split(input: String, delimiter: String) -> Array[String]",
5058 "basename(path: File, <suffix: String>) -> String",
5059 "basename(path: String, <suffix: String>) -> String",
5060 "basename(path: Directory, <suffix: String>) -> String",
5061 "join_paths(base: File, relative: String) -> File",
5062 "join_paths(base: File, relative: Array[String]+) -> File",
5063 "join_paths(paths: Array[String]+) -> File",
5064 "glob(pattern: String) -> Array[File]",
5065 "size(value: None, <unit: String>) -> Float",
5066 "size(value: File?, <unit: String>) -> Float",
5067 "size(value: String?, <unit: String>) -> Float",
5068 "size(value: Directory?, <unit: String>) -> Float",
5069 "size(value: X, <unit: String>) -> Float where `X`: any compound type that \
5070 recursively contains a `File` or `Directory`",
5071 "stdout() -> File",
5072 "stderr() -> File",
5073 "read_string(file: File) -> String",
5074 "read_int(file: File) -> Int",
5075 "read_float(file: File) -> Float",
5076 "read_boolean(file: File) -> Boolean",
5077 "read_lines(file: File) -> Array[String]",
5078 "write_lines(array: Array[String]) -> File",
5079 "read_tsv(file: File) -> Array[Array[String]]",
5080 "read_tsv(file: File, header: Boolean) -> Array[Object]",
5081 "read_tsv(file: File, header: Boolean, columns: Array[String]) -> Array[Object]",
5082 "write_tsv(data: Array[Array[String]]) -> File",
5083 "write_tsv(data: Array[Array[String]], header: Boolean, columns: Array[String]) \
5084 -> File",
5085 "write_tsv(data: Array[S], <header: Boolean>, <columns: Array[String]>) -> File \
5086 where `S`: any structure containing only primitive types",
5087 "read_map(file: File) -> Map[String, String]",
5088 "write_map(map: Map[String, String]) -> File",
5089 "read_json(file: File) -> Union",
5090 "write_json(value: X) -> File where `X`: any JSON-serializable type",
5091 "read_object(file: File) -> Object",
5092 "read_objects(file: File) -> Array[Object]",
5093 "write_object(object: Object) -> File",
5094 "write_object(object: S) -> File where `S`: any structure containing only \
5095 primitive types",
5096 "write_objects(objects: Array[Object]) -> File",
5097 "write_objects(objects: Array[S]) -> File where `S`: any structure containing \
5098 only primitive types",
5099 "prefix(prefix: String, array: Array[P]) -> Array[String] where `P`: any \
5100 primitive type",
5101 "suffix(suffix: String, array: Array[P]) -> Array[String] where `P`: any \
5102 primitive type",
5103 "quote(array: Array[P]) -> Array[String] where `P`: any primitive type",
5104 "squote(array: Array[P]) -> Array[String] where `P`: any primitive type",
5105 "sep(separator: String, array: Array[P]) -> String where `P`: any primitive type",
5106 "range(n: Int) -> Array[Int]",
5107 "transpose(array: Array[Array[X]]) -> Array[Array[X]]",
5108 "cross(a: Array[X], b: Array[Y]) -> Array[Pair[X, Y]]",
5109 "zip(a: Array[X], b: Array[Y]) -> Array[Pair[X, Y]]",
5110 "unzip(array: Array[Pair[X, Y]]) -> Pair[Array[X], Array[Y]]",
5111 "contains(array: Array[P], value: P) -> Boolean where `P`: any primitive type",
5112 "chunk(array: Array[X], size: Int) -> Array[Array[X]]",
5113 "flatten(array: Array[Array[X]]) -> Array[X]",
5114 "select_first(array: Array[X], <default: X>) -> X",
5115 "select_all(array: Array[X]) -> Array[X]",
5116 "as_pairs(map: Map[K, V]) -> Array[Pair[K, V]] where `K`: any primitive type",
5117 "as_map(pairs: Array[Pair[K, V]]) -> Map[K, V] where `K`: any primitive type",
5118 "keys(map: Map[K, V]) -> Array[K] where `K`: any primitive type",
5119 "keys(struct: S) -> Array[String] where `S`: any structure",
5120 "keys(object: Object) -> Array[String]",
5121 "contains_key(map: Map[K, V], key: K) -> Boolean where `K`: any primitive type",
5122 "contains_key(object: Object, key: String) -> Boolean",
5123 "contains_key(map: Map[String, V], keys: Array[String]) -> Boolean",
5124 "contains_key(struct: S, keys: Array[String]) -> Boolean where `S`: any structure",
5125 "contains_key(object: Object, keys: Array[String]) -> Boolean",
5126 "values(map: Map[K, V]) -> Array[V] where `K`: any primitive type",
5127 "collect_by_key(pairs: Array[Pair[K, V]]) -> Map[K, Array[V]] where `K`: any \
5128 primitive type",
5129 "defined(value: X) -> Boolean",
5130 "length(array: Array[X]) -> Int",
5131 "length(map: Map[K, V]) -> Int",
5132 "length(object: Object) -> Int",
5133 "length(string: String) -> Int",
5134 ]
5135 );
5136 }
5137
5138 #[test]
5139 fn it_binds_a_simple_function() {
5140 let f = STDLIB.function("floor").expect("should have function");
5141 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5142
5143 let e = f
5144 .bind(SupportedVersion::V1(V1::Zero), &[])
5145 .expect_err("bind should fail");
5146 assert_eq!(e, FunctionBindError::TooFewArguments(1));
5147
5148 let e = f
5149 .bind(
5150 SupportedVersion::V1(V1::One),
5151 &[PrimitiveType::String.into(), PrimitiveType::Boolean.into()],
5152 )
5153 .expect_err("bind should fail");
5154 assert_eq!(e, FunctionBindError::TooManyArguments(1));
5155
5156 let e = f
5158 .bind(
5159 SupportedVersion::V1(V1::Two),
5160 &[PrimitiveType::String.into()],
5161 )
5162 .expect_err("bind should fail");
5163 assert_eq!(
5164 e,
5165 FunctionBindError::ArgumentTypeMismatch {
5166 index: 0,
5167 expected: "`Float`".into()
5168 }
5169 );
5170
5171 let binding = f
5173 .bind(SupportedVersion::V1(V1::Zero), &[Type::Union])
5174 .expect("bind should succeed");
5175 assert_eq!(binding.index(), 0);
5176 assert_eq!(binding.return_type().to_string(), "Int");
5177
5178 let binding = f
5180 .bind(
5181 SupportedVersion::V1(V1::One),
5182 &[PrimitiveType::Float.into()],
5183 )
5184 .expect("bind should succeed");
5185 assert_eq!(binding.index(), 0);
5186 assert_eq!(binding.return_type().to_string(), "Int");
5187
5188 let binding = f
5190 .bind(
5191 SupportedVersion::V1(V1::Two),
5192 &[PrimitiveType::Integer.into()],
5193 )
5194 .expect("bind should succeed");
5195 assert_eq!(binding.index(), 0);
5196 assert_eq!(binding.return_type().to_string(), "Int");
5197 }
5198
5199 #[test]
5200 fn it_binds_a_generic_function() {
5201 let f = STDLIB.function("values").expect("should have function");
5202 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Two));
5203
5204 let e = f
5205 .bind(SupportedVersion::V1(V1::Zero), &[])
5206 .expect_err("bind should fail");
5207 assert_eq!(
5208 e,
5209 FunctionBindError::RequiresVersion(SupportedVersion::V1(V1::Two))
5210 );
5211
5212 let e = f
5213 .bind(SupportedVersion::V1(V1::Two), &[])
5214 .expect_err("bind should fail");
5215 assert_eq!(e, FunctionBindError::TooFewArguments(1));
5216
5217 let e = f
5218 .bind(
5219 SupportedVersion::V1(V1::Two),
5220 &[PrimitiveType::String.into(), PrimitiveType::Boolean.into()],
5221 )
5222 .expect_err("bind should fail");
5223 assert_eq!(e, FunctionBindError::TooManyArguments(1));
5224
5225 let e = f
5227 .bind(
5228 SupportedVersion::V1(V1::Two),
5229 &[PrimitiveType::String.into()],
5230 )
5231 .expect_err("bind should fail");
5232 assert_eq!(
5233 e,
5234 FunctionBindError::ArgumentTypeMismatch {
5235 index: 0,
5236 expected: "`Map[K, V]` where `K`: any primitive type".into()
5237 }
5238 );
5239
5240 let binding = f
5242 .bind(SupportedVersion::V1(V1::Two), &[Type::Union])
5243 .expect("bind should succeed");
5244 assert_eq!(binding.index(), 0);
5245 assert_eq!(binding.return_type().to_string(), "Array[Union]");
5246
5247 let ty: Type = MapType::new(PrimitiveType::String, PrimitiveType::String).into();
5249 let binding = f
5250 .bind(SupportedVersion::V1(V1::Two), &[ty])
5251 .expect("bind should succeed");
5252 assert_eq!(binding.index(), 0);
5253 assert_eq!(binding.return_type().to_string(), "Array[String]");
5254
5255 let ty: Type = MapType::new(PrimitiveType::String, Type::Object).into();
5257 let binding = f
5258 .bind(SupportedVersion::V1(V1::Two), &[ty])
5259 .expect("bind should succeed");
5260 assert_eq!(binding.index(), 0);
5261 assert_eq!(binding.return_type().to_string(), "Array[Object]");
5262
5263 let ty: Type = MapType::new(
5265 Type::from(PrimitiveType::String).optional(),
5266 PrimitiveType::Boolean,
5267 )
5268 .into();
5269 let binding = f
5270 .bind(SupportedVersion::V1(V1::Two), &[ty])
5271 .expect("bind should succeed");
5272 assert_eq!(binding.index(), 0);
5273 assert_eq!(binding.return_type().to_string(), "Array[Boolean]");
5274 }
5275
5276 #[test]
5277 fn it_removes_qualifiers() {
5278 let f = STDLIB.function("select_all").expect("should have function");
5279 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5280
5281 let array_string: Type = ArrayType::new(PrimitiveType::String).into();
5283 let binding = f
5284 .bind(SupportedVersion::V1(V1::One), &[array_string])
5285 .expect("bind should succeed");
5286 assert_eq!(binding.index(), 0);
5287 assert_eq!(binding.return_type().to_string(), "Array[String]");
5288
5289 let array_optional_string: Type =
5291 ArrayType::new(Type::from(PrimitiveType::String).optional()).into();
5292 let binding = f
5293 .bind(SupportedVersion::V1(V1::One), &[array_optional_string])
5294 .expect("bind should succeed");
5295 assert_eq!(binding.index(), 0);
5296 assert_eq!(binding.return_type().to_string(), "Array[String]");
5297
5298 let binding = f
5300 .bind(SupportedVersion::V1(V1::Two), &[Type::Union])
5301 .expect("bind should succeed");
5302 assert_eq!(binding.index(), 0);
5303 assert_eq!(binding.return_type().to_string(), "Array[Union]");
5304
5305 let array_string = Type::from(ArrayType::new(PrimitiveType::String)).optional();
5307 let array_array_string = ArrayType::new(array_string).into();
5308 let binding = f
5309 .bind(SupportedVersion::V1(V1::Zero), &[array_array_string])
5310 .expect("bind should succeed");
5311 assert_eq!(binding.index(), 0);
5312 assert_eq!(binding.return_type().to_string(), "Array[Array[String]]");
5313 }
5314
5315 #[test]
5316 fn it_binds_concrete_overloads() {
5317 let f = STDLIB.function("max").expect("should have function");
5318 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::One));
5319
5320 let e = f
5321 .bind(SupportedVersion::V1(V1::One), &[])
5322 .expect_err("bind should fail");
5323 assert_eq!(e, FunctionBindError::TooFewArguments(2));
5324
5325 let e = f
5326 .bind(
5327 SupportedVersion::V1(V1::Two),
5328 &[
5329 PrimitiveType::String.into(),
5330 PrimitiveType::Boolean.into(),
5331 PrimitiveType::File.into(),
5332 ],
5333 )
5334 .expect_err("bind should fail");
5335 assert_eq!(e, FunctionBindError::TooManyArguments(2));
5336
5337 let binding = f
5339 .bind(
5340 SupportedVersion::V1(V1::One),
5341 &[PrimitiveType::Integer.into(), PrimitiveType::Integer.into()],
5342 )
5343 .expect("binding should succeed");
5344 assert_eq!(binding.index(), 0);
5345 assert_eq!(binding.return_type().to_string(), "Int");
5346
5347 let binding = f
5349 .bind(
5350 SupportedVersion::V1(V1::Two),
5351 &[PrimitiveType::Integer.into(), PrimitiveType::Float.into()],
5352 )
5353 .expect("binding should succeed");
5354 assert_eq!(binding.index(), 1);
5355 assert_eq!(binding.return_type().to_string(), "Float");
5356
5357 let binding = f
5359 .bind(
5360 SupportedVersion::V1(V1::One),
5361 &[PrimitiveType::Float.into(), PrimitiveType::Integer.into()],
5362 )
5363 .expect("binding should succeed");
5364 assert_eq!(binding.index(), 2);
5365 assert_eq!(binding.return_type().to_string(), "Float");
5366
5367 let binding = f
5369 .bind(
5370 SupportedVersion::V1(V1::Two),
5371 &[PrimitiveType::Float.into(), PrimitiveType::Float.into()],
5372 )
5373 .expect("binding should succeed");
5374 assert_eq!(binding.index(), 3);
5375 assert_eq!(binding.return_type().to_string(), "Float");
5376
5377 let e = f
5379 .bind(
5380 SupportedVersion::V1(V1::One),
5381 &[PrimitiveType::String.into(), PrimitiveType::Integer.into()],
5382 )
5383 .expect_err("binding should fail");
5384 assert_eq!(
5385 e,
5386 FunctionBindError::ArgumentTypeMismatch {
5387 index: 0,
5388 expected: "`Int` or `Float`".into()
5389 }
5390 );
5391
5392 let e = f
5394 .bind(
5395 SupportedVersion::V1(V1::Two),
5396 &[PrimitiveType::Integer.into(), PrimitiveType::String.into()],
5397 )
5398 .expect_err("binding should fail");
5399 assert_eq!(
5400 e,
5401 FunctionBindError::ArgumentTypeMismatch {
5402 index: 1,
5403 expected: "`Int` or `Float`".into()
5404 }
5405 );
5406
5407 let e = f
5409 .bind(
5410 SupportedVersion::V1(V1::One),
5411 &[PrimitiveType::String.into(), PrimitiveType::Float.into()],
5412 )
5413 .expect_err("binding should fail");
5414 assert_eq!(
5415 e,
5416 FunctionBindError::ArgumentTypeMismatch {
5417 index: 0,
5418 expected: "`Int` or `Float`".into()
5419 }
5420 );
5421
5422 let e = f
5424 .bind(
5425 SupportedVersion::V1(V1::Two),
5426 &[PrimitiveType::Float.into(), PrimitiveType::String.into()],
5427 )
5428 .expect_err("binding should fail");
5429 assert_eq!(
5430 e,
5431 FunctionBindError::ArgumentTypeMismatch {
5432 index: 1,
5433 expected: "`Int` or `Float`".into()
5434 }
5435 );
5436 }
5437
5438 #[test]
5439 fn it_binds_generic_overloads() {
5440 let f = STDLIB
5441 .function("select_first")
5442 .expect("should have function");
5443 assert_eq!(f.minimum_version(), SupportedVersion::V1(V1::Zero));
5444
5445 let e = f
5446 .bind(SupportedVersion::V1(V1::Zero), &[])
5447 .expect_err("bind should fail");
5448 assert_eq!(e, FunctionBindError::TooFewArguments(1));
5449
5450 let e = f
5451 .bind(
5452 SupportedVersion::V1(V1::One),
5453 &[
5454 PrimitiveType::String.into(),
5455 PrimitiveType::Boolean.into(),
5456 PrimitiveType::File.into(),
5457 ],
5458 )
5459 .expect_err("bind should fail");
5460 assert_eq!(e, FunctionBindError::TooManyArguments(2));
5461
5462 let e = f
5464 .bind(
5465 SupportedVersion::V1(V1::Two),
5466 &[PrimitiveType::Integer.into()],
5467 )
5468 .expect_err("binding should fail");
5469 assert_eq!(
5470 e,
5471 FunctionBindError::ArgumentTypeMismatch {
5472 index: 0,
5473 expected: "`Array[X]`".into()
5474 }
5475 );
5476
5477 let array: Type = ArrayType::non_empty(Type::from(PrimitiveType::String).optional()).into();
5479 let binding = f
5480 .bind(SupportedVersion::V1(V1::Zero), std::slice::from_ref(&array))
5481 .expect("binding should succeed");
5482 assert_eq!(binding.index(), 0);
5483 assert_eq!(binding.return_type().to_string(), "String");
5484
5485 let binding = f
5487 .bind(
5488 SupportedVersion::V1(V1::One),
5489 &[array.clone(), PrimitiveType::String.into()],
5490 )
5491 .expect("binding should succeed");
5492 assert_eq!(binding.index(), 0);
5493 assert_eq!(binding.return_type().to_string(), "String");
5494
5495 let e = f
5497 .bind(
5498 SupportedVersion::V1(V1::Two),
5499 &[array.clone(), PrimitiveType::Integer.into()],
5500 )
5501 .expect_err("binding should fail");
5502 assert_eq!(
5503 e,
5504 FunctionBindError::ArgumentTypeMismatch {
5505 index: 1,
5506 expected: "`String`".into()
5507 }
5508 );
5509
5510 let array: Type = ArrayType::new(Type::from(PrimitiveType::String).optional()).into();
5512 let binding = f
5513 .bind(SupportedVersion::V1(V1::Zero), std::slice::from_ref(&array))
5514 .expect("binding should succeed");
5515 assert_eq!(binding.index(), 0);
5516 assert_eq!(binding.return_type().to_string(), "String");
5517
5518 let binding = f
5520 .bind(
5521 SupportedVersion::V1(V1::One),
5522 &[array.clone(), PrimitiveType::String.into()],
5523 )
5524 .expect("binding should succeed");
5525 assert_eq!(binding.index(), 0);
5526 assert_eq!(binding.return_type().to_string(), "String");
5527
5528 let e = f
5530 .bind(
5531 SupportedVersion::V1(V1::Two),
5532 &[array, PrimitiveType::Integer.into()],
5533 )
5534 .expect_err("binding should fail");
5535 assert_eq!(
5536 e,
5537 FunctionBindError::ArgumentTypeMismatch {
5538 index: 1,
5539 expected: "`String`".into()
5540 }
5541 );
5542 }
5543}