1use alloc::{string::String, vec::Vec};
16use core::fmt;
17
18pub const MAX_REGEX_DEPTH: usize = 256;
33
34const MAX_REPEAT: u32 = 100_000;
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub struct RegexError {
54 pub offset: usize,
56 pub kind: RegexErrorKind,
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75#[non_exhaustive]
76pub enum RegexErrorKind {
77 TrailingBackslash,
79 UnknownEscape,
85 InvalidHexEscape,
88 InvalidCodePoint,
92 MalformedProperty,
95 UnknownProperty,
99 UnclosedGroup,
101 UnmatchedParen,
103 UnknownGroup,
107 UnknownOption,
110 UnclosedComment,
112 InvalidGroupName,
115 DuplicateGroupName,
117 MixedGroups,
122 NumberedBackrefWithNamedGroups,
125 UndefinedGroupName,
127 BackreferenceOutOfRange {
131 group: u32,
133 groups: u32,
135 },
136 UnclosedClass,
138 EmptyClass,
141 UnescapedBracket,
143 InvalidRange,
147 UnknownPosixClass,
150 NothingToRepeat,
152 RepeatedQuantifier,
156 AmbiguousQuantifier,
160 InvalidInterval,
164 QuantifiedAssertion,
167 LookbehindNotFixed,
171 CaptureInLookbehind,
173 TooDeep,
175 NulCharacter,
178 BackslashBeforeDigit,
183 TooManyGroups,
185}
186
187impl fmt::Display for RegexErrorKind {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 let text = match self {
190 Self::TrailingBackslash => "the pattern ends with a lone `\\`",
191 Self::UnknownEscape => "escape outside the supported subset",
192 Self::InvalidHexEscape => {
193 "`\\x` needs two hex digits or `{1-8 hex digits}`; `\\u` needs four hex digits"
194 }
195 Self::InvalidCodePoint => {
196 "code point is a surrogate, above U+10FFFF, or a `\\xHH` byte above 7F"
197 }
198 Self::MalformedProperty => "`\\p` and `\\P` must be followed by `{name}`",
199 Self::UnknownProperty => "unsupported Unicode property name",
200 Self::UnclosedGroup => "unclosed group: `(` has no matching `)`",
201 Self::UnmatchedParen => "unmatched `)`",
202 Self::UnknownGroup => "group syntax outside the supported subset",
203 Self::UnknownOption => "inline options must be `i`, `m`, or `x`",
204 Self::UnclosedComment => "unclosed `(?#` comment",
205 Self::InvalidGroupName => "group names must be ASCII identifiers",
206 Self::DuplicateGroupName => "two named groups share a name",
207 Self::MixedGroups => {
208 "named and unnamed capturing groups are mixed; use `(?:...)` or name every group"
209 }
210 Self::NumberedBackrefWithNamedGroups => {
211 "numbered back-reference in a pattern with named groups; use `\\k<name>`"
212 }
213 Self::UndefinedGroupName => "back-reference to an undefined group name",
214 Self::BackreferenceOutOfRange { group, groups } => {
215 return write!(
216 f,
217 "back-reference to group {group}, but the highest group it can name is {groups}"
218 );
219 }
220 Self::UnclosedClass => "unclosed class: `[` has no matching `]`",
221 Self::EmptyClass => "empty character class (write `\\]` for a literal `]`)",
222 Self::UnescapedBracket => "unbalanced `]`; write `\\]` for the character",
223 Self::InvalidRange => "invalid range in class; write `\\-` for a literal `-`",
224 Self::UnknownPosixClass => "unsupported POSIX bracket expression",
225 Self::NothingToRepeat => "quantifier with nothing to repeat",
226 Self::RepeatedQuantifier => "quantifier follows another quantifier",
227 Self::AmbiguousQuantifier => {
228 "`{n}?`, `{n}+`, and `{n,m}+` mean different things in different engines"
229 }
230 Self::InvalidInterval => "invalid `{n,m}` quantifier; write `\\{` for a literal `{`",
231 Self::QuantifiedAssertion => "quantifier applied to an anchor or look-around",
232 Self::LookbehindNotFixed => "look-behind alternative does not have a fixed length",
233 Self::CaptureInLookbehind => "capture group or back-reference inside a look-behind",
234 Self::TooDeep => "groups and classes nest too deeply",
235 Self::NulCharacter => "raw NUL character; write `\\x00`",
236 Self::BackslashBeforeDigit => {
237 "`\\\\` followed by a digit in an `end`/`while` pattern; write `\\x5c`"
238 }
239 Self::TooManyGroups => "too many capture groups",
240 };
241 f.write_str(text)
242 }
243}
244
245impl fmt::Display for RegexError {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 write!(f, "at byte {}: {}", self.offset, self.kind)
248 }
249}
250
251impl core::error::Error for RegexError {}
252
253pub fn validate_regex(pattern: &str) -> Result<u32, RegexError> {
285 check(pattern, Backrefs::Own).map(|info| info.groups)
286}
287
288#[must_use]
317pub fn escape_regex(text: &str) -> String {
318 let mut out = String::with_capacity(text.len() + text.len() / 2);
319 for c in text.chars() {
320 match c {
321 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => out.push(c),
322 '\\' => out.push_str("\\x5c"),
323 ' '..='~' => {
324 out.push('\\');
325 out.push(c);
326 }
327 '\0'..='\u{1f}' | '\u{7f}' => {
328 let byte = c as u8;
330 out.push_str("\\x");
331 out.push(hex_digit(byte >> 4));
332 out.push(hex_digit(byte & 0x0F));
333 }
334 _ => out.push(c),
335 }
336 }
337 out
338}
339
340fn hex_digit(nibble: u8) -> char {
341 char::from(b"0123456789abcdef"[usize::from(nibble & 0x0F)])
342}
343
344#[derive(Clone, Copy, PartialEq, Eq, Debug)]
346pub(crate) enum Backrefs {
347 Own,
349 Begin,
352}
353
354#[derive(Clone, Copy, Debug)]
356pub(crate) struct Info {
357 pub(crate) groups: u32,
359 pub(crate) max_backref: Option<(u32, usize)>,
362}
363
364pub(crate) fn check(pattern: &str, backrefs: Backrefs) -> Result<Info, RegexError> {
366 check_in(pattern, backrefs, &mut Buffers::default())
367}
368
369#[derive(Default)]
372pub(crate) struct Buffers {
373 frames: Vec<Frame>,
374}
375
376pub(crate) fn check_in(
378 pattern: &str,
379 backrefs: Backrefs,
380 buffers: &mut Buffers,
381) -> Result<Info, RegexError> {
382 let mut parser = Parser::new(pattern, backrefs);
383 parser.frames = core::mem::take(&mut buffers.frames);
384 parser.frames.clear();
385 let result = parser.run();
386 buffers.frames = parser.frames;
387 result
388}
389
390type Len = Option<u32>;
392
393#[derive(Clone, Copy, PartialEq, Eq, Debug)]
394enum Kind {
395 Root,
396 Capture,
397 Named,
398 NonCapture,
399 Atomic,
400 Lookahead,
401 Lookbehind,
402 Implicit,
405}
406
407#[derive(Clone, Copy, Debug)]
410enum Last {
411 Nothing,
413 Atom(Len),
415 Quantified(Len),
417 Assertion,
419}
420
421#[derive(Clone, Copy, Debug)]
422struct Frame {
423 kind: Kind,
424 open: usize,
426 extended: bool,
428 alt: Len,
430 first_alt: Option<Len>,
432 same: bool,
434 last: Last,
435}
436
437impl Frame {
438 fn new(kind: Kind, open: usize, extended: bool) -> Frame {
439 Frame {
440 kind,
441 open,
442 extended,
443 alt: Some(0),
444 first_alt: None,
445 same: true,
446 last: Last::Nothing,
447 }
448 }
449
450 fn flush(&mut self) {
452 let pending = match self.last {
453 Last::Atom(len) | Last::Quantified(len) => len,
454 Last::Nothing | Last::Assertion => Some(0),
455 };
456 self.alt = match (self.alt, pending) {
457 (Some(a), Some(b)) => a.checked_add(b),
458 _ => None,
459 };
460 self.last = Last::Nothing;
461 }
462
463 fn end_alternative(&mut self) -> Len {
465 self.flush();
466 let len = self.alt;
467 match self.first_alt {
468 None => self.first_alt = Some(len),
469 Some(first) => self.same &= first == len,
470 }
471 self.alt = Some(0);
472 len
473 }
474
475 fn length(&self) -> Len {
477 match self.first_alt {
478 Some(len) if self.same => len,
479 _ => None,
480 }
481 }
482}
483
484struct Parser<'p> {
485 text: &'p str,
486 bytes: &'p [u8],
487 pos: usize,
488 backrefs: Backrefs,
489 frames: Vec<Frame>,
490 lookbehinds: usize,
491 groups: u32,
492 first_unnamed: Option<usize>,
493 first_named: Option<usize>,
494 names: Vec<(&'p str, usize)>,
496 named_refs: Vec<(&'p str, usize)>,
498 max_backref: Option<(u32, usize)>,
500 first_backref: Option<usize>,
501}
502
503impl<'p> Parser<'p> {
504 fn new(text: &'p str, backrefs: Backrefs) -> Parser<'p> {
505 Parser {
506 text,
507 bytes: text.as_bytes(),
508 pos: 0,
509 backrefs,
510 frames: Vec::new(),
511 lookbehinds: 0,
512 groups: 0,
513 first_unnamed: None,
514 first_named: None,
515 names: Vec::new(),
516 named_refs: Vec::new(),
517 max_backref: None,
518 first_backref: None,
519 }
520 }
521
522 fn err<T>(offset: usize, kind: RegexErrorKind) -> Result<T, RegexError> {
523 Err(RegexError { offset, kind })
524 }
525
526 fn peek(&self) -> Option<u8> {
527 self.bytes.get(self.pos).copied()
528 }
529
530 fn peek_at(&self, ahead: usize) -> Option<u8> {
531 self.bytes.get(self.pos.saturating_add(ahead)).copied()
532 }
533
534 fn top(&mut self) -> &mut Frame {
535 if self.frames.is_empty() {
538 self.frames.push(Frame::new(Kind::Root, 0, false));
539 }
540 let last = self.frames.len() - 1;
541 &mut self.frames[last]
542 }
543
544 fn extended(&self) -> bool {
545 self.frames.last().is_some_and(|f| f.extended)
546 }
547
548 fn depth_ok(&self, offset: usize, classes: usize) -> Result<(), RegexError> {
549 if self.frames.len().saturating_sub(1).saturating_add(classes) > MAX_REGEX_DEPTH {
551 return Self::err(offset, RegexErrorKind::TooDeep);
552 }
553 Ok(())
554 }
555
556 fn run(&mut self) -> Result<Info, RegexError> {
557 self.frames.push(Frame::new(Kind::Root, 0, false));
558 loop {
559 if self.extended() {
560 self.skip_extended();
561 }
562 let Some(b) = self.peek() else { break };
563 let at = self.pos;
564 match b {
565 b'(' => self.open_group()?,
566 b')' => self.close_paren()?,
567 b'|' => {
568 self.pos += 1;
569 self.alternative()?;
570 }
571 b'[' => {
572 self.class()?;
573 self.atom(Some(1));
574 }
575 b']' => return Self::err(at, RegexErrorKind::UnescapedBracket),
576 b'{' => self.interval()?,
577 b'*' | b'+' | b'?' => {
578 self.pos += 1;
579 self.modifier_after_simple();
580 self.quantify(at, None)?;
581 }
582 b'^' | b'$' => {
583 self.pos += 1;
584 self.assertion();
585 }
586 b'.' => {
587 self.pos += 1;
588 self.atom(Some(1));
589 }
590 b'\\' => self.escape()?,
591 0 => return Self::err(at, RegexErrorKind::NulCharacter),
592 _ => {
593 self.pos += char_len(b);
594 self.atom(Some(1));
595 }
596 }
597 }
598 while self.frames.last().is_some_and(|f| f.kind == Kind::Implicit) {
600 self.close_frame()?;
601 }
602 if let Some(frame) = self.frames.last() {
603 if frame.kind != Kind::Root {
604 return Self::err(frame.open, RegexErrorKind::UnclosedGroup);
605 }
606 }
607 self.finish()
608 }
609
610 fn finish(&mut self) -> Result<Info, RegexError> {
612 if let (Some(named), Some(unnamed)) = (self.first_named, self.first_unnamed) {
613 return Self::err(named.max(unnamed), RegexErrorKind::MixedGroups);
614 }
615 if self.backrefs == Backrefs::Own {
616 if let (Some(_), Some(at)) = (self.first_named, self.first_backref) {
617 return Self::err(at, RegexErrorKind::NumberedBackrefWithNamedGroups);
618 }
619 if let Some((group, at)) = self.max_backref {
620 if group > self.groups {
621 return Self::err(
622 at,
623 RegexErrorKind::BackreferenceOutOfRange {
624 group,
625 groups: self.groups,
626 },
627 );
628 }
629 }
630 }
631 self.names.sort_unstable();
632 for pair in self.names.windows(2) {
633 if pair[0].0 == pair[1].0 {
634 return Self::err(pair[0].1.max(pair[1].1), RegexErrorKind::DuplicateGroupName);
635 }
636 }
637 for &(name, at) in &self.named_refs {
638 if self
639 .names
640 .binary_search_by(|&(probe, _)| probe.cmp(name))
641 .is_err()
642 {
643 return Self::err(at, RegexErrorKind::UndefinedGroupName);
644 }
645 }
646 Ok(Info {
647 groups: self.groups,
648 max_backref: match self.backrefs {
649 Backrefs::Own => None,
650 Backrefs::Begin => self.max_backref,
651 },
652 })
653 }
654
655 fn skip_extended(&mut self) {
657 while let Some(b) = self.peek() {
658 match b {
659 b' ' | b'\t' | b'\n' | b'\r' | 0x0B | 0x0C => self.pos += 1,
660 b'#' => {
661 while let Some(c) = self.peek() {
662 self.pos += 1;
663 if c == b'\n' {
664 break;
665 }
666 }
667 }
668 _ => break,
669 }
670 }
671 }
672
673 fn atom(&mut self, len: Len) {
674 let top = self.top();
675 top.flush();
676 top.last = Last::Atom(len);
677 }
678
679 fn assertion(&mut self) {
680 let top = self.top();
681 top.flush();
682 top.last = Last::Assertion;
683 }
684
685 fn modifier_after_simple(&mut self) {
687 if matches!(self.peek(), Some(b'?' | b'+')) {
688 self.pos += 1;
689 }
690 }
691
692 fn quantify(&mut self, at: usize, fixed: Option<u32>) -> Result<(), RegexError> {
695 let top = self.top();
696 let len = match top.last {
697 Last::Nothing => return Self::err(at, RegexErrorKind::NothingToRepeat),
698 Last::Assertion => return Self::err(at, RegexErrorKind::QuantifiedAssertion),
699 Last::Quantified(_) => return Self::err(at, RegexErrorKind::RepeatedQuantifier),
700 Last::Atom(len) => len,
701 };
702 let len = match (len, fixed) {
703 (Some(len), Some(count)) => len.checked_mul(count),
704 _ => None,
705 };
706 top.last = Last::Quantified(len);
707 Ok(())
708 }
709
710 fn interval(&mut self) -> Result<(), RegexError> {
712 let at = self.pos;
713 self.pos += 1;
714 let min = self.number().ok_or(RegexError {
715 offset: at,
716 kind: RegexErrorKind::InvalidInterval,
717 })?;
718 let max = if self.peek() == Some(b',') {
719 self.pos += 1;
720 if self.peek() == Some(b'}') {
721 None
722 } else {
723 Some(self.number().ok_or(RegexError {
724 offset: at,
725 kind: RegexErrorKind::InvalidInterval,
726 })?)
727 }
728 } else {
729 Some(min)
730 };
731 let exact = self
732 .bytes
733 .get(at..self.pos)
734 .is_some_and(|s| !s.contains(&b','));
735 if self.peek() != Some(b'}')
736 || min > MAX_REPEAT
737 || max.is_some_and(|m| m > MAX_REPEAT || m < min)
738 {
739 return Self::err(at, RegexErrorKind::InvalidInterval);
740 }
741 self.pos += 1;
742 match self.peek() {
743 Some(b'+') => return Self::err(at, RegexErrorKind::AmbiguousQuantifier),
744 Some(b'?') if exact => return Self::err(at, RegexErrorKind::AmbiguousQuantifier),
745 Some(b'?') => self.pos += 1,
746 _ => {}
747 }
748 let fixed = match max {
749 Some(max) if max == min => Some(min),
750 _ => None,
751 };
752 self.quantify(at, fixed)
753 }
754
755 fn number(&mut self) -> Option<u32> {
758 let start = self.pos;
759 let mut value: u32 = 0;
760 while let Some(d @ b'0'..=b'9') = self.peek() {
761 value = value.saturating_mul(10).saturating_add(u32::from(d - b'0'));
762 self.pos += 1;
763 }
764 (self.pos > start).then_some(value)
765 }
766
767 fn alternative(&mut self) -> Result<(), RegexError> {
769 let top = self.top();
770 let (open, kind) = (top.open, top.kind);
771 if top.end_alternative().is_none() && kind == Kind::Lookbehind {
772 return Self::err(open, RegexErrorKind::LookbehindNotFixed);
773 }
774 Ok(())
775 }
776
777 fn open_group(&mut self) -> Result<(), RegexError> {
778 let at = self.pos;
779 self.depth_ok(at, 1)?;
780 let extended = self.extended();
781 self.pos += 1;
782 if self.peek() != Some(b'?') {
783 return self.push_capture(at, Kind::Capture, extended);
784 }
785 self.pos += 1;
786 let kind = match self.peek() {
787 Some(b':') => Kind::NonCapture,
788 Some(b'=' | b'!') => Kind::Lookahead,
789 Some(b'>') => Kind::Atomic,
790 Some(b'<') => match self.peek_at(1) {
791 Some(b'=' | b'!') => {
792 self.pos += 1;
793 Kind::Lookbehind
794 }
795 _ => {
796 self.pos += 1;
797 let name = self.group_name(at)?;
798 if self.peek() != Some(b'>') {
799 return Self::err(at, RegexErrorKind::InvalidGroupName);
800 }
801 self.pos += 1;
802 self.names.push((name, at));
803 return self.push_capture(at, Kind::Named, extended);
804 }
805 },
806 Some(b'#') => return self.comment(at),
807 Some(b'i' | b'm' | b'x' | b'-') => return self.options(at, extended),
808 _ => return Self::err(at, RegexErrorKind::UnknownGroup),
809 };
810 self.pos += 1;
811 if kind == Kind::Lookbehind {
812 self.lookbehinds += 1;
813 }
814 self.push_frame(at, kind, extended);
815 Ok(())
816 }
817
818 fn push_frame(&mut self, at: usize, kind: Kind, extended: bool) {
819 self.top().flush();
820 self.frames.push(Frame::new(kind, at, extended));
821 }
822
823 fn push_capture(&mut self, at: usize, kind: Kind, extended: bool) -> Result<(), RegexError> {
824 if self.lookbehinds > 0 {
825 return Self::err(at, RegexErrorKind::CaptureInLookbehind);
826 }
827 self.groups = self.groups.checked_add(1).ok_or(RegexError {
828 offset: at,
829 kind: RegexErrorKind::TooManyGroups,
830 })?;
831 let first = if kind == Kind::Named {
832 &mut self.first_named
833 } else {
834 &mut self.first_unnamed
835 };
836 if first.is_none() {
837 *first = Some(at);
838 }
839 self.push_frame(at, kind, extended);
840 Ok(())
841 }
842
843 fn group_name(&mut self, at: usize) -> Result<&'p str, RegexError> {
845 let start = self.pos;
846 while let Some(b) = self.peek() {
847 let ok =
848 b == b'_' || b.is_ascii_alphabetic() || (self.pos > start && b.is_ascii_digit());
849 if !ok {
850 break;
851 }
852 self.pos += 1;
853 }
854 if self.pos == start {
855 return Self::err(at, RegexErrorKind::InvalidGroupName);
856 }
857 Ok(self.text.get(start..self.pos).unwrap_or(""))
858 }
859
860 fn comment(&mut self, at: usize) -> Result<(), RegexError> {
863 self.pos += 1;
864 while let Some(b) = self.peek() {
865 match b {
866 b'\\' => self.pos += 1 + self.peek_at(1).map_or(0, char_len),
867 b')' => {
868 self.pos += 1;
869 return Ok(());
870 }
871 _ => self.pos += char_len(b),
872 }
873 }
874 Self::err(at, RegexErrorKind::UnclosedComment)
875 }
876
877 fn options(&mut self, at: usize, extended: bool) -> Result<(), RegexError> {
879 let mut on = true;
880 let mut letters = 0;
881 let mut negated_letters = 0;
882 let mut mode = extended;
883 loop {
884 match self.peek() {
885 Some(b'-') if on => on = false,
886 Some(c @ (b'i' | b'm' | b'x')) => {
887 if c == b'x' {
888 mode = on;
889 }
890 letters += 1;
891 if !on {
892 negated_letters += 1;
893 }
894 }
895 Some(b')') | Some(b':') => break,
896 _ => return Self::err(at, RegexErrorKind::UnknownOption),
897 }
898 self.pos += 1;
899 }
900 if letters == 0 || (!on && negated_letters == 0) {
901 return Self::err(at, RegexErrorKind::UnknownOption);
902 }
903 let colon = self.peek() == Some(b':');
904 self.pos += 1;
905 if colon {
906 self.push_frame(at, Kind::NonCapture, mode);
907 } else {
908 self.push_frame(at, Kind::Implicit, mode);
911 }
912 Ok(())
913 }
914
915 fn close_paren(&mut self) -> Result<(), RegexError> {
916 let at = self.pos;
917 while self.frames.last().is_some_and(|f| f.kind == Kind::Implicit) {
918 self.close_frame()?;
919 }
920 if self.frames.last().is_none_or(|f| f.kind == Kind::Root) {
921 return Self::err(at, RegexErrorKind::UnmatchedParen);
922 }
923 self.pos += 1;
924 self.close_frame()
925 }
926
927 fn close_frame(&mut self) -> Result<(), RegexError> {
929 let Some(mut frame) = self.frames.pop() else {
930 return Ok(());
931 };
932 let last_len = frame.end_alternative();
933 if frame.kind == Kind::Lookbehind {
934 if last_len.is_none() {
935 return Self::err(frame.open, RegexErrorKind::LookbehindNotFixed);
936 }
937 self.lookbehinds = self.lookbehinds.saturating_sub(1);
938 }
939 let len = frame.length();
940 let parent = self.top();
941 parent.last = match frame.kind {
944 Kind::Lookahead | Kind::Lookbehind => Last::Assertion,
945 _ => Last::Atom(len),
946 };
947 Ok(())
948 }
949
950 fn escape(&mut self) -> Result<(), RegexError> {
951 let at = self.pos;
952 let Some(c) = self.peek_at(1) else {
953 return Self::err(at, RegexErrorKind::TrailingBackslash);
954 };
955 match c {
956 b'A' | b'z' | b'Z' | b'G' | b'b' | b'B' => {
957 self.pos += 2;
958 self.assertion();
959 }
960 b'1'..=b'9' => self.backref(at)?,
961 b'k' => self.named_backref(at)?,
962 b'w' | b'W' | b'd' | b'D' | b's' | b'S' | b'h' | b'H' => {
963 self.pos += 2;
964 self.atom(Some(1));
965 }
966 b'p' | b'P' => {
967 self.property(at)?;
968 self.atom(Some(1));
969 }
970 _ => {
971 let _char = self.char_escape(at)?;
972 self.atom(Some(1));
973 }
974 }
975 Ok(())
976 }
977
978 fn backref(&mut self, at: usize) -> Result<(), RegexError> {
979 self.pos += 1;
980 let group = self.number().unwrap_or(0);
981 if self.lookbehinds > 0 {
983 return Self::err(at, RegexErrorKind::CaptureInLookbehind);
984 }
985 if self.first_backref.is_none() {
986 self.first_backref = Some(at);
987 }
988 if self.max_backref.is_none_or(|(max, _)| group > max) {
989 self.max_backref = Some((group, at));
990 }
991 self.atom(None);
992 Ok(())
993 }
994
995 fn named_backref(&mut self, at: usize) -> Result<(), RegexError> {
996 self.pos += 2;
997 if self.peek() != Some(b'<') {
998 return Self::err(at, RegexErrorKind::InvalidGroupName);
999 }
1000 self.pos += 1;
1001 let name = self.group_name(at)?;
1002 if self.peek() != Some(b'>') {
1003 return Self::err(at, RegexErrorKind::InvalidGroupName);
1004 }
1005 self.pos += 1;
1006 if self.lookbehinds > 0 {
1007 return Self::err(at, RegexErrorKind::CaptureInLookbehind);
1008 }
1009 self.named_refs.push((name, at));
1010 self.atom(None);
1011 Ok(())
1012 }
1013
1014 fn property(&mut self, at: usize) -> Result<(), RegexError> {
1016 self.pos += 2;
1017 if self.peek() != Some(b'{') {
1018 return Self::err(at, RegexErrorKind::MalformedProperty);
1019 }
1020 self.pos += 1;
1021 if self.peek() == Some(b'^') {
1022 self.pos += 1;
1023 }
1024 let start = self.pos;
1025 while let Some(b) = self.peek() {
1026 if b == b'}' {
1027 break;
1028 }
1029 if !(b.is_ascii_alphanumeric() || matches!(b, b'_' | b' ' | b'-')) {
1030 return Self::err(at, RegexErrorKind::MalformedProperty);
1031 }
1032 self.pos += 1;
1033 }
1034 if self.peek() != Some(b'}') {
1035 return Self::err(at, RegexErrorKind::MalformedProperty);
1036 }
1037 let name = self.bytes.get(start..self.pos).unwrap_or(&[]);
1038 self.pos += 1;
1039 if known_property(name) {
1040 Ok(())
1041 } else {
1042 Self::err(at, RegexErrorKind::UnknownProperty)
1043 }
1044 }
1045
1046 fn char_escape(&mut self, at: usize) -> Result<char, RegexError> {
1049 let Some(c) = self.peek_at(1) else {
1050 return Self::err(at, RegexErrorKind::TrailingBackslash);
1051 };
1052 let simple = match c {
1053 b't' => Some('\t'),
1054 b'n' => Some('\n'),
1055 b'r' => Some('\r'),
1056 b'f' => Some('\u{c}'),
1057 b'v' => Some('\u{b}'),
1058 b'a' => Some('\u{7}'),
1059 b'e' => Some('\u{1b}'),
1060 _ => None,
1061 };
1062 if let Some(ch) = simple {
1063 self.pos += 2;
1064 return Ok(ch);
1065 }
1066 match c {
1067 b'x' => self.hex_escape(at),
1068 b'u' => self.unicode_escape(at),
1069 b'0'..=b'9' => Self::err(at, RegexErrorKind::UnknownEscape),
1070 b'\\'
1071 if self.backrefs == Backrefs::Begin
1072 && self.peek_at(2).is_some_and(|d| d.is_ascii_digit()) =>
1073 {
1074 Self::err(at, RegexErrorKind::BackslashBeforeDigit)
1075 }
1076 0x21..=0x2F | 0x3A..=0x40 | 0x5B..=0x60 | 0x7B..=0x7E | b' ' => {
1077 self.pos += 2;
1078 Ok(char::from(c))
1079 }
1080 _ => Self::err(at, RegexErrorKind::UnknownEscape),
1081 }
1082 }
1083
1084 fn hex_escape(&mut self, at: usize) -> Result<char, RegexError> {
1086 self.pos += 2;
1087 if self.peek() == Some(b'{') {
1088 self.pos += 1;
1089 let start = self.pos;
1090 let mut value: u32 = 0;
1091 while let Some(d) = self.peek().and_then(hex_value) {
1092 value = value.saturating_mul(16).saturating_add(d);
1093 self.pos += 1;
1094 }
1095 let digits = self.pos - start;
1096 if digits == 0 || digits > 8 || self.peek() != Some(b'}') {
1097 return Self::err(at, RegexErrorKind::InvalidHexEscape);
1098 }
1099 self.pos += 1;
1100 return char::from_u32(value).ok_or(RegexError {
1101 offset: at,
1102 kind: RegexErrorKind::InvalidCodePoint,
1103 });
1104 }
1105 let (Some(hi), Some(lo)) = (
1106 self.peek().and_then(hex_value),
1107 self.peek_at(1).and_then(hex_value),
1108 ) else {
1109 return Self::err(at, RegexErrorKind::InvalidHexEscape);
1110 };
1111 let value = hi * 16 + lo;
1112 if value > 0x7F {
1113 return Self::err(at, RegexErrorKind::InvalidCodePoint);
1114 }
1115 self.pos += 2;
1116 char::from_u32(value).ok_or(RegexError {
1117 offset: at,
1118 kind: RegexErrorKind::InvalidCodePoint,
1119 })
1120 }
1121
1122 fn unicode_escape(&mut self, at: usize) -> Result<char, RegexError> {
1124 self.pos += 2;
1125 let mut value: u32 = 0;
1126 for _ in 0..4 {
1127 let Some(d) = self.peek().and_then(hex_value) else {
1128 return Self::err(at, RegexErrorKind::InvalidHexEscape);
1129 };
1130 value = value * 16 + d;
1131 self.pos += 1;
1132 }
1133 char::from_u32(value).ok_or(RegexError {
1134 offset: at,
1135 kind: RegexErrorKind::InvalidCodePoint,
1136 })
1137 }
1138
1139 fn class(&mut self) -> Result<(), RegexError> {
1141 let outer = self.pos;
1142 let mut stack: Vec<ClassFrame> = Vec::new();
1145 self.open_class(&mut stack)?;
1146 loop {
1147 let at = self.pos;
1148 let Some(b) = self.peek() else {
1149 return Self::err(outer, RegexErrorKind::UnclosedClass);
1150 };
1151 let Some(top) = stack.last_mut() else {
1152 return Ok(());
1153 };
1154 match b {
1155 b']' => {
1156 if top.items == 0 {
1157 return Self::err(at, RegexErrorKind::EmptyClass);
1158 }
1159 self.pos += 1;
1160 let _closed = stack.pop();
1161 match stack.last_mut() {
1162 None => return Ok(()),
1163 Some(parent) => parent.set_item(),
1164 }
1165 }
1166 b'[' if self.peek_at(1) == Some(b':') => {
1167 self.posix(at)?;
1168 top.set_item();
1169 }
1170 b'[' => self.open_class(&mut stack)?,
1171 b'&' if self.peek_at(1) == Some(b'&') => {
1172 if top.items == 0 {
1173 return Self::err(at, RegexErrorKind::EmptyClass);
1174 }
1175 top.items = 0;
1176 top.endpoint = None;
1177 self.pos += 2;
1178 }
1179 b'-' => {
1180 if top.items == 0 || self.peek_at(1) == Some(b']') {
1181 self.pos += 1;
1182 top.set_item();
1183 } else if let Some((lo, lo_at)) = top.endpoint {
1184 self.pos += 1;
1185 let hi = self.range_end(at)?;
1186 if hi < lo {
1187 return Self::err(lo_at, RegexErrorKind::InvalidRange);
1188 }
1189 top.set_item();
1190 } else {
1191 return Self::err(at, RegexErrorKind::InvalidRange);
1192 }
1193 }
1194 b'\\' => {
1195 let single = self.class_escape(at)?;
1196 if let Some(top) = stack.last_mut() {
1198 top.set_item();
1199 top.endpoint = single.map(|c| (c, at));
1200 }
1201 }
1202 0 => return Self::err(at, RegexErrorKind::NulCharacter),
1203 _ => {
1204 let c = self.next_char();
1205 top.set_item();
1206 top.endpoint = Some((c, at));
1207 }
1208 }
1209 }
1210 }
1211
1212 fn open_class(&mut self, stack: &mut Vec<ClassFrame>) -> Result<(), RegexError> {
1213 let at = self.pos;
1214 self.depth_ok(at, stack.len() + 1)?;
1215 self.pos += 1;
1216 if self.peek() == Some(b'^') {
1217 self.pos += 1;
1218 }
1219 stack.push(ClassFrame {
1220 items: 0,
1221 endpoint: None,
1222 });
1223 Ok(())
1224 }
1225
1226 fn range_end(&mut self, dash: usize) -> Result<char, RegexError> {
1229 let at = self.pos;
1230 match self.peek() {
1231 None => Self::err(dash, RegexErrorKind::InvalidRange),
1232 Some(b'[' | b']' | b'-') => Self::err(dash, RegexErrorKind::InvalidRange),
1233 Some(b'&') if self.peek_at(1) == Some(b'&') => {
1234 Self::err(dash, RegexErrorKind::InvalidRange)
1235 }
1236 Some(0) => Self::err(at, RegexErrorKind::NulCharacter),
1237 Some(b'\\') => match self.class_escape(at)? {
1238 Some(c) => Ok(c),
1239 None => Self::err(dash, RegexErrorKind::InvalidRange),
1240 },
1241 Some(_) => Ok(self.next_char()),
1242 }
1243 }
1244
1245 fn class_escape(&mut self, at: usize) -> Result<Option<char>, RegexError> {
1248 match self.peek_at(1) {
1249 None => Self::err(at, RegexErrorKind::TrailingBackslash),
1250 Some(b'w' | b'W' | b'd' | b'D' | b's' | b'S' | b'h' | b'H') => {
1251 self.pos += 2;
1252 Ok(None)
1253 }
1254 Some(b'p' | b'P') => {
1255 self.property(at)?;
1256 Ok(None)
1257 }
1258 Some(b'b' | b'B' | b'A' | b'z' | b'Z' | b'G' | b'k') => {
1259 Self::err(at, RegexErrorKind::UnknownEscape)
1260 }
1261 Some(_) => self.char_escape(at).map(Some),
1262 }
1263 }
1264
1265 fn posix(&mut self, at: usize) -> Result<(), RegexError> {
1267 let rest = self.bytes.get(at + 2..).unwrap_or(&[]);
1268 let rest = rest.strip_prefix(b"^").unwrap_or(rest);
1269 let end = rest
1270 .iter()
1271 .position(|&b| !b.is_ascii_lowercase())
1272 .unwrap_or(rest.len());
1273 let (name, tail) = rest.split_at(end);
1274 if !tail.starts_with(b":]") || !POSIX.contains(&name) {
1275 return Self::err(at, RegexErrorKind::UnknownPosixClass);
1276 }
1277 self.pos = at + 2 + usize::from(self.bytes.get(at + 2) == Some(&b'^')) + name.len() + 2;
1279 Ok(())
1280 }
1281
1282 fn next_char(&mut self) -> char {
1283 let c = self
1284 .text
1285 .get(self.pos..)
1286 .and_then(|s| s.chars().next())
1287 .unwrap_or('\0');
1288 self.pos += c.len_utf8();
1289 c
1290 }
1291}
1292
1293#[derive(Clone, Copy)]
1294struct ClassFrame {
1295 items: u32,
1296 endpoint: Option<(char, usize)>,
1297}
1298
1299impl ClassFrame {
1300 fn set_item(&mut self) {
1301 self.items = self.items.saturating_add(1);
1302 self.endpoint = None;
1303 }
1304}
1305
1306fn char_len(first: u8) -> usize {
1308 match first {
1309 0xF0..=0xFF => 4,
1310 0xE0..=0xEF => 3,
1311 0xC0..=0xDF => 2,
1312 _ => 1,
1313 }
1314}
1315
1316fn hex_value(b: u8) -> Option<u32> {
1317 char::from(b).to_digit(16)
1318}
1319
1320const POSIX: &[&[u8]] = &[
1322 b"alnum", b"alpha", b"ascii", b"blank", b"cntrl", b"digit", b"graph", b"lower", b"print",
1323 b"punct", b"space", b"upper", b"xdigit", b"word",
1324];
1325
1326const PROPERTIES: &[&str] = &[
1329 "c",
1331 "cc",
1332 "cf",
1333 "cn",
1334 "co",
1335 "cs",
1336 "l",
1337 "ll",
1338 "lm",
1339 "lo",
1340 "lt",
1341 "lu",
1342 "m",
1343 "mc",
1344 "me",
1345 "mn",
1346 "n",
1347 "nd",
1348 "nl",
1349 "no",
1350 "p",
1351 "pc",
1352 "pd",
1353 "pe",
1354 "pf",
1355 "pi",
1356 "po",
1357 "ps",
1358 "s",
1359 "sc",
1360 "sk",
1361 "sm",
1362 "so",
1363 "z",
1364 "zl",
1365 "zp",
1366 "zs",
1367 "alnum",
1369 "alpha",
1370 "ascii",
1371 "blank",
1372 "cntrl",
1373 "digit",
1374 "graph",
1375 "lower",
1376 "print",
1377 "punct",
1378 "space",
1379 "upper",
1380 "xdigit",
1381 "word",
1382 "any",
1383 "assigned",
1384 "alphabetic",
1386 "lowercase",
1387 "uppercase",
1388 "whitespace",
1389 "idstart",
1390 "idcontinue",
1391 "xidstart",
1392 "xidcontinue",
1393 "arabic",
1395 "armenian",
1396 "bengali",
1397 "bopomofo",
1398 "cherokee",
1399 "common",
1400 "cyrillic",
1401 "devanagari",
1402 "ethiopic",
1403 "georgian",
1404 "greek",
1405 "gujarati",
1406 "gurmukhi",
1407 "han",
1408 "hangul",
1409 "hebrew",
1410 "hiragana",
1411 "inherited",
1412 "kannada",
1413 "katakana",
1414 "khmer",
1415 "lao",
1416 "latin",
1417 "malayalam",
1418 "mongolian",
1419 "myanmar",
1420 "oriya",
1421 "sinhala",
1422 "syriac",
1423 "tamil",
1424 "telugu",
1425 "thaana",
1426 "thai",
1427 "tibetan",
1428];
1429
1430fn known_property(name: &[u8]) -> bool {
1432 let mut normalized = [0u8; 32];
1433 let mut len = 0;
1434 for &b in name {
1435 if matches!(b, b' ' | b'_' | b'-') {
1436 continue;
1437 }
1438 let Some(slot) = normalized.get_mut(len) else {
1439 return false;
1440 };
1441 *slot = b.to_ascii_lowercase();
1442 len += 1;
1443 }
1444 let normalized = normalized.get(..len).unwrap_or(&[]);
1445 PROPERTIES.iter().any(|p| p.as_bytes() == normalized)
1446}
1447
1448#[cfg(test)]
1449#[allow(clippy::unwrap_used, clippy::panic)]
1450mod tests {
1451 use alloc::string::ToString;
1452
1453 use super::*;
1454
1455 fn kind(pattern: &str) -> RegexErrorKind {
1456 match validate_regex(pattern) {
1457 Err(e) => e.kind,
1458 Ok(groups) => panic!("{pattern:?} was accepted with {groups} groups"),
1459 }
1460 }
1461
1462 #[test]
1463 fn test_accepts_common_grammar_patterns() {
1464 let ok = [
1465 (r"\b(if|else|while)\b", 1),
1466 (r#""(?:[^"\\]|\\.)*""#, 0),
1467 (r"(?<!\w)(\d+)(?:\.(\d+))?", 2),
1468 (r"^\s*(#)\s*(include)\b", 2),
1469 (r"\G(?=\S)", 0),
1470 (r"(?i:select|from)", 0),
1471 (r"(?<name>[a-z]+)\s*=\s*\k<name>", 1),
1472 (r"[\p{L}_][\p{L}\p{N}_]*", 0),
1473 (r"[[:alpha:][:digit:]]", 0),
1474 (r"[a-z&&[^aeiou]]", 0),
1475 (r"\x{1F600}|\u00e9|\x7f", 0),
1476 (r"a{2}b{1,}c{0,3}?", 0),
1477 (r"a*+b++c?+", 0),
1478 (r"(?>a+)b", 0),
1479 (r"(?<=ab|c)d", 0),
1480 (r"(?<=a(?:b|c))d", 0),
1481 (r"(?<=\d{3})x", 0),
1482 ("}", 0),
1483 (r"[-a]|[a-]|[\]\[]", 0),
1484 (r"(?#comment \) here)x", 0),
1485 ("(?x) a + # spaces and a comment ( ) [\n b", 0),
1486 (r"(?x) [ ]", 0),
1487 ];
1488 for (pattern, groups) in ok {
1489 assert_eq!(validate_regex(pattern), Ok(groups), "{pattern}");
1490 }
1491 }
1492
1493 #[test]
1494 fn test_rejects_with_offsets() {
1495 let cases = [
1496 ("ab\\", 2, RegexErrorKind::TrailingBackslash),
1497 (r"a\q", 1, RegexErrorKind::UnknownEscape),
1498 (r"\0", 0, RegexErrorKind::UnknownEscape),
1499 (r"x\xZZ", 1, RegexErrorKind::InvalidHexEscape),
1500 (r"\xff", 0, RegexErrorKind::InvalidCodePoint),
1501 (r"\x{110000}", 0, RegexErrorKind::InvalidCodePoint),
1502 (r"\uD800", 0, RegexErrorKind::InvalidCodePoint),
1503 (r"\pL", 0, RegexErrorKind::MalformedProperty),
1504 (r"\p{Klingon}", 0, RegexErrorKind::UnknownProperty),
1505 ("a(b", 1, RegexErrorKind::UnclosedGroup),
1506 ("a)b", 1, RegexErrorKind::UnmatchedParen),
1507 ("(?(1)a|b)", 0, RegexErrorKind::UnknownGroup),
1508 ("(?iq)", 0, RegexErrorKind::UnknownOption),
1509 ("(?q)", 0, RegexErrorKind::UnknownGroup),
1510 ("(?-)", 0, RegexErrorKind::UnknownOption),
1511 ("a(?#x", 1, RegexErrorKind::UnclosedComment),
1512 ("(?<1a>x)", 0, RegexErrorKind::InvalidGroupName),
1513 ("(?<a>x)(?<a>y)", 7, RegexErrorKind::DuplicateGroupName),
1514 ("(?<a>x)(y)", 7, RegexErrorKind::MixedGroups),
1515 (
1516 r"(?<a>x)\1",
1517 7,
1518 RegexErrorKind::NumberedBackrefWithNamedGroups,
1519 ),
1520 (r"(?<a>x)\k<b>", 7, RegexErrorKind::UndefinedGroupName),
1521 ("[abc", 0, RegexErrorKind::UnclosedClass),
1522 ("[]a]", 1, RegexErrorKind::EmptyClass),
1523 ("[^]", 2, RegexErrorKind::EmptyClass),
1524 ("[a&&]", 4, RegexErrorKind::EmptyClass),
1525 ("a]", 1, RegexErrorKind::UnescapedBracket),
1526 ("[z-a]", 1, RegexErrorKind::InvalidRange),
1527 (r"[a-\w]", 2, RegexErrorKind::InvalidRange),
1528 (r"[\w-a]", 3, RegexErrorKind::InvalidRange),
1529 ("[[:alpah:]]", 1, RegexErrorKind::UnknownPosixClass),
1530 ("*a", 0, RegexErrorKind::NothingToRepeat),
1531 ("(|*)", 2, RegexErrorKind::NothingToRepeat),
1532 ("a**", 2, RegexErrorKind::RepeatedQuantifier),
1533 ("a{2}{3}", 4, RegexErrorKind::RepeatedQuantifier),
1534 ("a{2}?", 1, RegexErrorKind::AmbiguousQuantifier),
1535 ("a{1,2}+", 1, RegexErrorKind::AmbiguousQuantifier),
1536 ("a{", 1, RegexErrorKind::InvalidInterval),
1537 ("a{,2}", 1, RegexErrorKind::InvalidInterval),
1538 ("a{3,2}", 1, RegexErrorKind::InvalidInterval),
1539 ("a{100001}", 1, RegexErrorKind::InvalidInterval),
1540 ("^*", 1, RegexErrorKind::QuantifiedAssertion),
1541 ("(?=a)+", 5, RegexErrorKind::QuantifiedAssertion),
1542 ("(?<=a+)b", 0, RegexErrorKind::LookbehindNotFixed),
1543 ("(?<=a|b*)c", 0, RegexErrorKind::LookbehindNotFixed),
1544 ("(?<=(?:ab|c))d", 0, RegexErrorKind::LookbehindNotFixed),
1545 ("(?<=(a))b", 4, RegexErrorKind::CaptureInLookbehind),
1546 ("a\0", 1, RegexErrorKind::NulCharacter),
1547 ];
1548 for (pattern, offset, expected) in cases {
1549 let err = validate_regex(pattern).unwrap_err();
1550 assert_eq!((err.offset, err.kind), (offset, expected), "{pattern:?}");
1551 }
1552 }
1553
1554 #[test]
1555 fn test_backreferences() {
1556 assert_eq!(validate_regex(r"(a)(b)\2\1"), Ok(2));
1557 assert_eq!(
1558 kind(r"(a)\2"),
1559 RegexErrorKind::BackreferenceOutOfRange {
1560 group: 2,
1561 groups: 1
1562 }
1563 );
1564 let info = check(r"\2(x)\10", Backrefs::Begin).unwrap();
1566 assert_eq!(info.groups, 1);
1567 assert_eq!(info.max_backref, Some((10, 5)));
1568 assert_eq!(
1569 check(r"\\1", Backrefs::Begin).unwrap_err().kind,
1570 RegexErrorKind::BackslashBeforeDigit
1571 );
1572 assert!(check(r"\\1", Backrefs::Own).is_ok());
1573 assert_eq!(
1574 check(r"[\\1]", Backrefs::Begin).unwrap_err().kind,
1575 RegexErrorKind::BackslashBeforeDigit
1576 );
1577 }
1578
1579 #[test]
1580 fn test_isolated_option_extends_to_enclosing_group() {
1581 assert_eq!(validate_regex("((?x) a # c\n) b"), Ok(1));
1583 assert_eq!(kind("((?x) a ) #)"), RegexErrorKind::UnmatchedParen);
1584 assert_eq!(kind("(?<=a(?i)b|cd)"), RegexErrorKind::LookbehindNotFixed);
1587 assert_eq!(validate_regex("(?<=a(?i)b|c)"), Ok(0));
1588 assert_eq!(kind("(?i)*"), RegexErrorKind::NothingToRepeat);
1589 }
1590
1591 #[test]
1592 fn test_extended_mode_comments_hide_metacharacters() {
1593 assert_eq!(validate_regex("(?x) a # ( [ unbalanced"), Ok(0));
1594 assert_eq!(validate_regex("(?x:a # )\n)"), Ok(0));
1595 assert_eq!(validate_regex(r"(?x) \# \ "), Ok(0));
1596 assert_eq!(kind("a # ("), RegexErrorKind::UnclosedGroup);
1598 }
1599
1600 #[test]
1601 fn test_depth_limit() {
1602 let ok = "(".repeat(MAX_REGEX_DEPTH) + &")".repeat(MAX_REGEX_DEPTH);
1603 assert_eq!(
1604 validate_regex(&ok),
1605 Ok(u32::try_from(MAX_REGEX_DEPTH).unwrap())
1606 );
1607 let deep = "(?:".repeat(MAX_REGEX_DEPTH + 1) + &")".repeat(MAX_REGEX_DEPTH + 1);
1608 assert_eq!(kind(&deep), RegexErrorKind::TooDeep);
1609 let classes = "[".repeat(MAX_REGEX_DEPTH + 1) + "a" + &"]".repeat(MAX_REGEX_DEPTH + 1);
1610 assert_eq!(kind(&classes), RegexErrorKind::TooDeep);
1611 }
1612
1613 #[test]
1614 fn test_escape_regex() {
1615 assert_eq!(escape_regex(""), "");
1616 assert_eq!(escape_regex("abc_09"), "abc_09");
1617 assert_eq!(escape_regex("a.b*c"), r"a\.b\*c");
1618 assert_eq!(escape_regex("\\n"), r"\x5cn");
1619 assert_eq!(escape_regex("\\1"), r"\x5c1");
1620 let joined = escape_regex("a\\") + &escape_regex("1");
1622 assert!(check(&joined, Backrefs::Begin).is_ok());
1623 assert_eq!(escape_regex("\0\u{7f}"), r"\x00\x7f");
1624 assert_eq!(escape_regex("é →"), "é\\ →");
1625 for text in ["((", "[", "\\", "{1}", "#x", "a b", "\\\\9"] {
1626 assert_eq!(
1627 check(&escape_regex(text), Backrefs::Begin).map(|i| i.groups),
1628 Ok(0)
1629 );
1630 }
1631 }
1632
1633 #[test]
1634 fn test_properties_normalize_like_oniguruma() {
1635 assert!(known_property(b"L"));
1636 assert!(known_property(b"White_Space"));
1637 assert!(known_property(b"xid-start"));
1638 assert!(!known_property(b""));
1639 assert!(!known_property(&[b'a'; 40]));
1640 }
1641
1642 #[test]
1643 fn test_display() {
1644 let e = RegexError {
1645 offset: 4,
1646 kind: RegexErrorKind::UnclosedClass,
1647 };
1648 assert_eq!(
1649 e.to_string(),
1650 "at byte 4: unclosed class: `[` has no matching `]`"
1651 );
1652 }
1653}