use std::str;
#[derive(Debug)]
pub enum JsonFragment<'a> {
Literal(String),
Repl(&'a str)
}
pub fn parse_literal(remainder: &[u8], json: &mut String) {
let _ = literal(remainder, json, false);
}
pub fn parse_fragments<'a>(remainder: &'a [u8], fragments: &mut Vec<JsonFragment<'a>>) {
let mut l = String::new();
let remainder = literal(remainder, &mut l, true);
if l.len() > 0 {
fragments.push(JsonFragment::Literal(l));
}
let (remainder, r) = repl(remainder);
if r.len() > 0 {
fragments.push(JsonFragment::Repl(r));
}
if remainder.len() > 0 {
parse_fragments(remainder, fragments);
}
}
fn repl(remainder: &[u8]) -> (&[u8], &str) {
if remainder.len() == 0 {
return (&[], "");
}
take_while(&remainder, (), |_, c| {
((), is_ident(c as char))
})
}
fn literal<'a>(remainder: &'a [u8], sanitised: &mut String, break_on_repl: bool) -> &'a [u8] {
if remainder.len() == 0 {
return &[];
}
let current = remainder[0];
match current {
b'"'|b'\'' => {
enum StringState {
Unescaped,
Escaped
}
let (rest, key) = take_while(&remainder[1..], StringState::Unescaped,
|s, c| {
match (s, c) {
(StringState::Unescaped, b'\\') => {
(StringState::Escaped, true)
},
(StringState::Escaped, _) => {
(StringState::Unescaped, true)
},
(StringState::Unescaped, c) if c == current => {
(StringState::Unescaped, false)
},
_ => {
(StringState::Unescaped, true)
}
}
}
);
sanitised.push('"');
sanitised.push_str(key);
sanitised.push('"');
literal(&rest[1..], sanitised, break_on_repl)
},
b'{'|b'['|b':' => {
sanitised.push(current as char);
literal(&remainder[1..], sanitised, break_on_repl)
},
b' '|b'\r'|b'\n'|b'\t' => {
literal(&remainder[1..], sanitised, break_on_repl)
},
b if (b as char).is_alphabetic() => {
let (rest, key) = take_while(remainder, (), |_, c| {
((), is_ident(c as char))
});
match key {
"true"|"false"|"null" =>
sanitised.push_str(key),
_ => {
sanitised.push('"');
sanitised.push_str(key);
sanitised.push('"');
}
}
literal(rest, sanitised, break_on_repl)
},
b if (b as char).is_numeric() => {
let (rest, key) = take_while(remainder, (), |_, c| {
((), {
(c as char).is_numeric() ||
c == b'.' ||
c == b'+' ||
c == b'-' ||
c == b'e' ||
c == b'E'
})
});
sanitised.push_str(key);
literal(rest, sanitised, break_on_repl)
},
b'$' if break_on_repl => {
let remainder = shift_while(&remainder[1..], |c| c == b' ');
remainder
},
_ => {
sanitised.push(current as char);
literal(&remainder[1..], sanitised, break_on_repl)
}
}
}
#[inline]
fn is_ident(c: char) -> bool {
c.is_alphabetic() || c == '_'
}
fn shift_while<F>(i: &[u8], f: F) -> &[u8]
where F: Fn(u8) -> bool
{
let mut ctr = 0;
for c in i {
if f(*c) {
ctr += 1;
}
else {
break;
}
}
&i[ctr..]
}
fn take_while<F, S>(i: &[u8], mut s: S, f: F) -> (&[u8], &str)
where F: Fn(S, u8) -> (S, bool)
{
let mut ctr = 0;
for c in i {
let (new_state, more) = f(s, *c);
if more {
s = new_state;
ctr += 1;
}
else {
break;
}
}
(&i[ctr..], unsafe { str::from_utf8_unchecked(&i[..ctr]) })
}