1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
mod utils; mod walker; use proc_macro::TokenStream; use quote::quote; use syn::spanned::Spanned; use syn::{parse_macro_input, ItemFn}; use utils::{get_iter_item_type, make_ident, to_pascal_case}; use walker::Walker; #[proc_macro_attribute] pub fn giver(attr: TokenStream, item: TokenStream) -> TokenStream { let func = parse_macro_input!(item as ItemFn); let iter_item_type = match get_iter_item_type(&func.sig.output) { Some(ty) => ty, None => { return fail( &func.sig.output, "return type must be `-> impl Iterator<Item = XXX>`", ) } }; let func_vis = &func.vis; let name_snake = func.sig.ident.to_string(); let name_pascal = to_pascal_case(&name_snake); let func_name = make_ident(&name_snake); let mod_name = make_ident(&format!("{}_mod", name_snake)); let state_enum_name = make_ident(&format!("{}State", name_pascal)); let struct_name = make_ident(&name_pascal); let w = Walker::walk(state_enum_name.clone(), &func.block.stmts); let state_idents = &w.states; let match_blocks = w.output.iter().map(|((_, s), b)| { let state_enum = &w.name; if b.is_empty() { quote! { #state_enum::#s | } } else { quote! { #state_enum::#s => { #(#b)* }, } } }); let new_code = quote! { mod #mod_name { enum #state_enum_name { #(#state_idents),* } struct #struct_name { state: #state_enum_name, } impl Iterator for #struct_name { type Item = #iter_item_type; fn next(&mut self) -> Option<#iter_item_type> { loop { match self.state { #(#match_blocks)* } } } } pub fn #func_name() -> impl Iterator<Item = #iter_item_type> { #struct_name { state: #state_enum_name::S0_Start } } } #func_vis use #mod_name::#func_name; }; if attr.to_string() == "print" { println!("{}", &new_code); } return TokenStream::from(new_code); } fn fail<T: Spanned>(s: &T, msg: &str) -> TokenStream { let msg = format!("[generoust] {}", msg); let err = syn::Error::new(s.span(), msg).to_compile_error(); return TokenStream::from(err); }