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