rexact-syntax 0.2.0

Regex parsers for ECMAScript, Python, Go, Rust, PCRE2 and Java dialects, with an error catalogue
Documentation
//! Java-specific syntax checks run before the shared PCRE-style parser.
//!
//! `java.util.regex` shares most syntax with PCRE2 but rejects, or reads
//! differently, a few constructs the PCRE2 parser accepts. Each is either
//! rejected here with a catalogue code (never read as something similar,
//! RULE-05) or is a documented difference handled in lowering.

use crate::error::ErrorCode;
use crate::parser::PErr;

/// Validate `chars` as Java pattern text.
pub(crate) fn check(chars: &[char]) -> Result<(), PErr> {
    let n = chars.len();
    let mut i = 0;
    let mut in_class = 0usize;
    while i < n {
        let c = chars[i];
        match c {
            '\\' => {
                let next = chars.get(i + 1).copied();
                match next {
                    // \Q ... \E quotes everything in between.
                    Some('Q') => {
                        i += 2;
                        while i < n && !(chars[i] == '\\' && chars.get(i + 1) == Some(&'E')) {
                            i += 1;
                        }
                        i += 2;
                        continue;
                    }
                    Some('b') if in_class > 0 => {
                        return Err(PErr::new(
                            ErrorCode::P004,
                            i,
                            "\\b is not allowed inside a Java character class",
                        ))
                    }
                    Some('b') if chars.get(i + 2) == Some(&'{') => {
                        return Err(PErr::new(
                            ErrorCode::U007,
                            i,
                            "\\b{...} boundaries are not supported",
                        ))
                    }
                    Some('0') if !matches!(chars.get(i + 2), Some('0'..='7')) => {
                        return Err(PErr::new(
                            ErrorCode::P004,
                            i,
                            "\\0 must be followed by an octal digit in Java",
                        ))
                    }
                    _ => {}
                }
                i += 2;
                continue;
            }
            '[' => {
                if in_class > 0 {
                    return Err(PErr::new(
                        ErrorCode::U007,
                        i,
                        "nested character classes (union) are not yet supported for Java",
                    ));
                }
                in_class = 1;
                i += 1;
                // A leading ^ and a leading ] are part of the class opener.
                if chars.get(i) == Some(&'^') {
                    i += 1;
                }
                continue;
            }
            ']' if in_class > 0 => in_class = 0,
            '&' if in_class > 0 && chars.get(i + 1) == Some(&'&') => {
                return Err(PErr::new(
                    ErrorCode::U007,
                    i,
                    "class intersection && is not yet supported for Java",
                ))
            }
            '(' if in_class == 0
                && chars.get(i + 1) == Some(&'?')
                && chars.get(i + 2) == Some(&'P') =>
            {
                return Err(PErr::new(
                    ErrorCode::P001,
                    i,
                    "Java has no (?P...) groups; use (?<name>...)",
                ))
            }
            '{' if in_class == 0 && !valid_brace(chars, i) => {
                return Err(PErr::new(
                    ErrorCode::P005,
                    i,
                    "illegal repetition: Java requires {n}, {n,} or {n,m}",
                ))
            }
            _ => {}
        }
        i += 1;
    }
    Ok(())
}

fn valid_brace(chars: &[char], i: usize) -> bool {
    let mut j = i + 1;
    let digits = |j: &mut usize| {
        let s = *j;
        while chars.get(*j).is_some_and(char::is_ascii_digit) {
            *j += 1;
        }
        *j > s
    };
    if !digits(&mut j) {
        return false;
    }
    if chars.get(j) == Some(&',') {
        j += 1;
        digits(&mut j);
    }
    chars.get(j) == Some(&'}')
}