Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement the string array functions from the WDL standard library. #255

Merged
merged 7 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 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
serializing a value was also added ([#254](https://github.com/stjude-rust-labs/wdl/pull/254)).
Expand Down
31 changes: 6 additions & 25 deletions wdl-engine/src/eval/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,7 @@ impl<'a, C: EvaluationContext> ExprEvaluator<'a, C> {
}
}
}
Value::Primitive(PrimitiveValue::Integer(v)) => write!(buffer, "{v}").unwrap(),
Value::Primitive(PrimitiveValue::Float(v)) => write!(buffer, "{v}").unwrap(),
Value::Primitive(PrimitiveValue::String(s))
| Value::Primitive(PrimitiveValue::File(s))
| Value::Primitive(PrimitiveValue::Directory(s)) => buffer.push_str(&s),
Value::Primitive(v) => write!(buffer, "{v}", v = v.raw()).unwrap(),
Value::Compound(CompoundValue::Array(v))
if matches!(placeholder.option(), Some(PlaceholderOption::Sep(_)))
&& v.elements()
Expand Down Expand Up @@ -980,31 +976,16 @@ impl<'a, C: EvaluationContext> ExprEvaluator<'a, C> {
if op == NumericOperator::Addition
&& !matches!(right, PrimitiveValue::Boolean(_)) =>
{
let s = match right {
PrimitiveValue::Boolean(_) => unreachable!(),
PrimitiveValue::Integer(v) => format!("{left}{v}"),
PrimitiveValue::Float(v) => format!("{left}{v}"),
PrimitiveValue::String(v)
| PrimitiveValue::File(v)
| PrimitiveValue::Directory(v) => format!("{left}{v}"),
};

Some(PrimitiveValue::new_string(s).into())
Some(
PrimitiveValue::new_string(format!("{left}{right}", right = right.raw()))
.into(),
)
}
(Value::Primitive(left), Value::Primitive(PrimitiveValue::String(right)))
if op == NumericOperator::Addition
&& !matches!(left, PrimitiveValue::Boolean(_)) =>
{
let s = match left {
PrimitiveValue::Boolean(_) => unreachable!(),
PrimitiveValue::Integer(v) => format!("{v}{right}"),
PrimitiveValue::Float(v) => format!("{v}{right}"),
PrimitiveValue::String(v)
| PrimitiveValue::File(v)
| PrimitiveValue::Directory(v) => format!("{v}{right}"),
};

Some(PrimitiveValue::new_string(s).into())
Some(PrimitiveValue::new_string(format!("{left}{right}", left = left.raw())).into())
}
(Value::Primitive(PrimitiveValue::String(_)), Value::None)
| (Value::None, Value::Primitive(PrimitiveValue::String(_)))
Expand Down
10 changes: 10 additions & 0 deletions wdl-engine/src/stdlib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ mod join_paths;
mod matches;
mod max;
mod min;
mod prefix;
mod quote;
mod read_boolean;
mod read_float;
mod read_int;
Expand All @@ -35,10 +37,13 @@ mod read_objects;
mod read_string;
mod read_tsv;
mod round;
mod sep;
mod size;
mod squote;
mod stderr;
mod stdout;
mod sub;
mod suffix;
mod write_json;
mod write_lines;
mod write_map;
Expand Down Expand Up @@ -255,6 +260,11 @@ pub static STDLIB: LazyLock<StandardLibrary> = LazyLock::new(|| {
func!(read_objects),
func!(write_object),
func!(write_objects),
func!(prefix),
func!(suffix),
func!(quote),
func!(squote),
func!(sep),
]),
}
});
Expand Down
106 changes: 106 additions & 0 deletions wdl-engine/src/stdlib/prefix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//! Implements the `prefix` function from the WDL standard library.

use std::sync::Arc;

use wdl_analysis::stdlib::STDLIB as ANALYSIS_STDLIB;
use wdl_analysis::types::PrimitiveTypeKind;
use wdl_ast::Diagnostic;

use super::CallContext;
use super::Function;
use super::Signature;
use crate::Array;
use crate::PrimitiveValue;
use crate::Value;

/// Adds a prefix to each element of the input array of primitive values.
///
/// Equivalent to evaluating "~{prefix}~{array[i]}" for each i in
/// range(length(array)).
///
/// https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#prefix
fn prefix(context: CallContext<'_>) -> Result<Value, Diagnostic> {
debug_assert_eq!(context.arguments.len(), 2);
debug_assert!(context.return_type_eq(ANALYSIS_STDLIB.array_string_type()));

let prefix = context
.coerce_argument(0, PrimitiveTypeKind::String)
.unwrap_string();

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

let elements = array
.elements()
.iter()
.map(|v| match v {
Value::Primitive(v) => {
PrimitiveValue::new_string(format!("{prefix}{v}", v = v.raw())).into()
}
_ => panic!("expected an array of primitive values"),
})
.collect();

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

/// Gets the function describing `prefix`.
pub const fn descriptor() -> Function {
Function::new(
const {
&[Signature::new(
"(String, Array[P]) -> Array[String] where `P`: any required primitive type",
prefix,
)]
},
)
}

#[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 prefix() {
let mut env = TestEnv::default();
let value = eval_v1_expr(&mut env, V1::Zero, "prefix('foo', [1, 2, 3])").unwrap();
let elements: Vec<_> = value
.as_array()
.unwrap()
.elements()
.iter()
.map(|v| v.as_string().unwrap().as_str())
.collect();
assert_eq!(elements, ["foo1", "foo2", "foo3"]);

let value = eval_v1_expr(&mut env, V1::Zero, "prefix('foo', [1.0, 1.1, 1.2])").unwrap();
let elements: Vec<_> = value
.as_array()
.unwrap()
.elements()
.iter()
.map(|v| v.as_string().unwrap().as_str())
.collect();
assert_eq!(elements, ["foo1.0", "foo1.1", "foo1.2"]);

let value =
eval_v1_expr(&mut env, V1::Zero, "prefix('foo', ['bar', 'baz', 'qux'])").unwrap();
let elements: Vec<_> = value
.as_array()
.unwrap()
.elements()
.iter()
.map(|v| v.as_string().unwrap().as_str())
.collect();
assert_eq!(elements, ["foobar", "foobaz", "fooqux"]);

let value = eval_v1_expr(&mut env, V1::One, "prefix('foo', [])").unwrap();
assert!(value.unwrap_array().elements().is_empty());
}
}
100 changes: 100 additions & 0 deletions wdl-engine/src/stdlib/quote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//! Implements the `quote` function from the WDL standard library.

use std::sync::Arc;

use wdl_analysis::stdlib::STDLIB as ANALYSIS_STDLIB;
use wdl_ast::Diagnostic;

use super::CallContext;
use super::Function;
use super::Signature;
use crate::Array;
use crate::PrimitiveValue;
use crate::Value;

/// Adds double-quotes (") around each element of the input array of primitive
/// values.
///
/// Equivalent to evaluating '"~{array[i]}"' for each i in range(length(array)).
///
/// https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#quote
fn quote(context: CallContext<'_>) -> Result<Value, Diagnostic> {
debug_assert_eq!(context.arguments.len(), 1);
debug_assert!(context.return_type_eq(ANALYSIS_STDLIB.array_string_type()));

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

let elements = array
.elements()
.iter()
.map(|v| match v {
Value::Primitive(v) => {
PrimitiveValue::new_string(format!("\"{v}\"", v = v.raw())).into()
}
_ => panic!("expected an array of primitive values"),
})
.collect();

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

/// Gets the function describing `quote`.
pub const fn descriptor() -> Function {
Function::new(
const {
&[Signature::new(
"(Array[P]) -> Array[String] where `P`: any required primitive type",
quote,
)]
},
)
}

#[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 quote() {
let mut env = TestEnv::default();
let value = eval_v1_expr(&mut env, V1::One, "quote([1, 2, 3])").unwrap();
let elements: Vec<_> = value
.as_array()
.unwrap()
.elements()
.iter()
.map(|v| v.as_string().unwrap().as_str())
.collect();
assert_eq!(elements, [r#""1""#, r#""2""#, r#""3""#]);

let value = eval_v1_expr(&mut env, V1::One, "quote([1.0, 1.1, 1.2])").unwrap();
let elements: Vec<_> = value
.as_array()
.unwrap()
.elements()
.iter()
.map(|v| v.as_string().unwrap().as_str())
.collect();
assert_eq!(elements, [r#""1.0""#, r#""1.1""#, r#""1.2""#]);

let value = eval_v1_expr(&mut env, V1::One, "quote(['bar', 'baz', 'qux'])").unwrap();
let elements: Vec<_> = value
.as_array()
.unwrap()
.elements()
.iter()
.map(|v| v.as_string().unwrap().as_str())
.collect();
assert_eq!(elements, [r#""bar""#, r#""baz""#, r#""qux""#]);

let value = eval_v1_expr(&mut env, V1::One, "quote([])").unwrap();
assert!(value.unwrap_array().elements().is_empty());
}
}
Loading