Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Varargs #1

Merged
merged 2 commits into from
Sep 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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! {
Expand Down Expand Up @@ -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<T>`, 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.
Expand Down
101 changes: 95 additions & 6 deletions multimethods_helper_proc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
#![feature(decl_macro)]
extern crate proc_macro;

use proc_macro as pm;
use proc_macro2 as pm2;
use syn::*;
use quote::*;

macro ident($str: literal$(, $expr: expr)*) {
Ident::new(&format!($str$(, $expr)*), pm2::Span::call_site())
}

const MAX_ARGS: usize = 12;

#[proc_macro]
pub fn impl_types(tokens: pm::TokenStream) -> pm::TokenStream {
let n: usize = tokens.to_string().parse().expect("impl_types! expects a number");
Expand All @@ -19,7 +26,7 @@ pub fn impl_types(tokens: pm::TokenStream) -> pm::TokenStream {

for i in 0..n {
let ix = syn::Index::from(i);
let tp = Ident::new(&format!("T{}", i), pm2::Span::call_site());
let tp = ident!("T{}", i);

type_parameters.push(tp.clone());
where_clauses.push(quote!(#tp: #types::SubType));
Expand All @@ -38,29 +45,30 @@ pub fn impl_types(tokens: pm::TokenStream) -> pm::TokenStream {
});
}

for i in n..12 {
type_ids.push(quote!(::std::any::TypeId::of::<!>()));
for i in n..MAX_ARGS {
is_refs.push(quote!(false));

let type_n = Ident::new(&format!("type{}", i), pm2::Span::call_site());
type_n_def.push(quote! {
fn #type_n(&self) -> #types::ConcreteType {
#types::ConcreteType {
id: ::std::any::TypeId::of::<!>(),
id: ::std::any::TypeId::of::<crate::value::Value>(),
is_ref: false,
parent: #types::ANY
}
}
});
}

let variant = Ident::new(&format!("T{}", n), pm2::Span::call_site());

quote!(
impl<#(#type_parameters),*> #types::Types for (#(#type_parameters,)*)
where
#(#where_clauses,)*
{
fn type_tuple(&self) -> #types::TypeTuple {
(#(#type_ids),*)
fn type_tuple(&self) -> #types::TypeIds {
#types::TypeIds::#variant(#(#type_ids),*)
}

fn has_ref(&self) -> bool {
Expand All @@ -72,3 +80,84 @@ pub fn impl_types(tokens: pm::TokenStream) -> pm::TokenStream {
).into()
}

struct VarargArgs {
e: Expr,
vararg: Ident,
positionals: Vec<Ident>
}

impl syn::parse::Parse for VarargArgs {
fn parse(stream: syn::parse::ParseStream) -> Result<Self> {
let e = stream.parse::<Expr>().unwrap();
stream.parse::<Token![,]>().unwrap();
let names = stream.parse_terminated::<Ident, Token![,]>(Ident::parse).unwrap();

let len = names.len();
let vararg = names.iter().last().unwrap().clone();
let positionals = names.into_iter().take(len-1).collect();

Ok(VarargArgs { e, vararg, positionals })
}
}

#[proc_macro]
pub fn match_vararg(tokens: pm::TokenStream) -> pm::TokenStream {
let VarargArgs { e, vararg, positionals } = parse_macro_input!(tokens as VarargArgs);

let tms = quote!(crate::types::TypeMatches);
let mut match_arms = Vec::new();

for i in positionals.len()..=MAX_ARGS {
let bs = (0..i).map(|j| ident!("b{}", j)).collect::<Vec<_>>();

// normal match
let variant = ident!("T{}", i);
let mut matches = Vec::new();

for j in 0..positionals.len() {
let a = positionals[j].clone();
let b = bs[j].clone();

matches.push(quote! {
#a.is_super_match(&#b)
});
}

for j in positionals.len()..i {
let b = bs[j].clone();

matches.push(quote! {
#vararg.is_super_match(&#b)
});
}

match_arms.push(quote! {
#tms::#variant(#(#bs),*) => true #(&& #matches)*,
});


// vararg match
if i < MAX_ARGS {
let variant = ident!("V{}", i+1);
matches.push(quote! {
#vararg.is_super_match(bv)
});

match_arms.push(quote! {
#tms::#variant(#(#bs,)* bv) => true #(&& #matches)*,
});
}
}

if positionals.len() > 0 {
match_arms.push(quote! {
_ => false
});
}

quote!(
match #e {
#(#match_arms)*
}
).into()
}
Loading