Skip to content

Commit b30ade6

Browse files
nbdd0121BennoLossin
authored andcommitted
rust: macros: use syn to parse module! macro
With `syn` being available in the kernel, use it to parse the complex custom `module!` macro to replace existing helpers. Only parsing is changed in this commit, the code generation is untouched. This has the benefit of better error message when the macro is used incorrectly, as it can point to a concrete span on what's going wrong. For example, if a field is specified twice, previously it reads: error: proc macro panicked --> samples/rust/rust_minimal.rs:7:1 | 7 | / module! { 8 | | type: RustMinimal, 9 | | name: "rust_minimal", 10 | | author: "Rust for Linux Contributors", 11 | | description: "Rust minimal sample", 12 | | license: "GPL", 13 | | license: "GPL", 14 | | } | |_^ | = help: message: Duplicated key "license". Keys can only be specified once. now it reads: error: duplicated key "license". Keys can only be specified once. --> samples/rust/rust_minimal.rs:13:5 | 13 | license: "GPL", | ^^^^^^^ Reviewed-by: Tamir Duberstein <tamird@gmail.com> Signed-off-by: Gary Guo <gary@garyguo.net>
1 parent ecb3287 commit b30ade6

3 files changed

Lines changed: 280 additions & 234 deletions

File tree

rust/macros/helpers.rs

Lines changed: 32 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,21 @@
11
// SPDX-License-Identifier: GPL-2.0
22

3-
use proc_macro2::{token_stream, Group, Ident, TokenStream, TokenTree};
4-
5-
pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
6-
if let Some(TokenTree::Ident(ident)) = it.next() {
7-
Some(ident.to_string())
8-
} else {
9-
None
10-
}
11-
}
12-
13-
pub(crate) fn try_sign(it: &mut token_stream::IntoIter) -> Option<char> {
14-
let peek = it.clone().next();
15-
match peek {
16-
Some(TokenTree::Punct(punct)) if punct.as_char() == '-' => {
17-
let _ = it.next();
18-
Some(punct.as_char())
19-
}
20-
_ => None,
21-
}
22-
}
23-
24-
pub(crate) fn try_literal(it: &mut token_stream::IntoIter) -> Option<String> {
25-
if let Some(TokenTree::Literal(literal)) = it.next() {
26-
Some(literal.to_string())
27-
} else {
28-
None
29-
}
30-
}
31-
32-
pub(crate) fn try_string(it: &mut token_stream::IntoIter) -> Option<String> {
33-
try_literal(it).and_then(|string| {
34-
if string.starts_with('\"') && string.ends_with('\"') {
35-
let content = &string[1..string.len() - 1];
36-
if content.contains('\\') {
37-
panic!("Escape sequences in string literals not yet handled");
38-
}
39-
Some(content.to_string())
40-
} else if string.starts_with("r\"") {
41-
panic!("Raw string literals are not yet handled");
42-
} else {
43-
None
44-
}
45-
})
46-
}
47-
48-
pub(crate) fn expect_ident(it: &mut token_stream::IntoIter) -> String {
49-
try_ident(it).expect("Expected Ident")
50-
}
3+
use proc_macro2::{
4+
token_stream,
5+
Ident,
6+
TokenStream,
7+
TokenTree, //
8+
};
9+
use quote::ToTokens;
10+
use syn::{
11+
parse::{
12+
Parse,
13+
ParseStream, //
14+
},
15+
Error,
16+
LitStr,
17+
Result, //
18+
};
5119

5220
pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
5321
if let TokenTree::Punct(punct) = it.next().expect("Reached end of token stream for Punct") {
@@ -57,27 +25,28 @@ pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
5725
}
5826
}
5927

60-
pub(crate) fn expect_string(it: &mut token_stream::IntoIter) -> String {
61-
try_string(it).expect("Expected string")
62-
}
28+
/// A string literal that is required to have ASCII value only.
29+
pub(crate) struct AsciiLitStr(LitStr);
6330

64-
pub(crate) fn expect_string_ascii(it: &mut token_stream::IntoIter) -> String {
65-
let string = try_string(it).expect("Expected string");
66-
assert!(string.is_ascii(), "Expected ASCII string");
67-
string
31+
impl Parse for AsciiLitStr {
32+
fn parse(input: ParseStream<'_>) -> Result<Self> {
33+
let s: LitStr = input.parse()?;
34+
if !s.value().is_ascii() {
35+
return Err(Error::new_spanned(s, "expected ASCII-only string literal"));
36+
}
37+
Ok(Self(s))
38+
}
6839
}
6940

70-
pub(crate) fn expect_group(it: &mut token_stream::IntoIter) -> Group {
71-
if let TokenTree::Group(group) = it.next().expect("Reached end of token stream for Group") {
72-
group
73-
} else {
74-
panic!("Expected Group");
41+
impl ToTokens for AsciiLitStr {
42+
fn to_tokens(&self, ts: &mut TokenStream) {
43+
self.0.to_tokens(ts);
7544
}
7645
}
7746

78-
pub(crate) fn expect_end(it: &mut token_stream::IntoIter) {
79-
if it.next().is_some() {
80-
panic!("Expected end");
47+
impl AsciiLitStr {
48+
pub(crate) fn value(&self) -> String {
49+
self.0.value()
8150
}
8251
}
8352

@@ -114,17 +83,3 @@ pub(crate) fn file() -> String {
11483
proc_macro::Span::call_site().file()
11584
}
11685
}
117-
118-
/// Parse a token stream of the form `expected_name: "value",` and return the
119-
/// string in the position of "value".
120-
///
121-
/// # Panics
122-
///
123-
/// - On parse error.
124-
pub(crate) fn expect_string_field(it: &mut token_stream::IntoIter, expected_name: &str) -> String {
125-
assert_eq!(expect_ident(it), expected_name);
126-
assert_eq!(expect_punct(it), ':');
127-
let string = expect_string(it);
128-
assert_eq!(expect_punct(it), ',');
129-
string
130-
}

rust/macros/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,10 @@ use syn::parse_macro_input;
131131
/// - `firmware`: array of ASCII string literals of the firmware files of
132132
/// the kernel module.
133133
#[proc_macro]
134-
pub fn module(ts: TokenStream) -> TokenStream {
135-
module::module(ts.into()).into()
134+
pub fn module(input: TokenStream) -> TokenStream {
135+
module::module(parse_macro_input!(input))
136+
.unwrap_or_else(|e| e.into_compile_error())
137+
.into()
136138
}
137139

138140
/// Declares or implements a vtable trait.

0 commit comments

Comments
 (0)