with_builtin_macros_proc_macros/
lib.rs

1#![forbid(unsafe_code)]
2#![allow(nonstandard_style, unused_imports)]
3
4/// For compatibility with older versions of Rust.
5extern crate proc_macro;
6
7use ::proc_macro::{
8    TokenStream,
9};
10use ::proc_macro2::{
11    Group,
12    Span,
13    TokenStream as TokenStream2,
14};
15use ::quote::{
16    quote,
17    quote_spanned,
18    ToTokens,
19};
20use ::syn::{*,
21    parse::{
22        Parse,
23        Parser,
24        ParseStream,
25    },
26    punctuated::Punctuated,
27    spanned::Spanned,
28    Result,
29};
30
31use self::builtin_macros as builtin;
32
33use compile_time_string::CompileTimeString;
34mod compile_time_string;
35
36supported_builtin_macros![
37    concat!(...),
38    concat_idents!(...),
39    env!(...),
40    option_env!(...),
41    include_from_root!(...),
42    include_bytes_from_root!(...),
43    include_str_from_root!(...),
44    stringify!(...),
45];
46
47/// Pre-processor (pattern) with hard-coded implementations for some of the
48/// built-in macros, so as to be able to chain their result with custom macros.
49///
50///  - [Click here to see the list of supported macros](
51///     builtin_macros/index.html)
52///
53/// By using a pre-processor, this also enables "using" macros where it is
54/// otherwise forbidden.
55///
56/// For instance, the following code fails to compile:
57///
58/// ```rust,compile_fail
59/// fn concat_idents!(f, o, o) ()
60/// {}
61///
62/// fn main ()
63/// {
64///     foo();
65/// }
66/// ```
67///
68/// But the following call does not:
69///
70/// ```rust
71/// # extern crate with_builtin_macros_proc_macros as with_builtin_macros;
72/// use ::with_builtin_macros::with_builtin;
73///
74/// with_builtin!(let $fname = concat_idents!(f, o, o) in {
75///     fn $fname ()
76///     {}
77/// });
78///
79/// fn main ()
80/// {
81///     foo();
82/// }
83/// ```
84///
85///   - Note: for this behavior, with some added neat features (such as case
86///     conversion), see the [`::paste::paste!`](https://docs.rs/paste) macro.
87///
88/// Even if we forget about allowed macro call-sites, there is also the issue
89/// of macro evaluation order leading to things like the following not
90/// working:
91///
92/// ```rust,compile_fail
93/// /// Some (proc-)macro that expects a string literal containing a hexadecimal
94/// /// sequence of digits, that it decodes into the represented byte sequence.
95/// use ::hex_literal::hex;
96///
97/// const BYTES: &[u8] = hex!(include_str!(concat!(
98///     env!("CARGO_MANIFEST_DIR"), "/", "data.hex",
99/// )));
100/// ```
101///
102/// With [`with_builtin!`], however, the following works:
103///
104/// ```rust
105/// # macro_rules! ignore {($($t:tt)*) => ()} ignore! {
106/// /// Some (proc-)macro that expects a string literal containing a hexadecimal
107/// /// sequence of digits, that it decodes into the represented byte sequence.
108/// use ::hex_literal::hex;
109/// use ::with_builtin_macros::with_builtin;
110///
111/// const BYTES: &[u8] = {
112///     with_builtin!(let $hex_str = include_str_from_root!("data.hex") in {
113///         hex!($hex_str)
114///     })
115/// };
116/// # } fn main () {}
117/// ```
118#[proc_macro] pub
119fn with_builtin (input: TokenStream)
120  -> TokenStream
121{
122    let Input {
123        metavar,
124        macro_invocation: MacroInvocation { builtin_macro, macro_input },
125        template,
126    } = parse_macro_input!(input);
127    let input_span = macro_input.span();
128    let expansion = match builtin_macro.call_on(macro_input) {
129        | Ok(it) => it,
130        | Err(mut err) => {
131            if format!("{:?}", err.span()) == format!("{:?}", Span::call_site()) {
132                err = Error::new(input_span, &err.to_string());
133            }
134            return err.to_compile_error().into();
135        },
136    };
137    map_replace(&metavar, &expansion, template)
138        .into()
139}
140
141struct Input {
142    metavar: Ident,
143    macro_invocation: MacroInvocation,
144    template: TokenStream2,
145}
146
147impl Parse for Input {
148    fn parse (input: ParseStream<'_>)
149      -> Result<Self>
150    {
151        let _:             Token![ let ]        = input.parse()?;
152        let _:             Token![ $   ]        = input.parse()?;
153        let metavar:               Ident        = input.parse()?;
154        let _:             Token![ =   ]        = input.parse()?;
155        let macro_invocation:   MacroInvocation = input.parse()?;
156        let _:             Token![ in  ]        = input.parse()?;
157        let template;              braced!(template in input);
158        Ok(Input {
159            metavar,
160            macro_invocation,
161            template: template.parse()?,
162        })
163    }
164}
165
166struct MacroInvocation {
167    builtin_macro: BuiltinMacro,
168    macro_input: TokenStream2,
169}
170
171impl Parse for MacroInvocation {
172    fn parse(input: ParseStream<'_>)
173      -> Result<MacroInvocation>
174    {
175        let builtin_macro:         BuiltinMacro = input.parse()?;
176        let _:             Token![ !   ]        = input.parse()?;
177        let macro_input = { let g: Group        = input.parse()?; g.stream() };
178        Ok(Self {
179            builtin_macro,
180            macro_input,
181        })
182    }
183}
184
185macro_rules! supported_builtin_macros {(
186    $(
187        $( #[cfg $cfg:tt] )?
188        $macro_name:ident !(...)
189    ),+ $(,)?
190) => (
191    mod kw {
192        $(
193            $( #[cfg $cfg] )?
194            ::syn::custom_keyword!($macro_name);
195        )+
196    }
197
198    mod builtin_macros {
199        use super::*;
200
201        $(
202            $( #[cfg $cfg] )?
203            pub
204            mod $macro_name;
205        )+
206    }
207
208    enum BuiltinMacro {
209        $(
210            $( #[cfg $cfg] )?
211            $macro_name(kw::$macro_name),
212        )*
213    }
214
215    impl Parse for BuiltinMacro {
216        fn parse (input: ParseStream<'_>)
217          -> Result<Self>
218        {
219            let lookahead = input.lookahead1();
220            match () {
221                $(
222                    $( #[cfg $cfg] )?
223                    _case if lookahead.peek(kw::$macro_name) => {
224                        input
225                            .parse::<kw::$macro_name>()
226                            .map(Self::$macro_name)
227                    },
228                )*
229                _default => Err(lookahead.error()),
230            }
231        }
232    }
233
234    impl Spanned for BuiltinMacro {
235        fn span (self: &'_ Self)
236          -> Span
237        {
238            match *self {
239                $(
240                    $( #[cfg $cfg] )?
241                    | Self::$macro_name(ref it) => it.span(),
242                )+
243            }
244        }
245    }
246
247    impl BuiltinMacro {
248        fn call_on (self: &'_ Self, args: TokenStream2)
249          -> Result<TokenStream2>
250        {
251            match *self {
252                $(
253                    $( #[cfg $cfg] )?
254                    | Self::$macro_name(_) => {
255                        builtin::$macro_name::call_on(args)
256                            .map(|it| it.into_token_stream())
257                    },
258                )+
259            }
260        }
261    }
262)}
263use supported_builtin_macros;
264
265trait MockToTokens {
266    fn into_token_stream (self: Self)
267      -> TokenStream2
268    ;
269
270    const _impl: () = {
271        impl MockToTokens for Vec<u8> {
272            fn into_token_stream (self: Vec<u8>)
273              -> TokenStream2
274            {
275                let each_byte = &self;
276                match self.len() {
277                    | 0 => quote!(
278                        [0_u8; 0]
279                    ),
280                    | _ => quote!(
281                        [
282                            #(#each_byte),*
283                        ]
284                    ),
285                }
286            }
287        }
288    };
289}
290
291fn map_replace (
292    metavar: &'_ Ident,
293    tokenized: &'_ TokenStream2,
294    template: TokenStream2,
295) -> TokenStream2
296{
297    use ::proc_macro2::{*, TokenTree as TT};
298    let mut tokens = template.into_iter().peekable();
299    let mut ret = TokenStream2::new();
300    loop {
301        match (tokens.next(), tokens.peek()) {
302            | (
303                Some(TT::Punct(dollar)),
304                Some(&TT::Ident(ref ident)),
305            )
306                if  dollar.as_char() == '$'
307                &&  ident == metavar
308            => {
309                drop(tokens.next());
310                ret.extend(tokenized.clone());
311            },
312
313            | (Some(TT::Group(group)), _) => {
314                ret.extend(Some(TT::Group(Group::new(
315                    group.delimiter(),
316                    map_replace(metavar, tokenized, group.stream()),
317                ))));
318            },
319
320            | (None, _) => break,
321
322            | (tt, _) => ret.extend(tt),
323        }
324    }
325    ret
326}
327
328#[proc_macro] pub
329fn with_eager_expansions(input: TokenStream)
330  -> TokenStream
331{
332    map_replace_eager_expansions(input.into()).into()
333}
334
335
336fn map_replace_eager_expansions (
337    template: TokenStream2,
338) -> TokenStream2
339{
340    use ::proc_macro2::{*, TokenTree as TT};
341    let mut tokens = template.into_iter().peekable();
342    let mut ret = TokenStream2::new();
343    loop {
344        let tt = tokens.next();
345        match (&tt, tokens.peek()) {
346            // ${ ... }
347            | (
348                &Some(TT::Punct(ref dollar)),
349                Some(&TT::Group(ref g)),
350            )   if dollar.as_char() == '#'
351                && g.delimiter() == Delimiter::Brace
352            => {
353                if let Ok(MacroInvocation { builtin_macro, macro_input }) =
354                    parse2(g.stream())
355                {
356                    drop(tokens.next());
357                    let input_span = macro_input.span();
358                    let expansion = match builtin_macro.call_on(macro_input) {
359                        | Ok(it) => it,
360                        | Err(mut err) => {
361                            if format!("{:?}", err.span())
362                            == format!("{:?}", Span::call_site())
363                            {
364                                err = Error::new(input_span, &err.to_string());
365                            }
366                            return err.to_compile_error().into();
367                        },
368                    };
369                    ret.extend(expansion);
370                } else {
371                    ret.extend(tt);
372                }
373            },
374
375            | (&Some(TT::Group(ref group)), _) => {
376                ret.extend(Some(TT::Group(Group::new(
377                    group.delimiter(),
378                    map_replace_eager_expansions(group.stream()),
379                ))));
380            },
381
382            | (None, _) => break,
383
384            | (_, _) => ret.extend(tt),
385        }
386    }
387    ret
388}