Skip to content

Instantly share code, notes, and snippets.

@PurpleMyst
Last active May 17, 2018 23:03
Show Gist options
  • Save PurpleMyst/ad15a566ac767122b292353adb953179 to your computer and use it in GitHub Desktop.
Save PurpleMyst/ad15a566ac767122b292353adb953179 to your computer and use it in GitHub Desktop.
JSON parser in Rust + nom..
//! ```cargo
//! [dependencies.nom]
//! version = "4.0"
//! features = ["verbose-errors"]
//! ```
#[macro_use]
extern crate nom;
#[derive(Clone, Debug, PartialEq)]
pub enum JsonValue {
String(String),
Number(f64),
Array(Vec<JsonValue>),
Object(Vec<(JsonValue, JsonValue)>),
True,
False,
Null,
}
named!(
pub object(&[u8]) -> JsonValue,
delimited!(
// We have an extra `nom::multispace0` before the `char!('{')` so you can begin JSON files
// with whitespace.
tuple!(nom::multispace0, char!('{'), nom::multispace0),
map!(
separated_list!(
tuple!(nom::multispace0, char!(','), nom::multispace0),
do_parse!(
key: string >>
tuple!(nom::multispace0, char!(':'), nom::multispace0) >>
value: value >>
(key, value)
)
),
JsonValue::Object
),
tuple!(nom::multispace0, char!('}'))
)
);
named!(
pub array(&[u8]) -> JsonValue,
delimited!(
tuple!(char!('['), nom::multispace0),
map!(separated_list!(tuple!(nom::multispace0, char!(','), nom::multispace0), value),
JsonValue::Array),
tuple!(nom::multispace0, char!(']'))
)
);
named!(
value(&[u8]) -> JsonValue,
alt!(string | number | object | array | constant)
);
fn escape_u(num: Vec<char>) -> Option<char> {
assert_eq!(num.len(), 4);
let mut c: u32 = 0;
for i in 0..4 {
println!("num[{}] → {:?}", i, num[i]);
c += num[i].to_digit(16).unwrap() * 16u32.pow(3 - i as u32)
}
std::char::from_u32(c)
}
// FIXME: If we have an unvalid `\uXXXX` sequence we should fail parsing, not panic.
named!(
string_char(&[u8]) -> char,
alt!(
tuple!(char!('\\'), char!('"')) => { |_| '"' } |
tuple!(char!('\\'), char!('\\')) => { |_| '\\' } |
tuple!(char!('\\'), char!('/')) => { |_| '/' } |
tuple!(char!('\\'), char!('b')) => { |_| '\x08' } |
tuple!(char!('\\'), char!('f')) => { |_| '\x0c' } |
tuple!(char!('\\'), char!('n')) => { |_| '\n' } |
tuple!(char!('\\'), char!('r')) => { |_| '\r' } |
tuple!(char!('\\'), char!('t')) => { |_| '\t' } |
tuple!(char!('\\'), char!('u'), count!(one_of!(b"0123456789abcdefABCDEF"), 4)) => { |(_, _, num)| escape_u(num).expect("Invalid unicode escape") } |
none_of!("\"") => { |c| c }
)
);
named!(
string(&[u8]) -> JsonValue,
map!(
delimited!(char!('"'), many0!(string_char), char!('"')),
|o| JsonValue::String(o.into_iter().collect())
)
);
named!(
number(&[u8]) -> JsonValue,
map!(nom::double, JsonValue::Number)
);
named!(
constant(&[u8]) -> JsonValue,
alt!(
tag!("true") => { |_| JsonValue::True } |
tag!("false") => { |_| JsonValue::False } |
tag!("null") => { |_| JsonValue::Null }
)
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn invalid_unicode_escape_panics() {
string(br#""\ud800""#).unwrap();
}
#[test]
fn it_works() {
use JsonValue::*;
let test_data = br#"
{
"key": "value",
"another_key": ["many", "values", ["even", {"nested": "ones"}]],
"we also have constants like": [true, false, null],
"you like numbers, right? here are some": [1, 2, 3, 69.45, 1e6, 9001],
"also, escapes": "\"\\\/\b\f\n\r\t\u0020"
}
"#;
assert_eq!(
object(test_data).unwrap().1,
Object(vec![
(String("key".to_owned()), String("value".to_owned())),
(
String("another_key".to_owned()),
Array(vec![
String("many".to_owned()),
String("values".to_owned()),
Array(vec![
String("even".to_owned()),
Object(vec![(
String("nested".to_owned()),
String("ones".to_owned()),
)]),
]),
]),
),
(
String("we also have constants like".to_owned()),
Array(vec![True, False, Null]),
),
(
String("you like numbers, right? here are some".to_owned()),
Array(vec![
Number(1.0),
Number(2.0),
Number(3.0),
Number(69.45),
Number(1000000.0),
Number(9001.0),
]),
),
(
String("also, escapes".to_owned()),
String("\"\\/\x08\x0c\n\r\t\u{0020}".to_owned()),
),
])
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment