Skip to content

Commit 563d61a

Browse files
committed
rust: macros: add foreach! macro to repeat code
Add a macro that can be used to repeat code. It allows to use the following syntax: foreach!(i in 0..=4) { paste! { struct [<Bit $i>]; } } This will be used by the Regmap abstraction in order to prevent redefining the same bit several times, hence prevent fields to overlap. Signed-off-by: Fabien Parent <[email protected]>
1 parent 84a8d2a commit 563d61a

File tree

2 files changed

+162
-0
lines changed

2 files changed

+162
-0
lines changed

rust/macros/foreach.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
use proc_macro::{
4+
Delimiter, TokenStream,
5+
TokenTree::{self, Group, Ident, Literal},
6+
};
7+
use std::ops::Range;
8+
9+
fn process_group(var: &str, i: isize, tokens: impl Iterator<Item = TokenTree>) -> Vec<TokenTree> {
10+
let mut tt = Vec::<TokenTree>::new();
11+
let mut tokens = tokens.peekable();
12+
13+
while let Some(token) = tokens.next() {
14+
match token {
15+
Group(ref group) => {
16+
let group_tokens = process_group(var, i, group.stream().into_iter());
17+
let stream = FromIterator::from_iter(group_tokens.into_iter());
18+
let new_group = proc_macro::Group::new(group.delimiter(), stream);
19+
tt.push(TokenTree::Group(new_group));
20+
}
21+
TokenTree::Punct(ref punct) => {
22+
if punct.to_string() == "$" {
23+
if let Some(TokenTree::Ident(ident)) = tokens.peek() {
24+
if ident.to_string() == var {
25+
tt.push(TokenTree::Literal(proc_macro::Literal::isize_unsuffixed(i)));
26+
tokens.next();
27+
continue;
28+
}
29+
}
30+
}
31+
32+
tt.push(token);
33+
}
34+
_ => tt.push(token),
35+
}
36+
}
37+
38+
tt
39+
}
40+
41+
pub(crate) fn expand(input: TokenStream) -> TokenStream {
42+
let mut tokens = input.into_iter().peekable();
43+
44+
let var = if let Some(Ident(i)) = tokens.next() {
45+
i.to_string()
46+
} else {
47+
panic!("foreach! first token should be an identifier");
48+
};
49+
50+
let token = tokens.next().expect("missing token, expecting ident 'in'");
51+
assert!(matches!(token, TokenTree::Ident(x) if x.to_string() == "in"));
52+
53+
let token = tokens
54+
.next()
55+
.expect("foreach!: missing token, expecting range");
56+
let token = if let Group(group) = token {
57+
group
58+
.stream()
59+
.into_iter()
60+
.next()
61+
.expect("foreach: missing token, expecting integer")
62+
} else {
63+
token
64+
};
65+
66+
let start = if let Literal(lit) = token {
67+
lit.to_string()
68+
.parse::<isize>()
69+
.expect("Failed to convert literal to isize")
70+
} else {
71+
panic!("foreach!: unexpected token '{token}'");
72+
};
73+
74+
let token = tokens
75+
.next()
76+
.expect("foreach!: missing token, expecting '.'");
77+
assert!(matches!(token, TokenTree::Punct(x) if x == '.'));
78+
let token = tokens
79+
.next()
80+
.expect("foreach!: missing token, expecting '.'");
81+
assert!(matches!(token, TokenTree::Punct(x) if x == '.'));
82+
83+
let is_inclusive_range = if let Some(TokenTree::Punct(p)) = tokens.peek() {
84+
if p.as_char() == '=' {
85+
tokens.next();
86+
true
87+
} else {
88+
false
89+
}
90+
} else {
91+
false
92+
};
93+
94+
let token = tokens
95+
.next()
96+
.expect("foreach!: missing token, expecting integer");
97+
let token = if let Group(group) = token {
98+
group
99+
.stream()
100+
.into_iter()
101+
.next()
102+
.expect("foreach: missing token, expecting integer")
103+
} else {
104+
token
105+
};
106+
107+
let end = if let Literal(lit) = token {
108+
lit.to_string()
109+
.parse::<isize>()
110+
.expect("Failed to convert literal to isize")
111+
} else {
112+
panic!("foreach!: unexpected token '{token}'");
113+
};
114+
let range = Range {
115+
start,
116+
end: end + if is_inclusive_range { 1 } else { 0 },
117+
};
118+
119+
let tokens = if let Some(Group(group)) = tokens.next() {
120+
if group.delimiter() != Delimiter::Brace {
121+
panic!("foreach! expected brace");
122+
}
123+
124+
group.stream().into_iter()
125+
} else {
126+
panic!("foreach! missing opening brace");
127+
};
128+
129+
let tokens: Vec<TokenTree> = tokens.collect();
130+
let mut tt = Vec::<TokenTree>::new();
131+
132+
for i in range {
133+
tt.extend_from_slice(&process_group(&var, i, tokens.clone().into_iter()));
134+
}
135+
136+
FromIterator::from_iter(tt)
137+
}

rust/macros/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#[macro_use]
1010
mod quote;
1111
mod concat_idents;
12+
mod foreach;
1213
mod helpers;
1314
mod module;
1415
mod paste;
@@ -412,6 +413,30 @@ pub fn paste(input: TokenStream) -> TokenStream {
412413
tokens.into_iter().collect()
413414
}
414415

416+
/// Repeat a fragment of code and provide a numerical index for the current repetition
417+
///
418+
/// # Examples
419+
///
420+
/// ```rust,ignore
421+
/// foreach!(i in 0..10) {
422+
/// paste! {
423+
/// fn [<func $i>]() {
424+
/// }
425+
/// }
426+
/// }
427+
///
428+
/// foreach!(i in 8..=15) {
429+
/// paste! {
430+
/// struct [<Bit $i>]() {
431+
/// }
432+
/// }
433+
/// }
434+
/// ```
435+
#[proc_macro]
436+
pub fn foreach(input: TokenStream) -> TokenStream {
437+
foreach::expand(input)
438+
}
439+
415440
/// Derives the [`Zeroable`] trait for the given struct.
416441
///
417442
/// This can only be used for structs where every field implements the [`Zeroable`] trait.

0 commit comments

Comments
 (0)