use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
const FIELD: &str = "field";
const NEW: &str = "new";
const NAME: &str = "name";
fn api() -> TokenStream {
quote! { ::yog_api::nanite }
}
pub(crate) fn class(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let class: syn::LitStr = syn::parse2(attr)?;
let mut s: syn::ItemStruct = syn::parse2(item)?;
if !matches!(s.fields, syn::Fields::Unit) {
return Err(syn::Error::new_spanned(
&s.ident,
"a nanite class is a unit struct: it holds the object",
));
}
let n = api();
s.fields = syn::Fields::Unnamed(syn::parse_quote! { (#n::Handle) });
s.semi_token = Some(Default::default());
let ident = &s.ident;
Ok(quote! {
#s
impl #n::Nanite for #ident {
const CLASS: &'static str = #class;
fn from_handle(handle: #n::Handle) -> Self {
#ident(handle)
}
fn handle(&self) -> &#n::Handle {
&self.0
}
}
})
}
struct Sig {
params: Vec<(syn::Type, TokenStream)>,
ret: syn::Type,
receiver: bool,
}
fn sig_of(f: &syn::ForeignItemFn) -> syn::Result<Sig> {
let mut params = Vec::new();
let mut receiver = false;
for input in &f.sig.inputs {
match input {
syn::FnArg::Receiver(_) => receiver = true,
syn::FnArg::Typed(t) => {
let syn::Pat::Ident(name) = &*t.pat else {
return Err(syn::Error::new_spanned(
&t.pat,
"a nanite's parameters are named",
));
};
let name = &name.ident;
match &*t.ty {
syn::Type::Reference(r) => params.push(((*r.elem).clone(), quote! { #name })),
ty => params.push((ty.clone(), quote! { &#name })),
}
}
}
}
let ret = match &f.sig.output {
syn::ReturnType::Default => syn::parse_quote! { () },
syn::ReturnType::Type(_, ty) => (**ty).clone(),
};
Ok(Sig {
params,
ret,
receiver,
})
}
fn java_name(f: &syn::ForeignItemFn) -> Option<syn::LitStr> {
f.attrs.iter().find(|a| a.path().is_ident(NAME))?.parse_args().ok()
}
fn has(f: &syn::ForeignItemFn, attr: &str) -> bool {
f.attrs.iter().any(|a| a.path().is_ident(attr))
}
pub(crate) fn members(item: TokenStream) -> syn::Result<TokenStream> {
let imp: syn::ItemImpl = syn::parse2(item)?;
let owner = &imp.self_ty;
let (mut fns, mut statics) = (Vec::new(), Vec::new());
for it in &imp.items {
let tokens = match it {
syn::ImplItem::Verbatim(t) => t.clone(),
other => {
fns.push(quote! { #other });
continue;
}
};
let f: syn::ForeignItemFn = syn::parse2(tokens)?;
let (def, member) = member(owner, &f)?;
fns.push(def);
statics.push(member);
}
let attrs = &imp.attrs;
Ok(quote! {
#(#attrs)*
impl #owner {
#(#fns)*
}
#(#statics)*
})
}
fn member(owner: &syn::Type, f: &syn::ForeignItemFn) -> syn::Result<(TokenStream, TokenStream)> {
let n = api();
let sig = sig_of(f)?;
let name = &f.sig.ident;
let owner_name = quote! { #owner }
.to_string()
.replace(' ', "")
.to_uppercase();
let stat = format_ident!(
"__YOG_NANITE_{}_{}",
owner_name,
name.to_string().to_uppercase()
);
let (java_name, desc) = java_name_and_desc(f, &sig);
let static_def = quote! {
#[doc(hidden)]
static #stat: #n::Member = #n::Member::new(<#owner as #n::Nanite>::CLASS, #java_name, || #desc);
};
let body = body(f, &sig, &stat);
let (vis, handle) = (&f.vis, format_ident!("{}", name.to_string().to_uppercase()));
let doc = format!("Member `{name}`, resolved once (what `#[inject]` names).");
let body = quote! {
#[doc = #doc]
#vis const #handle: &'static #n::Member = &#stat;
#body
};
Ok((body, static_def))
}
fn java_name_and_desc(f: &syn::ForeignItemFn, sig: &Sig) -> (TokenStream, TokenStream) {
let n = api();
let tys: Vec<&syn::Type> = sig.params.iter().map(|(t, _)| t).collect();
let ret = &sig.ret;
let java_name = java_name(f).unwrap_or_else(|| syn::LitStr::new(&f.sig.ident.to_string(), Span::call_site()));
if has(f, FIELD) {
(quote! { #java_name }, quote! { <#ret as #n::Java>::desc() })
} else if has(f, NEW) {
let void = quote! { <() as #n::Reply>::desc() };
(
quote! { #n::CONSTRUCTOR },
quote! { #n::method_desc(&[#(<#tys as #n::Java>::desc()),*], &#void) },
)
} else {
(
quote! { #java_name },
quote! { #n::method_desc(&[#(<#tys as #n::Java>::desc()),*], &<#ret as #n::Reply>::desc()) },
)
}
}
fn body(f: &syn::ForeignItemFn, sig: &Sig, stat: &syn::Ident) -> TokenStream {
let n = api();
let args: Vec<&TokenStream> = sig.params.iter().map(|(_, a)| a).collect();
let ret = &sig.ret;
let target = if sig.receiver {
quote! { #n::Nanite::handle(self).to_value() }
} else {
quote! { ::yog_api::yog_data::Value::Null }
};
let (vis, fsig) = (&f.vis, &f.sig);
let attrs: Vec<&syn::Attribute> = f
.attrs
.iter()
.filter(|a| ![FIELD, NEW, NAME].iter().any(|n| a.path().is_ident(n)))
.collect();
if !has(f, FIELD) {
return quote! {
#(#attrs)*
#vis #fsig {
<#ret as #n::Reply>::from_reply(#stat.call(#target, vec![#(#n::Java::to_value(#args)),*]))
}
};
}
let setter = format_ident!("set_{}", f.sig.ident);
let me = if sig.receiver {
quote! { &self, }
} else {
quote! {}
};
quote! {
#(#attrs)*
#vis #fsig {
<#ret as #n::Reply>::from_reply(#stat.get(#target))
}
#vis fn #setter(#me value: #ret) -> bool {
#stat.set(#target, #n::Java::to_value(&value))
}
}
}