interact_derive 0.3.1

A framework for online program state introspection
Documentation
#![recursion_limit = "256"]
extern crate proc_macro;
extern crate syn;

use proc_macro::TokenStream;
use proc_macro2::TokenStream as Tokens;
use proc_macro2::{Delimiter, Ident, Span, TokenTree};
use std::collections::{BTreeMap, HashSet};
use std::process::Command;
use syn::Meta::{List, Word};
use syn::NestedMeta::Meta;
use syn::{Data, DeriveInput, Fields, GenericParam, Generics};

#[macro_use]
extern crate quote;

struct DeriveInfo {
    from_interact: bool,
    opaque: bool,
    basic: bool,
}

/// # Attributes that Interact derive macro supports
///
/// ## Type context:
///
/// ```ignore
/// #[interact(mut_fn(function_name(param_a, param_b)))
/// #[interact(immut_fn(function_name(param_a, param_b)))
/// ```
///
/// ### Per field:
///
/// The ignore attribute allows to make some fields invisible:
/// ```ignore
/// #[interact(ignore))
/// ```
///
/// The downside is that having any ignored field on a type means that it is unbuildable, and
/// therefore cannot be passed as value to functions or to be assigned using `=` in
/// an expression.
#[proc_macro_derive(Interact, attributes(interact))]
pub fn derive_interact(input: TokenStream) -> TokenStream {
    derive_interact_inner(
        input,
        DeriveInfo {
            from_interact: false,
            opaque: false,
            basic: false,
        },
    )
}

#[proc_macro]
pub fn derive_interact_prelude(input: TokenStream) -> TokenStream {
    derive_interact_inner(
        input,
        DeriveInfo {
            from_interact: true,
            opaque: false,
            basic: true,
        },
    )
}

#[proc_macro]
pub fn derive_interact_opaque(input: TokenStream) -> TokenStream {
    derive_interact_inner(
        input,
        DeriveInfo {
            from_interact: true,
            opaque: true,
            basic: false,
        },
    )
}

#[proc_macro]
pub fn derive_interact_extern_opqaue(input: TokenStream) -> TokenStream {
    derive_interact_inner(
        input,
        DeriveInfo {
            from_interact: false,
            opaque: true,
            basic: false,
        },
    )
}

#[proc_macro]
pub fn derive_interact_basic(input: TokenStream) -> TokenStream {
    derive_interact_inner(
        input,
        DeriveInfo {
            from_interact: true,
            opaque: true,
            basic: true,
        },
    )
}

fn derive_interact_inner(input: TokenStream, info: DeriveInfo) -> TokenStream {
    let input: DeriveInput = syn::parse(input).unwrap();
    let name = &input.ident;

    let expanded = inner_derive_interact(&input, info);

    if let Some((_, value)) =
        std::env::vars().find(|(key, _)| key.as_str() == "INTERACT_DERIVE_SAVE_DIR")
    {
        let dir = std::path::Path::new(value.as_str());
        tokens_to_rustfmt_file(&dir.join(format!("interact_derive_{}.rs", name)), &expanded);
    }

    expanded.into()
}

fn inner_derive_interact(input: &DeriveInput, info: DeriveInfo) -> Tokens {
    let name = &input.ident;
    let kr = if info.from_interact {
        quote! { crate }
    } else {
        quote! { crate::interact }
    };

    let (mut_assign, deser_impls) = impls_for_deser(&kr, &input, &info);
    let access_impls = impls_for_access(&input, mut_assign);
    let reflect_impls = impls_for_reflect(&input, &info);
    let uses = if info.basic {
        quote! {}
    } else {
        quote! {use super::#name;}
    };
    let module_name = Ident::new(&format!("impls_for_{}", name), Span::call_site());

    quote! {
        mod #module_name {
            #uses

            use #kr::*;

            #access_impls
            #reflect_impls
            #deser_impls
        }
    }
}

#[derive(Debug)]
struct Function {
    mutability: Mutability,
    name: String,
    args: Vec<String>,
}

type FuncMap = BTreeMap<String, Function>;

fn fill_skip_bound_from_attr(attribute: Option<TokenTree>, skip_bound_set: &mut HashSet<String>) {
    let tt = if let Some(tt) = attribute {
        tt
    } else {
        panic!("Expected a token tree after skip_bound term");
    };

    if let TokenTree::Group(group) = tt {
        let mut ts = group.stream().into_iter();
        if let Some(TokenTree::Ident(s)) = ts.next() {
            skip_bound_set.insert(s.to_string());
        } else {
            panic!("Expected term in attribute");
        }
    }
}

fn get_attr_info(
    attribute: &syn::Attribute,
    map: &mut FuncMap,
    skip_bound_set: &mut HashSet<String>,
    mut_assign: &mut bool,
) {
    for func in attribute.tts.clone().into_iter() {
        let ts = match &func {
            TokenTree::Punct(_punct) => {
                // Skip comments
                continue;
            }
            TokenTree::Group(group) if group.delimiter() == Delimiter::Parenthesis => {
                group.stream()
            }
            _ => {
                panic!("extend () in `interact` data type attribute `{:?}`", func);
            }
        };

        let mut ts = ts.clone().into_iter();
        let mutability_term = if let Some(TokenTree::Ident(term)) = ts.next() {
            term.to_string()
        } else {
            panic!("expected mut_fn/immut_fn term in data type attribute")
        };

        let mutability = match mutability_term.as_str() {
            "mut_fn" => Mutability::ModifyAccess,
            "immut_fn" => Mutability::ReadAccess,
            "skip_bound" => {
                fill_skip_bound_from_attr(ts.next(), skip_bound_set);
                continue;
            }
            "mut_assign" => {
                *mut_assign = true;
                continue;
            }
            _ => panic!(
                "Invalid term {} in `{}`",
                mutability_term.as_str(),
                attribute.tts.to_string()
            ),
        };

        let fn_def = if let Some(fn_def) = ts.next() {
            fn_def
        } else {
            panic!(
                "Expected a token tree after mutability term in `{}`",
                attribute.tts.to_string()
            );
        };

        if let TokenTree::Group(group) = fn_def {
            if group.delimiter() == Delimiter::Parenthesis {
                let mut ts = group.stream().into_iter();

                let fn_name = if let Some(TokenTree::Ident(term)) = ts.next() {
                    term.to_string()
                } else {
                    panic!("Expected term in attribute `{}`", attribute.tts.to_string());
                };

                let name = String::from(fn_name.as_str());
                let mut func = Function {
                    name: name.clone(),
                    mutability,
                    args: vec![],
                };

                let fn_def = if let Some(fn_def) = ts.next() {
                    fn_def
                } else {
                    panic!(
                        "Expected parameter specification () after {} in `{}`",
                        fn_name.as_str(),
                        attribute.tts.to_string()
                    );
                };

                if let TokenTree::Group(group) = fn_def {
                    if group.delimiter() == Delimiter::Parenthesis {
                        for tt in group.stream().into_iter() {
                            if let TokenTree::Ident(term) = tt {
                                func.args.push(term.to_string());
                            } else if let TokenTree::Punct(ref punc) = tt {
                                if punc.as_char() == ',' {
                                    continue;
                                } else {
                                    panic!("Unexpected parameter token {:?}", tt);
                                }
                            } else {
                                panic!(
                                    "Expected term in attribute `{}`",
                                    attribute.tts.to_string()
                                );
                            }
                        }
                    }
                }

                if map.get(&name).is_some() {
                    panic!("Duplicate name {}", name.as_str());
                }

                map.insert(name, func);
            }
        }
    }
}

fn call_impls(fnmap: &FuncMap, mutability: Mutability) -> (Tokens, Vec<Tokens>) {
    let mut arms = vec![];
    let mut descs = vec![];

    for func in fnmap.values() {
        let name = &func.name;
        let name_ident = Ident::new(name.as_str(), Span::call_site());
        let mut match_vec = vec![];
        let mut arg_vec = vec![];
        let mut arg_vec_unpack = vec![];
        let mut arg_str_vec = vec![];

        for arg in &func.args {
            arg_str_vec.push(arg.as_str());
            match_vec.push({
                let v = syn::token::Underscore::default();
                if func.args.len() == 1 {
                    quote! { #v, }
                } else {
                    quote! { #v }
                }
            });
            arg_vec.push(Ident::new(arg.as_str(), Span::call_site()));
            arg_vec_unpack.push({
                let v = Ident::new(arg.as_str(), Span::call_site());
                if func.args.len() == 1 {
                    quote! { #v, }
                } else {
                    quote! { #v }
                }
            });
        }
        let arg_vec_ref = &arg_vec;
        let arg_vec_unpack_ref = &arg_vec_unpack;
        let match_vec = &match_vec;

        let mut call_impl = quote! {
            <(#(#match_vec),*)>::deser(&mut _climber.borrow_tracker()).map(|(#(#arg_vec_unpack_ref),*)| {
                if !_climber.is_probe_only() {
                    let _retval = self.#name_ident(#(#arg_vec_ref),*);
                    (_retcall)(&_retval, _climber);
                }
            }).map_err(CallError::Deser)
        };
        if mutability == Mutability::ReadAccess {
            if func.mutability == Mutability::ModifyAccess {
                call_impl = quote! {
                    <(#(#match_vec),*)>::deser(&mut _climber.borrow_tracker()).map(|(#(#arg_vec_unpack_ref),*)| {
                        if !_climber.is_probe_only() {
                            // Non executing unsafe code only for coercing type checking
                            if false {
                                let mself : &mut Self = unsafe { std::mem::uninitialized() };
                                let _ = mself.#name_ident(#(#arg_vec_ref),*);
                            }
                        }
                    }).map_err(CallError::Deser)?;

                    return Err(CallError::NeedMutable);
                };
            }
        }

        arms.push(quote! {
            #name => {
                #call_impl
            }
        });

        descs.push(quote! {
            Function {
                name: #name,
                args: &[#(#arg_str_vec),*],
            }
        });
    }

    let arms = if arms.len() != 0 {
        quote! { #(#arms),* }
    } else {
        quote! {}
    };

    (
        quote! {
            match func_name {
                #arms
                _ => return Err(CallError::NoSuchFunction),
            }
        },
        descs,
    )
}

fn impls_for_access(input: &DeriveInput, mut mut_assign: bool) -> Tokens {
    let name = &input.ident;
    let mut skip_bound_set = HashSet::new();
    let mut fnmap = BTreeMap::new();

    for attribute in &input.attrs {
        get_attr_info(attribute, &mut fnmap, &mut skip_bound_set, &mut mut_assign);
    }
    let generics = add_trait_bounds(
        input.generics.clone(),
        &skip_bound_set,
        &["Access", "Deser"],
    );
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    let code = {
        quote! { Reflect::Direct(self) }
    };
    let code_mut = {
        quote! { ReflectMut::Direct(self) }
    };

    let (immut_call_impls, immut_call_desc) = call_impls(&fnmap, Mutability::ReadAccess);
    let (mut_call_impls, mut_call_desc) = call_impls(&fnmap, Mutability::ModifyAccess);

    let mut_assign_code = if mut_assign {
        quote! {
            fn mut_assign<'a, 'b>(
                &mut self,
                _tracker: &mut deser::Tracker<'a, 'b>,
                _probe_only: bool,
            ) -> Result<(), AssignError> {
                deser_assign(self, _tracker, _probe_only)
            }
        }
    } else {
        quote! {}
    };

    quote! {
        impl #impl_generics Access for #name #ty_generics #where_clause {
            fn immut_access(&self) -> ImmutAccess {
                ImmutAccess {
                    reflect: #code,
                    functions: &[#(#immut_call_desc),*],
                }
            }

            fn mut_access(&mut self) -> MutAccess {
                MutAccess {
                    reflect: #code_mut,
                    functions: &[#(#mut_call_desc),*],
                }
            }

            fn immut_call<'a>(&self, func_name: &'static str,
                              _climber: &mut Climber<'a>,
                              mut _retcall: RetValCallback<'a>) -> Result<(), CallError> {
                #immut_call_impls
            }

            fn mut_call<'a>(&mut self, func_name: &'static str,
                            _climber: &mut Climber<'a>,
                            mut _retcall: RetValCallback<'a>) -> Result<(), CallError> {
                #mut_call_impls
            }

            #mut_assign_code
        }
    }
}

#[derive(Debug, Eq, PartialEq)]
enum Mutability {
    ModifyAccess,
    ReadAccess,
}

fn impls_by_mutability(
    name: syn::Ident,
    data_fields: &Fields,
    in_enum: bool,
    mtype: Mutability,
) -> (Tokens, Tokens, Tokens, Tokens) {
    let mut named_match_arms: Vec<Tokens> = vec![];
    let mut index_match_arms: Vec<Tokens> = vec![];
    let desc: Tokens;
    let name = format!("{}", format!("{}", name));
    let params;

    let qmut = match mtype {
        Mutability::ReadAccess => quote! {},
        Mutability::ModifyAccess => quote! { mut },
    };

    match data_fields {
        Fields::Named(ref fields) => {
            let mut idents: Vec<syn::Ident> = vec![];
            let fnames: Vec<_> = fields
                .named
                .iter()
                .filter(|f| !is_skipped(&f.attrs))
                .map(|f| {
                    let ident = &f.ident;
                    idents.push(ident.as_ref().unwrap().clone());

                    let f_i = if in_enum {
                        quote! { #ident }
                    } else {
                        quote! { & #qmut self.#ident }
                    };

                    (f_i, format!("{}", ident.as_ref().unwrap()))
                })
                .collect();

            let fnames_stringified: Vec<_> =
                fnames.iter().map(|(_, ref x)| quote! { #x }).collect();

            desc = quote! {
                Struct {
                    name: #name,
                    kind: StructKind::Fields(&[#(#fnames_stringified),*])
                }
            };

            named_match_arms = fnames
                .iter()
                .map(|(ref y, ref x)| {
                    quote! {
                        #x => Some(#y)
                    }
                })
                .collect();
            params = quote! {{#(#idents),*}};
        }
        Fields::Unnamed(ref fields) => {
            let mut idents: Vec<Tokens> = vec![];
            let mut i: usize = 0;

            let fnames: Vec<_> = fields
                .unnamed
                .iter()
                .filter(|f| !is_skipped(&f.attrs))
                .map(|_| {
                    let f_i = if in_enum {
                        let ident = Ident::new(&format!("f_{}", i), Span::call_site());
                        quote! { #ident }
                    } else {
                        quote! { & #qmut self.#i }
                    };

                    idents.push(f_i.clone());

                    let r = (quote! { #i }, f_i);
                    i += 1;
                    r
                })
                .collect();

            let n = fnames.len();
            desc = quote! {
                Struct{
                    name: #name,
                    kind: StructKind::Tuple(#n)
                }
            };

            index_match_arms = fnames
                .iter()
                .map(|(idx, ref x)| {
                    quote! { #idx => Some(#x) }
                })
                .collect();
            params = quote! {(#(#idents),*)};
        }
        Fields::Unit => {
            desc = quote! {
                Struct {
                    name: #name,
                    kind: StructKind::Unit,
                }
            };
            params = quote! {};
        }
    }

    let named_match_arms = if named_match_arms.len() != 0 {
        quote! { #(#named_match_arms),*, }
    } else {
        quote! {}
    };

    let index_match_arms = if index_match_arms.len() != 0 {
        quote! { #(#index_match_arms),*, }
    } else {
        quote! {}
    };

    (named_match_arms, index_match_arms, desc, params)
}

fn impls_for_reflect(input: &DeriveInput, info: &DeriveInfo) -> Tokens {
    if info.opaque {
        return quote! {};
    }

    let name = &input.ident;
    let generics = add_trait_bounds(input.generics.clone(), &HashSet::new(), &["Access"]);
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
    let str_name = format!("{}", format!("{}", name));

    match &input.data {
        Data::Struct(data) => {
            let (named_match_arms, index_match_arms, desc, _) =
                impls_by_mutability(name.clone(), &data.fields, false, Mutability::ReadAccess);
            let (named_match_mut_arms, index_match_mut_arms, _, _) =
                impls_by_mutability(name.clone(), &data.fields, false, Mutability::ModifyAccess);

            quote! {
                impl #impl_generics ReflectStruct for #name #ty_generics #where_clause {
                    fn get_desc(&self) -> Struct {
                        #desc
                    }

                    fn get_field_by_name(&self, name: &'static str) -> Option<&dyn Access> {
                        match name {
                            #named_match_arms
                            _ => None,
                        }
                    }

                    fn get_field_by_idx(&self, idx: usize) -> Option<&dyn Access> {
                        match idx {
                            #index_match_arms
                            _ => None,
                        }
                    }

                    fn get_field_by_name_mut(&mut self, name: &'static str) -> Option<&mut dyn Access> {
                        match name {
                            #named_match_mut_arms
                            _ => None,
                        }
                    }

                    fn get_field_by_idx_mut(&mut self, idx: usize) -> Option<&mut dyn Access> {
                        match idx {
                            #index_match_mut_arms
                            _ => None,
                        }
                    }
                }

                impl #impl_generics ReflectDirect for #name #ty_generics #where_clause {
                    fn immut_reflector(&self, reflector: &std::sync::Arc<Reflector>) -> NodeTree {
                        Reflector::reflect_struct(reflector, &self.get_desc(), self, false)
                    }

                    fn immut_climber<'a>(
                        &self,
                        climber: &mut Climber<'a>,
                    ) -> Result<Option<NodeTree>, ClimbError> {
                        climber.check_field_access_immut(&EnumOrStruct::Struct(self))
                    }

                    fn mut_climber<'a>(
                        &mut self,
                        climber: &mut Climber<'a>,
                    ) -> Result<Option<NodeTree>, ClimbError> {
                        climber.check_field_access_mut(EnumOrStructMut::Struct(self))
                    }
                }
            }
        }
        Data::Enum(data) => {
            let mut field_by_name_match_arms = vec![];
            let mut field_by_idx_match_arms = vec![];
            let mut field_by_name_match_mut_arms = vec![];
            let mut field_by_idx_match_mut_arms = vec![];
            let mut desc_match_arms = vec![];
            let mut opt_names = vec![];

            for variant in data.variants.iter() {
                let ident = &variant.ident;
                let variant_name = format!("{}", variant.ident);
                opt_names.push(variant_name);

                let (named_match_arms, index_match_arms, desc, params) = impls_by_mutability(
                    variant.ident.clone(),
                    &variant.fields,
                    true,
                    Mutability::ReadAccess,
                );
                let (named_match_mut_arms, index_match_mut_arms, _, _) = impls_by_mutability(
                    variant.ident.clone(),
                    &variant.fields,
                    true,
                    Mutability::ModifyAccess,
                );

                field_by_name_match_arms.push(quote! {
                    #name::#ident #params => {
                        match name {
                            #named_match_arms
                            _ => None,
                        }
                    }
                });

                field_by_idx_match_arms.push(quote! {
                    #name::#ident #params => {
                        match idx {
                            #index_match_arms
                            _ => None,
                        }
                    }
                });

                field_by_name_match_mut_arms.push(quote! {
                    #name::#ident #params => {
                        match name {
                            #named_match_mut_arms
                            _ => None,
                        }
                    }
                });

                field_by_idx_match_mut_arms.push(quote! {
                    #name::#ident #params => {
                        match idx {
                            #index_match_mut_arms
                            _ => None,
                        }
                    }
                });

                desc_match_arms.push(quote! {
                    #name::#ident #params => {
                        #desc
                    }
                });
            }

            let field_by_name_match_arms = if field_by_name_match_arms.len() != 0 {
                quote! { #(#field_by_name_match_arms),*, }
            } else {
                quote! { "_ => None"}
            };

            let field_by_idx_match_arms = if field_by_idx_match_arms.len() != 0 {
                quote! { #(#field_by_idx_match_arms),*, }
            } else {
                quote! { "_ => None"}
            };

            let field_by_name_match_mut_arms = if field_by_name_match_mut_arms.len() != 0 {
                quote! { #(#field_by_name_match_mut_arms),*, }
            } else {
                quote! { "_ => None"}
            };

            let field_by_idx_match_mut_arms = if field_by_idx_match_mut_arms.len() != 0 {
                quote! { #(#field_by_idx_match_mut_arms),*, }
            } else {
                quote! { "_ => None"}
            };

            let desc_match_arms = if desc_match_arms.len() != 0 {
                quote! { #(#desc_match_arms),*, }
            } else {
                quote! { "_ => panic!()"}
            };

            quote! {
                impl #impl_generics ReflectStruct for #name #ty_generics #where_clause {
                    fn get_desc(&self) -> Struct {
                        match self {
                            #desc_match_arms
                        }
                    }

                    fn get_field_by_name(&self, name: &'static str) -> Option<&dyn Access> {
                        match self {
                            #field_by_name_match_arms
                        }
                    }

                    fn get_field_by_idx(&self, idx: usize) -> Option<&dyn Access> {
                        match self {
                            #field_by_idx_match_arms
                        }
                    }

                    fn get_field_by_name_mut(&mut self, name: &'static str) -> Option<&mut dyn Access> {
                        match self {
                            #field_by_name_match_mut_arms
                        }
                    }

                    fn get_field_by_idx_mut(&mut self, idx: usize) -> Option<&mut dyn Access> {
                        match self {
                            #field_by_idx_match_mut_arms
                        }
                    }
                }

                impl #impl_generics ReflectEnum for #name #ty_generics #where_clause {
                    fn get_variant_desc(&self) -> Enum {
                        Enum {
                            name: #str_name,
                            opts: &[#(#opt_names),*],
                        }
                    }

                    fn get_variant_struct(&self) -> &dyn ReflectStruct {
                        self
                    }

                    fn get_variant_struct_mut(&mut self) -> &mut dyn ReflectStruct {
                        self
                    }
                }

                impl #impl_generics ReflectDirect for #name #ty_generics #where_clause {
                    fn immut_reflector(&self, reflector: &std::sync::Arc<Reflector>)
                        -> NodeTree
                    {
                        let p_struct = self.get_variant_struct();
                        let desc = p_struct.get_desc();
                        Reflector::reflect_struct(reflector, &desc, self, false)
                    }

                    fn immut_climber<'a>(
                        &self,
                        climber: &mut Climber<'a>,
                    ) -> Result<Option<NodeTree>, ClimbError> {
                        climber.check_field_access_immut(&EnumOrStruct::Enum(self))
                    }

                    fn mut_climber<'a>(
                        &mut self,
                        climber: &mut Climber<'a>,
                    ) -> Result<Option<NodeTree>, ClimbError> {
                        climber.check_field_access_mut(EnumOrStructMut::Enum(self))
                    }
                }
            }
        }
        _ => {
            panic!();
        }
    }
}

fn impl_struct_for_deser(name: Tokens, data_fields: &Fields, in_enum: bool) -> (Tokens, bool) {
    let name_str = format!("{}", format!("{}", name));
    let parse_name = if in_enum {
        quote! {}
    } else {
        quote! {
            let _name = Token::new_borrowed(TokenKind::Ident, #name_str);
            tracker.try_token(&_name)?;
        }
    };

    match data_fields {
        Fields::Named(ref fields) => {
            let mut match_arms = vec![];
            let mut match_arms_2 = vec![];
            let mut lets = vec![];
            let mut assigns = vec![];
            let mut names = vec![];

            for field in fields.named.iter() {
                if is_skipped(&field.attrs) {
                    return (
                        quote! {
                            return Err(deser::DeserError::Unbuildable);
                        },
                        false,
                    );
                }
                let ident = &field.ident;
                let ident_name = Ident::new(
                    &format!("_assign_{}", ident.as_ref().unwrap()),
                    Span::call_site(),
                );
                let ident_str = format!("{}", ident.as_ref().unwrap());

                lets.push(quote! {
                    let mut #ident_name = None;
                });
                match_arms.push(quote! { #ident_str => Some(#ident_str) });
                match_arms_2.push(quote! {
                    Some(#ident_str) => {
                        if #ident_name.is_some() {
                            return Err(deser::DeserError::Unbuildable);
                        }
                        tracker.step();
                        tracker.try_token(&_colon)?;
                        #ident_name = Some(Deser::deser(tracker)?);
                    }
                });
                names.push(quote! { (#ident_str, #ident_name.is_some()) });
                assigns.push(quote! { #ident : #ident_name.unwrap() });
            }

            let expecting = assigns.len();
            let assigns = if assigns.is_empty() {
                quote! {}
            } else {
                quote! { #(#assigns),* }
            };

            let processing = if expecting > 0 {
                let names = &names;
                quote! {
                    #(#lets);*
                    let mut __expecting = #expecting;
                    while __expecting > 0 {
                        if !tracker.has_remaining() {
                            for (name, exclude) in &[#(#names,)*] {
                                if *exclude {
                                    continue;
                                }
                                tracker.possible_token(
                                    Token::new_borrowed(TokenKind::Ident, *name));
                            }
                            return Err(deser::DeserError::EndOfTokenList);
                        }
                        if let TokenKind::Ident = tracker.top_kind() {
                            let opt = {
                                let text = tracker.top().text.as_ref();
                                match text {
                                    #(#match_arms),*,
                                    _ => None
                                }
                            };
                            match opt {
                                #(#match_arms_2),*,
                                _ => {
                                    let text = String::from(tracker.top().text.as_ref());
                                    for (name, exclude) in &[#(#names,)*] {
                                        if *exclude {
                                            continue;
                                        }
                                        if name.starts_with(&text) {
                                            tracker.possible_token(
                                                Token::new_borrowed(
                                                    TokenKind::Ident, *name));
                                        }
                                    }
                                    return Err(deser::DeserError::UnexpectedToken);
                                }
                            }
                            __expecting -= 1;
                            if __expecting == 0 {
                                tracker.try_token(&_curly_close)?;
                            } else {
                                tracker.try_token(&_comma)?;
                            }
                        } else {
                            return Err(deser::DeserError::UnexpectedToken);
                        }
                    }
                }
            } else {
                quote! {
                    tracker.try_token(&_culry_close)?;
                }
            };

            (
                quote! {
                    let _curly_open = Token::new_borrowed(TokenKind::CurlyOpen, " {");
                    let _curly_close = Token::new_borrowed(TokenKind::CurlyClose, "}");
                    let _comma = Token::new_borrowed(TokenKind::Comma, ", ");
                    let _colon = Token::new_borrowed(TokenKind::Colon, ": ");

                    #parse_name
                    tracker.try_token(&_curly_open)?;
                    #processing;

                    Ok(#name { #assigns })
                },
                true,
            )
        }
        Fields::Unnamed(ref fields) => {
            let mut assigns = vec![];

            for (idx, field) in fields.unnamed.iter().enumerate() {
                if is_skipped(&field.attrs) {
                    return (
                        quote! {
                            return Err(deser::DeserError::Unbuildable);
                        },
                        false,
                    );
                }

                let comma = if idx > 0 {
                    quote! { tracker.try_token(&_comma)?; }
                } else {
                    quote! {}
                };

                assigns.push(quote! {
                    {
                        #comma;
                        Deser::deser(tracker)?
                    }
                });
            }

            let assigns = if assigns.is_empty() {
                quote! {}
            } else {
                quote! { #(#assigns),* }
            };

            (
                quote! {
                    let _open = Token::new_borrowed(TokenKind::TupleOpen, "(");
                    let _close = Token::new_borrowed(TokenKind::TupleClose, ")");
                    let _comma = Token::new_borrowed(TokenKind::Comma, ", ");
                    let _colon = Token::new_borrowed(TokenKind::Colon, ": ");

                    #parse_name

                    tracker.try_token(&_open)?;
                    let rval = Ok(#name( #assigns ));
                    tracker.try_token(&_close)?;

                    rval
                },
                true,
            )
        }
        Fields::Unit => (
            quote! {
                #parse_name
                Ok(#name)
            },
            true,
        ),
    }
}

fn impls_for_deser(kr: &Tokens, input: &DeriveInput, info: &DeriveInfo) -> (bool, Tokens) {
    if info.opaque {
        return (false, quote! {});
    }

    let name = &input.ident;
    let generics = add_trait_bounds(input.generics.clone(), &HashSet::new(), &["Deser"]);
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    let (res, active) = match &input.data {
        Data::Struct(data) => impl_struct_for_deser(quote! {#name}, &data.fields, false),
        Data::Enum(data) => {
            let mut names = vec![];
            let mut match_arms = vec![];

            for variant in data.variants.iter() {
                let ident = &variant.ident;
                let variant_name = format!("{}", variant.ident);
                let variant_access = if info.from_interact {
                    quote! {#ident}
                } else {
                    quote! {#name::#ident}
                };

                let (code, _) = impl_struct_for_deser(variant_access, &variant.fields, true);
                match_arms.push(quote! {
                    #variant_name => {
                        return {
                            tracker.step();
                            #code
                        }
                    }
                });

                names.push(quote! { #variant_name });
            }

            let names = &names;
            (
                quote! {
                    if !tracker.has_remaining() {
                        for name in &[#(#names,)*] {
                            tracker.possible_token(
                                Token::new_borrowed(TokenKind::Ident, *name));
                        }
                        return Err(deser::DeserError::EndOfTokenList);
                    }
                    if let TokenKind::Ident = tracker.top_kind() {
                        let text = tracker.top().text.as_ref();
                        match text {
                            #(#match_arms),*
                            _ => {
                                let text = String::from(text);
                                for name in &[#(#names,)*] {
                                    if name.starts_with(&text) {
                                        tracker.possible_token(
                                            Token::new_borrowed(
                                                TokenKind::Ident, *name));
                                    }
                                }
                                return Err(deser::DeserError::UnexpectedToken);
                            }
                        }
                    } else {
                        return Err(deser::DeserError::UnexpectedToken);
                    }
                },
                true,
            )
        }
        _ => {
            panic!();
        }
    };

    (
        active,
        quote! {
            impl #impl_generics Deser for #name #ty_generics #where_clause {
                fn deser<'a, 'b>(tracker: &mut deser::Tracker<'a, 'b>) -> deser::Result<Self> {
                    use #kr::deser::*;
                    #res
                }
            }
        },
    )
}

fn get_interact_meta_items(attr: &syn::Attribute) -> Option<Vec<syn::NestedMeta>> {
    if attr.path.segments.len() == 1 && attr.path.segments[0].ident == "interact" {
        match attr.interpret_meta() {
            Some(List(ref meta)) => Some(meta.nested.iter().cloned().collect()),
            _ => {
                // TODO: produce an error
                None
            }
        }
    } else {
        None
    }
}

fn is_skipped(attrs: &Vec<syn::Attribute>) -> bool {
    for meta_items in attrs.iter().filter_map(get_interact_meta_items) {
        for meta_item in meta_items {
            match &meta_item {
                Meta(Word(word)) if word == "skip" => return true,
                _ => continue,
            }
        }
    }
    false
}

fn add_trait_bounds(
    mut generics: Generics,
    skip_set: &HashSet<String>,
    trait_names: &[&str],
) -> Generics {
    for param in &mut generics.params {
        if let GenericParam::Type(ref mut type_param) = *param {
            if let Some(_) = skip_set.get(&type_param.ident.to_string()) {
                continue;
            }
            for trait_name in trait_names {
                let trait_name = Ident::new(*trait_name, Span::call_site());
                let bound = syn::parse(quote! { #trait_name }.into()).unwrap();
                type_param.bounds.push(bound);
            }
        }
    }
    generics
}

fn tokens_to_rustfmt_file(filename: &std::path::Path, expanded: &Tokens) {
    let mut file = std::fs::File::create(&filename).unwrap();
    use std::io::Write;
    file.write_all(format!("{}", expanded).as_bytes()).unwrap();
    Command::new("rustfmt")
        .args(&[filename])
        .output()
        .expect("failed to execute process");
}