blob: 56add8eaa2dd86a342bfd045e93c08f9e101f452 [file] [log] [blame]
// `impl Trait` is not required for this parser but we use to to show that it can be used to
// significantly simplify things
#![cfg(feature = "std")]
#[macro_use]
extern crate criterion;
#[macro_use]
extern crate combine;
use std::{collections::HashMap, fs::File, io::Read, path::Path};
use {
combine::{
error::{Commit, ParseError},
parser::{
char::{char, digit, spaces, string},
choice::{choice, optional},
function::parser,
repeat::{many, many1, sep_by},
sequence::between,
token::{any, satisfy, satisfy_map},
},
stream::{
buffered,
position::{self, SourcePosition},
IteratorStream,
},
EasyParser, Parser, Stream, StreamOnce,
},
criterion::{black_box, Bencher, Criterion},
};
#[derive(PartialEq, Debug)]
enum Value {
Number(f64),
String(String),
Bool(bool),
Null,
Object(HashMap<String, Value>),
Array(Vec<Value>),
}
fn lex<Input, P>(p: P) -> impl Parser<Input, Output = P::Output>
where
P: Parser<Input>,
Input: Stream<Token = char>,
<Input as StreamOnce>::Error: ParseError<
<Input as StreamOnce>::Token,
<Input as StreamOnce>::Range,
<Input as StreamOnce>::Position,
>,
{
p.skip(spaces())
}
fn integer<Input>() -> impl Parser<Input, Output = i64>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
lex(many1(digit()))
.map(|s: String| {
let mut n = 0;
for c in s.chars() {
n = n * 10 + (c as i64 - '0' as i64);
}
n
})
.expected("integer")
}
fn number<Input>() -> impl Parser<Input, Output = f64>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
let i = char('0').map(|_| 0.0).or(integer().map(|x| x as f64));
let fractional = many(digit()).map(|digits: String| {
let mut magnitude = 1.0;
digits.chars().fold(0.0, |acc, d| {
magnitude /= 10.0;
match d.to_digit(10) {
Some(d) => acc + (d as f64) * magnitude,
None => panic!("Not a digit"),
}
})
});
let exp = satisfy(|c| c == 'e' || c == 'E').with(optional(char('-')).and(integer()));
lex(optional(char('-'))
.and(i)
.map(|(sign, n)| if sign.is_some() { -n } else { n })
.and(optional(char('.')).with(fractional))
.map(|(x, y)| if x >= 0.0 { x + y } else { x - y })
.and(optional(exp))
.map(|(n, exp_option)| match exp_option {
Some((sign, e)) => {
let e = if sign.is_some() { -e } else { e };
n * 10.0f64.powi(e as i32)
}
None => n,
}))
.expected("number")
}
fn json_char<Input>() -> impl Parser<Input, Output = char>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
parser(|input: &mut Input| {
let (c, committed) = any().parse_lazy(input).into_result()?;
let mut back_slash_char = satisfy_map(|c| {
Some(match c {
'"' => '"',
'\\' => '\\',
'/' => '/',
'b' => '\u{0008}',
'f' => '\u{000c}',
'n' => '\n',
'r' => '\r',
't' => '\t',
_ => return None,
})
});
match c {
'\\' => committed.combine(|_| back_slash_char.parse_stream(input).into_result()),
'"' => Err(Commit::Peek(Input::Error::empty(input.position()).into())),
_ => Ok((c, committed)),
}
})
}
fn json_string<Input>() -> impl Parser<Input, Output = String>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
between(char('"'), lex(char('"')), many(json_char())).expected("string")
}
fn object<Input>() -> impl Parser<Input, Output = Value>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
let field = (json_string(), lex(char(':')), json_value()).map(|t| (t.0, t.2));
let fields = sep_by(field, lex(char(',')));
between(lex(char('{')), lex(char('}')), fields)
.map(Value::Object)
.expected("object")
}
#[inline]
fn json_value<Input>() -> impl Parser<Input, Output = Value>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
json_value_()
}
// We need to use `parser!` to break the recursive use of `value` to prevent the returned parser
// from containing itself
parser! {
#[inline]
fn json_value_[Input]()(Input) -> Value
where [ Input: Stream<Token = char> ]
{
let array = between(
lex(char('[')),
lex(char(']')),
sep_by(json_value(), lex(char(','))),
).map(Value::Array);
choice((
json_string().map(Value::String),
object(),
array,
number().map(Value::Number),
lex(string("false").map(|_| Value::Bool(false))),
lex(string("true").map(|_| Value::Bool(true))),
lex(string("null").map(|_| Value::Null)),
))
}
}
#[test]
fn json_test() {
use self::Value::*;
let input = r#"{
"array": [1, ""],
"object": {},
"number": 3.14,
"small_number": 0.59,
"int": -100,
"exp": -1e2,
"exp_neg": 23e-2,
"true": true,
"false" : false,
"null" : null
}"#;
let result = json_value().easy_parse(input);
let expected = Object(
vec![
("array", Array(vec![Number(1.0), String("".to_string())])),
("object", Object(HashMap::new())),
("number", Number(3.14)),
("small_number", Number(0.59)),
("int", Number(-100.)),
("exp", Number(-1e2)),
("exp_neg", Number(23E-2)),
("true", Bool(true)),
("false", Bool(false)),
("null", Null),
]
.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect(),
);
match result {
Ok(result) => assert_eq!(result, (expected, "")),
Err(e) => {
println!("{}", e);
panic!();
}
}
}
fn test_data() -> String {
let mut data = String::new();
File::open(&Path::new(&"benches/data.json"))
.and_then(|mut file| file.read_to_string(&mut data))
.unwrap();
data
}
fn bench_json(bencher: &mut Bencher<'_>) {
let data = test_data();
let mut parser = json_value();
match parser.easy_parse(position::Stream::new(&data[..])) {
Ok((Value::Array(_), _)) => (),
Ok(_) => panic!(),
Err(err) => {
println!("{}", err);
panic!();
}
}
bencher.iter(|| {
let result = parser.easy_parse(position::Stream::new(&data[..]));
black_box(result)
});
}
fn bench_json_core_error(bencher: &mut Bencher<'_>) {
let data = test_data();
let mut parser = json_value();
match parser.parse(position::Stream::new(&data[..])) {
Ok((Value::Array(_), _)) => (),
Ok(_) => panic!(),
Err(err) => {
println!("{}", err);
panic!();
}
}
bencher.iter(|| {
let result = parser.parse(position::Stream::new(&data[..]));
black_box(result)
});
}
fn bench_json_core_error_no_position(bencher: &mut Bencher<'_>) {
let data = test_data();
let mut parser = json_value();
match parser.parse(&data[..]) {
Ok((Value::Array(_), _)) => (),
Ok(_) => panic!(),
Err(err) => {
println!("{}", err);
panic!();
}
}
bencher.iter(|| {
let result = parser.parse(&data[..]);
black_box(result)
});
}
fn bench_buffered_json(bencher: &mut Bencher<'_>) {
let data = test_data();
bencher.iter(|| {
let buffer =
buffered::Stream::new(position::Stream::new(IteratorStream::new(data.chars())), 1);
let mut parser = json_value();
match parser.easy_parse(position::Stream::with_positioner(
buffer,
SourcePosition::default(),
)) {
Ok((Value::Array(v), _)) => {
black_box(v);
}
Ok(_) => panic!(),
Err(err) => {
println!("{}", err);
panic!();
}
}
});
}
fn bench(c: &mut Criterion) {
c.bench_function("json", bench_json);
c.bench_function("json_core_error", bench_json_core_error);
c.bench_function(
"json_core_error_no_position",
bench_json_core_error_no_position,
);
c.bench_function("buffered_json", bench_buffered_json);
}
criterion_group!(json, bench);
criterion_main!(json);