use std::fmt;
use std::sync::Arc;
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
F64(f64),
Int(i64),
U64(u64),
Usize(usize),
Bool(bool),
Arr(Arc<Vec<f64>>),
}
impl Value {
pub fn type_name(&self) -> &'static str {
match self {
Value::F64(_) => "f64",
Value::Int(_) => "int",
Value::U64(_) => "u64",
Value::Usize(_) => "usize",
Value::Bool(_) => "bool",
Value::Arr(_) => "array",
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Value::F64(x) => Some(*x),
Value::Int(i) => Some(*i as f64),
Value::U64(u) => Some(*u as f64),
Value::Usize(u) => Some(*u as f64),
Value::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
Value::Arr(_) => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Value::Bool(b) => Some(*b),
Value::F64(x) if x.is_nan() => None,
Value::F64(x) => Some(*x != 0.0),
Value::Int(i) => Some(*i != 0),
Value::U64(u) => Some(*u != 0),
Value::Usize(u) => Some(*u != 0),
Value::Arr(_) => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
Value::Int(i) => Some(*i),
Value::U64(u) => i64::try_from(*u).ok(),
Value::Usize(u) => i64::try_from(*u).ok(),
Value::Bool(b) => Some(i64::from(*b)),
Value::F64(x) if x.fract() == 0.0 && *x >= -9.223_372_036_854_776e18 => {
if *x < 9.223_372_036_854_776e18 {
Some(*x as i64)
} else {
None
}
}
Value::F64(_) | Value::Arr(_) => None,
}
}
pub fn as_u64(&self) -> Option<u64> {
match self {
Value::U64(u) => Some(*u),
Value::Usize(u) => u64::try_from(*u).ok(),
Value::F64(x) if x.fract() == 0.0 && *x >= 0.0 => {
if *x < 1.844_674_407_370_955_2e19 {
Some(*x as u64)
} else {
None
}
}
other => other.as_i64().and_then(|i| u64::try_from(i).ok()),
}
}
pub fn as_usize(&self) -> Option<usize> {
match self {
Value::Usize(u) => Some(*u),
other => other.as_u64().and_then(|u| usize::try_from(u).ok()),
}
}
pub fn as_array(&self) -> Option<&[f64]> {
match self {
Value::Arr(a) => Some(a.as_slice()),
_ => None,
}
}
pub(crate) fn to_f64_lossy(&self) -> f64 {
self.as_f64().unwrap_or(f64::NAN)
}
pub(crate) fn num(&self) -> Result<Num, String> {
match self {
Value::F64(x) => Ok(Num::F(*x)),
Value::Int(i) => Ok(Num::I(*i)),
Value::Bool(b) => Ok(Num::I(i64::from(*b))),
Value::U64(u) => Ok(i64::try_from(*u).map_or(Num::F(*u as f64), Num::I)),
Value::Usize(u) => Ok(i64::try_from(*u).map_or(Num::F(*u as f64), Num::I)),
Value::Arr(_) => Err("expected a number, found an array".to_string()),
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum Num {
I(i64),
F(f64),
}
impl Num {
pub(crate) fn to_f64(self) -> f64 {
match self {
Num::I(i) => i as f64,
Num::F(x) => x,
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::F64(x) => write!(f, "{x}"),
Value::Int(i) => write!(f, "{i}"),
Value::U64(u) => write!(f, "{u}"),
Value::Usize(u) => write!(f, "{u}"),
Value::Bool(b) => write!(f, "{b}"),
Value::Arr(a) => {
f.write_str("[")?;
for (i, x) in a.iter().enumerate() {
if i > 0 {
f.write_str(", ")?;
}
write!(f, "{x}")?;
}
f.write_str("]")
}
}
}
}
impl From<f64> for Value {
fn from(x: f64) -> Self {
Value::F64(x)
}
}
impl From<i64> for Value {
fn from(i: i64) -> Self {
Value::Int(i)
}
}
impl From<i32> for Value {
fn from(i: i32) -> Self {
Value::Int(i64::from(i))
}
}
impl From<u64> for Value {
fn from(u: u64) -> Self {
Value::U64(u)
}
}
impl From<usize> for Value {
fn from(u: usize) -> Self {
Value::Usize(u)
}
}
impl From<bool> for Value {
fn from(b: bool) -> Self {
Value::Bool(b)
}
}
impl From<Vec<f64>> for Value {
fn from(v: Vec<f64>) -> Self {
Value::Arr(Arc::new(v))
}
}
impl From<&[f64]> for Value {
fn from(v: &[f64]) -> Self {
Value::Arr(Arc::new(v.to_vec()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn integral_conversions_respect_ranges() {
assert_eq!(Value::F64(-0.0).as_i64(), Some(0));
assert_eq!(Value::F64(9.223_372_036_854_776e18).as_i64(), None);
assert_eq!(
Value::F64(-9.223_372_036_854_776e18).as_i64(),
Some(i64::MIN)
);
assert_eq!(Value::F64(f64::NAN).as_i64(), None);
assert_eq!(Value::F64(f64::INFINITY).as_u64(), None);
assert_eq!(Value::F64(1.844_674_407_370_955_2e19).as_u64(), None);
assert_eq!(Value::U64(u64::MAX).as_u64(), Some(u64::MAX));
assert_eq!(Value::U64(u64::MAX).as_i64(), None);
assert_eq!(Value::Bool(true).as_u64(), Some(1));
assert_eq!(Value::Int(-3).as_usize(), None);
assert_eq!(Value::from(vec![1.0]).as_i64(), None);
}
#[test]
fn truthiness_and_display() {
assert_eq!(Value::F64(f64::NAN).as_bool(), None);
assert_eq!(Value::Int(2).as_bool(), Some(true));
assert_eq!(Value::from(vec![1.0]).as_bool(), None);
assert_eq!(Value::F64(3.0).to_string(), "3");
assert_eq!(Value::F64(0.5).to_string(), "0.5");
assert_eq!(Value::Bool(true).to_string(), "true");
assert_eq!(Value::from(vec![1.0, 2.5]).to_string(), "[1, 2.5]");
assert_eq!(
Value::from(vec![1.0]).to_f64_lossy().to_bits(),
f64::NAN.to_bits()
);
}
}