Skip to content

Commit

Permalink
refactor parser in its module, start parser from scratch
Browse files Browse the repository at this point in the history
  • Loading branch information
malta895 committed Sep 14, 2024
1 parent 81a7dec commit 19d2c50
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 100 deletions.
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod parser;
fn main() {
let buf = BufReader::new(std::io::stdin());

match parser::JSONParser::check_valid(buf) {
match parser::check_valid(buf) {
Err(e) => {
eprintln!("{}", e);
exit(1);
Expand Down
2 changes: 1 addition & 1 deletion src/parser/lexer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::io::BufRead;
use std::io::{BufRead, Read};

use super::{error::JSONError, token::Token};

Expand Down
120 changes: 22 additions & 98 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
@@ -1,116 +1,40 @@
use std::io::BufRead;
use std::io::{BufRead, Read};

mod token;
use lexer::lex;
use parser::parse;
use token::Token;

mod error;
use error::JSONError;

mod lexer;
mod parser;

pub struct JSONParser<R: BufRead> {
reader: R,
tokens: Vec<Token>,
current_line: i64,
}

impl<R: BufRead> JSONParser<R> {
fn lex(&mut self) -> Result<(), JSONError> {
let tokens = lex(&mut self.reader);
match tokens {
Ok(tokens) => {
self.tokens = tokens;
Ok(())
}
Err(lex_error) => Err(lex_error),
}
}

fn new(reader: R) -> JSONParser<R> {
JSONParser {
reader,
tokens: Vec::new(),
current_line: 1,
}
}

fn build_json_err(&self, message: String) -> JSONError {
JSONError::new(message.clone(), self.current_line)
}

pub fn check_valid(reader: R) -> Result<(), JSONError> {
let p = &mut Self::new(reader);
p.lex()?;

dbg!(&p.tokens);
p.current_line = 1;
let mut obj_depth = 0;
let mut is_inside_array = false;
let mut is_json_ended = false;
let mut is_inside_literal = false;
let mut is_after_comma = false;
for token in &p.tokens {
match token {
Token::OpenBrace => {
is_after_comma = false;
obj_depth += 1;
}
Token::ClosedBrace => {
if obj_depth == 0 {
return Err(p.build_json_err(format!("Unexpected {} outside obj", token)));
}
if is_after_comma {
return Err(p.build_json_err(format!("Unexpected {} after comma", token)));
}
obj_depth -= 1;
if obj_depth == 0 {
is_json_ended = true;
}
}
Token::NewLine => {
// ignore for now
}
Token::Column => {
// ignore for now
}
Token::Comma => {
is_after_comma = true;
}
Token::StringLiteral(_) => {
is_after_comma = false;
}
Token::Number(_) | Token::BoolTrue | Token::BoolFalse | Token::Null => {}
Token::OpenBracket => {}
Token::ClosedBracket => {}
}
}
if !is_json_ended {
return Err(p.build_json_err(String::from("Unexpected EOF")));
}
Ok(())
}
pub fn check_valid<R: BufRead>(reader: R) -> Result<(), JSONError> {
let tokens = lex(reader)?;
parse(tokens)
}

#[cfg(test)]
mod check_valid_tests {
use crate::parser::JSONParser;
use crate::parser::check_valid;

#[test]
fn should_not_report_error_for_obj() {
let res = JSONParser::check_valid("{}".as_bytes());
let res = check_valid("{}".as_bytes());
assert_eq!(res, Ok(()));
}

#[test]
fn should_report_error_for_not_closed_brace() {
let found_err = JSONParser::check_valid("{".as_bytes()).unwrap_err();
let found_err = check_valid("{".as_bytes()).unwrap_err();
assert_eq!("Unexpected EOF: at line 1", found_err.to_string())
}

#[test]
fn should_report_error_for_closed_brace_outside_obj() {
let found_err = JSONParser::check_valid("}".as_bytes()).unwrap_err();
let found_err = check_valid("}".as_bytes()).unwrap_err();
assert_eq!(
"Unexpected '}' outside obj: at line 1",
found_err.to_string()
Expand All @@ -119,74 +43,74 @@ mod check_valid_tests {

#[test]
fn should_report_error_for_random_char() {
let found_err = JSONParser::check_valid("{a".as_bytes()).unwrap_err();
let found_err = check_valid("{a".as_bytes()).unwrap_err();
assert_eq!("Unexpected 'a': at line 1", found_err.to_string())
}

#[test]
fn should_report_unexpected_eof_for_empty_file() {
let found_err = JSONParser::check_valid("".as_bytes()).unwrap_err();
let found_err = check_valid("".as_bytes()).unwrap_err();
assert_eq!("Unexpected EOF: at line 1", found_err.to_string())
}

#[test]
fn should_not_report_error_for_new_line_at_the_end_of_file() {
let res = JSONParser::check_valid("{}\n".as_bytes());
let res = check_valid("{}\n".as_bytes());
assert_eq!(Ok(()), res)
}

#[test]
fn should_recognize_base_case() {
let res = JSONParser::check_valid("{\"\":\"\"}\n".as_bytes());
let res = check_valid("{\"\":\"\"}\n".as_bytes());
assert_eq!(Ok(()), res)
}

#[test]
fn should_recognize_base_case_with_key_val() {
let res = JSONParser::check_valid("{\"key\":\"value\"}\n".as_bytes());
let res = check_valid("{\"key\":\"value\"}\n".as_bytes());
assert_eq!(Ok(()), res)
}

#[test]
fn should_recognize_string_with_spaces() {
let res = JSONParser::check_valid("{ \"key\":\"va l\",\n \"ke y2\":\"val\"}".as_bytes());
let res = check_valid("{ \"key\":\"va l\",\n \"ke y2\":\"val\"}".as_bytes());
assert_eq!(Ok(()), res)
}

#[test]
fn should_recognize_number() {
let res = JSONParser::check_valid("{ \"key\": 123}".as_bytes());
let res = check_valid("{ \"key\": 123}".as_bytes());
assert_eq!(Ok(()), res)
}

#[test]
fn should_recognize_true() {
let res = JSONParser::check_valid("{ \"key\": true}".as_bytes());
let res = check_valid("{ \"key\": true}".as_bytes());
assert_eq!(Ok(()), res)
}

#[test]
fn should_recognize_false() {
let res = JSONParser::check_valid("{ \"key\": false}".as_bytes());
let res = check_valid("{ \"key\": false}".as_bytes());
assert_eq!(Ok(()), res)
}

#[test]
fn should_recognize_null() {
let res = JSONParser::check_valid("{ \"key\": null}".as_bytes());
let res = check_valid("{ \"key\": null}".as_bytes());
assert_eq!(Ok(()), res)
}

#[test]
fn should_recognize_empty_array() {
let res = JSONParser::check_valid("{ \"key\": []}".as_bytes());
let res = check_valid("{ \"key\": []}".as_bytes());
assert_eq!(Ok(()), res)
}

#[test]
fn should_recognize_nested_objects() {
let res =
JSONParser::check_valid("{ \"key\": {\n\"inner_key\":\"inner_val\"\n}\n}".as_bytes());
check_valid("{ \"key\": {\n\"inner_key\":\"inner_val\"\n}\n}".as_bytes());
assert_eq!(Ok(()), res)
}
}
54 changes: 54 additions & 0 deletions src/parser/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use super::{error::JSONError, token::Token};

pub fn parse(tokens: Vec<Token>)-> Result<(), JSONError>{
if tokens.len() == 1 && tokens[0] == Token::OpenBrace {
return Err(JSONError::new("Unexpected EOF".to_string(), 1));
}
Ok(())
}

#[cfg(test)]
mod test_parser_pass{
use crate::parser::token::Token;
macro_rules! test_parser_passes {
($($name:ident: $value:expr,)*) => {
use super::parse;
$(
#[test]
fn $name() {
let input = $value;
assert_eq!((), parse(input).unwrap());
}
)*
}
}

test_parser_passes! {
with_closed_open_brace: vec![Token::OpenBrace, Token::ClosedBrace],
}
}

#[cfg(test)]
mod test_parser_failure {
use crate::parser::{token::Token, error::JSONError};
macro_rules! test_parser_fails {
($($name:ident: $value:expr,)*) => {
use super::parse;
$(
#[test]
fn $name() {
let (input, expected_err) = $value;
assert_eq!(expected_err, parse(input).unwrap_err());
}
)*
}
}

test_parser_fails! {
with_only_open_brace: (
vec![Token::OpenBrace],
JSONError::new("Unexpected EOF".to_string(), 1),
),
}
}

Empty file added src/parser/test_utils.rs
Empty file.

0 comments on commit 19d2c50

Please sign in to comment.