From 5e4e8c4356dd668a67907fabd1add6e3251558ba Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Fri, 7 Feb 2020 16:23:51 -0500 Subject: [PATCH] First commit, still need to handle strings and numbers --- .gitignore | 3 + Cargo.toml | 9 +++ src/lib.rs | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test.json | 14 ++++ 4 files changed, 209 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 test.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e04901 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +.idea/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7640913 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "naive-json-parser" +version = "0.1.0" +authors = ["Timothy Warren "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f3009be --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,183 @@ +use std::collections::HashMap; +use std::iter::FromIterator; +use crate::ParseError::UnexpectedEndOfInput; + +#[derive(Debug, PartialEq)] +pub enum JSONValue { + Object(HashMap), + Array(Vec), + String(String), + Number(f64), + True, + False, + Null, +} + +#[derive(Debug)] +pub enum ParseError { + UnexpectedEndOfInput(String), + ExpectedEndOfInput(String), + ExpectedObjectKey(String), + ExpectedToken(String), + UnexpectedToken(String), + ExpectedDigit(String), + ExpectedEscapeChar(String), + ExpectedUnicodeEscape(String), +} + +#[derive(Debug)] +pub struct JSON { + chars: Vec, + i: usize +} + +pub type JSONResult = Result; +type PartialResult = Result, ParseError>; + +impl JSON { + fn new(json: &str) -> Self { + JSON { + chars: json.chars().collect(), + i: 0 + } + } + + fn parse_value(&mut self) -> JSONResult { + self.skip_whitespace(); + + let mut types = self.parse_string()?.into_iter() + .chain(self.parse_number()?.into_iter()) + .chain(self.parse_object()?.into_iter()) + .chain(self.parse_array()?.into_iter()) + .chain(self.parse_keyword("true", JSONValue::True)?.into_iter()) + .chain(self.parse_keyword("false", JSONValue::False)?.into_iter()) + .chain(self.parse_keyword("null", JSONValue::Null)?.into_iter()); + + match types.next() { + Some(val) => Ok(val), + None => Err(UnexpectedEndOfInput(String::new())) + } + } + + fn parse_object(&mut self) -> PartialResult { + if self.chars[self.i] != '{' { + return Ok(None); + } + + self.i += 1; + + self.skip_whitespace(); + + let mut result: HashMap = HashMap::new(); + + let mut initial = true; + + // if it is not '}', + // we take the path of string -> whitespace -> ':' -> value -> ... + while self.chars[self.i] != '}' { + if initial == false { + self.eat_char(',')?; + self.skip_whitespace(); + } + + let key = match self.parse_string()? { + Some(value) => match value { + JSONValue::String(s) => s, + _ => panic!("parse_string returned non-string value"), + }, + None => String::new() + }; + + self.skip_whitespace(); + self.eat_char(':')?; + let value = self.parse_value()?; + + result.insert(key, value); + + initial = false; + } + + // Move to the next character: '}' + self.i += 1; + + Ok(Some(JSONValue::Object(result))) + } + + fn parse_array(&mut self) -> PartialResult { + if self.chars[self.i] != '[' { + return Ok(None); + } + + self.i += 1; + self.skip_whitespace(); + + let mut result: Vec = vec![]; + let mut initial = true; + + while self.chars[self.i] != ']' { + if initial == false { + self.eat_char(',')?; + } + let value = self.parse_value()?; + result.push(value); + initial = false; + } + + // move to next character: ']' + self.i += 1; + + Ok(Some(JSONValue::Array(result))) + } + + fn parse_string(&mut self) -> PartialResult { + todo!(); + } + + fn parse_number(&mut self) -> PartialResult { + todo!(); + } + + fn parse_keyword(&mut self, search: &str, value: JSONValue) -> PartialResult { + let slice = &String::from_iter(&self.chars[self.i..self.i+search.len()]); + if slice == search { + self.i += search.len(); + + return Ok(Some(value)); + } + + Ok(None) + } + + fn skip_whitespace(&mut self) { + while self.chars[self.i].is_ascii_whitespace() { + self.i += 1; + } + } + + fn eat_char(&mut self, ch: char) -> Result<(), ParseError> { + if self.chars[self.i] != ch { + let msg = format!(r#"Expected "{}"."#, ch); + return Err(ParseError::ExpectedToken(msg)); + } + + self.i += 1; + + Ok(()) + } + + pub fn parse(json: &str) -> JSONResult { + JSON::new(json).parse_value() + } +} + + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + assert_eq!(2 + 2, 4); + } +} diff --git a/test.json b/test.json new file mode 100644 index 0000000..b7e40a1 --- /dev/null +++ b/test.json @@ -0,0 +1,14 @@ +[{ + "a": 1, + "b": 4e3, + "c": [1, 2, 3], + "d": "foo", + "e": { + "f": { + "g": { + "h": null + } + } + }, + "i": ["\"", "\\", "/", "\b", "\f", "\n", "\r", "\t", "\u0001", "\uface"] +}] \ No newline at end of file