Mercurial > lbo > hg > lazycall
changeset 1:babb2f9cb5ad
All features working
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Fri, 20 Nov 2020 23:20:03 +0100 |
parents | 7401c615042c |
children | 2b71ed370156 |
files | examples/test_functions.rs lazycall_support/src/lib.rs src/lib.rs |
diffstat | 3 files changed, 109 insertions(+), 19 deletions(-) [+] |
line wrap: on
line diff
--- a/examples/test_functions.rs Fri Nov 20 22:15:20 2020 +0100 +++ b/examples/test_functions.rs Fri Nov 20 23:20:03 2020 +0100 @@ -1,4 +1,4 @@ -use lazycall::lazy; +use lazycall::{lazy, lazycall}; #[lazy] fn test_function_empty() -> i32 { @@ -19,11 +19,18 @@ impl TestStruct { #[lazy] - fn test_receiving_func(&mut self, a: i32) -> bool { - a%2 == 0 + fn test_receiving_func(&self, a: i32) -> bool { + (self.0+a)%2 == 0 } } + fn main() { + println!("{}", test_function_onearg(32));; + println!("lazy: {}", lazycall!(test_function_onearg(32))); + + let mystruct = TestStruct(32); + println!("{}", mystruct.test_receiving_func(33)); + println!("lazy: {}", lazycall!(mystruct.test_receiving_func(33))); }
--- a/lazycall_support/src/lib.rs Fri Nov 20 22:15:20 2020 +0100 +++ b/lazycall_support/src/lib.rs Fri Nov 20 23:20:03 2020 +0100 @@ -2,7 +2,7 @@ pub struct LazyVal<'a, Result>(&'a mut dyn FnMut() -> Result); impl<'a, Result> LazyVal<'a, Result> { - fn new<F: 'a + FnMut() -> Result>(f: &'a mut F) -> LazyVal<'a, Result> { + pub fn new<F: 'a + FnMut() -> Result>(f: &'a mut F) -> LazyVal<'a, Result> { LazyVal(f) } }
--- a/src/lib.rs Fri Nov 20 22:15:20 2020 +0100 +++ b/src/lib.rs Fri Nov 20 23:20:03 2020 +0100 @@ -1,9 +1,23 @@ - +use std::ops::Deref; use std::iter::IntoIterator; use proc_macro; use syn::parse_macro_input; +fn tuple_type_from_types<TT: quote::ToTokens, T: Deref<Target=TT>>( + types: &[T]) -> syn::TypeTuple { + + if types.len() == 1 { + let typ = types[0].deref(); + syn::parse_quote! { (#typ,) } + } else { + let types = types.iter().map(|t| t.deref()); + syn::parse_quote! { + (#(#types),*) + } + } +} + fn tuple_type_from_sig<'a, Iter: IntoIterator<Item = &'a syn::FnArg>>( args: Iter, ) -> syn::TypeTuple { @@ -11,21 +25,30 @@ .into_iter() .map(|a| match &a { &syn::FnArg::Receiver(_) => panic!("unexpected self in function argument list"), - &syn::FnArg::Typed(ref typ) => typ.ty.clone(), + &syn::FnArg::Typed(ref typ) => typ.ty.as_ref(), }) - .collect::<Vec<Box<syn::Type>>>(); + .collect::<Vec<&syn::Type>>(); - if types.len() == 1 { - let mut types = types; - let typ = types.pop().unwrap(); - syn::parse_quote! { (#typ,) } - } else { - syn::parse_quote! { - (#(#types),*) - } - } + tuple_type_from_types(types.as_slice()) +} + +fn lazied_method_name(id: &syn::Ident) -> syn::Ident { + syn::Ident::new((id.to_string() + "_lazy").as_str(), id.span()) } +/// Enable a function to be lazily called. +/// +/// This produces both the function itself and a wrapper enabling the function to be called lazily. +/// +/// ``` +/// #[lazy] +/// fn myfunc(arg: i32) -> bool { +/// arg%2 == 0 +/// } +/// +/// fn main() { +/// println!("{} vs {}", myfunc(32), lazy!(myfunc(expensive_function()))); +/// } #[proc_macro_attribute] pub fn lazy(_: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { let parsedfn = parse_macro_input!(item as syn::ItemFn); @@ -40,7 +63,7 @@ r => r.clone(), }; - let newname = syn::Ident::new((fnname.to_string() + "_lazy").as_str(), fnname.span()); + let newname = lazied_method_name(fnname); let mut newsig = sig.clone(); newsig.ident = newname.clone(); @@ -53,7 +76,7 @@ let receiver = args_iter.next().unwrap(); let tuty = tuple_type_from_sig(args_iter); // We are not evaluating for the receiver, thus one fewer argument. - let args_ixs: Vec<usize> = (0..newsig.inputs.len()-1).collect(); + let args_ixs: Vec<syn::Index> = (0..newsig.inputs.len()-1).map(syn::Index::from).collect(); (quote::quote! { #parsedfn @@ -67,7 +90,7 @@ Some(syn::FnArg::Typed(_)) => { // This is a freestanding function let tuty = tuple_type_from_sig(&newsig.inputs); - let args_ixs: Vec<usize> = (0..newsig.inputs.len()).collect(); + let args_ixs: Vec<syn::Index> = (0..newsig.inputs.len()).map(syn::Index::from).collect(); (quote::quote! { #parsedfn @@ -92,3 +115,63 @@ } } } + +#[proc_macro] +pub fn lazycall(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + use syn::spanned::Spanned; + use quote::ToTokens; + let funcall = parse_macro_input!(item as syn::Expr); + + match funcall { + syn::Expr::MethodCall(mc) => { + let receiver = &mc.receiver; + let args = (&mc.args).into_iter().collect::<Vec<&syn::Expr>>(); + let renamed_method = lazied_method_name(&mc.method); + + let tuple: syn::Expr; + if args.len() == 1 { + let typ = args[0].deref(); + tuple = syn::parse_quote! { (#typ,) }; + } else { + let args = args.iter().map(|t| t.deref()); + tuple = syn::parse_quote! { + (#(#args),*) + }; + } + + (quote::quote! { #receiver.#renamed_method(::lazycall_support::LazyVal::new(&mut || #tuple )) }).into() + }, + syn::Expr::Call(c) => { + let func = &c.func; + let renamed_func; + match func.deref() { + &syn::Expr::Path(ref ep) => { + let path = &ep.path; + let last = path.segments.last().unwrap(); + renamed_func = lazied_method_name(&last.ident); + }, + other => { println!("{:?}", other.to_token_stream()); unimplemented!() }, + } + let args = (&c.args).into_iter().collect::<Vec<&syn::Expr>>(); + + // We should be able to do this: + //let tuple = tuple_type_from_types(&args); + // but rustc has a bug: https://gist.github.com/dermesser/902c08436bb7241f1d560f42880e0376 + // so we do it manually. + + let tuple: syn::Expr; + if args.len() == 1 { + let typ = args[0].deref(); + tuple = syn::parse_quote! { (#typ,) }; + } else { + let args = args.iter().map(|t| t.deref()); + tuple = syn::parse_quote! { + (#(#args),*) + }; + } + let result = (quote::quote! { #renamed_func(::lazycall_support::LazyVal::new(&mut || #tuple )) }).into(); + result + }, + _ => syn::Error::new(funcall.span(), "syntax for lazycall!: lazycall!([receiver.]myfunction(a, b, c));").to_compile_error().into(), + } +}