#![forbid(unsafe_code)]
#![allow(nonstandard_style, unused_imports)]
extern crate proc_macro;
use ::proc_macro::{
TokenStream,
};
use ::proc_macro2::{
Group,
Span,
TokenStream as TokenStream2,
};
use ::quote::{
quote,
quote_spanned,
ToTokens,
};
use ::syn::{*,
parse::{
Parse,
Parser,
ParseStream,
},
punctuated::Punctuated,
spanned::Spanned,
Result,
};
use self::builtin_macros as builtin;
use compile_time_string::CompileTimeString;
mod compile_time_string;
supported_builtin_macros![
concat!(...),
concat_idents!(...),
env!(...),
option_env!(...),
include_from_root!(...),
include_bytes_from_root!(...),
include_str_from_root!(...),
stringify!(...),
];
#[proc_macro] pub
fn with_builtin (input: TokenStream)
-> TokenStream
{
let Input {
metavar,
macro_invocation: MacroInvocation { builtin_macro, macro_input },
template,
} = parse_macro_input!(input);
let input_span = macro_input.span();
let expansion = match builtin_macro.call_on(macro_input) {
| Ok(it) => it,
| Err(mut err) => {
if format!("{:?}", err.span()) == format!("{:?}", Span::call_site()) {
err = Error::new(input_span, &err.to_string());
}
return err.to_compile_error().into();
},
};
map_replace(&metavar, &expansion, template)
.into()
}
struct Input {
metavar: Ident,
macro_invocation: MacroInvocation,
template: TokenStream2,
}
impl Parse for Input {
fn parse (input: ParseStream<'_>)
-> Result<Self>
{
let _: Token![ let ] = input.parse()?;
let _: Token![ $ ] = input.parse()?;
let metavar: Ident = input.parse()?;
let _: Token![ = ] = input.parse()?;
let macro_invocation: MacroInvocation = input.parse()?;
let _: Token![ in ] = input.parse()?;
let template; braced!(template in input);
Ok(Input {
metavar,
macro_invocation,
template: template.parse()?,
})
}
}
struct MacroInvocation {
builtin_macro: BuiltinMacro,
macro_input: TokenStream2,
}
impl Parse for MacroInvocation {
fn parse(input: ParseStream<'_>)
-> Result<MacroInvocation>
{
let builtin_macro: BuiltinMacro = input.parse()?;
let _: Token![ ! ] = input.parse()?;
let macro_input = { let g: Group = input.parse()?; g.stream() };
Ok(Self {
builtin_macro,
macro_input,
})
}
}
macro_rules! supported_builtin_macros {(
$(
$( #[cfg $cfg:tt] )?
$macro_name:ident !(...)
),+ $(,)?
) => (
mod kw {
$(
$( #[cfg $cfg] )?
::syn::custom_keyword!($macro_name);
)+
}
mod builtin_macros {
use super::*;
$(
$( #[cfg $cfg] )?
pub
mod $macro_name;
)+
}
enum BuiltinMacro {
$(
$( #[cfg $cfg] )?
$macro_name(kw::$macro_name),
)*
}
impl Parse for BuiltinMacro {
fn parse (input: ParseStream<'_>)
-> Result<Self>
{
let lookahead = input.lookahead1();
match () {
$(
$( #[cfg $cfg] )?
_case if lookahead.peek(kw::$macro_name) => {
input
.parse::<kw::$macro_name>()
.map(Self::$macro_name)
},
)*
_default => Err(lookahead.error()),
}
}
}
impl Spanned for BuiltinMacro {
fn span (self: &'_ Self)
-> Span
{
match *self {
$(
$( #[cfg $cfg] )?
| Self::$macro_name(ref it) => it.span(),
)+
}
}
}
impl BuiltinMacro {
fn call_on (self: &'_ Self, args: TokenStream2)
-> Result<TokenStream2>
{
match *self {
$(
$( #[cfg $cfg] )?
| Self::$macro_name(_) => {
builtin::$macro_name::call_on(args)
.map(|it| it.into_token_stream())
},
)+
}
}
}
)}
use supported_builtin_macros;
trait MockToTokens {
fn into_token_stream (self: Self)
-> TokenStream2
;
const _impl: () = {
impl MockToTokens for Vec<u8> {
fn into_token_stream (self: Vec<u8>)
-> TokenStream2
{
let each_byte = &self;
match self.len() {
| 0 => quote!(
[0_u8; 0]
),
| _ => quote!(
[
#(#each_byte),*
]
),
}
}
}
};
}
fn map_replace (
metavar: &'_ Ident,
tokenized: &'_ TokenStream2,
template: TokenStream2,
) -> TokenStream2
{
use ::proc_macro2::{*, TokenTree as TT};
let mut tokens = template.into_iter().peekable();
let mut ret = TokenStream2::new();
loop {
match (tokens.next(), tokens.peek()) {
| (
Some(TT::Punct(dollar)),
Some(&TT::Ident(ref ident)),
)
if dollar.as_char() == '$'
&& ident == metavar
=> {
drop(tokens.next());
ret.extend(tokenized.clone());
},
| (Some(TT::Group(group)), _) => {
ret.extend(Some(TT::Group(Group::new(
group.delimiter(),
map_replace(metavar, tokenized, group.stream()),
))));
},
| (None, _) => break,
| (tt, _) => ret.extend(tt),
}
}
ret
}
#[proc_macro] pub
fn with_eager_expansions(input: TokenStream)
-> TokenStream
{
map_replace_eager_expansions(input.into()).into()
}
fn map_replace_eager_expansions (
template: TokenStream2,
) -> TokenStream2
{
use ::proc_macro2::{*, TokenTree as TT};
let mut tokens = template.into_iter().peekable();
let mut ret = TokenStream2::new();
loop {
let tt = tokens.next();
match (&tt, tokens.peek()) {
| (
&Some(TT::Punct(ref dollar)),
Some(&TT::Group(ref g)),
) if dollar.as_char() == '#'
&& g.delimiter() == Delimiter::Brace
=> {
if let Ok(MacroInvocation { builtin_macro, macro_input }) =
parse2(g.stream())
{
drop(tokens.next());
let input_span = macro_input.span();
let expansion = match builtin_macro.call_on(macro_input) {
| Ok(it) => it,
| Err(mut err) => {
if format!("{:?}", err.span())
== format!("{:?}", Span::call_site())
{
err = Error::new(input_span, &err.to_string());
}
return err.to_compile_error().into();
},
};
ret.extend(expansion);
} else {
ret.extend(tt);
}
},
| (&Some(TT::Group(ref group)), _) => {
ret.extend(Some(TT::Group(Group::new(
group.delimiter(),
map_replace_eager_expansions(group.stream()),
))));
},
| (None, _) => break,
| (_, _) => ret.extend(tt),
}
}
ret
}