diff --git a/json_parser/Cargo.toml b/json_parser/Cargo.toml index f82e4b1..2a4d11c 100644 --- a/json_parser/Cargo.toml +++ b/json_parser/Cargo.toml @@ -2,20 +2,6 @@ name = "json_parser" version = "0.1.0" edition = "2021" -authors = ["Your Name "] -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 \ No newline at end of file +clap = { version = "4.5.37", features = ["derive"] } \ No newline at end of file diff --git a/json_parser/src/main.rs b/json_parser/src/main.rs index 3e4fe24..dd4483d 100644 --- a/json_parser/src/main.rs +++ b/json_parser/src/main.rs @@ -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 { - 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, + + /// 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 = env::args().collect(); - - if args.len() < 2 { - println!("Usage: ./json_parser "); + // 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)), } } \ No newline at end of file diff --git a/json_parser/src/test.rs b/json_parser/src/test.rs new file mode 100644 index 0000000..590d687 --- /dev/null +++ b/json_parser/src/test.rs @@ -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 { + 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 +} \ No newline at end of file