Skip to main content

guard_core_engine/redos/
ast.rs

1//! Python `re`-flavor pattern parser.
2//!
3//! Port of the `re._parser` op tree the reference's synthesis machinery
4//! walks (`LITERAL`, `NOT_LITERAL`, `IN`, `ANY`, `CATEGORY`, the repeat
5//! ops, `SUBPATTERN`, `ATOMIC_GROUP`, `BRANCH`, `ASSERT`, `ASSERT_NOT`,
6//! `AT`, `GROUPREF`, `GROUPREF_EXISTS`). Escape, class, group, flag, and
7//! repeat rules mirror CPython's `_parser.py` (Python 3.14, which adds
8//! `\z`); patterns the reference rejects must fail to parse here too.
9//!
10//! Known divergence: `\N{...}` named escapes and the `x` (verbose) flag
11//! are rejected instead of honored; neither appears in any served or
12//! residual pattern.
13
14use std::collections::HashMap;
15
16/// Engine flags the safety chain threads through every layer.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
18pub struct Flags {
19    pub ignorecase: bool,
20    pub multiline: bool,
21    pub dotall: bool,
22    pub ascii: bool,
23}
24
25impl Flags {
26    /// The reference compiler's defaults: IGNORECASE | MULTILINE.
27    #[must_use]
28    pub fn ignorecase_multiline() -> Self {
29        Self {
30            ignorecase: true,
31            multiline: true,
32            dotall: false,
33            ascii: false,
34        }
35    }
36
37    pub(crate) fn apply_delta(&mut self, add: &Flags, del: &Flags) {
38        self.ignorecase = if del.ignorecase {
39            false
40        } else {
41            self.ignorecase || add.ignorecase
42        };
43        self.multiline = if del.multiline {
44            false
45        } else {
46            self.multiline || add.multiline
47        };
48        self.dotall = if del.dotall {
49            false
50        } else {
51            self.dotall || add.dotall
52        };
53        self.ascii = if del.ascii {
54            false
55        } else {
56            self.ascii || add.ascii
57        };
58    }
59}
60
61/// Character-class members.
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub enum ClassItem {
64    Literal(u32),
65    Range(u32, u32),
66    Category(Category),
67    Negate,
68}
69
70/// The six category escapes the reference maps.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
72pub enum Category {
73    Digit,
74    NotDigit,
75    Space,
76    NotSpace,
77    Word,
78    NotWord,
79}
80
81impl Category {
82    fn from_escape(escape: char) -> Option<Self> {
83        Some(match escape {
84            'd' => Self::Digit,
85            'D' => Self::NotDigit,
86            's' => Self::Space,
87            'S' => Self::NotSpace,
88            'w' => Self::Word,
89            'W' => Self::NotWord,
90            _ => return None,
91        })
92    }
93}
94
95/// Zero-width anchor kinds.
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum At {
98    Beginning,
99    BeginningLine,
100    End,
101    EndLine,
102    BeginningString,
103    EndString,
104    Boundary,
105    NonBoundary,
106}
107
108/// Repeat greediness kinds.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum RepeatKind {
111    Greedy,
112    Lazy,
113}
114
115/// Parser op tree, mirroring the reference `re._parser` codes.
116#[derive(Debug, Clone, PartialEq, Eq)]
117pub enum Op {
118    Literal(u32),
119    NotLiteral(u32),
120    Any,
121    In(Vec<ClassItem>),
122    Category(Category),
123    /// Unbounded upper bound is `None` (the reference `MAXREPEAT`).
124    Repeat {
125        kind: RepeatKind,
126        low: u32,
127        high: Option<u32>,
128        body: Vec<Op>,
129    },
130    SubPattern {
131        group: Option<u32>,
132        add: Flags,
133        del: Flags,
134        body: Vec<Op>,
135    },
136    Branch(Vec<Vec<Op>>),
137    Assert {
138        behind: bool,
139        negated: bool,
140        body: Vec<Op>,
141    },
142    /// Negative assertion with an empty body (the reference `FAILURE`).
143    Failure,
144    At(At),
145    GroupRef(u32),
146    GroupRefExists {
147        group: u32,
148        yes: Vec<Op>,
149        no: Option<Vec<Op>>,
150    },
151}
152
153impl Op {
154    /// The reference pairing ops: `LITERAL`, `NOT_LITERAL`, `IN`, `ANY`,
155    /// `CATEGORY`.
156    #[must_use]
157    pub fn is_pairing(&self) -> bool {
158        matches!(
159            self,
160            Self::Literal(_) | Self::NotLiteral(_) | Self::In(_) | Self::Any | Self::Category(_)
161        )
162    }
163}
164
165const MAXREPEAT: u64 = 4_294_967_287;
166
167/// Parse failure; the message mirrors the reference `re.error` text.
168#[derive(Debug, Clone, PartialEq, Eq)]
169pub struct ParseError(pub String);
170
171impl std::fmt::Display for ParseError {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        write!(f, "{}", self.0)
174    }
175}
176
177impl std::error::Error for ParseError {}
178
179struct Parser {
180    chars: Vec<char>,
181    pos: usize,
182    next_group: u32,
183    names: HashMap<String, u32>,
184    open_groups: Vec<u32>,
185    /// Flags mutated by global inline flag groups.
186    flags: Flags,
187}
188
189fn is_digit(c: char) -> bool {
190    c.is_ascii_digit()
191}
192
193fn is_oct_digit(c: char) -> bool {
194    matches!(c, '0'..='7')
195}
196
197fn is_hex_digit(c: char) -> bool {
198    c.is_ascii_hexdigit()
199}
200
201impl Parser {
202    fn new(pattern: &str, flags: Flags) -> Self {
203        Self {
204            chars: pattern.chars().collect(),
205            pos: 0,
206            next_group: 1,
207            names: HashMap::new(),
208            open_groups: Vec::new(),
209            flags,
210        }
211    }
212
213    fn peek(&self) -> Option<char> {
214        self.chars.get(self.pos).copied()
215    }
216
217    fn get(&mut self) -> Option<char> {
218        let c = self.peek();
219        if c.is_some() {
220            self.pos += 1;
221        }
222        c
223    }
224
225    fn match_char(&mut self, c: char) -> bool {
226        if self.peek() == Some(c) {
227            self.pos += 1;
228            true
229        } else {
230            false
231        }
232    }
233
234    fn get_while<F: Fn(char) -> bool>(&mut self, pred: F) -> String {
235        let mut out = String::new();
236        while let Some(c) = self.peek() {
237            if !pred(c) {
238                break;
239            }
240            out.push(c);
241            self.pos += 1;
242        }
243        out
244    }
245
246    fn get_until(&mut self, end: char, what: &str) -> Result<String, ParseError> {
247        let mut out = String::new();
248        while let Some(c) = self.get() {
249            if c == end {
250                return Ok(out);
251            }
252            out.push(c);
253        }
254        Err(ParseError(format!("missing {end}, unterminated {what}")))
255    }
256
257    fn check_group_name(&self, name: &str) -> Result<(), ParseError> {
258        // The reference uses `name.isidentifier()`: nonempty word
259        // characters, not starting with a digit.
260        let invalid = name.is_empty()
261            || name
262                .chars()
263                .any(|c| !(c.is_ascii_alphanumeric() || c == '_'))
264            || name.chars().next().is_some_and(|c| c.is_ascii_digit());
265        if invalid {
266            return Err(ParseError(format!("bad character in group name {name:?}")));
267        }
268        Ok(())
269    }
270
271    fn opengroup(&mut self, name: Option<String>) -> Result<u32, ParseError> {
272        let gid = self.next_group;
273        self.next_group += 1;
274        if let Some(name) = name {
275            self.names.insert(name, gid);
276        }
277        self.open_groups.push(gid);
278        Ok(gid)
279    }
280
281    fn closegroup(&mut self, gid: u32) {
282        self.open_groups.retain(|g| *g != gid);
283    }
284
285    fn checkgroup(&self, gid: u32) -> bool {
286        !self.open_groups.contains(&gid)
287    }
288
289    /// Reference `_parse_flags`: returns `Ok(None)` for global flags.
290    fn parse_flags(&mut self) -> Result<Option<(Flags, Flags)>, ParseError> {
291        let mut add = Flags::default();
292        let mut del = Flags::default();
293        let mut char_opt = self.get();
294        let mut negating = false;
295        loop {
296            let Some(c) = char_opt else {
297                return Err(ParseError("missing -, : or )".into()));
298            };
299            match c {
300                ')' => {
301                    if negating {
302                        // The reference requires a ':' after '-': a negated
303                        // flag list is always scoped.
304                        return Err(ParseError("missing :".into()));
305                    }
306                    // Global flags.
307                    self.flags.apply_delta(&add, &del);
308                    return Ok(None);
309                }
310                ':' => break,
311                '-' => negating = true,
312                'i' | 'm' | 's' | 'a' => {
313                    let flag = Flags {
314                        ignorecase: c == 'i',
315                        multiline: c == 'm',
316                        dotall: c == 's',
317                        ascii: c == 'a',
318                    };
319                    if negating {
320                        del.apply_delta(&flag, &Flags::default());
321                    } else {
322                        add.apply_delta(&flag, &Flags::default());
323                    }
324                }
325                // The reference accepts 'x' (verbose) and 'u' (default for
326                // str patterns); neither changes the op tree.
327                'x' | 'u' => {}
328                'L' => {
329                    return Err(ParseError(
330                        "bad inline flags: cannot use 'L' flag with a str pattern".into(),
331                    ));
332                }
333                _ => {
334                    if c.is_ascii_alphabetic() {
335                        return Err(ParseError("unknown flag".into()));
336                    }
337                    return Err(ParseError("missing -, : or )".into()));
338                }
339            }
340            char_opt = self.get();
341        }
342        if add.ignorecase && del.ignorecase
343            || add.multiline && del.multiline
344            || add.dotall && del.dotall
345            || add.ascii && del.ascii
346        {
347            return Err(ParseError(
348                "bad inline flags: flag turned on and off".into(),
349            ));
350        }
351        Ok(Some((add, del)))
352    }
353
354    /// Reference `_class_escape`.
355    fn class_escape(&mut self) -> Result<ClassItem, ParseError> {
356        let escape = self.get().ok_or_else(|| ParseError("bad escape".into()))?;
357        match escape {
358            'a' => Ok(ClassItem::Literal(0x07)),
359            'b' => Ok(ClassItem::Literal(0x08)),
360            'f' => Ok(ClassItem::Literal(0x0C)),
361            'n' => Ok(ClassItem::Literal(0x0A)),
362            'r' => Ok(ClassItem::Literal(0x0D)),
363            't' => Ok(ClassItem::Literal(0x09)),
364            'v' => Ok(ClassItem::Literal(0x0B)),
365            '\\' => Ok(ClassItem::Literal(0x5C)),
366            'd' | 'D' | 's' | 'S' | 'w' | 'W' => Ok(ClassItem::Category(
367                Category::from_escape(escape).expect("category letters guarded by the match arm"),
368            )),
369            'x' => {
370                let digits = self.get_while(is_hex_digit);
371                if digits.len() != 2 {
372                    return Err(ParseError(format!("incomplete escape \\x{digits}")));
373                }
374                let value = u32::from_str_radix(&digits, 16).unwrap_or(0);
375                Ok(ClassItem::Literal(value))
376            }
377            'u' => {
378                let digits = self.get_while(is_hex_digit);
379                if digits.len() != 4 {
380                    return Err(ParseError(format!("incomplete escape \\u{digits}")));
381                }
382                let value = u32::from_str_radix(&digits, 16).unwrap_or(0);
383                char::from_u32(value)
384                    .map(|_| ClassItem::Literal(value))
385                    .ok_or_else(|| ParseError(format!("illegal Unicode character in \\u{digits}")))
386            }
387            'U' => {
388                let digits = self.get_while(is_hex_digit);
389                if digits.len() != 8 {
390                    return Err(ParseError(format!("incomplete escape \\U{digits}")));
391                }
392                let value = u32::from_str_radix(&digits, 16).unwrap_or(0);
393                char::from_u32(value)
394                    .map(|_| ClassItem::Literal(value))
395                    .ok_or_else(|| ParseError(format!("illegal Unicode character in \\U{digits}")))
396            }
397            'N' => Err(ParseError(
398                "bad escape \\N: named escapes are not supported by the safety chain".into(),
399            )),
400            c if is_oct_digit(c) => {
401                let mut digits = String::new();
402                digits.push(c);
403                digits.push_str(&self.get_while(is_oct_digit));
404                // At most two more digits were taken; cap at three total.
405                let value = u32::from_str_radix(&digits, 8).unwrap_or(0x11000);
406                if value > 0o377 {
407                    return Err(ParseError(format!(
408                        "octal escape value \\{digits} outside of range 0-0o377"
409                    )));
410                }
411                Ok(ClassItem::Literal(value))
412            }
413            c if is_digit(c) => Err(ParseError(format!("bad escape \\{c}"))),
414            c if c.is_ascii_alphabetic() => Err(ParseError(format!("bad escape \\{c}"))),
415            c => Ok(ClassItem::Literal(u32::from(c))),
416        }
417    }
418
419    /// Reference `_escape` (outside a class).
420    fn escape(&mut self) -> Result<Op, ParseError> {
421        let escape = self.get().ok_or_else(|| ParseError("bad escape".into()))?;
422        if let Some(category) = Category::from_escape(escape) {
423            return Ok(Op::In(vec![ClassItem::Category(category)]));
424        }
425        match escape {
426            'A' => Ok(Op::At(At::BeginningString)),
427            'z' | 'Z' => Ok(Op::At(At::EndString)),
428            'b' => Ok(Op::At(At::Boundary)),
429            'B' => Ok(Op::At(At::NonBoundary)),
430            'a' => Ok(Op::Literal(0x07)),
431            'f' => Ok(Op::Literal(0x0C)),
432            'n' => Ok(Op::Literal(0x0A)),
433            'r' => Ok(Op::Literal(0x0D)),
434            't' => Ok(Op::Literal(0x09)),
435            'v' => Ok(Op::Literal(0x0B)),
436            '\\' => Ok(Op::Literal(0x5C)),
437            'x' => {
438                let digits = self.get_while(is_hex_digit);
439                if digits.len() != 2 {
440                    return Err(ParseError(format!("incomplete escape \\x{digits}")));
441                }
442                Ok(Op::Literal(u32::from_str_radix(&digits, 16).unwrap_or(0)))
443            }
444            'u' => {
445                let digits = self.get_while(is_hex_digit);
446                if digits.len() != 4 {
447                    return Err(ParseError(format!("incomplete escape \\u{digits}")));
448                }
449                let value = u32::from_str_radix(&digits, 16).unwrap_or(0);
450                char::from_u32(value)
451                    .map(|_| Op::Literal(value))
452                    .ok_or_else(|| ParseError(format!("illegal Unicode character in \\u{digits}")))
453            }
454            'U' => {
455                let digits = self.get_while(is_hex_digit);
456                if digits.len() != 8 {
457                    return Err(ParseError(format!("incomplete escape \\U{digits}")));
458                }
459                let value = u32::from_str_radix(&digits, 16).unwrap_or(0);
460                char::from_u32(value)
461                    .map(|_| Op::Literal(value))
462                    .ok_or_else(|| ParseError(format!("illegal Unicode character in \\U{digits}")))
463            }
464            'N' => Err(ParseError(
465                "bad escape \\N: named escapes are not supported by the safety chain".into(),
466            )),
467            '0' => {
468                let digits = self.get_while(is_oct_digit);
469                Ok(Op::Literal(u32::from_str_radix(&digits, 8).unwrap_or(0)))
470            }
471            c if is_digit(c) => {
472                let mut digits = String::new();
473                digits.push(c);
474                if self.peek().is_some_and(is_digit) {
475                    digits.push(self.get().expect("peeked digit"));
476                    let all_octal =
477                        digits.chars().all(is_oct_digit) && self.peek().is_some_and(is_oct_digit);
478                    if all_octal {
479                        digits.push(self.get().expect("peeked octal digit"));
480                        let value = u32::from_str_radix(&digits, 8).expect("octal digits");
481                        if value > 0o377 {
482                            return Err(ParseError(format!(
483                                "octal escape value \\{digits} outside of range 0-0o377"
484                            )));
485                        }
486                        return Ok(Op::Literal(value));
487                    }
488                }
489                let group: u32 = digits.parse().unwrap_or(u32::MAX);
490                if (group as u64) < u64::from(self.next_group) {
491                    if !self.checkgroup(group) {
492                        return Err(ParseError("cannot refer to an open group".into()));
493                    }
494                    return Ok(Op::GroupRef(group));
495                }
496                Err(ParseError(format!("invalid group reference {group}")))
497            }
498            c if c.is_ascii_alphabetic() => Err(ParseError(format!("bad escape \\{c}"))),
499            c => Ok(Op::Literal(u32::from(c))),
500        }
501    }
502
503    fn parse_class(&mut self) -> Result<Op, ParseError> {
504        let negate = self.match_char('^');
505        let mut set: Vec<ClassItem> = Vec::new();
506        loop {
507            let this = self
508                .get()
509                .ok_or_else(|| ParseError("unterminated character set".into()))?;
510            if this == ']' && !set.is_empty() {
511                break;
512            }
513            let this_text = if this == '\\' {
514                format!("\\{}", self.peek().map(String::from).unwrap_or_default())
515            } else {
516                this.to_string()
517            };
518            let code1 = if this == '\\' {
519                self.class_escape()?
520            } else {
521                ClassItem::Literal(u32::from(this))
522            };
523            if self.match_char('-') {
524                let that = self
525                    .get()
526                    .ok_or_else(|| ParseError("unterminated character set".into()))?;
527                if that == ']' {
528                    set.push(code1);
529                    set.push(ClassItem::Literal(u32::from('-')));
530                    break;
531                }
532                let that_text = if that == '\\' {
533                    format!("\\{}", self.peek().map(String::from).unwrap_or_default())
534                } else {
535                    that.to_string()
536                };
537                let code2 = if that == '\\' {
538                    self.class_escape()?
539                } else {
540                    ClassItem::Literal(u32::from(that))
541                };
542                let (ClassItem::Literal(lo), ClassItem::Literal(hi)) = (&code1, &code2) else {
543                    return Err(ParseError(format!(
544                        "bad character range {this_text}-{that_text}"
545                    )));
546                };
547                if hi < lo {
548                    return Err(ParseError(format!(
549                        "bad character range {this_text}-{that_text}"
550                    )));
551                }
552                set.push(ClassItem::Range(*lo, *hi));
553            } else {
554                set.push(code1);
555            }
556        }
557        // Reference `_uniq`: dedupe preserving order.
558        let mut seen: Vec<ClassItem> = Vec::new();
559        for item in set {
560            if !seen.contains(&item) {
561                seen.push(item);
562            }
563        }
564        if seen.len() == 1
565            && let ClassItem::Literal(cp) = seen[0]
566        {
567            if negate {
568                return Ok(Op::NotLiteral(cp));
569            }
570            return Ok(Op::Literal(cp));
571        }
572        let mut items = Vec::with_capacity(seen.len() + 1);
573        if negate {
574            items.push(ClassItem::Negate);
575        }
576        items.extend(seen);
577        Ok(Op::In(items))
578    }
579
580    fn parse_repeat(&mut self, ops: &mut Vec<Op>, marker: char) -> Result<(), ParseError> {
581        // The caller only ever passes the reference `REPEAT_CHARS`.
582        let (low, high) = if marker == '?' {
583            (0u32, Some(1u32))
584        } else if marker == '*' || marker == '+' {
585            (u32::from(marker == '+'), None)
586        } else {
587            // The '{' itself was consumed by the caller.
588            let brace_pos = self.pos - 1;
589            if self.peek() == Some('}') {
590                ops.push(Op::Literal(u32::from('{')));
591                return Ok(());
592            }
593            let lo = self.get_while(is_digit);
594            let hi = if self.match_char(',') {
595                Some(self.get_while(is_digit))
596            } else {
597                None // hi = lo
598            };
599            if !self.match_char('}') {
600                // Not a quantifier: literal '{', re-reading the digit
601                // run from just after the brace (the reference seeks).
602                ops.push(Op::Literal(u32::from('{')));
603                self.pos = brace_pos + 1;
604                return Ok(());
605            }
606            let mut min = 0u32;
607            let mut max = high_from(lo.as_str(), hi.as_deref())?;
608            if !lo.is_empty() {
609                min = parse_repeat_count(lo.as_str())?;
610            }
611            if let Some(mx) = max {
612                if mx < min {
613                    return Err(ParseError("min repeat greater than max repeat".into()));
614                }
615                max = Some(mx);
616            }
617            (min, max)
618        };
619        let Some(last) = ops.last() else {
620            return Err(ParseError("nothing to repeat".into()));
621        };
622        if matches!(last, Op::At(_)) {
623            return Err(ParseError("nothing to repeat".into()));
624        }
625        if matches!(last, Op::Repeat { .. }) {
626            return Err(ParseError("multiple repeat".into()));
627        }
628        let last = ops.pop().expect("checked non-empty");
629        let body = match last {
630            Op::SubPattern {
631                group: None,
632                add,
633                del,
634                body,
635            } if add == Flags::default() && del == Flags::default() => body,
636            other => vec![other],
637        };
638        // Lazy marker only: the reference oracle pins the Python 3.10
639        // syntax floor, where possessive quantifiers are a repeat error.
640        let mut kind = RepeatKind::Greedy;
641        if self.match_char('?') {
642            kind = RepeatKind::Lazy;
643        } else if self.peek() == Some('+') {
644            return Err(ParseError("multiple repeat".into()));
645        }
646        ops.push(Op::Repeat {
647            kind,
648            low,
649            high,
650            body,
651        });
652        Ok(())
653    }
654
655    fn parse_sequence(&mut self, depth: usize) -> Result<Vec<Op>, ParseError> {
656        if depth > 200 {
657            return Err(ParseError("pattern nesting too deep".into()));
658        }
659        // Alternation: parse one branch, then keep going past `|`.
660        let mut branches: Vec<Vec<Op>> = Vec::new();
661        loop {
662            let branch = self.parse_branch(depth)?;
663            branches.push(branch);
664            if self.peek() == Some('|') {
665                self.pos += 1;
666                continue;
667            }
668            break;
669        }
670        let branches: Vec<Vec<Op>> = branches
671            .into_iter()
672            .map(unpack_transparent_groups)
673            .collect();
674        if branches.len() == 1 {
675            return Ok(branches.into_iter().next().expect("one branch"));
676        }
677        Ok(vec![Op::Branch(branches)])
678    }
679
680    fn parse_branch(&mut self, depth: usize) -> Result<Vec<Op>, ParseError> {
681        let mut ops: Vec<Op> = Vec::new();
682        while let Some(this) = self.peek() {
683            if this == '|' || this == ')' {
684                break;
685            }
686            self.pos += 1;
687            // `|` and `)` broke out of the loop above, and every remaining
688            // `SPECIAL_CHARS` member has an arm here, so the final catch-all
689            // only ever sees plain literal characters.
690            match this {
691                '\\' => {
692                    ops.push(self.escape()?);
693                }
694                '[' => {
695                    ops.push(self.parse_class()?);
696                }
697                '*' | '+' | '?' | '{' => {
698                    self.parse_repeat(&mut ops, this)?;
699                }
700                '.' => ops.push(Op::Any),
701                '^' => ops.push(Op::At(At::Beginning)),
702                '$' => ops.push(Op::At(At::End)),
703                '(' => {
704                    if self.peek() == Some('?') {
705                        self.pos += 1;
706                        let Some(char) = self.get() else {
707                            return Err(ParseError("unexpected end of pattern".into()));
708                        };
709                        match char {
710                            'P' => {
711                                if self.match_char('<') {
712                                    let name = self.get_until('>', "group name")?;
713                                    self.check_group_name(&name)?;
714                                    #[cfg(not(coverage))] // unreachable:
715                                    // `opengroup` is infallible, it always
716                                    // returns the next sequential group id
717                                    let gid = self.opengroup(Some(name))?;
718                                    #[cfg(coverage)]
719                                    let gid =
720                                        self.opengroup(Some(name)).expect("opengroup cannot fail");
721                                    let body = self.parse_group_body(depth)?;
722                                    self.closegroup(gid);
723                                    ops.push(Op::SubPattern {
724                                        group: Some(gid),
725                                        add: Flags::default(),
726                                        del: Flags::default(),
727                                        body,
728                                    });
729                                } else if self.match_char('=') {
730                                    let name = self.get_until(')', "group name")?;
731                                    self.check_group_name(&name)?;
732                                    let Some(gid) = self.names.get(&name).copied() else {
733                                        return Err(ParseError(format!(
734                                            "unknown group name {name:?}"
735                                        )));
736                                    };
737                                    if !self.checkgroup(gid) {
738                                        return Err(ParseError(
739                                            "cannot refer to an open group".into(),
740                                        ));
741                                    }
742                                    ops.push(Op::GroupRef(gid));
743                                } else {
744                                    let char = self.get().ok_or_else(|| {
745                                        ParseError("unexpected end of pattern".into())
746                                    })?;
747                                    return Err(ParseError(format!("unknown extension ?P{char}")));
748                                }
749                            }
750                            ':' => {
751                                let body = self.parse_group_body(depth)?;
752                                ops.push(Op::SubPattern {
753                                    group: None,
754                                    add: Flags::default(),
755                                    del: Flags::default(),
756                                    body,
757                                });
758                            }
759                            '#' => loop {
760                                match self.get() {
761                                    None => {
762                                        return Err(ParseError(
763                                            "missing ), unterminated comment".into(),
764                                        ));
765                                    }
766                                    Some(')') => break,
767                                    Some(_) => {}
768                                }
769                            },
770                            '=' => {
771                                let body = self.parse_group_body(depth)?;
772                                ops.push(Op::Assert {
773                                    behind: false,
774                                    negated: false,
775                                    body,
776                                });
777                            }
778                            '!' => {
779                                let body = self.parse_group_body(depth)?;
780                                if body.is_empty() {
781                                    ops.push(Op::Failure);
782                                } else {
783                                    ops.push(Op::Assert {
784                                        behind: false,
785                                        negated: true,
786                                        body,
787                                    });
788                                }
789                            }
790                            '<' => {
791                                let Some(marker) = self.get() else {
792                                    return Err(ParseError("unexpected end of pattern".into()));
793                                };
794                                if marker != '=' && marker != '!' {
795                                    return Err(ParseError(format!(
796                                        "unknown extension ?<{marker}"
797                                    )));
798                                }
799                                let body = self.parse_group_body(depth)?;
800                                if marker == '=' {
801                                    ops.push(Op::Assert {
802                                        behind: true,
803                                        negated: false,
804                                        body,
805                                    });
806                                } else if body.is_empty() {
807                                    ops.push(Op::Failure);
808                                } else {
809                                    ops.push(Op::Assert {
810                                        behind: true,
811                                        negated: true,
812                                        body,
813                                    });
814                                }
815                            }
816                            '(' => {
817                                let condname = self.get_until(')', "group name")?;
818                                let group = if !condname.is_empty()
819                                    && condname.chars().all(|c| c.is_ascii_digit())
820                                {
821                                    let value: u32 = condname.parse().map_err(|_| {
822                                        ParseError(format!("invalid group reference {condname}"))
823                                    })?;
824                                    if value == 0 {
825                                        return Err(ParseError("bad group number".into()));
826                                    }
827                                    value
828                                } else {
829                                    self.check_group_name(&condname)?;
830                                    let Some(gid) = self.names.get(&condname).copied() else {
831                                        return Err(ParseError(format!(
832                                            "unknown group name {condname:?}"
833                                        )));
834                                    };
835                                    gid
836                                };
837                                // The reference parses exactly one branch
838                                // per conditional arm; the explicit `|`
839                                // between them is consumed here.
840                                let yes = self.parse_branch(depth + 1)?;
841                                let no = if self.match_char('|') {
842                                    let no = self.parse_branch(depth + 1)?;
843                                    if self.peek() == Some('|') {
844                                        return Err(ParseError(
845                                            "conditional backref with more than two branches"
846                                                .into(),
847                                        ));
848                                    }
849                                    Some(no)
850                                } else {
851                                    None
852                                };
853                                if !self.match_char(')') {
854                                    return Err(ParseError(
855                                        "missing ), unterminated subpattern".into(),
856                                    ));
857                                }
858                                ops.push(Op::GroupRefExists { group, yes, no });
859                            }
860                            // The reference oracle pins the Python 3.10
861                            // syntax floor: atomic groups are a compile
862                            // failure there.
863                            '>' => {
864                                return Err(ParseError("unknown extension ?>".into()));
865                            }
866                            'i' | 'm' | 's' | 'a' | 'x' | 'u' | 'L' | '-' => {
867                                // Re-enter the flag parser at the first char
868                                // (it was consumed here).
869                                self.pos -= 1;
870                                let Some((add, del)) = self.parse_flags()? else {
871                                    // Global flags: only valid at the very
872                                    // start of the outer expression.
873                                    if depth != 0 || !ops.is_empty() {
874                                        return Err(ParseError(
875                                            "global flags not at the start of the expression"
876                                                .into(),
877                                        ));
878                                    }
879                                    continue;
880                                };
881                                let body = self.parse_group_body(depth)?;
882                                ops.push(Op::SubPattern {
883                                    group: None,
884                                    add,
885                                    del,
886                                    body,
887                                });
888                            }
889                            other => {
890                                return Err(ParseError(format!("unknown extension ?{other}")));
891                            }
892                        }
893                    } else {
894                        #[cfg(not(coverage))] // unreachable: `opengroup` is
895                        // infallible, it always returns the next group id
896                        let gid = self.opengroup(None)?;
897                        #[cfg(coverage)]
898                        let gid = self.opengroup(None).expect("opengroup cannot fail");
899                        let body = self.parse_group_body(depth)?;
900                        self.closegroup(gid);
901                        ops.push(Op::SubPattern {
902                            group: Some(gid),
903                            add: Flags::default(),
904                            del: Flags::default(),
905                            body,
906                        });
907                    }
908                }
909                other => ops.push(Op::Literal(u32::from(other))),
910            }
911        }
912        Ok(ops)
913    }
914
915    fn parse_group_body(&mut self, depth: usize) -> Result<Vec<Op>, ParseError> {
916        let body = self.parse_sequence(depth + 1)?;
917        if !self.match_char(')') {
918            return Err(ParseError("missing ), unterminated subpattern".into()));
919        }
920        Ok(body)
921    }
922}
923
924fn parse_repeat_count(digits: &str) -> Result<u32, ParseError> {
925    let value: u64 = digits
926        .parse()
927        .map_err(|_| ParseError("the repetition number is too large".into()))?;
928    if value >= MAXREPEAT {
929        return Err(ParseError("the repetition number is too large".into()));
930    }
931    #[cfg(not(coverage))] // unreachable: MAXREPEAT sits below u32::MAX, so
932    // the guard above already rejected every value wider than u32
933    let count = u32::try_from(value)
934        .map_err(|_| ParseError("the repetition number is too large".into()))?;
935    #[cfg(coverage)]
936    let count = u32::try_from(value).expect("the guard above bounds value to u32");
937    Ok(count)
938}
939
940/// The repeat upper bound: with no comma the reference reuses `hi = lo`
941/// (empty `lo` means unbounded); with a comma an empty `hi` is unbounded.
942fn high_from(lo: &str, hi: Option<&str>) -> Result<Option<u32>, ParseError> {
943    match hi {
944        None if lo.is_empty() => Ok(None),
945        None => parse_repeat_count(lo).map(Some),
946        Some("") => Ok(None),
947        Some(digits) => parse_repeat_count(digits).map(Some),
948    }
949}
950
951/// The reference's end-of-branch unpacking of transparent
952/// (non-capturing, unflagged) groups.
953fn unpack_transparent_groups(ops: Vec<Op>) -> Vec<Op> {
954    let mut unpacked = Vec::with_capacity(ops.len());
955    for op in ops {
956        match op {
957            Op::SubPattern {
958                group: None,
959                add,
960                del,
961                body,
962            } if add == Flags::default() && del == Flags::default() => {
963                unpacked.extend(body);
964            }
965            other => unpacked.push(other),
966        }
967    }
968    unpacked
969}
970
971/// Parse `pattern` under `flags`; returns the op tree and the final flags
972/// (global inline flags included), mirroring `re._parser.parse`.
973///
974/// # Errors
975///
976/// A [`ParseError`] whose message mirrors the reference `re.error` text
977/// whenever the reference parser would reject the pattern.
978pub fn parse(pattern: &str, flags: Flags) -> Result<(Vec<Op>, Flags), ParseError> {
979    let mut parser = Parser::new(pattern, flags);
980    let ops = parser.parse_sequence(0)?;
981    if parser.peek() == Some(')') {
982        return Err(ParseError("unbalanced parenthesis".into()));
983    }
984    Ok((ops, parser.flags))
985}
986
987#[cfg(test)]
988mod tests {
989    use super::*;
990
991    fn parse_ok(pattern: &str, flags: Flags) -> (Vec<Op>, Flags) {
992        parse(pattern, flags).expect("pattern must parse")
993    }
994
995    /// Destructure a subpattern op; panics on any other variant.
996    fn subpattern_parts(op: Op) -> (Option<u32>, Flags, Flags, Vec<Op>) {
997        match op {
998            Op::SubPattern {
999                group,
1000                add,
1001                del,
1002                body,
1003            } => (group, add, del, body),
1004            other => panic!("expected a subpattern, got {other:?}"),
1005        }
1006    }
1007
1008    #[test]
1009    fn literals_and_anchors() {
1010        let (ops, _) = parse_ok(r"a^$", Flags::default());
1011        assert_eq!(
1012            ops,
1013            vec![
1014                Op::Literal(u32::from('a')),
1015                Op::At(At::Beginning),
1016                Op::At(At::End),
1017            ]
1018        );
1019    }
1020
1021    #[test]
1022    fn any_node_and_dotall_inline_flag() {
1023        let (plain, _) = parse_ok(".", Flags::default());
1024        assert_eq!(plain, vec![Op::Any]);
1025        let (dotted, flags) = parse_ok("(?s).", Flags::default());
1026        assert_eq!(dotted, vec![Op::Any]);
1027        assert!(flags.dotall);
1028    }
1029
1030    #[test]
1031    fn char_class_with_range_negation_and_escapes() {
1032        let (ops, _) = parse_ok(r"[a-c\d]", Flags::default());
1033        assert_eq!(
1034            ops,
1035            vec![Op::In(vec![
1036                ClassItem::Range(u32::from('a'), u32::from('c')),
1037                ClassItem::Category(Category::Digit),
1038            ])]
1039        );
1040    }
1041
1042    #[test]
1043    fn negated_single_literal_becomes_not_literal() {
1044        let (ops, _) = parse_ok(r"[^a]", Flags::default());
1045        assert_eq!(ops, vec![Op::NotLiteral(u32::from('a'))]);
1046    }
1047
1048    #[test]
1049    fn negated_multi_item_class_inserts_negate() {
1050        let (ops, _) = parse_ok(r"[^ab]", Flags::default());
1051        assert_eq!(
1052            ops,
1053            vec![Op::In(vec![
1054                ClassItem::Negate,
1055                ClassItem::Literal(u32::from('a')),
1056                ClassItem::Literal(u32::from('b')),
1057            ])]
1058        );
1059    }
1060
1061    #[test]
1062    fn leading_bracket_and_dash_are_literal_inside_a_class() {
1063        let (ops, _) = parse_ok(r"[]-]", Flags::default());
1064        assert_eq!(
1065            ops,
1066            vec![Op::In(vec![
1067                ClassItem::Literal(u32::from(']')),
1068                ClassItem::Literal(u32::from('-')),
1069            ])]
1070        );
1071    }
1072
1073    #[test]
1074    fn backslash_b_inside_a_class_is_backspace() {
1075        // The single-literal class optimization unwraps the IN node.
1076        let (ops, _) = parse_ok(r"[\b]", Flags::default());
1077        assert_eq!(ops, vec![Op::Literal(0x08)]);
1078    }
1079
1080    #[test]
1081    fn escaped_backslash_inside_a_class_is_a_literal_backslash() {
1082        // `\\` inside a class parses to the 0x5C literal.
1083        let (ops, _) = parse_ok(r"[\\]", Flags::default());
1084        assert_eq!(ops, vec![Op::Literal(0x5C)]);
1085    }
1086
1087    #[test]
1088    fn class_dedupes_preserving_order() {
1089        let (ops, _) = parse_ok(r"[aab]", Flags::default());
1090        assert_eq!(
1091            ops,
1092            vec![Op::In(vec![
1093                ClassItem::Literal(u32::from('a')),
1094                ClassItem::Literal(u32::from('b')),
1095            ])]
1096        );
1097    }
1098
1099    #[test]
1100    fn alternation_builds_a_branch_node() {
1101        let (ops, _) = parse_ok("a|bc", Flags::default());
1102        assert_eq!(
1103            ops,
1104            vec![Op::Branch(vec![
1105                vec![Op::Literal(u32::from('a'))],
1106                vec![Op::Literal(u32::from('b')), Op::Literal(u32::from('c')),],
1107            ])]
1108        );
1109    }
1110
1111    #[test]
1112    fn empty_alternation_branch_is_allowed() {
1113        let (ops, _) = parse_ok("(a|)", Flags::default());
1114        let (_, _, _, body) = subpattern_parts(ops.into_iter().next().expect("an op"));
1115        assert_eq!(
1116            body,
1117            vec![Op::Branch(vec![vec![Op::Literal(u32::from('a'))], vec![],])]
1118        );
1119    }
1120
1121    #[test]
1122    fn non_capturing_groups_are_unpacked() {
1123        let (ops, _) = parse_ok("(?:a)(?:b)", Flags::default());
1124        assert_eq!(
1125            ops,
1126            vec![Op::Literal(u32::from('a')), Op::Literal(u32::from('b')),]
1127        );
1128    }
1129
1130    #[test]
1131    fn nested_non_capturing_wrappers_unpack_completely() {
1132        let (ops, _) = parse_ok("(?:(?:ab))", Flags::default());
1133        assert_eq!(
1134            ops,
1135            vec![Op::Literal(u32::from('a')), Op::Literal(u32::from('b')),]
1136        );
1137    }
1138
1139    #[test]
1140    fn capturing_groups_are_numbered_in_order() {
1141        // The leading literal keeps the catch-all filter arm exercised.
1142        let (ops, _) = parse_ok("(?:a)(b)(c)", Flags::default());
1143        let groups: Vec<Option<u32>> = ops
1144            .iter()
1145            .filter_map(|op| match op {
1146                Op::SubPattern { group, .. } => Some(*group),
1147                _ => None,
1148            })
1149            .collect();
1150        assert_eq!(groups, vec![Some(1), Some(2)]);
1151    }
1152
1153    #[test]
1154    fn named_groups_register_a_backreference_target() {
1155        let (ops, _) = parse_ok(r"(?P<word>x)(?P=word)", Flags::default());
1156        assert_eq!(ops[1], Op::GroupRef(1));
1157    }
1158
1159    #[test]
1160    fn comment_groups_disappear() {
1161        let (ops, _) = parse_ok("(?#note)a", Flags::default());
1162        assert_eq!(ops, vec![Op::Literal(u32::from('a'))]);
1163    }
1164
1165    #[test]
1166    fn lookahead_and_lookbehind_become_asserts() {
1167        let (ops, _) = parse_ok(r"(?=a)(?!b)(?<=c)(?<!d)", Flags::default());
1168        let expected = [
1169            (false, false, 'a'),
1170            (false, true, 'b'),
1171            (true, false, 'c'),
1172            (true, true, 'd'),
1173        ];
1174        for (index, (behind, negated, literal)) in expected.into_iter().enumerate() {
1175            assert_eq!(
1176                ops[index],
1177                Op::Assert {
1178                    behind,
1179                    negated,
1180                    body: vec![Op::Literal(u32::from(literal))],
1181                }
1182            );
1183        }
1184    }
1185
1186    #[test]
1187    fn empty_negative_assertions_become_failure() {
1188        let (ops, _) = parse_ok("(?!)", Flags::default());
1189        assert_eq!(ops, vec![Op::Failure]);
1190        let (ops, _) = parse_ok("(?<!)", Flags::default());
1191        assert_eq!(ops, vec![Op::Failure]);
1192    }
1193
1194    #[test]
1195    fn conditional_groups_parse_both_branches() {
1196        let (ops, _) = parse_ok("(a)(?(1)b|c)", Flags::default());
1197        assert_eq!(
1198            ops[1],
1199            Op::GroupRefExists {
1200                group: 1,
1201                yes: vec![Op::Literal(u32::from('b'))],
1202                no: Some(vec![Op::Literal(u32::from('c'))]),
1203            }
1204        );
1205    }
1206
1207    #[test]
1208    fn conditional_group_by_name_resolves_the_number() {
1209        let (ops, _) = parse_ok("(?P<x>a)(?(x)b)", Flags::default());
1210        assert_eq!(
1211            ops[1],
1212            Op::GroupRefExists {
1213                group: 1,
1214                yes: vec![Op::Literal(u32::from('b'))],
1215                no: None,
1216            }
1217        );
1218    }
1219
1220    #[test]
1221    fn repeats_apply_to_the_previous_item() {
1222        // The trailing literal keeps the catch-all filter arm exercised.
1223        let (ops, _) = parse_ok("a*b+c?d{2}e{3,}f{1,4}g", Flags::default());
1224        let kinds: Vec<(u32, Option<u32>)> = ops
1225            .iter()
1226            .filter_map(|op| match op {
1227                Op::Repeat { low, high, .. } => Some((*low, *high)),
1228                _ => None,
1229            })
1230            .collect();
1231        assert_eq!(
1232            kinds,
1233            vec![
1234                (0, None),
1235                (1, None),
1236                (0, Some(1)),
1237                (2, Some(2)),
1238                (3, None),
1239                (1, Some(4))
1240            ]
1241        );
1242    }
1243
1244    #[test]
1245    fn open_brace_counts_are_unbounded_and_lazy_marker_is_consumed() {
1246        let (ops, _) = parse_ok("a{2,}?", Flags::default());
1247        assert_eq!(
1248            ops,
1249            vec![Op::Repeat {
1250                kind: RepeatKind::Lazy,
1251                low: 2,
1252                high: None,
1253                body: vec![Op::Literal(u32::from('a'))],
1254            }]
1255        );
1256    }
1257
1258    #[test]
1259    fn empty_brace_bounds_mean_zero_to_unbounded() {
1260        let (ops, _) = parse_ok("a{,}", Flags::default());
1261        assert_eq!(
1262            ops[0],
1263            Op::Repeat {
1264                kind: RepeatKind::Greedy,
1265                low: 0,
1266                high: None,
1267                body: vec![Op::Literal(u32::from('a'))],
1268            }
1269        );
1270    }
1271
1272    #[test]
1273    fn invalid_brace_becomes_a_literal_and_reparse_follows() {
1274        let (ops, _) = parse_ok("a{x}", Flags::default());
1275        assert_eq!(
1276            ops,
1277            vec![
1278                Op::Literal(u32::from('a')),
1279                Op::Literal(u32::from('{')),
1280                Op::Literal(u32::from('x')),
1281                Op::Literal(u32::from('}')),
1282            ]
1283        );
1284    }
1285
1286    #[test]
1287    fn repeats_unwrap_a_transparent_group_body() {
1288        let (ops, _) = parse_ok("(?:ab)+", Flags::default());
1289        assert_eq!(
1290            ops,
1291            vec![Op::Repeat {
1292                kind: RepeatKind::Greedy,
1293                low: 1,
1294                high: None,
1295                body: vec![Op::Literal(u32::from('a')), Op::Literal(u32::from('b')),],
1296            }]
1297        );
1298    }
1299
1300    #[test]
1301    fn flagged_groups_keep_their_flag_deltas() {
1302        let (ops, _) = parse_ok("(?i:x)", Flags::default());
1303        let (_, add, del, _) = subpattern_parts(ops.into_iter().next().expect("an op"));
1304        assert!(add.ignorecase);
1305        assert_eq!(del, Flags::default());
1306    }
1307
1308    #[test]
1309    fn negated_scoped_flags_are_recorded() {
1310        let (ops, _) = parse_ok("(?-i:x)", Flags::ignorecase_multiline());
1311        let (_, _, del, _) = subpattern_parts(ops.into_iter().next().expect("an op"));
1312        assert!(del.ignorecase);
1313    }
1314
1315    #[test]
1316    fn global_inline_flags_update_the_final_flags() {
1317        let (_, flags) = parse_ok("(?i)a", Flags::default());
1318        assert!(flags.ignorecase);
1319        let (_, flags) = parse_ok("(?is)a", Flags::default());
1320        assert!(flags.ignorecase && flags.dotall);
1321    }
1322
1323    #[test]
1324    fn global_flags_must_precede_any_pattern_item() {
1325        assert_eq!(
1326            parse("a(?i)", Flags::default()).err(),
1327            Some(ParseError(
1328                "global flags not at the start of the expression".into()
1329            ))
1330        );
1331        assert_eq!(
1332            parse("(a)(?i)", Flags::default()).err(),
1333            Some(ParseError(
1334                "global flags not at the start of the expression".into()
1335            ))
1336        );
1337    }
1338
1339    #[test]
1340    fn consecutive_global_flag_groups_are_allowed_at_the_start() {
1341        let (_, flags) = parse_ok("(?i)(?m)a", Flags::default());
1342        assert!(flags.ignorecase && flags.multiline);
1343    }
1344
1345    #[test]
1346    fn negated_global_flag_group_is_an_error() {
1347        assert_eq!(
1348            parse("(?-i)", Flags::default()).err(),
1349            Some(ParseError("missing :".into()))
1350        );
1351    }
1352
1353    #[test]
1354    fn turned_on_and_off_flag_is_an_error() {
1355        assert_eq!(
1356            parse("(?i-i:x)", Flags::default()).err(),
1357            Some(ParseError(
1358                "bad inline flags: flag turned on and off".into()
1359            ))
1360        );
1361    }
1362
1363    #[test]
1364    fn locale_flag_is_rejected_for_str_patterns() {
1365        assert_eq!(
1366            parse("(?L:x)", Flags::default()).err(),
1367            Some(ParseError(
1368                "bad inline flags: cannot use 'L' flag with a str pattern".into()
1369            ))
1370        );
1371    }
1372
1373    #[test]
1374    fn unknown_flag_is_an_error() {
1375        // The reference reports an unknown extension for non-flag letters.
1376        assert_eq!(
1377            parse("(?q:x)", Flags::default()).err(),
1378            Some(ParseError("unknown extension ?q".into()))
1379        );
1380    }
1381
1382    #[test]
1383    fn escapes_and_octal_forms() {
1384        // The trailing anchor keeps the catch-all filter arm exercised.
1385        let (ops, _) = parse_ok(r"\a\f\n\r\t\v\\\x41\101\0\A", Flags::default());
1386        let literals: Vec<u32> = ops
1387            .iter()
1388            .filter_map(|op| match op {
1389                Op::Literal(cp) => Some(*cp),
1390                _ => None,
1391            })
1392            .collect();
1393        assert_eq!(
1394            literals,
1395            vec![0x07, 0x0C, 0x0A, 0x0D, 0x09, 0x0B, 0x5C, 0x41, 0o101, 0x0]
1396        );
1397    }
1398
1399    #[test]
1400    fn unicode_escapes() {
1401        let (ops, _) = parse_ok(r"\u00e9\U0001F600", Flags::default());
1402        assert_eq!(ops, vec![Op::Literal(0xE9), Op::Literal(0x1F600)]);
1403    }
1404
1405    #[test]
1406    fn zero_width_escapes() {
1407        let (ops, _) = parse_ok(r"\A\Z\b\B", Flags::default());
1408        assert_eq!(
1409            ops,
1410            vec![
1411                Op::At(At::BeginningString),
1412                Op::At(At::EndString),
1413                Op::At(At::Boundary),
1414                Op::At(At::NonBoundary),
1415            ]
1416        );
1417    }
1418
1419    #[test]
1420    fn escaped_punctuation_is_literal() {
1421        let (ops, _) = parse_ok(r"\.\*\+\?", Flags::default());
1422        assert_eq!(
1423            ops,
1424            vec![
1425                Op::Literal(u32::from('.')),
1426                Op::Literal(u32::from('*')),
1427                Op::Literal(u32::from('+')),
1428                Op::Literal(u32::from('?')),
1429            ]
1430        );
1431    }
1432
1433    #[test]
1434    fn backreference_to_a_closed_group_parses() {
1435        let (ops, _) = parse_ok(r"(a)\1", Flags::default());
1436        assert_eq!(ops[1], Op::GroupRef(1));
1437        let (ops, _) = parse_ok(r"(a)\1\1", Flags::default());
1438        assert_eq!(ops[1], Op::GroupRef(1));
1439        assert_eq!(ops[2], Op::GroupRef(1));
1440    }
1441
1442    #[test]
1443    fn errors_mirror_the_reference_messages() {
1444        let cases = [
1445            (
1446                "(unclosed",
1447                ParseError("missing ), unterminated subpattern".into()),
1448            ),
1449            (")", ParseError("unbalanced parenthesis".into())),
1450            ("[invalid", ParseError("unterminated character set".into())),
1451            ("*leading", ParseError("nothing to repeat".into())),
1452            ("^*", ParseError("nothing to repeat".into())),
1453            ("a**", ParseError("multiple repeat".into())),
1454            (
1455                "a{3,2}",
1456                ParseError("min repeat greater than max repeat".into()),
1457            ),
1458            (
1459                "a{99999999999999999999}",
1460                ParseError("the repetition number is too large".into()),
1461            ),
1462            (
1463                "a{4294967287}",
1464                ParseError("the repetition number is too large".into()),
1465            ),
1466            (r"[z-a]", ParseError("bad character range z-a".into())),
1467            (r"[\d-a]", ParseError("bad character range \\d-a".into())),
1468            (r"\q", ParseError("bad escape \\q".into())),
1469            (r"\x2", ParseError("incomplete escape \\x2".into())),
1470            (r"\u12", ParseError("incomplete escape \\u12".into())),
1471            (
1472                r"\N{DASH}",
1473                ParseError(
1474                    "bad escape \\N: named escapes are not supported by the safety chain".into(),
1475                ),
1476            ),
1477            (r"\1", ParseError("invalid group reference 1".into())),
1478            (r"(a)\2", ParseError("invalid group reference 2".into())),
1479            (r"(a\1)", ParseError("cannot refer to an open group".into())),
1480            (
1481                "(?P<1bad>x)",
1482                ParseError("bad character in group name \"1bad\"".into()),
1483            ),
1484            ("(?P=n)", ParseError("unknown group name \"n\"".into())),
1485            ("(?(0)a)", ParseError("bad group number".into())),
1486            ("(?(n)a)", ParseError("unknown group name \"n\"".into())),
1487            (
1488                "(?P<x",
1489                ParseError("missing >, unterminated group name".into()),
1490            ),
1491            ("(?P", ParseError("unexpected end of pattern".into())),
1492            ("(?P!x)", ParseError("unknown extension ?P!".into())),
1493            ("(?<x>a)", ParseError("unknown extension ?<x".into())),
1494            ("(?", ParseError("unexpected end of pattern".into())),
1495            (
1496                "(?#open",
1497                ParseError("missing ), unterminated comment".into()),
1498            ),
1499            (
1500                "(?(1)a|b|c)",
1501                ParseError("conditional backref with more than two branches".into()),
1502            ),
1503            (
1504                "(?P<>x)",
1505                ParseError("bad character in group name \"\"".into()),
1506            ),
1507            (
1508                "(?P<n>(?P=n)",
1509                ParseError("cannot refer to an open group".into()),
1510            ),
1511            ("(?<", ParseError("unexpected end of pattern".into())),
1512            (
1513                "(?(99999999999)a)",
1514                ParseError("invalid group reference 99999999999".into()),
1515            ),
1516            (
1517                "(?(1)a",
1518                ParseError("missing ), unterminated subpattern".into()),
1519            ),
1520        ];
1521        for (pattern, expected) in cases {
1522            assert_eq!(
1523                parse(pattern, Flags::default()).err(),
1524                Some(expected),
1525                "pattern {pattern:?}"
1526            );
1527        }
1528    }
1529
1530    #[test]
1531    fn group_name_validation_rejects_embedded_and_leading_bad_characters() {
1532        // A bad character inside the name trips the identifier scan...
1533        assert_eq!(
1534            parse(r"(?P<a-b>x)", Flags::default()).err(),
1535            Some(ParseError("bad character in group name \"a-b\"".into()))
1536        );
1537        // ...and a backreference through such a name fails the same way.
1538        assert_eq!(
1539            parse(r"(a)(?P=a-b)", Flags::default()).err(),
1540            Some(ParseError("bad character in group name \"a-b\"".into()))
1541        );
1542        // A conditional referencing a bad name fails before the lookup.
1543        assert_eq!(
1544            parse(r"(a)(?(a-b)x)", Flags::default()).err(),
1545            Some(ParseError("bad character in group name \"a-b\"".into()))
1546        );
1547    }
1548
1549    #[test]
1550    fn every_flag_turned_on_and_off_is_an_error() {
1551        for flags in ["i", "m", "s", "a"] {
1552            let pattern = format!("(?{flags}-{flags}:x)");
1553            assert_eq!(
1554                parse(&pattern, Flags::default()).err(),
1555                Some(ParseError(
1556                    "bad inline flags: flag turned on and off".into()
1557                )),
1558                "{pattern}"
1559            );
1560        }
1561    }
1562
1563    #[test]
1564    fn class_escape_error_arms_match_the_reference() {
1565        // A trailing backslash inside a class runs out of input.
1566        assert_eq!(
1567            parse(r"[a\", Flags::default()).err(),
1568            Some(ParseError("bad escape".into()))
1569        );
1570        // An over-wide \U escape is illegal inside a class.
1571        assert_eq!(
1572            parse(r"[\U00110000]", Flags::default()).err(),
1573            Some(ParseError(
1574                "illegal Unicode character in \\U00110000".into()
1575            ))
1576        );
1577        // Non-octal digits and unknown letters are bad escapes in a class.
1578        assert_eq!(
1579            parse(r"[\8]", Flags::default()).err(),
1580            Some(ParseError("bad escape \\8".into()))
1581        );
1582        assert_eq!(
1583            parse(r"[\q]", Flags::default()).err(),
1584            Some(ParseError("bad escape \\q".into()))
1585        );
1586    }
1587
1588    #[test]
1589    fn outside_class_escape_error_arms_match_the_reference() {
1590        // A trailing backslash runs out of input.
1591        assert_eq!(
1592            parse(r"\", Flags::default()).err(),
1593            Some(ParseError("bad escape".into()))
1594        );
1595        // A surrogate \\u escape is an illegal scalar; \\U has eight digits
1596        // so a value beyond the scalar maximum is expressible.
1597        assert_eq!(
1598            parse(r"\ud800", Flags::default()).err(),
1599            Some(ParseError("illegal Unicode character in \\ud800".into()))
1600        );
1601        assert_eq!(
1602            parse(r"\U00110000", Flags::default()).err(),
1603            Some(ParseError(
1604                "illegal Unicode character in \\U00110000".into()
1605            ))
1606        );
1607    }
1608
1609    #[test]
1610    fn class_range_error_arms_match_the_reference() {
1611        // A dangling range dash runs out of input.
1612        assert_eq!(
1613            parse(r"[a-", Flags::default()).err(),
1614            Some(ParseError("unterminated character set".into()))
1615        );
1616        // The range end may itself be an escape sequence.
1617        let (ops, _) = parse_ok(r"[a-\x7f]", Flags::default());
1618        assert_eq!(ops[0], Op::In(vec![ClassItem::Range(0x61, 0x7f)]));
1619        // ...and a bad escape in the range end propagates its error.
1620        assert_eq!(
1621            parse(r"[a-\q]", Flags::default()).err(),
1622            Some(ParseError("bad escape \\q".into()))
1623        );
1624    }
1625
1626    #[test]
1627    fn repeat_bound_errors_match_the_reference() {
1628        // A count past MAXREPEAT is rejected...
1629        assert_eq!(
1630            parse("a{4294967287}", Flags::default()).err(),
1631            Some(ParseError("the repetition number is too large".into()))
1632        );
1633        // ...as is one that does not even fit u64.
1634        assert_eq!(
1635            parse("a{99999999999999999999}", Flags::default()).err(),
1636            Some(ParseError("the repetition number is too large".into()))
1637        );
1638        // An open comma skips the high-bound parse, so the low bound is
1639        // validated on its own.
1640        assert_eq!(
1641            parse("a{4294967287,}", Flags::default()).err(),
1642            Some(ParseError("the repetition number is too large".into()))
1643        );
1644    }
1645
1646    #[test]
1647    fn high_from_maps_every_bound_shape() {
1648        assert_eq!(high_from("", None), Ok(None), "no comma and no low");
1649        assert_eq!(high_from("3", None), Ok(Some(3)), "no comma reuses the low");
1650        assert_eq!(
1651            high_from("", Some("")),
1652            Ok(None),
1653            "an open comma is unbounded"
1654        );
1655        assert_eq!(high_from("2", Some("5")), Ok(Some(5)));
1656        assert_eq!(
1657            high_from("9", Some("99999999999999999999")).err(),
1658            Some(ParseError("the repetition number is too large".into()))
1659        );
1660    }
1661
1662    #[test]
1663    fn named_reference_and_conditional_error_arms_match_the_reference() {
1664        // A (?P= backreference without its closing paren...
1665        assert_eq!(
1666            parse(r"(a)(?P=x", Flags::default()).err(),
1667            Some(ParseError("missing ), unterminated group name".into()))
1668        );
1669        // ...a lookahead whose body cannot parse...
1670        assert_eq!(
1671            parse(r"(?=[", Flags::default()).err(),
1672            Some(ParseError("unterminated character set".into()))
1673        );
1674        // ...a negative lookahead whose body never closes...
1675        assert_eq!(
1676            parse(r"(?!(", Flags::default()).err(),
1677            Some(ParseError("missing ), unterminated subpattern".into()))
1678        );
1679        // ...and the same for lookbehinds.
1680        assert_eq!(
1681            parse(r"(?<=(", Flags::default()).err(),
1682            Some(ParseError("missing ), unterminated subpattern".into()))
1683        );
1684    }
1685
1686    #[test]
1687    fn conditional_error_arms_match_the_reference() {
1688        // An unterminated condition name...
1689        assert_eq!(
1690            parse(r"(a)(?(x", Flags::default()).err(),
1691            Some(ParseError("missing ), unterminated group name".into()))
1692        );
1693        // ...a yes-branch that cannot parse...
1694        assert_eq!(
1695            parse(r"(a)(?(1)[", Flags::default()).err(),
1696            Some(ParseError("unterminated character set".into()))
1697        );
1698        // ...and a no-branch that cannot parse.
1699        assert_eq!(
1700            parse(r"(a)(?(1)b|(", Flags::default()).err(),
1701            Some(ParseError("missing ), unterminated subpattern".into()))
1702        );
1703    }
1704
1705    #[test]
1706    fn a_plain_group_body_that_cannot_parse_is_an_error() {
1707        assert_eq!(
1708            parse("([", Flags::default()).err(),
1709            Some(ParseError("unterminated character set".into()))
1710        );
1711        // A scoped-flag group body fails the same way.
1712        assert_eq!(
1713            parse("(?i:[", Flags::default()).err(),
1714            Some(ParseError("unterminated character set".into()))
1715        );
1716    }
1717
1718    #[test]
1719    fn possessive_quantifiers_are_rejected_at_the_reference_floor() {
1720        assert_eq!(
1721            parse("a*+", Flags::default()).err(),
1722            Some(ParseError("multiple repeat".into()))
1723        );
1724    }
1725
1726    #[test]
1727    fn atomic_groups_are_rejected_at_the_reference_floor() {
1728        assert_eq!(
1729            parse("(?>a)+", Flags::default()).err(),
1730            Some(ParseError("unknown extension ?>".into()))
1731        );
1732    }
1733
1734    #[test]
1735    fn an_open_group_reference_is_an_error() {
1736        assert_eq!(
1737            parse(r"(a\1)", Flags::default()).err(),
1738            Some(ParseError("cannot refer to an open group".into()))
1739        );
1740    }
1741
1742    #[test]
1743    fn two_digit_group_references_prefer_octal_when_it_fits() {
1744        // \101 is three octal digits (0o101 = 'A').
1745        let (ops, _) = parse_ok(r"\101", Flags::default());
1746        assert_eq!(ops, vec![Op::Literal(0o101)]);
1747    }
1748
1749    #[test]
1750    fn two_digit_reference_to_a_defined_group_parses() {
1751        // \12 with only two groups defined is an invalid reference; a
1752        // twelve-group pattern can address group 12 with two digits.
1753        assert_eq!(
1754            parse(r"()()\12", Flags::default()).err(),
1755            Some(ParseError("invalid group reference 12".into()))
1756        );
1757        let groups = "()".repeat(12);
1758        let (ops, _) = parse_ok(&format!("{groups}\\12"), Flags::default());
1759        assert_eq!(ops[12], Op::GroupRef(12));
1760    }
1761
1762    #[test]
1763    fn deep_nesting_is_an_error() {
1764        let pattern = format!("{}a{}", "(".repeat(300), ")".repeat(300));
1765        assert!(parse(&pattern, Flags::default()).is_err());
1766    }
1767
1768    #[test]
1769    fn scoped_flag_group_body_is_parsed() {
1770        let (ops, _) = parse_ok("(?i:a|b)", Flags::default());
1771        let (_, _, _, body) = subpattern_parts(ops.into_iter().next().expect("an op"));
1772        assert_eq!(body.len(), 1);
1773    }
1774
1775    #[test]
1776    fn conditional_without_else_uses_none() {
1777        let (ops, _) = parse_ok("(a)(?(1)b)", Flags::default());
1778        assert_eq!(
1779            ops[1],
1780            Op::GroupRefExists {
1781                group: 1,
1782                yes: vec![Op::Literal(u32::from('b'))],
1783                no: None,
1784            }
1785        );
1786    }
1787
1788    #[test]
1789    fn is_pairing_covers_the_reference_pairing_ops() {
1790        assert!(Op::Literal(97).is_pairing());
1791        assert!(Op::NotLiteral(97).is_pairing());
1792        assert!(Op::Any.is_pairing());
1793        assert!(Op::Category(Category::Digit).is_pairing());
1794        assert!(Op::In(vec![]).is_pairing());
1795        assert!(!Op::At(At::Boundary).is_pairing());
1796        assert!(!Op::Failure.is_pairing());
1797        assert!(!Op::GroupRef(1).is_pairing());
1798    }
1799
1800    #[test]
1801    fn apply_delta_delete_arms_clear_flags() {
1802        // parse_slots and repeat_prefix drive negated scoped-flag groups
1803        // (`(?-i:x)`) through this method with a non-empty `del`; the four
1804        // delete arms must clear the flags the walk started with.
1805        let mut flags = Flags {
1806            ignorecase: true,
1807            multiline: true,
1808            dotall: true,
1809            ascii: true,
1810        };
1811        let del = Flags {
1812            ignorecase: true,
1813            multiline: true,
1814            dotall: true,
1815            ascii: true,
1816        };
1817        flags.apply_delta(&Flags::default(), &del);
1818        assert_eq!(flags, Flags::default());
1819    }
1820
1821    #[test]
1822    fn negated_category_escapes_inside_a_class() {
1823        let (ops, _) = parse_ok(r"[\D\S\W]", Flags::default());
1824        assert_eq!(
1825            ops,
1826            vec![Op::In(vec![
1827                ClassItem::Category(Category::NotDigit),
1828                ClassItem::Category(Category::NotSpace),
1829                ClassItem::Category(Category::NotWord),
1830            ])]
1831        );
1832    }
1833
1834    #[test]
1835    fn parse_error_display_shows_the_message() {
1836        let err = parse("(", Flags::default()).expect_err("pattern must fail");
1837        assert_eq!(err.to_string(), "missing ), unterminated subpattern");
1838    }
1839
1840    #[test]
1841    fn verbose_and_unicode_flags_parse_as_noops() {
1842        // The reference accepts 'x' and 'u' inline flags; neither changes
1843        // the op tree or the effective flags for str patterns.
1844        let (ops, flags) = parse_ok("(?ux)a", Flags::default());
1845        assert_eq!(ops, vec![Op::Literal(u32::from('a'))]);
1846        assert_eq!(flags, Flags::default());
1847    }
1848
1849    #[test]
1850    fn flag_parser_rejects_junk_after_valid_flags() {
1851        assert_eq!(
1852            parse("(?i", Flags::default()).err(),
1853            Some(ParseError("missing -, : or )".into()))
1854        );
1855        assert_eq!(
1856            parse("(?iz", Flags::default()).err(),
1857            Some(ParseError("unknown flag".into()))
1858        );
1859        assert_eq!(
1860            parse("(?i1", Flags::default()).err(),
1861            Some(ParseError("missing -, : or )".into()))
1862        );
1863    }
1864
1865    #[test]
1866    fn class_control_escapes_are_literals() {
1867        let (ops, _) = parse_ok(r"[\a\f\n\r\t\v]", Flags::default());
1868        assert_eq!(
1869            ops,
1870            vec![Op::In(vec![
1871                ClassItem::Literal(0x07),
1872                ClassItem::Literal(0x0C),
1873                ClassItem::Literal(0x0A),
1874                ClassItem::Literal(0x0D),
1875                ClassItem::Literal(0x09),
1876                ClassItem::Literal(0x0B),
1877            ])]
1878        );
1879    }
1880
1881    #[test]
1882    fn class_unicode_escape_edges() {
1883        let (ops, _) = parse_ok(r"[\u00e9\U0001F600]", Flags::default());
1884        assert_eq!(
1885            ops,
1886            vec![Op::In(vec![
1887                ClassItem::Literal(0xE9),
1888                ClassItem::Literal(0x1F600),
1889            ])]
1890        );
1891        assert_eq!(
1892            parse(r"[\x1]", Flags::default()).err(),
1893            Some(ParseError("incomplete escape \\x1".into()))
1894        );
1895        assert_eq!(
1896            parse(r"[\u12]", Flags::default()).err(),
1897            Some(ParseError("incomplete escape \\u12".into()))
1898        );
1899        assert_eq!(
1900            parse(r"[\U0001]", Flags::default()).err(),
1901            Some(ParseError("incomplete escape \\U0001".into()))
1902        );
1903        assert_eq!(
1904            parse(r"[\ud800]", Flags::default()).err(),
1905            Some(ParseError("illegal Unicode character in \\ud800".into()))
1906        );
1907        assert_eq!(
1908            parse(r"[\N]", Flags::default()).err(),
1909            Some(ParseError(
1910                "bad escape \\N: named escapes are not supported by the safety chain".into()
1911            ))
1912        );
1913    }
1914
1915    #[test]
1916    fn class_octal_escape_edges() {
1917        let (ops, _) = parse_ok(r"[\101\0]", Flags::default());
1918        assert_eq!(
1919            ops,
1920            vec![Op::In(vec![
1921                ClassItem::Literal(0o101),
1922                ClassItem::Literal(0),
1923            ])]
1924        );
1925        assert_eq!(
1926            parse(r"[\777]", Flags::default()).err(),
1927            Some(ParseError(
1928                "octal escape value \\777 outside of range 0-0o377".into()
1929            ))
1930        );
1931    }
1932
1933    #[test]
1934    fn outside_class_escape_edges() {
1935        assert_eq!(
1936            parse(r"\U0001", Flags::default()).err(),
1937            Some(ParseError("incomplete escape \\U0001".into()))
1938        );
1939        assert_eq!(
1940            parse(r"\777", Flags::default()).err(),
1941            Some(ParseError(
1942                "octal escape value \\777 outside of range 0-0o377".into()
1943            ))
1944        );
1945    }
1946
1947    #[test]
1948    fn empty_brace_pair_is_a_literal_brace() {
1949        let (ops, _) = parse_ok("a{}", Flags::default());
1950        assert_eq!(
1951            ops,
1952            vec![
1953                Op::Literal(u32::from('a')),
1954                Op::Literal(u32::from('{')),
1955                Op::Literal(u32::from('}')),
1956            ]
1957        );
1958    }
1959
1960    #[test]
1961    #[should_panic(expected = "expected a subpattern")]
1962    fn subpattern_parts_rejects_other_variants() {
1963        let _ = subpattern_parts(Op::Any);
1964    }
1965}