1use crate::ast::*;
12use crate::dialect::Dialect;
13use crate::error::{ErrorCode, RexactError};
14use crate::flags::Flags;
15use crate::locate::locate;
16use rexact_unicode as uni;
17
18#[derive(Debug, Clone)]
20pub struct ParseLimits {
21 pub max_length: u64,
23 pub max_depth: u32,
25}
26
27impl Default for ParseLimits {
28 fn default() -> Self {
29 Self {
30 max_length: 1_000_000,
31 max_depth: 100,
32 }
33 }
34}
35
36#[derive(Debug, Clone)]
38pub struct Pattern {
39 pub source: String,
41 pub dialect: Dialect,
43 pub flags: Flags,
45 pub ast: Node,
47 pub group_count: u32,
49 pub group_names: Vec<String>,
51 pub notes: Vec<String>,
53}
54
55impl Pattern {
56 pub fn parse(source: &str, dialect: Dialect, flags: Flags) -> Result<Pattern, RexactError> {
58 Self::parse_with(source, dialect, flags, &ParseLimits::default())
59 }
60
61 pub fn parse_with(
63 source: &str,
64 dialect: Dialect,
65 flags: Flags,
66 limits: &ParseLimits,
67 ) -> Result<Pattern, RexactError> {
68 let chars: Vec<char> = source.chars().collect();
69 let fail = |e: PErr| {
70 let mut r = RexactError::new(e.code);
71 r.detail = e.detail;
72 r.position = Some(locate(&chars, e.offset));
73 r
74 };
75 if chars.len() as u64 > limits.max_length {
76 return Err(fail(PErr::new(
77 ErrorCode::L005,
78 0,
79 "pattern exceeds the length limit",
80 )));
81 }
82 if matches!(dialect, Dialect::Go | Dialect::Rust) {
83 let out = crate::parser_re2::parse(&chars, dialect, flags, limits).map_err(fail)?;
84 return Ok(Pattern {
85 source: source.to_string(),
86 dialect,
87 flags: out.flags,
88 ast: out.ast,
89 group_count: out.groups,
90 group_names: out.names,
91 notes: out.notes,
92 });
93 }
94 if dialect == Dialect::Java {
95 crate::java::check(&chars).map_err(fail)?;
97 let out = crate::parser_pcre::parse(&chars, flags, limits).map_err(fail)?;
98 return Ok(Pattern {
99 source: source.to_string(),
100 dialect,
101 flags: out.flags,
102 ast: out.ast,
103 group_count: out.groups,
104 group_names: out.names,
105 notes: Vec::new(),
106 });
107 }
108 if dialect == Dialect::Pcre2 {
109 let byte_chars: Vec<char>;
111 let input: &[char] = if flags.u {
112 &chars
113 } else {
114 byte_chars = source.bytes().map(char::from).collect();
115 &byte_chars
116 };
117 let out = crate::parser_pcre::parse(input, flags, limits).map_err(fail)?;
118 return Ok(Pattern {
119 source: source.to_string(),
120 dialect,
121 flags: out.flags,
122 ast: out.ast,
123 group_count: out.groups,
124 group_names: out.names,
125 notes: Vec::new(),
126 });
127 }
128 let (groups, has_named) = scan_groups(&chars, dialect, flags.x);
129 let mut p = Parser {
130 chars: &chars,
131 pos: 0,
132 d: dialect,
133 flags,
134 max_depth: limits.max_depth,
135 depth: 0,
136 total_groups: groups,
137 has_named,
138 next_capture: 1,
139 names: Vec::new(),
140 seen_content: false,
141 };
142 let ast = p.alt().map_err(fail)?;
143 if p.pos < chars.len() {
144 return Err(fail(PErr::new(ErrorCode::P007, p.pos, "unmatched ')'")));
145 }
146 Ok(Pattern {
147 source: source.to_string(),
148 dialect,
149 flags: p.flags,
150 ast,
151 group_count: p.next_capture - 1,
152 group_names: p.names,
153 notes: Vec::new(),
154 })
155 }
156
157 pub fn parse_literal(text: &str, dialect: Dialect) -> Result<Pattern, RexactError> {
160 let shape = |why: &str| {
161 let mut e = RexactError::new(ErrorCode::P010);
162 e.detail = Some(format!("{why}; expected /body/flags"));
163 e
164 };
165 let rest = text
166 .strip_prefix('/')
167 .ok_or_else(|| shape("missing opening '/'"))?;
168 let close = rest
169 .rfind('/')
170 .ok_or_else(|| shape("missing closing '/'"))?;
171 let (body, flag_text) = (&rest[..close], &rest[close + 1..]);
172 if body.is_empty() {
173 return Err(shape("empty body (// is a comment, not a regex)"));
174 }
175 if !flag_text.chars().all(|c| c.is_ascii_alphabetic()) {
176 return Err(shape("flags must be letters"));
177 }
178 let (flags, notes) = Flags::from_letters(dialect, flag_text)?;
179 let mut p = Pattern::parse(body, dialect, flags)?;
180 p.notes = notes;
181 Ok(p)
182 }
183}
184
185pub(crate) struct PErr {
186 pub(crate) code: ErrorCode,
187 pub(crate) offset: usize,
188 pub(crate) detail: Option<String>,
189}
190
191impl PErr {
192 pub(crate) fn new(code: ErrorCode, offset: usize, detail: &str) -> Self {
193 Self {
194 code,
195 offset,
196 detail: Some(detail.to_string()),
197 }
198 }
199}
200
201pub(crate) type R<T> = Result<T, PErr>;
202
203pub(crate) fn err<T>(code: ErrorCode, offset: usize, detail: &str) -> R<T> {
204 Err(PErr::new(code, offset, detail))
205}
206
207fn scan_groups(chars: &[char], d: Dialect, verbose: bool) -> (u32, bool) {
210 let (mut count, mut named) = (0u32, false);
211 let (mut i, mut in_class, mut class_start) = (0usize, false, 0usize);
212 while i < chars.len() {
213 let c = chars[i];
214 if c == '\\' {
215 i += 2;
216 continue;
217 }
218 if in_class {
219 if c == ']' && !(d == Dialect::Python && i == class_start) {
221 in_class = false;
222 }
223 } else if c == '[' {
224 in_class = true;
225 class_start = i + 1;
226 if chars.get(class_start) == Some(&'^') {
227 class_start += 1;
228 }
229 } else if c == '#' && verbose && d == Dialect::Python {
230 while i < chars.len() && chars[i] != '\n' {
231 i += 1;
232 }
233 continue;
234 } else if c == '(' {
235 if chars.get(i + 1) != Some(&'?') {
236 count += 1;
237 } else {
238 let is_named = match d {
239 Dialect::Python => {
240 chars.get(i + 2) == Some(&'P') && chars.get(i + 3) == Some(&'<')
241 }
242 _ => {
243 chars.get(i + 2) == Some(&'<')
244 && !matches!(chars.get(i + 3), Some('=') | Some('!'))
245 }
246 };
247 if is_named {
248 count += 1;
249 named = true;
250 }
251 }
252 }
253 i += 1;
254 }
255 (count, named)
256}
257
258enum Esc {
259 Char(u32),
260 Short(Shorthand),
261 Prop(PropertyRef),
262 Assert(Assertion),
263}
264
265enum ClassAtom {
266 Ch(u32),
267 Sh(Shorthand),
268 Prop(PropertyRef),
269}
270
271impl ClassAtom {
272 fn item(self) -> ClassItem {
273 match self {
274 ClassAtom::Ch(c) => ClassItem::Range(c, c),
275 ClassAtom::Sh(s) => ClassItem::Shorthand(s),
276 ClassAtom::Prop(p) => ClassItem::Property(p),
277 }
278 }
279}
280
281struct Parser<'a> {
282 chars: &'a [char],
283 pos: usize,
284 d: Dialect,
285 flags: Flags,
286 max_depth: u32,
287 depth: u32,
288 total_groups: u32,
289 has_named: bool,
290 next_capture: u32,
291 names: Vec<String>,
292 seen_content: bool,
294}
295
296fn mk(kind: NodeKind, start: usize, end: usize) -> Node {
297 Node {
298 kind,
299 span: Span { start, end },
300 }
301}
302
303fn shorthand(c: char) -> Option<Shorthand> {
304 Some(match c {
305 'd' => Shorthand::Digit,
306 'D' => Shorthand::NotDigit,
307 'w' => Shorthand::Word,
308 'W' => Shorthand::NotWord,
309 's' => Shorthand::Space,
310 'S' => Shorthand::NotSpace,
311 _ => return None,
312 })
313}
314
315impl Parser<'_> {
316 fn peek(&self) -> Option<char> {
317 self.chars.get(self.pos).copied()
318 }
319 fn peek_at(&self, n: usize) -> Option<char> {
320 self.chars.get(self.pos + n).copied()
321 }
322 fn eat(&mut self, c: char) -> bool {
323 if self.peek() == Some(c) {
324 self.pos += 1;
325 true
326 } else {
327 false
328 }
329 }
330 fn is_py(&self) -> bool {
331 self.d == Dialect::Python
332 }
333 fn es_unicode(&self) -> bool {
335 self.d == Dialect::Ecma && self.flags.u
336 }
337
338 fn skip_x(&mut self) {
340 if !(self.is_py() && self.flags.x) {
341 return;
342 }
343 while let Some(c) = self.peek() {
344 if matches!(c, ' ' | '\t' | '\n' | '\r' | '\x0b' | '\x0c') {
345 self.pos += 1;
346 } else if c == '#' {
347 while let Some(c) = self.peek() {
348 if c == '\n' {
349 break;
350 }
351 self.pos += 1;
352 }
353 } else {
354 break;
355 }
356 }
357 }
358
359 fn alt(&mut self) -> R<Node> {
360 self.depth += 1;
361 if self.depth > self.max_depth {
362 return err(
363 ErrorCode::L006,
364 self.pos,
365 "group nesting exceeds the depth limit",
366 );
367 }
368 let start = self.pos;
369 let mut branches = vec![self.concat()?];
370 while self.eat('|') {
371 self.seen_content = true;
372 branches.push(self.concat()?);
373 }
374 self.depth -= 1;
375 Ok(match branches.len() {
376 1 => branches.remove(0),
377 _ => mk(NodeKind::Alt(branches), start, self.pos),
378 })
379 }
380
381 fn concat(&mut self) -> R<Node> {
382 let start = self.pos;
383 let mut items = Vec::new();
384 loop {
385 self.skip_x();
386 if matches!(self.peek(), None | Some('|') | Some(')')) {
387 break;
388 }
389 if let Some(n) = self.term()? {
390 self.seen_content = true;
391 items.push(n);
392 }
393 }
394 Ok(match items.len() {
395 0 => mk(NodeKind::Empty, start, self.pos),
396 1 => items.remove(0),
397 _ => mk(NodeKind::Concat(items), start, self.pos),
398 })
399 }
400
401 fn term(&mut self) -> R<Option<Node>> {
402 let start = self.pos;
403 let (atom, quantifiable) = self.atom()?;
404 self.skip_x();
405 if !quantifiable || atom.is_none() {
406 if self.quantifier_ahead()? {
407 return err(ErrorCode::P005, self.pos, "nothing to repeat");
408 }
409 return Ok(atom);
410 }
411 let Some(atom) = atom else { return Ok(None) };
412 let Some((min, max)) = self.take_quantifier()? else {
413 return Ok(Some(atom));
414 };
415 let mut greedy = true;
416 if self.eat('?') {
417 greedy = false;
418 } else if self.is_py() && self.peek() == Some('+') {
419 return err(
420 ErrorCode::U004,
421 self.pos,
422 "possessive quantifier changes matching semantics",
423 );
424 }
425 if self.quantifier_ahead()? {
426 return err(ErrorCode::P005, self.pos, "multiple repeat");
427 }
428 Ok(Some(mk(
429 NodeKind::Repeat {
430 node: Box::new(atom),
431 min,
432 max,
433 greedy,
434 },
435 start,
436 self.pos,
437 )))
438 }
439
440 fn quantifier_ahead(&self) -> R<bool> {
441 Ok(match self.peek() {
442 Some('*') | Some('+') | Some('?') => true,
443 Some('{') => self.braces()?.is_some(),
444 _ => false,
445 })
446 }
447
448 fn digits(&self, mut i: usize) -> (Option<u64>, usize) {
449 let mut v: Option<u64> = None;
450 while let Some(d) = self.chars.get(i).and_then(|c| c.to_digit(10)) {
451 v = Some(
452 v.unwrap_or(0)
453 .saturating_mul(10)
454 .saturating_add(u64::from(d)),
455 );
456 i += 1;
457 }
458 (v, i)
459 }
460
461 fn braces(&self) -> R<Option<(u64, Option<u64>, usize)>> {
464 let (lo, i) = self.digits(self.pos + 1);
465 match self.chars.get(i) {
466 Some('}') => Ok(lo.map(|l| (l, Some(l), i + 1))),
467 Some(',') => {
468 let (hi, j) = self.digits(i + 1);
469 if self.chars.get(j) != Some(&'}') {
470 return Ok(None);
471 }
472 Ok(match (lo, hi) {
473 (None, _) if !self.is_py() => None,
474 (l, h) => Some((l.unwrap_or(0), h, j + 1)),
475 })
476 }
477 _ => Ok(None),
478 }
479 }
480
481 fn take_quantifier(&mut self) -> R<Option<(u64, Option<u64>)>> {
482 let start = self.pos;
483 let q = match self.peek() {
484 Some('*') => {
485 self.pos += 1;
486 (0, None)
487 }
488 Some('+') => {
489 self.pos += 1;
490 (1, None)
491 }
492 Some('?') => {
493 self.pos += 1;
494 (0, Some(1))
495 }
496 Some('{') => match self.braces()? {
497 Some((lo, hi, end)) => {
498 self.pos = end;
499 if self.is_py()
500 && (lo >= u64::from(u32::MAX)
501 || hi.is_some_and(|h| h >= u64::from(u32::MAX)))
502 {
503 return err(ErrorCode::P005, start, "repetition count out of range");
504 }
505 if hi.is_some_and(|h| lo > h) {
506 return err(
507 ErrorCode::P005,
508 start,
509 "minimum repeat is greater than maximum",
510 );
511 }
512 (lo, hi)
513 }
514 None => return Ok(None),
515 },
516 _ => return Ok(None),
517 };
518 Ok(Some(q))
519 }
520
521 fn atom(&mut self) -> R<(Option<Node>, bool)> {
524 let start = self.pos;
525 let Some(c) = self.peek() else {
526 return Ok((None, false));
527 };
528 match c {
529 '(' => self.group(),
530 '[' => Ok((Some(self.class()?), true)),
531 '.' => {
532 self.pos += 1;
533 Ok((Some(mk(NodeKind::Any, start, self.pos)), true))
534 }
535 '^' | '$' => {
536 self.pos += 1;
537 let a = if c == '^' {
538 Assertion::LineStart
539 } else {
540 Assertion::LineEnd
541 };
542 Ok((Some(mk(NodeKind::Assert(a), start, self.pos)), false))
543 }
544 '\\' => {
545 let esc = self.escape(false)?;
546 let (kind, q) = match esc {
547 Esc::Char(c) => (NodeKind::Literal(c), true),
548 Esc::Short(s) => (
549 NodeKind::Class(Class {
550 negated: false,
551 items: vec![ClassItem::Shorthand(s)],
552 }),
553 true,
554 ),
555 Esc::Prop(p) => (
556 NodeKind::Class(Class {
557 negated: false,
558 items: vec![ClassItem::Property(p)],
559 }),
560 true,
561 ),
562 Esc::Assert(a) => (NodeKind::Assert(a), false),
563 };
564 Ok((Some(mk(kind, start, self.pos)), q))
565 }
566 '*' | '+' | '?' => err(ErrorCode::P005, start, "nothing to repeat"),
567 '{' => {
568 if self.braces()?.is_some() {
569 return err(ErrorCode::P005, start, "nothing to repeat");
570 }
571 if self.es_unicode() {
572 return err(ErrorCode::P001, start, "lone '{' in Unicode mode");
573 }
574 self.pos += 1;
575 Ok((
576 Some(mk(NodeKind::Literal('{' as u32), start, self.pos)),
577 true,
578 ))
579 }
580 '}' | ']' if self.es_unicode() => err(
581 ErrorCode::P001,
582 start,
583 "lone bracket in Unicode mode; escape it",
584 ),
585 _ => {
586 self.pos += 1;
587 Ok((Some(mk(NodeKind::Literal(c as u32), start, self.pos)), true))
588 }
589 }
590 }
591
592 fn group_body(&mut self, open: usize) -> R<Node> {
595 let n = self.alt()?;
596 if !self.eat(')') {
597 return err(ErrorCode::P007, open, "unclosed group");
598 }
599 Ok(n)
600 }
601
602 fn group(&mut self) -> R<(Option<Node>, bool)> {
603 let open = self.pos;
604 self.pos += 1;
605 if self.eat('?') {
606 return self.special_group(open);
607 }
608 let idx = self.next_capture;
609 self.next_capture += 1;
610 let body = self.group_body(open)?;
611 let kind = NodeKind::Group {
612 capture: Some(idx),
613 name: None,
614 node: Box::new(body),
615 };
616 Ok((Some(mk(kind, open, self.pos)), true))
617 }
618
619 fn plain_group(
620 &mut self,
621 open: usize,
622 capture: Option<u32>,
623 name: Option<String>,
624 ) -> R<(Option<Node>, bool)> {
625 let body = self.group_body(open)?;
626 let kind = NodeKind::Group {
627 capture,
628 name,
629 node: Box::new(body),
630 };
631 Ok((Some(mk(kind, open, self.pos)), true))
632 }
633
634 fn look(&mut self, open: usize, ahead: bool, negate: bool) -> R<(Option<Node>, bool)> {
635 let body = self.group_body(open)?;
636 let kind = NodeKind::Look {
637 ahead,
638 negate,
639 node: Box::new(body),
640 };
641 let q = !(self.d == Dialect::Ecma && (!ahead || self.flags.u));
643 Ok((Some(mk(kind, open, self.pos)), q))
644 }
645
646 fn named(&mut self, open: usize) -> R<(Option<Node>, bool)> {
647 let name_start = self.pos;
648 let mut name = String::new();
649 loop {
650 match self.peek() {
651 Some('>') => break,
652 Some(c) => {
653 name.push(c);
654 self.pos += 1;
655 }
656 None => return err(ErrorCode::P009, name_start, "unterminated group name"),
657 }
658 }
659 let ok_first = |c: char| c.is_alphabetic() || c == '_' || (!self.is_py() && c == '$');
660 let ok_rest = |c: char| c.is_alphanumeric() || c == '_' || (!self.is_py() && c == '$');
661 let valid = name.chars().next().is_some_and(ok_first) && name.chars().all(ok_rest);
662 if !valid {
663 return err(ErrorCode::P009, name_start, "invalid group name");
664 }
665 if self.names.contains(&name) {
666 return err(ErrorCode::P009, name_start, "duplicate group name");
667 }
668 self.pos += 1; let idx = self.next_capture;
670 self.next_capture += 1;
671 self.names.push(name.clone());
672 self.plain_group(open, Some(idx), Some(name))
673 }
674
675 fn special_group(&mut self, open: usize) -> R<(Option<Node>, bool)> {
676 let Some(c) = self.peek() else {
677 return err(ErrorCode::P007, open, "unclosed group");
678 };
679 self.pos += 1;
680 match (self.d, c) {
681 (_, ':') => self.plain_group(open, None, None),
682 (_, '=') => self.look(open, true, false),
683 (_, '!') => self.look(open, true, true),
684 (_, '<') => match self.peek() {
685 Some('=') => {
686 self.pos += 1;
687 self.look(open, false, false)
688 }
689 Some('!') => {
690 self.pos += 1;
691 self.look(open, false, true)
692 }
693 _ if self.d == Dialect::Ecma => self.named(open),
694 _ => err(
695 ErrorCode::P001,
696 open,
697 "unknown group extension '(?<'; Python names use (?P<name>...)",
698 ),
699 },
700 (Dialect::Python, 'P') => match self.peek() {
701 Some('<') => {
702 self.pos += 1;
703 self.named(open)
704 }
705 Some('=') => err(ErrorCode::U001, open, "named backreference (?P=name)"),
706 _ => err(ErrorCode::P001, open, "unknown group extension '(?P'"),
707 },
708 (Dialect::Python, '#') => {
709 while let Some(c) = self.peek() {
710 self.pos += 1;
711 if c == ')' {
712 return Ok((None, false));
713 }
714 }
715 err(ErrorCode::P007, open, "unterminated comment")
716 }
717 (Dialect::Python, '>') => err(
718 ErrorCode::U004,
719 open,
720 "atomic group changes matching semantics",
721 ),
722 (Dialect::Python, '(') => err(ErrorCode::U003, open, "conditional group"),
723 (Dialect::Python, _) => {
724 self.pos -= 1;
725 self.py_flags(open)
726 }
727 _ => err(
728 ErrorCode::P001,
729 open,
730 "unrecognised group syntax after '(?'",
731 ),
732 }
733 }
734
735 fn py_flags(&mut self, open: usize) -> R<(Option<Node>, bool)> {
737 let (mut on, mut off) = (Flags::none(), Flags::none());
738 let mut turning_off = false;
739 let mut any = false;
740 loop {
741 let Some(c) = self.peek() else {
742 return err(ErrorCode::P007, open, "unclosed group");
743 };
744 match c {
745 '-' if !turning_off => {
746 turning_off = true;
747 any = false;
748 self.pos += 1;
749 continue;
750 }
751 'a' | 'i' | 'm' | 's' | 'u' | 'x' => {
752 if turning_off && matches!(c, 'a' | 'u') {
753 return err(
754 ErrorCode::P003,
755 self.pos,
756 "flags 'a' and 'u' cannot be turned off",
757 );
758 }
759 let target = if turning_off { &mut off } else { &mut on };
760 match c {
761 'a' => target.a = true,
762 'i' => target.i = true,
763 'm' => target.m = true,
764 's' => target.s = true,
765 'u' => target.u = true,
766 _ => target.x = true,
767 }
768 }
769 'L' => {
770 return err(
771 ErrorCode::P003,
772 self.pos,
773 "flag 'L' cannot be used with str patterns",
774 )
775 }
776 ')' | ':' => break,
777 _ => return err(ErrorCode::P001, open, "unknown flag in inline group"),
778 }
779 any = true;
780 self.pos += 1;
781 }
782 if turning_off && !any {
783 return err(ErrorCode::P001, self.pos, "missing flag after '-'");
784 }
785 if !turning_off && !any {
786 return err(ErrorCode::P001, open, "missing inline flags");
787 }
788 if on.a && on.u {
789 return err(ErrorCode::P003, open, "flags 'a' and 'u' are incompatible");
790 }
791 if self.eat(')') {
792 if turning_off {
793 return err(ErrorCode::P001, open, "global flags cannot be turned off");
794 }
795 if self.seen_content || self.depth > 1 {
796 return err(
797 ErrorCode::P001,
798 open,
799 "global flags not at the start of the expression",
800 );
801 }
802 self.flags = self.flags.union(on);
803 return Ok((None, false));
804 }
805 self.pos += 1; let saved = self.flags;
807 self.flags = saved.union(on).minus(off);
808 let body = self.group_body(open);
809 self.flags = saved;
810 let kind = NodeKind::FlagGroup {
811 delta: FlagDelta { on, off },
812 node: Box::new(body?),
813 };
814 Ok((Some(mk(kind, open, self.pos)), true))
815 }
816
817 fn class_atom(&mut self) -> R<ClassAtom> {
820 match self.peek() {
821 Some('\\') => Ok(match self.escape(true)? {
822 Esc::Char(c) => ClassAtom::Ch(c),
823 Esc::Short(s) => ClassAtom::Sh(s),
824 Esc::Prop(p) => ClassAtom::Prop(p),
825 Esc::Assert(_) => return err(ErrorCode::X001, self.pos, "assertion inside class"),
827 }),
828 Some(c) => {
829 self.pos += 1;
830 Ok(ClassAtom::Ch(c as u32))
831 }
832 None => err(ErrorCode::P007, self.pos, "unterminated character class"),
833 }
834 }
835
836 fn class(&mut self) -> R<Node> {
837 let open = self.pos;
838 self.pos += 1;
839 let negated = self.eat('^');
840 let mut items = Vec::new();
841 let mut first = true;
842 loop {
843 let Some(c) = self.peek() else {
844 return err(ErrorCode::P007, open, "unterminated character class");
845 };
846 if c == ']' && !(first && self.is_py()) {
847 self.pos += 1;
848 break;
849 }
850 first = false;
851 let a = self.class_atom()?;
852 if self.peek() == Some('-') && self.peek_at(1).is_some_and(|n| n != ']') {
853 let dash = self.pos;
854 self.pos += 1;
855 let b = self.class_atom()?;
856 match (a, b) {
857 (ClassAtom::Ch(x), ClassAtom::Ch(y)) => {
858 if x > y {
859 return err(ErrorCode::P006, dash, "range end precedes range start");
860 }
861 items.push(ClassItem::Range(x, y));
862 }
863 (a, b) => {
864 if self.d != Dialect::Ecma || self.flags.u {
866 return err(
867 ErrorCode::P006,
868 dash,
869 "a class escape cannot be a range endpoint",
870 );
871 }
872 for x in [a, ClassAtom::Ch('-' as u32), b] {
873 items.push(x.item());
874 }
875 }
876 }
877 } else {
878 items.push(a.item());
879 }
880 }
881 Ok(mk(
882 NodeKind::Class(Class { negated, items }),
883 open,
884 self.pos,
885 ))
886 }
887
888 fn hex(&mut self, n: usize) -> Option<u32> {
891 let mut v = 0u32;
892 for k in 0..n {
893 v = v * 16 + self.peek_at(k)?.to_digit(16)?;
894 }
895 self.pos += n;
896 Some(v)
897 }
898
899 fn octal(&mut self, first: u32, max_extra: usize) -> u32 {
900 let mut v = first;
901 for _ in 0..max_extra {
902 match self.peek().and_then(|c| c.to_digit(8)) {
903 Some(d) => {
904 v = v * 8 + d;
905 self.pos += 1;
906 }
907 None => break,
908 }
909 }
910 v
911 }
912
913 fn escape(&mut self, in_class: bool) -> R<Esc> {
914 let bs = self.pos;
915 self.pos += 1;
916 let Some(c) = self.peek() else {
917 return err(ErrorCode::P004, bs, "pattern ends with a backslash");
918 };
919 self.pos += 1;
920 if let Some(s) = shorthand(c) {
921 return Ok(Esc::Short(s));
922 }
923 match self.d {
924 Dialect::Ecma => self.esc_ecma(c, bs, in_class),
925 _ => self.esc_py(c, bs, in_class),
926 }
927 }
928
929 fn esc_ecma(&mut self, c: char, bs: usize, in_class: bool) -> R<Esc> {
930 let u = self.flags.u;
931 Ok(Esc::Char(match c {
932 'b' if in_class => 8,
933 'b' => return Ok(Esc::Assert(Assertion::WordBoundary)),
934 'B' if in_class && u => {
935 return err(ErrorCode::P004, bs, "\\B is not allowed in a class")
936 }
937 'B' if in_class => 'B' as u32,
938 'B' => return Ok(Esc::Assert(Assertion::NotWordBoundary)),
939 'f' => 12,
940 'n' => 10,
941 'r' => 13,
942 't' => 9,
943 'v' => 11,
944 '0' if !self.peek().is_some_and(|n| n.is_ascii_digit()) => 0,
945 '0'..='9' => return self.es_digits(c, bs, in_class),
946 'k' => {
947 if in_class && !u {
948 'k' as u32
949 } else if u || self.has_named {
950 if self.peek() == Some('<') && !in_class {
951 return err(ErrorCode::U001, bs, "named backreference \\k<name>");
952 }
953 return err(ErrorCode::P004, bs, "invalid named reference");
954 } else {
955 'k' as u32
956 }
957 }
958 'c' => match self.peek() {
959 Some(l) if l.is_ascii_alphabetic() => {
960 self.pos += 1;
961 l as u32 % 32
962 }
963 _ if u => return err(ErrorCode::P004, bs, "invalid control escape"),
964 _ => {
965 self.pos -= 1;
967 '\\' as u32
968 }
969 },
970 'x' => match self.hex(2) {
971 Some(v) => v,
972 None if u => return err(ErrorCode::P004, bs, "\\x needs two hex digits"),
973 None => 'x' as u32,
974 },
975 'u' => return self.es_unicode_escape(bs),
976 'p' | 'P' if u => return self.es_property(c == 'P', bs),
977 '-' if in_class && u => '-' as u32,
978 _ if u => {
979 if "^$\\.*+?()[]{}|/".contains(c) {
980 c as u32
981 } else {
982 return err(
983 ErrorCode::P004,
984 bs,
985 "invalid identity escape in Unicode mode",
986 );
987 }
988 }
989 _ => c as u32,
990 }))
991 }
992
993 fn es_property(&mut self, negated: bool, bs: usize) -> R<Esc> {
996 if self.peek() != Some('{') {
997 return err(
998 ErrorCode::P004,
999 bs,
1000 "\\p must be followed by {Name} in Unicode mode",
1001 );
1002 }
1003 let start = self.pos + 1;
1004 let mut i = start;
1005 while self
1006 .chars
1007 .get(i)
1008 .is_some_and(|c| c.is_ascii_alphanumeric() || *c == '_' || *c == '=')
1009 {
1010 i += 1;
1011 }
1012 if self.chars.get(i) != Some(&'}') {
1013 return err(ErrorCode::P004, bs, "malformed \\p{...} escape");
1014 }
1015 let body: String = self.chars[start..i].iter().collect();
1016 self.pos = i + 1;
1017 let unknown = |what: &str| {
1018 let hint = uni::suggest(what);
1019 let tail = if hint.is_empty() {
1020 String::new()
1021 } else {
1022 format!("; did you mean: {}", hint.join(", "))
1023 };
1024 PErr::new(
1025 ErrorCode::P008,
1026 bs,
1027 &format!("unknown Unicode property or value '{what}'{tail}"),
1028 )
1029 };
1030 let (kind, name) = match body.split_once('=') {
1031 None => match uni::gc_canonical(&body) {
1032 Some(n) => (PropKind::GeneralCategory, n),
1033 None => match uni::binary_canonical(&body) {
1034 Some(n) => (PropKind::Binary, n),
1035 None => return Err(unknown(&body)),
1036 },
1037 },
1038 Some(("General_Category" | "gc", v)) => (
1039 PropKind::GeneralCategory,
1040 uni::gc_canonical(v).ok_or_else(|| unknown(v))?,
1041 ),
1042 Some(("Script" | "sc", v)) => (
1043 PropKind::Script,
1044 uni::script_canonical(v).ok_or_else(|| unknown(v))?,
1045 ),
1046 Some(("Script_Extensions" | "scx", v)) => (
1047 PropKind::ScriptExtensions,
1048 uni::script_canonical(v).ok_or_else(|| unknown(v))?,
1049 ),
1050 Some((n, _)) => return Err(unknown(n)),
1051 };
1052 Ok(Esc::Prop(PropertyRef {
1053 negated,
1054 kind,
1055 name,
1056 exact: false,
1057 }))
1058 }
1059
1060 fn es_digits(&mut self, c: char, bs: usize, in_class: bool) -> R<Esc> {
1061 let u = self.flags.u;
1062 let first = c.to_digit(10).unwrap_or(0);
1063 if u {
1064 if c == '0' || in_class {
1065 return err(
1066 ErrorCode::P004,
1067 bs,
1068 "invalid decimal escape in Unicode mode",
1069 );
1070 }
1071 let (n, _) = self.digits(self.pos - 1);
1072 return if n.is_some_and(|n| n <= u64::from(self.total_groups)) {
1073 err(ErrorCode::U001, bs, "backreference")
1074 } else {
1075 err(
1076 ErrorCode::P004,
1077 bs,
1078 "reference to a group that does not exist",
1079 )
1080 };
1081 }
1082 if !in_class && c != '0' {
1083 let (n, end) = self.digits(self.pos - 1);
1084 if n.is_some_and(|n| n <= u64::from(self.total_groups)) {
1085 self.pos = end;
1086 return err(ErrorCode::U001, bs, "backreference");
1087 }
1088 }
1089 if first >= 8 {
1090 return Ok(Esc::Char(c as u32));
1091 }
1092 let extra = if first <= 3 { 2 } else { 1 };
1093 Ok(Esc::Char(self.octal(first, extra)))
1094 }
1095
1096 fn es_unicode_escape(&mut self, bs: usize) -> R<Esc> {
1097 let u = self.flags.u;
1098 if u && self.peek() == Some('{') {
1099 let mut i = self.pos + 1;
1100 let mut v: u32 = 0;
1101 let mut n = 0;
1102 while let Some(d) = self.chars.get(i).and_then(|c| c.to_digit(16)) {
1103 v = v.saturating_mul(16).saturating_add(d);
1104 i += 1;
1105 n += 1;
1106 }
1107 if n == 0 || self.chars.get(i) != Some(&'}') || v > 0x10FFFF {
1108 return err(ErrorCode::P004, bs, "invalid \\u{...} escape");
1109 }
1110 self.pos = i + 1;
1111 return Ok(Esc::Char(v));
1112 }
1113 match self.hex(4) {
1114 Some(hi) if u && (0xD800..0xDC00).contains(&hi) => {
1115 if self.peek() == Some('\\') && self.peek_at(1) == Some('u') {
1117 let save = self.pos;
1118 self.pos += 2;
1119 if let Some(lo) = self.hex(4) {
1120 if (0xDC00..0xE000).contains(&lo) {
1121 return Ok(Esc::Char(0x10000 + ((hi - 0xD800) << 10) + (lo - 0xDC00)));
1122 }
1123 }
1124 self.pos = save;
1125 }
1126 Ok(Esc::Char(hi))
1127 }
1128 Some(v) => Ok(Esc::Char(v)),
1129 None if u => err(ErrorCode::P004, bs, "\\u needs four hex digits"),
1130 None => Ok(Esc::Char('u' as u32)),
1131 }
1132 }
1133
1134 fn esc_py(&mut self, c: char, bs: usize, in_class: bool) -> R<Esc> {
1135 Ok(Esc::Char(match c {
1136 'b' if in_class => 8,
1137 'b' => return Ok(Esc::Assert(Assertion::WordBoundary)),
1138 'B' | 'A' | 'Z' if in_class => {
1139 return err(ErrorCode::P004, bs, "assertion escape inside a class")
1140 }
1141 'B' => return Ok(Esc::Assert(Assertion::NotWordBoundary)),
1142 'A' => return Ok(Esc::Assert(Assertion::TextStart)),
1143 'Z' => return Ok(Esc::Assert(Assertion::TextEnd)),
1144 'a' => 7,
1145 'f' => 12,
1146 'n' => 10,
1147 'r' => 13,
1148 't' => 9,
1149 'v' => 11,
1150 'x' => self
1151 .hex(2)
1152 .ok_or_else(|| PErr::new(ErrorCode::P004, bs, "\\x needs two hex digits"))?,
1153 'u' => self
1154 .hex(4)
1155 .ok_or_else(|| PErr::new(ErrorCode::P004, bs, "\\u needs four hex digits"))?,
1156 'U' => match self.hex(8) {
1157 Some(v) if v <= 0x10FFFF => v,
1158 _ => {
1159 return err(
1160 ErrorCode::P004,
1161 bs,
1162 "\\U needs eight hex digits up to 10FFFF",
1163 )
1164 }
1165 },
1166 'N' => {
1167 return err(
1168 ErrorCode::U007,
1169 bs,
1170 "\\N{name} escapes are not yet supported",
1171 )
1172 }
1173 '0' => self.octal(0, 2),
1174 '1'..='9' => return self.py_digits(c, bs, in_class),
1175 _ if c.is_ascii_alphabetic() => {
1176 return err(ErrorCode::P004, bs, "unknown escape of an ASCII letter")
1177 }
1178 _ => c as u32,
1179 }))
1180 }
1181
1182 fn py_digits(&mut self, c: char, bs: usize, in_class: bool) -> R<Esc> {
1183 let first = c.to_digit(10).unwrap_or(0);
1184 let is_octal3 = first < 8
1185 && self.peek().is_some_and(|d| d.is_digit(8))
1186 && self.peek_at(1).is_some_and(|d| d.is_digit(8));
1187 if in_class || is_octal3 {
1188 if first >= 8 {
1189 return err(ErrorCode::P004, bs, "bad escape");
1190 }
1191 let v = self.octal(first, 2);
1192 if v > 0o377 {
1193 return err(ErrorCode::P004, bs, "octal escape value outside 0-0o377");
1194 }
1195 return Ok(Esc::Char(v));
1196 }
1197 let second = self.peek().and_then(|d| d.to_digit(10));
1199 let n = u64::from(first) * if second.is_some() { 10 } else { 1 }
1200 + u64::from(second.unwrap_or(0));
1201 let end = self.pos + usize::from(second.is_some());
1202 if n <= u64::from(self.total_groups) {
1203 self.pos = end;
1204 err(ErrorCode::U001, bs, "backreference")
1205 } else {
1206 err(
1207 ErrorCode::P004,
1208 bs,
1209 "reference to a group that does not exist",
1210 )
1211 }
1212 }
1213}