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