Skip to main content

priact_actor_macro/
lib.rs

1//! actor-macro: the `define_actor!` proc-macro for building priority-driven actors.
2//!
3//! ```rust
4//! use priact::{define_actor, spawn_actor, Actor};
5//! define_actor! { … }
6//! ```
7#![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
14// Represents one field: `name: Type`
15struct 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
32// Represents one method: `@priority(P) fn foo(&mut self, ...) -> Ret { .. }` or `async fn`
33struct 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
64// Top-level parse for define_actor!
65struct 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/// The procedural macro entry point
100#[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    // Struct fields
111    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    // Enum variants: always tuple variants (even zero-arg)
118    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    // Priority match arms
138    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    // handle() match arms: always tuple patterns
145    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    // Method implementations: directly use the parsed signature and body
165    let method_defs = methods.iter().map(|m| {
166        let sig = &m.func.sig;
167        let body = &m.func.block;
168        quote! {
169            // Note: ItemFn's visibility is not used; we force `pub` here.
170            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}