forr 0.2.3

a compile time for loop for code generation
Documentation
use manyhow::Result;
use proc_macro2::{TokenStream, TokenTree};
use proc_macro_utils::{TokenStream2Ext, TokenTreePunct};
use quote::ToTokens;

use crate::Parser;

struct Expression;

pub fn step_colon(input: &mut Parser) -> Result<()> {
    if input
        .next_if_each((TokenTree::is_dollar, TokenTree::is_colon))
        .or_else(|| input.next_if_each((TokenTree::is_pound, TokenTree::is_colon)))
        .is_none()
    {
        bail_optional_span!(input, "expected `$:` or `#:`");
    } else {
        Ok(())
    }
}

pub fn peek_colon(input: &mut Parser) -> bool {
    input
        .peek_if_each((TokenTree::is_dollar, TokenTree::is_colon))
        .or_else(|| input.peek_if_each((TokenTree::is_pound, TokenTree::is_colon)))
        .is_some()
}

pub fn iff(input: TokenStream) -> Result {
    let mut input = input.parser();

    let expression = Expression::parse(true, &mut input)?;

    step_colon(&mut input)?;

    Ok(expression.then_some(input).into_token_stream())
}

impl Expression {
    fn parse(llhs: bool, input: &mut Parser) -> Result<bool> {
        let lhs = Expression::parse_lazy(input)?;
        Ok(if input.next_tt_or_or().is_some() {
            let rhs = Expression::parse(true, input)?;
            llhs && lhs || rhs
        } else if input.next_tt_and_and().is_some() {
            let rhs = Expression::parse(lhs, input)?;
            llhs && rhs
        } else if input.is_empty() || peek_colon(input) {
            llhs && lhs
        } else {
            bail_optional_span!(input, "expected `&&` or `||`");
        })
    }

    fn parse_all(input: TokenStream) -> Result<bool> {
        let mut input = input.parser();
        let ret = Expression::parse(true, &mut input)?;
        if input.is_empty() {
            Ok(ret)
        } else {
            bail_optional_span!(input, "expected `&&` or `||`");
        }
    }

    fn parse_lazy(input: &mut Parser) -> Result<bool> {
        Ok(if let Some(parenthesized) = input.next_parenthesized() {
            Expression::parse_all(parenthesized.stream())?
        } else if input.next_tt_not().is_some() {
            !Expression::parse_lazy(input)?
        } else if input.next_keyword("true").is_some() {
            true
        } else if input.next_keyword("false").is_some() {
            false
        } else if input.next_keyword("empty").is_some() {
            let Some(parenthesized) = input.next_parenthesized() else {
                bail_optional_span!(input, "expected `(...)`");
            };

            parenthesized.stream().is_empty()
        } else if input.next_keyword("equals").is_some() {
            let Some(lhs) = input.next_parenthesized() else {
                bail_optional_span!(input, "expected `(...)`");
            };
            let Some(rhs) = input.next_parenthesized() else {
                bail_optional_span!(input, "expected `(...)`");
            };

            lhs.stream().to_string() == rhs.stream().to_string()
        } else if input.next_keyword("equals_any").is_some() {
            let Some(lhs) = input.next_parenthesized() else {
                bail_optional_span!(input, "expected `(...)`");
            };
            let Some(rhs) = input.next_bracketed() else {
                bail_optional_span!(input, "expected `[...]`");
            };

            let mut input = rhs.stream().parser();
            while let Some(rhs) = input.next_parenthesized() {
                if lhs.stream().to_string() == rhs.stream().to_string() {
                    return Ok(true);
                }
                _ = input.next_tt_comma();
            }
            _ = input.next_tt_comma();
            if !input.is_empty() {
                bail_optional_span!(input, "expected `(...)`");
            }
            false
        } else {
            bail_optional_span!(
                input,
                "expected `(...)`, `!...`, `true`, `false`, or `empty`"
            );
        })
    }
}

#[test]
fn expression() {
    use quote::quote;
    assert!(Expression::parse_all(quote!(true && true || false)).unwrap());
    assert!(Expression::parse_all(quote!(true && (true || false))).unwrap());
    assert!(!Expression::parse_all(quote!(false || true && false)).unwrap());
    assert!(Expression::parse_all(quote!(false || empty() && true)).unwrap());
    assert!(!Expression::parse_all(quote!(false || empty(()) && true)).unwrap());
    assert!(Expression::parse_all(quote!(equals_any(hello)[(hello), (hi)])).unwrap());
    assert!(Expression::parse_all(quote!(equals_any(hello)[(hello)(hi)])).unwrap());
    assert!(Expression::parse_all(quote!(equals_any(hello)[(hello)(hi),])).unwrap());
    assert!(!Expression::parse_all(quote!(equals_any(hello)[(hi),])).unwrap());
}