cmod-macros 0.4.6

Build a cross-language module with Rust FFI
Documentation
use super::utils::*;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, parse_quote, ImplItem, ImplItemFn, ItemImpl, Stmt};

pub fn cmod_methods(_attr: TokenStream, input: TokenStream) -> TokenStream {
    let mut input = parse_macro_input!(input as ItemImpl);
    let lua_input = input.clone();
    let class_type = lua_input.self_ty;

    input.attrs.clear();
    input.items.iter_mut().for_each(|ii| {
        if let ImplItem::Fn(md) = ii {
            md.attrs.clear();
        }
    });

    let mut item_record: Vec<Stmt> = Vec::new();
    lua_input.items.iter().for_each(|ii| {
        if let ImplItem::Fn(md) = ii {
            match inner_method_handle(md) {
                Flag::Static => {
                    item_record.push(method_static(md.clone()));
                }
                Flag::Class => {
                    item_record.push(method_class(md.clone()));
                }
                _ => (),
            }
        }
    });
    let mut ifn: ImplItemFn = parse_quote!(
        fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {}
    );
    ifn.block.stmts = item_record;
    TokenStream::from(quote!(
        #input

        impl mlua::UserData for #class_type {
            #ifn
        }
    ))
}

enum Flag {
    Empty,
    Static,
    Class,
}

fn inner_method_handle(inner_method: &ImplItemFn) -> Flag {
    for p in inner_method.attrs.iter() {
        if p.meta.path().is_ident("staticmethod") {
            return Flag::Static;
        } else if p.meta.path().is_ident("classmethod") {
            return Flag::Class;
        }
    }
    Flag::Empty
}

pub fn method_static(input: ImplItemFn) -> Stmt {
    let lua_input = input.clone();
    let function = Function::parse_impl_fn(lua_input);
    let Function {
        name,
        asy,
        input: inp,
        args,
        ret: _,
        map_ret,
    } = function;
    let name_str = name.to_string();
    if asy {
        parse_quote!(
            methods.add_async_function(#name_str,|lua,#inp|async {Self::#name(#args).await.map_err(cmod::ffi::lua::map_err)#map_ret});
        )
    } else {
        parse_quote!(
            methods.add_function(#name_str,|lua,#inp|Self::#name(#args).map_err(cmod::ffi::lua::map_err)#map_ret);
        )
    }
}

pub fn method_class(input: ImplItemFn) -> Stmt {
    let lua_input = input.clone();
    let function = Function::parse_impl_fn(lua_input);
    let Function {
        name,
        asy,
        input: inp,
        args,
        ret: _,
        map_ret,
    } = function;
    let name_str = name.to_string();
    if asy {
        parse_quote!(
            methods.add_async_method(#name_str,|lua,this,#inp|async move{this.#name(#args).await.map_err(cmod::ffi::lua::map_err)#map_ret});
        )
    } else {
        parse_quote!(
            methods.add_method(#name_str,|lua,this,#inp|this.#name(#args).map_err(cmod::ffi::lua::map_err)#map_ret);
        )
    }
}