kdl/
value.rs

1use std::fmt::Display;
2
3/// A specific [KDL Value](https://github.com/kdl-org/kdl/blob/main/SPEC.md#value).
4#[derive(Debug, Clone, PartialOrd)]
5pub enum KdlValue {
6    /// A [KDL String](https://github.com/kdl-org/kdl/blob/main/SPEC.md#string).
7    String(String),
8
9    /// A non-float [KDL
10    /// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number)
11    Integer(i128),
12
13    /// A floating point [KDL
14    /// Number](https://github.com/kdl-org/kdl/blob/main/SPEC.md#number)
15    Float(f64),
16
17    /// A [KDL Boolean](https://github.com/kdl-org/kdl/blob/main/SPEC.md#boolean).
18    Bool(bool),
19
20    /// The [KDL Null Value](https://github.com/kdl-org/kdl/blob/main/SPEC.md#null).
21    Null,
22}
23
24impl Eq for KdlValue {}
25
26fn normalize_float(f: &f64) -> f64 {
27    match f {
28        _ if f == &f64::INFINITY => f64::MAX,
29        _ if f == &f64::NEG_INFINITY => -f64::MAX,
30        // We collapse NaN to 0.0 because we're evil like that.
31        _ if f.is_nan() => 0.0,
32        _ => *f,
33    }
34}
35
36impl PartialEq for KdlValue {
37    fn eq(&self, other: &Self) -> bool {
38        match (self, other) {
39            (Self::String(l0), Self::String(r0)) => l0 == r0,
40            (Self::Integer(l0), Self::Integer(r0)) => l0 == r0,
41            (Self::Float(l0), Self::Float(r0)) => normalize_float(l0) == normalize_float(r0),
42            (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
43            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
44        }
45    }
46}
47
48// NOTE: I know, I know. This is terrible and I shouldn't do it, but it's
49// better than not being able to hash KdlValue at all.
50impl std::hash::Hash for KdlValue {
51    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
52        match self {
53            Self::String(val) => val.hash(state),
54            Self::Integer(val) => val.hash(state),
55            Self::Float(val) => {
56                let val = normalize_float(val);
57                // Good enough to be close-ish for our purposes.
58                (val.trunc() as i128).hash(state);
59                (val.fract() as i128).hash(state);
60            }
61            Self::Bool(val) => val.hash(state),
62            Self::Null => core::mem::discriminant(self).hash(state),
63        }
64    }
65}
66
67impl KdlValue {
68    /// Returns `true` if the value is a [`KdlValue::String`].
69    pub fn is_string(&self) -> bool {
70        matches!(self, Self::String(..))
71    }
72
73    /// Returns `true` if the value is a [`KdlValue::Integer`].
74    pub fn is_integer(&self) -> bool {
75        matches!(self, Self::Integer(..))
76    }
77
78    /// Returns `true` if the value is a [`KdlValue::Float`].
79    pub fn is_float(&self) -> bool {
80        matches!(self, Self::Float(..))
81    }
82
83    /// Returns `true` if the value is a [`KdlValue::Bool`].
84    pub fn is_bool(&self) -> bool {
85        matches!(self, Self::Bool(..))
86    }
87
88    /// Returns `true` if the value is a [`KdlValue::Null`].
89    pub fn is_null(&self) -> bool {
90        matches!(self, Self::Null)
91    }
92
93    /// Returns `Some(&str)` if the `KdlValue` is a [`KdlValue::String`],
94    /// otherwise returns `None`.
95    pub fn as_string(&self) -> Option<&str> {
96        use KdlValue::*;
97        match self {
98            String(s) => Some(s),
99            _ => None,
100        }
101    }
102
103    /// Returns `Some(i128)` if the `KdlValue` is a [`KdlValue::Integer`],
104    /// otherwise returns `None`.
105    pub fn as_integer(&self) -> Option<i128> {
106        use KdlValue::*;
107        match self {
108            Integer(i) => Some(*i),
109            _ => None,
110        }
111    }
112
113    /// Returns `Some(f64)` if the `KdlValue` is a [`KdlValue::Float`],
114    /// otherwise returns `None`.
115    pub fn as_float(&self) -> Option<f64> {
116        match self {
117            Self::Float(i) => Some(*i),
118            _ => None,
119        }
120    }
121
122    /// Returns `Some(bool)` if the `KdlValue` is a [`KdlValue::Bool`], otherwise returns `None`.
123    pub fn as_bool(&self) -> Option<bool> {
124        if let Self::Bool(v) = self {
125            Some(*v)
126        } else {
127            None
128        }
129    }
130}
131
132impl Display for KdlValue {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        match self {
135            Self::String(_) => self.write_string(f),
136            Self::Integer(value) => write!(f, "{value:?}"),
137            Self::Float(value) => write!(
138                f,
139                "{}",
140                if value == &f64::INFINITY {
141                    "#inf".into()
142                } else if value == &f64::NEG_INFINITY {
143                    "#-inf".into()
144                } else if value.is_nan() {
145                    "#nan".into()
146                } else {
147                    format!("{:?}", *value)
148                }
149            ),
150            Self::Bool(value) => write!(f, "#{value}"),
151            Self::Null => write!(f, "#null"),
152        }
153    }
154}
155
156pub(crate) fn is_plain_ident(ident: &str) -> bool {
157    let ident_bytes = ident.as_bytes();
158    ident
159        .find(crate::v2_parser::is_disallowed_ident_char)
160        .is_none()
161        && ident_bytes.first().map(|c| c.is_ascii_digit()) != Some(true)
162        && !(ident.chars().next().map(|c| matches!(c, '.' | '-' | '+')) == Some(true)
163            && ident_bytes.get(1).map(|c| c.is_ascii_digit()) == Some(true))
164        && ident != "inf"
165        && ident != "-inf"
166        && ident != "nan"
167        && ident != "true"
168        && ident != "false"
169        && ident != "null"
170}
171
172#[cfg(test)]
173#[test]
174fn plain_ident_test() {
175    assert!(is_plain_ident("foo123,bar"));
176    assert!(is_plain_ident("foo123~!@$%^&*.:'|?+<>,"));
177}
178
179impl KdlValue {
180    fn write_string(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181        let string = self.as_string().unwrap();
182        if !string.is_empty() && is_plain_ident(string) {
183            write!(f, "{string}")?;
184        } else {
185            write!(f, "\"")?;
186            for char in string.chars() {
187                match char {
188                    '\\' | '"' => write!(f, "\\{char}")?,
189                    '\n' => write!(f, "\\n")?,
190                    '\r' => write!(f, "\\r")?,
191                    '\t' => write!(f, "\\t")?,
192                    '\u{08}' => write!(f, "\\b")?,
193                    '\u{0C}' => write!(f, "\\f")?,
194                    _ => write!(f, "{char}")?,
195                }
196            }
197            write!(f, "\"")?;
198        }
199        Ok(())
200    }
201}
202
203impl From<i128> for KdlValue {
204    fn from(value: i128) -> Self {
205        Self::Integer(value)
206    }
207}
208
209impl From<f64> for KdlValue {
210    fn from(value: f64) -> Self {
211        Self::Float(value)
212    }
213}
214
215impl From<&str> for KdlValue {
216    fn from(value: &str) -> Self {
217        Self::String(value.to_string())
218    }
219}
220
221impl From<String> for KdlValue {
222    fn from(value: String) -> Self {
223        Self::String(value)
224    }
225}
226
227impl From<bool> for KdlValue {
228    fn from(value: bool) -> Self {
229        Self::Bool(value)
230    }
231}
232
233impl<T> From<Option<T>> for KdlValue
234where
235    T: Into<Self>,
236{
237    fn from(value: Option<T>) -> Self {
238        match value {
239            Some(value) => value.into(),
240            None => Self::Null,
241        }
242    }
243}
244
245#[cfg(feature = "v1")]
246impl From<kdlv1::KdlValue> for KdlValue {
247    fn from(value: kdlv1::KdlValue) -> Self {
248        match value {
249            kdlv1::KdlValue::RawString(s) => Self::String(s),
250            kdlv1::KdlValue::String(s) => Self::String(s),
251            kdlv1::KdlValue::Base2(i) => Self::Integer(i.into()),
252            kdlv1::KdlValue::Base8(i) => Self::Integer(i.into()),
253            kdlv1::KdlValue::Base10(i) => Self::Integer(i.into()),
254            kdlv1::KdlValue::Base10Float(f) => Self::Float(f),
255            kdlv1::KdlValue::Base16(i) => Self::Integer(i.into()),
256            kdlv1::KdlValue::Bool(b) => Self::Bool(b),
257            kdlv1::KdlValue::Null => Self::Null,
258        }
259    }
260}
261
262#[cfg(test)]
263mod test {
264    use super::*;
265
266    #[test]
267    fn formatting() {
268        let string = KdlValue::String("foo\n".into());
269        assert_eq!(format!("{string}"), r#""foo\n""#);
270
271        let integer = KdlValue::Integer(1234567890);
272        assert_eq!(format!("{integer}"), "1234567890");
273
274        let float = KdlValue::Float(1234567890.12345);
275        assert_eq!(format!("{float}"), "1234567890.12345");
276
277        let boolean = KdlValue::Bool(true);
278        assert_eq!(format!("{boolean}"), "#true");
279
280        let null = KdlValue::Null;
281        assert_eq!(format!("{null}"), "#null");
282    }
283}