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