Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions json_parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,6 @@
name = "json_parser"
version = "0.1.0"
edition = "2021"
authors = ["Your Name <your.email@example.com>"]
description = "A JSON parser implementation following the JSON specification"

[dependencies]
# No external dependencies needed

[profile.release]
opt-level = 3
debug = false
strip = true
debug-assertions = false
overflow-checks = false
lto = true
panic = 'abort'
incremental = false
codegen-units = 1
rpath = false
clap = { version = "4.5.37", features = ["derive"] }
204 changes: 85 additions & 119 deletions json_parser/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,154 +1,120 @@
use std::env;
use clap::{Parser, ArgAction};
use std::fs;
use std::io::{self, Read};
use std::path::Path;
use std::process;

mod json_value;
mod lexer;
mod parser;
mod json_value;

use json_value::JsonValue;
use parser::ParseError;

fn parse_json(input: &str) -> Result<JsonValue, ParseError> {
let mut lexer = lexer::Lexer::new(input);
let tokens = lexer.tokenize();

if tokens.is_empty() {
return Err(ParseError::InvalidJson);
}

// Check if the first token is a valid starting token for JSON (object or array)
if tokens[0] != lexer::Token::OpenBrace && tokens[0] != lexer::Token::OpenBracket {
return Err(ParseError::InvalidJson);
}

let mut parser = parser::Parser::new(tokens);
let result = parser.parse()?;
use json_value::JsonValue;

// Double-check the result just to be safe
match result {
JsonValue::Object(_) | JsonValue::Array(_) => Ok(result),
_ => Err(ParseError::InvalidJson),
}
/// A JSON parser that validates JSON files
#[derive(Parser)]
#[command(
name = "JSON Parser",
author = "Your Name",
version = "1.0.0",
about = "A JSON parser that validates JSON files",
long_about = "A JSON parser implementation built in Rust, following the JSON specification."
)]
struct Args {
/// Path to the JSON file to validate
#[arg(required = false)]
file: Option<String>,

/// Print detailed error information when validation fails
#[arg(short, long, action = ArgAction::SetTrue)]
verbose: bool,

/// Read JSON from standard input instead of a file
#[arg(short, long, action = ArgAction::SetTrue)]
stdin: bool,
}

fn main() {
let args: Vec<String> = env::args().collect();

if args.len() < 2 {
println!("Usage: ./json_parser <file>");
// Parse command-line arguments
let args = Args::parse();

// Determine whether to read from file or stdin
let json_content = if args.stdin {
// Read from stdin
read_from_stdin()
} else if let Some(file_path) = args.file.as_deref() {
// Read from file
read_from_file(file_path)
} else {
eprintln!("Error: Please provide a file path or use --stdin to read from standard input");
process::exit(1);
}

let file_path = &args[1];
let content = match fs::read_to_string(file_path) {
Ok(content) => content,
Err(e) => {
println!("Error reading file: {}", e);
process::exit(1);
}
};

match parse_json(&content) {
// Validate the JSON content
match validate_json(&json_content) {
Ok(_) => {
println!("Valid JSON");
process::exit(0);
},
Err(e) => {
println!("Invalid JSON: {:?}", e);
}
Err(err) => {
if args.verbose {
eprintln!("Invalid JSON: {}", err);
} else {
eprintln!("Invalid JSON");
}
process::exit(1);
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_empty_object() {
let result = parse_json("{}");
assert!(result.is_ok());
}

#[test]
fn test_string_key_value() {
let result = parse_json("{\"key\": \"value\"}");
assert!(result.is_ok());
}

#[test]
fn test_multiple_types() {
let result = parse_json("{\"key1\": true, \"key2\": false, \"key3\": null, \"key4\": \"value\", \"key5\": 101}");
assert!(result.is_ok());
}

#[test]
fn test_nested_structures() {
let result = parse_json("{\"key\": \"value\", \"key-n\": 101, \"key-o\": {}, \"key-l\": []}");
assert!(result.is_ok());
// Function to read content from a file
fn read_from_file(file_path: &str) -> String {
let path = Path::new(file_path);
match fs::read_to_string(path) {
Ok(content) => content,
Err(err) => {
eprintln!("Error reading file '{}': {}", file_path, err);
process::exit(1);
}
}
}

#[test]
fn test_complex_json() {
let json = r#"
{
"string": "Hello World",
"number": 42,
"boolean": true,
"null": null,
"array": [1, 2, 3, "four", null, true, {"nested": "object"}],
"object": {
"nested": "value",
"another": 123
}
// Function to read content from stdin
fn read_from_stdin() -> String {
let mut buffer = String::new();
match io::stdin().read_to_string(&mut buffer) {
Ok(_) => buffer,
Err(err) => {
eprintln!("Error reading from stdin: {}", err);
process::exit(1);
}
"#;
let result = parse_json(json);
assert!(result.is_ok());
}
}

#[test]
fn test_invalid_json() {
let result = parse_json("{");
assert!(result.is_err());

let result = parse_json("}{");
assert!(result.is_err());

let result = parse_json("{\"key\": \"unclosed string}");
assert!(result.is_err());

let result = parse_json("true");
assert!(result.is_err());

let result = parse_json("123");
assert!(result.is_err());
// Function to validate JSON
fn validate_json(content: &str) -> Result<(), String> {
let mut lexer = lexer::Lexer::new(content);
let tokens = lexer.tokenize();

let result = parse_json("\"string\"");
assert!(result.is_err());
if tokens.is_empty() {
return Err("Empty JSON or only whitespace".to_string());
}

let result = parse_json("null");
assert!(result.is_err());
// Check if the first token is a valid starting token for JSON
if tokens[0] != lexer::Token::OpenBrace && tokens[0] != lexer::Token::OpenBracket {
return Err("JSON must start with { or [".to_string());
}

#[test]
fn test_all_json_files() {
let test_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("test");
for entry in fs::read_dir(&test_dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) == Some("json") {
let file_name = path.file_name().unwrap().to_str().unwrap();
let content = fs::read_to_string(&path).unwrap();
let res = parse_json(&content);
println!("{}: {:?}", file_name, res);
if file_name.starts_with("pass") {
assert!(res.is_ok(), "{} should be valid JSON", file_name);
} else if file_name.starts_with("fail") {
assert!(res.is_err(), "{} should be invalid JSON", file_name);
}
let mut parser = parser::Parser::new(tokens);
match parser.parse() {
Ok(result) => {
// Ensure the result is an object or array
match result {
JsonValue::Object(_) | JsonValue::Array(_) => Ok(()),
_ => Err("JSON must be either an object or an array".to_string()),
}
}
},
Err(err) => Err(format!("{:?}", err)),
}
}
53 changes: 53 additions & 0 deletions json_parser/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// src/tests.rs

#[cfg(test)]
mod tests {
use crate::json_value::JsonValue;
use crate::lexer;
use crate::parser;
use crate::parser::ParseError;

fn parse_json(input: &str) -> Result<JsonValue, ParseError> {
let mut lexer = lexer::Lexer::new(input);
let tokens = lexer.tokenize();

if tokens.is_empty() {
return Err(ParseError::InvalidJson);
}

// Check if the first token is a valid starting token for JSON (object or array)
if tokens[0] != lexer::Token::OpenBrace && tokens[0] != lexer::Token::OpenBracket {
return Err(ParseError::InvalidJson);
}

let mut parser = parser::Parser::new(tokens);
let result = parser.parse()?;

// Double-check the result just to be safe
match result {
JsonValue::Object(_) | JsonValue::Array(_) => Ok(result),
_ => Err(ParseError::InvalidJson),
}
}

// Add your actual test functions here
#[test]
fn test_empty_object() {
let input = "{}";
assert!(parse_json(input).is_ok());
}

#[test]
fn test_empty_array() {
let input = "[]";
assert!(parse_json(input).is_ok());
}

#[test]
fn test_invalid_json() {
let input = "{";
assert!(parse_json(input).is_err());
}

// Add more tests as needed
}