#![deny(missing_docs)]
mod parse;
use proc_macro::TokenStream;
use parse::{Field, Struct};
#[proc_macro_derive(Yo, attributes(yo))]
pub fn yo(input: TokenStream) -> TokenStream {
match parse::parse(input).and_then(emit) {
Ok(out) => out,
Err(why) => complain(&why),
}
}
fn complain(why: &str) -> TokenStream {
format!("::core::compile_error!{{ {why:?} }}")
.parse()
.expect("a compile_error with a string in it")
}
fn emit(s: Struct) -> Result<TokenStream, String> {
let name = &s.name;
let mut out = String::new();
out.push_str(&shape(&s));
out.push_str(&field(&s));
out.push_str(&indexed(&s));
if let Some(id) = the_id(&s)? {
out.push_str(&document(&s, id));
}
out.push_str(&paths(&s));
out.parse().map_err(|e| {
format!("Yo wrote something the compiler would not take for {name}, which is a bug in the derive: {e}")
})
}
fn the_id(s: &Struct) -> Result<Option<&Field>, String> {
let mut marked = s.fields.iter().filter(|f| f.id);
let Some(first) = marked.next() else {
return Ok(None);
};
if let Some(second) = marked.next() {
return Err(format!(
"{} marks both {} and {} as its id, and a document is stored under one",
s.name, first.label, second.label
));
}
Ok(Some(first))
}
fn shape(s: &Struct) -> String {
let mut fields = String::new();
for f in &s.fields {
let (label, ty) = (&f.label, &f.ty);
fields.push_str(&format!("({label:?}, <{ty} as ::yo::Shape>::describe),"));
}
let (name, label) = (&s.name, &s.name);
format!(
"#[automatically_derived]
impl ::yo::Shape for {name} {{
fn describe(d: &mut ::yo::Desc) {{
d.strukt({label:?}, &[{fields}]);
}}
}}
"
)
}
fn field(s: &Struct) -> String {
let mut write = String::new();
let mut read = String::new();
for f in &s.fields {
let (name, label) = (&f.name, &f.label);
write.push_str(&format!(
"b.key({label:?}.as_bytes())?; ::yo::doc::Field::write(&self.{name}, b)?;"
));
read.push_str(&format!("{name}: ::yo::doc::at(d, {label:?})?,"));
}
let name = &s.name;
format!(
"#[automatically_derived]
impl ::yo::doc::Field for {name} {{
fn write(&self, b: &mut ::yo::doc::Builder) -> ::yo::Result<()> {{
b.begin_object()?;
{write}
b.end_object()
}}
fn read(d: ::yo::doc::Doc<'_>) -> ::yo::Result<{name}> {{
::yo::doc::expect_object(d, {name:?})?;
Ok({name} {{ {read} }})
}}
}}
"
)
}
fn indexed(s: &Struct) -> String {
let mut indexes = String::new();
for f in &s.fields {
if let Some(kind) = f.kind {
let path = format!("$.{}", f.label);
indexes.push_str(&format!("({path:?}, ::yo::doc::IndexKind::{kind}),"));
}
}
let name = &s.name;
format!(
"#[automatically_derived]
impl ::yo::doc::Indexed for {name} {{
const INDEXES: &'static [(&'static str, ::yo::doc::IndexKind)] = &[{indexes}];
}}
"
)
}
fn document(s: &Struct, id: &Field) -> String {
let (name, ty, at) = (&s.name, &id.ty, &id.name);
format!(
"#[automatically_derived]
impl ::yo::doc::Document for {name} {{
type Id = {ty};
fn id(&self) -> &{ty} {{
&self.{at}
}}
}}
"
)
}
fn paths(s: &Struct) -> String {
let mut consts = String::new();
for f in &s.fields {
let Some(kind) = f.kind else { continue };
let asked = match (kind, &f.elem) {
("Array", Some(elem)) => elem,
_ => &f.ty,
};
let (upper, label, path) = (f.label.to_uppercase(), &f.label, format!("$.{}", f.label));
let name = &s.name;
let held = if kind == "Ordered" {
format!("::yo::doc::Ordered<{name}, {asked}>")
} else {
format!("::yo::doc::Path<{name}, {asked}>")
};
let built = if kind == "Ordered" {
format!("::yo::doc::Ordered::new({path:?})")
} else {
format!("::yo::doc::Path::new({path:?}, ::yo::doc::IndexKind::{kind})")
};
consts.push_str(&format!(
" /// The `{path}` path, which is indexed for {kind} and named
/// `{label}` on this type.
pub const {upper}: {held} = {built};
"
));
}
if consts.is_empty() {
return String::new();
}
let name = &s.name;
format!("impl {name} {{\n{consts}}}\n")
}