struct Reader<'a> {
buf: &'a [u8],
pos: usize,
escaped: bool,
}
fn space(byte: u8) -> bool {
matches!(byte, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r')
}
fn is_null(buf: &[u8], start: usize, end: usize) -> bool {
end == start + 4 && buf[start..end].eq_ignore_ascii_case(b"null")
}
fn closer(open: u8) -> u8 {
match open {
b'[' => b']',
b'{' => b'}',
_ => b')',
}
}
impl<'a> Reader<'a> {
fn new(text: &'a str) -> Self {
Self { buf: text.as_bytes(), pos: 0, escaped: false }
}
fn at_end(&self) -> bool {
self.pos >= self.buf.len()
}
fn byte(&self) -> u8 {
self.buf[self.pos]
}
fn skip_space(&mut self) {
while !self.at_end() && space(self.byte()) {
self.pos += 1;
self.escaped = false;
}
}
fn skip_quoted(&mut self) -> bool {
let quote = self.byte();
self.pos += 1;
while !self.at_end() {
let mut escapes = false;
if self.byte() == b'\\' {
escapes = !self.escaped;
} else if self.byte() == quote && !self.escaped {
return true;
}
self.escaped = escapes;
self.pos += 1;
}
false
}
fn skip_bracketed(&mut self) -> bool {
let mut open: Vec<u8> = Vec::new();
while !self.at_end() {
let mut escapes = false;
let byte = self.byte();
if byte == b'"' || byte == b'\'' {
if !self.escaped && !self.skip_quoted() {
return false;
}
} else if matches!(byte, b'[' | b'{' | b'(') {
open.push(closer(byte));
} else if open.last() == Some(&byte) {
open.pop();
if open.is_empty() {
return true;
}
} else if byte == b'\\' {
escapes = true;
}
self.escaped = escapes;
self.pos += 1;
}
false
}
fn step(&mut self, start: &mut Option<usize>, end: &mut usize) -> bool {
let byte = self.byte();
let mut escapes = false;
if byte == b'"' || byte == b'\'' {
start.get_or_insert(self.pos);
if !self.escaped && !self.skip_quoted() {
return false;
}
*end = self.pos;
} else if matches!(byte, b'{' | b'(' | b'[') {
start.get_or_insert(self.pos);
if !self.skip_bracketed() {
return false;
}
*end = self.pos;
} else if byte == b'\\' {
start.get_or_insert(self.pos);
escapes = true;
*end = self.pos;
} else if !space(byte) {
start.get_or_insert(self.pos);
*end = self.pos;
}
self.escaped = escapes;
self.pos += 1;
true
}
fn piece(&mut self, stops: &[u8]) -> Option<(usize, usize)> {
let mut start = None;
let mut end = 0;
while !self.at_end() && !stops.contains(&self.byte()) {
if !self.step(&mut start, &mut end) {
return None;
}
}
if self.at_end() {
return None;
}
Some(start.map_or((0, 0), |start| (start, end + 1)))
}
fn text(&self, (start, end): (usize, usize)) -> Option<String> {
(!is_null(self.buf, start, end)).then(|| unquote(self.buf, start, end, true))
}
fn next(&mut self, stops: &[u8]) -> Option<Option<String>> {
let bounds = self.piece(stops)?;
Some(self.text(bounds))
}
fn finished(&mut self) -> bool {
self.pos += 1;
self.skip_space();
self.at_end()
}
}
fn unquote(buf: &[u8], start: usize, end: usize, scopes: bool) -> String {
let mut out = Vec::with_capacity(end - start);
let mut escaped = false;
let mut quote = None;
let mut open: Vec<u8> = Vec::new();
for at in start..end {
let byte = buf[at];
if escaped {
out.push(byte);
escaped = false;
continue;
}
if open.is_empty() && byte == b'\\' {
let next_is_quote = at + 1 < end && matches!(buf[at + 1], b'\'' | b'"');
if quote.is_some() || next_is_quote {
escaped = true;
continue;
}
}
if open.is_empty() && (byte == b'\'' || byte == b'"') {
match quote {
Some(q) if q == byte => {
quote = None;
continue;
}
None => {
quote = Some(byte);
continue;
}
Some(_) => {}
}
}
if quote.is_none() && open.last() == Some(&byte) {
open.pop();
}
if quote.is_none() && scopes && matches!(byte, b'[' | b'{' | b'(') {
open.push(closer(byte));
}
out.push(byte);
}
String::from_utf8_lossy(&out).into_owned()
}
pub(crate) fn list(text: &str) -> Option<Vec<Option<String>>> {
let mut reader = Reader::new(text);
reader.skip_space();
if reader.at_end() || reader.byte() != b'[' {
return None;
}
reader.pos += 1;
reader.skip_space();
let mut items = Vec::new();
while !reader.at_end() {
let mut start = None;
let mut end = 0;
while !reader.at_end() && !matches!(reader.byte(), b',' | b']') {
if !reader.step(&mut start, &mut end) {
return None;
}
}
if reader.at_end() {
return None;
}
if reader.byte() != b']' || start.is_some() || !items.is_empty() {
items.push(reader.text(start.map_or((0, 0), |start| (start, end + 1))));
}
if reader.byte() == b']' {
break;
}
reader.pos += 1;
reader.skip_space();
}
reader.finished().then_some(items)
}
pub(crate) fn map(text: &str) -> Option<Vec<(String, Option<String>)>> {
let mut reader = Reader::new(text);
reader.skip_space();
if reader.at_end() || reader.byte() != b'{' {
return None;
}
reader.pos += 1;
reader.skip_space();
if reader.at_end() {
return None;
}
let mut entries = Vec::new();
if reader.byte() == b'}' {
return reader.finished().then_some(entries);
}
while !reader.at_end() {
let key = reader.next(b"=")??;
reader.pos += 1;
reader.skip_space();
let value = reader.next(b",}")?;
entries.push((key, value));
if reader.byte() == b'}' {
break;
}
reader.pos += 1;
reader.skip_space();
}
reader.finished().then_some(entries)
}
pub(crate) fn fields(text: &str, names: &[&str], unnamed: bool) -> Option<Vec<Option<String>>> {
let mut reader = Reader::new(text);
let mut out = vec![None; names.len()];
reader.skip_space();
if reader.at_end() || !matches!(reader.byte(), b'{' | b'(') {
return None;
}
let close = closer(reader.byte());
reader.pos += 1;
reader.skip_space();
if reader.at_end() {
return None;
}
if reader.byte() == close {
return reader.finished().then_some(out);
}
if close == b'}' {
while !reader.at_end() {
let (start, end) = named_key(&mut reader)?;
if is_null(reader.buf, start, end) || unnamed {
return None;
}
let key = unquote(reader.buf, start, end, false);
let at = names.iter().position(|name| *name == key)?;
reader.pos += 1;
reader.skip_space();
out[at] = reader.next(b",}")?;
if reader.byte() == b'}' {
break;
}
reader.pos += 1;
reader.skip_space();
}
} else {
let mut at = 0;
while !reader.at_end() {
if at == names.len() {
return None;
}
out[at] = reader.next(b",)")?;
if reader.byte() == b')' {
break;
}
at += 1;
reader.pos += 1;
reader.skip_space();
if !reader.at_end() && reader.byte() == b')' {
break;
}
}
}
reader.finished().then_some(out)
}
fn named_key(reader: &mut Reader<'_>) -> Option<(usize, usize)> {
let mut start = None;
let mut end = 0;
while !reader.at_end() && reader.byte() != b':' {
let byte = reader.byte();
let mut escapes = false;
if reader.escaped {
start.get_or_insert(reader.pos);
end = reader.pos;
} else if byte == b'"' || byte == b'\'' {
start.get_or_insert(reader.pos);
if !reader.skip_quoted() {
return None;
}
end = reader.pos;
} else if byte == b'\\' {
start.get_or_insert(reader.pos);
escapes = true;
end = reader.pos;
} else if !space(byte) {
start.get_or_insert(reader.pos);
end = reader.pos;
}
reader.escaped = escapes;
reader.pos += 1;
}
if reader.at_end() {
return None;
}
Some((start?, end + 1))
}
#[cfg(test)]
mod tests {
use super::*;
fn some(items: &[&str]) -> Vec<Option<String>> {
items.iter().map(|item| Some((*item).to_string())).collect()
}
#[test]
fn a_list_splits_the_way_the_pin_splits_it() {
assert_eq!(list("[]"), Some(vec![]));
assert_eq!(list(" [ 1 , 2 ] "), Some(some(&["1", "2"])));
assert_eq!(list("[1,,2]"), Some(some(&["1", "", "2"])));
assert_eq!(list("[1,]"), Some(some(&["1", ""])));
assert_eq!(list("[ null , NULL, \"null\"]"), Some(vec![None, None, Some("null".into())]));
assert_eq!(
list(r#"["a, b", 'c', d\,e, [x, y]]"#),
Some(some(&["a, b", "c", r"d\", "e", "[x, y]"]))
);
assert_eq!(list(r#"[\"a]"#), Some(some(&["\"a"])));
assert_eq!(list(r"[a\b]"), Some(some(&[r"a\b"])));
assert_eq!(list("[1, 2"), None);
assert_eq!(list("1, 2]"), None);
assert_eq!(list("[1] x"), None);
}
#[test]
fn a_map_and_a_struct_split_the_way_the_pin_splits_them() {
assert_eq!(map("{}"), Some(vec![]));
assert_eq!(
map(" { a = 1 ,b= 2 } "),
Some(vec![("a".into(), Some("1".into())), ("b".into(), Some("2".into()))])
);
assert_eq!(map("{a=}"), Some(vec![("a".into(), Some(String::new()))]));
assert_eq!(map("{a=NULL}"), Some(vec![("a".into(), None)]));
assert_eq!(map("{NULL=1}"), None);
assert_eq!(map("{a=1"), None);
assert_eq!(fields("{b: 1}", &["a", "b"], false), Some(vec![None, Some("1".into())]));
assert_eq!(fields("{a: 1, a: 2}", &["a"], false), Some(some(&["2"])));
assert_eq!(fields("{A: 1}", &["a"], false), None);
assert_eq!(fields("(1)", &["a", "b"], false), Some(vec![Some("1".into()), None]));
assert_eq!(fields("(1,)", &["", ""], true), Some(vec![Some("1".into()), None]));
assert_eq!(fields("(1,2,3)", &["a", "b"], false), None);
assert_eq!(fields("{a: {b: [1, 2]}}", &["a"], false), Some(some(&["{b: [1, 2]}"])));
}
}