1use proc_macro::TokenStream;
15use proc_macro_crate::{FoundCrate, crate_name};
16use proc_macro2::Span;
17use quote::{format_ident, quote};
18use syn::parse::{Parse, ParseStream};
19use syn::punctuated::Punctuated;
20use syn::{
21 Error, Expr, FnArg, ImplItem, ItemImpl, LitInt, Result, ReturnType, Token, Type,
22 parse_macro_input,
23};
24
25mod behavior_kw {
26 syn::custom_keyword!(addr);
27 syn::custom_keyword!(message);
28 syn::custom_keyword!(sends);
29 syn::custom_keyword!(births);
30 syn::custom_keyword!(error);
31}
32
33fn behavior_crate() -> Result<proc_macro2::TokenStream> {
34 if std::env::var("CARGO_PKG_NAME").as_deref() == Ok("bombay-behavior") {
35 return Ok(quote!(::behavior));
39 }
40 match crate_name("bombay-behavior") {
41 Ok(FoundCrate::Itself) => Ok(quote!(::behavior)),
42 Ok(FoundCrate::Name(name)) => {
43 let name = syn::Ident::new(&name, Span::call_site());
44 Ok(quote!(::#name))
45 }
46 Err(error) => Err(Error::new(
47 Span::call_site(),
48 format!("could not resolve the bombay-behavior crate: {error}"),
49 )),
50 }
51}
52
53struct BehaviorArgs {
54 addr: Type,
55 message: Type,
56 sends: Type,
57 births: Type,
58 error: Type,
59}
60
61impl Parse for BehaviorArgs {
62 fn parse(input: ParseStream) -> Result<Self> {
63 input.parse::<behavior_kw::addr>()?;
64 input.parse::<Token![=]>()?;
65 let addr = input.parse()?;
66 input.parse::<Token![,]>()?;
67 input.parse::<behavior_kw::message>()?;
68 input.parse::<Token![=]>()?;
69 let message = input.parse()?;
70 input.parse::<Token![,]>()?;
71 input.parse::<behavior_kw::sends>()?;
72 input.parse::<Token![=]>()?;
73 let sends = input.parse()?;
74 input.parse::<Token![,]>()?;
75 input.parse::<behavior_kw::births>()?;
76 input.parse::<Token![=]>()?;
77 let births = input.parse()?;
78 input.parse::<Token![,]>()?;
79 input.parse::<behavior_kw::error>()?;
80 input.parse::<Token![=]>()?;
81 let error = input.parse()?;
82 if input.peek(Token![,]) {
83 input.parse::<Token![,]>()?;
84 }
85 if !input.is_empty() {
86 return Err(input
87 .error("expected exactly addr, message, sends, births, and error in that order"));
88 }
89 Ok(Self {
90 addr,
91 message,
92 sends,
93 births,
94 error,
95 })
96 }
97}
98
99fn validate_receiver(method: &syn::ImplItemFn) -> Result<()> {
100 let Some(FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
101 return Err(Error::new_spanned(
102 &method.sig,
103 "behavior methods must begin with &mut self",
104 ));
105 };
106 if !matches!(receiver.kind, syn::ReceiverKind::Reference(_, _, Some(_))) {
107 return Err(Error::new_spanned(
108 receiver,
109 "behavior methods must begin with &mut self",
110 ));
111 }
112 if method.sig.constness.is_some()
113 || method.sig.asyncness.is_some()
114 || matches!(method.sig.safety, syn::Safety::Unsafe(_))
115 || !method.sig.generics.params.is_empty()
116 {
117 return Err(Error::new_spanned(
118 &method.sig,
119 "behavior init and receive methods must be synchronous, safe, and non-generic",
120 ));
121 }
122 if matches!(method.sig.output, ReturnType::Default) {
123 return Err(Error::new_spanned(
124 &method.sig,
125 "behavior methods must declare their complete Actions result type",
126 ));
127 }
128 Ok(())
129}
130
131#[proc_macro_attribute]
137pub fn behavior(args: TokenStream, item: TokenStream) -> TokenStream {
138 let args = parse_macro_input!(args as BehaviorArgs);
139 let item = parse_macro_input!(item as ItemImpl);
140
141 if item.trait_.is_some() {
142 return Error::new_spanned(
143 &item,
144 "#[behavior] applies to an inherent impl, not a trait impl",
145 )
146 .to_compile_error()
147 .into();
148 }
149
150 let init = item.items.iter().find_map(|item| match item {
151 ImplItem::Fn(method) if method.sig.ident == "init" => Some(method),
152 _ => None,
153 });
154 let receive = item.items.iter().find_map(|item| match item {
155 ImplItem::Fn(method) if method.sig.ident == "receive" => Some(method),
156 _ => None,
157 });
158 let Some(receive) = receive else {
159 return Error::new_spanned(
160 &item.self_ty,
161 "#[behavior] requires a receive(&mut self, from, message) method",
162 )
163 .to_compile_error()
164 .into();
165 };
166 if let Err(error) = init
167 .map_or(Ok(()), validate_receiver)
168 .and_then(|()| validate_receiver(receive))
169 {
170 return error.to_compile_error().into();
171 }
172 if let Some(init) = init
173 && init.sig.inputs.len() != 1
174 {
175 return Error::new_spanned(&init.sig, "init must accept exactly &mut self")
176 .to_compile_error()
177 .into();
178 }
179 if receive.sig.inputs.len() != 3 {
180 return Error::new_spanned(
181 &receive.sig,
182 "receive must accept exactly &mut self, from, and message",
183 )
184 .to_compile_error()
185 .into();
186 }
187
188 let BehaviorArgs {
189 addr,
190 message,
191 sends,
192 births,
193 error,
194 } = args;
195 let self_ty = &item.self_ty;
196 let (impl_generics, _, where_clause) = item.generics.split_for_impl();
197 let behavior = match behavior_crate() {
198 Ok(behavior) => behavior,
199 Err(error) => return error.to_compile_error().into(),
200 };
201 let initialize = init.map_or_else(
202 || quote!(::core::result::Result::Ok(#behavior::Actions::cont())),
203 |_| quote!(<#self_ty>::init(self)),
204 );
205
206 quote! {
207 #item
208
209 impl #impl_generics #behavior::Behavior for #self_ty #where_clause {
210 type Addr = #addr;
211 type Msg = #message;
212 type Event = #behavior::User<#addr, #message>;
213 type Sends = #sends;
214 type Ph = #behavior::Never;
215 type Error = #error;
216 type Birth = #births;
217
218 fn init(
219 &mut self,
220 _: #behavior::InitializationTurn,
221 ) -> #behavior::BehaviorActed<Self> {
222 #initialize
223 }
224
225 fn transition(
226 &mut self,
227 _: #behavior::ActiveTurn,
228 event: Self::Event,
229 ) -> #behavior::BehaviorActed<Self> {
230 <#self_ty>::receive(self, event.from, event.message)
231 }
232 }
233
234 impl #impl_generics #behavior::BehaviorBase for #self_ty #where_clause {
235 type Base = Self;
236
237 fn base(&self) -> &Self {
238 self
239 }
240 }
241 }
242 .into()
243}
244
245struct Compose {
247 count: LitInt,
248 ty: Type,
249 build: Expr,
250}
251
252impl Parse for Compose {
253 fn parse(input: ParseStream) -> Result<Self> {
254 let content;
255 syn::parenthesized!(content in input);
256 let count: Expr = content.parse()?;
257 let Expr::Lit(syn::ExprLit {
258 lit: syn::Lit::Int(count),
259 ..
260 }) = count
261 else {
262 return Err(Error::new_spanned(
263 count,
264 "worker count must be a usize literal (ranges are computed at expansion)",
265 ));
266 };
267 content.parse::<Token![,]>()?;
268 let ty: Type = content.parse()?;
269 content.parse::<Token![,]>()?;
270 let build: Expr = content.parse()?;
271 Ok(Compose { count, ty, build })
272 }
273}
274
275struct Specs(Punctuated<Compose, Token![,]>);
276
277impl Parse for Specs {
278 fn parse(input: ParseStream) -> Result<Self> {
279 Ok(Specs(Punctuated::parse_terminated(input)?))
280 }
281}
282
283#[proc_macro]
288pub fn workers(input: TokenStream) -> TokenStream {
289 let Specs(specs) = parse_macro_input!(input as Specs);
290 let specs: Vec<Compose> = specs.into_iter().collect();
291 if specs.is_empty() {
292 return Error::new(
293 proc_macro2::Span::call_site(),
294 "workers! needs at least one (count, Type, build_fn) spec",
295 )
296 .to_compile_error()
297 .into();
298 }
299
300 let first_ty = &specs[0].ty;
301 let behavior = match behavior_crate() {
302 Ok(behavior) => behavior,
303 Err(error) => return error.to_compile_error().into(),
304 };
305 let variants: Vec<_> = specs
306 .iter()
307 .enumerate()
308 .map(|(i, _)| format_ident!("V{i}"))
309 .collect();
310 let variant_defs = specs.iter().zip(&variants).map(|(s, v)| {
311 let ty = &s.ty;
312 quote! { #v(#ty) }
313 });
314
315 let mut start = 0_usize;
316 let mut build_arms = Vec::new();
317 for (s, v) in specs.iter().zip(&variants) {
318 let n: usize = match s.count.base10_parse() {
319 Ok(n) => n,
320 Err(e) => return e.to_compile_error().into(),
321 };
322 let end = start + n;
323 let build = &s.build;
324 build_arms
325 .push(quote! { #start..#end => ::core::option::Option::Some(Worker::#v((#build)(i))) });
326 start = end;
327 }
328 let total = start;
329
330 let step_arms = variants
331 .iter()
332 .map(|v| quote! { Worker::#v(b) => b.transition(turn, ev) });
333 let init_arms = variants
334 .iter()
335 .map(|v| quote! { Worker::#v(b) => b.init(turn) });
336
337 let out = quote! {
338 {
339 enum Worker {
341 #(#variant_defs),*
342 }
343
344 impl #behavior::Behavior for Worker {
345 type Addr = <#first_ty as #behavior::Behavior>::Addr;
346 type Msg = <#first_ty as #behavior::Behavior>::Msg;
347 type Event = <#first_ty as #behavior::Behavior>::Event;
348 type Sends = <#first_ty as #behavior::Behavior>::Sends;
349 type Ph = #behavior::Never;
350 type Error = <#first_ty as #behavior::Behavior>::Error;
351 type Birth = <#first_ty as #behavior::Behavior>::Birth;
352
353 fn init(&mut self, turn: #behavior::InitializationTurn) -> ::core::result::Result<
354 #behavior::Actions<Self::Addr, Self::Ph, Self::Sends, Self::Birth>,
355 Self::Error,
356 > {
357 match self {
358 #(#init_arms),*
359 }
360 }
361
362 fn transition(
363 &mut self,
364 turn: #behavior::ActiveTurn,
365 ev: Self::Event,
366 ) -> ::core::result::Result<
367 #behavior::Actions<Self::Addr, Self::Ph, Self::Sends, Self::Birth>,
368 Self::Error,
369 > {
370 match self {
371 #(#step_arms),*
372 }
373 }
374
375 }
376
377 fn build_worker(i: usize) -> ::core::option::Option<Worker> {
378 match i {
379 #(#build_arms,)*
380 _ => ::core::option::Option::None,
381 }
382 }
383
384 (#total, build_worker as fn(usize) -> ::core::option::Option<Worker>)
385 }
386 };
387 out.into()
388}