atsamd_hal_macros/
lib.rs

1//! This crate contains proc-macros to be used by the `atsamd-hal` crate. It is
2//! not intended to be used outside this crate, and no stability guarantees are
3//! made.
4//!
5//! The main purpose of this crate is to separate the task of writing the code
6//! to support peripherals of the atsamd families from the task of figuring out
7//! which specific devices has those peripherals.
8//!
9//! The actual mapping of devices to peripherals is specified in the
10//! `devices.yaml` file. In the `atsamd-hal` crate you then only need to care
11//! about the peripherals themselves (and their different variants).
12//!
13//! To use the macros in this crate, you need to specify a **peripheral
14//! expression**, which can be one of the following:
15//!
16//! - A peripheral from `devices.yaml` in the form of a string. Examples:
17//!   `"serial-numbers"` or `"sercom3"`.
18//! - A peripheral from `devices.yaml` suffixed with the device family.
19//!   Examples: `"serial-numbers-d11"` or `"sercom3-d5x"`
20//! - A pin from `devices.yaml`. Examples: `"pb22"`.
21//! - An expression of the form `any([peripheral expression], ...)`. Example:
22//!   `any("pm-d11", "pm-d21", "rstc-d5x")`.
23//! - An expression of the form `all([peripheral expression], ...)`. Example:
24//!   `all("tc4", "tc5")`.
25//! - An expression of the form `not([peripheral expression])`. Example:
26//!   `not("dmac-d5x")`.
27
28use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
29
30mod error;
31mod generation;
32mod parsing;
33
34use error::Error;
35use generation::{add_cfgs_to_input, cfg_args, gen_cfgs, hal_expr_to_devices};
36use parsing::{eat_attribute, eat_eof, eat_group, eat_hal_expr, eat_operator, eat_string_literal};
37
38/// Attribute macro which expands to a suitable `#[cfg(...)]` expression.
39///
40/// It can be used like `#[hal_cfg([peripheral expression])]`.
41///
42/// The macro will look up all devices that fulfill the expression and expand
43/// the macro into a cfg attribute of the form `#[cfg(any(feature = "device1",
44/// feature = "device2", ...)]`.
45#[proc_macro_attribute]
46pub fn hal_cfg(args: TokenStream, input: TokenStream) -> TokenStream {
47    hal_cfg_impl(args).map_or_else(
48        |e| e.to_compile_error("hal_cfg"),
49        |cfgs| add_cfgs_to_input(cfgs, input),
50    )
51}
52
53fn hal_cfg_impl(args: TokenStream) -> Result<Group, Error> {
54    let mut args = args.into_iter().peekable();
55    let expr = eat_hal_expr(&mut args)?;
56    if args.peek().is_some() {
57        eat_operator(",", &mut args)?;
58    }
59    eat_eof(&mut args)?;
60    let cfgs = gen_cfgs(&expr)?;
61    Ok(cfgs)
62}
63
64/// Macro which expands to a `mod foo;` item with different paths for each
65/// device.
66///
67/// It can be used like this:
68///
69/// ```ignore
70/// #[hal_module(
71///     any("nvmctrl-d11", "nvmctrl-d21") => "calibration/d11.rs",
72///     "nvmctrl-d5x" => "calibration/d5x.rs",
73/// )]
74/// pub mod calibration {}
75///
76/// #[hal_module("aes")]
77/// pub mod aes {}
78/// ```
79///
80/// This will then expand to something of the form:
81/// ```ignore
82/// #[cfg(any(feature = "samd11c", ...))]
83/// #[path = "calibration/d11.rs"]
84/// pub mod calibration;
85///
86/// #[cfg(any(feature = "samd51g", ...))]
87/// #[path = "calibration/d5x.rs"]
88/// pub mod calibration;
89///
90/// #[cfg(any(feature = "samd51g", ...))]
91/// pub mod aes;
92/// ```
93///
94/// Ideally you would be to write `pub mod calibration;` instead of
95/// `pub mod calibration {}`, but unfortunately non-inline modules are not
96/// currently supposed in proc macros. See
97/// [rust#54727](https://github.com/rust-lang/rust/issues/54727) for details.
98#[proc_macro_attribute]
99pub fn hal_module(args: TokenStream, input: TokenStream) -> TokenStream {
100    hal_module_impl(args, input).unwrap_or_else(|e| e.to_compile_error("hal_module"))
101}
102
103fn hal_module_impl(args: TokenStream, input: TokenStream) -> Result<TokenStream, Error> {
104    let mut args = args.into_iter().peekable();
105    let args = &mut args;
106
107    let input = input
108        .into_iter()
109        .map(|token| {
110            // Replace `{}` with `;`
111            if let TokenTree::Group(g) = &token {
112                if g.delimiter() == Delimiter::Brace && g.stream().into_iter().count() == 0 {
113                    return TokenTree::Punct(Punct::new(';', Spacing::Alone));
114                }
115            }
116            token
117        })
118        .collect::<Vec<_>>();
119
120    let mut out = TokenStream::new();
121
122    while args.peek().is_some() {
123        let hal_expr = eat_hal_expr(args)?;
124
125        out.extend([
126            TokenTree::Punct(Punct::new('#', Spacing::Alone)),
127            TokenTree::Group(gen_cfgs(&hal_expr)?),
128        ]);
129
130        if matches!(args.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '=') {
131            eat_operator("=>", args)?;
132            let path = eat_string_literal(args)?;
133            out.extend([
134                TokenTree::Punct(Punct::new('#', Spacing::Alone)),
135                TokenTree::Group(Group::new(
136                    Delimiter::Bracket,
137                    TokenStream::from_iter([
138                        TokenTree::Ident(Ident::new("path", Span::call_site())),
139                        TokenTree::Punct(Punct::new('=', Spacing::Alone)),
140                        TokenTree::Literal(Literal::string(&path)),
141                    ]),
142                )),
143            ]);
144        }
145
146        out.extend(input.iter().cloned());
147
148        if args.peek().is_some() {
149            eat_operator(",", args)?;
150        }
151    }
152    eat_eof(args)?;
153
154    Ok(out)
155}
156
157/// Helper macro to allow using `#[hal_cfg(..)]` macro in more places
158///
159/// Normally the `#[cfg(..)]` macro is allowed in many more places than
160/// proc-macros, such as directly on a statement. This mitigates that
161/// restriction.
162///
163/// It can be used like this:
164///
165/// ```ignore
166/// #[hal_macro_helper]
167/// struct MyStruct {
168///     #[hal_cfg("sercom0")]
169///     my_field: String
170/// }
171/// ```
172///
173/// This works, because attributes are allowed on the outer item.
174///
175/// The `#[hal_macro_helper]` will search through the item and replace all
176/// instances of the `#[hal_cfg(..)]` attribute with the corresponding
177/// `#[cfg(..)]` attribute instead. This way the inner attributes are
178/// technically not interpreted as a proc-macro at all.
179#[proc_macro_attribute]
180pub fn hal_macro_helper(_args: TokenStream, input: TokenStream) -> TokenStream {
181    hal_macro_helper_impl(input).unwrap_or_else(|e| e.to_compile_error("hal_macro_helper"))
182}
183
184fn hal_macro_helper_impl(input: TokenStream) -> Result<TokenStream, Error> {
185    let input = input.into_iter();
186    let mut out = Vec::with_capacity(input.size_hint().0);
187    let mut last_was_pound = false;
188
189    for mut arg in input {
190        let saved_last_was_pound = std::mem::replace(&mut last_was_pound, false);
191        match &mut arg {
192            TokenTree::Group(group) => {
193                if saved_last_was_pound {
194                    replace_inner_macros(group)?;
195                } else {
196                    let mut new_group =
197                        Group::new(group.delimiter(), hal_macro_helper_impl(group.stream())?);
198                    new_group.set_span(group.span());
199                    *group = new_group;
200                }
201            }
202            TokenTree::Punct(p) if p.as_char() == '#' && p.spacing() == Spacing::Alone => {
203                last_was_pound = true;
204            }
205            _ => (),
206        }
207        out.push(arg);
208    }
209    Ok(out.into_iter().collect())
210}
211
212fn replace_inner_macros(group: &mut Group) -> Result<(), Error> {
213    let mut tokens = group.stream().into_iter();
214    let Some(TokenTree::Ident(func)) = tokens.next() else {
215        return Ok(());
216    };
217
218    if func.to_string().as_str() != "hal_cfg" {
219        return Ok(());
220    }
221
222    let Some(TokenTree::Group(inner_group)) = tokens.next() else {
223        return Ok(());
224    };
225
226    if inner_group.delimiter() != Delimiter::Parenthesis {
227        return Ok(());
228    }
229
230    if tokens.next().is_some() {
231        return Ok(());
232    }
233
234    let mut new_group = hal_cfg_impl(inner_group.stream())?;
235    new_group.set_span(group.span());
236    *group = new_group;
237
238    Ok(())
239}
240
241/// Helper macro to make conditional docs work nicer
242///
243/// Can be used like this:
244///
245/// ```ignore
246/// #[hal_docs(
247///     {
248///         /// Example struct!
249///         ///
250///         /// It can do things!
251///     }
252///     "usb" => {
253///         ///
254///         /// It also supports usb on this device.
255///     }
256/// )]
257/// pub struct ExampleStruct;
258/// ```
259#[proc_macro_attribute]
260pub fn hal_docs(args: TokenStream, input: TokenStream) -> TokenStream {
261    hal_docs_impl(args, input).unwrap_or_else(|e| e.to_compile_error("hal_docs"))
262}
263
264fn hal_docs_impl(args: TokenStream, input: TokenStream) -> Result<TokenStream, Error> {
265    let mut args = args.into_iter().peekable();
266    let args = &mut args;
267
268    let mut out = TokenStream::new();
269
270    while args.peek().is_some() {
271        let mut attribute_formatter = None;
272
273        if !matches!(args.peek(), Some(TokenTree::Group(_))) {
274            let expr = eat_hal_expr(args)?;
275            eat_operator("=>", args)?;
276
277            let mut cfg_args = cfg_args(hal_expr_to_devices(&expr)?);
278            cfg_args.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
279            attribute_formatter = Some(move |attribute_body: TokenStream| {
280                TokenStream::from_iter([
281                    TokenTree::Ident(Ident::new("cfg_attr", Span::call_site())),
282                    TokenTree::Group(Group::new(
283                        Delimiter::Parenthesis,
284                        TokenStream::from_iter(cfg_args.iter().cloned().chain(attribute_body)),
285                    )),
286                ])
287            });
288        }
289
290        let group = eat_group(Delimiter::Brace, args)?;
291        let mut items = group.stream().into_iter().peekable();
292
293        while items.peek().is_some() {
294            let mut attribute = eat_attribute(&mut items)?;
295            if let Some(attribute_formatter) = &mut attribute_formatter {
296                attribute.inner_stream = attribute_formatter(attribute.inner_stream);
297            }
298            out.extend([
299                attribute.pound,
300                TokenTree::Group(Group::new(Delimiter::Bracket, attribute.inner_stream)),
301            ])
302        }
303    }
304    eat_eof(args)?;
305
306    out.extend(input);
307
308    Ok(out)
309}