Skip to content

Commit 4814de3

Browse files
BennoLossinhoshinolina
authored andcommitted
rust: macros: refactor generics parsing of #[pin_data] into its own function
Other macros might also want to parse generics. Additionally this makes the code easier to read, as the next commit will introduce more code in `#[pin_data]`. Also add more comments to explain how parsing generics work. Signed-off-by: Benno Lossin <[email protected]>
1 parent e1c8704 commit 4814de3

2 files changed

Lines changed: 94 additions & 62 deletions

File tree

rust/macros/helpers.rs

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: GPL-2.0
22

3-
use proc_macro::{token_stream, Group, TokenTree};
3+
use proc_macro::{token_stream, Group, Punct, Spacing, TokenStream, TokenTree};
44

55
pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
66
if let Some(TokenTree::Ident(ident)) = it.next() {
@@ -89,3 +89,87 @@ pub(crate) fn get_string(it: &mut token_stream::IntoIter, expected_name: &str) -
8989
assert_eq!(expect_punct(it), ',');
9090
string
9191
}
92+
93+
pub(crate) struct Generics {
94+
pub(crate) impl_generics: Vec<TokenTree>,
95+
pub(crate) ty_generics: Vec<TokenTree>,
96+
}
97+
98+
/// Parses the given `TokenStream` into `Generics` and the rest.
99+
///
100+
/// The generics are not present in the rest, but a where clause might remain.
101+
pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec<TokenTree>) {
102+
// `impl_generics`, the declared generics with their bounds.
103+
let mut impl_generics = vec![];
104+
// Only the names of the generics, without any bounds.
105+
let mut ty_generics = vec![];
106+
// Tokens not related to the generics e.g. the `where` token and definition.
107+
let mut rest = vec![];
108+
// The current level of `<`.
109+
let mut nesting = 0;
110+
let mut toks = input.into_iter();
111+
// If we are at the beginning of a generic parameter.
112+
let mut at_start = true;
113+
for tt in &mut toks {
114+
match tt.clone() {
115+
TokenTree::Punct(p) if p.as_char() == '<' => {
116+
if nesting >= 1 {
117+
// This is inside of the generics and part of some bound.
118+
impl_generics.push(tt);
119+
}
120+
nesting += 1;
121+
}
122+
TokenTree::Punct(p) if p.as_char() == '>' => {
123+
// This is a parsing error, so we just end it here.
124+
if nesting == 0 {
125+
break;
126+
} else {
127+
nesting -= 1;
128+
if nesting >= 1 {
129+
// We are still inside of the generics and part of some bound.
130+
impl_generics.push(tt);
131+
}
132+
if nesting == 0 {
133+
break;
134+
}
135+
}
136+
}
137+
tt => {
138+
if nesting == 1 {
139+
// Here depending on the token, it might be a generic variable name.
140+
match &tt {
141+
// Ignore const.
142+
TokenTree::Ident(i) if i.to_string() == "const" => {}
143+
TokenTree::Ident(_) if at_start => {
144+
ty_generics.push(tt.clone());
145+
// We also already push the `,` token, this makes it easier to append
146+
// generics.
147+
ty_generics.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
148+
at_start = false;
149+
}
150+
TokenTree::Punct(p) if p.as_char() == ',' => at_start = true,
151+
// Lifetimes begin with `'`.
152+
TokenTree::Punct(p) if p.as_char() == '\'' && at_start => {
153+
ty_generics.push(tt.clone());
154+
}
155+
_ => {}
156+
}
157+
}
158+
if nesting >= 1 {
159+
impl_generics.push(tt);
160+
} else if nesting == 0 {
161+
// If we haven't entered the generics yet, we still want to keep these tokens.
162+
rest.push(tt);
163+
}
164+
}
165+
}
166+
}
167+
rest.extend(toks);
168+
(
169+
Generics {
170+
impl_generics,
171+
ty_generics,
172+
},
173+
rest,
174+
)
175+
}

rust/macros/pin_data.rs

Lines changed: 9 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,19 @@
11
// SPDX-License-Identifier: Apache-2.0 OR MIT
22

3-
use proc_macro::{Punct, Spacing, TokenStream, TokenTree};
3+
use crate::helpers::{parse_generics, Generics};
4+
use proc_macro::TokenStream;
45

56
pub(crate) fn pin_data(args: TokenStream, input: TokenStream) -> TokenStream {
67
// This proc-macro only does some pre-parsing and then delegates the actual parsing to
78
// `kernel::__pin_data!`.
8-
//
9-
// In here we only collect the generics, since parsing them in declarative macros is very
10-
// elaborate. We also do not need to analyse their structure, we only need to collect them.
119

12-
// `impl_generics`, the declared generics with their bounds.
13-
let mut impl_generics = vec![];
14-
// Only the names of the generics, without any bounds.
15-
let mut ty_generics = vec![];
16-
// Tokens not related to the generics e.g. the `impl` token.
17-
let mut rest = vec![];
18-
// The current level of `<`.
19-
let mut nesting = 0;
20-
let mut toks = input.into_iter();
21-
// If we are at the beginning of a generic parameter.
22-
let mut at_start = true;
23-
for tt in &mut toks {
24-
match tt.clone() {
25-
TokenTree::Punct(p) if p.as_char() == '<' => {
26-
if nesting >= 1 {
27-
impl_generics.push(tt);
28-
}
29-
nesting += 1;
30-
}
31-
TokenTree::Punct(p) if p.as_char() == '>' => {
32-
if nesting == 0 {
33-
break;
34-
} else {
35-
nesting -= 1;
36-
if nesting >= 1 {
37-
impl_generics.push(tt);
38-
}
39-
if nesting == 0 {
40-
break;
41-
}
42-
}
43-
}
44-
tt => {
45-
if nesting == 1 {
46-
match &tt {
47-
TokenTree::Ident(i) if i.to_string() == "const" => {}
48-
TokenTree::Ident(_) if at_start => {
49-
ty_generics.push(tt.clone());
50-
ty_generics.push(TokenTree::Punct(Punct::new(',', Spacing::Alone)));
51-
at_start = false;
52-
}
53-
TokenTree::Punct(p) if p.as_char() == ',' => at_start = true,
54-
TokenTree::Punct(p) if p.as_char() == '\'' && at_start => {
55-
ty_generics.push(tt.clone());
56-
}
57-
_ => {}
58-
}
59-
}
60-
if nesting >= 1 {
61-
impl_generics.push(tt);
62-
} else if nesting == 0 {
63-
rest.push(tt);
64-
}
65-
}
66-
}
67-
}
68-
rest.extend(toks);
10+
let (
11+
Generics {
12+
impl_generics,
13+
ty_generics,
14+
},
15+
mut rest,
16+
) = parse_generics(input);
6917
// This should be the body of the struct `{...}`.
7018
let last = rest.pop();
7119
quote!(::kernel::__pin_data! {

0 commit comments

Comments
 (0)