use std::borrow::Cow;
use ordered_float::OrderedFloat;
use saphyr_parser::{ScalarStyle, Tag};
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub enum Scalar<'input> {
Null,
Boolean(bool),
Integer(i64),
FloatingPoint(OrderedFloat<f64>),
String(Cow<'input, str>),
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub enum ScalarOwned {
Null,
Boolean(bool),
Integer(i64),
FloatingPoint(OrderedFloat<f64>),
String(String),
}
impl<'input> Scalar<'input> {
define_yaml_scalar_conversion_ops!(borrowing);
#[must_use]
pub fn into_owned(self) -> ScalarOwned {
match self {
Self::Null => ScalarOwned::Null,
Self::Boolean(v) => ScalarOwned::Boolean(v),
Self::Integer(v) => ScalarOwned::Integer(v),
Self::FloatingPoint(v) => ScalarOwned::FloatingPoint(v),
Self::String(v) => ScalarOwned::String(v.into_owned()),
}
}
pub fn parse_from_cow_and_metadata(
v: Cow<'input, str>,
style: ScalarStyle,
tag: Option<&Cow<'input, Tag>>,
) -> Option<Self> {
if style != ScalarStyle::Plain {
Some(Self::String(v))
} else if let Some(tag) = tag.map(Cow::as_ref) {
if tag.is_yaml_core_schema() {
match tag.suffix.as_ref() {
"bool" => v.parse::<bool>().ok().map(Self::Boolean),
"int" => v.parse::<i64>().ok().map(Self::Integer),
"float" => parse_core_schema_fp(&v)
.map(OrderedFloat)
.map(Self::FloatingPoint),
"null" => match v.as_ref() {
"~" | "null" => Some(Self::Null),
_ => None,
},
"str" => Some(Self::String(v)),
_ => None,
}
} else {
Some(Self::parse_from_cow(v))
}
} else {
Some(Self::parse_from_cow(v))
}
}
#[must_use]
pub fn parse_from_cow(v: Cow<'input, str>) -> Self {
if let Some(number) = v.strip_prefix("0x") {
if let Ok(i) = i64::from_str_radix(number, 16) {
return Self::Integer(i);
}
} else if let Some(number) = v.strip_prefix("0o") {
if let Ok(i) = i64::from_str_radix(number, 8) {
return Self::Integer(i);
}
} else if let Some(number) = v.strip_prefix('+') {
if let Ok(i) = number.parse::<i64>() {
return Self::Integer(i);
}
}
match &*v {
"~" | "null" | "NULL" => Self::Null,
"true" => Self::Boolean(true),
"false" => Self::Boolean(false),
_ => {
if let Ok(integer) = v.parse::<i64>() {
Self::Integer(integer)
} else if let Some(float) = parse_core_schema_fp(&v) {
Self::FloatingPoint(float.into())
} else {
Self::String(v)
}
}
}
}
}
impl ScalarOwned {
define_yaml_scalar_conversion_ops!(owned);
#[must_use]
pub fn as_scalar(&self) -> Scalar<'_> {
match self {
Self::Null => Scalar::Null,
Self::Boolean(v) => Scalar::Boolean(*v),
Self::Integer(v) => Scalar::Integer(*v),
Self::FloatingPoint(v) => Scalar::FloatingPoint(*v),
Self::String(v) => Scalar::String(v.as_str().into()),
}
}
pub fn parse_from_cow_and_metadata(
v: Cow<'_, str>,
style: ScalarStyle,
tag: Option<&Cow<'_, Tag>>,
) -> Option<Self> {
Scalar::parse_from_cow_and_metadata(v, style, tag).map(Scalar::into_owned)
}
#[must_use]
pub fn parse_from_cow(v: Cow<'_, str>) -> Self {
Scalar::parse_from_cow(v).into_owned()
}
}
impl<'input> From<&'input ScalarOwned> for Scalar<'input> {
fn from(value: &'input ScalarOwned) -> Self {
value.as_scalar()
}
}
pub fn parse_core_schema_fp(v: &str) -> Option<f64> {
match v {
".inf" | ".Inf" | ".INF" | "+.inf" | "+.Inf" | "+.INF" => Some(f64::INFINITY),
"-.inf" | "-.Inf" | "-.INF" => Some(f64::NEG_INFINITY),
".nan" | ".NaN" | ".NAN" => Some(f64::NAN),
_ if v.as_bytes().iter().any(u8::is_ascii_digit) => v.parse::<f64>().ok(),
_ => None,
}
}