diff options
Diffstat (limited to 'third_party/rust/nom/tests/json.rs')
-rw-r--r-- | third_party/rust/nom/tests/json.rs | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/third_party/rust/nom/tests/json.rs b/third_party/rust/nom/tests/json.rs new file mode 100644 index 0000000000..e8a06fd778 --- /dev/null +++ b/third_party/rust/nom/tests/json.rs @@ -0,0 +1,236 @@ +#![cfg(feature = "alloc")] + +use nom::{ + branch::alt, + bytes::complete::{tag, take}, + character::complete::{anychar, char, multispace0, none_of}, + combinator::{map, map_opt, map_res, value, verify}, + error::ParseError, + multi::{fold_many0, separated_list0}, + number::complete::double, + sequence::{delimited, preceded, separated_pair}, + IResult, Parser, +}; + +use std::collections::HashMap; + +#[derive(Debug, PartialEq, Clone)] +pub enum JsonValue { + Null, + Bool(bool), + Str(String), + Num(f64), + Array(Vec<JsonValue>), + Object(HashMap<String, JsonValue>), +} + +fn boolean(input: &str) -> IResult<&str, bool> { + alt((value(false, tag("false")), value(true, tag("true"))))(input) +} + +fn u16_hex(input: &str) -> IResult<&str, u16> { + map_res(take(4usize), |s| u16::from_str_radix(s, 16))(input) +} + +fn unicode_escape(input: &str) -> IResult<&str, char> { + map_opt( + alt(( + // Not a surrogate + map(verify(u16_hex, |cp| !(0xD800..0xE000).contains(cp)), |cp| { + cp as u32 + }), + // See https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF for details + map( + verify( + separated_pair(u16_hex, tag("\\u"), u16_hex), + |(high, low)| (0xD800..0xDC00).contains(high) && (0xDC00..0xE000).contains(low), + ), + |(high, low)| { + let high_ten = (high as u32) - 0xD800; + let low_ten = (low as u32) - 0xDC00; + (high_ten << 10) + low_ten + 0x10000 + }, + ), + )), + // Could be probably replaced with .unwrap() or _unchecked due to the verify checks + std::char::from_u32, + )(input) +} + +fn character(input: &str) -> IResult<&str, char> { + let (input, c) = none_of("\"")(input)?; + if c == '\\' { + alt(( + map_res(anychar, |c| { + Ok(match c { + '"' | '\\' | '/' => c, + 'b' => '\x08', + 'f' => '\x0C', + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + _ => return Err(()), + }) + }), + preceded(char('u'), unicode_escape), + ))(input) + } else { + Ok((input, c)) + } +} + +fn string(input: &str) -> IResult<&str, String> { + delimited( + char('"'), + fold_many0(character, String::new, |mut string, c| { + string.push(c); + string + }), + char('"'), + )(input) +} + +fn ws<'a, O, E: ParseError<&'a str>, F: Parser<&'a str, O, E>>(f: F) -> impl Parser<&'a str, O, E> { + delimited(multispace0, f, multispace0) +} + +fn array(input: &str) -> IResult<&str, Vec<JsonValue>> { + delimited( + char('['), + ws(separated_list0(ws(char(',')), json_value)), + char(']'), + )(input) +} + +fn object(input: &str) -> IResult<&str, HashMap<String, JsonValue>> { + map( + delimited( + char('{'), + ws(separated_list0( + ws(char(',')), + separated_pair(string, ws(char(':')), json_value), + )), + char('}'), + ), + |key_values| key_values.into_iter().collect(), + )(input) +} + +fn json_value(input: &str) -> IResult<&str, JsonValue> { + use JsonValue::*; + + alt(( + value(Null, tag("null")), + map(boolean, Bool), + map(string, Str), + map(double, Num), + map(array, Array), + map(object, Object), + ))(input) +} + +fn json(input: &str) -> IResult<&str, JsonValue> { + ws(json_value).parse(input) +} + +#[test] +fn json_string() { + assert_eq!(string("\"\""), Ok(("", "".to_string()))); + assert_eq!(string("\"abc\""), Ok(("", "abc".to_string()))); + assert_eq!( + string("\"abc\\\"\\\\\\/\\b\\f\\n\\r\\t\\u0001\\u2014\u{2014}def\""), + Ok(("", "abc\"\\/\x08\x0C\n\r\t\x01ββdef".to_string())), + ); + assert_eq!(string("\"\\uD83D\\uDE10\""), Ok(("", "π".to_string()))); + + assert!(string("\"").is_err()); + assert!(string("\"abc").is_err()); + assert!(string("\"\\\"").is_err()); + assert!(string("\"\\u123\"").is_err()); + assert!(string("\"\\uD800\"").is_err()); + assert!(string("\"\\uD800\\uD800\"").is_err()); + assert!(string("\"\\uDC00\"").is_err()); +} + +#[test] +fn json_object() { + use JsonValue::*; + + let input = r#"{"a":42,"b":"x"}"#; + + let expected = Object( + vec![ + ("a".to_string(), Num(42.0)), + ("b".to_string(), Str("x".to_string())), + ] + .into_iter() + .collect(), + ); + + assert_eq!(json(input), Ok(("", expected))); +} + +#[test] +fn json_array() { + use JsonValue::*; + + let input = r#"[42,"x"]"#; + + let expected = Array(vec![Num(42.0), Str("x".to_string())]); + + assert_eq!(json(input), Ok(("", expected))); +} + +#[test] +fn json_whitespace() { + use JsonValue::*; + + let input = r#" + { + "null" : null, + "true" :true , + "false": false , + "number" : 123e4 , + "string" : " abc 123 " , + "array" : [ false , 1 , "two" ] , + "object" : { "a" : 1.0 , "b" : "c" } , + "empty_array" : [ ] , + "empty_object" : { } + } + "#; + + assert_eq!( + json(input), + Ok(( + "", + Object( + vec![ + ("null".to_string(), Null), + ("true".to_string(), Bool(true)), + ("false".to_string(), Bool(false)), + ("number".to_string(), Num(123e4)), + ("string".to_string(), Str(" abc 123 ".to_string())), + ( + "array".to_string(), + Array(vec![Bool(false), Num(1.0), Str("two".to_string())]) + ), + ( + "object".to_string(), + Object( + vec![ + ("a".to_string(), Num(1.0)), + ("b".to_string(), Str("c".to_string())), + ] + .into_iter() + .collect() + ) + ), + ("empty_array".to_string(), Array(vec![]),), + ("empty_object".to_string(), Object(HashMap::new()),), + ] + .into_iter() + .collect() + ) + )) + ); +} |