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
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), } 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())), _ => 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 pasted = String::new(); let mut is_lifetime = false; for segment in segments { match segment { Segment::String(segment) => { pasted.push_str(&segment); } 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('-', "_"); pasted.push_str(&resolved); } } } 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)) }