fvm-macros 1.0.0

provide the macros for user to write contract more convenient
Documentation
use cfg_if::cfg_if;
use proc_macro2::{TokenStream as TokenStream2, TokenTree};
use quote::{quote};
use syn::spanned::Spanned;
use syn::{Result};

macro_rules! generate_panic {
    ($spanned:expr, $($msg:tt)*) => {
        return Err(::syn::Error::new(
            <_ as ::syn::spanned::Spanned>::span(&$spanned),
            format_args!($($msg)*)
        ))
    }
}

pub(crate) fn generate_cross_call(attr: TokenStream2, item: TokenStream2) -> Result<TokenStream2> {
    // get item and address
    let attr = TokenStream2::from(attr);
    let tokens: Vec<TokenTree> = attr.clone().into_iter().collect();
    if tokens.len() < 1 {
        generate_panic!(attr.span(), "cross argument is not enough");
    }

    let mut is_cns: bool = false;
    let addr_token = match &tokens[0] {
        TokenTree::Literal(l) => {
            let s = l.to_string().trim_matches('"').to_lowercase();
            let addr_hex = s.trim_start_matches("0x");
            if addr_hex.len() != 40 {
                generate_panic!(attr.span(), "contract address length error: {}", addr_hex.len())
            }
            let address = hex::decode(addr_hex).expect("contract address error");
            quote! {
                fvm_std::types::Address::new([#(#address), *])
            }
        }
        TokenTree::Ident(l) if l.to_string() == "cns_name" => {
            match &tokens[2] {
                TokenTree::Literal(l) => {
                    is_cns = true;
                    quote! {
                       #l.to_string().trim_matches('"')
                    }
                }
                _ => generate_panic!(attr.span(), "the argument must be string of CNS")
            }
        }
        _ => generate_panic!(attr.span(), "the first token must be string of address or ident of `cns_name`")
    };

    // 解析item所有方法,生成对应结构体的方法实现
    let it = syn::parse2::<syn::ItemTrait>(item)
        .map_err(|err| syn::Error::new(attr.span(), err))?;
    let name = it.ident;

    let methods = it.items.iter()
        .filter_map(|item| {
            match item {
                syn::TraitItem::Method(m) => Some(m),
                _ => None
            }
        })
        .map(|item| {
            // get method name
            let m_name = &item.sig.ident;
            let m_name_str = format!("{}", m_name);
            let m_input = &item.sig.inputs
                .iter()
                .filter_map(|input| {  // ignore `self` argument
                    match input {
                        syn::FnArg::Typed(tp) => Some(tp),
                        _ => None
                    }
                })
                .collect::<Vec<_>>();
            let m_output = &item.sig.output;

            let input_encode = {
                let params = m_input.iter()
                    .map(|input| {
                        let v = &input.pat;
                        // quote! {input.append(&mut #v.encode());}
                        quote! {input.append(&mut scale::Encode::encode(&#v));}
                    })
                    .collect::<Vec<_>>();
                quote! {
                    let mut input = scale::Encode::encode(&#m_name_str);
                    #(#params)*
                }
            };

            let output_decode = match m_output {
                syn::ReturnType::Type(_, tp) => quote! {
                    <#tp as scale::Decode>::decode(&mut &res[..]).expect("func return type error")
                },
                _ => quote! {}
            };

            let mut cross_func = quote! {call_contract(&#addr_token, input.as_slice())};
            if is_cns {
                cross_func = quote! {cns_call_contract(#addr_token.to_string().trim_matches('"').as_bytes(), input.as_slice())}
            }

            cfg_if! {
                 if #[cfg(feature = "advance")] {
                    quote! {
                        pub fn #m_name(#(#m_input), *) #m_output {
                            #input_encode
                            let mut res = fvm_std::advance::#cross_func;
                            #output_decode
                        }
                    }
                } else {
                    quote! {
                        pub fn #m_name(#(#m_input), *) #m_output {
                             #input_encode
                             let res = fvm_std::normal::#cross_func;
                             #output_decode
                        }
                    }
                }
                }
        })
        .collect::<Vec<_>>();


    Ok(quote! {
        struct #name {}

        impl #name {
            #(#methods)*
        }
    })
}