vorant_gen 0.2.0

A proc-macro library for generating state machines
Documentation
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::spanned::Spanned;

use crate::{
    machine_fn::{Ctx, PointBody, PointDef, Scope, Stmts},
    save::PointSave,
};

pub struct IfPoint {
    save: PointSave,
    cond: Box<syn::Expr>,
    first_point: Box<PointBody>,
    next_points: std::vec::IntoIter<syn::Stmt>,
}

impl IfPoint {
    pub fn can_from(value: &syn::ExprIf) -> bool {
        PointSave::can_from(&value.attrs)
    }
}

impl TryFrom<syn::ExprIf> for IfPoint {
    type Error = syn::Error;

    fn try_from(value: syn::ExprIf) -> Result<Self, Self::Error> {
        let save = (value.span(), value.attrs).try_into()?;

        let mut iter_stmts = value.then_branch.stmts.into_iter();
        let body = PointBody::parse(&mut iter_stmts)?;

        if let Some(branch) = value.else_branch {
            return Err(syn::Error::new(
                branch.0.span(),
                "else branches are not supported",
            ));
        }

        Ok(IfPoint {
            save,
            cond: value.cond,
            first_point: Box::new(body),
            next_points: iter_stmts,
        })
    }
}

impl IfPoint {
    pub fn expand_call(&mut self, ctx: &Ctx) -> TokenStream {
        let Self {
            first_point,
            save,
            cond,
            ..
        } = self;

        let ident = format_ident!("If{}", ctx.if_idx);
        let first_stmts =
            first_point
                .stmts
                .expand(ctx, &Scope::If(IfScope { save }), first_point.end.is_some());

        let constructor = self.save.expand_constructor();
        let first_point = first_point.end.as_mut().map(|x| x.expand_construct(ctx));

        quote! {
            if #cond {
                #first_stmts
                #first_point
            }

            return #ident { #constructor }.offer_after();
        }
    }

    pub fn expand(
        self,
        ctx: &mut Ctx,
        mut after: Stmts,
        next: Option<&mut PointDef>,
        scope: &Scope<'_>,
    ) -> Result<TokenStream, syn::Error> {
        let Self {
            save,
            first_point,
            next_points,
            ..
        } = self;

        let machine_ident = ctx.machine_ident.clone();

        let ident = format_ident!("If{}", ctx.if_idx);
        let def = save.expand_def();

        let destructor = save.expand_destructure();
        let inner_points = if let Some(first_point) = first_point.end {
            Some(crate::machine_fn::expand_all(
                ctx,
                first_point,
                next_points,
                &Scope::If(IfScope { save: &save }),
            )?)
        } else {
            None
        };

        let outer_next = next.map(|x| x.expand_construct(ctx));
        let after = after.expand(ctx, scope, outer_next.is_some());

        let out = quote! {
            pub struct #ident {
                #def
            }

            #inner_points

            impl #ident {
                pub fn offer_after(self) -> ::vorant::Step<#machine_ident> {
                    let Self { #destructor } = self;

                    #after
                    #outer_next
                }
            }
        };

        ctx.if_idx += 1;
        Ok(out)
    }
}

pub struct IfScope<'s> {
    save: &'s PointSave,
}

impl<'s> IfScope<'s> {
    pub fn expand_end(&self, ctx: &Ctx, _expr: Option<&syn::Expr>) -> TokenStream {
        let ident = format_ident!("If{}", ctx.if_idx);
        let constructor = self.save.expand_constructor();

        quote! {
            return #ident { #constructor }.offer_after();
        }
    }
}