Initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
23
Cargo.lock
generated
Normal file
23
Cargo.lock
generated
Normal file
@@ -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"
|
||||||
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "json_savior"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
json = "0.12.4"
|
||||||
|
lyn = "0.1.0"
|
||||||
268
src/main.rs
Normal file
268
src/main.rs
Normal file
@@ -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<JsonValue> {
|
||||||
|
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::<String>().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<JsonValue> {
|
||||||
|
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::<i32>() {
|
||||||
|
// eprintln!("Parsed {integer}");
|
||||||
|
Some(Action::Request(integer.into()))
|
||||||
|
} else if let Ok(float) = input.parse::<f32>() {
|
||||||
|
// 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<JsonValue> {
|
||||||
|
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<JsonValue> {
|
||||||
|
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<JsonValue> {
|
||||||
|
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<f32>] = &[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<Array>] = &[
|
||||||
|
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<Object>] = &[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());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user