floop 0.1.2

A more convenient and less error prone replacement for loop `{ select! { .. }}`
Documentation
#![expect(clippy::result_large_err, reason="unsynn!{..} causes it so i have no idea how i would fix it.")]

pub(crate) mod rust_analyzer_friendly;

use unsynn::*;
use std::{fmt::Display, result::Result};

unsynn!{
    pub(crate) keyword KBefore = "before";
    pub(crate) keyword KAfter = "after";
    keyword KIn = "in";
    keyword KBiased = "biased";
    keyword KUnbiased = "unbiased";
    keyword KFootgun= "footgun";
    keyword KIf = "if";
    
    /// `Tokens,`, `Tokens EOF`, or `{Tokens}`.
    pub(crate) enum Block {
        Braced {
            tokens: BraceGroupContaining<Delimited<Vec<TokenTree>, Semicolon>>,
            // commas are optional for braced blocks,
            // expressions starting with blocks like `_ in foo => { ... } + 4` don't parse,
            // but that's also the case for rust `match` statements.
            _seperator: Option<Comma>,
        },
        Expr {
            _except: Except<Either<Comma, EndOfStream>>,
            tokens: LazyVec<TokenTree, Either<Comma, EndOfStream>>,
        },
    }

    /// `Pattern +> Block`
    pub(crate) struct Arm<P: Parser> {
        pattern: P,
        _arrow: FatArrow,
        block: Block,
    }

    /// `Pattern in footgun? Expr =>`
    pub(crate) struct FuturePattern {
        // ensure there's at least 1 token.
        _except_1: Except<KIn>,
        pattern: LazyVec<TokenTree, KIn>,
        condition: Option<Cons<KIf, Except<Comma>, LazyVec<TokenTree, Comma>>>,
        // adding `footgun` infront of `expr` bypasses the footgun detector.
        footgun: Option<KFootgun>,
        _except_2: Except<FatArrow>,
        expr: LazyVecUntil<TokenTree, FatArrow>,
    }

    pub(crate) enum AnyArm {
        Before(Arm<KBefore>),
        After(Arm<KAfter>),
        Future(Arm<FuturePattern>),
    }

    pub(crate) struct UnvalidatedAst {
        biasedness: Either<KBiased, KUnbiased>,
        arms: Vec<AnyArm>,
    }
}

impl<P: Parse> Arm<P> {
    pub(crate) fn pattern(&self) -> &P {
        &self.pattern
    }

    pub(crate) fn block(&self) -> TokenStream {
        match &self.block {
            Block::Braced { tokens, _seperator } => tokens.content.to_token_stream(),
            Block::Expr { _except, tokens } => tokens.vec.to_token_stream(),
        }
    }
}

impl FuturePattern {
    pub(crate) fn pattern(&self) -> &[TokenTree] {
        &self.pattern.vec
    }

    pub(crate) fn expr(&self) -> &[TokenTree] {
        &self.expr.vec
    }

    pub(crate) fn footgun_allowed(&self) -> bool {
        self.footgun.is_some()
    }

    pub(crate) fn condition(&self) -> Option<&[TokenTree]> {
        self.condition.as_ref().map(|condition| condition.third.vec.as_slice())
    }
}

#[derive(Debug)]
pub(crate) enum ValidationError {
    TooManyBeforeAfter {
        before_count: usize,
        after_count: usize,
    }
}

impl Display for ValidationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::TooManyBeforeAfter { before_count, after_count } => {
                write!(f, "expected 0..=1 before and after, found {before_count} befores and {after_count} afters")
            }
        }
    }
}

impl UnvalidatedAst {
    pub(crate) fn validated(self) -> Result<Ast, ValidationError> {
        let mut before = None;
        let mut before_count = 0;
        let mut after = None;
        let mut after_count = 0;
        let mut arms = Vec::new();

        for arm in self.arms {
            match arm {
                AnyArm::Before(arm) => {
                    before = Some(arm);
                    before_count += 1;
                }
                AnyArm::After(arm) => {
                    after = Some(arm);
                    after_count += 1;
                }
                AnyArm::Future(arm) => {
                    arms.push(arm);
                }
            }
        };

        if before_count > 1 || after_count > 1 {
            return Err(ValidationError::TooManyBeforeAfter {
                before_count,
                after_count,
            });
        }
        Ok(Ast {
            biased: matches!(self.biasedness, Either::First(_)),
            before,
            after,
            arms,
        })
    }
}

pub(crate) struct Ast {
    pub(crate) biased: bool,
    pub(crate) before: Option<Arm<KBefore>>,
    pub(crate) after: Option<Arm<KAfter>>,
    pub(crate) arms: Vec<Arm<FuturePattern>>,
}

#[cfg(test)]
mod test {
    use unsynn::{Parse, ToTokens};

    use crate::parser::UnvalidatedAst;
    fn test_parse(tokens: &str) {
        match UnvalidatedAst::parse_all(&mut tokens.to_token_iter()) {
            Ok(ast) => match ast.validated() {
                Ok(_) => {}
                Err(err) => panic!("{err}"),
            }
            Err(err) => panic!("{err}"),
        }
    }

    #[test]
    fn parse_empty() {
        test_parse("unbiased");
    }

    #[test]
    fn parse_arm() {
        test_parse(r#"
            biased
            bar in baz => {
                todo!();
            },
            foo in Timer::new() => println!("bar")
        "#);
    }

    #[test]
    fn parse_all() {
        // yes, this was written by hand.
        test_parse("
            unbiased
            before => eprintln!(await),
            (foo1, Ok(foo2)) in for In(foo) in { break 42 } => match foo {
                Some(x) => x,
                None => panic!(),
            }
            after => (),
            bar in Timer::new(30s) => {
                x;
                y;
                for i in 0..10 {
                    foobar();
                }
            },
        ");
    }
}