Last active
July 18, 2016 17:45
-
-
Save rhysd/3328dae79484b1b066077f460f46b0de to your computer and use it in GitHub Desktop.
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
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