|
| 1 | +use chumsky::prelude::*; |
| 2 | +use std::collections::HashMap; |
| 3 | + |
| 4 | +#[derive(Clone, Debug)] |
| 5 | +enum Json { |
| 6 | + Invalid, |
| 7 | + Null, |
| 8 | + Bool(bool), |
| 9 | + Str(String), |
| 10 | + Num(f64), |
| 11 | + Array(Vec<Json>), |
| 12 | + Object(HashMap<String, Json>), |
| 13 | +} |
| 14 | + |
| 15 | +fn parser() -> impl Parser<char, Json, Error = Simple<char>> { |
| 16 | + recursive(|value| { |
| 17 | + let frac = just('.').chain(text::digits(10)); |
| 18 | + |
| 19 | + let exp = just('e') |
| 20 | + .or(just('E')) |
| 21 | + .chain(just('+').or(just('-')).or_not()) |
| 22 | + .chain(text::digits(10)); |
| 23 | + |
| 24 | + let number = just('-') |
| 25 | + .or_not() |
| 26 | + .chain(text::int(10)) |
| 27 | + .chain(frac.or_not().flatten()) |
| 28 | + .chain::<char, _, _>(exp.or_not().flatten()) |
| 29 | + .collect::<String>() |
| 30 | + .from_str() |
| 31 | + .unwrapped() |
| 32 | + .labelled("number"); |
| 33 | + |
| 34 | + let escape = just('\\').ignore_then( |
| 35 | + just('\\') |
| 36 | + .or(just('/')) |
| 37 | + .or(just('"')) |
| 38 | + .or(just('b').to('\x08')) |
| 39 | + .or(just('f').to('\x0C')) |
| 40 | + .or(just('n').to('\n')) |
| 41 | + .or(just('r').to('\r')) |
| 42 | + .or(just('t').to('\t')) |
| 43 | + .or(just('u').ignore_then( |
| 44 | + filter(|c: &char| c.is_digit(16)) |
| 45 | + .repeated() |
| 46 | + .exactly(4) |
| 47 | + .collect::<String>() |
| 48 | + .validate(|digits, span, emit| { |
| 49 | + char::from_u32(u32::from_str_radix(&digits, 16).unwrap()) |
| 50 | + .unwrap_or_else(|| { |
| 51 | + emit(Simple::custom(span, "invalid unicode character")); |
| 52 | + '\u{FFFD}' // unicode replacement character |
| 53 | + }) |
| 54 | + }), |
| 55 | + )), |
| 56 | + ); |
| 57 | + |
| 58 | + let string = just('"') |
| 59 | + .ignore_then(filter(|c| *c != '\\' && *c != '"').or(escape).repeated()) |
| 60 | + .then_ignore(just('"')) |
| 61 | + .collect::<String>() |
| 62 | + .labelled("string"); |
| 63 | + |
| 64 | + let array = value |
| 65 | + .clone() |
| 66 | + .chain(just(',').ignore_then(value.clone()).repeated()) |
| 67 | + .or_not() |
| 68 | + .flatten() |
| 69 | + .delimited_by(just('['), just(']')) |
| 70 | + .map(Json::Array) |
| 71 | + .labelled("array"); |
| 72 | + |
| 73 | + let member = string.clone().then_ignore(just(':').padded()).then(value); |
| 74 | + let object = member |
| 75 | + .clone() |
| 76 | + .chain(just(',').padded().ignore_then(member).repeated()) |
| 77 | + .or_not() |
| 78 | + .flatten() |
| 79 | + .padded() |
| 80 | + .delimited_by(just('{'), just('}')) |
| 81 | + .collect::<HashMap<String, Json>>() |
| 82 | + .map(Json::Object) |
| 83 | + .labelled("object"); |
| 84 | + |
| 85 | + just("null") |
| 86 | + .to(Json::Null) |
| 87 | + .labelled("null") |
| 88 | + .or(just("true").to(Json::Bool(true)).labelled("true")) |
| 89 | + .or(just("false").to(Json::Bool(false)).labelled("false")) |
| 90 | + .or(number.map(Json::Num)) |
| 91 | + // SWAP THESE LINES |
| 92 | + .or(string) |
| 93 | + //.or(string.map(Json::Str)) |
| 94 | + .or(array) |
| 95 | + .or(object) |
| 96 | + .recover_with(nested_delimiters('{', '}', [('[', ']')], |_| Json::Invalid)) |
| 97 | + .recover_with(nested_delimiters('[', ']', [('{', '}')], |_| Json::Invalid)) |
| 98 | + .recover_with(skip_then_retry_until(['}', ']'])) |
| 99 | + .padded() |
| 100 | + }) |
| 101 | + .then_ignore(end().recover_with(skip_then_retry_until([]))) |
| 102 | +} |
| 103 | + |
| 104 | +fn main() { |
| 105 | + parser() |
| 106 | + .parse("{ \"a\": 5, b: \"hello, world!\"}") |
| 107 | + .unwrap(); |
| 108 | +} |
0 commit comments