#![allow(dead_code)]
use proc_macro2::{self, TokenStream};
use quote::ToTokens;
use syn;
use ast;
use attr;
use quote;
#[derive(Debug, Copy, Clone)]
pub enum BindingStyle {
Move,
MoveMut,
Ref,
RefMut,
}
impl BindingStyle {
fn with_packed(self, is_packed: bool) -> BindingStyle {
match self {
BindingStyle::Move | BindingStyle::MoveMut => self,
BindingStyle::Ref if is_packed => BindingStyle::Move,
BindingStyle::RefMut if is_packed => BindingStyle::MoveMut,
BindingStyle::Ref | BindingStyle::RefMut => self,
}
}
}
impl quote::ToTokens for BindingStyle {
fn to_tokens(&self, tokens: &mut TokenStream) {
match *self {
BindingStyle::Move => (),
BindingStyle::MoveMut => tokens.extend(quote!(mut)),
BindingStyle::Ref => tokens.extend(quote!(ref)),
BindingStyle::RefMut => {
tokens.extend(quote!(ref mut));
}
}
}
}
#[derive(Debug)]
pub struct BindingInfo<'a> {
pub expr: TokenStream,
pub ident: syn::Ident,
pub field: &'a ast::Field<'a>,
}
#[derive(Debug)]
pub struct CommonVariant<'a> {
path: syn::Path,
name: &'a syn::Ident,
style: ast::Style,
attrs: &'a attr::Input,
}
pub struct Matcher<T> {
binding_name: String,
binding_style: BindingStyle,
is_packed: bool,
field_filter: T,
}
impl Matcher<fn (&ast::Field) -> bool> {
pub fn new(style: BindingStyle, is_packed: bool) -> Self {
Matcher {
binding_name: "__arg".into(),
binding_style: style.with_packed(is_packed),
is_packed,
field_filter: |_| true,
}
}
}
impl<T: Fn (&ast::Field) -> bool> Matcher<T> {
pub fn with_name(self, name: String) -> Self {
Matcher {
binding_name: name,
..self
}
}
pub fn with_field_filter<P>(self, field_filter: P) -> Matcher<P> {
Matcher {
field_filter,
binding_name: self.binding_name,
binding_style: self.binding_style,
is_packed: self.is_packed,
}
}
pub fn build_arms<F>(self, input: &ast::Input, binding_name: &str, f: F) -> TokenStream
where
F: Fn(
syn::Path,
usize,
&syn::Ident,
ast::Style,
&attr::Input,
Vec<BindingInfo>,
) -> TokenStream,
{
let variants = self.build_match_pattern(input, binding_name);
let mut t = TokenStream::new();
for (i, (variant, (pat, bindings))) in variants.into_iter().enumerate() {
let body = f(
variant.path,
i,
variant.name,
variant.style,
variant.attrs,
bindings,
);
quote!(#pat => { #body }).to_tokens(&mut t);
}
t
}
pub fn build_2_arms<F>(
self,
(left_matched_expr, right_matched_expr): (TokenStream, TokenStream),
left: (&ast::Input, &str),
right: (&ast::Input, &str),
f: F,
) -> TokenStream
where
F: Fn(
usize,
CommonVariant,
CommonVariant,
(Vec<BindingInfo>, Vec<BindingInfo>),
) -> TokenStream,
{
let left_variants = self.build_match_pattern(left.0, left.1);
let right_variants = self.build_match_pattern(right.0, right.1);
assert_eq!(left_variants.len(), right_variants.len());
if left_variants.len() == 1 {
let (left, (left_pat, left_bindings)) = left_variants.into_iter().next().unwrap();
let (right, (right_pat, right_bindings)) = right_variants.into_iter().next().unwrap();
let body = f(0, left, right, (left_bindings, right_bindings));
quote! {
match #left_matched_expr {
#left_pat => match #right_matched_expr {
#right_pat => #body,
},
}
}
} else {
let mut t = TokenStream::new();
for (i, (left, right)) in left_variants.into_iter().zip(right_variants).enumerate() {
let (left, (left_pat, left_bindings)) = left;
let (right, (right_pat, right_bindings)) = right;
let body = f(i, left, right, (left_bindings, right_bindings));
quote!((#left_pat, #right_pat) => { #body }).to_tokens(&mut t);
}
quote! {
match (&#left_matched_expr, &#right_matched_expr) {
#t
_ => unreachable!(),
}
}
}
}
pub fn build_match_pattern<'a>(
&self,
input: &'a ast::Input,
binding_name: &str,
) -> Vec<(CommonVariant<'a>, (TokenStream, Vec<BindingInfo<'a>>))> {
let ident = &input.ident;
match input.body {
ast::Body::Enum(ref variants) => variants
.iter()
.map(|variant| {
let variant_ident = &variant.ident;
let path = parse_quote!(#ident::#variant_ident);
let pat = self.build_match_pattern_impl(
&path,
variant.style,
&variant.fields,
binding_name,
);
(
CommonVariant {
path,
name: variant_ident,
style: variant.style,
attrs: &variant.attrs,
},
pat,
)
})
.collect(),
ast::Body::Struct(style, ref vd) => {
let path = parse_quote!(#ident);
vec![(
CommonVariant {
path,
name: ident,
style,
attrs: &input.attrs,
},
self.build_match_pattern_impl(ident, style, vd, binding_name),
)]
}
}
}
fn build_match_pattern_impl<'a, N>(
&self,
name: &N,
style: ast::Style,
fields: &'a [ast::Field<'a>],
binding_name: &str,
) -> (TokenStream, Vec<BindingInfo<'a>>)
where
N: quote::ToTokens,
{
let (stream, matches) = match style {
ast::Style::Unit => (TokenStream::new(), Vec::new()),
ast::Style::Tuple => {
let (stream, matches) = fields.iter().enumerate().fold(
(TokenStream::new(), Vec::new()),
|(stream, matches), field| {
self.build_inner_pattern(
(stream, matches),
field,
binding_name,
|f, ident, binding| {
if (self.field_filter)(f) {
quote!(#binding #ident ,)
} else {
quote!(_ ,)
}
},
)
},
);
(quote! { ( #stream ) }, matches)
}
ast::Style::Struct => {
let (stream, matches) = fields.iter().enumerate().fold(
(TokenStream::new(), Vec::new()),
|(stream, matches), field| {
self.build_inner_pattern(
(stream, matches),
field,
binding_name,
|field, ident, binding| {
let field_name = field.ident.as_ref().unwrap();
if (self.field_filter)(field) {
quote!(#field_name : #binding #ident ,)
} else {
quote!(#field_name : _ ,)
}
},
)
},
);
(quote! { { #stream } }, matches)
}
};
let mut all_tokens = TokenStream::new();
name.to_tokens(&mut all_tokens);
all_tokens.extend(stream);
(all_tokens, matches)
}
fn build_inner_pattern<'a>(
&self,
(mut stream, mut matches): (TokenStream, Vec<BindingInfo<'a>>),
(i, field): (usize, &'a ast::Field),
binding_name: &str,
f: impl FnOnce(&ast::Field, &syn::Ident, BindingStyle) -> TokenStream,
) -> (TokenStream, Vec<BindingInfo<'a>>) {
let binding_style = self.binding_style;
let ident: syn::Ident = syn::Ident::new(
&format!("{}_{}", binding_name, i),
proc_macro2::Span::call_site(),
);
let expr = syn::Expr::Path(syn::ExprPath {
attrs: vec![],
qself: None,
path: syn::Path::from(ident.clone())
});
let expr = if self.is_packed {
expr.into_token_stream()
} else {
quote!((*#expr))
};
f(field, &ident, binding_style).to_tokens(&mut stream);
matches.push(BindingInfo {
expr,
ident,
field,
});
(stream, matches)
}
}