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(),
+    }
+}