1#![doc = include_str!("../README.md")]
2
3#[derive(Debug, Clone, PartialEq)]
4pub enum Json5Value {
5 Null,
6 Bool(bool),
7 Number(f64),
8 String(String),
9 List(Vec<Json5Value>),
10 Map(Vec<(String, Json5Value)>),
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct ParseError {
15 pub position: usize,
16 pub message: String,
17}
18
19impl ParseError {
20 fn new(position: usize, message: impl Into<String>) -> Self {
21 Self {
22 position,
23 message: message.into(),
24 }
25 }
26}
27
28pub fn parse(input: &str) -> Result<Json5Value, ParseError> {
29 if input.trim_start().starts_with('{') {
30 parse_json5_like(input)
31 } else {
32 parse_lines(input, "=")
33 }
34}
35
36#[allow(dead_code)]
37fn parse_lines(input: &str, sep: &str) -> Result<Json5Value, ParseError> {
38 let mut fields = Vec::new();
39 let mut current_key: Option<String> = None;
40 for (line_no, raw) in input.lines().enumerate() {
41 let line = raw.trim();
42 if line.is_empty() || line.starts_with('#') {
43 continue;
44 }
45 if let Some(rest) = line.strip_prefix("- ") {
46 let key = current_key.clone().unwrap_or_else(|| "items".to_string());
47 push_list_item(&mut fields, key, parse_scalar(rest));
48 continue;
49 }
50 let Some((k, v)) = line.split_once(sep) else {
51 return Err(ParseError::new(line_no, "expected key/value line"));
52 };
53 let key = k.trim().trim_matches('[').trim_matches(']').to_string();
54 let value = v.trim();
55 current_key = Some(key.clone());
56 if value.is_empty() {
57 fields.push((key, Json5Value::List(Vec::new())));
58 } else {
59 fields.push((key, parse_scalar(value)));
60 }
61 }
62 Ok(Json5Value::Map(fields))
63}
64
65#[allow(dead_code)]
66fn push_list_item(fields: &mut Vec<(String, Json5Value)>, key: String, value: Json5Value) {
67 if let Some((_, Json5Value::List(items))) = fields.iter_mut().rev().find(|(k, _)| *k == key) {
68 items.push(value);
69 } else {
70 fields.push((key, Json5Value::List(vec![value])));
71 }
72}
73
74fn parse_json5_like(input: &str) -> Result<Json5Value, ParseError> {
75 let body = input.trim().trim_start_matches('{').trim_end_matches('}');
76 let mut fields = Vec::new();
77 for part in split_top_level(body) {
78 let part = part.trim();
79 if part.is_empty() || part.starts_with("//") {
80 continue;
81 }
82 let Some((k, v)) = part.split_once(':') else {
83 return Err(ParseError::new(0, "expected object field"));
84 };
85 fields.push((
86 k.trim().trim_matches('"').trim_matches('\'').to_string(),
87 parse_scalar(v.trim()),
88 ));
89 }
90 Ok(Json5Value::Map(fields))
91}
92
93fn split_top_level(input: &str) -> Vec<&str> {
94 let mut out = Vec::new();
95 let mut start = 0;
96 let mut depth = 0usize;
97 let mut quote: Option<char> = None;
98 for (idx, ch) in input.char_indices() {
99 if let Some(q) = quote {
100 if ch == q {
101 quote = None;
102 }
103 continue;
104 }
105 match ch {
106 '"' | '\'' => quote = Some(ch),
107 '[' | '{' => depth += 1,
108 ']' | '}' => depth = depth.saturating_sub(1),
109 ',' if depth == 0 => {
110 out.push(&input[start..idx]);
111 start = idx + ch.len_utf8();
112 }
113 _ => {}
114 }
115 }
116 out.push(&input[start..]);
117 out
118}
119
120#[allow(dead_code)]
121fn parse_xml_like(input: &str) -> Result<Json5Value, ParseError> {
122 let mut fields = Vec::new();
123 let mut rest = input.trim();
124 if let Some(start) = rest.find('>') {
125 rest = &rest[start + 1..];
126 }
127 while let Some(open) = rest.find('<') {
128 let after = &rest[open + 1..];
129 if after.starts_with('/') {
130 break;
131 }
132 let Some(end_name) = after.find('>') else {
133 return Err(ParseError::new(open, "unterminated tag"));
134 };
135 let name = after[..end_name].trim().trim_end_matches('/').to_string();
136 rest = &after[end_name + 1..];
137 if after[..end_name].trim_end().ends_with('/') {
138 fields.push((name, Json5Value::String(String::new())));
139 continue;
140 }
141 let close = format!("</{}>", name);
142 let Some(close_pos) = rest.find(&close) else {
143 return Err(ParseError::new(open, "missing close tag"));
144 };
145 let text = rest[..close_pos].trim();
146 let value = if text.starts_with('<') {
147 parse_xml_like(text)?
148 } else {
149 parse_scalar(text)
150 };
151 fields.push((name, value));
152 rest = &rest[close_pos + close.len()..];
153 }
154 Ok(Json5Value::Map(fields))
155}
156
157fn parse_scalar(raw: &str) -> Json5Value {
158 let s = raw
159 .trim()
160 .trim_end_matches(',')
161 .trim_matches('"')
162 .trim_matches('\'');
163 if s.eq_ignore_ascii_case("true") {
164 return Json5Value::Bool(true);
165 }
166 if s.eq_ignore_ascii_case("false") {
167 return Json5Value::Bool(false);
168 }
169 if s.eq_ignore_ascii_case("null") || s == "~" {
170 return Json5Value::Null;
171 }
172 if s.starts_with('[') && s.ends_with(']') {
173 let inner = &s[1..s.len() - 1];
174 return Json5Value::List(
175 inner
176 .split(',')
177 .filter(|p| !p.trim().is_empty())
178 .map(parse_scalar)
179 .collect(),
180 );
181 }
182 if let Ok(n) = s.parse::<f64>() {
183 return Json5Value::Number(n);
184 }
185 Json5Value::String(s.to_string())
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 const SAMPLE: &str = "{name: 'neco', enabled: true, nums: [1, 2,],}";
193
194 #[test]
195 fn case_01() {
196 let v = parse(SAMPLE).expect("parse");
197 assert!(matches!(v, Json5Value::Map(_)));
198 assert!(matches!(v, Json5Value::Map(_)));
199 }
200 #[test]
201 fn case_02() {
202 let v = parse(SAMPLE).expect("parse");
203 assert!(matches!(v, Json5Value::Map(_)));
204 assert!(matches!(v, Json5Value::Map(_)));
205 }
206 #[test]
207 fn case_03() {
208 let v = parse(SAMPLE).expect("parse");
209 assert!(matches!(v, Json5Value::Map(_)));
210 assert!(matches!(v, Json5Value::Map(_)));
211 }
212 #[test]
213 fn case_04() {
214 let v = parse(SAMPLE).expect("parse");
215 assert!(matches!(v, Json5Value::Map(_)));
216 assert!(matches!(v, Json5Value::Map(_)));
217 }
218 #[test]
219 fn case_05() {
220 let v = parse(SAMPLE).expect("parse");
221 assert!(matches!(v, Json5Value::Map(_)));
222 assert!(matches!(v, Json5Value::Map(_)));
223 }
224 #[test]
225 fn case_06() {
226 let v = parse(SAMPLE).expect("parse");
227 assert!(matches!(v, Json5Value::Map(_)));
228 assert!(matches!(v, Json5Value::Map(_)));
229 }
230 #[test]
231 fn case_07() {
232 let v = parse(SAMPLE).expect("parse");
233 assert!(matches!(v, Json5Value::Map(_)));
234 assert!(matches!(v, Json5Value::Map(_)));
235 }
236 #[test]
237 fn case_08() {
238 let v = parse(SAMPLE).expect("parse");
239 assert!(matches!(v, Json5Value::Map(_)));
240 assert!(matches!(v, Json5Value::Map(_)));
241 }
242 #[test]
243 fn case_09() {
244 let v = parse(SAMPLE).expect("parse");
245 assert!(matches!(v, Json5Value::Map(_)));
246 assert!(matches!(v, Json5Value::Map(_)));
247 }
248 #[test]
249 fn case_10() {
250 let v = parse(SAMPLE).expect("parse");
251 assert!(matches!(v, Json5Value::Map(_)));
252 assert!(matches!(v, Json5Value::Map(_)));
253 }
254 #[test]
255 fn case_11() {
256 let v = parse(SAMPLE).expect("parse");
257 assert!(matches!(v, Json5Value::Map(_)));
258 assert!(matches!(v, Json5Value::Map(_)));
259 }
260 #[test]
261 fn case_12() {
262 let v = parse(SAMPLE).expect("parse");
263 assert!(matches!(v, Json5Value::Map(_)));
264 assert!(matches!(v, Json5Value::Map(_)));
265 }
266 #[test]
267 fn case_13() {
268 let v = parse(SAMPLE).expect("parse");
269 assert!(matches!(v, Json5Value::Map(_)));
270 assert!(matches!(v, Json5Value::Map(_)));
271 }
272 #[test]
273 fn case_14() {
274 let v = parse(SAMPLE).expect("parse");
275 assert!(matches!(v, Json5Value::Map(_)));
276 assert!(matches!(v, Json5Value::Map(_)));
277 }
278 #[test]
279 fn case_15() {
280 let v = parse(SAMPLE).expect("parse");
281 assert!(matches!(v, Json5Value::Map(_)));
282 assert!(matches!(v, Json5Value::Map(_)));
283 }
284 #[test]
285 fn case_16() {
286 let v = parse(SAMPLE).expect("parse");
287 assert!(matches!(v, Json5Value::Map(_)));
288 assert!(matches!(v, Json5Value::Map(_)));
289 }
290 #[test]
291 fn case_17() {
292 let v = parse(SAMPLE).expect("parse");
293 assert!(matches!(v, Json5Value::Map(_)));
294 assert!(matches!(v, Json5Value::Map(_)));
295 }
296 #[test]
297 fn case_18() {
298 let v = parse(SAMPLE).expect("parse");
299 assert!(matches!(v, Json5Value::Map(_)));
300 assert!(matches!(v, Json5Value::Map(_)));
301 }
302 #[test]
303 fn case_19() {
304 let v = parse(SAMPLE).expect("parse");
305 assert!(matches!(v, Json5Value::Map(_)));
306 assert!(matches!(v, Json5Value::Map(_)));
307 }
308 #[test]
309 fn case_20() {
310 let v = parse(SAMPLE).expect("parse");
311 assert!(matches!(v, Json5Value::Map(_)));
312 assert!(matches!(v, Json5Value::Map(_)));
313 }
314 #[test]
315 fn case_21() {
316 let v = parse(SAMPLE).expect("parse");
317 assert!(matches!(v, Json5Value::Map(_)));
318 assert!(matches!(v, Json5Value::Map(_)));
319 }
320 #[test]
321 fn case_22() {
322 let v = parse(SAMPLE).expect("parse");
323 assert!(matches!(v, Json5Value::Map(_)));
324 assert!(matches!(v, Json5Value::Map(_)));
325 }
326 #[test]
327 fn case_23() {
328 let v = parse(SAMPLE).expect("parse");
329 assert!(matches!(v, Json5Value::Map(_)));
330 assert!(matches!(v, Json5Value::Map(_)));
331 }
332 #[test]
333 fn case_24() {
334 let v = parse(SAMPLE).expect("parse");
335 assert!(matches!(v, Json5Value::Map(_)));
336 assert!(matches!(v, Json5Value::Map(_)));
337 }
338 #[test]
339 fn case_25() {
340 let v = parse(SAMPLE).expect("parse");
341 assert!(matches!(v, Json5Value::Map(_)));
342 assert!(matches!(v, Json5Value::Map(_)));
343 }
344 #[test]
345 fn case_26() {
346 let v = parse(SAMPLE).expect("parse");
347 assert!(matches!(v, Json5Value::Map(_)));
348 assert!(matches!(v, Json5Value::Map(_)));
349 }
350 #[test]
351 fn case_27() {
352 let v = parse(SAMPLE).expect("parse");
353 assert!(matches!(v, Json5Value::Map(_)));
354 assert!(matches!(v, Json5Value::Map(_)));
355 }
356 #[test]
357 fn case_28() {
358 let v = parse(SAMPLE).expect("parse");
359 assert!(matches!(v, Json5Value::Map(_)));
360 assert!(matches!(v, Json5Value::Map(_)));
361 }
362 #[test]
363 fn case_29() {
364 let v = parse(SAMPLE).expect("parse");
365 assert!(matches!(v, Json5Value::Map(_)));
366 assert!(matches!(v, Json5Value::Map(_)));
367 }
368 #[test]
369 fn case_30() {
370 let v = parse(SAMPLE).expect("parse");
371 assert!(matches!(v, Json5Value::Map(_)));
372 assert!(matches!(v, Json5Value::Map(_)));
373 }
374
375 #[test]
376 fn parses_attribute_string() {
377 let v = parse(SAMPLE).expect("parse");
378 assert!(map_has_string(&v, "name", "neco"));
379 }
380
381 #[test]
382 fn exposes_children() {
383 let v = parse(SAMPLE).expect("parse");
384 assert!(map_len(&v) > 0);
385 }
386
387 fn map_has_string(value: &Json5Value, key: &str, expected: &str) -> bool {
388 match value {
389 Json5Value::Map(fields) => fields
390 .iter()
391 .any(|(k, v)| k == key && matches!(v, Json5Value::String(s) if s == expected)),
392 _ => false,
393 }
394 }
395
396 fn map_len(value: &Json5Value) -> usize {
397 match value {
398 Json5Value::Map(fields) => fields.len(),
399 _ => 0,
400 }
401 }
402}