1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use proc_macro::TokenStream;
6use proc_macro2::TokenStream as TokenStream2;
7use quote::quote;
8
9#[proc_macro]
11pub fn inputln(input: TokenStream) -> TokenStream {
12 let Message { msg } = syn::parse_macro_input!(input as Message);
13
14 let print_msg = msg
16 .map(|Format { expr, args }| {
17 let msg = match expr {
18 syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(fmt), .. }) => {
20 if args.is_some() {
22 quote! { &::std::format!(#fmt, #args) }
23 }
24 else if fmt.value().contains(&['{', '}'][..]) {
26 quote! { &::std::format!(#fmt) }
27 }
28 else {
30 quote! { #fmt }
31 }
32 },
33
34 _ => panic!()
36 };
37
38 quote! {
39 use ::std::io::Write;
40
41 ::std::io::stdout().write_all(#msg.as_bytes()).unwrap();
42 ::std::io::stdout().flush().unwrap();
43 }
44 })
45 .unwrap_or(quote! {});
46
47 quote! {{
49 #print_msg
50
51 let mut buffer = String::new();
52 ::std::io::stdin().read_line(&mut buffer).unwrap();
53
54 buffer.trim().to_owned()
55 }}
56 .into()
57}
58
59struct Message {
61 pub msg: Option<Format>
62}
63
64impl syn::parse::Parse for Message {
65 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
66 Ok(Self { msg: input.parse().ok() })
67 }
68}
69
70struct Format {
72 pub expr: syn::Expr,
73 pub args: Option<TokenStream2>,
74}
75
76impl syn::parse::Parse for Format {
77 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
78 let expr = input.parse()?;
80
81 if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(_), .. }) = &expr {}
83 else { return Err(syn::Error::new_spanned(expr, "Expected literal string")) }
84
85 let args = if input.peek(syn::token::Comma) {
87 Some(input.parse()?)
88 } else {
89 None
90 };
91
92 Ok(Self { expr, args })
93 }
94}