use crate::ser::{Fragment, Map, Seq, Serialize};
use alloc::borrow::Cow;
use alloc::boxed::Box;
use arrayvec::{ArrayVec, ArrayString};
pub fn to_string<T: ?Sized + Serialize, const CAP: usize, const LAYER_CAP: usize>(value: &T, out: &mut ArrayString<CAP>) {
to_string_impl::<CAP, LAYER_CAP>(&value, out);
}
struct Serializer<'a, const LAYER_CAP: usize> {
stack: ArrayVec<Layer<'a>, LAYER_CAP>,
}
enum Layer<'a> {
Seq(Box<dyn Seq + 'a>),
Map(Box<dyn Map + 'a>),
}
impl<'a, const LAYER_CAP: usize> Drop for Serializer<'a, LAYER_CAP> {
fn drop(&mut self) {
while !self.stack.is_empty() {
self.stack.pop();
}
}
}
fn to_string_impl<const CAP: usize, const LAYER_CAP: usize>(value: &dyn Serialize, out: &mut ArrayString<CAP>) {
let mut serializer = Serializer { stack: ArrayVec::<_, LAYER_CAP>::new() };
let mut fragment = value.begin();
loop {
match fragment {
Fragment::Null => out.push_str("null"),
Fragment::Bool(b) => out.push_str(if b { "true" } else { "false" }),
Fragment::Str(s) => escape_str(&s, out),
Fragment::U64(n) => out.push_str(itoa::Buffer::new().format(n)),
Fragment::I64(n) => out.push_str(itoa::Buffer::new().format(n)),
Fragment::F64(n) => {
if n.is_finite() {
out.push_str(ryu::Buffer::new().format_finite(n));
} else {
out.push_str("null");
}
}
Fragment::Seq(mut seq) => {
out.push('[');
match unsafe { extend_lifetime!(seq.next() as Option<&dyn Serialize>) } {
Some(first) => {
serializer.stack.push(Layer::Seq(seq));
fragment = first.begin();
continue;
}
None => out.push(']'),
}
}
Fragment::Map(mut map) => {
out.push('{');
match unsafe { extend_lifetime!(map.next() as Option<(Cow<str>, &dyn Serialize)>) }
{
Some((key, first)) => {
escape_str(&key, out);
out.push(':');
serializer.stack.push(Layer::Map(map));
fragment = first.begin();
continue;
}
None => out.push('}'),
}
}
}
loop {
match serializer.stack.last_mut() {
Some(Layer::Seq(seq)) => {
match unsafe { extend_lifetime!(seq.next() as Option<&dyn Serialize>) } {
Some(next) => {
out.push(',');
fragment = next.begin();
break;
}
None => out.push(']'),
}
}
Some(Layer::Map(map)) => {
match unsafe {
extend_lifetime!(map.next() as Option<(Cow<str>, &dyn Serialize)>)
} {
Some((key, next)) => {
out.push(',');
escape_str(&key, out);
out.push(':');
fragment = next.begin();
break;
}
None => out.push('}'),
}
}
None => return,
}
serializer.stack.pop();
}
}
}
#[allow(clippy::zero_prefixed_literal)]
fn escape_str<const CAP: usize>(value: &str, out: &mut ArrayString<CAP>) {
out.push('"');
let bytes = value.as_bytes();
let mut start = 0;
for (i, &byte) in bytes.iter().enumerate() {
let escape = ESCAPE[byte as usize];
if escape == 0 {
continue;
}
if start < i {
out.push_str(&value[start..i]);
}
match escape {
self::BB => out.push_str("\\b"),
self::TT => out.push_str("\\t"),
self::NN => out.push_str("\\n"),
self::FF => out.push_str("\\f"),
self::RR => out.push_str("\\r"),
self::QU => out.push_str("\\\""),
self::BS => out.push_str("\\\\"),
self::U => {
static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";
out.push_str("\\u00");
out.push(HEX_DIGITS[(byte >> 4) as usize] as char);
out.push(HEX_DIGITS[(byte & 0xF) as usize] as char);
}
_ => unreachable!(),
}
start = i + 1;
}
if start != bytes.len() {
out.push_str(&value[start..]);
}
out.push('"');
}
const BB: u8 = b'b'; const TT: u8 = b't'; const NN: u8 = b'n'; const FF: u8 = b'f'; const RR: u8 = b'r'; const QU: u8 = b'"'; const BS: u8 = b'\\'; const U: u8 = b'u';
#[rustfmt::skip]
static ESCAPE: [u8; 256] = [
U, U, U, U, U, U, U, U, BB, TT, NN, U, FF, RR, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, 0, 0, QU, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, BS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];