mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-18 00:24:58 +08:00
823d4737d4
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 <gary@garyguo.net> Reviewed-by: Björn Roy Baron <bjorn3_gh@protonmail.com> Reviewed-by: Benno Lossin <benno.lossin@proton.me> Reviewed-by: Alice Ryhl <aliceryhl@google.com> Reviewed-by: Martin Rodriguez Reboredo <yakoyoku@gmail.com> Link: https://lore.kernel.org/r/20230628171108.1150742-1-gary@garyguo.net [ Added SPDX license identifier as discussed in the list and fixed typo. ] Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
97 lines
3.8 KiB
Rust
97 lines
3.8 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
use proc_macro::{Delimiter, Group, Ident, Spacing, Span, TokenTree};
|
|
|
|
fn concat(tokens: &[TokenTree], group_span: Span) -> TokenTree {
|
|
let mut tokens = tokens.iter();
|
|
let mut segments = Vec::new();
|
|
let mut span = None;
|
|
loop {
|
|
match tokens.next() {
|
|
None => break,
|
|
Some(TokenTree::Literal(lit)) => segments.push((lit.to_string(), lit.span())),
|
|
Some(TokenTree::Ident(ident)) => {
|
|
let mut value = ident.to_string();
|
|
if value.starts_with("r#") {
|
|
value.replace_range(0..2, "");
|
|
}
|
|
segments.push((value, ident.span()));
|
|
}
|
|
Some(TokenTree::Punct(p)) if p.as_char() == ':' => {
|
|
let Some(TokenTree::Ident(ident)) = tokens.next() else {
|
|
panic!("expected identifier as modifier");
|
|
};
|
|
|
|
let (mut value, sp) = segments.pop().expect("expected identifier before modifier");
|
|
match ident.to_string().as_str() {
|
|
// Set the overall span of concatenated token as current span
|
|
"span" => {
|
|
assert!(
|
|
span.is_none(),
|
|
"span modifier should only appear at most once"
|
|
);
|
|
span = Some(sp);
|
|
}
|
|
"lower" => value = value.to_lowercase(),
|
|
"upper" => value = value.to_uppercase(),
|
|
v => panic!("unknown modifier `{v}`"),
|
|
};
|
|
segments.push((value, sp));
|
|
}
|
|
_ => panic!("unexpected token in paste segments"),
|
|
};
|
|
}
|
|
|
|
let pasted: String = segments.into_iter().map(|x| x.0).collect();
|
|
TokenTree::Ident(Ident::new(&pasted, span.unwrap_or(group_span)))
|
|
}
|
|
|
|
pub(crate) fn expand(tokens: &mut Vec<TokenTree>) {
|
|
for token in tokens.iter_mut() {
|
|
if let TokenTree::Group(group) = token {
|
|
let delimiter = group.delimiter();
|
|
let span = group.span();
|
|
let mut stream: Vec<_> = group.stream().into_iter().collect();
|
|
// Find groups that looks like `[< A B C D >]`
|
|
if delimiter == Delimiter::Bracket
|
|
&& stream.len() >= 3
|
|
&& matches!(&stream[0], TokenTree::Punct(p) if p.as_char() == '<')
|
|
&& matches!(&stream[stream.len() - 1], TokenTree::Punct(p) if p.as_char() == '>')
|
|
{
|
|
// Replace the group with concatenated token
|
|
*token = concat(&stream[1..stream.len() - 1], span);
|
|
} else {
|
|
// Recursively expand tokens inside the group
|
|
expand(&mut stream);
|
|
let mut group = Group::new(delimiter, stream.into_iter().collect());
|
|
group.set_span(span);
|
|
*token = TokenTree::Group(group);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Path segments cannot contain invisible delimiter group, so remove them if any.
|
|
for i in (0..tokens.len().saturating_sub(3)).rev() {
|
|
// Looking for a double colon
|
|
if matches!(
|
|
(&tokens[i + 1], &tokens[i + 2]),
|
|
(TokenTree::Punct(a), TokenTree::Punct(b))
|
|
if a.as_char() == ':' && a.spacing() == Spacing::Joint && b.as_char() == ':'
|
|
) {
|
|
match &tokens[i + 3] {
|
|
TokenTree::Group(group) if group.delimiter() == Delimiter::None => {
|
|
tokens.splice(i + 3..i + 4, group.stream());
|
|
}
|
|
_ => (),
|
|
}
|
|
|
|
match &tokens[i] {
|
|
TokenTree::Group(group) if group.delimiter() == Delimiter::None => {
|
|
tokens.splice(i..i + 1, group.stream());
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
}
|