fvm-macros 1.0.0

provide the macros for user to write contract more convenient
Documentation
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote};
use syn::{FnArg, ImplItem, ItemImpl, 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() {
                     std::panic::set_hook(Box::new(|info| {
                        // let backtrace = Backtrace::new();
                        let message = match info.payload().downcast_ref::<&'static str>() {
                            Some(msg) => *msg,
                            None => match info.payload().downcast_ref::<String>() {
                                Some(msg) => msg.as_str(),
                                None => "",
                            },
                        };
                        let (file, line) = if let Some(location) = info.location() {
                            (location.file(), location.line())
                        } else {
                            (Default::default(), Default::default())
                        };
                        let res = format!("panic: {} at: {}:{}", message, file, line);
                        fvm_std::normal::revert(res.as_str());
                    }));

                    // 1. fetch input
                    let input_raw = fvm_std::normal::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::normal::revert("deploy param error");
                    }
                    // scale::Encode::encode(&inst.#m_name(#(#names), *))
                    // 3. call new method
                    let instance = <#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::normal::revert("invoke param error");
                    }
                    scale::Encode::encode(&inst.#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() {
                std::panic::set_hook(Box::new(|info| {
                    // let backtrace = Backtrace::new();
                    let message = match info.payload().downcast_ref::<&'static str>() {
                        Some(msg) => *msg,
                        None => match info.payload().downcast_ref::<String>() {
                            Some(msg) => msg.as_str(),
                            None => "",
                        },
                    };
                    let (file, line) = if let Some(location) = info.location() {
                        (location.file(), location.line())
                    } else {
                        (Default::default(), Default::default())
                    };
                    // NOTE: trace not support with wasm now, consider it later
                    // let trace = backtrace.frames().iter()
                    //     .map(|frame| {
                    //         let mut frame_str = String::new();
                    //         for symbol in frame.symbols() {
                    //             if let Some(name) = symbol.name() {
                    //                 frame_str.push_str(name.to_string().as_str());
                    //             }
                    //             if let Some(path) = symbol.filename() {
                    //                 frame_str.push_str(path.to_str().get_or_insert(""));
                    //                 frame_str.push_str(":");
                    //             }
                    //             if let Some(number) = symbol.lineno() {
                    //                 frame_str.push_str(number.to_string().as_str());
                    //             }
                    //         }
                    //         frame_str
                    //     })
                    //     .fold(String::new(), |mut acc, x| {
                    //         acc.push_str(format!("   {}\n", x).as_str());
                    //         acc
                    //     });

                    // let res = format!("panic: {} at: {}:{}\n{}", message, file, line, trace);
                    let res = format!("panic: {} at: {}:{}", message, file, line);
                    fvm_std::normal::revert(res.as_str());
                }));

                // 1. restore obj form ledger
                let mut inst = <#i_name>::read_ledger("".as_bytes(), "".as_bytes());

                // 2. fetch input and resolve call method name
                let input_raw = fvm_std::normal::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::normal::revert(format!("no such method to call: {}", m_name).as_str());
                    }
                };

                // 4. store to ledger
                inst.write_ledger("".as_bytes(), "invoke".as_bytes());

                // 5. return call result
                fvm_std::normal::ret(&*res);
            }
        }
    }
}