embedded_resources/
lib.rs

1use 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/// Mark a struct as a resource for extraction from the `Peripherals` instance.
18///
19/// # Example
20/// ```rust
21/// use embassy_stm32::peripherals::*;
22/// use embedded_resources::resource_group;
23///
24/// #[resource_group]
25/// pub(crate) struct UsbResources { // `pub(crate)` enables resources to be used across a project hierarchy
26///     dp: PA12, // type aliases are generated (`type Dp = PA12` in this case)
27///     dm: PA11,
28///     usb: USB,
29/// }
30///
31/// #[resource_group(no_aliases)] // only custom aliases are generated
32/// struct LedResources {
33///     r: PA2,
34///     g: PA3,
35///     b: PA4,
36///     #[alias = PWMTimer] // specify a custom alias for this resource
37///     tim2: TIM2,
38/// }
39///
40/// #[embassy_executor::task]
41/// async fn usb_task(r: UsbResources) {
42///     // use r.dp, r.dm, r.usb
43/// }
44///
45/// async fn setup_leds<'a>(r: LedResources) -> SimplePWM<'a, PWMTimer> {
46///     // setup three channel PWM (one for each color)       ^ alias used here
47/// }
48///
49/// #[embassy_executor::task]
50/// async fn led_task(rgb_pwm: SimplePWM<'a, PWMTimer>) {
51///     // use rgb_pwm                       ^ alias used here
52/// }
53///
54/// #[embassy_executor::main]
55/// async fn main(spawner: embassy_executor::Spawner) {
56///     let p = embassy_stm32::init(Default::default());
57///
58///     let rgb_pwm = setup_leds(led_resources!(p));
59///
60///     spawner.spawn(usb_task(usb_resources!(p))).unwrap();
61///     spawner.spawn(led_task(rgb_pwm)).unwrap();
62///
63///     // can still use p.PA0, p.PA1, etc
64/// }
65/// ```
66#[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    // propagate visibility from struct to fields
87    s.fields
88        .iter_mut()
89        .for_each(|field| field.vis = vis.clone());
90
91    let mut aliases = Vec::new();
92
93    // search for "alias" attribute and remove/record for rendering
94    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}