chikatetsu-macros 0.1.0

Macros for the chikatetsu actor framework
Documentation
use proc_macro::TokenStream;

use syn::*;
use syn::punctuated::Punctuated;

use quote::*;

fn parse_handles(attrs: Vec<Attribute>) -> (Vec<Ident>, Vec<Ident>) {
    let mut message_idents = Vec::new();
    let mut reply_idents = Vec::new();
    for attr in attrs {
        if attr.path.is_ident("handles") {
            if let Ok(idents) = attr.parse_args_with(Punctuated::<Ident, Token![,]>::parse_terminated) {
                if idents.len() == 2 {
                    let mut idents = idents.into_iter();
                    message_idents.push(idents.next().unwrap());
                    reply_idents.push(idents.next().unwrap());
                }
            }
        }
        
    }
    (message_idents, reply_idents)
}

#[proc_macro_derive(Actor, attributes(handles))]
pub fn derive_message_group(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let input_ident = input.ident;

    let messages_ident = format_ident!("{}Messages", input_ident);
    let replies_ident = format_ident!("{}Replies", input_ident);

    let (messages, replies) = parse_handles(input.attrs);

    let message_impl = quote! {
        #(impl ::chikatetsu::message::Message for #messages {
            type Group = #messages_ident;
            type Reply = #replies;

            fn to_group(self) -> #messages_ident {
                #messages_ident::#messages(self)
            }
        })*
    };

    let message_group_enum = quote! {
        pub enum #messages_ident {
            #(#messages(#messages)),*
        }

        impl ::chikatetsu::message::MessageGroup for #messages_ident {
            type ReplyGroup = #replies_ident;
        }
    };

    let reply_group_enum = quote! {
        pub enum #replies_ident {
            #(#replies(#replies)),*
        }

        #(
            impl ::chikatetsu::message::Reply for #replies {
                type Group = #replies_ident;

                fn from_group(group: #replies_ident) -> Option<Self> {
                    match group {
                        #replies_ident::#replies(inner) => Some(inner),
                        _ => None,
                    }
                }
            }
        )*
    };

    let actor_impl = quote! {
        #[::chikatetsu::async_trait::async_trait]
        impl ::chikatetsu::actor::Actor for #input_ident {
            type MessageGroup = #messages_ident;
            async fn handle_all(&mut self, msg: #messages_ident) -> #replies_ident {
                match msg {
                    #(#messages_ident::#messages(inner) => #replies_ident::#replies(self.handle(inner).await)),*
                }
            }
        }
    };

    let output = quote!{
        #message_impl
        #message_group_enum
        #reply_group_enum
        #actor_impl
    };
    TokenStream::from(output)
}