From b63710492fdbdcaa66054e6eec120a2ac8211f99 Mon Sep 17 00:00:00 2001 From: Akshay Date: Tue, 5 May 2020 12:11:28 +0530 Subject: init --- .gitignore | 2 +- Cargo.toml | 18 ++++++++++++ src/lib.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 Cargo.toml create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore index 088ba6b..83c440c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Generated by Cargo # will have compiled files and executables -/target/ +**/target/** # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..bb0fb84 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "currying" +version = "0.1.0" +authors = ["Akshay "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +proc-macro2 = "1.0.9" +quote = "1.0" + +[dependencies.syn] +version = "1.0" +features = ["full"] + +[lib] +proc-macro = true diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8a030c4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,97 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::punctuated::Punctuated; +use syn::{parse_macro_input, Block, FnArg, ItemFn, Pat, ReturnType, Type}; + +#[proc_macro_attribute] +pub fn curry(_attr: TokenStream, item: TokenStream) -> TokenStream { + let parsed = parse_macro_input!(item as ItemFn); + generate_curry(parsed).into() +} + +fn extract_type(a: FnArg) -> Box { + match a { + FnArg::Typed(p) => p.ty, + _ => panic!("Not supported on types with `self`!"), + } +} + +fn extract_arg_pat(a: FnArg) -> Box { + match a { + FnArg::Typed(p) => p.pat, + _ => panic!("Not supported on types with `self`!"), + } +} + +fn extract_return_type(a: ReturnType) -> Box { + match a { + ReturnType::Type(_, p) => p, + _ => panic!("Not supported on functions without return types!"), + } +} + +fn extract_arg_types(fn_args: Punctuated) -> Vec> { + return fn_args.into_iter().map(extract_type).collect::>(); +} + +fn extract_arg_idents(fn_args: Punctuated) -> Vec> { + return fn_args.into_iter().map(extract_arg_pat).collect::>(); +} + +fn generate_type_aliases( + fn_arg_types: &[Box], + fn_return_type: Box, + fn_name: &syn::Ident, +) -> Vec { + let type_t0 = format_ident!("_{}_T0", fn_name); + let mut type_aliases = vec![quote! { type #type_t0 = #fn_return_type }]; + for (i, t) in (1..).zip(fn_arg_types.into_iter().rev()) { + let p = format_ident!("_{}_{}", fn_name, format!("T{}", i - 1)); + let n = format_ident!("_{}_{}", fn_name, format!("T{}", i)); + type_aliases.push(quote! { + type #n = impl Fn(#t) -> #p + }) + } + return type_aliases; +} + +fn generate_body(fn_args: &[Box], body: Box) -> proc_macro2::TokenStream { + quote! { + return #( move |#fn_args| )* #body + } +} + +fn generate_curry(parsed: ItemFn) -> proc_macro2::TokenStream { + let fn_body = parsed.block; + let sig = parsed.sig; + let vis = parsed.vis; + let fn_name = sig.ident; + let fn_args = sig.inputs; + let fn_return_type = sig.output; + + let arg_idents = extract_arg_idents(fn_args.clone()); + let first_ident = &arg_idents.first().unwrap(); + let curried_body = generate_body(&arg_idents[1..], fn_body.clone()); + + let arg_types = extract_arg_types(fn_args.clone()); + let first_type = &arg_types.first().unwrap(); + let type_aliases = generate_type_aliases( + &arg_types[1..], + extract_return_type(fn_return_type), + &fn_name, + ); + + let return_type = format_ident!("_{}_{}", &fn_name, format!("T{}", type_aliases.len() - 1)); + + let gen = quote! { + #(#type_aliases);* ; + #vis fn #fn_name (#first_ident: #first_type) -> #return_type { + #curried_body ; + } + }; + + println!("{}", gen); + return gen; +} -- cgit v1.2.3