Skip to content

Instantly share code, notes, and snippets.

@south37
Last active June 25, 2022 15:18
Show Gist options
  • Save south37/509ba25f32340184ad2981062e41a1e8 to your computer and use it in GitHub Desktop.
Save south37/509ba25f32340184ad2981062e41a1e8 to your computer and use it in GitHub Desktop.
rust で combine を使って書いた json parser
// Rewrite code with `parser!` macro.
// Target: https://github.com/Marwes/combine/blob/f32fe7c135b8c3104843939b7b505f8b0ea4862e/benches/json.rs
#[macro_use]
extern crate combine;
use std::collections::hash_map::HashMap;
use combine::parser::char::{char, string, digit, spaces};
use combine::parser::choice::{choice, optional};
use combine::parser::function::parser;
use combine::parser::item::{any, satisfy, satisfy_map};
use combine::parser::repeat::{many, many1, sep_by};
use combine::parser::sequence::between;
use combine::{Parser, Stream};
use combine::error::{Consumed, ParseError};
#[derive(PartialEq, Debug)]
enum Value {
Number(f64),
String(String),
Bool(bool),
Null,
Object(HashMap<String, Value>),
Array(Vec<Value>),
}
parser! {
fn lex[P](p: P)(P::Input) -> P::Output
where [
P: Parser,
P::Input: Stream<Item = char>
]
{
p.skip(spaces())
}
}
parser! {
fn integer[I]()(I) -> i64
where [
I: Stream<Item = char>
]
{
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")
}
}
parser! {
fn number[I]()(I) -> f64
where [
I: Stream<Item = char>
]
{
let s = char('-').or(char('+'));
let i = 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 s2 = char('-').or(char('+'));
let exp = satisfy(|c| c == 'e' || c == 'E').with(optional(s2).and(integer()));
lex(optional(s)
.and(i)
.map(|(sign, n)| match sign {
Some('-') => { -n }
_ => { 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 = match sign {
Some('-') => { -e }
_ => { e }
};
n * 10.0f64.powi(e as i32)
}
None => n,
})).expected("number")
}
}
parser! {
fn json_char[I]()(I) -> char
where [
I: Stream<Item = char>
]
{
parser(|input: &mut I| {
let (c, consumed) = try!(any().parse_lazy(input).into());
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 {
'\\' => consumed.combine(|_| back_slash_char.parse_stream(input)),
'"' => Err(Consumed::Empty(I::Error::empty(input.position()).into())),
_ => Ok((c, consumed)),
}
})
}
}
parser! {
fn json_string[I]()(I) -> String
where [
I: Stream<Item = char>
]
{
between(char('"'), lex(char('"')), many(json_char())).expected("string")
}
}
parser! {
fn object[I]()(I) -> Value
where [
I: Stream<Item = char>
]
{
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")
}
}
parser! {
fn array[I]()(I) -> Value
where [
I: Stream<Item = char>
]
{
let values = sep_by(json_value(), lex(char(',')));
between(lex(char('[')), lex(char(']')), values)
.map(Value::Array)
.expected("array")
}
}
parser! {
#[inline(always)]
fn json_value[I]()(I) -> Value
where [
I: Stream<Item = char>
]
{
json_value_()
}
}
// We need to use `parser!` to break the recursive use of `value` to prevent the returned parser
// from containing itself
parser!{
#[inline(always)]
fn json_value_[I]()(I) -> Value
where [
I: Stream<Item = char>
]
{
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)),
))
}
}
fn main() {
let mut p = number();
let result = p.parse("-34.56e3");
println!("{:?}", result);
println!("{}", result.unwrap().0);
let mut p2 = json_string();
let result2 = p2.parse("\"this is json string!\\nGood!\"");
println!("{:?}", result2);
println!("{}", result2.unwrap().0);
let mut p3 = json_value();
let result3 = p3.parse("{ \"key\": [1, 2, 3] }");
println!("{:?}", result3);
println!("{:?}", result3.unwrap().0);
}
@south37
Copy link
Author

south37 commented Jul 21, 2018

https://github.com/Marwes/combine/blob/master/benches/json.rs を parser! macro を利用する形で書き直したもの。
Parser::Input::Error 型に対する型付けを書かずに済んでいる。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment