embedded_resources/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::{format_ident, quote, ToTokens};
4use syn::{Attribute, Ident, ItemStruct, ItemType, Meta, Type, Visibility};
5
6fn generate_alias_stmt(
7    vis: &Visibility,
8    alias_value: &impl ToTokens,
9    alias_type: &impl ToTokens,
10) -> ItemType {
11    syn::parse2(quote! { #vis type #alias_value = #alias_type; }).unwrap()
12}
13
14/// Mark a struct as a resource for extraction from the `Peripherals` instance.
15///
16/// # Example
17/// ```rust
18/// use embassy_stm32::peripherals::*;
19/// use embedded_resources::resource_group;
20///
21/// #[resource_group]
22/// pub(crate) struct UsbResources { // `pub(crate)` enables resources to be used across a project hierarchy
23///     dp: PA12, // type aliases are generated (`type Dp = PA12` in this case)
24///     dm: PA11,
25///     usb: USB,
26/// }
27///
28/// #[resource_group(no_aliases)] // only custom aliases are generated
29/// struct LedResources {
30///     r: PA2,
31///     g: PA3,
32///     b: PA4,
33///     #[alias = PWMTimer] // specify a custom alias for this resource
34///     tim2: TIM2,
35/// }
36///
37/// #[embassy_executor::task]
38/// async fn usb_task(r: UsbResources) {
39///     // use r.dp, r.dm, r.usb
40/// }
41///
42/// async fn setup_leds<'a>(r: LedResources) -> SimplePWM<'a, PWMTimer> {
43///     // setup three channel PWM (one for each color)       ^ alias used here
44/// }
45///
46/// #[embassy_executor::task]
47/// async fn led_task(rgb_pwm: SimplePWM<'a, PWMTimer>) {
48///     // use rgb_pwm                       ^ alias used here
49/// }
50///
51/// #[embassy_executor::main]
52/// async fn main(spawner: embassy_executor::Spawner) {
53///     let p = embassy_stm32::init(Default::default());
54///
55///     let rgb_pwm = setup_leds(led_resources!(p));
56///
57///     spawner.spawn(usb_task(usb_resources!(p))).unwrap();
58///     spawner.spawn(led_task(rgb_pwm)).unwrap();
59///
60///     // can still use p.PA0, p.PA1, etc
61/// }
62/// ```
63#[proc_macro_attribute]
64pub fn resource_group(args: TokenStream, item: TokenStream) -> TokenStream {
65    let mut s: ItemStruct = syn::parse2(item.into()).expect("Resource item must be a struct.");
66
67    let attr: Option<Ident> = syn::parse2(args.into()).unwrap();
68
69    let generate_aliases = match attr {
70        None => true,
71        Some(ident) => {
72            assert_eq!(
73                ident.to_string(),
74                "no_aliases",
75                "Expected identifier \"no_aliases\"."
76            );
77            false
78        }
79    };
80
81    let vis = s.vis.clone();
82
83    // propagate visibility from struct to fields
84    s.fields
85        .iter_mut()
86        .for_each(|field| field.vis = vis.clone());
87
88    let mut aliases = Vec::new();
89
90    // search for "alias" attribute and remove/record for rendering
91    s.fields.iter_mut().for_each(|field| {
92        let mut custom_alias = false;
93
94        field.attrs = field
95            .attrs
96            .iter()
97            .cloned()
98            .filter(|attr| {
99                if let Meta::NameValue(alias) = &attr.meta {
100                    if let Some(ident) = alias.path.get_ident() {
101                        if ident.to_string().eq("alias") {
102                            aliases.push(generate_alias_stmt(&vis, &alias.value, &field.ty));
103                            custom_alias = true;
104                            return false;
105                        }
106                    }
107                }
108
109                true
110            })
111            .collect();
112
113        if generate_aliases && !custom_alias {
114            aliases.push(generate_alias_stmt(
115                &vis,
116                &format_ident!(
117                    "{}",
118                    inflector::cases::classcase::to_class_case(
119                        field.ident.as_ref().unwrap().to_string().as_str()
120                    )
121                ),
122                &field.ty,
123            ));
124        }
125    });
126
127    let use_macro_ident = Ident::new(
128        inflector::cases::snakecase::to_snake_case(s.ident.to_string().as_str()).as_str(),
129        Span::call_site(),
130    );
131    let macro_vis = if let Visibility::Restricted(_) = vis {
132        Some(quote! { #vis use #use_macro_ident; })
133    } else {
134        None
135    };
136
137    let ident = &s.ident;
138    let field_idents: Vec<Ident> = s
139        .fields
140        .iter()
141        .cloned()
142        .map(|field| field.ident.unwrap())
143        .collect();
144    let field_types: Vec<Type> = s
145        .fields
146        .iter()
147        .cloned()
148        .map(|field| {
149            if let Type::Path(ty) = field.ty {
150                let ident = &ty.path.segments.last().unwrap().ident;
151                syn::parse2(quote! { #ident }).unwrap()
152            } else {
153                field.ty
154            }
155        })
156        .collect();
157    let field_attrs: Vec<Vec<Attribute>> =
158        s.fields.iter().cloned().map(|field| field.attrs).collect();
159    let doc = format!(
160        "Extract `{}` from a `Peripherals` instance.",
161        ident.to_string()
162    );
163
164    quote! {
165        #(
166            #aliases
167        )*
168
169        #s
170
171        #[doc = #doc]
172        macro_rules! #use_macro_ident {
173            ( $P:ident ) => {
174                #ident {
175                    #(
176                        #(
177                            #field_attrs
178                        )*
179                        #field_idents: $P.#field_types
180                    ),*
181                }
182            };
183        }
184
185        #macro_vis
186    }
187    .into()
188}