use wdl_analysis::types::PrimitiveType;
use wdl_analysis::types::Type;
use wdl_ast::Diagnostic;
use super::CallContext;
use super::Callback;
use super::Function;
use super::Signature;
use crate::Value;
use crate::diagnostics::function_call_failed;
const FUNCTION_NAME: &str = "length";
fn array_length(context: CallContext<'_>) -> Result<Value, Diagnostic> {
debug_assert_eq!(context.arguments.len(), 1);
debug_assert!(context.return_type_eq(PrimitiveType::Integer));
Ok(i64::try_from(
context.arguments[0]
.value
.as_array()
.expect("argument should be an array")
.len(),
)
.map_err(|_| {
function_call_failed(
FUNCTION_NAME,
"array length exceeds a signed 64-bit integer",
context.call_site,
)
})?
.into())
}
fn map_length(context: CallContext<'_>) -> Result<Value, Diagnostic> {
debug_assert_eq!(context.arguments.len(), 1);
debug_assert!(context.return_type_eq(PrimitiveType::Integer));
Ok(i64::try_from(
context.arguments[0]
.value
.as_map()
.expect("argument should be a map")
.len(),
)
.map_err(|_| {
function_call_failed(
FUNCTION_NAME,
"map length exceeds a signed 64-bit integer",
context.call_site,
)
})?
.into())
}
fn object_length(context: CallContext<'_>) -> Result<Value, Diagnostic> {
debug_assert_eq!(context.arguments.len(), 1);
debug_assert!(context.return_type_eq(PrimitiveType::Integer));
let object = context.coerce_argument(0, Type::Object).unwrap_object();
Ok(i64::try_from(object.len())
.map_err(|_| {
function_call_failed(
FUNCTION_NAME,
"object members length exceeds a signed 64-bit integer",
context.call_site,
)
})?
.into())
}
fn string_length(context: CallContext<'_>) -> Result<Value, Diagnostic> {
debug_assert_eq!(context.arguments.len(), 1);
debug_assert!(context.return_type_eq(PrimitiveType::Integer));
let s = context
.coerce_argument(0, PrimitiveType::String)
.unwrap_string();
Ok(i64::try_from(s.chars().count())
.map_err(|_| {
function_call_failed(
FUNCTION_NAME,
"string character length exceeds a signed 64-bit integer",
context.call_site,
)
})?
.into())
}
pub const fn descriptor() -> Function {
Function::new(
const {
&[
Signature::new("(array: Array[X]) -> Int", Callback::Sync(array_length)),
Signature::new("(map: Map[K, V]) -> Int", Callback::Sync(map_length)),
Signature::new("(object: Object) -> Int", Callback::Sync(object_length)),
Signature::new("(string: String) -> Int", Callback::Sync(string_length)),
]
},
)
}
#[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;
#[tokio::test]
async fn length() {
let env = TestEnv::default();
let value = eval_v1_expr(&env, V1::Zero, "length([])").await.unwrap();
assert_eq!(value.unwrap_integer(), 0);
let value = eval_v1_expr(&env, V1::Zero, "length({})").await.unwrap();
assert_eq!(value.unwrap_integer(), 0);
let value = eval_v1_expr(&env, V1::Zero, "length(object {})")
.await
.unwrap();
assert_eq!(value.unwrap_integer(), 0);
let value = eval_v1_expr(&env, V1::Zero, "length('')").await.unwrap();
assert_eq!(value.unwrap_integer(), 0);
let value = eval_v1_expr(&env, V1::Zero, "length([1, 2, 3, 4, 5])")
.await
.unwrap();
assert_eq!(value.unwrap_integer(), 5);
let value = eval_v1_expr(&env, V1::Zero, "length({ 'foo': 1, 'bar': 2, 'baz': 3})")
.await
.unwrap();
assert_eq!(value.unwrap_integer(), 3);
let value = eval_v1_expr(&env, V1::Zero, "length(object { foo: 1, bar: 2, baz: 3})")
.await
.unwrap();
assert_eq!(value.unwrap_integer(), 3);
let value = eval_v1_expr(&env, V1::Zero, "length('hello world!')")
.await
.unwrap();
assert_eq!(value.unwrap_integer(), 12);
}
}