fvm-macros 1.0.0

provide the macros for user to write contract more convenient
Documentation
use proc_macro2::TokenStream as TokenStream2;
use syn::{ItemImpl, ImplItem, FnArg};
use quote::{quote};
use syn::Visibility;
use syn::spanned::Spanned;
use crate::common::is_tokens_empty;

/// 在deploy时需要调用`new`构造出对象
pub (crate) fn generate_deploy(items: &ItemImpl) -> TokenStream2 {
    // get Impl name
    let i_name = items.self_ty.as_ref();
    // get new method
    let functions = items.items.iter()
        .filter_map(|item| {
            match item {
                ImplItem::Method(m) => Some(m),
                _ => None
            }
        })
        .filter(|method| {
            method.sig.ident.to_string().eq("new")
        })
        .map(|method| {
            let mt = &method.sig.ident;
            let mut n: usize = 0usize;
            let (names, in_types): (Vec<_>, Vec<_>) = method.sig.inputs.iter()
                .map(|param| match param {
                    FnArg::Typed(tp) => {
                        n += 1;
                        let index = format!("p_{}", n);
                        let index_ident = syn::Ident::new(&index, index.span());
                        (quote! {#index_ident}, &tp.ty)
                    }
                    _ => { unreachable!("{}-th error input params for constructor function `new`", n) }
                })
                .unzip();

            let padding_str = if in_types.len() > 0 {
                quote! {let _ = <String as scale::Decode>::decode(input).unwrap();}
            } else {
                quote! {}
            };
            quote! {
                #[no_mangle]
                fn deploy() {
                    // 1. fetch input
                    let input_raw = fvm_std::advance::input();
                    let input = &mut &input_raw[..];
                    // 2. decode input
                    // add the padding string for deploy and invoke params format uniform
                    // with deploy: "N" + deploy_params
                    // with invoke: invoke_method + invoke_params
                    #padding_str
                    let (#(#names,) *) = (#(<#in_types as scale::Decode>::decode(input).unwrap(),)*);
                    if input.len() != 0 { // check the input correct
                        fvm_std::advance::revert("deploy param error");
                    }
                    // scale::Encode::encode(&inst.#m_name(#(#names), *))
                    // 3. call new method
                    <#i_name>::#mt(#(#names), *);
                    // 4. store to ledger

                    // instance.write_ledger("".as_bytes(), "deploy".as_bytes())
                }
            }
        }).collect::<Vec<_>>();
    quote! {
        #(#functions)*
    }
}

pub (crate) fn generate_invoke(items: &ItemImpl) -> TokenStream2 {
    // 1. 解析每个方法的签名
    // 2. 为每个方法的参数的类型实现decode方法
    // 3. 生成call方法
    // get Impl name
    let i_name = items.self_ty.as_ref();

    //为每个方法生成调用的Token
    let method_list = items.items.iter()
        .filter_map(|item| {
            match item {
                ImplItem::Method(m) => Some(m),
                _ => None
            }
        })
        .filter_map(|method| {
            match &method.vis {
                Visibility::Public(_) => Some(method),
                _ => None
            }
        })
        .map(|method| {
            // ignore not `pub` methods
            let m_name = &method.sig.ident;
            let m_name_str = format!("{}", &method.sig.ident);
            // 这里需要排除self参数
            let mut n: usize = 0usize;
            let (names, in_types): (Vec<_>, Vec<_>) = method.sig.inputs.iter()
                .filter(
                    |param| match param {
                        FnArg::Typed(_) => { true }
                        _ => { false }
                    })
                .map(|param| match param {
                    FnArg::Typed(tp) => {
                        n += 1;
                        let index = format!("p_{}", n);
                        let index_ident = syn::Ident::new(&index, index.span());
                        (quote! {#index_ident}, &tp.ty)
                    }
                    _ => { unreachable!("unreachable") }
                })
                .unzip();

            quote! {
                #m_name_str => {
                    let (#(#names,) *) = (#(<#in_types as scale::Decode>::decode(input).unwrap(),)*);
                    if input.len() != 0 { // check the input correct
                        fvm_std::advance::revert("invoke param error");
                    }
                    scale::Encode::encode(&#i_name::#m_name(#(#names), *))
                }
            }
        }).collect::<Vec<_>>();

    if is_tokens_empty(&method_list) {
        quote! {
            #[no_mangle]
            fn invoke() {
                panic!("no method to call");
            }
        }
    } else {
        quote! {
            #[no_mangle]
            fn invoke() {
                // 2. fetch input and resolve call method name
                let input_raw = fvm_std::advance::input();
                let input = &mut &input_raw[..];
                let m_name = <String as ::scale::Decode>::decode(input).unwrap();

                // 3. deliver method and call
                let res = match m_name.as_str() {
                    #(#method_list)*
                    _ => {
                        // fvm_std::advance::revert(format!("no such method to call: {}", m_name).as_str());
                        fvm_std::advance::revert("no such method to call");
                    }
                };
                // 5. return call result
                fvm_std::advance::ret(&*res);
            }
        }
    }
}

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

    #[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)
    }
}