intuicio-derive 0.3.5

Procedural macro module for Intuicio scripting platform
Documentation
use proc_macro::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
    parse_macro_input, AttributeArgs, FnArg, Ident, ImplItem, ItemImpl, Lit, Meta, NestedMeta, Pat,
    ReturnType, Visibility,
};

#[derive(Default)]
struct ImplAttributes {
    pub module_name: Option<Ident>,
}

#[derive(Default)]
struct MethodAttributes {
    pub name: Option<Ident>,
    pub use_registry: bool,
    pub use_context: bool,
    pub debug: bool,
}

macro_rules! parse_impl_attributes {
    ($attributes:ident) => {{
        let mut result = ImplAttributes::default();
        let attributes = parse_macro_input!($attributes as AttributeArgs);
        for attribute in attributes {
            match attribute {
                NestedMeta::Meta(meta) => match meta {
                    Meta::NameValue(name_value) => {
                        if name_value.path.is_ident("module_name") {
                            match name_value.lit {
                                Lit::Str(content) => {
                                    result.module_name =
                                        Some(Ident::new(&content.value(), Span::call_site().into()))
                                }
                                _ => {}
                            }
                        }
                    }
                    _ => {}
                },
                _ => {}
            }
        }
        result
    }};
}

macro_rules! parse_method_attributes {
    ($attributes:expr) => {{
        let mut found = false;
        let mut result = MethodAttributes::default();
        for attribute in $attributes {
            let attribute = match attribute.parse_meta() {
                Ok(attribute) => attribute,
                Err(err) => return TokenStream::from(err.to_compile_error()),
            };
            match attribute {
                Meta::List(list) => {
                    if list.path.is_ident("intuicio_method") {
                        found = true;
                        for meta in list.nested.iter() {
                            match meta {
                                NestedMeta::Meta(meta) => match meta {
                                    Meta::Path(path) => {
                                        if path.is_ident("use_registry") {
                                            result.use_registry = true;
                                        } else if path.is_ident("use_context") {
                                            result.use_context = true;
                                        } else if path.is_ident("debug") {
                                            result.debug = true;
                                        }
                                    }
                                    Meta::NameValue(name_value) => {
                                        if name_value.path.is_ident("name") {
                                            match &name_value.lit {
                                                Lit::Str(content) => {
                                                    result.name = Some(Ident::new(
                                                        &content.value(),
                                                        Span::call_site().into(),
                                                    ))
                                                }
                                                _ => {}
                                            }
                                        }
                                    }
                                    _ => {}
                                },
                                _ => {}
                            }
                        }
                    }
                }
                _ => {}
            }
        }
        (result, found)
    }};
}

pub fn intuicio_methods(attributes: TokenStream, input: TokenStream) -> TokenStream {
    let ImplAttributes { module_name } = parse_impl_attributes!(attributes);
    let item = parse_macro_input!(input as ItemImpl);
    if item.trait_.is_some() {
        panic!("Intuicio methods must be applied only for non-trait implementations!");
    }
    let module_name = if let Some(module_name) = module_name {
        quote! { result.module_name = Some(stringify!(#module_name).to_owned()); }
    } else {
        quote! {}
    };
    let struct_type = &item.self_ty;
    let struct_handle = quote! {result.struct_handle = Some(registry.find_struct(intuicio_core::struct_type::StructQuery::of_type_name::<#struct_type>()).unwrap()); };
    let items = item
        .items
        .iter()
        .filter_map(|item| match item {
            ImplItem::Method(method) => Some(method),
            _ => None,
        })
        .collect::<Vec<_>>();
    let mut methods = Vec::with_capacity(items.len());
    for item in items {
        let (
            MethodAttributes {
                name,
                use_registry,
                use_context,
                debug,
            },
            found,
        ) = parse_method_attributes!(&item.attrs);
        if !found {
            continue;
        }
        let intuicio_function_ident = Ident::new(
            &format!("{}__intuicio_function", item.sig.ident),
            Span::call_site().into(),
        );
        let define_signature_ident = Ident::new(
            &format!("{}__define_signature", item.sig.ident),
            Span::call_site().into(),
        );
        let define_function_ident = Ident::new(
            &format!("{}__define_function", item.sig.ident),
            Span::call_site().into(),
        );
        let vis = item.vis.clone();
        let ident = item.sig.ident.clone();
        let name = if let Some(name) = name {
            quote! { result.name = stringify!(#name).to_owned(); }
        } else {
            quote! {}
        };
        let visibility = match vis {
            Visibility::Inherited => {
                quote! { result.visibility = intuicio_core::Visibility::Private; }
            }
            Visibility::Restricted(_) | Visibility::Crate(_) => {
                quote! { result.visibility = intuicio_core::Visibility::Module; }
            }
            Visibility::Public(_) => quote! {},
        };
        let arg_idents = item
            .sig
            .inputs
            .iter()
            .filter_map(|arg| match arg {
                FnArg::Receiver(_) => Some(Ident::new("this", Span::call_site().into())),
                FnArg::Typed(meta) => match &*meta.pat {
                    Pat::Ident(ident) => {
                        if (use_registry && ident.ident == "registry")
                            || (use_context && ident.ident == "context")
                        {
                            None
                        } else {
                            Some(ident.ident.clone())
                        }
                    }
                    _ => panic!("Only identifiers are accepted as argument names!"),
                },
            })
            .collect::<Vec<_>>();
        let call_arg_idents = item
            .sig
            .inputs
            .iter()
            .map(|arg| match arg {
                FnArg::Receiver(_) => Ident::new("this", Span::call_site().into()),
                FnArg::Typed(meta) => match &*meta.pat {
                    Pat::Ident(ident) => ident.ident.clone(),
                    _ => panic!("Only identifiers are accepted as argument names!"),
                },
            })
            .collect::<Vec<_>>();
        let arg_types = item
            .sig
            .inputs
            .iter()
            .filter_map(|arg| match arg {
                FnArg::Receiver(_) => Some(struct_type),
                FnArg::Typed(meta) => {
                    let ident = match &*meta.pat {
                        Pat::Ident(ident) => &ident.ident,
                        _ => panic!("Only identifiers are accepted as argument names!"),
                    };
                    if (use_registry && ident == "registry") || (use_context && ident == "context")
                    {
                        None
                    } else {
                        Some(&meta.ty)
                    }
                }
            })
            .collect::<Vec<_>>();
        let return_idents = match item.sig.output {
            ReturnType::Default => vec![],
            ReturnType::Type(_, _) => vec!["result"],
        };
        let return_types = match item.sig.output {
            ReturnType::Default => vec![],
            ReturnType::Type(_, ref ty) => vec![ty.clone()],
        };
        let result = quote! {
            #[allow(dead_code)]
            #[allow(non_snake_case)]
            pub fn #intuicio_function_ident(
                context: &mut intuicio_core::context::Context,
                registry: &intuicio_core::registry::Registry,
            ) {
                use intuicio_data::data_stack::DataStackPack;
                #[allow(unused_mut)]
                let (#(mut #arg_idents,)*) = <(#(#arg_types,)*)>::stack_pop(context.stack());
                let result = #struct_type::#ident(#(#call_arg_idents,)*);
                (result,).stack_push_reversed(context.stack());
            }

            #[allow(dead_code)]
            #[allow(non_snake_case)]
            pub fn #define_signature_ident(
                registry: &intuicio_core::registry::Registry
            ) -> intuicio_core::function::FunctionSignature {
                let mut result = intuicio_core::function::FunctionSignature::new(stringify!(#ident));
                #visibility
                #name
                #module_name
                #struct_handle
                #(
                    result.inputs.push(
                        intuicio_core::function::FunctionParameter::new(
                            stringify!(#arg_idents),
                            registry.find_struct(intuicio_core::struct_type::StructQuery::of_type_name::<#arg_types>()).unwrap()
                        )
                    );
                )*
                #(
                    result.outputs.push(
                        intuicio_core::function::FunctionParameter::new(
                            #return_idents,
                            registry.find_struct(intuicio_core::struct_type::StructQuery::of_type_name::<#return_types>()).unwrap()
                        )
                    );
                )*
                result
            }

            #[allow(dead_code)]
            #[allow(non_snake_case)]
            pub fn #define_function_ident(
                registry: &intuicio_core::registry::Registry
            ) -> intuicio_core::function::Function {
                intuicio_core::function::Function::new(
                    #struct_type::#define_signature_ident(registry),
                    intuicio_core::function::FunctionBody::pointer(#struct_type::#intuicio_function_ident),
                )
            }
        };
        if debug {
            println!(
                "* Debug of `intuicio_methods` attribute macro\n- Input: {}\n- Result: {}",
                item.to_token_stream(),
                result
            );
        }
        methods.push(result);
    }
    quote! {
        impl #struct_type {
            #(#methods)*
        }

        #item
    }
    .into()
}