use std::fmt::Display;
#[derive(Debug, Clone, PartialOrd)]
pub enum KdlValue {
String(String),
Integer(i128),
Float(f64),
Bool(bool),
Null,
}
impl Eq for KdlValue {}
fn normalize_float(f: &f64) -> f64 {
match f {
_ if f == &f64::INFINITY => f64::MAX,
_ if f == &f64::NEG_INFINITY => -f64::MAX,
_ if f.is_nan() => 0.0,
_ => *f,
}
}
impl PartialEq for KdlValue {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::String(l0), Self::String(r0)) => l0 == r0,
(Self::Integer(l0), Self::Integer(r0)) => l0 == r0,
(Self::Float(l0), Self::Float(r0)) => normalize_float(l0) == normalize_float(r0),
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
impl std::hash::Hash for KdlValue {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Self::String(val) => val.hash(state),
Self::Integer(val) => val.hash(state),
Self::Float(val) => {
let val = normalize_float(val);
(val.trunc() as i128).hash(state);
(val.fract() as i128).hash(state);
}
Self::Bool(val) => val.hash(state),
Self::Null => core::mem::discriminant(self).hash(state),
}
}
}
impl KdlValue {
pub fn is_string(&self) -> bool {
matches!(self, Self::String(..))
}
pub fn is_integer(&self) -> bool {
matches!(self, Self::Integer(..))
}
pub fn is_float(&self) -> bool {
matches!(self, Self::Float(..))
}
pub fn is_bool(&self) -> bool {
matches!(self, Self::Bool(..))
}
pub fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
pub fn as_string(&self) -> Option<&str> {
use KdlValue::*;
match self {
String(s) => Some(s),
_ => None,
}
}
pub fn as_integer(&self) -> Option<i128> {
use KdlValue::*;
match self {
Integer(i) => Some(*i),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
Self::Float(i) => Some(*i),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
if let Self::Bool(v) = self {
Some(*v)
} else {
None
}
}
}
impl Display for KdlValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::String(_) => self.write_string(f),
Self::Integer(value) => write!(f, "{value:?}"),
Self::Float(value) => write!(
f,
"{}",
if value == &f64::INFINITY {
"#inf".into()
} else if value == &f64::NEG_INFINITY {
"#-inf".into()
} else if value.is_nan() {
"#nan".into()
} else {
format!("{:?}", *value)
}
),
Self::Bool(value) => write!(f, "#{value}"),
Self::Null => write!(f, "#null"),
}
}
}
pub(crate) fn is_plain_ident(ident: &str) -> bool {
let ident_bytes = ident.as_bytes();
ident
.find(crate::v2_parser::is_disallowed_ident_char)
.is_none()
&& ident_bytes.first().map(|c| c.is_ascii_digit()) != Some(true)
&& !(ident.chars().next().map(|c| matches!(c, '.' | '-' | '+')) == Some(true)
&& ident_bytes.get(1).map(|c| c.is_ascii_digit()) == Some(true))
&& ident != "inf"
&& ident != "-inf"
&& ident != "nan"
&& ident != "true"
&& ident != "false"
&& ident != "null"
}
#[cfg(test)]
#[test]
fn plain_ident_test() {
assert!(is_plain_ident("foo123,bar"));
assert!(is_plain_ident("foo123~!@$%^&*.:'|?+<>,"));
}
impl KdlValue {
fn write_string(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let string = self.as_string().unwrap();
if !string.is_empty() && is_plain_ident(string) {
write!(f, "{string}")?;
} else {
write!(f, "\"")?;
for char in string.chars() {
match char {
'\\' | '"' => write!(f, "\\{char}")?,
'\n' => write!(f, "\\n")?,
'\r' => write!(f, "\\r")?,
'\t' => write!(f, "\\t")?,
'\u{08}' => write!(f, "\\b")?,
'\u{0C}' => write!(f, "\\f")?,
_ => write!(f, "{char}")?,
}
}
write!(f, "\"")?;
}
Ok(())
}
}
impl From<i128> for KdlValue {
fn from(value: i128) -> Self {
Self::Integer(value)
}
}
impl From<f64> for KdlValue {
fn from(value: f64) -> Self {
Self::Float(value)
}
}
impl From<&str> for KdlValue {
fn from(value: &str) -> Self {
Self::String(value.to_string())
}
}
impl From<String> for KdlValue {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<bool> for KdlValue {
fn from(value: bool) -> Self {
Self::Bool(value)
}
}
impl<T> From<Option<T>> for KdlValue
where
T: Into<Self>,
{
fn from(value: Option<T>) -> Self {
match value {
Some(value) => value.into(),
None => Self::Null,
}
}
}
#[cfg(feature = "v1")]
impl From<kdlv1::KdlValue> for KdlValue {
fn from(value: kdlv1::KdlValue) -> Self {
match value {
kdlv1::KdlValue::RawString(s) => Self::String(s),
kdlv1::KdlValue::String(s) => Self::String(s),
kdlv1::KdlValue::Base2(i) => Self::Integer(i.into()),
kdlv1::KdlValue::Base8(i) => Self::Integer(i.into()),
kdlv1::KdlValue::Base10(i) => Self::Integer(i.into()),
kdlv1::KdlValue::Base10Float(f) => Self::Float(f),
kdlv1::KdlValue::Base16(i) => Self::Integer(i.into()),
kdlv1::KdlValue::Bool(b) => Self::Bool(b),
kdlv1::KdlValue::Null => Self::Null,
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn formatting() {
let string = KdlValue::String("foo\n".into());
assert_eq!(format!("{string}"), r#""foo\n""#);
let integer = KdlValue::Integer(1234567890);
assert_eq!(format!("{integer}"), "1234567890");
let float = KdlValue::Float(1234567890.12345);
assert_eq!(format!("{float}"), "1234567890.12345");
let boolean = KdlValue::Bool(true);
assert_eq!(format!("{boolean}"), "#true");
let null = KdlValue::Null;
assert_eq!(format!("{null}"), "#null");
}
}