fuzzy_json/
parser.rs

1use nom::combinator;
2use nom::{
3    branch::alt,
4    bytes::complete::{escaped_transform, is_not, tag, take_while, take_while1},
5    character::complete::{char, one_of},
6    combinator::{eof, map, opt, recognize, value},
7    multi::{many0, many1, separated_list0},
8    sequence::{delimited, pair, terminated, tuple},
9    IResult,
10};
11
12use crate::json::JSON;
13pub fn parse_fson(input: &str) -> Option<JSON> {
14    let n = input.len();
15    let mut input = input.chars();
16    for _ in 0..n {
17        if let Ok((_, data)) = parse_data(input.as_str()) {
18            return Some(data);
19        }
20        input.next();
21    }
22    None
23}
24
25pub fn parse_data(input: &str) -> IResult<&str, JSON> {
26    alt((
27        parse_dict,
28        parse_array,
29        parse_null,
30        parse_str,
31        parse_float,
32        parse_int,
33    ))(input)
34}
35
36pub fn spaces(input: &str) -> IResult<&str, &str> {
37    take_while(|c: char| c.is_whitespace())(input)
38}
39
40fn comment(input: &str) -> IResult<&str, &str> {
41    let (input, _) = alt((tag("//"), tag("#"), tag(";"), tag("--")))(input)?;
42    let (input, _) = opt(is_not("\n\r"))(input)?;
43    alt((eof, spaces))(input)
44}
45
46pub fn commentable_spaces(input: &str) -> IResult<&str, ()> {
47    let (input, _) = spaces(input)?;
48    let (input, _) = many0(tuple((comment, spaces)))(input)?;
49    Ok((input, ()))
50}
51
52pub fn identifier(input: &str) -> IResult<&str, String> {
53    fn head(c: char) -> bool {
54        c.is_alphabetic() || c == '_' || c == '#' || c == '@'
55    }
56    fn tail(c: char) -> bool {
57        c.is_alphanumeric() || head(c)
58    }
59    let (input, s) = take_while1(head)(input)?;
60    let (input, t) = take_while(tail)(input)?;
61    let mut name = String::new();
62    name.push_str(s);
63    name.push_str(t);
64    Ok((input, name))
65}
66
67pub fn parse_null(input: &str) -> IResult<&str, JSON> {
68    value(
69        JSON::Null,
70        alt((
71            tag("null"),
72            tag("Null"),
73            tag("NULL"),
74            tag("NUL"),
75            tag("Nil"),
76            tag("nil"),
77            tag("None"),
78            tag("Nothing"),
79        )),
80    )(input)
81}
82
83pub fn parse_int(input: &str) -> IResult<&str, JSON> {
84    fn decimal(input: &str) -> IResult<&str, &str> {
85        recognize(many1(terminated(one_of("0123456789"), many0(char('_')))))(input)
86    }
87    let mut num_value = map(pair(opt(tag("-")), decimal), |(sign, num): (_, &str)| {
88        let num: String = num.chars().filter(|&c| c != '_').collect();
89        let val = num.parse::<i128>().unwrap();
90        match sign {
91            None => JSON::Int(val),
92            _ => JSON::Int(-val),
93        }
94    });
95    num_value(input)
96}
97
98pub fn parse_float(input: &str) -> IResult<&str, JSON> {
99    fn decimal(input: &str) -> IResult<&str, &str> {
100        recognize(many1(terminated(one_of("0123456789"), many0(char('_')))))(input)
101    }
102    let mut value = map(
103        alt((
104            recognize(tuple((opt(char('-')), char('.'), decimal))),
105            recognize(tuple((opt(char('-')), decimal, char('.'), decimal))),
106        )),
107        |num_str: &str| {
108            let num: String = num_str.chars().filter(|&c| c != '_').collect();
109            let x: f64 = num.parse().unwrap();
110            JSON::Float(x)
111        },
112    );
113    value(input)
114}
115
116pub fn parse_str(input: &str) -> IResult<&str, JSON> {
117    let mut value = alt((
118        combinator::value(JSON::Str(String::new()), tag("\"\"")),
119        map(
120            delimited(
121                tag("\""),
122                escaped_transform(
123                    is_not("\"\\"),
124                    '\\',
125                    alt((
126                        combinator::value("\\", tag("\\")),
127                        combinator::value("\"", tag("\"")),
128                        combinator::value("\'", tag("\'")),
129                        combinator::value("\n", tag("n")),
130                        combinator::value("\r", tag("r")),
131                        combinator::value("\t", tag("t")),
132                    )),
133                ),
134                tag("\""),
135            ),
136            JSON::Str,
137        ),
138        combinator::value(JSON::Str(String::new()), tag("\'\'")),
139        map(
140            delimited(
141                tag("\'"),
142                escaped_transform(
143                    is_not("\'\\"),
144                    '\\',
145                    alt((
146                        combinator::value("\\", tag("\\")),
147                        combinator::value("\"", tag("\"")),
148                        combinator::value("\'", tag("\'")),
149                        combinator::value("\n", tag("n")),
150                        combinator::value("\r", tag("r")),
151                        combinator::value("\t", tag("t")),
152                    )),
153                ),
154                tag("\'"),
155            ),
156            JSON::Str,
157        ),
158    ));
159    value(input)
160}
161
162pub fn parse_array(input: &str) -> IResult<&str, JSON> {
163    let mut value = map(
164        tuple((
165            tag("["),
166            commentable_spaces,
167            separated_list0(
168                tuple((tag(","), commentable_spaces)),
169                terminated(parse_data, commentable_spaces),
170            ),
171            opt(tuple((tag(","), commentable_spaces))),
172            tag("]"),
173        )),
174        |(_, _, elems, _, _)| JSON::Array(elems),
175    );
176    value(input)
177}
178
179pub fn parse_dict(input: &str) -> IResult<&str, JSON> {
180    let mut value = map(
181        tuple((
182            tag("{"),
183            commentable_spaces,
184            separated_list0(
185                tuple((tag(","), commentable_spaces)),
186                map(
187                    tuple((
188                        alt((
189                            map(parse_str, |data| match data {
190                                JSON::Str(s) => s,
191                                _ => panic!(),
192                            }),
193                            identifier,
194                        )),
195                        commentable_spaces,
196                        tag(":"),
197                        commentable_spaces,
198                        parse_data,
199                        commentable_spaces,
200                    )),
201                    |(name, _, _, _, data, _)| (name, data),
202                ),
203            ),
204            opt(tuple((tag(","), commentable_spaces))),
205            tag("}"),
206        )),
207        |(_, _, items, _, _)| JSON::Dict(items),
208    );
209    value(input)
210}
211
212#[cfg(test)]
213mod test_parser {
214
215    use crate::json::JSON;
216    use crate::parser::parse_fson;
217
218    macro_rules! assert_value {
219        ($code: expr, $expected: expr) => {
220            assert_eq!(
221                parse_fson($code),
222                Some($expected),
223                "{} != {:?}",
224                $code,
225                $expected
226            );
227        };
228    }
229
230    #[test]
231    fn test_simple() {
232        assert_value!("null", JSON::Null);
233        assert_value!("None", JSON::Null);
234        assert_value!("NUL.", JSON::Null);
235        assert_value!("This is a NULL.", JSON::Null);
236        assert_value!("PI is 3.141", JSON::Float(3.141));
237        assert_value!("PI is 3.", JSON::Int(3));
238        assert_value!("\"\"", JSON::Str(String::from("")));
239        assert_value!("''", JSON::Str(String::from("")));
240        assert_value!("\"dog\"", JSON::Str(String::from("dog")));
241        assert_value!("\"'\"", JSON::Str(String::from("'")));
242        assert_value!("'\"'", JSON::Str(String::from("\"")));
243    }
244
245    #[test]
246    fn test_array() {
247        assert_value!("[]", JSON::Array(vec![]));
248        assert_value!("[null]", JSON::Array(vec![JSON::Null]));
249        assert_value!(
250            "[null, 1, []]",
251            JSON::Array(vec![JSON::Null, JSON::Int(1), JSON::Array(vec![]),])
252        );
253    }
254
255    #[test]
256    fn test_dict() {
257        assert_value!("{}", JSON::Dict(vec![]));
258        assert_value!(
259            "{x: 42, \"x 2\": {}}",
260            JSON::Dict(vec![
261                (String::from("x"), JSON::Int(42)),
262                (String::from("x 2"), JSON::Dict(vec![])),
263            ])
264        );
265    }
266
267    #[test]
268    fn test_complex() {
269        assert_value!(
270            r#"
271{
272  "x": 42,
273  'y': {},
274  z: [ nil ], // trailing comma!
275}.
276            "#,
277            JSON::Dict(vec![
278                (String::from("x"), JSON::Int(42)),
279                (String::from("y"), JSON::Dict(vec![])),
280                (String::from("z"), JSON::Array(vec![JSON::Null])),
281            ])
282        );
283    }
284}