embedded_resources/
lib.rs1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::{format_ident, quote, ToTokens};
4use syn::{
5 parse_quote, Attribute, Ident, ItemStruct, ItemType, Meta, Path, PathArguments, Type,
6 Visibility,
7};
8
9fn generate_alias_stmt(
10 vis: &Visibility,
11 alias_value: &impl ToTokens,
12 alias_type: &impl ToTokens,
13) -> ItemType {
14 syn::parse2(quote! { #vis type #alias_value = #alias_type; }).unwrap()
15}
16
17#[proc_macro_attribute]
67pub fn resource_group(args: TokenStream, item: TokenStream) -> TokenStream {
68 let mut s: ItemStruct = syn::parse2(item.into()).expect("Resource item must be a struct.");
69
70 let attr: Option<Ident> = syn::parse2(args.into()).unwrap();
71
72 let generate_aliases = match attr {
73 None => true,
74 Some(ident) => {
75 assert_eq!(
76 ident.to_string(),
77 "no_aliases",
78 "Expected identifier \"no_aliases\"."
79 );
80 false
81 }
82 };
83
84 let vis = s.vis.clone();
85
86 s.fields
88 .iter_mut()
89 .for_each(|field| field.vis = vis.clone());
90
91 let mut aliases = Vec::new();
92
93 s.fields.iter_mut().for_each(|field| {
95 let mut custom_alias = false;
96
97 field.attrs = field
98 .attrs
99 .iter()
100 .filter(|attr| {
101 if let Meta::NameValue(alias) = &attr.meta {
102 if let Some(ident) = alias.path.get_ident() {
103 if ident.to_string().eq("alias") {
104 aliases.push(generate_alias_stmt(&vis, &alias.value, &field.ty));
105 custom_alias = true;
106 return false;
107 }
108 }
109 }
110
111 true
112 })
113 .cloned()
114 .collect();
115
116 if generate_aliases && !custom_alias {
117 aliases.push(generate_alias_stmt(
118 &vis,
119 &format_ident!(
120 "{}",
121 inflector::cases::classcase::to_class_case(
122 field.ident.as_ref().unwrap().to_string().as_str()
123 )
124 ),
125 &field.ty,
126 ));
127 }
128 });
129
130 let use_macro_ident = Ident::new(
131 inflector::cases::snakecase::to_snake_case(s.ident.to_string().as_str()).as_str(),
132 Span::call_site(),
133 );
134 let macro_vis = if let Visibility::Restricted(_) = vis {
135 Some(quote! { #vis use #use_macro_ident; })
136 } else {
137 None
138 };
139
140 let ident = &s.ident;
141 let field_idents: Vec<Ident> = s
142 .fields
143 .iter()
144 .cloned()
145 .map(|field| field.ident.unwrap())
146 .collect();
147 let field_types: Vec<Type> = s
148 .fields
149 .iter()
150 .cloned()
151 .map(|field| {
152 if let Type::Path(ref ty) = field.ty {
153 let seg = &ty.path.segments.last().unwrap();
154 match &seg.arguments {
155 PathArguments::None => {
156 let ident = &seg.ident;
157 syn::parse2(quote! { #ident }).unwrap()
158 }
159 PathArguments::AngleBracketed(generic_args) => {
160 let ident = generic_args.args.last().unwrap();
161 syn::parse2(quote! { #ident }).unwrap()
162 }
163 PathArguments::Parenthesized(_) => todo!(),
164 }
165 } else {
166 field.ty
167 }
168 })
169 .collect();
170 let field_attrs: Vec<Vec<Attribute>> =
171 s.fields.iter().cloned().map(|field| field.attrs).collect();
172 let doc = format!("Extract `{}` from a `Peripherals` instance.", ident);
173
174 let peri_path: Path = if cfg!(feature = "stm32") {
175 parse_quote! { ::embassy_stm32::Peri }
176 } else if cfg!(feature = "nrf") {
177 parse_quote! { ::embassy_nrf::Peri }
178 } else if cfg!(feature = "_test") {
179 parse_quote! { Peri }
180 } else {
181 return syn::Error::new(
182 Span::call_site(),
183 "Exactly one ecosystem feature must be specified.",
184 )
185 .to_compile_error()
186 .into();
187 };
188
189 s.fields.iter_mut().for_each(|field| {
190 let ty = &field.ty;
191 field.ty = parse_quote! { #peri_path<'static, #ty> };
192 });
193
194 quote! {
195 #(
196 #aliases
197 )*
198
199 #s
200
201 #[doc = #doc]
202 macro_rules! #use_macro_ident {
203 ( $P:ident ) => {
204 #ident {
205 #(
206 #(
207 #field_attrs
208 )*
209 #field_idents: $P.#field_types
210 ),*
211 }
212 };
213 }
214
215 #macro_vis
216 }
217 .into()
218}