use parser_combinator::choice::choice; use parser_combinator::combinators::repeated; use parser_combinator::primitives::{any_char, literal, literal_char, one_of, pred}; use parser_combinator::sequence::seq; use parser_combinator::Parser; use parser_combinator::Representation; use proptest::prelude::*; use rstest::*; proptest! { #[test] fn doesnt_crash(s in "\\PC*") { let _output = json_object().parse(&s); } #[test] fn parse_string(s in r#"[^"]+"#) { let input = format!("\"{}\"", s); let output = json_string().parse(&input).unwrap(); match output { (JsonValue::Str(output_s), "") if output_s == s => (), _ => panic!(), } } } #[test] fn test_parsing() { let output = literal("a").parse("a yolo"); assert_eq!(output.unwrap(), ("a", " yolo")); } /* * JSON BNF * ::= ::= | | | | | ::= "[" [] {"," }* "]" ::= "{" [] {"," }* "}" ::= ":" */ #[derive(Debug, Clone, PartialEq)] enum JsonValue { Null, Bool(bool), Str(String), Num(f64), Array(Vec), Object(Vec<(String, JsonValue)>), } trait JsonParser<'a, T>: Parser<&'a str, T, &'a str> {} impl<'a, T, P> JsonParser<'a, T> for P where P: Parser<&'a str, T, &'a str> {} fn json_null<'a>() -> impl JsonParser<'a, JsonValue> { literal("null").to(JsonValue::Null) } fn json_bool<'a>() -> impl JsonParser<'a, JsonValue> { choice(( literal("true").to(JsonValue::Bool(true)), literal("false").to(JsonValue::Bool(false)), )) } fn json_number<'a>() -> impl JsonParser<'a, JsonValue> { fn digit<'a>() -> impl JsonParser<'a, &'a str> { one_of("1234567890") } fn digits<'a>() -> impl JsonParser<'a, Vec<&'a str>> { repeated(digit()).at_least(1) } let json_number_inner = choice(( seq((digits(), literal(".").ignore_then(digits()).optional())).map( |(mut digits, maybe_decimal)| { if let Some(decimal_digits) = maybe_decimal { digits.push("."); digits.extend(decimal_digits.into_iter()); } digits.into_iter().collect::() }, ), literal(".").ignore_then(digits()).map(|decimal_digits| { let mut d = vec!["."]; d.extend(decimal_digits.into_iter()); d.into_iter().collect::() }), )) .map(|digits| digits.parse::().unwrap()); literal("-") .optional() .then(json_number_inner) .map(|(maybe_sign, mut val)| { if maybe_sign.is_some() { val *= -1.0; } JsonValue::Num(val) }) } fn json_string_raw<'a>() -> impl JsonParser<'a, String> { seq(( literal_char('"'), repeated(pred(any_char, |ch| *ch != '"')), literal_char('"'), )) .map(|(_, s, _)| s.iter().cloned().collect::()) } fn json_string<'a>() -> impl JsonParser<'a, JsonValue> { json_string_raw().map(JsonValue::Str) } fn whitespace<'a>() -> impl JsonParser<'a, ()> { repeated(choice(( literal_char('\t'), literal_char('\n'), literal_char(' '), ))) .to(()) } fn json_array<'a>() -> impl JsonParser<'a, JsonValue> { move |input| { let val = json_value().surrounded_by(whitespace()); repeated(val) .separated_by(literal(","), false) .delimited(literal_char('['), literal_char(']')) .map(JsonValue::Array) .parse(input) } } fn json_object<'a>() -> impl JsonParser<'a, JsonValue> { move |input| { let kv = json_string_raw() .surrounded_by(whitespace()) .then_ignore(literal_char(':')) .then(json_value().surrounded_by(whitespace())); repeated(kv) .separated_by(literal_char(','), false) .delimited(literal_char('{'), literal_char('}')) .map(JsonValue::Object) .parse(input) } } fn json_value<'a>() -> impl JsonParser<'a, JsonValue> { choice(( json_null(), json_bool(), json_number(), json_string(), json_array(), json_object(), )) } #[test] fn parse_json_primitives() { assert_eq!( json_string().parse(r#""yolo swagg""#).unwrap(), (JsonValue::Str("yolo swagg".into()), "") ); assert_eq!( json_number().parse("-383").unwrap().0, JsonValue::Num(-383f64) ); assert_eq!( json_number().parse("-.383").unwrap().0, JsonValue::Num(-0.383) ); assert_eq!( json_number().parse(".383").unwrap().0, JsonValue::Num(0.383) ); assert_eq!( json_number().parse("-1.383").unwrap().0, JsonValue::Num(-1.383) ); } #[rstest] #[case(r#"[ 4, 9, "ara",]"#)] fn parse_json_array_err(#[case] input: &str) { assert!(json_array().parse(input).is_err()); } #[rstest] #[case("[[],[]]", (JsonValue::Array(vec![JsonValue::Array(vec![]), JsonValue::Array(vec![])]), ""))] #[case(r#"[ 4, 9, "foo" ]"#, ( JsonValue::Array(vec![ JsonValue::Num(4.), JsonValue::Num(9.0), JsonValue::Str("foo".to_string()) ]), "" ))] #[case(r#"[8,null,[],5],{}"#, ( JsonValue::Array(vec![ JsonValue::Num(8.), JsonValue::Null, JsonValue::Array(vec![]), JsonValue::Num(5.), ]), ",{}" ))] fn parse_json_array(#[case] input: &str, #[case] expected: (JsonValue, &str)) { assert_eq!(json_array().parse(input).unwrap(), expected); } #[test] fn parse_json_object() { assert_eq!( json_object().parse(r#"{ "a": 23}"#).unwrap().0, JsonValue::Object(vec![("a".into(), JsonValue::Num(23.))]) ); assert_eq!( json_object().parse(r#"{}"#).unwrap().0, JsonValue::Object(vec![]) ); } #[test] fn parse_json_document() { let test_json = include_str!("joplin-cfg.json"); let parsed_json = json_object().parse(test_json); assert!(parsed_json.is_ok()); } #[rstest] #[case(json_null().representation(), Representation::new("null"))] #[case(json_bool().representation(), Representation::new("true | false"))] #[case(json_number().representation(), Representation::new("- | ε (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | )+ . (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | )+ | ε | . (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | )+"))] fn representations_test( #[case] parser_representation: Representation, #[case] expected: Representation, ) { assert_eq!(parser_representation, expected); }