1use std::fmt::Display;
2
3#[derive(Debug, Clone, PartialOrd)]
5pub enum KdlValue {
6 String(String),
8
9 Integer(i128),
12
13 Float(f64),
16
17 Bool(bool),
19
20 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 _ 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
48impl 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 (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 pub fn is_string(&self) -> bool {
70 matches!(self, Self::String(..))
71 }
72
73 pub fn is_integer(&self) -> bool {
75 matches!(self, Self::Integer(..))
76 }
77
78 pub fn is_float(&self) -> bool {
80 matches!(self, Self::Float(..))
81 }
82
83 pub fn is_bool(&self) -> bool {
85 matches!(self, Self::Bool(..))
86 }
87
88 pub fn is_null(&self) -> bool {
90 matches!(self, Self::Null)
91 }
92
93 pub fn as_string(&self) -> Option<&str> {
96 use KdlValue::*;
97 match self {
98 String(s) => Some(s),
99 _ => None,
100 }
101 }
102
103 pub fn as_integer(&self) -> Option<i128> {
106 use KdlValue::*;
107 match self {
108 Integer(i) => Some(*i),
109 _ => None,
110 }
111 }
112
113 pub fn as_float(&self) -> Option<f64> {
116 match self {
117 Self::Float(i) => Some(*i),
118 _ => None,
119 }
120 }
121
122 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}