Last active
June 25, 2022 15:18
-
-
Save south37/509ba25f32340184ad2981062e41a1e8 to your computer and use it in GitHub Desktop.
rust で combine を使って書いた json parser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://github.com/Marwes/combine/blob/master/benches/json.rs を parser! macro を利用する形で書き直したもの。
Parser::Input::Error 型に対する型付けを書かずに済んでいる。