repetitive 0.2.0

Macro for generating repetitive code
Documentation
use proc_macro2::Group;
use syn::{Token, parse::ParseStream, token::Bracket};

use super::*;

#[derive(Debug, Clone)]
pub enum Pattern {
    Empty,
    Name(Name),
    Literal(Value),
    List(Vec<Pattern>),
}

pub enum PatternMatches {
    Matches,
    Mismatched(Error),
    Unknown(UnknownGuard),
}

impl Pattern {
    pub fn peek(input: ParseStream) -> bool {
        input.peek(Bracket) || Name::peek(input) || Expr::peek(input) || input.peek(Token![_])
    }

    pub fn matches(&self, value: &Value, ctx: &mut Context) -> PatternMatches {
        match self {
            Self::Empty => PatternMatches::Matches,
            Self::Name(_) => PatternMatches::Matches,

            Self::Literal(lit) => {
                let is_same_kind = match (&lit.kind, &value.kind) {
                    (ValueKind::Bool(_), ValueKind::Bool(_)) => true,
                    (ValueKind::Int(_), ValueKind::Int(_)) => true,
                    (ValueKind::Float(_), ValueKind::Float(_)) => true,
                    (ValueKind::String(_), ValueKind::String(_)) => true,
                    (ValueKind::Char(_), ValueKind::Char(_)) => true,
                    (ValueKind::Ident(_), ValueKind::Ident(_)) => true,
                    _ => false,
                };

                if !is_same_kind {
                    return PatternMatches::Mismatched(Error::PatternKindMismatch {
                        span: value.span,
                        expected: lit.kind.kind_str(),
                        found: value.kind.kind_str(),
                    });
                }

                let eq = match Op::Eq(value.span).compute(&[lit.clone(), value.clone()], ctx) {
                    Ok(val) => val,
                    Err(err) => {
                        return PatternMatches::Unknown(ctx.push_error(err).unknown_guard());
                    }
                };

                let ValueKind::Bool(eq) = eq.kind else {
                    if let ValueKind::Unknown(guard) = &eq.kind {
                        return PatternMatches::Unknown(*guard);
                    }

                    unreachable!("pattern literal eq must be a bool");
                };

                match eq {
                    true => PatternMatches::Matches,
                    false => {
                        PatternMatches::Mismatched(Error::PatternValueMismatch { span: value.span })
                    }
                }
            }

            Self::List(pat) => {
                let ValueKind::List(value_list) = &value.kind else {
                    return PatternMatches::Mismatched(Error::PatternKindMismatch {
                        span: value.span,
                        expected: "list",
                        found: value.kind.kind_str(),
                    });
                };

                if pat.len() != value_list.len() {
                    return PatternMatches::Mismatched(Error::PatternListLengthMismatch {
                        span: value.span,
                        expected: pat.len(),
                        found: value_list.len(),
                    });
                }

                for (pat_item, value_item) in pat.iter().zip(value_list) {
                    match pat_item.matches(value_item, ctx) {
                        PatternMatches::Matches => {}
                        PatternMatches::Mismatched(err) => return PatternMatches::Mismatched(err),
                        PatternMatches::Unknown(guard) => return PatternMatches::Unknown(guard),
                    }
                }

                PatternMatches::Matches
            }
        }
    }

    pub fn queue_insert(&self, value_expr: Value, namespace: &mut Namespace, ctx: &mut Context) {
        match self.matches(&value_expr, ctx) {
            PatternMatches::Matches => {}
            PatternMatches::Mismatched(err) => {
                ctx.push_error(err);
                return;
            }
            PatternMatches::Unknown(_) => return,
        }

        match self {
            Pattern::Name(name) => {
                namespace.queue_insert(*name, value_expr, ctx);
            }

            Pattern::Empty => {}
            Pattern::Literal(_) => {}

            Pattern::List(pat) => {
                let ValueKind::List(value) = value_expr.kind else {
                    unreachable!("pattern list must be a list");
                };

                for (pat, value) in pat.iter().zip(value) {
                    pat.queue_insert(value, namespace, ctx);
                }
            }
        }
    }
}

impl ContextParse for Pattern {
    fn ctx_parse(input: ParseStream, ctx: &mut Context) -> Result<Self, Error>
    where
        Self: Sized,
    {
        if input.peek(Bracket) {
            let group = Group::ctx_parse(input, ctx)?;

            return Ok(Self::List(
                ctx_parse_punctuated.ctx_parse2(group.stream(), ctx)?,
            ));
        }

        if Name::peek(input) {
            return Ok(Self::Name(Name::ctx_parse(input, ctx)?));
        }

        if let Some(lit) = Value::ctx_parse_option_lit(input, ctx)? {
            return Ok(Self::Literal(lit));
        }

        if let Some(_) = <Option<Token![_]>>::ctx_parse(input, ctx)? {
            return Ok(Self::Empty);
        }

        Err(Error::ParseError(input.error("expected pattern")))
    }
}

fn ctx_parse_punctuated<T: ContextParse>(
    input: ParseStream,
    ctx: &mut Context,
) -> Result<Vec<T>, Error> {
    let mut items = Vec::new();

    while !input.is_empty() {
        let item = T::ctx_parse(input, ctx)?;
        items.push(item);

        if input.is_empty() {
            break;
        }

        <Token![,]>::ctx_parse(input, ctx)?;
    }

    Ok(items)
}