use proc_macro2::Span;
pub(crate) enum Part {
Lit(String),
Field(String),
Count,
}
pub(crate) struct Alternative {
pub items: Vec<String>,
pub len: Option<Vec<String>>,
}
pub(crate) struct Only {
pub path: Vec<String>,
pub mask: Option<u64>,
}
pub(crate) struct Field {
pub name: String,
pub hide: bool,
pub rename: Option<String>,
pub items: bool,
pub text: bool,
pub only: Option<Only>,
pub value: Option<Vec<String>>,
pub len: Option<(String, Span)>,
}
pub(crate) struct Variant {
pub name: String,
pub summary: Option<Vec<Part>>,
pub fields: Vec<Field>,
}
pub(crate) enum Body {
Struct(Vec<Field>),
Enum(Vec<Variant>),
}
pub(crate) struct Ty {
pub name: String,
pub generic: bool,
pub summary: Option<Vec<Part>>,
pub body: Body,
pub alternatives: Vec<Alternative>,
}
fn json_str(s: &str, out: &mut String) {
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
' '..='~' if c != '\'' => out.push(c),
_ => {
let mut buf = [0u16; 2];
for unit in c.encode_utf16(&mut buf) {
out.push_str(&format!("\\u{unit:04x}"));
}
}
}
}
out.push('"');
}
fn list<T>(items: &[T], out: &mut String, mut each: impl FnMut(&T, &mut String)) {
out.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
each(item, out);
}
out.push(']');
}
fn members(summary: Option<&[Part]>, fields: &[Field], out: &mut String) {
if let Some(parts) = summary {
out.push_str(",\"summary\":");
list(parts, out, |p, out| {
let (kind, text) = match p {
Part::Lit(t) => ("lit", t.as_str()),
Part::Field(f) => ("field", f.as_str()),
Part::Count => ("count", ""),
};
out.push_str("[\"");
out.push_str(kind);
out.push_str("\",");
json_str(text, out);
out.push(']');
});
}
let hidden: Vec<&Field> = fields.iter().filter(|f| f.hide).collect();
if !hidden.is_empty() {
out.push_str(",\"hide\":");
list(&hidden, out, |f, out| json_str(&f.name, out));
}
let renamed: Vec<&Field> = fields.iter().filter(|f| f.rename.is_some()).collect();
if !renamed.is_empty() {
out.push_str(",\"rename\":{");
for (i, f) in renamed.iter().enumerate() {
if i > 0 {
out.push(',');
}
json_str(&f.name, out);
out.push(':');
json_str(f.rename.as_deref().unwrap_or_default(), out);
}
out.push('}');
}
if let Some(f) = fields.iter().find(|f| f.items) {
if f.only.is_none() && f.value.is_none() {
out.push_str(",\"items\":");
source(f, out);
} else {
out.push_str(",\"slots\":");
source(f, out);
out.pop(); if let Some(only) = &f.only {
out.push_str(",\"only\":{\"path\":");
list(&only.path, out, |s, out| json_str(s, out));
if let Some(mask) = only.mask {
out.push_str(&format!(",\"mask\":{mask}"));
}
out.push('}');
}
if let Some(value) = &f.value {
out.push_str(",\"value\":");
list(value, out, |s, out| json_str(s, out));
}
out.push('}');
}
}
let texts: Vec<&Field> = fields.iter().filter(|f| f.text).collect();
if !texts.is_empty() {
out.push_str(",\"text\":");
list(&texts, out, |f, out| source(f, out));
}
}
fn source(f: &Field, out: &mut String) {
out.push_str("{\"field\":");
json_str(&f.name, out);
if let Some((len, _)) = &f.len {
out.push_str(",\"len\":");
json_str(len, out);
}
out.push('}');
}
pub(crate) fn json_tail(ty: &Ty) -> String {
let mut j = String::from(if ty.generic { "\"generic\":true" } else { "\"generic\":false" });
match &ty.body {
Body::Struct(fields) => {
j.push_str(",\"kind\":\"struct\"");
members(ty.summary.as_deref(), fields, &mut j);
}
Body::Enum(variants) => {
j.push_str(",\"kind\":\"enum\",\"variants\":{");
let mut first = true;
for v in variants {
let mut m = String::new();
members(v.summary.as_deref(), &v.fields, &mut m);
if m.is_empty() {
continue; }
if !first {
j.push(',');
}
first = false;
json_str(&v.name, &mut j);
j.push_str(":{");
j.push_str(&m[1..]);
j.push('}');
}
j.push('}');
}
}
if !ty.alternatives.is_empty() {
j.push_str(",\"alternatives\":");
list(&ty.alternatives, &mut j, |alt, out| {
out.push_str("{\"items\":");
list(&alt.items, out, |s, out| json_str(s, out));
if let Some(len) = &alt.len {
out.push_str(",\"len\":");
list(len, out, |s, out| json_str(s, out));
}
out.push('}');
});
}
j.push('}');
j
}
pub(crate) fn entry_call(ty: &Ty) -> String {
format!("::debuggable::__entry!({:?}, {:?});", ty.name, json_tail(ty))
}