1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3use syn::{
4 braced, parenthesized,
5 parse::{Parse, ParseStream},
6 parse_macro_input, parse_quote,
7 punctuated::Punctuated,
8 Error, Expr, ExprBreak, ExprReturn, GenericParam, Generics, Ident, ItemFn, LifetimeDef, Member,
9 Pat, PatIdent, PatPath, PatTupleStruct, Path, PathArguments, PathSegment, ReturnType,
10 Signature, Token, Type, TypeParam, TypePath, Visibility,
11};
12
13fn quote_do(e: &Expr) -> Expr {
14 parse_quote! {
15 {
16 use ::core::ops::{Generator, GeneratorState};
17 use ::effing_mad::frunk::Coproduct;
18 let mut gen = #e;
19 let mut injection = Coproduct::inject(::effing_mad::injection::Begin);
20 loop {
21 let res = {
24 let pinned = unsafe { ::core::pin::Pin::new_unchecked(&mut gen) };
26 pinned.resume(injection)
27 };
28 match res {
29 GeneratorState::Yielded(effs) =>
30 injection = (yield effs.embed()).subset().ok().unwrap(),
31 GeneratorState::Complete(v) => break v,
32 }
33 }
34 }
35 }
36}
37
38struct Effectful {
39 effects: Punctuated<Type, Token![,]>,
40}
41
42impl Parse for Effectful {
43 fn parse(input: ParseStream) -> Result<Self, Error> {
44 let effects = Punctuated::parse_terminated(input)?;
45 Ok(Effectful { effects })
46 }
47}
48
49impl syn::visit_mut::VisitMut for Effectful {
50 fn visit_expr_mut(&mut self, e: &mut Expr) {
51 match e {
52 Expr::Field(ref mut ef) => {
53 self.visit_expr_mut(&mut ef.base);
54 let Member::Named(ref name) = ef.member else { return };
55 if name == "do_" {
56 *e = quote_do(&ef.base);
57 }
58 },
59 Expr::Yield(ref y) => {
60 let Some(ref expr) = y.expr else { panic!("no expr?") };
61 *e = parse_quote! {
62 {
63 let effect = { #expr };
64 let marker = ::effing_mad::macro_impl::mark(&effect);
65 let injs = yield ::effing_mad::frunk::Coproduct::inject(effect);
66 ::effing_mad::macro_impl::get_inj(injs, marker).unwrap()
67 }
68 };
69 },
70 e => syn::visit_mut::visit_expr_mut(self, e),
71 }
72 }
73}
74
75#[proc_macro_attribute]
109pub fn effectful(args: TokenStream, item: TokenStream) -> TokenStream {
110 let mut effects = parse_macro_input!(args as Effectful);
111 let effect_names = effects.effects.iter();
112 let yield_type = quote! {
113 <::effing_mad::frunk::Coprod!(#(#effect_names),*) as ::effing_mad::macro_impl::FlattenEffects>::Out
114 };
115 let ItemFn {
116 mut attrs,
117 vis,
118 sig,
119 mut block,
120 } = parse_macro_input!(item as ItemFn);
121 let Signature {
122 constness,
123 unsafety,
124 ident,
125 generics,
126 inputs,
127 output,
128 ..
129 } = sig;
130 let return_type = match output {
131 ReturnType::Default => quote!(()),
132 ReturnType::Type(_r_arrow, ref ty) => ty.to_token_stream(),
133 };
134 syn::visit_mut::visit_block_mut(&mut effects, &mut block);
135 let mut cloneable = false;
136 attrs.retain(|attr| {
137 if attr.path == parse_quote!(effectful::cloneable) {
138 cloneable = true;
139 false } else {
141 true
142 }
143 });
144 let clone_bound = cloneable.then_some(quote!( + ::core::clone::Clone + ::core::marker::Unpin));
145 quote! {
146 #(#attrs)*
147 #vis #constness #unsafety
148 fn #ident #generics(#inputs)
149 -> impl ::core::ops::Generator<
150 <#yield_type as ::effing_mad::injection::EffectList>::Injections,
151 Yield = #yield_type,
152 Return = #return_type
153 > #clone_bound {
154 move |_begin: <#yield_type as ::effing_mad::injection::EffectList>::Injections| {
155 #block
156 }
157 }
158 }
159 .into()
160}
161
162struct EffectArg {
163 name: Ident,
164 ty: Type,
165}
166
167impl Parse for EffectArg {
168 fn parse(input: ParseStream) -> syn::Result<Self> {
169 let name = input.parse()?;
170 let _: Token![:] = input.parse()?;
171 let ty: Type = input.parse()?;
172 Ok(EffectArg { name, ty })
173 }
174}
175
176struct Effect {
177 name: Ident,
178 args: Punctuated<EffectArg, Token![,]>,
179 ret: Type,
180}
181
182impl Parse for Effect {
183 fn parse(input: ParseStream) -> syn::Result<Self> {
184 <Token![fn]>::parse(input)?;
185 let name = input.parse()?;
186
187 let content;
188 parenthesized!(content in input);
189 let args = Punctuated::parse_terminated(&content)?;
190
191 <Token![->]>::parse(input)?;
192 let ret = input.parse()?;
193
194 Ok(Effect { name, args, ret })
195 }
196}
197
198struct Effects {
199 vis: Visibility,
200 group_name: Ident,
201 generics: Generics,
202 effects: Punctuated<Effect, Token![;]>,
203}
204
205impl Parse for Effects {
206 fn parse(input: ParseStream) -> syn::Result<Self> {
207 let vis = input.parse()?;
208 let group_name = input.parse()?;
209 let generics = input.parse()?;
210
211 let content;
212 braced!(content in input);
213 let effects = Punctuated::parse_terminated(&content)?;
214
215 Ok(Effects {
216 vis,
217 group_name,
218 generics,
219 effects,
220 })
221 }
222}
223
224#[proc_macro]
244pub fn effects(input: TokenStream) -> TokenStream {
245 let Effects {
246 vis,
247 group_name,
248 generics,
249 effects,
250 } = parse_macro_input!(input as Effects);
251
252 let eff_name = effects.iter().map(|eff| &eff.name).collect::<Vec<_>>();
253 let phantom_datas = generics
254 .params
255 .iter()
256 .map(|param| match param {
257 GenericParam::Type(TypeParam { ident, .. }) => {
258 quote!(::core::marker::PhantomData::<#ident>)
259 },
260 GenericParam::Lifetime(LifetimeDef { lifetime, .. }) => {
261 quote!(::core::marker::PhantomData::<&#lifetime ()>)
262 },
263 GenericParam::Const(_) => todo!(),
264 })
265 .collect::<Vec<_>>();
266 let phantom_datas = quote!(#(#phantom_datas),*);
267 let maybe_phantom_data = generics
268 .lt_token
269 .map(|_| quote!(::core::marker::PhantomData::<#group_name #generics>));
270
271 let arg_name = effects
272 .iter()
273 .map(|eff| eff.args.iter().map(|arg| &arg.name).collect::<Vec<_>>())
274 .collect::<Vec<_>>();
275 let arg_ty = effects
276 .iter()
277 .map(|eff| eff.args.iter().map(|arg| &arg.ty).collect::<Vec<_>>())
278 .collect::<Vec<_>>();
279 let ret_ty = effects.iter().map(|eff| &eff.ret).collect::<Vec<_>>();
280
281 quote! {
282 #vis struct #group_name #generics (#phantom_datas);
283
284 impl #generics #group_name #generics {
285 #(
286 fn #eff_name(#(#arg_name: #arg_ty),*) -> #eff_name #generics {
287 #eff_name(#(#arg_name,)* #maybe_phantom_data)
288 }
289 )*
290 }
291
292 impl #generics ::effing_mad::EffectGroup for #group_name #generics {
293 type Effects = ::effing_mad::frunk::Coprod!(#(#eff_name #generics),*);
294 }
295
296 #(
297 #[allow(non_camel_case_types)]
298 #vis struct #eff_name #generics (#(#arg_ty,)* #maybe_phantom_data);
299
300 impl #generics ::effing_mad::Effect for #eff_name #generics {
301 type Injection = #ret_ty;
302 }
303 )*
304 }
305 .into()
306}
307
308struct HandlerArm {
309 pat: Pat,
310 body: Expr,
311}
312
313impl Parse for HandlerArm {
314 fn parse(input: ParseStream) -> syn::Result<Self> {
315 let pat = input.parse()?;
316 <Token![=>]>::parse(input)?;
317 let body = input.parse()?;
318 Ok(HandlerArm { pat, body })
319 }
320}
321
322struct Handler {
323 asyncness: Option<Token![async]>,
324 moveness: Option<Token![move]>,
325 group: TypePath,
326 arms: Punctuated<HandlerArm, Token![,]>,
327 is_shorthand: bool,
328}
329
330impl Parse for Handler {
331 fn parse(input: ParseStream) -> syn::Result<Self> {
332 let asyncness = input.parse()?;
333 let moveness = input.parse()?;
334 let ahead = input.fork();
335 if ahead.parse::<HandlerArm>().is_ok() {
336 let single_arm: HandlerArm = input.parse().unwrap();
338 let path = match &single_arm.pat {
339 Pat::Ident(PatIdent { ident, .. }) => Path {
341 leading_colon: None,
342 segments: Punctuated::from_iter(std::iter::once(PathSegment {
343 ident: ident.clone(),
344 arguments: PathArguments::None,
345 })),
346 },
347 Pat::Path(PatPath { path, .. }) | Pat::TupleStruct(PatTupleStruct { path, .. }) => {
348 path.clone()
349 },
350 p => panic!("invalid pattern in handler: {p:?}"),
351 };
352 let group = TypePath { qself: None, path };
353
354 return Ok(Handler {
355 asyncness,
356 moveness,
357 group,
358 arms: Punctuated::from_iter(std::iter::once(single_arm)),
359 is_shorthand: true,
360 });
361 }
362 let group = input.parse()?;
363
364 let content;
365 braced!(content in input);
366 let arms = Punctuated::parse_terminated(&content)?;
367 Ok(Handler {
368 asyncness,
369 moveness,
370 group,
371 arms,
372 is_shorthand: false,
373 })
374 }
375}
376
377struct FixControlFlow<T: ToTokens> {
379 eff_ty: T,
380 is_shorthand: bool,
381}
382
383impl<T: ToTokens> syn::visit_mut::VisitMut for FixControlFlow<T> {
384 fn visit_expr_mut(&mut self, e: &mut Expr) {
385 let eff = &self.eff_ty;
386 match e {
387 Expr::Break(ExprBreak { expr, .. }) => {
388 let expr = expr
389 .as_ref()
390 .map(ToTokens::to_token_stream)
391 .unwrap_or(quote!(()));
392 *e = parse_quote!(return ::core::ops::ControlFlow::Break(#expr));
393 },
394 Expr::Return(ExprReturn { expr, .. }) => {
395 let expr = expr
396 .as_ref()
397 .map(ToTokens::to_token_stream)
398 .unwrap_or(quote!(()));
399 let inj = if self.is_shorthand {
400 quote!(#expr)
401 } else {
402 quote!(::effing_mad::injection::Tagged::<_, #eff>::new(#expr))
403 };
404 *e = parse_quote! {
405 return ::core::ops::ControlFlow::Continue(
406 ::effing_mad::frunk::Coproduct::inject(#inj)
407 );
408 };
409 },
410 e => syn::visit_mut::visit_expr_mut(self, e),
411 }
412 }
413}
414
415#[proc_macro]
454pub fn handler(input: TokenStream) -> TokenStream {
455 let Handler {
456 asyncness,
457 moveness,
458 group,
459 arms,
460 is_shorthand,
461 } = parse_macro_input!(input as Handler);
462
463 let generics = match group.path.segments.last().unwrap().arguments {
464 PathArguments::None => None,
465 PathArguments::AngleBracketed(ref v) => Some(v),
466 PathArguments::Parenthesized(_) => panic!("stop that"),
467 };
468
469 let mut matcher = quote! { match effs {} };
470 for arm in arms {
471 let HandlerArm { pat, mut body } = arm;
472 let eff_ty = match &pat {
473 Pat::Ident(ident) => quote!(#ident),
475 Pat::Path(path) => quote!(#path),
476 Pat::TupleStruct(PatTupleStruct { path, .. }) => quote!(#path),
477 p => panic!("invalid pattern in handler: {p:?}"),
478 };
479 let new_pat = if generics.is_some() {
480 match &pat {
481 Pat::Ident(ident) => quote!(#ident(::core::marker::PhantomData)),
482 Pat::Path(path) => quote!(#path(::core::marker::PhantomData)),
483 Pat::TupleStruct(p) => {
484 let mut p = p.clone();
485 p.pat.elems.push(parse_quote!(::core::marker::PhantomData));
486 quote!(#p)
487 },
488 p => panic!("invalid pattern in handler: {p:?}"),
489 }
490 } else {
491 quote!(#pat)
492 };
493 if let Expr::Break(_) | Expr::Return(_) = body {
494 body = parse_quote!({ #body });
495 }
496 syn::visit_mut::visit_expr_mut(
497 &mut FixControlFlow {
498 eff_ty: &eff_ty,
499 is_shorthand,
500 },
501 &mut body,
502 );
503 if is_shorthand {
504 matcher = quote! {
505 {
506 let #new_pat = effs;
507 #body
508 }
509 };
510 } else {
511 matcher = quote! {
512 match effs.uninject() {
513 Ok(#new_pat) => {
514 let __effing_inj = #body;
515 #[allow(unreachable_code)]
516 ::effing_mad::frunk::Coproduct::inject(
517 ::effing_mad::injection::Tagged::<_, #eff_ty #generics>::new(
518 __effing_inj
519 )
520 )
521 },
522 Err(effs) => #matcher,
523 }
524 };
525 }
526 }
527 let effs_ty = if is_shorthand {
528 quote!(#group)
529 } else {
530 quote!(<#group as ::effing_mad::EffectGroup>::Effects)
531 };
532 quote! {
533 #moveness |effs: #effs_ty| #asyncness {
534 let __effing_inj = #matcher;
535 #[allow(unreachable_code)]
538 ::core::ops::ControlFlow::<_, _>::Continue(__effing_inj)
539 }
540 }
541 .into()
542}