Skip to content

Commit 823d473

Browse files
nbdd0121ojeda
authored andcommitted
rust: macros: add paste! proc macro
This macro provides a flexible way to concatenated identifiers together and it allows the resulting identifier to be used to declare new items, which `concat_idents!` does not allow. It also allows identifiers to be transformed before concatenated. The `concat_idents!` example let x_1 = 42; let x_2 = concat_idents!(x, _1); assert!(x_1 == x_2); can be written with `paste!` macro like this: let x_1 = 42; let x_2 = paste!([<x _1>]); assert!(x_1 == x_2); However `paste!` macro is more flexible because it can be used to create a new variable: let x_1 = 42; paste!(let [<x _2>] = [<x _1>];); assert!(x_1 == x_2); While this is not possible with `concat_idents!`. This macro is similar to the `paste!` crate [1], but this is a fresh implementation to avoid vendoring large amount of code directly. Also, I have augmented it to provide a way to specify span of the resulting token, allowing precise control. For example, this code is broken because the variable is declared inside the macro, so Rust macro hygiene rules prevents access from the outside: macro_rules! m { ($id: ident) => { // The resulting token has hygiene of the macro. paste!(let [<$id>] = 1;) } } m!(a); let _ = a; In this version of `paste!` macro I added a `span` modifier to allow this: macro_rules! m { ($id: ident) => { // The resulting token has hygiene of `$id`. paste!(let [<$id:span>] = 1;) } } m!(a); let _ = a; Link: http://docs.rs/paste/ [1] Signed-off-by: Gary Guo <[email protected]> Reviewed-by: Björn Roy Baron <[email protected]> Reviewed-by: Benno Lossin <[email protected]> Reviewed-by: Alice Ryhl <[email protected]> Reviewed-by: Martin Rodriguez Reboredo <[email protected]> Link: https://lore.kernel.org/r/[email protected] [ Added SPDX license identifier as discussed in the list and fixed typo. ] Signed-off-by: Miguel Ojeda <[email protected]>
1 parent 0b4e3b6 commit 823d473

File tree

2 files changed

+193
-0
lines changed

2 files changed

+193
-0
lines changed

rust/macros/lib.rs

+97
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod quote;
77
mod concat_idents;
88
mod helpers;
99
mod module;
10+
mod paste;
1011
mod pin_data;
1112
mod pinned_drop;
1213
mod vtable;
@@ -246,3 +247,99 @@ pub fn pin_data(inner: TokenStream, item: TokenStream) -> TokenStream {
246247
pub fn pinned_drop(args: TokenStream, input: TokenStream) -> TokenStream {
247248
pinned_drop::pinned_drop(args, input)
248249
}
250+
251+
/// Paste identifiers together.
252+
///
253+
/// Within the `paste!` macro, identifiers inside `[<` and `>]` are concatenated together to form a
254+
/// single identifier.
255+
///
256+
/// This is similar to the [`paste`] crate, but with pasting feature limited to identifiers
257+
/// (literals, lifetimes and documentation strings are not supported). There is a difference in
258+
/// supported modifiers as well.
259+
///
260+
/// # Example
261+
///
262+
/// ```ignore
263+
/// use kernel::macro::paste;
264+
///
265+
/// macro_rules! pub_no_prefix {
266+
/// ($prefix:ident, $($newname:ident),+) => {
267+
/// paste! {
268+
/// $(pub(crate) const $newname: u32 = [<$prefix $newname>];)+
269+
/// }
270+
/// };
271+
/// }
272+
///
273+
/// pub_no_prefix!(
274+
/// binder_driver_return_protocol_,
275+
/// BR_OK,
276+
/// BR_ERROR,
277+
/// BR_TRANSACTION,
278+
/// BR_REPLY,
279+
/// BR_DEAD_REPLY,
280+
/// BR_TRANSACTION_COMPLETE,
281+
/// BR_INCREFS,
282+
/// BR_ACQUIRE,
283+
/// BR_RELEASE,
284+
/// BR_DECREFS,
285+
/// BR_NOOP,
286+
/// BR_SPAWN_LOOPER,
287+
/// BR_DEAD_BINDER,
288+
/// BR_CLEAR_DEATH_NOTIFICATION_DONE,
289+
/// BR_FAILED_REPLY
290+
/// );
291+
///
292+
/// assert_eq!(BR_OK, binder_driver_return_protocol_BR_OK);
293+
/// ```
294+
///
295+
/// # Modifiers
296+
///
297+
/// For each identifier, it is possible to attach one or multiple modifiers to
298+
/// it.
299+
///
300+
/// Currently supported modifiers are:
301+
/// * `span`: change the span of concatenated identifier to the span of the specified token. By
302+
/// default the span of the `[< >]` group is used.
303+
/// * `lower`: change the identifier to lower case.
304+
/// * `upper`: change the identifier to upper case.
305+
///
306+
/// ```ignore
307+
/// use kernel::macro::paste;
308+
///
309+
/// macro_rules! pub_no_prefix {
310+
/// ($prefix:ident, $($newname:ident),+) => {
311+
/// kernel::macros::paste! {
312+
/// $(pub(crate) const fn [<$newname:lower:span>]: u32 = [<$prefix $newname:span>];)+
313+
/// }
314+
/// };
315+
/// }
316+
///
317+
/// pub_no_prefix!(
318+
/// binder_driver_return_protocol_,
319+
/// BR_OK,
320+
/// BR_ERROR,
321+
/// BR_TRANSACTION,
322+
/// BR_REPLY,
323+
/// BR_DEAD_REPLY,
324+
/// BR_TRANSACTION_COMPLETE,
325+
/// BR_INCREFS,
326+
/// BR_ACQUIRE,
327+
/// BR_RELEASE,
328+
/// BR_DECREFS,
329+
/// BR_NOOP,
330+
/// BR_SPAWN_LOOPER,
331+
/// BR_DEAD_BINDER,
332+
/// BR_CLEAR_DEATH_NOTIFICATION_DONE,
333+
/// BR_FAILED_REPLY
334+
/// );
335+
///
336+
/// assert_eq!(br_ok(), binder_driver_return_protocol_BR_OK);
337+
/// ```
338+
///
339+
/// [`paste`]: https://docs.rs/paste/
340+
#[proc_macro]
341+
pub fn paste(input: TokenStream) -> TokenStream {
342+
let mut tokens = input.into_iter().collect();
343+
paste::expand(&mut tokens);
344+
tokens.into_iter().collect()
345+
}

rust/macros/paste.rs

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
use proc_macro::{Delimiter, Group, Ident, Spacing, Span, TokenTree};
4+
5+
fn concat(tokens: &[TokenTree], group_span: Span) -> TokenTree {
6+
let mut tokens = tokens.iter();
7+
let mut segments = Vec::new();
8+
let mut span = None;
9+
loop {
10+
match tokens.next() {
11+
None => break,
12+
Some(TokenTree::Literal(lit)) => segments.push((lit.to_string(), lit.span())),
13+
Some(TokenTree::Ident(ident)) => {
14+
let mut value = ident.to_string();
15+
if value.starts_with("r#") {
16+
value.replace_range(0..2, "");
17+
}
18+
segments.push((value, ident.span()));
19+
}
20+
Some(TokenTree::Punct(p)) if p.as_char() == ':' => {
21+
let Some(TokenTree::Ident(ident)) = tokens.next() else {
22+
panic!("expected identifier as modifier");
23+
};
24+
25+
let (mut value, sp) = segments.pop().expect("expected identifier before modifier");
26+
match ident.to_string().as_str() {
27+
// Set the overall span of concatenated token as current span
28+
"span" => {
29+
assert!(
30+
span.is_none(),
31+
"span modifier should only appear at most once"
32+
);
33+
span = Some(sp);
34+
}
35+
"lower" => value = value.to_lowercase(),
36+
"upper" => value = value.to_uppercase(),
37+
v => panic!("unknown modifier `{v}`"),
38+
};
39+
segments.push((value, sp));
40+
}
41+
_ => panic!("unexpected token in paste segments"),
42+
};
43+
}
44+
45+
let pasted: String = segments.into_iter().map(|x| x.0).collect();
46+
TokenTree::Ident(Ident::new(&pasted, span.unwrap_or(group_span)))
47+
}
48+
49+
pub(crate) fn expand(tokens: &mut Vec<TokenTree>) {
50+
for token in tokens.iter_mut() {
51+
if let TokenTree::Group(group) = token {
52+
let delimiter = group.delimiter();
53+
let span = group.span();
54+
let mut stream: Vec<_> = group.stream().into_iter().collect();
55+
// Find groups that looks like `[< A B C D >]`
56+
if delimiter == Delimiter::Bracket
57+
&& stream.len() >= 3
58+
&& matches!(&stream[0], TokenTree::Punct(p) if p.as_char() == '<')
59+
&& matches!(&stream[stream.len() - 1], TokenTree::Punct(p) if p.as_char() == '>')
60+
{
61+
// Replace the group with concatenated token
62+
*token = concat(&stream[1..stream.len() - 1], span);
63+
} else {
64+
// Recursively expand tokens inside the group
65+
expand(&mut stream);
66+
let mut group = Group::new(delimiter, stream.into_iter().collect());
67+
group.set_span(span);
68+
*token = TokenTree::Group(group);
69+
}
70+
}
71+
}
72+
73+
// Path segments cannot contain invisible delimiter group, so remove them if any.
74+
for i in (0..tokens.len().saturating_sub(3)).rev() {
75+
// Looking for a double colon
76+
if matches!(
77+
(&tokens[i + 1], &tokens[i + 2]),
78+
(TokenTree::Punct(a), TokenTree::Punct(b))
79+
if a.as_char() == ':' && a.spacing() == Spacing::Joint && b.as_char() == ':'
80+
) {
81+
match &tokens[i + 3] {
82+
TokenTree::Group(group) if group.delimiter() == Delimiter::None => {
83+
tokens.splice(i + 3..i + 4, group.stream());
84+
}
85+
_ => (),
86+
}
87+
88+
match &tokens[i] {
89+
TokenTree::Group(group) if group.delimiter() == Delimiter::None => {
90+
tokens.splice(i..i + 1, group.stream());
91+
}
92+
_ => (),
93+
}
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)