use crate::value::Value;
use crate::value::bytes::Bytes;
use crate::value::list::List;
use crate::value::map::Map;
use crate::value::number::Number;
use bstr::ByteSlice;
use bstr::ByteVec;
use either::Either;
use itertools::Itertools;
pub(crate) trait Print {
fn print_syntax(self) -> Vec<u8>;
fn print_input(self, printer: Printer) -> Vec<u8>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Printer {
Bytes,
List,
Map,
Root,
}
impl Printer {
pub(crate) fn narrow(self, other: Self) -> Self {
match self {
_ if other == Printer::Map => other,
_ if self == Printer::Map => self,
_ if other == Printer::List => other,
_ if self == Printer::List => self,
_ if other == Printer::Bytes => other,
_ if self == Printer::Bytes => self,
p => p,
}
}
fn escape(self) -> &'static [u8] {
match self {
Printer::Bytes => b"",
Printer::List => br#""', "#,
Printer::Map => br#""',: "#,
Printer::Root => b"",
}
}
}
impl Print for Value {
fn print_syntax(self) -> Vec<u8> {
match self {
Value::Bytes(v) => v.print_syntax(),
Value::List(v) => v.print_syntax(),
Value::Map(v) => v.print_syntax(),
}
}
fn print_input(self, printer: Printer) -> Vec<u8> {
match self {
Value::Bytes(v) => v.print_input(printer),
Value::List(v) => v.print_input(printer),
Value::Map(v) => v.print_input(printer),
}
}
}
impl Print for Bytes {
fn print_syntax(self) -> Vec<u8> {
number_or_quote_bytes(self.data)
}
fn print_input(self, printer: Printer) -> Vec<u8> {
match find_unescaped_bytes(&self.data, printer.escape()) {
Some(_) => number_or_quote_bytes(self.data),
None => self.data,
}
}
}
impl Print for List {
fn print_syntax(self) -> Vec<u8> {
let iter = self.into_iter();
let iter = iter.map(Value::print_syntax);
let iter = Itertools::intersperse(iter, b" ".into());
let prefix = core::iter::once(b"[".to_vec());
let suffix = core::iter::once(b"]".to_vec());
prefix.chain(iter).chain(suffix).flatten().collect()
}
fn print_input(self, printer: Printer) -> Vec<u8> {
let printer = printer.narrow(Printer::List);
let iter = self.into_iter().map(|v| v.print_input(printer));
let iter = Itertools::intersperse(iter, b", ".into());
iter.flatten().collect()
}
}
impl Print for Map {
fn print_syntax(self) -> Vec<u8> {
let iter = self.into_iter();
let iter = iter.flat_map(|(k, v)| [escape_map_key(k), v.print_syntax()]);
let iter = Itertools::intersperse(iter, b" ".into());
let prefix = core::iter::once(b"{".to_vec());
let suffix = core::iter::once(b"}".to_vec());
prefix.chain(iter).chain(suffix).flatten().collect()
}
fn print_input(self, printer: Printer) -> Vec<u8> {
let printer = printer.narrow(Printer::Map);
let iter = self.into_iter();
let iter = iter.flat_map(|(k, v)| [escape_map_key(k), v.print_input(printer)]);
let iter = Itertools::intersperse(iter, b": ".into());
iter.flatten().collect()
}
}
fn escape_map_key(data: Vec<u8>) -> Vec<u8> {
let quote = br#"""#;
let data = match escape(data, quote) {
Either::Right(data) => return quote_unescaped(data, quote),
Either::Left(data) => data,
};
if data.iter().any(u8::is_ascii_whitespace) {
return quote_unescaped(data, quote);
}
if find_unescaped_bytes(&data, Printer::Map.escape()).is_some() {
return quote_unescaped(data, quote);
}
data
}
fn escape(mut data: Vec<u8>, bytes: &[u8]) -> Either<Vec<u8>, Vec<u8>> {
let Some(index) = find_unescaped_bytes(&data, bytes) else {
return Either::Left(data);
};
let rest = data.split_off(index + 1);
let byte = data.pop().unwrap_or_default();
data.push_byte(b'\\');
data.push_byte(byte);
data.append(&mut escape(rest, bytes).into_inner());
Either::Right(data)
}
fn find_unescaped_bytes(data: &[u8], bytes: &[u8]) -> Option<usize> {
bytes.iter().find_map(|b| find_unescaped_byte(data, *b))
}
fn find_unescaped_byte(data: &[u8], byte: u8) -> Option<usize> {
data.find_byte(byte)
.filter(|i| *i == 0 || data.get(i - 1) != Some(&b'\\'))
}
fn number_or_quote_bytes(data: Vec<u8>) -> Vec<u8> {
match Number::try_from(data.as_slice()).ok() {
None => quote_escaped(data, br#"""#),
Some(_) => data,
}
}
pub(crate) fn quote_escaped(data: Vec<u8>, quote: &[u8]) -> Vec<u8> {
quote_unescaped(escape(data, quote).into_inner(), quote)
}
fn quote_unescaped(mut data: Vec<u8>, quote: &[u8]) -> Vec<u8> {
data.splice(0..0, quote.iter().copied());
data.extend_from_slice(quote);
data
}
#[cfg(test)]
mod test {
use crate::value::Value;
use crate::value::list::List;
use crate::value::map::Map;
use crate::value::print::Print;
use crate::value::print::Printer;
#[test]
fn test_print_syntax_bytes() {
assert_eq!(
print_syntax(Value::from(r#"helloworld"#)),
r#""helloworld""#
);
assert_eq!(
print_syntax(Value::from(r#"hello world"#)),
r#""hello world""#
);
assert_eq!(
print_syntax(Value::from("hello 'a' world")),
r#""hello 'a' world""#
);
assert_eq!(
print_syntax(Value::from(r#"hello "b" world"#)),
r#""hello \"b\" world""#
);
}
#[test]
fn test_print_syntax_list() {
assert_eq!(
print_syntax(list_from(&[r#"hello,world"#])),
r#"["hello,world"]"#
);
assert_eq!(
print_syntax(list_from(&[r#"hello, world"#])),
r#"["hello, world"]"#
);
assert_eq!(
print_syntax(list_from(&[r#"hello, "a", world"#])),
r#"["hello, \"a\", world"]"#
);
assert_eq!(
print_syntax(list_from(&[r#"hello, "b, world""#])),
r#"["hello, \"b, world\""]"#
);
assert_eq!(
print_syntax(list_from(&["hello", "world"])),
r#"["hello" "world"]"#
);
assert_eq!(
print_syntax(list_from(&[r#""a""#, r#""b""#])),
r#"["\"a\"" "\"b\""]"#
);
assert_eq!(
print_syntax(list_from(&[r#"a,"b""#, r#""c",d"#])),
r#"["a,\"b\"" "\"c\",d"]"#
);
}
#[test]
fn test_print_syntax_map() {
assert_eq!(
print_syntax(Map::from((r#"ab"#, r#"cd"#)).into()),
r#"{ab "cd"}"#
);
assert_eq!(
print_syntax(Map::from((r#"a b"#, r#"c d"#)).into()),
r#"{"a b" "c d"}"#
);
assert_eq!(
print_syntax(Map::from((r#"a'b"#, r#"c'd"#)).into()),
r#"{"a'b" "c'd"}"#
);
assert_eq!(
print_syntax(Map::from((r#"a"b"#, r#"c"d"#)).into()),
r#"{"a\"b" "c\"d"}"#
);
assert_eq!(
print_syntax(Map::from((r#"a,b"#, r#"c:d"#)).into()),
r#"{"a,b" "c:d"}"#
);
assert_eq!(
print_syntax(Map::from((r#"a:b"#, r#"c,d"#)).into()),
r#"{"a:b" "c,d"}"#
);
}
#[test]
fn test_print_input_bytes() {
assert_eq!(print_input(Value::from(r#"a"#)), r#"a"#);
assert_eq!(print_input(Value::from(r#"a b"#)), r#"a b"#);
assert_eq!(print_input(Value::from("a 'b' c")), r#"a 'b' c"#);
assert_eq!(print_input(Value::from(r#"a "b" c"#)), r#"a "b" c"#);
}
#[test]
fn test_print_input_list() {
assert_eq!(
print_input(list_from(&[r#"hellosimpleworld"#])),
"hellosimpleworld"
);
assert_eq!(
print_input(list_from(&[r#"hello,world"#])),
r#""hello,world""#
);
assert_eq!(
print_input(list_from(&[r#"hello, world"#])),
r#""hello, world""#
);
assert_eq!(
print_input(list_from(&[r#"hello, "a", world"#])),
r#""hello, \"a\", world""#
);
assert_eq!(
print_input(list_from(&[r#"hello, "b, world""#])),
r#""hello, \"b, world\"""#
);
assert_eq!(
print_input(list_from(&["hello", "a", "world"])),
"hello, a, world"
);
assert_eq!(
print_input(list_from(&[r#""a""#, r#""b""#])),
r#""\"a\"", "\"b\"""#
);
assert_eq!(
print_input(list_from(&[r#"a,"b""#, r#""c",d"#])),
r#""a,\"b\"", "\"c\",d""#
);
}
#[test]
fn test_print_input_map() {
assert_eq!(
print_input(Map::from((r#"ab"#, r#"cd"#)).into()),
r#"ab: cd"#
);
assert_eq!(
print_input(Map::from((r#"a b"#, r#"c d"#)).into()),
r#""a b": "c d""#
);
assert_eq!(
print_input(Map::from((r#"a'b"#, r#"c'd"#)).into()),
r#""a'b": "c'd""#
);
assert_eq!(
print_input(Map::from((r#"a"b"#, r#"c"d"#)).into()),
r#""a\"b": "c\"d""#
);
assert_eq!(
print_input(Map::from((r#"a,b"#, r#"c:d"#)).into()),
r#""a,b": "c:d""#
);
assert_eq!(
print_input(Map::from((r#"a:b"#, r#"c,d"#)).into()),
r#""a:b": "c,d""#
);
}
#[track_caller]
fn list_from(items: &[&str]) -> Value {
items.to_vec().into_iter().collect::<List>().into()
}
#[track_caller]
fn print_syntax(value: Value) -> String {
String::from_utf8_lossy(&value.print_syntax()).into_owned()
}
#[track_caller]
fn print_input(value: Value) -> String {
String::from_utf8_lossy(&value.print_input(Printer::Root)).into_owned()
}
}