regexpr 0.4.1

Regular expresions
Documentation
use alloc::boxed::Box;

use crate::matcher::{LookAhead, LookAheadKind, RegexCtx};

#[derive(Clone, Debug)]
pub enum MatchCase {
    Start,
    End,
    Char(char),
    List(Box<[MatchCase]>),
    Group {
        case: Box<MatchCase>,
        capture_id: usize,
    },
    Or(Box<[MatchCase]>),
    AnyOne,
    Opt(Box<MatchCase>),
    OneOrMore {
        case: Box<MatchCase>,
        lazy: bool,
    },
    Star {
        case: Box<MatchCase>,
        lazy: bool,
    },
    Whitespace,
    NotWhitespace,
    Decimal,
    NotDecimal,
    Word,
    NotWord,
    Capture(usize),
    Between(char, char),
    CharMatch(Box<[MatchCase]>),
    RangeLoop {
        case: Box<MatchCase>,
        min: Option<usize>,
        max: Option<usize>,
    },
    Not(Box<MatchCase>),
}

impl MatchCase {
    fn lazy_star_loop<'a>(
        &'a self,
        ctx: &mut RegexCtx<'_, 'a>,
        lookahead: &LookAhead<'_, 'a>,
    ) -> bool {
        loop {
            if ctx.borrow_shallow(|ctx| (lookahead.match_all(ctx), false)) {
                return true;
            }
            let is_match = ctx.borrow_shallow(|ctx| {
                let ret = self.matches(ctx, lookahead);
                (ret, ret)
            });
            if !is_match {
                return true;
            }
        }
    }
    fn greedy_star_loop<'a>(
        &'a self,
        ctx: &mut RegexCtx<'_, 'a>,
        lookahead: &LookAhead<'_, 'a>,
    ) -> bool {
        let mut last_next_match = None;

        loop {
            if ctx.borrow_shallow(|ctx| (lookahead.match_all(ctx), false)) {
                last_next_match = Some(ctx.clone());
            }

            let is_match = ctx.borrow_shallow(|ctx| {
                let ret = self.matches(ctx, lookahead);
                (ret, ret)
            });
            if !is_match {
                if let Some(it) = last_next_match {
                    *ctx = it;
                }
                return true;
            }
        }
    }
    fn star_loop<'a>(
        &'a self,
        ctx: &mut RegexCtx<'_, 'a>,
        lazy: bool,
        lookahead: &LookAhead<'_, 'a>,
    ) -> bool {
        if lazy {
            self.lazy_star_loop(ctx, lookahead)
        } else {
            self.greedy_star_loop(ctx, lookahead)
        }
    }
    #[allow(clippy::too_many_lines)]
    pub(crate) fn matches<'a>(
        &'a self,
        ctx: &mut RegexCtx<'_, 'a>,
        lookahead: &LookAhead<'_, 'a>,
    ) -> bool {
        macro_rules! next {
            () => {{
                let Some(ch) = ctx.next_char() else {
                    return false;
                };
                ch
            }};
        }

        match self {
            MatchCase::Char(expected) => next!() == *expected,
            MatchCase::Whitespace => next!().is_whitespace(),
            MatchCase::NotWhitespace => !next!().is_whitespace(),
            MatchCase::Decimal => next!().is_digit(10),
            MatchCase::Word => {
                let c = next!();
                c.is_alphanumeric() || c == '_'
            }
            MatchCase::NotWord => {
                let c = next!();
                !c.is_alphanumeric() && c != '_'
            }
            MatchCase::NotDecimal => !next!().is_digit(10),
            MatchCase::Group { case, capture_id } => {
                let curr = ctx.char_iter();
                ctx.start_capture(*capture_id, curr);
                let ret = case.matches(ctx, lookahead);
                ctx.end_capture(&ctx.char_iter());
                ret
            }
            MatchCase::List(cases) => {
                for i in 0..cases.len() {
                    let rem = cases.get(i + 1..).unwrap_or(&[]);
                    let look = LookAhead::new(LookAheadKind::List(rem), Some(lookahead));
                    let is_match = cases[i].matches(ctx, &look);
                    if !is_match {
                        return false;
                    }
                }
                true
            }
            MatchCase::Or(l) => l.iter().any(|rule| {
                ctx.borrow_shallow(|newit| {
                    let ret = rule.matches(newit, lookahead);
                    (ret, ret)
                })
            }),
            MatchCase::Opt(c) => {
                ctx.borrow_shallow(|newit| {
                    if c.matches(newit, lookahead)
                        && lookahead.match_all(&mut newit.shallow_clone())
                    {
                        ((), true)
                    } else {
                        ((), false)
                    }
                });
                true
            }
            MatchCase::AnyOne => ctx.next_char().is_some(),
            MatchCase::OneOrMore { case, lazy } => {
                if !case.matches(ctx, lookahead) {
                    return false;
                }

                case.star_loop(ctx, *lazy, lookahead)
            }
            MatchCase::Star { case, lazy } => case.star_loop(ctx, *lazy, lookahead),
            MatchCase::Start => ctx.char_offset() == 0,
            MatchCase::End => ctx.next_char().is_none(),
            MatchCase::Between(start, end) => {
                let c = next!();
                let (start, end) = if ctx.conf().case_sensitive {
                    (*start, *end)
                } else {
                    (
                        start.to_lowercase().next().unwrap_or(*start),
                        end.to_lowercase().next().unwrap_or(*end),
                    )
                };
                c >= start && c <= end
            }
            MatchCase::Not(match_case) => match ctx.peek_char() {
                Some(_) => !match_case.matches(ctx, lookahead),
                None => false,
            },
            MatchCase::CharMatch(cases) => {
                let ret = cases
                    .iter()
                    .any(|case| case.matches(&mut ctx.shallow_clone(), lookahead));
                ctx.next_char();
                ret
            }
            MatchCase::RangeLoop { case, min, max } => {
                let mut n = 0;

                if let Some(min) = min {
                    for i in 0..*min {
                        let look = LookAhead::new(
                            LookAheadKind::Repeat {
                                m: case,
                                num: *min - i - 1,
                            },
                            Some(lookahead),
                        );
                        if !case.matches(ctx, &look) {
                            return false;
                        }
                        n += 1;
                    }
                }

                loop {
                    if max.is_some_and(|max| n >= max) {
                        break;
                    }

                    if !ctx.borrow_shallow(|it| {
                        let ret = case.matches(it, lookahead);
                        (ret, ret)
                    }) {
                        break;
                    }

                    n += 1;
                }

                true
            }
            MatchCase::Capture(n) => {
                let case_sensitive = ctx.conf().case_sensitive;
                ctx.get_capture(*n)
                    .chars()
                    .map(|c| {
                        if case_sensitive {
                            c
                        } else {
                            c.to_lowercase().next().unwrap_or(c)
                        }
                    })
                    .all(|c| next!() == c)
            }
        }
    }
}