use std::{
fmt::Display,
ops::{Add, Div, Mul, Sub},
time::Duration,
};
use anyhow::{bail, Result};
use chrono::naive::NaiveTime;
use crate::{
calculate::{Arithmetic, Calculateable},
compare::{Compareable, Operator},
};
#[cfg_attr(not(feature = "lax_comparison"), derive(PartialEq))]
#[derive(Debug, PartialOrd, Clone)]
pub enum Value {
String(String),
Numeric(f64),
Bool(bool),
Time(NaiveTime),
Duration(Duration),
}
impl Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self {
&Self::String(s) => write!(f, "{}", s),
&Self::Numeric(d) => write!(f, "{}", d),
&Self::Bool(b) => write!(f, "{}", b),
&Self::Time(t) => write!(f, "{}", t),
&Self::Duration(d) => write!(f, "{:?}", d),
}
}
}
#[cfg(feature = "lax_comparison")]
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::String(left), Value::String(right)) => left == right,
(Value::Numeric(left), Value::Numeric(right)) => left == right,
(Value::Bool(left), Value::Bool(right)) => left == right,
(Value::Time(left), Value::Time(right)) => left == right,
(Value::Duration(left), Value::Duration(right)) => left == right,
(Value::Bool(left), Value::Numeric(right)) => left == &(right != &0f64),
(Value::Numeric(left), Value::Bool(right)) => &(left != &0f64) == right,
(Value::String(left), Value::Numeric(right)) => str::parse::<f64>(left).ok().as_ref() == Some(right),
(Value::Numeric(left), Value::String(right)) => str::parse::<f64>(right).ok().as_ref() == Some(left),
(Value::String(left), Value::Bool(right)) => left == &format!("{}", right),
(Value::Bool(left), Value::String(right)) => &format!("{}", left) == right,
_ => panic!("Unable to compare {:?} against {:?}", self, other),
}
}
}
impl Compareable for Value {
fn compare(&self, other: &Self, operator: Operator) -> bool {
match operator {
Operator::Equal => self == other,
Operator::NotEqual => self != other,
Operator::Greater => self > other,
Operator::Less => self < other,
Operator::GreaterEqual => self >= other,
Operator::LessEqual => self <= other,
}
}
}
impl Calculateable for Value {
fn calculate(self, value: &Self, arithmetic: Arithmetic) -> Result<Self> {
match arithmetic {
Arithmetic::Add => self + value,
Arithmetic::Sub => self - value,
Arithmetic::Mul => self * value,
Arithmetic::Div => self / value,
}
}
}
impl Add<&Self> for Value {
type Output = Result<Value>;
fn add(self, other: &Self) -> Self::Output {
match (self, other) {
(Value::String(lhs), Value::String(rhs)) => Ok(Value::String(lhs + &rhs)),
(Value::String(lhs), Value::Numeric(rhs)) => Ok(Value::String(format!("{} {}", lhs, rhs))),
(Value::Numeric(lhs), Value::Numeric(rhs)) => Ok(Value::Numeric(lhs + rhs)),
(Value::Duration(lhs), Value::Duration(rhs)) => Ok(Value::Duration(lhs + *rhs)),
(Value::Time(lhs), Value::Duration(rhs)) => Ok(Value::Time(
lhs + chrono::Duration::from_std(rhs.clone()).expect("Unable to convert duration"),
)),
_ => bail!("Incompatible types for addition"),
}
}
}
impl Sub<&Self> for Value {
type Output = Result<Value>;
fn sub(self, other: &Self) -> Self::Output {
match (self, other) {
(Value::Numeric(lhs), Value::Numeric(rhs)) => Ok(Value::Numeric(lhs - rhs)),
(Value::Duration(lhs), Value::Duration(rhs)) => Ok(Value::Duration(lhs - *rhs)),
(Value::Time(lhs), Value::Duration(rhs)) => Ok(Value::Time(
lhs - chrono::Duration::from_std(rhs.clone()).expect("Unable to convert duration"),
)),
_ => bail!("Incompatible types for substraction"),
}
}
}
impl Mul<&Self> for Value {
type Output = Result<Value>;
fn mul(self, other: &Self) -> Self::Output {
match (self, other) {
(Value::Numeric(lhs), Value::Numeric(rhs)) => Ok(Value::Numeric(lhs * rhs)),
(Value::Duration(lhs), Value::Numeric(rhs)) => Ok(Value::Duration(lhs * rhs.round() as u32)),
_ => bail!("Incompatible types for multiiplication"),
}
}
}
impl Div<&Self> for Value {
type Output = Result<Value>;
fn div(self, other: &Self) -> Self::Output {
match (self, other) {
(Value::Numeric(lhs), Value::Numeric(rhs)) => Ok(Value::Numeric(lhs / rhs)),
(Value::Duration(lhs), Value::Numeric(rhs)) => Ok(Value::Duration(lhs / rhs.round() as u32)),
_ => bail!("Incompatible types for division"),
}
}
}
macro_rules! impl_value {
($from:ty, $to:expr) => {
impl From<$from> for Value {
fn from(value: $from) -> Self {
$to(value.into())
}
}
};
}
impl TryFrom<chrono::Duration> for Value {
type Error = anyhow::Error;
fn try_from(duration: chrono::Duration) -> Result<Self, Self::Error> {
Ok(duration.to_std()?.into())
}
}
impl_value!(&str, Value::String);
impl_value!(String, Value::String);
impl_value!(i8, Value::Numeric);
impl_value!(u8, Value::Numeric);
impl_value!(i16, Value::Numeric);
impl_value!(u16, Value::Numeric);
impl_value!(i32, Value::Numeric);
impl_value!(u32, Value::Numeric);
impl_value!(f32, Value::Numeric);
impl_value!(f64, Value::Numeric);
impl_value!(bool, Value::Bool);
impl_value!(NaiveTime, Value::Time);
impl_value!(Duration, Value::Duration);