commit 9905d6418f4205b3f7ae0b2775e50df74ff6771a Author: Niverton Date: Mon Dec 16 19:56:40 2024 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7620c33 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,23 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + +[[package]] +name = "json_savior" +version = "0.1.0" +dependencies = [ + "json", + "lyn", +] + +[[package]] +name = "lyn" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f08d9299a146aa1eb3c5451e6d9045708f232a43fd8d4436f69cf00fcb2a019c" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..df7c378 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "json_savior" +version = "0.1.0" +edition = "2021" + +[dependencies] +json = "0.12.4" +lyn = "0.1.0" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..ae0a052 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,268 @@ +use std::io::{Read, Write}; + +use json::{object::Object, Array, JsonValue}; +use lyn::{Action, Scanner}; + +fn eat_white_space(scan: &mut Scanner) { + while let Some(c) = scan.peek() { + if c.is_whitespace() { + scan.pop(); + } else { + break; + } + } +} + +fn eat_until_separator(scan: &mut Scanner) { + while let Some(c) = scan.peek() { + if *c == ',' || *c == '\n' || *c == ':' { + scan.pop(); + break; + } else if c.is_whitespace() { + scan.pop(); + } else { + break; + } + } +} + +fn is_quote(c: char) -> bool { + c == '"' || c == '\'' +} + +fn is_value_end(c: char) -> bool { + c.is_whitespace() || c == ':' || c == ',' || c == ']' || c == '}' +} + +fn scan_string(scan: &mut Scanner) -> Option { + scan.scan(|input| { + let mut chars = input.chars(); + let start = chars.next().unwrap(); + if is_quote(start) { + if let Some(back) = chars.next_back() { + if back == start { + // Same quote type is important + return Some(Action::Return(chars.collect::().into())); + } + } + // We're waiting for the end quote + Some(Action::Require) + } else if is_value_end(start) { + None + } else { + // Not a quoted string, continue until end of value + if let Some(back) = chars.next_back() { + if is_value_end(back) { + return None; + } + } + Some(Action::Request(input.into())) + } + }) + .unwrap_or_default() +} + +fn scan_number(scan: &mut Scanner) -> Option { + scan.scan(|input| { + // Quick to write, probably horrible perf :d + let start = input.chars().next().unwrap(); + if input.len() == 1 && (start == '-' || start == '+') { + // eprintln!("Parsed sign"); + Some(Action::Require) + } else if let Ok(integer) = input.parse::() { + // eprintln!("Parsed {integer}"); + Some(Action::Request(integer.into())) + } else if let Ok(float) = input.parse::() { + // eprintln!("Parsed {float}"); + Some(Action::Request(float.into())) + } else { + // eprintln!("None on {input}"); + None + } + }) + // .inspect_err(|e| eprintln!("Error: {e:?}")) + .unwrap_or_default() +} + +fn scan_array(scan: &mut Scanner) -> Option { + if scan.take(&'[') { + let mut ret = Array::new(); + while let Some(value) = scan_value(scan) { + ret.push(value); + eat_until_separator(scan); + } + eat_white_space(scan); + assert!(scan.peek().unwrap() == &']'); + scan.pop(); + Some(ret.into()) + } else { + None + } +} + +fn scan_key_value(scan: &mut Scanner) -> Option<(String, JsonValue)> { + if let Some(key) = scan_string(scan).as_ref().and_then(JsonValue::as_str) { + // eprintln!("Read key {key}"); + eat_until_separator(scan); + scan_value(scan).map(|value| (key.to_owned(), value)) + } else { + None + } +} + +fn scan_object(scan: &mut Scanner) -> Option { + if scan.take(&'{') { + let mut ret = Object::new(); + eat_white_space(scan); + while let Some((key, value)) = scan_key_value(scan) { + eprintln!("Read {key} {value:?}"); + ret.insert(&key, value); + eat_until_separator(scan); + eat_white_space(scan); + } + assert!(scan.peek().unwrap() == &'}'); + scan.pop(); + Some(ret.into()) + } else { + None + } +} + +fn scan_value(scan: &mut Scanner) -> Option { + eat_white_space(scan); + scan_array(scan) + .or_else(|| scan_object(scan)) + .or_else(|| scan_number(scan)) + .or_else(|| scan_string(scan)) +} + +fn main() { + let mut input = String::new(); + std::io::stdin().read_to_string(&mut input).unwrap(); + + let mut scan = Scanner::new(&input); + let out = scan_value(&mut scan).unwrap(); + std::io::stdout() + .write_all(out.to_string().as_bytes()) + .unwrap() +} + +#[cfg(test)] +mod test { + use json::array; + use json::object::Object; + use json::Array; + use json::JsonValue; + use lyn::Scanner; + + use crate::scan_array; + use crate::scan_key_value; + use crate::scan_number; + use crate::scan_object; + use crate::scan_string; + + #[test] + fn test_scan_string() { + let input = "string1:string2\n\"string\",,"; + let output: &[Option<&str>] = &[Some("string1"), Some("string2"), Some("string"), None]; + let mut scan = Scanner::new(input); + for out in output { + assert_eq!( + scan_string(&mut scan).as_ref().and_then(JsonValue::as_str), + *out + ); + scan.pop(); //skip the break char we put + } + assert!(scan.is_done()); + } + + #[test] + fn test_scan_number() { + let input = "13,155.12,-25,,"; + let output: &[Option] = &[Some(13.), Some(155.12), Some(-25.), None]; + let mut scan = Scanner::new(input); + for expected in output { + let ret = scan_number(&mut scan).as_ref().and_then(JsonValue::as_f32); + assert_eq!(ret, *expected); + + scan.pop(); //skip the break char we put + } + assert!(scan.is_done()); + } + + #[test] + fn test_key_value() { + let input = "key:value,lol:125,,lel:,\"quoted\":'also_quoted'"; + let output: &[Option<(String, JsonValue)>] = &[ + Some(("key".into(), JsonValue::String("value".into()))), + Some(("lol".into(), JsonValue::Number(125.into()))), + None, + None, + Some(("quoted".into(), JsonValue::String("also_quoted".into()))), + ]; + let mut scan = Scanner::new(input); + for expected in output { + let ret = scan_key_value(&mut scan); + assert_eq!(ret, *expected); + + scan.pop(); //skip the break char we put + } + assert!(scan.is_done()); + } + + #[test] + fn test_scan_array() { + let input = "[],[12, 1\n 3]\n[1],[[1],[2]],mdr"; + let output: &[Option] = &[ + Some(vec![]), + Some(vec![ + JsonValue::Number(12.into()), + JsonValue::Number(1.into()), + JsonValue::Number(3.into()), + ]), + Some(vec![JsonValue::Number(1.into())]), + Some(vec![array![1], array![2]]), + None, + ]; + let mut scan = Scanner::new(input); + for expected in output { + let ret = scan_array(&mut scan); + assert_eq!( + ret.is_some(), + expected.is_some(), + "Expected {expected:?}, got {ret:?}" + ); + if let Some(JsonValue::Array(arr)) = ret { + assert_eq!(&arr, expected.as_ref().unwrap()); + scan.pop(); //skip the break char we put + } + } + assert_eq!(scan_string(&mut scan), Some(JsonValue::from("mdr"))); + assert!(scan.is_done()); + } + + #[test] + fn test_scan_object() { + let input = "{lol:mdr\nboloss:[megalol], \"truc\":12}"; + let output: &[Option] = &[Some(Object::from_iter([ + ("lol", JsonValue::from("mdr")), + ("boloss", array!["megalol"]), + ("truc", JsonValue::from(12)), + ]))]; + let mut scan = Scanner::new(input); + for expected in output { + let ret = scan_object(&mut scan); + assert_eq!( + ret.is_some(), + expected.is_some(), + "Expected {expected:?}, got {ret:?}" + ); + if let Some(JsonValue::Object(obj)) = ret { + assert_eq!(&obj, expected.as_ref().unwrap()); + scan.pop(); //skip the break char we put + } + } + // assert_eq!(scan_string(&mut scan), Some(JsonValue::from("mdr"))); + assert!(scan.is_done()); + } +}