priact_actor_macro/
lib.rs1#![doc = include_str!("../README.md")]
8
9use proc_macro::TokenStream;
10use quote::quote;
11use syn::parse::{Parse, ParseStream, Result};
12use syn::{braced, parenthesized, punctuated::Punctuated, token, Ident, ItemFn, Token, Type};
13
14struct FieldDef {
16 name: Ident,
17 #[allow(dead_code)]
18 colon_token: Token![:],
19 ty: Type,
20}
21
22impl Parse for FieldDef {
23 fn parse(input: ParseStream) -> Result<Self> {
24 Ok(FieldDef {
25 name: input.parse()?,
26 colon_token: input.parse()?,
27 ty: input.parse()?,
28 })
29 }
30}
31
32struct MethodDef {
34 _at: Token![@],
35 _prio_kw: Ident,
36 _paren: token::Paren,
37 priority: Ident,
38 func: ItemFn,
39}
40
41impl Parse for MethodDef {
42 fn parse(input: ParseStream) -> Result<Self> {
43 let _at: Token![@] = input.parse()?;
44 let _prio_kw: Ident = input.parse()?;
45 if _prio_kw != "priority" {
46 return Err(syn::Error::new(_prio_kw.span(), "expected `priority`"));
47 }
48 let content;
49 let _paren = parenthesized!(content in input);
50 let priority: Ident = content.parse()?;
51
52 let func: ItemFn = input.parse()?;
53
54 Ok(MethodDef {
55 _at,
56 _prio_kw,
57 _paren,
58 priority,
59 func,
60 })
61 }
62}
63
64struct ActorDef {
66 actor_name: Ident,
67 fields: Punctuated<FieldDef, Token![,]>,
68 _impl_kw: Token![impl],
69 msg_name: Ident,
70 methods: Vec<MethodDef>,
71}
72
73impl Parse for ActorDef {
74 fn parse(input: ParseStream) -> Result<Self> {
75 let actor_name: Ident = input.parse()?;
76 let content;
77 braced!(content in input);
78 let fields = content.parse_terminated(FieldDef::parse)?;
79
80 let _impl_kw: Token![impl] = input.parse()?;
81 let msg_name: Ident = input.parse()?;
82 let methods_content;
83 braced!(methods_content in input);
84
85 let mut methods = Vec::new();
86 while !methods_content.is_empty() {
87 methods.push(methods_content.parse::<MethodDef>()?);
88 }
89 Ok(ActorDef {
90 actor_name,
91 fields,
92 _impl_kw,
93 msg_name,
94 methods,
95 })
96 }
97}
98
99#[proc_macro]
101pub fn define_actor(input: TokenStream) -> TokenStream {
102 let ActorDef {
103 actor_name,
104 fields,
105 _impl_kw: _,
106 msg_name,
107 methods,
108 } = syn::parse_macro_input!(input as ActorDef);
109
110 let struct_fields = fields.iter().map(|f| {
112 let name = &f.name;
113 let ty = &f.ty;
114 quote! { pub #name: #ty, }
115 });
116
117 let variants = methods.iter().map(|m| {
119 let name = &m.func.sig.ident;
120 let args: Vec<_> = m
121 .func
122 .sig
123 .inputs
124 .iter()
125 .skip(1)
126 .filter_map(|arg| {
127 if let syn::FnArg::Typed(pat_ty) = arg {
128 Some(&pat_ty.ty)
129 } else {
130 None
131 }
132 })
133 .collect();
134 quote! { #name( #(#args),* ), }
135 });
136
137 let priorities = methods.iter().map(|m| {
139 let name = &m.func.sig.ident;
140 let prio = &m.priority;
141 quote! { #msg_name::#name(..) => Priority::#prio, }
142 });
143
144 let handle_arms = methods.iter().map(|m| {
146 let sig = &m.func.sig;
147 let name = &sig.ident;
148 let is_async = sig.asyncness.is_some();
149 let arg_idents: Vec<_> = sig.inputs.iter().skip(1).filter_map(|arg| {
150 if let syn::FnArg::Typed(pat_ty) = arg {
151 if let syn::Pat::Ident(pi) = &*pat_ty.pat {
152 Some(&pi.ident)
153 } else { None }
154 } else { None }
155 }).collect();
156
157 if is_async {
158 quote! { #msg_name::#name( #(#arg_idents),* ) => { self.#name( #(#arg_idents),* ).await; true }, }
159 } else {
160 quote! { #msg_name::#name( #(#arg_idents),* ) => { self.#name( #(#arg_idents),* ); true }, }
161 }
162 });
163
164 let method_defs = methods.iter().map(|m| {
166 let sig = &m.func.sig;
167 let body = &m.func.block;
168 quote! {
169 pub #sig #body
171 }
172 });
173
174 let expanded = quote! {
175 pub struct #actor_name {
176 #(#struct_fields)*
177 }
178
179 impl Drop for #actor_name {
180 fn drop(&mut self) {
181 println!("[{}] Actor instance being dropped.", stringify!(#actor_name));
182 }
183 }
184
185 pub enum #msg_name {
186 #(#variants)*
187 Shutdown,
188 }
189
190 impl Prioritized for #msg_name {
191 fn priority(&self) -> Priority {
192 match self {
193 #(#priorities)*
194 #msg_name::Shutdown => Priority::Shutdown,
195 }
196 }
197 }
198
199 #[async_trait::async_trait]
200 impl Actor for #actor_name {
201 type Msg = #msg_name;
202 async fn handle(&mut self, msg: Self::Msg) -> bool {
203 match msg {
204 #(#handle_arms)*
205 #msg_name::Shutdown => false,
206 }
207 }
208 }
209
210 #[allow(non_snake_case)]
211 impl #actor_name {
212 #(#method_defs)*
213 }
214 };
215
216 TokenStream::from(expanded)
217}