use std::{collections::BTreeMap, fmt::Display};
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Text(String),
Int(i64),
Bool(bool),
Float(f64),
ArrayOrTuple(Vec<Value>),
Struct(BTreeMap<String, Value>),
}
impl Value {
pub fn is_printable(&self) -> bool {
match self {
Value::Text(_) | Value::Int(_) | Value::Bool(_) | Value::Float(_) => true,
Value::ArrayOrTuple(value) => value.iter().all(Value::is_printable),
Value::Struct(_) => false,
}
}
}
#[derive(Debug)]
pub struct ValueNotPrintableError;
impl Display for ValueNotPrintableError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("the value is not printable")
}
}
impl std::error::Error for ValueNotPrintableError {}
impl TryInto<String> for Value {
type Error = ValueNotPrintableError;
fn try_into(self) -> Result<String, Self::Error> {
Ok(match self {
Value::Bool(value) => value.to_string(),
Value::Float(value) => value.to_string(),
Value::Int(value) => value.to_string(),
Value::Text(value) => value.clone(),
Value::ArrayOrTuple(value) => value
.into_iter()
.map(Value::try_into)
.try_fold::<String, _, Result<String, ValueNotPrintableError>>(
String::new(),
|mut current, next: Result<String, ValueNotPrintableError>| {
current.push_str(&next?);
Ok(current)
},
)?,
Value::Struct(_) => return Err(ValueNotPrintableError),
})
}
}
#[cfg(feature = "serde")]
pub(crate) mod serde_values;
impl<'s> From<&'s str> for Value {
fn from(value: &'s str) -> Self {
Self::Text(value.to_string())
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Self::Text(value)
}
}
impl From<i64> for Value {
fn from(value: i64) -> Self {
Self::Int(value)
}
}
impl From<bool> for Value {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl From<f64> for Value {
fn from(value: f64) -> Self {
Self::Float(value)
}
}
impl<V: Into<Value>, const N: usize> From<[V; N]> for Value {
fn from(value: [V; N]) -> Self {
Self::ArrayOrTuple(value.map(Into::into).to_vec())
}
}
impl<V: Into<Value>> FromIterator<V> for Value {
fn from_iter<T: IntoIterator<Item = V>>(iter: T) -> Self {
Self::ArrayOrTuple(iter.into_iter().map(Into::into).collect())
}
}
impl<V: Into<Value>> From<Vec<V>> for Value {
fn from(value: Vec<V>) -> Self {
Self::ArrayOrTuple(value.into_iter().map(Into::into).collect())
}
}
impl<'a, V: Into<Value>> FromIterator<(&'a str, V)> for Value {
fn from_iter<T: IntoIterator<Item = (&'a str, V)>>(iter: T) -> Self {
Self::Struct(
iter.into_iter()
.map(|(key, value)| (key.to_string(), value.into()))
.collect::<BTreeMap<_, _>>(),
)
}
}
#[macro_export]
macro_rules! value {
([$($body:tt)*]) => {
$crate::value!(@array ($($body)*) -> ())
};
({$($body:tt)*}) => {
$crate::value!(@struct ($($body)*) -> ())
};
($key:ident: $($tail:tt)*) => {
$crate::value!(@struct ($key: $($tail)*) -> ())
};
($expr:expr, $($tail:tt)*) => {
$crate::value!(@array ($($tail)*) -> ($crate::value!($expr),))
};
([$($array_body:tt)*], $($tail:tt)*) => {
$crate::value!(@array ($($tail)*) -> ($crate::value!(@array ($($array_body)*) -> ()),))
};
({$($struct_body:tt)*}, $($tail:tt)*) => {
$crate::value!(@array ($($tail)*) -> ($crate::value!(@struct ($($struct_body)*) -> ()),))
};
(@array () -> ($($accum:tt)*)) => {
$crate::Value::ArrayOrTuple(::std::vec::Vec::from([$($accum)*]))
};
(@array ({$($struct_body:tt)*} $(, $($tail:tt)*)?) -> ($($accum:tt)*)) => {
$crate::value!(@array ($($($tail)*)?) -> ($($accum)* $crate::value!(@struct ($($struct_body)*) -> ()),))
};
(@array ([$($array_body:tt)*] $(, $($tail:tt)*)?) -> ($($accum:tt)*)) => {
$crate::value!(@array ($($($tail)*)?) -> ($($accum)* $crate::value!(@array ($($array_body)*) -> ()),))
};
(@array ($expr:expr $(, $($tail:tt)*)?) -> ($($accum:tt)*)) => {
$crate::value!(@array ($($($tail)*)?) -> ($($accum)* $crate::value!($expr),))
};
({$($body:tt)*}) => {
$crate::value!(@struct ($($body)*) -> ())
};
(@struct () -> ($($accum:tt)*)) => {
$crate::Value::Struct(::std::collections::BTreeMap::from([$($accum)*]))
};
(@struct ($key:ident: {$($struct_body:tt)*} $(, $($tail:tt)*)?) -> ($($accum:tt)*)) => {
$crate::value!(@struct ($($($tail)*)?) -> ($($accum)* (::std::stringify!($key).to_string(), $crate::value!(@struct ($($struct_body)*) -> ())),))
};
(@struct ($key:ident: [$($array_body:tt)*] $(, $($tail:tt)*)?) -> ($($accum:tt)*)) => {
$crate::value!(@struct ($($($tail)*)?) -> ($($accum)* (::std::stringify!($key).to_string(), $crate::value!(@array ($($array_body)*) -> ())),))
};
(@struct ($key:ident: $expr:expr $(, $($tail:tt)*)?) -> ($($accum:tt)*)) => {
$crate::value!(@struct ($($($tail)*)?) -> ($($accum)* (::std::stringify!($key).to_string(), $crate::value!($expr)),))
};
($expr:expr) => {
$crate::Value::from($expr)
};
}
#[cfg(test)]
mod test_value_macro {
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
use crate::Value;
#[test]
#[allow(clippy::approx_constant, clippy::nonminimal_bool)]
fn test_primitive() {
assert_eq!(value!(1), Value::Int(1));
assert_eq!(value!(true), Value::Bool(true));
assert_eq!(value!(6.28), Value::Float(6.28));
assert_eq!(value!(0 - 1), Value::Int(-1));
assert_eq!(value!("h".to_string() + "i"), Value::Text("hi".to_string()));
assert_eq!(value!(!true), Value::Bool(false));
}
#[test]
fn test_array() {
assert_eq!(value!([]), Value::ArrayOrTuple(Vec::new()));
assert_eq!(value!([1,]), Value::ArrayOrTuple(vec![Value::Int(1)]));
assert_eq!(value!([1 + 1,]), Value::ArrayOrTuple(vec![Value::Int(2)]));
assert_eq!(
value!([1, 1 + 1, 1 + 1 + 1]),
Value::ArrayOrTuple(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
);
assert_eq!(
value![1, 2, 3],
Value::ArrayOrTuple(vec![Value::Int(1), Value::Int(2), Value::Int(3)])
);
}
#[test]
fn test_struct() {
assert_eq!(value!({}), Value::Struct(BTreeMap::new()));
assert_eq!(
value!({name: "Josh"}),
Value::Struct(BTreeMap::from([(
"name".to_string(),
Value::Text("Josh".to_string())
)]))
);
assert_eq!(
value!({name: "Josh", age: 21}),
Value::Struct(BTreeMap::from([
("name".to_string(), Value::Text("Josh".to_string())),
("age".to_string(), Value::Int(21))
]))
);
assert_eq!(
value! {name: "Josh", age: 21},
Value::Struct(BTreeMap::from([
("name".to_string(), Value::Text("Josh".to_string())),
("age".to_string(), Value::Int(21))
]))
);
}
#[test]
fn test_complex() {
assert_eq!(
value! {person: {name: "Peter", favorite: {sport: "baseball", hobby: "coding"}}},
Value::Struct(BTreeMap::from([(
"person".to_string(),
Value::Struct(BTreeMap::from([
("name".to_string(), Value::Text("Peter".to_string())),
(
"favorite".to_string(),
Value::Struct(BTreeMap::from([
("sport".to_string(), Value::Text("baseball".to_string())),
("hobby".to_string(), Value::Text("coding".to_string()))
]))
)
]))
)]))
);
assert_eq!(
value! {name: "Peter", age: 30 + 2, hobbies: ["table".to_string() + " tennis"]},
Value::Struct(BTreeMap::from([
("name".to_string(), Value::Text("Peter".to_string())),
("age".to_string(), Value::Int(32)),
(
"hobbies".to_string(),
Value::ArrayOrTuple(vec![Value::Text("table tennis".to_string())])
)
]))
);
}
}