#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
#[proc_macro]
pub fn input(input: TokenStream) -> TokenStream {
let Message { msg } = syn::parse_macro_input!(input as Message);
let print_msg = msg
.map(|Format { expr, args }| {
let msg = match expr {
syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(fmt), .. }) => {
if args.is_some() {
quote! { &::std::format!(#fmt, #args) }
}
else if fmt.value().contains(&['{', '}'][..]) {
quote! { &::std::format!(#fmt) }
}
else {
quote! { #fmt }
}
},
_ => panic!()
};
quote! {
use ::std::io::Write;
::std::io::stdout().write_all(#msg.as_bytes()).unwrap();
::std::io::stdout().flush().unwrap();
}
})
.unwrap_or(quote! {});
quote! {{
#print_msg
use ::std::io::BufRead;
::std::io::stdin().lock().lines()
}}
.into()
}
struct Message {
pub msg: Option<Format>
}
impl syn::parse::Parse for Message {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self { msg: input.parse().ok() })
}
}
struct Format {
pub expr: syn::Expr,
pub args: Option<TokenStream2>,
}
impl syn::parse::Parse for Format {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let expr = input.parse()?;
if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(_), .. }) = &expr {}
else { return Err(syn::Error::new_spanned(expr, "Expected literal string")) }
let args = if input.peek(syn::token::Comma) {
Some(input.parse()?)
} else {
None
};
Ok(Self { expr, args })
}
}