use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, quote, ToTokens};
use syn::{
parse_quote, Attribute, Ident, ItemStruct, ItemType, Meta, Path, PathArguments, Type,
Visibility,
};
fn generate_alias_stmt(
vis: &Visibility,
alias_value: &impl ToTokens,
alias_type: &impl ToTokens,
) -> ItemType {
syn::parse2(quote! { #vis type #alias_value = #alias_type; }).unwrap()
}
#[proc_macro_attribute]
pub fn resource_group(args: TokenStream, item: TokenStream) -> TokenStream {
let mut s: ItemStruct = syn::parse2(item.into()).expect("Resource item must be a struct.");
let attr: Option<Ident> = syn::parse2(args.into()).unwrap();
let generate_aliases = match attr {
None => true,
Some(ident) => {
assert_eq!(
ident.to_string(),
"no_aliases",
"Expected identifier \"no_aliases\"."
);
false
}
};
let vis = s.vis.clone();
s.fields
.iter_mut()
.for_each(|field| field.vis = vis.clone());
let mut aliases = Vec::new();
s.fields.iter_mut().for_each(|field| {
let mut custom_alias = false;
field.attrs = field
.attrs
.iter()
.filter(|attr| {
if let Meta::NameValue(alias) = &attr.meta {
if let Some(ident) = alias.path.get_ident() {
if ident.to_string().eq("alias") {
aliases.push(generate_alias_stmt(&vis, &alias.value, &field.ty));
custom_alias = true;
return false;
}
}
}
true
})
.cloned()
.collect();
if generate_aliases && !custom_alias {
aliases.push(generate_alias_stmt(
&vis,
&format_ident!(
"{}",
inflector::cases::classcase::to_class_case(
field.ident.as_ref().unwrap().to_string().as_str()
)
),
&field.ty,
));
}
});
let use_macro_ident = Ident::new(
inflector::cases::snakecase::to_snake_case(s.ident.to_string().as_str()).as_str(),
Span::call_site(),
);
let macro_vis = if let Visibility::Restricted(_) = vis {
Some(quote! { #vis use #use_macro_ident; })
} else {
None
};
let ident = &s.ident;
let field_idents: Vec<Ident> = s
.fields
.iter()
.cloned()
.map(|field| field.ident.unwrap())
.collect();
let field_types: Vec<Type> = s
.fields
.iter()
.cloned()
.map(|field| {
if let Type::Path(ref ty) = field.ty {
let seg = &ty.path.segments.last().unwrap();
match &seg.arguments {
PathArguments::None => {
let ident = &seg.ident;
syn::parse2(quote! { #ident }).unwrap()
}
PathArguments::AngleBracketed(generic_args) => {
let ident = generic_args.args.last().unwrap();
syn::parse2(quote! { #ident }).unwrap()
}
PathArguments::Parenthesized(_) => todo!(),
}
} else {
field.ty
}
})
.collect();
let field_attrs: Vec<Vec<Attribute>> =
s.fields.iter().cloned().map(|field| field.attrs).collect();
let doc = format!("Extract `{}` from a `Peripherals` instance.", ident);
let peri_path: Path = if cfg!(feature = "stm32") {
parse_quote! { ::embassy_stm32::Peri }
} else if cfg!(feature = "nrf") {
parse_quote! { ::embassy_nrf::Peri }
} else if cfg!(feature = "_test") {
parse_quote! { Peri }
} else {
return syn::Error::new(
Span::call_site(),
"Exactly one ecosystem feature must be specified.",
)
.to_compile_error()
.into();
};
s.fields.iter_mut().for_each(|field| {
let ty = &field.ty;
field.ty = parse_quote! { #peri_path<'static, #ty> };
});
quote! {
#(
#aliases
)*
#s
#[doc = #doc]
macro_rules! #use_macro_ident {
( $P:ident ) => {
#ident {
#(
#(
#field_attrs
)*
#field_idents: $P.#field_types
),*
}
};
}
#macro_vis
}
.into()
}