use std::{cmp::Ordering, sync::Arc};
use anyhow::{Context, Result, bail};
use serde_json::{Map as JsonMap, Number as JsonNumber, Value as Json};
#[derive(Debug, Clone, PartialEq)]
pub enum Scalar {
Null,
Bool(bool),
I64(i64),
U64(u64),
F64(f64),
Str(Arc<str>),
}
impl From<Scalar> for Value {
fn from(value: Scalar) -> Self {
match value {
Scalar::Null => Self::Null,
Scalar::Bool(value) => Self::Bool(value),
Scalar::I64(value) => Self::I64(value),
Scalar::U64(value) => Self::U64(value),
Scalar::F64(value) => Self::F64(value),
Scalar::Str(value) => Self::Str(value),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
I64(i64),
U64(u64),
F64(f64),
Str(Arc<str>),
List(Vec<Value>),
Struct(Vec<(String, Value)>),
}
impl Value {
pub fn to_value(&self) -> Json {
match self {
Self::Null => Json::Null,
Self::Bool(value) => Json::Bool(*value),
Self::I64(value) => Json::Number(JsonNumber::from(*value)),
Self::U64(value) => Json::Number(JsonNumber::from(*value)),
Self::F64(value) => JsonNumber::from_f64(*value)
.map(Json::Number)
.unwrap_or(Json::Null),
Self::Str(value) => Json::String(value.to_string()),
Self::List(values) => Json::Array(values.iter().map(Value::to_value).collect()),
Self::Struct(fields) => Json::Object(
fields
.iter()
.map(|(name, value)| (name.clone(), value.to_value()))
.collect::<JsonMap<_, _>>(),
),
}
}
pub(crate) fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
pub(crate) fn truthy(&self) -> Result<bool> {
match self {
Self::Bool(value) => Ok(*value),
Self::Null => Ok(false),
other => bail!("expected boolean expression, got {other:?}"),
}
}
pub(crate) fn as_i128(&self) -> Option<i128> {
match self {
Self::I64(value) => Some(*value as i128),
Self::U64(value) => Some(*value as i128),
_ => None,
}
}
pub(crate) fn as_u64(&self) -> Option<u64> {
match self {
Self::I64(value) if *value >= 0 => Some(*value as u64),
Self::U64(value) => Some(*value),
_ => None,
}
}
pub(crate) fn as_f64(&self) -> Option<f64> {
match self {
Self::I64(value) => Some(*value as f64),
Self::U64(value) => Some(*value as f64),
Self::F64(value) => Some(*value),
_ => None,
}
}
pub(crate) fn as_list(&self) -> Result<Option<&[Value]>> {
match self {
Self::Null => Ok(None),
Self::List(values) => Ok(Some(values)),
other => bail!("expected list, got {other:?}"),
}
}
pub(crate) fn into_struct(self) -> Result<Option<Vec<(String, Value)>>> {
match self {
Self::Null => Ok(None),
Self::Struct(fields) => Ok(Some(fields)),
other => bail!("expected struct, got {other:?}"),
}
}
pub(crate) fn eq_value(&self, rhs: &Self) -> bool {
match (self, rhs) {
(Self::Null, Self::Null) => true,
(Self::Bool(left), Self::Bool(right)) => left == right,
(Self::Str(left), Self::Str(right)) => left == right,
(Self::List(left), Self::List(right)) => {
left.len() == right.len() && left.iter().zip(right).all(|(l, r)| l.eq_value(r))
}
(Self::Struct(left), Self::Struct(right)) => {
left.len() == right.len()
&& left
.iter()
.zip(right)
.all(|((ln, lv), (rn, rv))| ln == rn && lv.eq_value(rv))
}
_ => self
.as_f64()
.zip(rhs.as_f64())
.is_some_and(|(left, right)| left == right),
}
}
pub(crate) fn compare(&self, rhs: &Self) -> Result<Ordering> {
match (self, rhs) {
(Self::Bool(left), Self::Bool(right)) => Ok(left.cmp(right)),
(Self::Str(left), Self::Str(right)) => Ok(left.cmp(right)),
_ => self
.as_f64()
.zip(rhs.as_f64())
.and_then(|(left, right)| left.partial_cmp(&right))
.context("cannot compare values"),
}
}
}