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