1use proc_macro::TokenStream;
15use quote::{format_ident, quote};
16use syn::parse::{Parse, ParseStream};
17use syn::punctuated::Punctuated;
18use syn::{Error, Expr, LitInt, Result, Token, Type, parse_macro_input};
19
20struct Compose {
22 count: LitInt,
23 ty: Type,
24 build: Expr,
25}
26
27impl Parse for Compose {
28 fn parse(input: ParseStream) -> Result<Self> {
29 let content;
30 syn::parenthesized!(content in input);
31 let count: Expr = content.parse()?;
32 let Expr::Lit(syn::ExprLit {
33 lit: syn::Lit::Int(count),
34 ..
35 }) = count
36 else {
37 return Err(Error::new_spanned(
38 count,
39 "worker count must be a usize literal (ranges are computed at expansion)",
40 ));
41 };
42 content.parse::<Token![,]>()?;
43 let ty: Type = content.parse()?;
44 content.parse::<Token![,]>()?;
45 let build: Expr = content.parse()?;
46 Ok(Compose { count, ty, build })
47 }
48}
49
50struct Specs(Punctuated<Compose, Token![,]>);
51
52impl Parse for Specs {
53 fn parse(input: ParseStream) -> Result<Self> {
54 Ok(Specs(Punctuated::parse_terminated(input)?))
55 }
56}
57
58#[proc_macro]
63pub fn workers(input: TokenStream) -> TokenStream {
64 let Specs(specs) = parse_macro_input!(input as Specs);
65 let specs: Vec<Compose> = specs.into_iter().collect();
66 if specs.is_empty() {
67 return Error::new(
68 proc_macro2::Span::call_site(),
69 "workers! needs at least one (count, Type, build_fn) spec",
70 )
71 .to_compile_error()
72 .into();
73 }
74
75 let first_ty = &specs[0].ty;
76 let variants: Vec<_> = specs
77 .iter()
78 .enumerate()
79 .map(|(i, _)| format_ident!("V{i}"))
80 .collect();
81 let variant_defs = specs.iter().zip(&variants).map(|(s, v)| {
82 let ty = &s.ty;
83 quote! { #v(#ty) }
84 });
85
86 let mut start = 0_usize;
87 let mut build_arms = Vec::new();
88 for (s, v) in specs.iter().zip(&variants) {
89 let n: usize = match s.count.base10_parse() {
90 Ok(n) => n,
91 Err(e) => return e.to_compile_error().into(),
92 };
93 let end = start + n;
94 let build = &s.build;
95 build_arms.push(quote! { #start..#end => Crew::#v((#build)(i)) });
96 start = end;
97 }
98 let total = start;
99
100 let step_arms = variants
101 .iter()
102 .map(|v| quote! { Crew::#v(b) => b.transition(ev) });
103 let init_arms = variants.iter().map(|v| quote! { Crew::#v(b) => b.init() });
104
105 let out = quote! {
106 {
107 enum Crew {
109 #(#variant_defs),*
110 }
111
112 impl ::behavior::Behavior for Crew {
113 type Addr = <#first_ty as ::behavior::Behavior>::Addr;
114 type Msg = <#first_ty as ::behavior::Behavior>::Msg;
115 type Event = <#first_ty as ::behavior::Behavior>::Event;
116 type Sends = <#first_ty as ::behavior::Behavior>::Sends;
117 type Ph = ::behavior::Never;
118 type Error = <#first_ty as ::behavior::Behavior>::Error;
119 type Birth = <#first_ty as ::behavior::Behavior>::Birth;
120
121 fn init(&mut self) -> ::core::result::Result<
122 ::behavior::Actions<Self::Addr, Self::Ph, Self::Sends, Self::Birth>,
123 Self::Error,
124 > {
125 match self {
126 #(#init_arms),*
127 }
128 }
129
130 fn transition(
131 &mut self,
132 ev: Self::Event,
133 ) -> ::core::result::Result<
134 ::behavior::Actions<Self::Addr, Self::Ph, Self::Sends, Self::Birth>,
135 Self::Error,
136 > {
137 match self {
138 #(#step_arms),*
139 }
140 }
141
142 }
143
144 fn crew_build(i: usize) -> Crew {
145 match i {
146 #(#build_arms,)*
147 _ => unreachable!("workers!: fleet index out of range — driver/behavior desync"),
148 }
149 }
150
151 (#total, crew_build as fn(usize) -> Crew)
152 }
153 };
154 out.into()
155}