From 66aa7023f7b7b04e9c89a25fb866f26c6159a924 Mon Sep 17 00:00:00 2001 From: phicr Date: Thu, 12 Sep 2019 13:42:44 -0300 Subject: [PATCH] Implement Varargs --- README.md | 33 +++++- multimethods_proc/src/lib.rs | 177 +++++++++++++++++++++++++-------- multimethods_tests/src/main.rs | 29 ++++++ src/main.rs | 40 +++++--- src/method.rs | 4 + src/types/abs.rs | 24 ++++- src/types/mod.rs | 3 + src/types/type_match_tree.rs | 56 ++++++++++- src/types/vararg.rs | 73 ++++++++++++++ 9 files changed, 379 insertions(+), 60 deletions(-) create mode 100644 src/types/vararg.rs diff --git a/README.md b/README.md index d5bb031..ff0454f 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ fn main() { } ``` -Methods that Return references requires the call to be expressed as `(FUNC.rr)(args...)`. Hopefully this won't be necessary in the future. (or I will find a nicer syntax, at least) +Methods that return references requires the call to be expressed as `(FUNC.rr)(args...)`. Hopefully this won't be necessary in the future. (or I will find a nicer syntax, at least) ```rust multifunction! { @@ -287,6 +287,37 @@ fn main() { } ``` +A variadic method can be defined using the special `Vararg![T]` macro. The type of the variadic +argument is `multimethods::types::vararg::Vararg`, which can be iterated through and indexed. + +```rust +// Vararg doesn't need to be imported as it's merely a marker for the multifunction! macro +use multimethods::multifunction; + +multifunction! { + fn SUM(args: Vararg![i32]) -> i32 { + args.iter().sum() + } +} + +// Vararg![] is equivalent to Vararg![Abstract![ANY]] +multifunction! { + fn PRINT_ALL(args: Vararg![]) { + for arg in args { + println!("{}", arg) + } + } +} + +fn main() { + println!("{}", SUM(1, 2, 3)); // 6 + + PRINT_ALL("a", 2); // a + // 2 +} +``` + + ## Limitations * Only up to 12 arguments per method are allowed. This number was chosen as it is the largest size of a tuple that has trait implementations for it in the standard library. diff --git a/multimethods_proc/src/lib.rs b/multimethods_proc/src/lib.rs index a0aa272..bcd9d7f 100644 --- a/multimethods_proc/src/lib.rs +++ b/multimethods_proc/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(decl_macro)] + extern crate proc_macro; use std::collections::HashMap; @@ -5,9 +7,14 @@ use proc_macro as pm; use proc_macro2 as pm2; use syn::*; use syn::punctuated::Punctuated; -use syn::token::{Paren, Comma}; use quote::*; +macro ident($str: literal$(, $expr: expr)*) { + Ident::new(&format!($str$(, $expr)*), pm2::Span::call_site()) +} + +const MAX_ARGS: usize = 12; + struct Method { public: bool, expr: pm2::TokenStream, @@ -19,7 +26,7 @@ struct Keys(Vec); impl syn::parse::Parse for Keys { fn parse(input: syn::parse::ParseStream) -> Result { - let punct = >::parse_terminated(input).unwrap(); + let punct = >::parse_terminated(input).unwrap(); Ok(Keys(punct.into_iter().collect())) } } @@ -216,18 +223,29 @@ fn method_defs<'a, I: Iterator>(item_fns: I, fmc: bool) -> Meth let mut methods = Methods::new(); for item_fn in item_fns { - let root = root(fmc); let name = item_fn.sig.ident.clone(); - let num_args = item_fn.sig.inputs.len(); - let is_abstract = has_abstract_type(args(&item_fn.sig)); - let types = types(args(&item_fn.sig), &item_fn.sig.output, is_abstract, fmc); - let closure = create_closure(&item_fn, fmc); - let insertion = get_insertion_function(is_abstract); - let variant = get_variant(num_args, &item_fn.sig.output); - let inner_func = get_inner_function(num_args, &item_fn.sig.output, fmc); - let constructor = get_inner_constructor(args(&item_fn.sig), &item_fn.sig.output); - let inner_trait = get_inner_trait(args(&item_fn.sig), &item_fn.sig.output, fmc); + let has_vararg = has_vararg_type(args(&item_fn.sig)); + + let is_abstract; + let match_value; + let insertion; + if has_vararg { + let funcs = vararg_functions(&item_fn, fmc); + let values = funcs.iter().map(|f| function(f,fmc)); + let positionals = item_fn.sig.inputs.len() - 1; + + is_abstract = true; + match_value = quote!((#positionals, ::std::vec![#(#values),*])); + insertion = quote!(insert_vararg); + + } else { + is_abstract = has_abstract_type(args(&item_fn.sig)); + match_value = function(item_fn, fmc); + insertion = get_insertion_function(is_abstract); + } + + let types = types(args(&item_fn.sig), &item_fn.sig.output, is_abstract, has_vararg, fmc); if !methods.contains_key(&name) { methods.insert(name.clone(), Vec::new()); @@ -238,28 +256,60 @@ fn method_defs<'a, I: Iterator>(item_fns: I, fmc: bool) -> Meth of_func.push( Method { public: if let Visibility::Public(_) = item_fn.vis { true } else { false }, - - expr: if num_args == 0 { - quote! { - table.#insertion( - #types, - #root Function::#variant(#inner_func::new(#closure)) - ) - } - } else { - quote! { - table.#insertion( - #types, - #root Function::#variant(<#inner_func as #inner_trait>::#constructor(#closure)) - ) - } - } + expr: quote!(table.#insertion(#types, #match_value)) } ); } methods } + +fn vararg_functions(origin: &ItemFn, fmc: bool) -> Vec { + let root = root(fmc); + let vis = &origin.vis; + let body = &origin.block; + let output = &origin.sig.output; + let num_args = origin.sig.inputs.len() - 1; + let p_args = args(&origin.sig).take(num_args).collect::>(); + let p_names = args(&origin.sig).take(num_args).map(arg_name).collect::>(); + let vararg_arg = origin.sig.inputs.iter().last().unwrap(); + let vararg_name = arg_name(vararg_arg.clone()); + let vararg_type = vararg(&arg_type(vararg_arg.clone()), fmc); + + let mut functions = Vec::new(); + + for i in (num_args..=MAX_ARGS).rev() { + let vs = (i..MAX_ARGS).map(|j| ident!("__VArg_Multimethods_{}", j)).collect::>(); + + functions.push(parse2(quote! { + #vis fn _f(#(#p_args,)* #(#vs: #vararg_type),*) #output { + let __VarargCall = |#(#p_args,)* #vararg_name: #root Vararg<#vararg_type>| #body; + __VarargCall(#(#p_names,)* #root Vararg::new(::std::vec![#(#vs),*])) + } + }).unwrap()); + } + functions +} + + +fn function(item_fn: &ItemFn, fmc: bool) -> pm2::TokenStream { + let root = root(fmc); + let num_args = item_fn.sig.inputs.len(); + let closure = create_closure(&item_fn, fmc); + let variant = get_variant(num_args, &item_fn.sig.output); + let inner_func = get_inner_function(num_args, &item_fn.sig.output, fmc); + let constructor = get_inner_constructor(args(&item_fn.sig), &item_fn.sig.output); + let inner_trait = get_inner_trait(args(&item_fn.sig), &item_fn.sig.output, fmc); + + if num_args == 0 { + quote!(#root Function::#variant(#inner_func::new(#closure))) + + } else { + quote!(#root Function::#variant(<#inner_func as #inner_trait>::#constructor(#closure))) + } +} + + fn is_public>(vis: I) -> bool { let mut public = None; @@ -301,12 +351,12 @@ fn args(sig: &Signature) -> impl Iterator { } -fn types(inputs: I, output: &ReturnType, is_abs: bool, fmc: bool) -> pm2::TokenStream +fn types(inputs: I, output: &ReturnType, is_abs: bool, has_var: bool, fmc: bool) -> pm2::TokenStream where I: Iterator { if is_abs { - type_matches(inputs, output, fmc) + type_matches(inputs, output, has_var, fmc) } else { type_ids(inputs, output, fmc) @@ -329,7 +379,7 @@ fn type_ids(inputs: I, output: &ReturnType, fmc: bool) -> pm2::TokenStream types.push(quote!(<#ty as #root TypeOf>::associated_type_of())); } - let variant = Ident::new(&format!("T{}", types.len()), pm2::Span::call_site()); + let variant = ident!("T{}", types.len()); let returns_ref = is_ref_return(output); quote! { @@ -338,7 +388,7 @@ fn type_ids(inputs: I, output: &ReturnType, fmc: bool) -> pm2::TokenStream } -fn type_matches(inputs: I, output: &ReturnType, fmc: bool) -> pm2::TokenStream +fn type_matches(inputs: I, output: &ReturnType, has_var: bool, fmc: bool) -> pm2::TokenStream where I: Iterator { @@ -351,14 +401,17 @@ fn type_matches(inputs: I, output: &ReturnType, fmc: bool) -> pm2::TokenStrea for input in inputs { let ty = arg_type(input); + let ty = if let Some(vty) = vararg(&ty, fmc) { vty } else { ty }; + if let Some(aty) = abstract_type(&ty) { types.push(quote!(#type_match::Abstract(#aty))); + } else { types.push(quote!(#type_match::Concrete(<#ty as #sub_type>::#assoc_type()))); } } - let variant = Ident::new(&format!("T{}", types.len()), pm2::Span::call_site()); + let variant = ident!("{}{}", if has_var {"V"} else {"T"}, types.len()); let returns_ref = is_ref_return(output); quote! { @@ -459,6 +512,7 @@ fn get_inner_trait(inputs: I, output: &ReturnType, fmc: bool) -> pm2::TokenSt } } + fn has_abstract_type(inputs: I) -> bool where I: Iterator @@ -471,6 +525,20 @@ fn has_abstract_type(inputs: I) -> bool false } +fn has_vararg_type(inputs: I) -> bool + where + I: Iterator +{ + for input in inputs { + if vararg(&arg_type(input), false).is_some() { + return true; + } + } + false +} + + + fn root(fmc: bool) -> pm2::TokenStream { if fmc { quote!() @@ -479,15 +547,20 @@ fn root(fmc: bool) -> pm2::TokenStream { } } +fn arg_name(arg: FnArg) -> Pat { + if let FnArg::Typed(pat) = arg { + *pat.pat + } else { + panic!("methods cannot have a `self` argument") + } +} + fn arg_type(arg: FnArg) -> Type { if let FnArg::Typed(pat) = arg { *pat.ty } else { - Type::Tuple(TypeTuple { - elems: Punctuated::new(), - paren_token: Paren { span: pm2::Span::call_site() } - }) + panic!("methods cannot have a `self` argument") } } @@ -534,14 +607,13 @@ fn is_ref(ty: &Type) -> bool { } } -fn abstract_type(ty: &Type) -> Option { +fn abstract_type(ty: &Type) -> Option { match ty { Type::Paren(t) => abstract_type(&*t.elem), Type::Macro(m) => { if path_ends_with(&m.mac.path, "Abstract") { - let tokens = m.mac.tokens.clone(); - parse2::(tokens).ok() + parse2::(m.mac.tokens.clone()).ok() } else { None @@ -552,6 +624,30 @@ fn abstract_type(ty: &Type) -> Option { } } +fn vararg(ty: &Type, fmc: bool) -> Option { + match ty { + Type::Paren(t) => vararg(&*t.elem, fmc), + + Type::Macro(m) => { + if path_ends_with(&m.mac.path, "Vararg") { + if m.mac.tokens.is_empty() { + let root = root(fmc); + parse2(quote!(#root Abstract![#root ANY])).ok() + + } else { + parse2(m.mac.tokens.clone()).ok() + } + + } else { + None + } + } + _ => None + } +} + + + fn path_ends_with(p: &Path, s: &str) -> bool { if let Some(segment) = p.segments.iter().last() { segment.ident.to_string() == s.to_string() @@ -561,7 +657,6 @@ fn path_ends_with(p: &Path, s: &str) -> bool { } } - #[allow(dead_code)] fn arg_has_attr(f: &FnArg, attr: &str) -> bool { match f { diff --git a/multimethods_tests/src/main.rs b/multimethods_tests/src/main.rs index 465443f..c22bcbd 100644 --- a/multimethods_tests/src/main.rs +++ b/multimethods_tests/src/main.rs @@ -293,6 +293,35 @@ mod readme { assert_eq!(IS_MY_COLLECTION(coll2), true); } } + + mod vararg { + use multimethods::{multifunction, FromValue}; + + multifunction! { + fn SUM(args: Vararg![i32]) -> i32 { + args.iter().sum() + } + } + + multifunction! { + fn PRINT_ALL(args: Vararg![]) -> Vec { + let mut result = Vec::new(); + for arg in args { + result.push(format!("{}", arg)) + } + result + } + } + + #[test] + fn readme_vararg() { + assert_eq!(SUM(1, 2, 3), 6); + assert_eq!( + >::from_value(PRINT_ALL("a", 2)), + vec!["a".to_string(), "2".to_string()] + ); + } + } } fn main() { diff --git a/src/main.rs b/src/main.rs index 5059305..6f2459d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,6 +34,29 @@ pub use conversion::*; use multimethods_proc::*; +#[__fmc] +multifunction! { + fn var(args: Vararg![i32]) { + for (i, arg) in args.iter().enumerate() { + println!("{}: {}", i, arg); + } + } + + fn var(a: i64, args: Vararg![i64]) { + println!("a: {}", a); + for (i, arg) in args.iter().enumerate() { + println!("{}: {}", i, arg); + } + } + + fn var(a: i64, args: Vararg![]) { + println!("ABSTRACT a: {}", a); + for (i, arg) in args.iter().enumerate() { + println!("ABSTRACT {}: {}", i, arg); + } + } +} + #[__fmc] multifunction! { fn hi() -> String { @@ -95,13 +118,6 @@ multifunction! { } } -#[__fmc] -multifunction! { - fn sunga(a: i32) -> i32 { - a + 1 - } -} - #[__fmc] multifunction! { fn debugin(a: i32) -> String { @@ -171,9 +187,9 @@ fn main() { println!("hi0: {}", hi()); println!("hi1: {}", hi(String::from("john"))); - let mut k = 0i32.into_value(); - for _ in 0..=200_000 { - k = sunga(k); - } - println!("{}", k); + var(); + var(1); + var(1,2); + var(1i64, 2i64); + var(1i64, 1, 2); } diff --git a/src/method.rs b/src/method.rs index 9a54e43..5411ca3 100644 --- a/src/method.rs +++ b/src/method.rs @@ -113,6 +113,10 @@ impl MethodTable { self.abstracts.insert(key.0, value, key.1); } + pub fn insert_vararg(&mut self, key: AbstractTypeKey, (p, values): (usize, Vec)) { + self.abstracts.insert_vararg(key.0, p, values, key.1); + } + pub fn get(&self, key: T) -> &Method { get_method!(self, key, get) } diff --git a/src/types/abs.rs b/src/types/abs.rs index 7078958..b65c47f 100644 --- a/src/types/abs.rs +++ b/src/types/abs.rs @@ -52,7 +52,7 @@ pub macro impl_abstract_type($($type: ty: $abstract: expr),*$(,)?) { } -pub macro Abstract($t: ident) { +pub macro Abstract($t: path) { Value } @@ -217,6 +217,28 @@ impl AsTypeMatches for T { } } +impl TypeMatches { + pub fn len(&self) -> usize { + use TypeMatches::*; + match self { + T0() => 0, + T1(..) => 1, + T2(..) => 2, + T3(..) => 3, + T4(..) => 4, + T5(..) => 5, + T6(..) => 6, + T7(..) => 7, + T8(..) => 8, + T9(..) => 9, + T10(..) => 10, + T11(..) => 11, + T12(..) => 12, + _ => panic!("unknown size") + } + } +} + pub fn matches_all (a: &TypeMatches, b: &T) -> bool { use TypeMatches::*; diff --git a/src/types/mod.rs b/src/types/mod.rs index 2fc2507..9479b55 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -15,6 +15,9 @@ pub use abstract_impl::*; pub mod type_value; pub use type_value::*; +pub mod vararg; +pub use vararg::*; + type TI = TypeId; #[derive(Debug,Clone,PartialEq,Eq,Hash)] pub enum TypeIds { diff --git a/src/types/type_match_tree.rs b/src/types/type_match_tree.rs index 905fe2e..d6a65eb 100644 --- a/src/types/type_match_tree.rs +++ b/src/types/type_match_tree.rs @@ -1,7 +1,12 @@ use crate::types::*; +pub enum MatchValue { + Single(T), + Vararg(usize, Vec) +} + pub struct TypeMatchNode { - value: T, + value: MatchValue, type_match: TypeMatches, children: Vec> } @@ -31,6 +36,10 @@ impl TypeMatchTree { insert_to_children(self.children_mut(rr), TypeMatchNode::new(key, value)); } + pub fn insert_vararg(&mut self, key: TypeMatches, p: usize, values: Vec, rr: bool) { + insert_to_children(self.children_mut(rr), TypeMatchNode::new_vararg(key, p, values)); + } + pub fn get<'a>(&'a self, key: &TypeMatches, rr: bool) -> Option<&'a T> { get_from_children(self.children(rr), key) } @@ -47,13 +56,50 @@ impl TypeMatchTree { impl TypeMatchNode { fn new(key: TypeMatches, value: T) -> Self { TypeMatchNode { - value, + value: MatchValue::Single(value), + type_match: key, + children: Vec::new() + } + } + + fn new_vararg(key: TypeMatches, positionals: usize, values: Vec) -> Self { + TypeMatchNode { + value: MatchValue::Vararg(positionals, values), type_match: key, children: Vec::new() } } } +impl MatchValue { + fn get<'a>(&'a self, key: &TypeMatches) -> &'a T { + match self { + MatchValue::Single(value) => value, + MatchValue::Vararg(n,values) => { + &values[key.len() - n] + } + } + } + + fn get_mut<'a>(&'a mut self, key: &TypeMatches) -> &'a mut T { + match self { + MatchValue::Single(value) => value, + MatchValue::Vararg(n,values) => { + &mut values[key.len() - *n] + } + } + } + + fn remove(self, key: &TypeMatches) -> T { + match self { + MatchValue::Single(value) => value, + MatchValue::Vararg(n, mut values) => { + values.remove(key.len() - n) + } + } + } +} + fn insert_to_children( children: &mut Vec>, mut node: TypeMatchNode) { @@ -100,7 +146,7 @@ fn get_from_children<'a, T>( return Some(value); } else { - return Some(&child.value); + return Some(child.value.get(key)); } } } @@ -116,7 +162,7 @@ fn get_from_children_mut<'a, T>( return Some(value) } else { - return Some(&mut child.value) + return Some(child.value.get_mut(key)) } } } @@ -134,7 +180,7 @@ fn remove_from_children<'a, T> ( } else { let mut m = children.remove(i); children.append(&mut m.children); - return Some(m.value); + return Some(m.value.remove(key)); } } } diff --git a/src/types/vararg.rs b/src/types/vararg.rs new file mode 100644 index 0000000..2e6e08c --- /dev/null +++ b/src/types/vararg.rs @@ -0,0 +1,73 @@ +use std::ops::*; +use std::iter::*; + +pub struct Vararg { + elements: Vec +} + +pub macro Vararg { + () => { Abstract![ANY] }, + ($T: ty) => { $T } +} + +impl Vararg { + pub fn new(elements: Vec) -> Self { + Vararg { elements } + } + + pub fn iter(&self) -> impl Iterator { + self.elements.iter() + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.elements.iter_mut() + } +} + +impl<'a, T> IntoIterator for &'a Vararg { + type Item = &'a T; + type IntoIter = <&'a Vec as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + (&self.elements).into_iter() + } +} + +impl<'a, T> IntoIterator for &'a mut Vararg { + type Item = &'a mut T; + type IntoIter = <&'a mut Vec as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + (&mut self.elements).into_iter() + } +} + +impl<'a, T> IntoIterator for Vararg { + type Item = T; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.elements.into_iter() + } +} + +impl Index for Vararg + where + I: std::slice::SliceIndex<[T]> +{ + type Output = as Index>::Output; + + fn index(&self, index: I) -> &Self::Output { + &self.elements[index] + } +} + +impl IndexMut for Vararg + where + I: std::slice::SliceIndex<[T]> +{ + fn index_mut(&mut self, index: I) -> &mut Self::Output { + &mut self.elements[index] + } +} +