fvm-macros 1.0.0

provide the macros for user to write contract more convenient
Documentation
use proc_macro2::{TokenStream as TokenStream2, TokenTree};
use quote::{quote, ToTokens};
use serde_json::from_str;
use syn::{FnArg, ImplItem, ImplItemMethod, ItemImpl, ReturnType, Visibility};

pub(crate) fn generate_abi(items: &ItemImpl) -> TokenStream2 {
    // 生成 __generate_abi() -> ABI, 其内容如下:todo: 考虑多模块重名的情况
    //   - 构造ContractMeta(当前是无参构造函数)
    let i_name = items.self_ty.as_ref();
    let i_name_str = quote!(#i_name).to_string();

    let construct = items.items
        .iter()
        .filter_map(|item| {
            match item {
                ImplItem::Method(method) => { Some(method) }
                _ => None
            }
        })
        .filter(|method| {
            method.sig.ident.to_string().eq("new")
        })
        .map(|method| {
            let inputs = generate_inputs(method);
            quote! {
                fvm_abi::ConstructorSpec {
                    input: vec![#( #inputs ),*],
                }
            }
        })
        .take(1)
        .collect::<Vec<_>>()
        .pop()
        .unwrap();

    //   - 构造Vec<MethodInfo>
    let mut field_id = 0;
    let method_info_list = items.items
        .iter()
        .filter_map(|item| {
            match item {
                ImplItem::Method(method) => { Some(method) }
                _ => None
            }
        })
        .filter(|&method| {
            // 排除掉非pub方法和new方法的ABI生成
            if let Visibility::Public(_) = method.vis {
                return !method.sig.ident.to_string().eq("new");
            }
            false
        })
        .map(|method| {
            let inputs = generate_inputs(method);
            let out_type = match &method.sig.output {
                ReturnType::Default => { vec![quote! {}] }
                ReturnType::Type(_, ty) => {
                    let tf = quote! {
                        fvm_abi::TypeInfo {
                            type_id: scale_info::meta_type::<#ty>(),
                        }
                    };
                    vec![tf]
                }
            };
            let m_name = method.sig.ident.to_string();
            let mut parallel_level: i32 = 3;
            let mut re_filed: Vec<TokenStream2> = vec![];
            let mut re_call_methods: Vec<TokenStream2> = vec![];
            let mut cross_mutex: Vec<TokenStream2> = vec![];
            let mut key_name = String::new();

            method.attrs.iter().for_each(|x| {
                match x.path.segments[0].ident.to_string().as_str() {
                    "parallel_field" => {
                        parallel_level = if parallel_level > 2 { 2 } else { parallel_level };
                        let mut field_name: String = String::new();
                        let mut para_index: Vec<i32> = vec![];
                        x.tokens.to_token_stream().into_iter().for_each(|y| {
                            match y {
                                TokenTree::Group(g) => {
                                    g.stream().into_iter().for_each(|t| {
                                        match t {
                                            TokenTree::Ident(s1) if s1.to_string() == "field_name"
                                                || s1.to_string() == "para_index" => {
                                                key_name = s1.to_string();
                                            }
                                            TokenTree::Literal(s2) => {
                                                match key_name.as_str() {
                                                    "field_name" => {
                                                        field_name = s2.to_string().trim_matches('\"').to_string();
                                                        field_id += 1;
                                                    }
                                                    "para_index" => {
                                                        for x in s2.to_string().trim_matches('\"').split(',') {
                                                            para_index.push(from_str::<i32>(x.trim())
                                                                .expect("index should not contain non numeric"));
                                                        }
                                                    }
                                                    _ => ()
                                                }
                                            }
                                            _ => (),
                                        }
                                    })
                                }
                                _ => (),
                            }
                            re_filed.push(quote! {fvm_abi::MutexField {
                                                    field_name: String::from(#field_name),
                                                    field_id: #field_id,
                                                    parallel_index: vec![#( #para_index ),*],
                                                }});
                        });
                    }
                    "parallel_call_method" => {
                        parallel_level = if parallel_level > 2 { 2 } else { parallel_level };
                        x.tokens.to_token_stream().into_iter().for_each(|y| {
                            match y {
                                TokenTree::Group(g) => {
                                    g.stream().into_iter().for_each(|t| {
                                        match t {
                                            TokenTree::Ident(s1) if s1.to_string() == "methods" => {
                                                key_name = s1.to_string();
                                            }
                                            TokenTree::Literal(s2) => {
                                                match key_name.as_str() {
                                                    "methods" => {
                                                        for x in s2.to_string().trim_matches('\"').split(',') {
                                                            re_call_methods.push(quote! { String::from(#x.trim())});
                                                        }
                                                    }
                                                    _ => (),
                                                }
                                            }
                                            _ => ()
                                        }
                                    })
                                }
                                _ => (),
                            }
                        })
                    }
                    "parallel_cross" => {
                        parallel_level = if parallel_level > 2 { 2 } else { parallel_level };
                        let mut cross_address: String = String::new();
                        let mut cross_methods: Vec<TokenStream2> = vec![];
                        let mut address_index: i32 = -1;
                        let mut cns_name = String::new();
                        let mut cns_index = -1;

                        x.tokens.to_token_stream().into_iter().for_each(|y| {
                            match y {
                                TokenTree::Group(g) => {
                                    g.stream().into_iter().for_each(|x| {
                                        match x {
                                            TokenTree::Ident(l) => {
                                                key_name = l.to_string();
                                            }
                                            TokenTree::Literal(s2) => {
                                                match key_name.as_str() {
                                                    "address" => {
                                                        cross_address = s2.to_string().trim_matches('"').to_string();
                                                    }
                                                    "methods" => {
                                                        for x in s2.to_string().trim_matches('"').split(',') {
                                                            cross_methods.push(quote! { String::from(#x.trim())});
                                                        }
                                                    }
                                                    "address_index" => {
                                                        address_index = from_str::<i32>(s2.to_string().as_str())
                                                            .expect("cross address index should not contain non numeric");
                                                    }
                                                    "cns_name" => {
                                                        cns_name = s2.to_string().trim_matches('"').to_string();
                                                    }
                                                    "cns_index" => {
                                                        cns_index = from_str::<i32>(s2.to_string().as_str())
                                                            .expect("cns cross index should not contain non numeric");
                                                    }
                                                    _ => ()
                                                }
                                            }
                                            _ => ()
                                        }
                                    });
                                    cross_mutex.push(quote! {
                                            fvm_abi::MutexCall {
                                                address_index: #address_index,
                                                address: String::from(#cross_address),
                                                methods: vec![#(#cross_methods), *],
                                                cns_name: String::from(#cns_name),
                                                cns_index: #cns_index,
                                            }
                                        });
                                }
                                _ => (),
                            }
                        })
                    }
                    "parallel_contract" => {
                        parallel_level = 1
                    }
                    _ => {}
                }
            });
            if parallel_level != 2 {
                re_filed = vec![];
                re_call_methods = vec![];
                cross_mutex = vec![];
            }
            if parallel_level == 3 {
                parallel_level = 0
            }

            quote! {
                fvm_abi::MethodInfo {
                    name:  String::from(#m_name),
                    input: vec![#( #inputs ),*],
                    output: vec![#( #out_type ),*],
                    parallel_level: #parallel_level,
                    mutex_fields: vec![ # (#re_filed), *],
                    method_calls: vec! [# (#re_call_methods), *],
                    mutex_calls: vec![#(#cross_mutex), *],
                }
            }
        })
        .collect::<Vec<_>>();

    let meta_token = quote! {
        fvm_abi::ContractMeta {
            name: String::from(#i_name_str),
            constructor: #construct,
        }
    };

    let methods_token = quote! {
       vec![#( #method_info_list ), *]
    };

    //   - 构造ABI结构体, 返回
    quote! {
        #[cfg(not(target_arch = "wasm32"))]
        const _: () = {
            use fvm_mock::build_runtime;

            #[no_mangle]
            pub fn __generate_abi() -> fvm_abi::ABI {
                fvm_abi::ABI::new(#meta_token, #methods_token)
            }
        };
    }
}

fn generate_inputs(method: &ImplItemMethod) -> Vec<TokenStream2> {
    method.sig.inputs
        .iter()
        .filter_map(|param| {
            // ignore `self` param
            match param {
                FnArg::Typed(tp) => {
                    let ty = &tp.ty;
                    Some(quote! {
                        fvm_abi::TypeInfo {
                            type_id: scale_info::meta_type::<#ty>(),
                        }
                    })
                }
                _ => None,
            }
        })
        .collect::<Vec<_>>()
}

pub(crate) fn is_tokens_empty(token_list: &Vec<TokenStream2>) -> bool {
    for tk in token_list {
        if tk.to_string() != quote! {}.to_string() {
            return false;
        }
    }
    true
}

#[cfg(test)]
mod test {
    use crate::common::is_tokens_empty;
    use quote::quote;

    #[test]
    fn test_is_tokens_empty() {
        let token_list = vec![quote! {}];
        assert!(is_tokens_empty(&token_list));

        let token_list2 = vec![quote! {}, quote! {use quote::quote;}];
        assert_eq!(is_tokens_empty(&token_list2), false)
    }
}