1use crate::linked_hash_map::LinkedHashMap;
2use std::hash::{Hash, Hasher};
3
4#[derive(Clone, PartialEq, PartialOrd, Debug, Eq, Ord)]
15pub enum Yaml {
16 Real(String),
17 Integer(i64),
18 String(String),
19 Boolean(bool),
20 Array(Vec<Yaml>),
21 Hash(LinkedHashMap<Yaml, Yaml>),
22 Alias(usize),
23 Tagged(String, Box<Yaml>),
24 Null,
25 BadValue,
26}
27
28impl Hash for Yaml {
29 #[inline]
30 fn hash<H: Hasher>(&self, state: &mut H) {
31 match self {
32 Self::Real(s) => {
33 0.hash(state);
34 s.hash(state);
35 }
36 Self::Integer(i) => {
37 1.hash(state);
38 i.hash(state);
39 }
40 Self::String(s) => {
41 2.hash(state);
42 s.hash(state);
43 }
44 Self::Boolean(b) => {
45 3.hash(state);
46 b.hash(state);
47 }
48 Self::Array(a) => {
49 4.hash(state);
50 a.hash(state);
51 }
52 Self::Hash(h) => {
53 5.hash(state);
54 h.hash(state);
55 }
56 Self::Alias(i) => {
57 6.hash(state);
58 i.hash(state);
59 }
60 Self::Tagged(tag, value) => {
61 7.hash(state);
62 tag.hash(state);
63 value.hash(state);
64 }
65 Self::Null => {
66 8.hash(state);
67 }
68 Self::BadValue => {
69 9.hash(state);
70 }
71 }
72 }
73}
74
75static BAD_VALUE: Yaml = Yaml::BadValue;
77
78impl Yaml {
80 #[inline(always)]
81 #[must_use]
82 pub const fn as_bool(&self) -> Option<bool> {
83 match *self {
84 Self::Boolean(b) => Some(b),
85 _ => None,
86 }
87 }
88
89 #[inline(always)]
90 #[must_use]
91 pub const fn as_i64(&self) -> Option<i64> {
92 match *self {
93 Self::Integer(i) => Some(i),
94 _ => None,
95 }
96 }
97
98 #[inline]
99 #[must_use]
100 pub fn as_f64(&self) -> Option<f64> {
101 match *self {
102 Self::Real(ref s) => parse_f64(s),
103 _ => None,
104 }
105 }
106
107 #[inline(always)]
108 #[must_use]
109 pub fn as_str(&self) -> Option<&str> {
110 match *self {
111 Self::String(ref s) => Some(s),
112 _ => None,
113 }
114 }
115
116 #[inline(always)]
117 #[must_use]
118 pub fn as_vec(&self) -> Option<&[Self]> {
119 match *self {
120 Self::Array(ref v) => Some(v),
121 _ => None,
122 }
123 }
124
125 #[inline(always)]
126 #[must_use]
127 pub const fn as_hash(&self) -> Option<&LinkedHashMap<Self, Self>> {
128 match *self {
129 Self::Hash(ref h) => Some(h),
130 _ => None,
131 }
132 }
133
134 #[inline(always)]
135 #[must_use]
136 pub const fn is_null(&self) -> bool {
137 matches!(*self, Self::Null)
138 }
139
140 #[inline(always)]
141 #[must_use]
142 pub const fn is_badvalue(&self) -> bool {
143 matches!(*self, Self::BadValue)
144 }
145
146 #[inline]
148 #[must_use]
149 pub fn parse_str(v: &str) -> Self {
150 if let Some(stripped) = v.strip_prefix("0x")
152 && !stripped.is_empty()
153 && stripped.chars().all(|c| c.is_ascii_hexdigit())
154 && let Ok(i) = i64::from_str_radix(stripped, 16)
155 {
156 return Self::Integer(i);
157 }
158 if let Some(stripped) = v.strip_prefix("+0x")
159 && !stripped.is_empty()
160 && stripped.chars().all(|c| c.is_ascii_hexdigit())
161 && let Ok(i) = i64::from_str_radix(stripped, 16)
162 {
163 return Self::Integer(i);
164 }
165 if let Some(stripped) = v.strip_prefix("-0x")
166 && !stripped.is_empty()
167 && stripped.chars().all(|c| c.is_ascii_hexdigit())
168 && let Ok(i) = i64::from_str_radix(stripped, 16)
169 {
170 return Self::Integer(-i);
171 }
172 if let Some(stripped) = v.strip_prefix("0o")
174 && !stripped.is_empty()
175 && stripped.chars().all(|c| c.is_ascii_digit() && c < '8')
176 && let Ok(i) = i64::from_str_radix(stripped, 8)
177 {
178 return Self::Integer(i);
179 }
180 if let Some(stripped) = v.strip_prefix("+0o")
181 && !stripped.is_empty()
182 && stripped.chars().all(|c| c.is_ascii_digit() && c < '8')
183 && let Ok(i) = i64::from_str_radix(stripped, 8)
184 {
185 return Self::Integer(i);
186 }
187 if let Some(stripped) = v.strip_prefix("-0o")
188 && !stripped.is_empty()
189 && stripped.chars().all(|c| c.is_ascii_digit() && c < '8')
190 && let Ok(i) = i64::from_str_radix(stripped, 8)
191 {
192 return Self::Integer(-i);
193 }
194 if let Some(stripped) = v.strip_prefix("0b")
196 && !stripped.is_empty()
197 && stripped.chars().all(|c| c == '0' || c == '1')
198 && let Ok(i) = i64::from_str_radix(stripped, 2)
199 {
200 return Self::Integer(i);
201 }
202 if let Some(stripped) = v.strip_prefix("+0b")
203 && !stripped.is_empty()
204 && stripped.chars().all(|c| c == '0' || c == '1')
205 && let Ok(i) = i64::from_str_radix(stripped, 2)
206 {
207 return Self::Integer(i);
208 }
209 if let Some(stripped) = v.strip_prefix("-0b")
210 && !stripped.is_empty()
211 && stripped.chars().all(|c| c == '0' || c == '1')
212 && let Ok(i) = i64::from_str_radix(stripped, 2)
213 {
214 return Self::Integer(-i);
215 }
216 if let Some(stripped) = v.strip_prefix('+')
217 && let Ok(i) = stripped.parse::<i64>()
218 && !has_invalid_leading_zeros(v)
219 && !has_invalid_sign_prefix(v)
220 {
221 return Self::Integer(i);
222 }
223 match v {
224 "~" | "null" => Self::Null,
225 "true" => Self::Boolean(true),
226 "false" => Self::Boolean(false),
227 _ if v.parse::<i64>().is_ok()
228 && !has_invalid_leading_zeros(v)
229 && !has_invalid_sign_prefix(v) =>
230 {
231 if let Ok(i) = v.parse::<i64>() {
232 Self::Integer(i)
233 } else {
234 Self::String(v.into())
235 }
236 }
237 _ if parse_f64(v).is_some() => Self::Real(v.into()),
238 _ => Self::String(v.into()),
239 }
240 }
241}
242
243fn has_invalid_sign_prefix(v: &str) -> bool {
245 v.starts_with("++") || v.starts_with("+-") || v.starts_with("-+") || v.starts_with("--")
246}
247
248fn has_invalid_leading_zeros(v: &str) -> bool {
252 if v == "0" || v == "+0" || v == "-0" {
254 return false;
255 }
256
257 if v.starts_with('0') && v.len() > 1 && v.chars().nth(1)
259 .expect("nth(1) cannot be None when v.len() > 1").is_ascii_digit() {
260 return true;
261 }
262
263 if (v.starts_with("+0") || v.starts_with("-0"))
265 && v.len() > 2
266 && v.chars().nth(2)
267 .expect("nth(2) cannot be None when v.len() > 2").is_ascii_digit()
268 {
269 return true;
270 }
271
272 false
273}
274
275pub fn parse_f64(v: &str) -> Option<f64> {
277 match v {
278 ".inf" | ".Inf" | ".INF" | "+.inf" | "+.Inf" | "+.INF" => Some(f64::INFINITY),
279 "-.inf" | "-.Inf" | "-.INF" => Some(f64::NEG_INFINITY),
280 ".nan" | ".NaN" | ".NAN" => Some(f64::NAN),
281 _ => {
282 if has_invalid_leading_zeros(v) || has_invalid_sign_prefix(v) {
284 None
285 } else {
286 v.parse::<f64>().ok()
287 }
288 }
289 }
290}
291
292impl std::ops::Index<&str> for Yaml {
294 type Output = Self;
295 #[inline]
296 fn index(&self, idx: &str) -> &Self {
297 let key = Self::String(idx.to_owned());
298 match self.as_hash() {
299 Some(h) => h.get(&key).unwrap_or(&BAD_VALUE),
300 None => &BAD_VALUE,
301 }
302 }
303}
304
305impl std::ops::Index<usize> for Yaml {
307 type Output = Self;
308 #[inline]
309 fn index(&self, idx: usize) -> &Self {
310 if let Some(v) = self.as_vec() {
311 v.get(idx).unwrap_or(&BAD_VALUE)
312 } else if let Some(h) = self.as_hash() {
313 let key = Self::Integer(idx as i64);
314 h.get(&key).unwrap_or(&BAD_VALUE)
315 } else {
316 &BAD_VALUE
317 }
318 }
319}