1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
extern crate proc_macro; mod enum_hack; use proc_macro2::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree}; use proc_macro_hack::proc_macro_hack; use quote::{quote, ToTokens}; use std::iter::FromIterator; use syn::parse::{Error, Parse, ParseStream, Parser, Result}; use syn::{parenthesized, parse_macro_input, Lit, LitStr, Token}; #[proc_macro] pub fn item(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as PasteInput); proc_macro::TokenStream::from(input.expanded) } #[proc_macro] pub fn item_with_macros(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as PasteInput); proc_macro::TokenStream::from(enum_hack::wrap(input.expanded)) } #[proc_macro_hack] pub fn expr(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as PasteInput); let output = input.expanded; proc_macro::TokenStream::from(quote!({ #output })) } #[doc(hidden)] #[proc_macro_derive(EnumHack)] pub fn enum_hack(input: proc_macro::TokenStream) -> proc_macro::TokenStream { enum_hack::extract(input) } struct PasteInput { expanded: TokenStream, } impl Parse for PasteInput { fn parse(input: ParseStream) -> Result<Self> { let mut expanded = TokenStream::new(); while !input.is_empty() { match input.parse()? { TokenTree::Group(group) => { let delimiter = group.delimiter(); let content = group.stream(); let span = group.span(); if delimiter == Delimiter::Bracket && is_paste_operation(&content) { let segments = parse_bracket_as_segments.parse2(content)?; let pasted = paste_segments(span, &segments)?; pasted.to_tokens(&mut expanded); } else if delimiter == Delimiter::None && is_single_ident(&content) { content.to_tokens(&mut expanded); } else { let nested = PasteInput::parse.parse2(content)?; let mut group = Group::new(delimiter, nested.expanded); group.set_span(span); group.to_tokens(&mut expanded); } } other => other.to_tokens(&mut expanded), } } Ok(PasteInput { expanded }) } } fn is_paste_operation(input: &TokenStream) -> bool { let input = input.clone(); parse_bracket_as_segments.parse2(input).is_ok() } fn is_single_ident(input: &TokenStream) -> bool { let mut has_ident = false; for tt in input.clone() { match tt { TokenTree::Ident(_) if !has_ident => has_ident = true, _ => return false, } } has_ident } enum Segment { String(String), Apostrophe(Span), Env(LitStr), Modifier(Token![:], Ident), } fn parse_bracket_as_segments(input: ParseStream) -> Result<Vec<Segment>> { input.parse::<Token![<]>()?; let segments = parse_segments(input)?; input.parse::<Token![>]>()?; if !input.is_empty() { return Err(input.error("invalid input")); } Ok(segments) } fn parse_segments(input: ParseStream) -> Result<Vec<Segment>> { let mut segments = Vec::new(); while !(input.is_empty() || input.peek(Token![>])) { match input.parse()? { TokenTree::Ident(ident) => { let mut fragment = ident.to_string(); if fragment.starts_with("r#") { fragment = fragment.split_off(2); } if fragment == "env" && input.peek(Token![!]) { input.parse::<Token![!]>()?; let arg; parenthesized!(arg in input); let var: LitStr = arg.parse()?; segments.push(Segment::Env(var)); } else { segments.push(Segment::String(fragment)); } } TokenTree::Literal(lit) => { let value = match syn::parse_str(&lit.to_string())? { Lit::Str(string) => string.value().replace('-', "_"), Lit::Int(_) => lit.to_string(), _ => return Err(Error::new(lit.span(), "unsupported literal")), }; segments.push(Segment::String(value)); } TokenTree::Punct(punct) => match punct.as_char() { '_' => segments.push(Segment::String("_".to_string())), '\'' => segments.push(Segment::Apostrophe(punct.span())), ':' => segments.push(Segment::Modifier(Token), input.parse()?)), _ => return Err(Error::new(punct.span(), "unexpected punct")), }, TokenTree::Group(group) => { if group.delimiter() == Delimiter::None { let nested = parse_segments.parse2(group.stream())?; segments.extend(nested); } else { return Err(Error::new(group.span(), "unexpected token")); } } } } Ok(segments) } fn paste_segments(span: Span, segments: &[Segment]) -> Result<TokenStream> { let mut evaluated = Vec::new(); let mut is_lifetime = false; for segment in segments { match segment { Segment::String(segment) => { evaluated.push(segment.clone()); } Segment::Apostrophe(span) => { if is_lifetime { return Err(Error::new(*span, "unexpected lifetime")); } is_lifetime = true; } Segment::Env(var) => { let resolved = match std::env::var(var.value()) { Ok(resolved) => resolved, Err(_) => { return Err(Error::new(var.span(), "no such env var")); } }; let resolved = resolved.replace('-', "_"); evaluated.push(resolved); } Segment::Modifier(colon, ident) => { let span = quote!(#colon #ident); let last = match evaluated.pop() { Some(last) => last, None => return Err(Error::new_spanned(span, "unexpected modifier")), }; if ident == "lower" { evaluated.push(last.to_lowercase()); } else if ident == "upper" { evaluated.push(last.to_uppercase()); } else if ident == "snake" { let mut acc = String::new(); let mut prev = '_'; for ch in last.chars() { if ch.is_uppercase() && prev != '_' { acc.push('_'); } acc.push(ch); prev = ch; } evaluated.push(acc.to_lowercase()); } else if ident == "camel" { let mut acc = String::new(); let mut prev = '_'; for ch in last.chars() { if ch != '_' { if prev == '_' { for chu in ch.to_uppercase() { acc.push(chu); } } else if prev.is_uppercase() { for chl in ch.to_lowercase() { acc.push(chl); } } else { acc.push(ch); } } prev = ch; } evaluated.push(acc); } else { return Err(Error::new_spanned(span, "unsupported modifier")); } } } } let pasted = evaluated.into_iter().collect::<String>(); let ident = TokenTree::Ident(Ident::new(&pasted, span)); let tokens = if is_lifetime { let apostrophe = TokenTree::Punct(Punct::new('\'', Spacing::Joint)); vec![apostrophe, ident] } else { vec![ident] }; Ok(TokenStream::from_iter(tokens)) }