channel-protocol 0.3.0

Enable function-based communication instead of enum message sending. All based on channels.
Documentation
use proc_macro2::TokenStream;
use quote::ToTokens;
use quote::quote;
use syn::Field;
use syn::FieldMutability;
use syn::Ident;
use syn::ReturnType;
use syn::punctuated::Punctuated;
use syn::token::Comma;

use crate::channel_protocol::ProtocolMessageFnArg;
use crate::channel_protocol::{Protocol, ProtocolMessage};
use crate::render::message::MessageSignatureKind;

struct MessageStructDefinitionRenderer<'a> {
    message: &'a ProtocolMessage,
}

fn arg_to_field(arg: &ProtocolMessageFnArg) -> Field {
    Field {
        attrs: Vec::new(),
        vis: syn::Visibility::Inherited,
        mutability: FieldMutability::None,
        ident: Some(arg.ident.clone()),
        colon_token: Some(syn::token::Colon::default()),
        ty: arg.ty.clone(),
    }
}

impl ToTokens for MessageStructDefinitionRenderer<'_> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        if self.message.args.is_empty() {
            return;
        }

        let struct_name = self.message.struct_ident();
        let fields = self
            .message
            .args
            .iter()
            .map(arg_to_field)
            .collect::<Punctuated<_, Comma>>();

        tokens.extend(quote! {
            #[derive(Debug)]
            struct #struct_name {
                #fields
            }
        });
    }
}

struct MessageDebugImplRenderer<'a> {
    protocol: &'a Protocol,
}

struct MessageMatchArmDebugImplRenderer<'a> {
    message: &'a ProtocolMessage,
    enum_ident: &'a Ident,
}

impl ToTokens for MessageMatchArmDebugImplRenderer<'_> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let message = self.message;
        let ident = &message.ident;
        let variant_ident = message.pascal_case_ident();
        let enum_ident = self.enum_ident;

        let match_arm = match self.message.signature_kind() {
            MessageSignatureKind::None => {
                quote! {
                    #enum_ident::#variant_ident => write!(f, "{}()", stringify!(#ident)),
                }
            }
            MessageSignatureKind::OnlyReturn => {
                let ret = &message.output;
                quote! {
                    #enum_ident::#variant_ident(_) => write!(f, "{}() {}", stringify!(#ident), stringify!(#ret)),
                }
            }
            MessageSignatureKind::OnlyParam => {
                let struct_param = message.struct_ident();

                let arg_names = message
                    .args
                    .iter()
                    .map(|arg| &arg.ident)
                    .collect::<Punctuated<_, Comma>>();

                #[allow(clippy::literal_string_with_formatting_args)]
                let punctuated_arg_format = message
                    .args
                    .iter()
                    .map(|_| "{:?}")
                    .collect::<Vec<_>>()
                    .join(", ");

                let format_str = format!("{{}}({punctuated_arg_format})");

                quote! {
                    #enum_ident::#variant_ident(#struct_param { #arg_names }) => {
                        write!(f, #format_str, stringify!(#ident), #arg_names)
                    },
                }
            }
            MessageSignatureKind::ParamReturn => {
                let struct_param = message.struct_ident();
                let ret = &message.output;

                let arg_names = message
                    .args
                    .iter()
                    .map(|arg| &arg.ident)
                    .collect::<Punctuated<_, Comma>>();

                #[allow(clippy::literal_string_with_formatting_args)]
                let punctuated_arg_format = message
                    .args
                    .iter()
                    .map(|_| "{:?}")
                    .collect::<Vec<_>>()
                    .join(", ");

                let format_str = format!("{{}}({punctuated_arg_format}) {{}}");

                quote! {
                    #enum_ident::#variant_ident(#struct_param { #arg_names }, _) => {
                        write!(f, #format_str, stringify!(#ident), #arg_names, stringify!(#ret))
                    },
                }
            }
        };

        tokens.extend(match_arm);
    }
}

impl ToTokens for MessageDebugImplRenderer<'_> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let message_enum_ident = self.protocol.message_enum_ident();
        let match_arms =
            self.protocol
                .messages
                .iter()
                .map(|message| MessageMatchArmDebugImplRenderer {
                    message,
                    enum_ident: &message_enum_ident,
                });

        tokens.extend(quote! {
            impl std::fmt::Debug for #message_enum_ident {
                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                    match self {
                        #(#match_arms)*
                    }
                }
            }
        });
    }
}

struct MessageEnumDefinitionRenderer<'a> {
    protocol: &'a Protocol,
}

impl ToTokens for MessageEnumDefinitionRenderer<'_> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        if self.protocol.messages.is_empty() {
            return;
        }

        let variants = self
            .protocol
            .messages
            .iter()
            .map(|m| {
                let variant_name = m.pascal_case_ident();

                let ret_val = match &m.output {
                    ReturnType::Default => None,
                    ReturnType::Type(_, ty) => Some(quote! {
                        oneshot::Sender<#ty>
                    }),
                };

                if m.args.is_empty() {
                    ret_val.map_or_else(
                        || {
                            quote! {
                                #variant_name
                            }
                        },
                        |ret_val| {
                            quote! {
                                #variant_name(#ret_val)
                            }
                        },
                    )
                } else {
                    let struct_name = m.struct_ident();
                    quote! {
                        #variant_name(#struct_name, #ret_val)
                    }
                }
            })
            .collect::<Punctuated<_, Comma>>();

        let vis = &self.protocol.vis;
        let message_enum_name = self.protocol.message_enum_ident();

        tokens.extend(quote! {
            #vis enum #message_enum_name {
                #variants
            }
        });
    }
}

pub fn build(protocol: &Protocol) -> TokenStream {
    let message_enum = MessageEnumDefinitionRenderer { protocol };
    let message_structs = protocol
        .messages
        .iter()
        .map(|m| MessageStructDefinitionRenderer { message: m });
    let enum_debug_impl = MessageDebugImplRenderer { protocol };

    quote! {
        #(#message_structs)*
        #message_enum
        #enum_debug_impl
    }
}