Skip to content

Commit

Permalink
feat: implement the generic array functions from the WDL standard lib…
Browse files Browse the repository at this point in the history
…rary. (#256)
  • Loading branch information
peterhuene authored Nov 21, 2024
1 parent a208c01 commit 8d90374
Show file tree
Hide file tree
Showing 24 changed files with 1,350 additions and 23 deletions.
6 changes: 3 additions & 3 deletions wdl-analysis/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,8 +496,8 @@ impl Coercible for Type {
}
}

// Union is always coercible to the target
(Self::Union, _) => true,
// Union is always coercible to the target (and vice versa)
(Self::Union, _) | (_, Self::Union) => true,

// None is coercible to an optional type
(Self::None, ty) if ty.is_optional() => true,
Expand Down Expand Up @@ -2195,7 +2195,7 @@ mod test {
] {
assert!(Type::Union.is_coercible_to(&types, &kind.into()));
assert!(Type::Union.is_coercible_to(&types, &PrimitiveType::optional(kind).into()));
assert!(!Type::from(kind).is_coercible_to(&types, &Type::Union));
assert!(Type::from(kind).is_coercible_to(&types, &Type::Union));
}

for optional in [true, false] {
Expand Down
1 change: 1 addition & 0 deletions wdl-engine/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* Implement the generic array functions from the WDL standard library ([#256](https://github.com/stjude-rust-labs/wdl/pull/256)).
* Implement the string array functions from the WDL standard library ([#255](https://github.com/stjude-rust-labs/wdl/pull/255)).
* Replaced the `Value::from_json` method with `Value::deserialize` which allows
for deserialization from any self-describing data format; a method for
Expand Down
2 changes: 1 addition & 1 deletion wdl-engine/src/eval/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,7 @@ impl<'a, C: EvaluationContext> ExprEvaluator<'a, C> {
Ok(Some(value)) => Ok(value.clone()),
_ => Err(array_index_out_of_range(
i,
array.elements().len(),
array.len(),
index.span(),
target.span(),
)),
Expand Down
20 changes: 20 additions & 0 deletions wdl-engine/src/stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ use crate::Value;

mod basename;
mod ceil;
mod chunk;
mod contains;
mod cross;
mod find;
mod flatten;
mod floor;
mod glob;
mod join_paths;
Expand All @@ -26,6 +30,7 @@ mod max;
mod min;
mod prefix;
mod quote;
mod range;
mod read_boolean;
mod read_float;
mod read_int;
Expand All @@ -37,19 +42,24 @@ mod read_objects;
mod read_string;
mod read_tsv;
mod round;
mod select_all;
mod select_first;
mod sep;
mod size;
mod squote;
mod stderr;
mod stdout;
mod sub;
mod suffix;
mod transpose;
mod unzip;
mod write_json;
mod write_lines;
mod write_map;
mod write_object;
mod write_objects;
mod write_tsv;
mod zip;

/// Represents a function call argument.
pub struct CallArgument {
Expand Down Expand Up @@ -265,6 +275,16 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
func!(quote),
func!(squote),
func!(sep),
func!(range),
func!(transpose),
func!(cross),
func!(zip),
func!(unzip),
func!(contains),
func!(chunk),
func!(flatten),
func!(select_first),
func!(select_all),
]),
}
});
Expand Down
194 changes: 194 additions & 0 deletions wdl-engine/src/stdlib/chunk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
//! Implements the `chunk` function from the WDL standard library.
use std::sync::Arc;

use wdl_ast::Diagnostic;

use super::CallContext;
use super::Function;
use super::Signature;
use crate::Array;
use crate::Value;
use crate::diagnostics::function_call_failed;

/// Given an array and a length `n`, splits the array into consecutive,
/// non-overlapping arrays of n elements.
///
/// If the length of the array is not a multiple `n` then the final sub-array
/// will have length(array) % `n` elements.
///
/// https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#-chunk
fn chunk(context: CallContext<'_>) -> Result<Value, Diagnostic> {
debug_assert_eq!(context.arguments.len(), 2);

let array = context.arguments[0]
.value
.as_array()
.expect("argument should be an array");

let size = context.arguments[1]
.value
.as_integer()
.expect("argument should be an integer");

if size < 0 {
return Err(function_call_failed(
"chunk",
"chunk size cannot be negative",
context.arguments[1].span,
));
}

let element_ty = context
.types()
.type_definition(
context
.return_type
.as_compound()
.expect("type should be compound")
.definition(),
)
.as_array()
.expect("type should be an array")
.element_type();

let elements = array
.elements()
.chunks(size as usize)
.map(|chunk| {
Array::new_unchecked(element_ty, Arc::new(Vec::from_iter(chunk.iter().cloned()))).into()
})
.collect();

Ok(Array::new_unchecked(context.return_type, Arc::new(elements)).into())
}

/// Gets the function describing `chunk`.
pub const fn descriptor() -> Function {
Function::new(const { &[Signature::new("(Array[X], Int) -> Array[Array[X]]", chunk)] })
}

#[cfg(test)]
mod test {
use pretty_assertions::assert_eq;
use wdl_ast::version::V1;

use crate::v1::test::TestEnv;
use crate::v1::test::eval_v1_expr;

#[test]
fn chunk() {
let mut env = TestEnv::default();

let value = eval_v1_expr(&mut env, V1::Two, "chunk([], 10)").unwrap();
assert_eq!(value.as_array().unwrap().len(), 0);

let value = eval_v1_expr(&mut env, V1::Two, "chunk([1, 2, 3, 4, 5], 1)").unwrap();
let elements: Vec<_> = value
.as_array()
.unwrap()
.elements()
.iter()
.map(|v| {
v.as_array()
.unwrap()
.elements()
.iter()
.map(|v| v.as_integer().unwrap())
.collect::<Vec<_>>()
})
.collect();
assert_eq!(elements, [[1], [2], [3], [4], [5]]);

let value = eval_v1_expr(&mut env, V1::Two, "chunk([1, 2, 3, 4, 5], 2)").unwrap();
let elements: Vec<_> = value
.as_array()
.unwrap()
.elements()
.iter()
.map(|v| {
v.as_array()
.unwrap()
.elements()
.iter()
.map(|v| v.as_integer().unwrap())
.collect::<Vec<_>>()
})
.collect();
assert_eq!(elements, [[1, 2].as_slice(), &[3, 4], &[5]]);

let value = eval_v1_expr(&mut env, V1::Two, "chunk([1, 2, 3, 4, 5], 3)").unwrap();
let elements: Vec<_> = value
.as_array()
.unwrap()
.elements()
.iter()
.map(|v| {
v.as_array()
.unwrap()
.elements()
.iter()
.map(|v| v.as_integer().unwrap())
.collect::<Vec<_>>()
})
.collect();
assert_eq!(elements, [[1, 2, 3].as_slice(), &[4, 5]]);

let value = eval_v1_expr(&mut env, V1::Two, "chunk([1, 2, 3, 4, 5], 4)").unwrap();
let elements: Vec<_> = value
.as_array()
.unwrap()
.elements()
.iter()
.map(|v| {
v.as_array()
.unwrap()
.elements()
.iter()
.map(|v| v.as_integer().unwrap())
.collect::<Vec<_>>()
})
.collect();
assert_eq!(elements, [[1, 2, 3, 4].as_slice(), &[5]]);

let value = eval_v1_expr(&mut env, V1::Two, "chunk([1, 2, 3, 4, 5], 5)").unwrap();
let elements: Vec<_> = value
.as_array()
.unwrap()
.elements()
.iter()
.map(|v| {
v.as_array()
.unwrap()
.elements()
.iter()
.map(|v| v.as_integer().unwrap())
.collect::<Vec<_>>()
})
.collect();
assert_eq!(elements, [[1, 2, 3, 4, 5]]);

let value = eval_v1_expr(&mut env, V1::Two, "chunk([1, 2, 3, 4, 5], 10)").unwrap();
let elements: Vec<_> = value
.as_array()
.unwrap()
.elements()
.iter()
.map(|v| {
v.as_array()
.unwrap()
.elements()
.iter()
.map(|v| v.as_integer().unwrap())
.collect::<Vec<_>>()
})
.collect();
assert_eq!(elements, [[1, 2, 3, 4, 5]]);

let diagnostic = eval_v1_expr(&mut env, V1::Two, "chunk([1, 2, 3], -10)").unwrap_err();
assert_eq!(
diagnostic.message(),
"call to function `chunk` failed: chunk size cannot be negative"
);
}
}
Loading

0 comments on commit 8d90374

Please sign in to comment.