pupactor_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse_macro_input, Data, DeriveInput, Fields, Ident, LitStr};
4
5#[proc_macro_derive(ActorMsgHandle, attributes(actor))]
6pub fn actor_msg_handle_derive(input: TokenStream) -> TokenStream {
7    let input = parse_macro_input!(input as DeriveInput);
8    let enum_name = input.ident; // enum's name
9
10    let mut actor_idents = Vec::new();
11
12    // Extract name from attr #[actor(FirstTestActor)]
13    for attr in input.attrs {
14        if attr.path().is_ident("actor") {
15            attr.parse_nested_meta(|meta| {
16                if meta.path.is_ident("kind") {
17                    // this parses the `kind`
18                    let value = meta.value()?; // this parses the `=`
19                    let lit_str: LitStr = value.parse()?; // this parses `"EarlGrey"`
20                    actor_idents.push(Ident::new(&lit_str.value(), lit_str.span()));
21                    Ok(())
22                } else {
23                    Err(meta.error("no kind attribute"))
24                }
25            })
26                .unwrap_or_else(|err| {
27                    panic!("Failed to parse actor attribute: {}", err);
28                });
29        }
30    }
31
32    let expanded_list: Vec<_> = actor_idents
33        .into_iter()
34        .map(|actor_ident| {
35            // Extract enum variants and generation match case
36            let variants = if let Data::Enum(data_enum) = &input.data {
37                data_enum.variants.iter().map(|variant| {
38                    let variant_name = &variant.ident;
39                    match &variant.fields {
40                        Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
41                            // let field_type = &fields.unnamed[0].ty;
42                            quote! {
43                                #enum_name::#variant_name(val) => pupactor::AsyncHandle::async_handle(self, val).await.into(),
44                            }
45                        }
46                        _ => quote! {
47                            _ => panic!("Unsupported enum variant or structure"),
48                        },
49                    }
50                }).collect::<Vec<_>>()
51            } else {
52                panic!("ActorMsgHandle can only be derived for enums");
53            };
54
55            // Code generation
56            quote! {
57                impl pupactor::AsyncHandle<#enum_name> for #actor_ident {
58                    #[inline(always)]
59                    async fn async_handle(&mut self, value: #enum_name) -> pupactor::ActorCmdRes<Self::Cmd> {
60                        match value {
61                            #(#variants)*
62                        }
63                    }
64                }
65            }
66        })
67        .collect();
68
69    TokenStream::from(quote! {
70        #(#expanded_list)*
71    })
72}
73
74#[proc_macro_derive(Pupactor, attributes(actor, listener))]
75pub fn pupactor_derive(input: TokenStream) -> TokenStream {
76    let input = parse_macro_input!(input as DeriveInput);
77    let struct_name = input.ident;
78
79    // Search type `cmd`
80    let mut cmd_ident = None;
81    for attr in input.attrs {
82        if attr.path().is_ident("actor") {
83            attr.parse_nested_meta(|meta| {
84                if meta.path.is_ident("cmd") {
85                    // this parses the `kind`
86                    let value = meta.value()?; // this parses the `=`
87                    let lit_str: LitStr = value.parse()?; // this parses `"EarlGrey"`
88                    cmd_ident = Some(Ident::new(&lit_str.value(), lit_str.span()));
89                    Ok(())
90                } else {
91                    Err(meta.error("no kind attribute"))
92                }
93            })
94                .unwrap_or_else(|err| {
95                    panic!("Failed to parse actor attribute: {}", err);
96                });
97        }
98    }
99
100    // Actor `cmd` msg. By default, is Infallible
101    let cmd_ident = match cmd_ident {
102        Some(cmd_ident) => {
103            quote! { #cmd_ident }
104        }
105        None => quote! { std::convert::Infallible }
106    };
107
108    // Find all properties with attr #[listener]
109    let listeners = if let Data::Struct(data_struct) = input.data {
110        data_struct
111            .fields
112            .iter()
113            .filter_map(|field| {
114                let field_name = field.ident.clone();
115                field
116                    .attrs
117                    .iter()
118                    .find(|attr| attr.path().is_ident("listener"))
119                    .map(|_| field_name)
120            })
121            .collect::<Vec<_>>()
122    } else {
123        panic!("`Pupactor` can only be derived for structs");
124    };
125
126    // Code that handle matches and handle msg
127    let match_msg_inside_loop = quote! {
128        match msg {
129            pupactor::ActorMsg::Msg(msg) => {
130                let cmd: pupactor::ActorCmdRes<Self::Cmd> = <Self as pupactor::AsyncHandle<_>>::async_handle(self, msg).await.into();
131                if let Err(err) = cmd.0 {
132                    if err.is_ok() {
133                        return err;
134                    } else {
135                        break;
136                    }
137                } else {
138                    continue;
139                }
140            }
141            pupactor::ActorMsg::Cmd(cmd) => {
142                return Ok(Self::Cmd::from(cmd));
143            }
144        }
145    };
146
147    let internal_loop = match listeners.len() {
148        0 => quote! { },
149        1 => {
150            let field_name = listeners.first().unwrap();
151            quote! {
152                while let Some(msg) = Listener::next_msg(&mut self.#field_name).await {
153                    #match_msg_inside_loop
154                }
155            }
156        }
157        _ => {
158            let listener_branches = listeners.iter().map(|field_name| {
159                quote! {
160                    msg = Listener::next_msg(&mut self.#field_name) => {
161                        if let Some(msg) = msg {
162                            #match_msg_inside_loop
163                        } else {
164                            break;
165                        }
166                    }
167                }
168            });
169            quote! {
170                loop {
171                    tokio::select! {
172                        #(#listener_branches)*
173                    }
174                }
175            }
176        }
177    };
178
179    let expanded = quote! {
180        impl pupactor::Actor for #struct_name {
181            type Cmd = #cmd_ident;
182
183            async fn infinite_loop(&mut self) -> Result<Self::Cmd, pupactor::Break> {
184                #internal_loop
185                Err(pupactor::Break)
186            }
187        }
188    };
189    TokenStream::from(expanded)
190}
191
192/// ActorCmd msg required always implement `From<Infallible>`
193#[proc_macro_derive(ActorCmd)]
194pub fn actor_cmd_derive(input: TokenStream) -> TokenStream {
195    let input = parse_macro_input!(input as DeriveInput);
196    let struct_name = input.ident;
197
198    let expanded = quote! {
199        impl From<std::convert::Infallible> for #struct_name {
200            #[inline(always)]
201            fn from(_: std::convert::Infallible) -> Self {
202                unreachable!()
203            }
204        }
205    };
206
207    TokenStream::from(expanded)
208}