johnrs/
lib.rs

1use std::collections::HashMap;
2use std::fmt::{self, Display, Formatter};
3use regex::Regex;
4
5pub fn stringify(input: &JohnValue) -> String {
6    match input {
7        JohnValue::JohnAbyss => "#".to_string(),
8        JohnValue::JohnBool(b) => b.to_string(),
9        JohnValue::JohnInt(i) => i.to_string(),
10        JohnValue::JohnFloat(f) => f.to_string(),
11        JohnValue::JohnString(s) => format!("\"{}\"", s),
12        JohnValue::JohnChar(c) => format!("'{}'", c),
13        JohnValue::JohnArray(a) => format!("[{}]", a.iter().map(|v| stringify(v)).collect::<Vec<String>>().join(", ")),
14        JohnValue::JohnObject(o) => format!("{{{}}}", o.iter().map(|(k, v)| format!("{}: {}", k, stringify(v))).collect::<Vec<String>>().join(", ")),
15        JohnValue::JohnTuple(t) => format!("({})", t.iter().map(|v| stringify(v)).collect::<Vec<String>>().join(", ")),
16        JohnValue::JohnRange(start, end, step) => {
17            if let Some(step) = step {
18                format!("{}..{}..{}", start, end, step)
19            } else {
20                format!("{}..{}", start, end)
21            }
22        }
23        JohnValue::JohnIndex(true, i) => format!("^{}", i),
24        JohnValue::JohnIndex(false, i) => format!("*{}", i),
25        JohnValue::JohnVersion(major, minor, patch, build) => {
26            if let Some(patch) = patch {
27                if let Some(build) = build {
28                    format!("v{}.{}.{}.{}", major, minor, patch, build)
29                } else {
30                    format!("v{}.{}.{}", major, minor, patch)
31                }
32            } else {
33                format!("v{}.{}", major, minor)
34            }
35        }
36    }
37}
38
39pub fn parse(input: &str) -> Result<JohnValue, String> {
40    let tokens = tokenize(input);
41    let mut parser = JohnParser::new(tokens);
42    // Either Top level object or value
43    match parser.peek() {
44        Some(Token::Identifier(_)) => {
45            parser.parse_tl_john_object()
46        }
47        Some(_) => {
48            parser.parse_john_value()
49        }
50        None => {
51            Err("Empty input".to_string())
52        }
53    }
54}
55
56struct JohnParser {
57    tokens: Vec<Token>,
58    index: usize,
59}
60
61impl JohnParser {
62    fn new(tokens: Vec<Token>) -> JohnParser {
63        JohnParser {
64            tokens,
65            index: 0,
66        }
67    }
68
69    fn next(&mut self) -> Option<&Token> {
70        if self.index < self.tokens.len() {
71            let token = &self.tokens[self.index];
72            self.index += 1;
73            Some(token)
74        } else {
75            None
76        }
77    }
78
79    fn peek(&self) -> Option<&Token> {
80        if self.index < self.tokens.len() {
81            Some(&self.tokens[self.index])
82        } else {
83            None
84        }
85    }
86
87    fn parse_tl_john_object(&mut self) -> Result<JohnValue, String> {
88        let mut object = HashMap::new();
89        while let Some(Token::Identifier(key)) = self.next() {
90            let k = key.to_string();
91            match self.peek() {
92                Some(_) => { 
93                    object.insert(k, self.parse_john_value()?);
94                }
95                None => {
96                    return Err("Invalid value".to_string())
97                }
98            }
99        }
100        Ok(JohnValue::JohnObject(object))
101    }
102
103    fn parse_john_value(&mut self) -> Result<JohnValue, String> {
104        match self.next() {
105            Some(token) => match token {
106                Token::Value(value) => {
107                    parse_john_primitive(value)
108                }
109                Token::OpenCurly => {
110                    self.parse_john_object()
111                }
112                Token::OpenSquare => {
113                    self.parse_john_array()
114                }
115                Token::OpenParen => {
116                    self.parse_john_tuple()
117                }
118                _ => {
119                    Err(format!("Unexpected {}", token))
120                }
121            }
122            _ => {
123                Err("Invalid value".to_string())
124            }
125        }
126    }
127
128    fn parse_john_object(&mut self) -> Result<JohnValue, String> {
129        let mut object = HashMap::new();
130        while let Some(Token::Identifier(key)) = self.next() { // should discard closing curly
131            let k = key.to_string();
132            match self.peek() {
133                Some(_) => { 
134                    object.insert(k, self.parse_john_value()?);
135                }
136                None => {
137                    return Err("Invalid value".to_string())
138                }
139            }
140        }
141        Ok(JohnValue::JohnObject(object))
142    }
143
144    fn parse_john_array(&mut self) -> Result<JohnValue, String> {
145        let mut array = vec![];
146        while let Ok(value) = self.parse_john_value() {
147            array.push(value);
148        }
149        Ok(JohnValue::JohnArray(array))
150        // Funny thing, any construct can end with any bracket in this implementation...
151    }
152
153    fn parse_john_tuple(&mut self) -> Result<JohnValue, String> {
154        let mut tuple = vec![];
155        while let Ok(value) = self.parse_john_value() {
156            tuple.push(value);
157        }
158        Ok(JohnValue::JohnTuple(tuple))
159    }
160}
161
162fn parse_john_primitive(value: &String) -> Result<JohnValue, String> {
163    if let Ok(int) = value.parse::<i64>() {
164        Ok(JohnValue::JohnInt(int))
165    } else if let Ok(float) = value.parse::<f64>() {
166        Ok(JohnValue::JohnFloat(float))
167    } else if value == "true" {
168        Ok(JohnValue::JohnBool(true))
169    } else if value == "false" {
170        Ok(JohnValue::JohnBool(false))
171    } else if value.starts_with("\"") && value.ends_with("\"") {
172        Ok(JohnValue::JohnString(value[1..value.len() - 1].to_string()))
173    } else if value.starts_with("'") && value.ends_with("'") {
174        Ok(JohnValue::JohnChar(value.chars().nth(1).unwrap()))
175    } else if value == "abyss" || value == "#" {
176        Ok(JohnValue::JohnAbyss)
177    } else if value.starts_with("v") {
178        let parts: Vec<&str> = value[1..].split('.').collect();
179        let major = parts[0].parse::<i64>().unwrap();
180        let minor = parts[1].parse::<i64>().unwrap();
181        if parts.len() == 2 {
182            Ok(JohnValue::JohnVersion(major, minor, None, None))
183        } else if parts.len() == 3 {
184            Ok(JohnValue::JohnVersion(major, minor, Some(parts[2].parse::<i64>().unwrap()), None))
185        } else if parts.len() == 4 {
186            Ok(JohnValue::JohnVersion(major, minor, Some(parts[2].parse::<i64>().unwrap()), Some(parts[3].parse::<i64>().unwrap())))
187        } else {
188            Err("Invalid version".to_string())
189        }
190    } else if value.starts_with("*") {
191        Ok(JohnValue::JohnIndex(false, value[1..].parse::<i64>().unwrap()))
192    } else if value.starts_with("^") {
193        Ok(JohnValue::JohnIndex(true, value[1..].parse::<i64>().unwrap()))
194    } else if value.contains("..") {
195        let parts: Vec<&str> = value.split("..").collect();
196        let start = parts[0].parse::<i64>().unwrap();
197        let end = parts[1].parse::<i64>().unwrap();
198        if parts.len() == 2 {
199            Ok(JohnValue::JohnRange(start, end, None))
200        } else if parts.len() == 3 {
201            Ok(JohnValue::JohnRange(start, end, Some(parts[2].parse::<i64>().unwrap())))
202        } else {
203            Err("Invalid range".to_string())
204        }
205    } else {
206        Err("Invalid value".to_string())
207    }
208}
209
210pub fn minify(input: &str) -> String {
211    let re = Regex::new(r"\s*([\{\[\(\)\]\}])\s*").unwrap();
212    let tokens = tokenize(input);
213    return re.replace_all(
214        &tokens
215            .iter()
216            .map(|t| t.into())
217            .collect::<Vec<String>>()
218            .join(" "),
219        "$1"
220    ).into();
221}
222
223fn tokenize(input: &str) -> Vec<Token> {
224    let mut tokens = vec![];
225    let mut chars = input.chars().peekable();
226    while let Some(c) = chars.next() {
227        match c {
228            '{' => tokens.push(Token::OpenCurly),
229            '}' => tokens.push(Token::CloseCurly),
230            '[' => tokens.push(Token::OpenSquare),
231            ']' => tokens.push(Token::CloseSquare),
232            '(' => tokens.push(Token::OpenParen),
233            ')' => tokens.push(Token::CloseParen),
234            _ if c.is_whitespace() || c == ':' || c == ',' || c == ';' => {}
235            // parse anything that isn't a delimeter; differentiation in "into" impl
236            _ => {
237                let mut s = String::new();
238                s.push(c);
239                while let Some(&c) = chars.peek() {
240                    if !c.is_whitespace() && c != ':' && c != ',' && c != ';' && c != '{' && c != '}' && c != '[' && c != ']' && c != '(' && c != ')' {
241                        s.push(c);
242                        chars.next();
243                    } else {
244                        break;
245                    }
246                }
247                tokens.push(s.into());
248            }
249        }
250    }
251    tokens
252}
253
254#[derive(Debug)]
255#[derive(PartialEq)]
256enum Token {
257    OpenCurly,
258    CloseCurly,
259    OpenSquare,
260    CloseSquare,
261    OpenParen,
262    CloseParen,
263    Value(String),
264    Identifier(String)
265}
266
267impl Into<String> for &Token {
268    fn into(self) -> String {
269        match self {
270            Token::OpenCurly => "{".to_string(),
271            Token::CloseCurly => "}".to_string(),
272            Token::OpenSquare => "[".to_string(),
273            Token::CloseSquare => "]".to_string(),
274            Token::OpenParen => "(".to_string(),
275            Token::CloseParen => ")".to_string(),
276            Token::Value(s) => s.to_string(),
277            Token::Identifier(s) => s.to_string(),
278        }
279    }
280}
281
282impl Display for Token {
283    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
284        write!(f, "{}", Into::<String>::into(self))
285    }
286}
287
288impl From<String> for Token {
289    fn from(s: String) -> Self {
290        match s.as_str() {
291            "{" => Token::OpenCurly,
292            "}" => Token::CloseCurly,
293            "[" => Token::OpenSquare,
294            "]" => Token::CloseSquare,
295            "(" => Token::OpenParen,
296            ")" => Token::CloseParen,
297            _ => {
298                // the power of JOHN is that i literally don't have to worry about context
299                let re = Regex::new(r"^[a-zA-Z_]\w*$").unwrap();
300                if re.is_match(&s) && s != "true" && s != "false" && s != "abyss" {
301                    Token::Identifier(s)
302                } else {
303                    Token::Value(s)
304                }
305            }
306        }
307    }
308}
309
310#[derive(Debug)]
311#[derive(PartialEq)]
312pub enum JohnValue {
313    JohnAbyss,
314    JohnBool(bool),
315    JohnInt(i64),
316    JohnFloat(f64),
317    JohnString(String),
318    JohnChar(char),
319    JohnArray(Vec<JohnValue>),
320    JohnObject(HashMap<String, JohnValue>),
321    JohnTuple(Vec<JohnValue>),
322    JohnRange(i64, i64, Option<i64>),
323    JohnIndex(bool, i64),
324    JohnVersion(i64, i64, Option<i64>, Option<i64>),
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330    
331    #[test]
332    fn test_tokenize() {
333        assert_eq!(tokenize(r#"
334            {
335                a: 1,
336                b 2;
337                c [
338                    3,
339                    4
340                    { d: "hi" }
341                ], g (1 "2" 3)
342            }
343        "#), vec![
344            Token::OpenCurly,
345            Token::Identifier("a".to_string()),
346            Token::Value("1".to_string()),
347            Token::Identifier("b".to_string()),
348            Token::Value("2".to_string()),
349            Token::Identifier("c".to_string()),
350            Token::OpenSquare,
351            Token::Value("3".to_string()),
352            Token::Value("4".to_string()),
353            Token::OpenCurly,
354            Token::Identifier("d".to_string()),
355            Token::Value("\"hi\"".to_string()),
356            Token::CloseCurly,
357            Token::CloseSquare,
358            Token::Identifier("g".to_string()),
359            Token::OpenParen,
360            Token::Value("1".to_string()),
361            Token::Value("\"2\"".to_string()),
362            Token::Value("3".to_string()),
363            Token::CloseParen,
364            Token::CloseCurly,
365        ]);
366    }
367
368    #[test]
369    fn test_minify() {
370        assert_eq!(minify(r"
371            {
372                a: 1,
373                b 2;
374                c 3
375            }
376        "), r"{a 1 b 2 c 3}");
377    }
378
379    #[test]
380    fn test_value_int() {
381        assert_eq!(parse("1"), Ok(JohnValue::JohnInt(1)));
382    }
383
384    #[test]
385    fn test_value_float() {
386        assert_eq!(parse("1.25"), Ok(JohnValue::JohnFloat(1.25)));
387    }
388
389    #[test]
390    fn test_value_string() {
391        assert_eq!(parse(r#""hello""#), Ok(JohnValue::JohnString("hello".to_string())));
392    }
393
394    #[test]
395    fn test_value_char() {
396        assert_eq!(parse("'a'"), Ok(JohnValue::JohnChar('a')));
397    }
398
399    #[test]
400    fn test_value_bool() {
401        assert_eq!(parse("true"), Ok(JohnValue::JohnBool(true)));
402        assert_eq!(parse("false"), Ok(JohnValue::JohnBool(false)));
403    }
404
405    #[test]
406    fn test_value_abyss() {
407        assert_eq!(parse("#"), Ok(JohnValue::JohnAbyss));
408        assert_eq!(parse("abyss"), Ok(JohnValue::JohnAbyss));
409    }
410
411    #[test]
412    fn test_value_range() {
413        assert_eq!(parse("1..5"), Ok(JohnValue::JohnRange(1, 5, None)));
414        assert_eq!(parse("1..5..2"), Ok(JohnValue::JohnRange(1, 5, Some(2))));
415    }
416
417    #[test]
418    fn test_value_index() {
419        assert_eq!(parse("*1"), Ok(JohnValue::JohnIndex(false, 1)));
420        assert_eq!(parse("^1"), Ok(JohnValue::JohnIndex(true, 1)));
421    }
422
423    #[test]
424    fn test_value_version() {
425        assert_eq!(parse("v1.2"), Ok(JohnValue::JohnVersion(1, 2, None, None)));
426        assert_eq!(parse("v1.2.3"), Ok(JohnValue::JohnVersion(1, 2, Some(3), None)));
427        assert_eq!(parse("v1.2.3.4"), Ok(JohnValue::JohnVersion(1, 2, Some(3), Some(4))));
428    }
429
430    #[test]
431    fn test_tl_object() {
432        assert_eq!(parse("a 1 b 2 c 3"), Ok(JohnValue::JohnObject(
433            vec![
434                ("a".to_string(), JohnValue::JohnInt(1)),
435                ("b".to_string(), JohnValue::JohnInt(2)),
436                ("c".to_string(), JohnValue::JohnInt(3)),
437            ].into_iter().collect()
438        )));
439    }
440
441    #[test]
442    fn test_value_array() {
443        assert_eq!(parse("[1, 2, 3]"), Ok(JohnValue::JohnArray(
444            vec![
445                JohnValue::JohnInt(1),
446                JohnValue::JohnInt(2),
447                JohnValue::JohnInt(3),
448            ]
449        )));
450    }
451
452    #[test]
453    fn test_value_tuple() {
454        assert_eq!(parse("(1, 2, 3)"), Ok(JohnValue::JohnTuple(
455            vec![
456                JohnValue::JohnInt(1),
457                JohnValue::JohnInt(2),
458                JohnValue::JohnInt(3),
459            ]
460        )));
461    }
462
463    #[test]
464    fn test_value_tuple_different_types() {
465        assert_eq!(parse(r#"(1, "2", 3.0)"#), Ok(JohnValue::JohnTuple(
466            vec![
467                JohnValue::JohnInt(1),
468                JohnValue::JohnString("2".to_string()),
469                JohnValue::JohnFloat(3.0),
470            ]
471        )));
472    }
473
474    #[test]
475    fn test_value_object() {
476        assert_eq!(parse(r#"
477            {
478                a: 1,
479                b 2;
480                c 3
481            }
482        "#), Ok(JohnValue::JohnObject(
483            vec![
484                ("a".to_string(), JohnValue::JohnInt(1)),
485                ("b".to_string(), JohnValue::JohnInt(2)),
486                ("c".to_string(), JohnValue::JohnInt(3)),
487            ].into_iter().collect()
488        )));
489    }
490
491    #[test]
492    fn test_value_object_nested() {
493        assert_eq!(parse(r#"
494            {
495                a: 1,
496                b 2;
497                c [
498                    3,
499                    4
500                    { d: "hi" }
501                ], g (1 "2" 3)
502            }
503        "#), Ok(JohnValue::JohnObject(
504            vec![
505                ("a".to_string(), JohnValue::JohnInt(1)),
506                ("b".to_string(), JohnValue::JohnInt(2)),
507                ("c".to_string(), JohnValue::JohnArray(
508                    vec![
509                        JohnValue::JohnInt(3),
510                        JohnValue::JohnInt(4),
511                        JohnValue::JohnObject(
512                            vec![
513                                ("d".to_string(), JohnValue::JohnString("hi".to_string())),
514                            ].into_iter().collect()
515                        ),
516                    ]
517                )),
518                ("g".to_string(), JohnValue::JohnTuple(
519                    vec![
520                        JohnValue::JohnInt(1),
521                        JohnValue::JohnString("2".to_string()),
522                        JohnValue::JohnInt(3),
523                    ]
524                )),
525            ].into_iter().collect()
526        )));
527    }
528
529    #[test]
530    fn test_value_object_nested_nested() {
531        assert_eq!(parse(r#"
532            {
533                a: 1,
534                b 2;
535                c [
536                    3,
537                    4
538                    { d: "hi" }
539                ], g (1 "2" 3),
540                h {
541                    i: 5,
542                    j 6
543                }
544            }
545        "#), Ok(JohnValue::JohnObject(
546            vec![
547                ("a".to_string(), JohnValue::JohnInt(1)),
548                ("b".to_string(), JohnValue::JohnInt(2)),
549                ("c".to_string(), JohnValue::JohnArray(
550                    vec![
551                        JohnValue::JohnInt(3),
552                        JohnValue::JohnInt(4),
553                        JohnValue::JohnObject(
554                            vec![
555                                ("d".to_string(), JohnValue::JohnString("hi".to_string())),
556                            ].into_iter().collect()
557                        ),
558                    ]
559                )),
560                ("g".to_string(), JohnValue::JohnTuple(
561                    vec![
562                        JohnValue::JohnInt(1),
563                        JohnValue::JohnString("2".to_string()),
564                        JohnValue::JohnInt(3),
565                    ]
566                )),
567                ("h".to_string(), JohnValue::JohnObject(
568                    vec![
569                        ("i".to_string(), JohnValue::JohnInt(5)),
570                        ("j".to_string(), JohnValue::JohnInt(6)),
571                    ].into_iter().collect()
572                )),
573            ].into_iter().collect()
574        )));
575    }
576
577    #[test]
578    fn test_stringify() {
579        assert_eq!(stringify(&JohnValue::JohnInt(1)), "1".to_string());
580        assert_eq!(stringify(&JohnValue::JohnFloat(1.25)), "1.25".to_string());
581        assert_eq!(stringify(&JohnValue::JohnString("hello".to_string())), r#""hello""#.to_string());
582        assert_eq!(stringify(&JohnValue::JohnChar('a')), r#"'a'"#.to_string());
583        assert_eq!(stringify(&JohnValue::JohnBool(true)), "true".to_string());
584        assert_eq!(stringify(&JohnValue::JohnBool(false)), "false".to_string());
585        assert_eq!(stringify(&JohnValue::JohnAbyss), "#".to_string());
586        assert_eq!(stringify(&JohnValue::JohnRange(1, 5, None)), "1..5".to_string());
587        assert_eq!(stringify(&JohnValue::JohnRange(1, 5, Some(2))), "1..5..2".to_string());
588        assert_eq!(stringify(&JohnValue::JohnIndex(false, 1)), "*1".to_string());
589        assert_eq!(stringify(&JohnValue::JohnIndex(true, 1)), "^1".to_string());
590        assert_eq!(stringify(&JohnValue::JohnVersion(1, 2, None, None)), "v1.2".to_string());
591        assert_eq!(stringify(&JohnValue::JohnVersion(1, 2, Some(3), None)), "v1.2.3".to_string());
592        assert_eq!(stringify(&JohnValue::JohnVersion(1, 2, Some(3), Some(4)),), "v1.2.3.4".to_string());
593        assert_eq!(stringify(&JohnValue::JohnArray(
594            vec![
595                JohnValue::JohnInt(1),
596                JohnValue::JohnInt(2),
597                JohnValue::JohnInt(3),
598            ]
599        )), "[1, 2, 3]".to_string());
600        assert_eq!(stringify(&JohnValue::JohnTuple(
601            vec![
602                JohnValue::JohnInt(1),
603                JohnValue::JohnInt(2),
604                JohnValue::JohnInt(3),
605            ]
606        )), "(1, 2, 3)".to_string());
607    }
608}