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