| use std::char; |
| use std::collections::HashMap; |
| use std::fmt; |
| use std::iter::Peekable; |
| use std::str::FromStr; |
| |
| use crate::JsonValue; |
| |
| /// Parse error. |
| /// |
| /// ``` |
| /// use tinyjson::{JsonParser, JsonParseError}; |
| /// let error = JsonParser::new("[1, 2, 3".chars()).parse().unwrap_err(); |
| /// assert!(matches!(error, JsonParseError{..})); |
| /// ``` |
| #[derive(Debug)] |
| pub struct JsonParseError { |
| msg: String, |
| line: usize, |
| col: usize, |
| } |
| |
| impl JsonParseError { |
| fn new(msg: String, line: usize, col: usize) -> JsonParseError { |
| JsonParseError { msg, line, col } |
| } |
| |
| /// Get the line numbr where the parse error happened. This value is 1-based. |
| /// |
| /// ``` |
| /// use tinyjson::{JsonParser, JsonParseError}; |
| /// let error = JsonParser::new("[1, 2, 3".chars()).parse().unwrap_err(); |
| /// assert_eq!(error.line(), 1); |
| /// ``` |
| pub fn line(&self) -> usize { |
| self.line |
| } |
| |
| /// Get the column numbr where the parse error happened. This value is 1-based. |
| /// |
| /// ``` |
| /// use tinyjson::{JsonParser, JsonParseError}; |
| /// let error = JsonParser::new("[1, 2, 3".chars()).parse().unwrap_err(); |
| /// assert_eq!(error.column(), 8); |
| /// ``` |
| pub fn column(&self) -> usize { |
| self.col |
| } |
| } |
| |
| impl fmt::Display for JsonParseError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!( |
| f, |
| "Parse error at line:{}, col:{}: {}", |
| self.line, self.col, &self.msg, |
| ) |
| } |
| } |
| |
| impl std::error::Error for JsonParseError {} |
| |
| /// Convenient type alias for parse results. |
| pub type JsonParseResult = Result<JsonValue, JsonParseError>; |
| |
| // Note: char::is_ascii_whitespace is not available because some characters are not defined as |
| // whitespace character in JSON spec. For example, U+000C FORM FEED is whitespace in Rust but |
| // it isn't in JSON. |
| fn is_whitespace(c: char) -> bool { |
| match c { |
| '\u{0020}' | '\u{000a}' | '\u{000d}' | '\u{0009}' => true, |
| _ => false, |
| } |
| } |
| |
| /// JSON parser to parse UTF-8 string into `JsonValue` value. |
| /// |
| /// Basically you don't need to use this struct directly thanks to `FromStr` trait implementation. |
| /// |
| /// ``` |
| /// use tinyjson::{JsonParser, JsonValue}; |
| /// |
| /// let mut parser = JsonParser::new("[1, 2, 3]".chars()); |
| /// let array = parser.parse().unwrap(); |
| /// |
| /// // Equivalent to the above code using `FromStr` |
| /// let array: JsonValue = "[1, 2, 3]".parse().unwrap(); |
| /// ``` |
| pub struct JsonParser<I> |
| where |
| I: Iterator<Item = char>, |
| { |
| chars: Peekable<I>, |
| line: usize, |
| col: usize, |
| } |
| |
| impl<I: Iterator<Item = char>> JsonParser<I> { |
| /// Create a new parser instance from an iterator which iterates characters. The iterator is usually built from |
| /// `str::chars` for parsing `str` or `String` values. |
| pub fn new(it: I) -> Self { |
| JsonParser { |
| chars: it.peekable(), |
| line: 1, |
| col: 0, |
| } |
| } |
| |
| fn err<T>(&self, msg: String) -> Result<T, JsonParseError> { |
| Err(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 next_pos(&mut self, c: char) { |
| if c == '\n' { |
| self.col = 0; |
| self.line += 1; |
| } else { |
| self.col += 1; |
| } |
| } |
| |
| fn peek(&mut self) -> Result<char, JsonParseError> { |
| while let Some(c) = self.chars.peek().copied() { |
| if !is_whitespace(c) { |
| return Ok(c); |
| } |
| self.next_pos(c); |
| self.chars.next().unwrap(); |
| } |
| self.unexpected_eof() |
| } |
| |
| fn next(&mut self) -> Option<char> { |
| while let Some(c) = self.chars.next() { |
| self.next_pos(c); |
| if !is_whitespace(c) { |
| return Some(c); |
| } |
| } |
| None |
| } |
| |
| fn consume(&mut self) -> Result<char, JsonParseError> { |
| if let Some(c) = self.next() { |
| Ok(c) |
| } else { |
| self.unexpected_eof() |
| } |
| } |
| |
| fn consume_no_skip(&mut self) -> Result<char, JsonParseError> { |
| if let Some(c) = self.chars.next() { |
| self.next_pos(c); |
| Ok(c) |
| } else { |
| self.unexpected_eof() |
| } |
| } |
| |
| fn parse_object(&mut self) -> JsonParseResult { |
| if self.consume()? != '{' { |
| return self.err(String::from("Object must starts with '{'")); |
| } |
| |
| if self.peek()? == '}' { |
| self.consume().unwrap(); |
| return Ok(JsonValue::Object(HashMap::new())); |
| } |
| |
| let mut m = HashMap::new(); |
| loop { |
| let key = match self.parse_any()? { |
| JsonValue::String(s) => s, |
| v => return self.err(format!("Key of object must be string but found {:?}", v)), |
| }; |
| |
| let c = self.consume()?; |
| if c != ':' { |
| return self.err(format!( |
| "':' is expected after key of object but actually found '{}'", |
| c |
| )); |
| } |
| |
| m.insert(key, self.parse_any()?); |
| |
| match self.consume()? { |
| ',' => {} |
| '}' => return Ok(JsonValue::Object(m)), |
| c => { |
| return self.err(format!( |
| "',' or '}}' is expected for object but actually found '{}'", |
| c.escape_debug(), |
| )) |
| } |
| } |
| } |
| } |
| |
| fn parse_array(&mut self) -> JsonParseResult { |
| if self.consume()? != '[' { |
| return self.err(String::from("Array must starts with '['")); |
| } |
| |
| if self.peek()? == ']' { |
| self.consume().unwrap(); |
| return Ok(JsonValue::Array(vec![])); |
| } |
| |
| let mut v = vec![self.parse_any()?]; |
| loop { |
| match self.consume()? { |
| ',' => {} |
| ']' => return Ok(JsonValue::Array(v)), |
| c => { |
| return self.err(format!( |
| "',' or ']' is expected for array but actually found '{}'", |
| c |
| )) |
| } |
| } |
| |
| v.push(self.parse_any()?); // Next element |
| } |
| } |
| |
| fn push_utf16(&self, s: &mut String, utf16: &mut Vec<u16>) -> Result<(), JsonParseError> { |
| if utf16.is_empty() { |
| return Ok(()); |
| } |
| |
| match String::from_utf16(utf16) { |
| Ok(utf8) => s.push_str(&utf8), |
| Err(err) => return self.err(format!("Invalid UTF-16 sequence {:?}: {}", &utf16, err)), |
| } |
| utf16.clear(); |
| Ok(()) |
| } |
| |
| fn parse_string(&mut self) -> JsonParseResult { |
| if self.consume()? != '"' { |
| return self.err(String::from("String must starts with double quote")); |
| } |
| |
| let mut utf16 = Vec::new(); // Buffer for parsing \uXXXX UTF-16 characters |
| let mut s = String::new(); |
| loop { |
| let c = match self.consume_no_skip()? { |
| '\\' => match self.consume_no_skip()? { |
| '\\' => '\\', |
| '/' => '/', |
| '"' => '"', |
| 'b' => '\u{0008}', |
| 'f' => '\u{000c}', |
| 'n' => '\n', |
| 'r' => '\r', |
| 't' => '\t', |
| 'u' => { |
| let mut u = 0u16; |
| for _ in 0..4 { |
| let c = self.consume()?; |
| if let Some(h) = c.to_digit(16) { |
| u = u * 0x10 + h as u16; |
| } else { |
| return self.err(format!("Unicode character must be \\uXXXX (X is hex character) format but found character '{}'", c)); |
| } |
| } |
| utf16.push(u); |
| // Additional \uXXXX character may follow. UTF-16 characters must be converted |
| // into UTF-8 string as sequence because surrogate pairs must be considered |
| // like "\uDBFF\uDFFF". |
| continue; |
| } |
| c => return self.err(format!("'\\{}' is invalid escaped character", c)), |
| }, |
| '"' => { |
| self.push_utf16(&mut s, &mut utf16)?; |
| return Ok(JsonValue::String(s)); |
| } |
| // Note: c.is_control() is not available here because JSON accepts 0x7f (DEL) in |
| // string literals but 0x7f is control character. |
| // Rough spec of JSON says string literal cannot contain control characters. But it |
| // can actually contain 0x7f. |
| c if (c as u32) < 0x20 => { |
| return self.err(format!( |
| "String cannot contain control character {}", |
| c.escape_debug(), |
| )); |
| } |
| c => c, |
| }; |
| |
| self.push_utf16(&mut s, &mut utf16)?; |
| |
| s.push(c); |
| } |
| } |
| |
| fn parse_constant(&mut self, s: &'static str) -> Option<JsonParseError> { |
| for c in s.chars() { |
| match self.consume_no_skip() { |
| Ok(x) if x != c => { |
| return Some(JsonParseError::new( |
| format!("Unexpected character '{}' while parsing '{}'", c, s), |
| self.line, |
| self.col, |
| )); |
| } |
| Ok(_) => {} |
| Err(e) => return Some(e), |
| } |
| } |
| None |
| } |
| |
| fn parse_null(&mut self) -> JsonParseResult { |
| match self.parse_constant("null") { |
| Some(err) => Err(err), |
| None => Ok(JsonValue::Null), |
| } |
| } |
| |
| fn parse_true(&mut self) -> JsonParseResult { |
| match self.parse_constant("true") { |
| Some(err) => Err(err), |
| None => Ok(JsonValue::Boolean(true)), |
| } |
| } |
| |
| fn parse_false(&mut self) -> JsonParseResult { |
| match self.parse_constant("false") { |
| Some(err) => Err(err), |
| None => Ok(JsonValue::Boolean(false)), |
| } |
| } |
| |
| fn parse_number(&mut self) -> JsonParseResult { |
| let neg = if self.peek()? == '-' { |
| self.consume_no_skip().unwrap(); |
| true |
| } else { |
| false |
| }; |
| |
| let mut s = String::new(); |
| let mut saw_dot = false; |
| let mut saw_exp = false; |
| |
| while let Some(d) = self.chars.peek() { |
| match d { |
| '0'..='9' => s.push(*d), |
| '.' => { |
| saw_dot = true; |
| break; |
| } |
| 'e' | 'E' => { |
| saw_exp = true; |
| break; |
| } |
| _ => break, |
| } |
| self.consume_no_skip().unwrap(); |
| } |
| |
| if s.is_empty() { |
| return self.err("Integer part must not be empty in number literal".to_string()); |
| } |
| |
| if s.starts_with('0') && s.len() > 1 { |
| return self |
| .err("Integer part of number must not start with 0 except for '0'".to_string()); |
| } |
| |
| if saw_dot { |
| s.push(self.consume_no_skip().unwrap()); // eat '.' |
| while let Some(d) = self.chars.peek() { |
| match d { |
| '0'..='9' => s.push(*d), |
| 'e' | 'E' => { |
| saw_exp = true; |
| break; |
| } |
| _ => break, |
| } |
| self.consume_no_skip().unwrap(); |
| } |
| if s.ends_with('.') { |
| return self.err("Fraction part of number must not be empty".to_string()); |
| } |
| } |
| |
| if saw_exp { |
| s.push(self.consume_no_skip().unwrap()); // eat 'e' or 'E' |
| if let Some('+') | Some('-') = self.chars.peek() { |
| s.push(self.consume_no_skip().unwrap()); |
| } |
| |
| let mut saw_digit = false; |
| while let Some(d) = self.chars.peek() { |
| match d { |
| '0'..='9' => s.push(*d), |
| _ => break, |
| } |
| saw_digit = true; |
| self.consume_no_skip().unwrap(); |
| } |
| |
| if !saw_digit { |
| return self.err("Exponent part must not be empty in number literal".to_string()); |
| } |
| } |
| |
| match s.parse::<f64>() { |
| Ok(n) => Ok(JsonValue::Number(if neg { -n } else { n })), |
| Err(err) => self.err(format!("Invalid number literal '{}': {}", s, err)), |
| } |
| } |
| |
| fn parse_any(&mut self) -> JsonParseResult { |
| match self.peek()? { |
| '0'..='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.escape_debug())), |
| } |
| } |
| |
| /// Run the parser to parse one JSON value. |
| pub fn parse(&mut self) -> JsonParseResult { |
| let v = self.parse_any()?; |
| |
| if let Some(c) = self.next() { |
| return self.err(format!( |
| "Expected EOF but got character '{}'", |
| c.escape_debug(), |
| )); |
| } |
| |
| Ok(v) |
| } |
| } |
| |
| /// Parse given `str` object into `JsonValue` value. This is recommended way to parse strings into JSON value with |
| /// this library. |
| /// |
| /// ``` |
| /// use tinyjson::JsonValue; |
| /// |
| /// let array: JsonValue = "[1, 2, 3]".parse().unwrap(); |
| /// assert!(array.is_array()); |
| /// ``` |
| impl FromStr for JsonValue { |
| type Err = JsonParseError; |
| |
| fn from_str(s: &str) -> Result<Self, Self::Err> { |
| JsonParser::new(s.chars()).parse() |
| } |
| } |