105 lines · rust
1// SPDX-License-Identifier: GPL-2.02 3use proc_macro::{Delimiter, Group, Ident, Spacing, Span, TokenTree};4 5fn 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)) => {13 // Allow us to concat string literals by stripping quotes14 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 span36 "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 _ => panic!("unexpected token in paste segments"),50 };51 }52 53 let pasted: String = segments.into_iter().map(|x| x.0).collect();54 TokenTree::Ident(Ident::new(&pasted, span.unwrap_or(group_span)))55}56 57pub(crate) fn expand(tokens: &mut Vec<TokenTree>) {58 for token in tokens.iter_mut() {59 if let TokenTree::Group(group) = token {60 let delimiter = group.delimiter();61 let span = group.span();62 let mut stream: Vec<_> = group.stream().into_iter().collect();63 // Find groups that looks like `[< A B C D >]`64 if delimiter == Delimiter::Bracket65 && stream.len() >= 366 && matches!(&stream[0], TokenTree::Punct(p) if p.as_char() == '<')67 && matches!(&stream[stream.len() - 1], TokenTree::Punct(p) if p.as_char() == '>')68 {69 // Replace the group with concatenated token70 *token = concat(&stream[1..stream.len() - 1], span);71 } else {72 // Recursively expand tokens inside the group73 expand(&mut stream);74 let mut group = Group::new(delimiter, stream.into_iter().collect());75 group.set_span(span);76 *token = TokenTree::Group(group);77 }78 }79 }80 81 // Path segments cannot contain invisible delimiter group, so remove them if any.82 for i in (0..tokens.len().saturating_sub(3)).rev() {83 // Looking for a double colon84 if matches!(85 (&tokens[i + 1], &tokens[i + 2]),86 (TokenTree::Punct(a), TokenTree::Punct(b))87 if a.as_char() == ':' && a.spacing() == Spacing::Joint && b.as_char() == ':'88 ) {89 match &tokens[i + 3] {90 TokenTree::Group(group) if group.delimiter() == Delimiter::None => {91 tokens.splice(i + 3..i + 4, group.stream());92 }93 _ => (),94 }95 96 match &tokens[i] {97 TokenTree::Group(group) if group.delimiter() == Delimiter::None => {98 tokens.splice(i..i + 1, group.stream());99 }100 _ => (),101 }102 }103 }104}105