diff --git a/src/lib.rs b/src/lib.rs index fd311e1..a42e8d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,221 +4,4 @@ mod parser; pub mod primitives; pub mod sequence; -#[cfg(test)] pub use parser::{ParseResult, Parser, ParserInput}; - -#[cfg(test)] -mod tests { - use super::*; - use crate::choice::choice; - use crate::combinators::repeated; - use crate::primitives::{any_char, literal, literal_char, one_of, pred}; - use crate::sequence::seq; - - #[test] - fn test_parsing() { - let output = literal("a")("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() -> impl JsonParser<'static, JsonValue> { - let digit = || one_of("1234567890"); - let digits = || 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() -> impl JsonParser<'static, String> { - seq(( - literal_char('"'), - repeated(pred(any_char, |ch| *ch != '"')), - literal_char('"'), - )) - .map(|(_, s, _)| s.iter().cloned().collect::()) - } - - fn json_string() -> impl JsonParser<'static, JsonValue> { - json_string_raw().map(JsonValue::Str) - } - - fn whitespace() -> impl JsonParser<'static, ()> { - repeated(choice(( - literal_char('\t'), - literal_char('\n'), - literal_char(' '), - ))) - .to(()) - } - - fn json_array() -> impl JsonParser<'static, 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() -> impl JsonParser<'static, 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() -> impl JsonParser<'static, 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) - ); - } - - #[test] - fn parse_json_array() { - assert!(json_array().parse(r#"[ 4, 9, "ara",]"#).is_err()); - assert_eq!( - json_array().parse("[[],[]]").unwrap().0, - JsonValue::Array(vec![JsonValue::Array(vec![]), JsonValue::Array(vec![])]) - ); - assert_eq!( - json_array().parse(r#"[ 4, 9, "foo" ]"#).unwrap(), - ( - JsonValue::Array(vec![ - JsonValue::Num(4.), - JsonValue::Num(9.0), - JsonValue::Str("foo".to_string()) - ]), - "" - ) - ); - assert_eq!( - json_array().parse(r#"[8,null,[],5],{}"#).unwrap(), - ( - JsonValue::Array(vec![ - JsonValue::Num(8.), - JsonValue::Null, - JsonValue::Array(vec![]), - JsonValue::Num(5.), - ]), - ",{}" - ) - ); - assert_eq!(json_value().parse("true"), Ok((JsonValue::Bool(true), ""))); - } - - #[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() { - let test_json = include_str!("joplin-cfg.json"); - let parsed_json = json_object().parse(test_json); - assert!(parsed_json.is_ok()); - } -} diff --git a/src/parser/named_parser.rs b/src/parser/named_parser.rs index 2c630b5..d035412 100644 --- a/src/parser/named_parser.rs +++ b/src/parser/named_parser.rs @@ -1,5 +1,5 @@ -use crate::parser::{ParseResult, Parser, ParserInput}; use super::boxed_parser::BoxedParser; +use crate::parser::{ParseResult, Parser, ParserInput}; pub struct NamedParser<'a, I, O, E> where diff --git a/src/joplin-cfg.json b/tests/joplin-cfg.json similarity index 100% rename from src/joplin-cfg.json rename to tests/joplin-cfg.json diff --git a/tests/json_parser.rs b/tests/json_parser.rs new file mode 100644 index 0000000..0b598f7 --- /dev/null +++ b/tests/json_parser.rs @@ -0,0 +1,212 @@ +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; + +#[test] +fn test_parsing() { + let output = literal("a")("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() -> impl JsonParser<'static, JsonValue> { + let digit = || one_of("1234567890"); + let digits = || 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() -> impl JsonParser<'static, String> { + seq(( + literal_char('"'), + repeated(pred(any_char, |ch| *ch != '"')), + literal_char('"'), + )) + .map(|(_, s, _)| s.iter().cloned().collect::()) +} + +fn json_string() -> impl JsonParser<'static, JsonValue> { + json_string_raw().map(JsonValue::Str) +} + +fn whitespace() -> impl JsonParser<'static, ()> { + repeated(choice(( + literal_char('\t'), + literal_char('\n'), + literal_char(' '), + ))) + .to(()) +} + +fn json_array() -> impl JsonParser<'static, 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() -> impl JsonParser<'static, 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() -> impl JsonParser<'static, 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) + ); +} + +#[test] +fn parse_json_array() { + assert!(json_array().parse(r#"[ 4, 9, "ara",]"#).is_err()); + assert_eq!( + json_array().parse("[[],[]]").unwrap().0, + JsonValue::Array(vec![JsonValue::Array(vec![]), JsonValue::Array(vec![])]) + ); + assert_eq!( + json_array().parse(r#"[ 4, 9, "foo" ]"#).unwrap(), + ( + JsonValue::Array(vec![ + JsonValue::Num(4.), + JsonValue::Num(9.0), + JsonValue::Str("foo".to_string()) + ]), + "" + ) + ); + assert_eq!( + json_array().parse(r#"[8,null,[],5],{}"#).unwrap(), + ( + JsonValue::Array(vec![ + JsonValue::Num(8.), + JsonValue::Null, + JsonValue::Array(vec![]), + JsonValue::Num(5.), + ]), + ",{}" + ) + ); + assert_eq!(json_value().parse("true"), Ok((JsonValue::Bool(true), ""))); +} + +#[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() { + let test_json = include_str!("joplin-cfg.json"); + let parsed_json = json_object().parse(test_json); + assert!(parsed_json.is_ok()); +}