substruct 0.1.3

A proc-macro to create subsets of structs
Documentation
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;

pub(crate) enum Expr {
    Ident(syn::Ident),
    Not(NotExpr),
    All(AllExpr),
    Any(AnyExpr),
}

impl Expr {
    pub fn evaluate(&self, ident: &syn::Ident) -> bool {
        match self {
            Self::Ident(lit) => ident == lit,
            Self::Not(e) => e.evaluate(ident),
            Self::Any(e) => e.evaluate(ident),
            Self::All(e) => e.evaluate(ident),
        }
    }
}

impl Parse for Expr {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        if !input.peek2(syn::token::Paren) {
            return Ok(Self::Ident(input.parse()?));
        }

        let ident: syn::Ident = input.fork().parse()?;

        match () {
            _ if ident == "not" => input.parse().map(Self::Not),
            _ if ident == "any" => input.parse().map(Self::Any),
            _ if ident == "all" => input.parse().map(Self::All),
            _ => Err(syn::Error::new(
                ident.span(),
                format!("unexpected operator `{ident}`, expected `not`, `any`, or `all`"),
            )),
        }
    }
}

impl ToTokens for Expr {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::Ident(ident) => ident.to_tokens(tokens),
            Self::Not(e) => e.to_tokens(tokens),
            Self::All(e) => e.to_tokens(tokens),
            Self::Any(e) => e.to_tokens(tokens),
        }
    }
}

pub(crate) struct NotExpr {
    pub ident: syn::Ident,
    pub paren: syn::token::Paren,
    pub expr: Box<Expr>,
}

impl NotExpr {
    pub fn evaluate(&self, ident: &syn::Ident) -> bool {
        !self.expr.evaluate(ident)
    }
}

impl Parse for NotExpr {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;

        Ok(Self {
            ident: input.parse()?,
            paren: syn::parenthesized!(content in input),
            expr: content.parse()?,
        })
    }
}

impl ToTokens for NotExpr {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.ident.to_tokens(tokens);
        self.paren
            .surround(tokens, |tokens| self.expr.to_tokens(tokens));
    }
}

pub(crate) struct AnyExpr {
    pub ident: syn::Ident,
    pub paren: syn::token::Paren,
    pub exprs: Punctuated<Expr, syn::Token![,]>,
}

impl AnyExpr {
    pub fn evaluate(&self, ident: &syn::Ident) -> bool {
        self.exprs.iter().any(|e| e.evaluate(ident))
    }
}

impl Parse for AnyExpr {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;

        let ident: syn::Ident = input.parse()?;
        if ident != "any" {
            return Err(syn::Error::new(
                ident.span(),
                format_args!("expected `any`, got `{ident}` instead"),
            ));
        }

        Ok(Self {
            ident,
            paren: syn::parenthesized!(content in input),
            exprs: Punctuated::parse_terminated(&content)?,
        })
    }
}

impl ToTokens for AnyExpr {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.ident.to_tokens(tokens);
        self.paren
            .surround(tokens, |tokens| self.exprs.to_tokens(tokens));
    }
}

pub(crate) struct AllExpr {
    pub ident: syn::Ident,
    pub paren: syn::token::Paren,
    pub exprs: Punctuated<Expr, syn::Token![,]>,
}

impl AllExpr {
    pub fn evaluate(&self, ident: &syn::Ident) -> bool {
        self.exprs.iter().all(|e| e.evaluate(ident))
    }
}

impl Parse for AllExpr {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;

        let ident: syn::Ident = input.parse()?;
        if ident != "all" {
            return Err(syn::Error::new(
                ident.span(),
                format_args!("expected `all`, got `{ident}` instead"),
            ));
        }

        Ok(Self {
            ident,
            paren: syn::parenthesized!(content in input),
            exprs: Punctuated::parse_terminated(&content)?,
        })
    }
}

impl ToTokens for AllExpr {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.ident.to_tokens(tokens);
        self.paren
            .surround(tokens, |tokens| self.exprs.to_tokens(tokens));
    }
}