diff --git a/wdl-engine/CHANGELOG.md b/wdl-engine/CHANGELOG.md index 99a60402a..e6d40ccce 100644 --- a/wdl-engine/CHANGELOG.md +++ b/wdl-engine/CHANGELOG.md @@ -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)). diff --git a/wdl-engine/src/eval/v1.rs b/wdl-engine/src/eval/v1.rs index bca770156..d14cb785f 100644 --- a/wdl-engine/src/eval/v1.rs +++ b/wdl-engine/src/eval/v1.rs @@ -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() @@ -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(_))) diff --git a/wdl-engine/src/stdlib.rs b/wdl-engine/src/stdlib.rs index 17077e536..209d47b05 100644 --- a/wdl-engine/src/stdlib.rs +++ b/wdl-engine/src/stdlib.rs @@ -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; @@ -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; @@ -255,6 +260,11 @@ pub static STDLIB: LazyLock = LazyLock::new(|| { func!(read_objects), func!(write_object), func!(write_objects), + func!(prefix), + func!(suffix), + func!(quote), + func!(squote), + func!(sep), ]), } }); diff --git a/wdl-engine/src/stdlib/prefix.rs b/wdl-engine/src/stdlib/prefix.rs new file mode 100644 index 000000000..c6fe17914 --- /dev/null +++ b/wdl-engine/src/stdlib/prefix.rs @@ -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 { + 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()); + } +} diff --git a/wdl-engine/src/stdlib/quote.rs b/wdl-engine/src/stdlib/quote.rs new file mode 100644 index 000000000..01aa74802 --- /dev/null +++ b/wdl-engine/src/stdlib/quote.rs @@ -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 { + 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()); + } +} diff --git a/wdl-engine/src/stdlib/sep.rs b/wdl-engine/src/stdlib/sep.rs new file mode 100644 index 000000000..8b6cbbad9 --- /dev/null +++ b/wdl-engine/src/stdlib/sep.rs @@ -0,0 +1,103 @@ +//! Implements the `sep` function from the WDL standard library. + +use std::fmt::Write; + +use wdl_analysis::types::PrimitiveTypeKind; +use wdl_ast::Diagnostic; + +use super::CallContext; +use super::Function; +use super::Signature; +use crate::PrimitiveValue; +use crate::Value; + +/// Concatenates the elements of an array together into a string with the given +/// separator between consecutive elements. +/// +/// There are always N-1 separators in the output string, where N is the length +/// of the input array. +/// +/// A separator is never added after the last element. +/// +/// Returns an empty string if the array is empty. +/// +/// https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#sep-1 +fn sep(context: CallContext<'_>) -> Result { + debug_assert_eq!(context.arguments.len(), 2); + debug_assert!(context.return_type_eq(PrimitiveTypeKind::String)); + + let sep = context + .coerce_argument(0, PrimitiveTypeKind::String) + .unwrap_string(); + + let array = context.arguments[1] + .value + .as_array() + .expect("value should be an array"); + + let s = array + .elements() + .iter() + .enumerate() + .fold(String::new(), |mut s, (i, v)| { + if i > 0 { + s.push_str(&sep); + } + + match v { + Value::Primitive(v) => { + write!(&mut s, "{v}", v = v.raw()).expect("failed to write to a string") + } + _ => panic!("expected an array of primitive values"), + } + + s + }); + + Ok(PrimitiveValue::new_string(s).into()) +} + +/// Gets the function describing `sep`. +pub const fn descriptor() -> Function { + Function::new( + const { + &[Signature::new( + "(String, Array[P]) -> String where `P`: any required primitive type", + sep, + )] + }, + ) +} + +#[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 sep() { + let mut env = TestEnv::default(); + let value = eval_v1_expr( + &mut env, + V1::One, + "sep(' ', prefix('-i ', ['file_1', 'file_2']))", + ) + .unwrap(); + assert_eq!(value.unwrap_string().as_str(), "-i file_1 -i file_2"); + + let value = eval_v1_expr(&mut env, V1::One, "sep('', ['a', 'b', 'c'])").unwrap(); + assert_eq!(value.unwrap_string().as_str(), "abc"); + + let value = eval_v1_expr(&mut env, V1::One, "sep(' ', ['a', 'b', 'c'])").unwrap(); + assert_eq!(value.unwrap_string().as_str(), "a b c"); + + let value = eval_v1_expr(&mut env, V1::One, "sep(',', [1])").unwrap(); + assert_eq!(value.unwrap_string().as_str(), "1"); + + let value = eval_v1_expr(&mut env, V1::One, "sep(',', [])").unwrap(); + assert_eq!(value.unwrap_string().as_str(), ""); + } +} diff --git a/wdl-engine/src/stdlib/squote.rs b/wdl-engine/src/stdlib/squote.rs new file mode 100644 index 000000000..5e492f7fd --- /dev/null +++ b/wdl-engine/src/stdlib/squote.rs @@ -0,0 +1,98 @@ +//! Implements the `squote` 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 single-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#squote +fn squote(context: CallContext<'_>) -> Result { + 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 `squote`. +pub const fn descriptor() -> Function { + Function::new( + const { + &[Signature::new( + "(Array[P]) -> Array[String] where `P`: any required primitive type", + squote, + )] + }, + ) +} + +#[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 squote() { + let mut env = TestEnv::default(); + let value = eval_v1_expr(&mut env, V1::One, "squote([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, ["'1'", "'2'", "'3'"]); + + let value = eval_v1_expr(&mut env, V1::One, "squote([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, ["'1.0'", "'1.1'", "'1.2'"]); + + let value = eval_v1_expr(&mut env, V1::One, "squote(['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, ["'bar'", "'baz'", "'qux'"]); + + let value = eval_v1_expr(&mut env, V1::One, "squote([])").unwrap(); + assert!(value.unwrap_array().elements().is_empty()); + } +} diff --git a/wdl-engine/src/stdlib/suffix.rs b/wdl-engine/src/stdlib/suffix.rs new file mode 100644 index 000000000..00c73270d --- /dev/null +++ b/wdl-engine/src/stdlib/suffix.rs @@ -0,0 +1,106 @@ +//! Implements the `suffix` 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 suffix to each element of the input array of primitive values. +/// +/// Equivalent to evaluating "~{array[i]}~{suffix}" for each i in +/// range(length(array)). +/// +/// https://github.com/openwdl/wdl/blob/wdl-1.2/SPEC.md#suffix +fn suffix(context: CallContext<'_>) -> Result { + debug_assert_eq!(context.arguments.len(), 2); + debug_assert!(context.return_type_eq(ANALYSIS_STDLIB.array_string_type())); + + let suffix = 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!("{v}{suffix}", 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 `suffix`. +pub const fn descriptor() -> Function { + Function::new( + const { + &[Signature::new( + "(String, Array[P]) -> Array[String] where `P`: any required primitive type", + suffix, + )] + }, + ) +} + +#[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 suffix() { + let mut env = TestEnv::default(); + let value = eval_v1_expr(&mut env, V1::One, "suffix('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, ["1foo", "2foo", "3foo"]); + + let value = eval_v1_expr(&mut env, V1::One, "suffix('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, ["1.0foo", "1.1foo", "1.2foo"]); + + let value = + eval_v1_expr(&mut env, V1::One, "suffix('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, ["barfoo", "bazfoo", "quxfoo"]); + + let value = eval_v1_expr(&mut env, V1::One, "suffix('foo', [])").unwrap(); + assert!(value.unwrap_array().elements().is_empty()); + } +} diff --git a/wdl-engine/src/stdlib/write_tsv.rs b/wdl-engine/src/stdlib/write_tsv.rs index 68c5b1fd2..2b1ba016d 100644 --- a/wdl-engine/src/stdlib/write_tsv.rs +++ b/wdl-engine/src/stdlib/write_tsv.rs @@ -31,25 +31,16 @@ pub(crate) fn write_tsv_value( value: &PrimitiveValue, ) -> Result { match value { - PrimitiveValue::Boolean(v) => { - write!(&mut writer, "{v}")?; + PrimitiveValue::String(v) | PrimitiveValue::File(v) | PrimitiveValue::Directory(v) + if v.contains('\t') => + { + Ok(false) } - PrimitiveValue::Integer(v) => { - write!(&mut writer, "{v}")?; - } - PrimitiveValue::Float(v) => { - write!(&mut writer, "{v}")?; - } - PrimitiveValue::String(v) | PrimitiveValue::File(v) | PrimitiveValue::Directory(v) => { - if v.contains('\t') { - return Ok(false); - } - - write!(&mut writer, "{v}")?; + v => { + write!(&mut writer, "{v}", v = v.raw())?; + Ok(true) } } - - Ok(true) } /// Helper for writing a `Array[Array[String]]` to a TSV file. diff --git a/wdl-engine/src/value.rs b/wdl-engine/src/value.rs index 47e7e5233..65cdd66be 100644 --- a/wdl-engine/src/value.rs +++ b/wdl-engine/src/value.rs @@ -779,40 +779,60 @@ impl PrimitiveValue { /// Returns `None` if the values cannot be compared based on their types. pub fn compare(left: &Self, right: &Self) -> Option { match (left, right) { - (PrimitiveValue::Boolean(left), PrimitiveValue::Boolean(right)) => { - Some(left.cmp(right)) - } - (PrimitiveValue::Integer(left), PrimitiveValue::Integer(right)) => { - Some(left.cmp(right)) - } - (PrimitiveValue::Integer(left), PrimitiveValue::Float(right)) => { + (Self::Boolean(left), Self::Boolean(right)) => Some(left.cmp(right)), + (Self::Integer(left), Self::Integer(right)) => Some(left.cmp(right)), + (Self::Integer(left), Self::Float(right)) => { Some(OrderedFloat(*left as f64).cmp(right)) } - (PrimitiveValue::Float(left), PrimitiveValue::Integer(right)) => { + (Self::Float(left), Self::Integer(right)) => { Some(left.cmp(&OrderedFloat(*right as f64))) } - (PrimitiveValue::Float(left), PrimitiveValue::Float(right)) => Some(left.cmp(right)), - (PrimitiveValue::String(left), PrimitiveValue::String(right)) - | (PrimitiveValue::String(left), PrimitiveValue::File(right)) - | (PrimitiveValue::String(left), PrimitiveValue::Directory(right)) - | (PrimitiveValue::File(left), PrimitiveValue::File(right)) - | (PrimitiveValue::File(left), PrimitiveValue::String(right)) - | (PrimitiveValue::Directory(left), PrimitiveValue::Directory(right)) - | (PrimitiveValue::Directory(left), PrimitiveValue::String(right)) => { - Some(left.cmp(right)) - } + (Self::Float(left), Self::Float(right)) => Some(left.cmp(right)), + (Self::String(left), Self::String(right)) + | (Self::String(left), Self::File(right)) + | (Self::String(left), Self::Directory(right)) + | (Self::File(left), Self::File(right)) + | (Self::File(left), Self::String(right)) + | (Self::Directory(left), Self::Directory(right)) + | (Self::Directory(left), Self::String(right)) => Some(left.cmp(right)), _ => None, } } + + /// Gets a raw display of the value. + /// + /// This differs from the [Display][fmt::Display] implementation in that + /// strings, files, and directories are not quoted and not escaped. + pub fn raw(&self) -> impl fmt::Display + use<'_> { + /// Helper for displaying a raw value. + struct Display<'a>(&'a PrimitiveValue); + + impl fmt::Display for Display<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + PrimitiveValue::Boolean(v) => write!(f, "{v}"), + PrimitiveValue::Integer(v) => write!(f, "{v}"), + PrimitiveValue::Float(v) => write!(f, "{v:?}"), + PrimitiveValue::String(v) + | PrimitiveValue::File(v) + | PrimitiveValue::Directory(v) => { + write!(f, "{v}") + } + } + } + } + + Display(self) + } } impl fmt::Display for PrimitiveValue { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - PrimitiveValue::Boolean(v) => write!(f, "{v}"), - PrimitiveValue::Integer(v) => write!(f, "{v}"), - PrimitiveValue::Float(v) => write!(f, "{v:?}"), - PrimitiveValue::String(s) | PrimitiveValue::File(s) | PrimitiveValue::Directory(s) => { + Self::Boolean(v) => write!(f, "{v}"), + Self::Integer(v) => write!(f, "{v}"), + Self::Float(v) => write!(f, "{v:?}"), + Self::String(s) | Self::File(s) | Self::Directory(s) => { // TODO: handle necessary escape sequences write!(f, "\"{s}\"") }