nvim_utils_macros/
lib.rs

1#[allow(unused_imports)]
2use mlua::lua_State;
3use proc_macro::TokenStream;
4use proc_macro2::Span;
5use quote::quote;
6use syn::{AttributeArgs, Error, Ident, Path, Result};
7
8struct Plugin {
9    path: Path,
10}
11
12impl Plugin {
13    fn parse(args: AttributeArgs) -> Result<Self> {
14        let mut path = None;
15
16        for arg in args {
17            use syn::Meta::Path;
18            use syn::NestedMeta::*;
19            match arg {
20                Meta(Path(p)) => path = Some(p),
21                _ => {
22                    return Err(Error::new_spanned(arg, "expected `name = \"...\"`"));
23                }
24            }
25        }
26
27        let path = path.ok_or_else(|| Error::new(Span::call_site(), "expected module path"))?;
28
29        Ok(Self { path })
30    }
31}
32
33#[proc_macro_attribute]
34pub fn module(attr: TokenStream, item: TokenStream) -> TokenStream {
35    let attr = syn::parse_macro_input!(attr as AttributeArgs);
36    let plugin = match Plugin::parse(attr) {
37        Ok(plugin) => plugin,
38        Err(err) => return err.to_compile_error().into(),
39    };
40
41    let path = plugin
42        .path
43        .segments
44        .iter()
45        .map(|s| s.ident.to_string())
46        .collect::<Vec<_>>()
47        .join("_");
48
49    let func = syn::parse_macro_input!(item as syn::ItemFn);
50    let name = func.sig.ident.clone();
51
52    let entry = Ident::new(&format!("luaopen_{path}"), Span::call_site());
53    let wrapped = quote! {
54        #func
55
56        #[no_mangle]
57        unsafe extern "C" fn #entry(state: *mut lua_State) -> std::os::raw::c_int {
58            mlua::Lua::init_from_ptr(state)
59                .entrypoint1(#name)
60                .expect("failed to register module")
61        }
62    };
63
64    wrapped.into()
65}