Skip to content

Instantly share code, notes, and snippets.

@rhysd
Last active July 18, 2016 17:45
Show Gist options
  • Save rhysd/3328dae79484b1b066077f460f46b0de to your computer and use it in GitHub Desktop.
Save rhysd/3328dae79484b1b066077f460f46b0de to your computer and use it in GitHub Desktop.
use std::collections::HashMap;
use std::str::Chars;
use std::iter::Peekable;
use std::char;
#[derive(Debug)]
enum JsonValue {
Number(f64),
Boolean(bool),
String(String),
Null,
Array(Vec<JsonValue>),
Object(HashMap<String, JsonValue>),
}
#[derive(Debug)]
struct JsonParseError {
msg: String,
line: usize,
col: usize,
}
impl JsonParseError {
fn new(msg: String, line: usize, col: usize) -> JsonParseError {
JsonParseError {msg: msg, line: line, col: col}
}
}
type JsonParseResult = Result<JsonValue, JsonParseError>;
struct JsonParser<'a> {
chars: Peekable<Chars<'a>>,
line: usize,
col: usize,
}
impl<'a> JsonParser<'a> {
fn for_string(s: &String) -> JsonParser {
JsonParser::for_chars(s.chars())
}
fn for_str(s: &str) -> JsonParser {
JsonParser::for_chars(s.chars())
}
fn for_chars(c: Chars) -> JsonParser {
JsonParser {chars: c.peekable(), line: 0, col: 0}
}
fn err(&self, msg: String) -> JsonParseResult {
Err(self.error(msg))
}
fn error(&self, msg: String) -> JsonParseError {
JsonParseError::new(msg, self.line, self.col)
}
fn unexpected_eof(&self) -> Result<char, JsonParseError> {
Err(JsonParseError::new(String::from("Unexpected EOF"), self.line, self.col))
}
fn peek(&mut self) -> Result<char, JsonParseError> {
loop {
match self.chars.peek() {
Some(c) => {
if !c.is_whitespace() {
return Ok(*c);
}
if *c == '\n' {
self.col = 0;
self.line += 1;
} else {
self.col += 1;
}
},
None => break,
}
self.chars.next();
}
self.unexpected_eof()
}
fn next(&mut self) -> Result<char, JsonParseError> {
while let Some(c) = self.chars.next() {
self.col += 1;
if !c.is_whitespace() {
return Ok(c);
}
if c == '\n' {
self.col = 0;
self.line += 1;
}
}
self.unexpected_eof()
}
fn next_wo_skip(&mut self) -> Result<char, JsonParseError> {
match self.chars.next() {
Some(c) => Ok(c),
None => self.unexpected_eof(),
}
}
fn parse_object(&mut self) -> JsonParseResult {
if try!(self.next()) != '{' {
return self.err(String::from("Object must starts with '{'"));
}
let mut m = HashMap::new();
loop {
let c = try!(self.peek());
if c == '}' {
let _ = self.next();
break;
}
let key = match try!(self.parse()) {
JsonValue::String(s) => s,
v => return self.err(format!("Key of object must be string but found {:?}", v)),
};
let c = try!(self.next());
if c != ':' {
return self.err(format!("':' is expected after key of object but actually found '{}'", c));
}
m.insert(key, try!(self.parse()));
match try!(self.peek()) {
',' => { let _ = self.next(); },
'}' => {},
c => return self.err(format!("',' is expected for object but actually found '{}'", c)),
}
}
Ok(JsonValue::Object(m))
}
fn parse_array(&mut self) -> JsonParseResult {
if try!(self.next()) != '[' {
return self.err(String::from("Array must starts with '['"));
}
let mut v = vec![];
loop {
let c = try!(self.peek());
if c == ']' {
let _ = self.next();
break;
}
v.push(try!(self.parse()));
match try!(self.peek()) {
',' => { let _ = self.next(); },
']' => {},
c => return self.err(format!("',' is expected for array but actually found '{}'", c)),
}
}
Ok(JsonValue::Array(v))
}
fn parse_special_char(&mut self) -> Result<char, JsonParseError> {
Ok(
match try!(self.next()) {
'\\' => '\\',
'"' => '"',
'b' => '\u{0008}',
'f' => '\u{000c}',
'n' => '\n',
'r' => '\r',
't' => '\t',
'u' => {
let mut u = 0 as u32;
for _ in 0..4 {
let c = try!(self.next());
let h = match c.to_digit(16) {
Some(n) => n,
None => return Err(self.error(format!("Unicode character must be \\uXXXX (X is hex character) format but found '{}'", c))),
};
u = u * 0x10 + h;
}
match char::from_u32(u) {
Some(c) => c,
None => return Err(self.error(format!("Cannot convert \\u{:x} in unicode character", u))),
}
},
c => c,
}
)
}
fn parse_string(&mut self) -> JsonParseResult {
if try!(self.next()) != '"' {
return self.err(String::from("String must starts with double quote"));
}
let mut s = String::new();
loop {
s.push(
match try!(self.next_wo_skip()) {
'\\' => try!(self.parse_special_char()),
'"' => break,
c => c,
}
);
}
return Ok(JsonValue::String(s));
}
fn parse_name(&mut self, s: &'static str) -> Option<JsonParseError> {
for c in s.chars() {
match self.next_wo_skip() {
Ok(x) if x != c => {
return Some(
JsonParseError::new(
format!("err while parsing '{}', invalid character '{}' found", s, c),
self.line,
self.col,
)
);
},
Ok(_) => {},
Err(e) => return Some(e),
}
}
None
}
fn parse_null(&mut self) -> JsonParseResult {
match self.parse_name("null") {
Some(err) => Err(err),
None => Ok(JsonValue::Null),
}
}
fn parse_true(&mut self) -> JsonParseResult {
match self.parse_name("true") {
Some(err) => Err(err),
None => Ok(JsonValue::Boolean(true)),
}
}
fn parse_false(&mut self) -> JsonParseResult {
match self.parse_name("false") {
Some(err) => Err(err),
None => Ok(JsonValue::Boolean(false)),
}
}
fn parse_number(&mut self) -> JsonParseResult {
let mut c = try!(self.next());
let negative = match c {
'-' => {
c = try!(self.next());
true
},
_ => false,
};
let mut s = c.to_string();
loop {
let d = match self.chars.peek() {
Some(x) => *x,
None => break,
};
s.push(
match d {
'0'...'9' | '.' | 'e' | 'E' => d,
_ => break,
}
);
self.chars.next();
}
let n: f64 = match s.parse() {
Ok(num) => num,
Err(_) => return self.err(format!("Invalid number: {}", s)),
};
Ok(JsonValue::Number(if negative { -n } else { n }))
}
fn parse(&mut self) -> JsonParseResult {
match try!(self.peek()) {
'1'...'9' | '-' => self.parse_number(),
'"' => self.parse_string(),
'[' => self.parse_array(),
'{' => self.parse_object(),
't' => self.parse_true(),
'f' => self.parse_false(),
'n' => self.parse_null(),
c => self.err(format!("Invalid character: {}", c)),
}
}
}
fn main() {
let s = r#"
{
"bool": true,
"arr": [1, null, "test"],
"nested": {
"blah": false,
"blahblah": 3.14
},
"unicode": "\u2764"
}
"#;
let mut p = JsonParser::for_str(&s);
println!("Result: {:?}", p.parse());
// => Result: Ok(Object({"bool": Boolean(true), "nested": Object({"blah": Boolean(false), "blahblah": Number(3.14)}), "arr": Array([Number(1), Null, String("test")])}))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment