| #![recursion_limit = "128"] | |
| use proc_macro::TokenStream; | |
| use quote::quote; | |
| use syn::Error; | |
| use syn::ItemFn; | |
| #[allow(clippy::needless_doctest_main)] | |
| /// Procedural macro. This generates a function to read and parse | |
| /// an environment variable to a usize at compile time. We have to use a | |
| /// function since procedural macros can't generate expressions at the moment. | |
| /// Example: | |
| /// | |
| /// | |
| /// ```ignore | |
| /// make_read_env_var!("MAX_COUNT") | |
| /// | |
| /// fn do_it() { | |
| /// let max = read_MAX_COUNT(); | |
| /// } | |
| /// ``` | |
| #[proc_macro] | |
| pub fn make_read_env_var(input: TokenStream) -> TokenStream { | |
| let env_arg = syn::parse_macro_input!(input as syn::LitStr); | |
| let env_var_name = env_arg.value(); | |
| let app_size = match std::env::var(&env_var_name) { | |
| Ok(r) => r, | |
| Err(_r) => { | |
| return syn::Error::new( | |
| env_arg.span(), | |
| format!("Failed to find {} in environment, is it set?", env_var_name), | |
| ) | |
| .to_compile_error() | |
| .into() | |
| } | |
| }; | |
| let size = match app_size.parse::<usize>() { | |
| Ok(r) => r, | |
| Err(_r) => { | |
| return syn::Error::new( | |
| env_arg.span(), | |
| format!("Environment var {} can't be parsed to usize", env_var_name), | |
| ) | |
| .to_compile_error() | |
| .into() | |
| } | |
| }; | |
| let concat = format!("read_{}", env_var_name); | |
| let i = syn::Ident::new(&concat, proc_macro2::Span::call_site()); | |
| let result = quote! { | |
| fn #i() -> usize { #size } | |
| }; | |
| result.into() | |
| } | |
| #[allow(clippy::needless_doctest_main)] | |
| /// Procedural attribute macro. This is meant to be applied to a binary's async | |
| /// `main()`, transforming into a function that returns a type acceptable for | |
| /// `main()`. In other words, this will not compile with libtock-rs: | |
| /// ```ignore | |
| /// async fn main() { | |
| /// // async code | |
| /// } | |
| /// ``` | |
| /// and this will: | |
| /// ```ignore | |
| /// #[libtock::main] | |
| /// async fn main() { | |
| /// // async code | |
| /// } | |
| /// ``` | |
| #[proc_macro_attribute] | |
| pub fn main(_: TokenStream, input: TokenStream) -> TokenStream { | |
| generate_main_wrapped(input.into()).into() | |
| } | |
| fn generate_main_wrapped(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream { | |
| try_generate_main_wrapped(input).unwrap_or_else(|err| err.to_compile_error()) | |
| } | |
| fn try_generate_main_wrapped( | |
| input: proc_macro2::TokenStream, | |
| ) -> Result<proc_macro2::TokenStream, Error> { | |
| let ast = syn::parse2::<ItemFn>(input)?; | |
| let block = ast.block; | |
| let output = &ast.sig.output; | |
| Ok(quote!( | |
| fn main() #output { | |
| static mut MAIN_INVOKED: bool = false; | |
| unsafe { | |
| if MAIN_INVOKED { | |
| panic!("Main called recursively; this is unsafe with #[libtock::main]"); | |
| } | |
| MAIN_INVOKED = true; | |
| } | |
| let _block = async #block; | |
| unsafe { ::libtock::executor::block_on(_block) } | |
| } | |
| )) | |
| } | |
| #[cfg(test)] | |
| mod tests { | |
| use super::*; | |
| #[test] | |
| fn wraps_main_into_blocking_executor() { | |
| let method_def: proc_macro2::TokenStream = quote! { | |
| async fn main() -> ::libtock::result::TockResult<()>{ | |
| method_call().await; | |
| } | |
| }; | |
| let actual: ItemFn = syn::parse2::<ItemFn>(generate_main_wrapped(method_def)).unwrap(); | |
| let expected: ItemFn = syn::parse2::<ItemFn>(quote!( | |
| fn main() -> ::libtock::result::TockResult<()> { | |
| static mut MAIN_INVOKED: bool = false; | |
| unsafe { | |
| if MAIN_INVOKED { | |
| panic!("Main called recursively; this is unsafe with #[libtock::main]"); | |
| } | |
| MAIN_INVOKED = true; | |
| } | |
| let _block = async { | |
| method_call().await; | |
| }; | |
| unsafe { ::libtock::executor::block_on(_block) } | |
| } | |
| )) | |
| .unwrap(); | |
| assert_eq!(actual, expected); | |
| } | |
| } |