First commit, still need to handle strings and numbers

This commit is contained in:
Timothy Warren 2020-02-07 16:23:51 -05:00
commit 5e4e8c4356
4 changed files with 209 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
Cargo.lock
.idea/

9
Cargo.toml Normal file
View File

@ -0,0 +1,9 @@
[package]
name = "naive-json-parser"
version = "0.1.0"
authors = ["Timothy Warren <tim@timshomepage.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

183
src/lib.rs Normal file
View File

@ -0,0 +1,183 @@
use std::collections::HashMap;
use std::iter::FromIterator;
use crate::ParseError::UnexpectedEndOfInput;
#[derive(Debug, PartialEq)]
pub enum JSONValue {
Object(HashMap<String, JSONValue>),
Array(Vec<JSONValue>),
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<char>,
i: usize
}
pub type JSONResult = Result<JSONValue, ParseError>;
type PartialResult = Result<Option<JSONValue>, 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<String, JSONValue> = 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<JSONValue> = 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);
}
}

14
test.json Normal file
View File

@ -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"]
}]