use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
const MAX_QUANT: usize = crate::limits::DEFAULT_REGEX_MAX_QUANT;
const MAX_PARSE_DEPTH: u32 = crate::limits::DEFAULT_REGEX_MAX_PARSE_DEPTH;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegexError {
message: String,
unsupported: bool,
}
impl RegexError {
pub(crate) fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
unsupported: false,
}
}
pub(crate) fn unsupported(message: impl Into<String>) -> Self {
Self {
message: message.into(),
unsupported: true,
}
}
#[must_use]
pub fn is_unsupported(&self) -> bool {
self.unsupported
}
}
impl fmt::Display for RegexError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid regular expression: {}", self.message)
}
}
pub(crate) enum Node {
Empty,
Char(u32),
Any,
Class { neg: bool, items: Vec<ClassItem> },
ClassSet { neg: bool, set: Box<ClassSetExpr> },
Start,
End,
WordBoundary { neg: bool },
Group {
index: Option<usize>,
inner: Box<Node>,
},
Modifier {
ignore_case: Option<bool>,
multiline: Option<bool>,
dotall: Option<bool>,
inner: Box<Node>,
},
Look { neg: bool, inner: Box<Node> },
LookBehind { neg: bool, inner: Box<Node> },
Backref(usize),
NamedBackref(alloc::string::String),
Concat(Vec<Node>),
Alt(Vec<Node>),
Repeat {
inner: Box<Node>,
min: usize,
max: Option<usize>,
greedy: bool,
},
}
pub(crate) enum ClassItem {
Char(u32),
Range(u32, u32),
Shorthand(Shorthand),
}
pub(crate) enum ClassSetExpr {
Union(Vec<ClassSetExpr>),
Intersection(Vec<ClassSetExpr>),
Difference(Vec<ClassSetExpr>),
Items(Vec<ClassItem>),
Strings(Vec<Vec<u32>>),
Negated(Box<ClassSetExpr>),
}
#[derive(Clone, Copy)]
pub(crate) enum Shorthand {
Digit,
NotDigit,
Word,
NotWord,
Space,
NotSpace,
Property(super::props::PropEscape, bool),
}
pub(crate) type GroupNames = Vec<(usize, alloc::string::String)>;
pub(crate) fn parse(
pattern: &str,
unicode: bool,
unicode_sets: bool,
) -> Result<(Node, usize, GroupNames), RegexError> {
let chars: Vec<char> = pattern.chars().collect();
let has_named = unicode || scan_has_named_group(&chars);
let mut p = Parser {
chars,
pos: 0,
group_count: 0,
group_names: Vec::new(),
depth: 0,
unicode,
unicode_sets,
in_negated_class: false,
branch_path: Vec::new(),
next_alt_id: 0,
decl_paths: Vec::new(),
named_refs: Vec::new(),
numeric_refs: Vec::new(),
pattern_has_named_groups: has_named,
};
let node = p.parse_alt()?;
if p.pos != p.chars.len() {
return Err(RegexError::new(alloc::format!(
"unexpected `{}`",
p.chars[p.pos]
)));
}
for r in &p.named_refs {
if !p.group_names.iter().any(|(_, n)| n == r) {
return Err(RegexError::new(alloc::format!(
"named backreference to undefined group `{r}`"
)));
}
}
if unicode {
for &n in &p.numeric_refs {
if n > p.group_count {
return Err(RegexError::new(alloc::format!(
"backreference to nonexistent group \\{n}"
)));
}
}
}
Ok((node, p.group_count, p.group_names))
}
struct Parser {
chars: Vec<char>,
pos: usize,
group_count: usize,
group_names: Vec<(usize, alloc::string::String)>,
depth: u32,
unicode: bool,
unicode_sets: bool,
in_negated_class: bool,
branch_path: Vec<(u32, u32)>,
next_alt_id: u32,
decl_paths: Vec<(alloc::string::String, Vec<(u32, u32)>)>,
named_refs: Vec<alloc::string::String>,
numeric_refs: Vec<usize>,
pattern_has_named_groups: bool,
}
fn scan_has_named_group(chars: &[char]) -> bool {
let mut i = 0;
let mut in_class = false;
while i < chars.len() {
match chars[i] {
'\\' => {
i += 2;
continue;
}
'[' => in_class = true,
']' => in_class = false,
'(' if !in_class
&& chars.get(i + 1) == Some(&'?')
&& chars.get(i + 2) == Some(&'<')
&& !matches!(chars.get(i + 3), Some('=') | Some('!')) =>
{
return true;
}
_ => {}
}
i += 1;
}
false
}
fn paths_mutually_exclusive(a: &[(u32, u32)], b: &[(u32, u32)]) -> bool {
for &(aid, abr) in a {
if let Some(&(_, bbr)) = b.iter().find(|&&(bid, _)| bid == aid)
&& abr != bbr
{
return true;
}
}
false
}
impl Parser {
fn peek(&self) -> Option<char> {
self.chars.get(self.pos).copied()
}
fn bump(&mut self) -> Option<char> {
let c = self.peek();
if c.is_some() {
self.pos += 1;
}
c
}
fn eat(&mut self, c: char) -> bool {
if self.peek() == Some(c) {
self.pos += 1;
true
} else {
false
}
}
fn parse_alt(&mut self) -> Result<Node, RegexError> {
let alt_id = self.next_alt_id;
self.next_alt_id += 1;
self.branch_path.push((alt_id, 0));
let mut branches = alloc::vec![self.parse_concat()?];
while self.eat('|') {
if let Some(last) = self.branch_path.last_mut() {
last.1 += 1;
}
branches.push(self.parse_concat()?);
}
self.branch_path.pop();
Ok(if branches.len() == 1 {
branches.pop().unwrap()
} else {
Node::Alt(branches)
})
}
fn parse_concat(&mut self) -> Result<Node, RegexError> {
let mut nodes = Vec::new();
while let Some(c) = self.peek() {
if c == '|' || c == ')' {
break;
}
nodes.push(self.parse_quantified()?);
}
Ok(match nodes.len() {
0 => Node::Empty,
1 => nodes.pop().unwrap(),
_ => Node::Concat(nodes),
})
}
fn parse_quantified(&mut self) -> Result<Node, RegexError> {
let atom = self.parse_atom()?;
let (min, max) = match self.peek() {
Some('*') => {
self.pos += 1;
(0, None)
}
Some('+') => {
self.pos += 1;
(1, None)
}
Some('?') => {
self.pos += 1;
(0, Some(1))
}
Some('{') => match self.try_parse_brace()? {
Some(bounds) => bounds,
None if self.unicode => {
return Err(RegexError::new("lone `{` quantifier"));
}
None => return Ok(atom),
},
_ => return Ok(atom),
};
let greedy = !self.eat('?');
match &atom {
Node::LookBehind { .. } => {
return Err(RegexError::new(
"a lookbehind assertion is not quantifiable",
));
}
Node::Look { .. } if self.unicode => {
return Err(RegexError::new(
"a lookahead assertion is not quantifiable in unicode mode",
));
}
Node::Start | Node::End | Node::WordBoundary { .. } if self.unicode => {
return Err(RegexError::new("this assertion is not quantifiable"));
}
_ => {}
}
Ok(Node::Repeat {
inner: Box::new(atom),
min,
max,
greedy,
})
}
fn try_parse_brace(&mut self) -> Result<Option<(usize, Option<usize>)>, RegexError> {
let save = self.pos;
self.pos += 1; let min = self.parse_int()?;
let Some(min) = min else {
self.pos = save;
return Ok(None);
};
let max = if self.eat(',') {
if self.peek() == Some('}') {
None
} else {
match self.parse_int()? {
Some(m) => Some(m),
None => {
self.pos = save;
return Ok(None);
}
}
}
} else {
Some(min)
};
if !self.eat('}') {
self.pos = save;
return Ok(None);
}
if let Some(m) = max
&& min > m
{
return Err(RegexError::new(
"quantifier range out of order (min greater than max)",
));
}
Ok(Some((min, max)))
}
fn parse_int(&mut self) -> Result<Option<usize>, RegexError> {
let start = self.pos;
let mut value: usize = 0;
let mut overflow = false;
while let Some(c) = self.peek() {
if let Some(d) = c.to_digit(10) {
value = value.saturating_mul(10).saturating_add(d as usize);
if value > MAX_QUANT {
overflow = true;
}
self.pos += 1;
} else {
break;
}
}
if self.pos == start {
Ok(None)
} else if overflow {
Err(RegexError::new("quantifier bound too large"))
} else {
Ok(Some(value))
}
}
fn parse_atom(&mut self) -> Result<Node, RegexError> {
match self.peek() {
Some('(') => self.parse_group(),
Some('[') if self.unicode_sets => self.parse_class_set(),
Some('[') => self.parse_class(),
Some('.') => {
self.pos += 1;
Ok(Node::Any)
}
Some('^') => {
self.pos += 1;
Ok(Node::Start)
}
Some('$') => {
self.pos += 1;
Ok(Node::End)
}
Some('\\') => self.parse_escape(),
Some(c @ ('*' | '+' | '?')) => Err(RegexError::new(alloc::format!(
"nothing to repeat before `{c}`"
))),
Some('{') => {
if self.unicode {
return Err(RegexError::new("lone `{` is not allowed in unicode mode"));
}
match self.try_parse_brace()? {
Some(_) => Err(RegexError::new("nothing to repeat before `{`")),
None => {
self.pos += 1;
Ok(Node::Char(u32::from('{')))
}
}
}
Some(c @ ('}' | ']')) if self.unicode => Err(RegexError::new(alloc::format!(
"lone `{c}` is not allowed in unicode mode"
))),
Some(c) => {
self.pos += 1;
Ok(Node::Char(c as u32))
}
None => Ok(Node::Empty),
}
}
fn parse_group(&mut self) -> Result<Node, RegexError> {
self.depth += 1;
if self.depth > MAX_PARSE_DEPTH {
self.depth -= 1;
return Err(RegexError::new("pattern nested too deeply"));
}
let result = self.parse_group_inner();
self.depth -= 1;
result
}
fn parse_group_inner(&mut self) -> Result<Node, RegexError> {
self.pos += 1; let index = if self.peek() == Some('?') {
if self.chars.get(self.pos + 1) == Some(&':') {
self.pos += 2;
None
} else if matches!(self.chars.get(self.pos + 1), Some('=' | '!')) {
let neg = self.chars.get(self.pos + 1) == Some(&'!');
self.pos += 2; let inner = self.parse_alt()?;
if !self.eat(')') {
return Err(RegexError::new("unterminated lookahead `(?=`"));
}
return Ok(Node::Look {
neg,
inner: Box::new(inner),
});
} else if self.chars.get(self.pos + 1) == Some(&'<')
&& matches!(self.chars.get(self.pos + 2), Some('=' | '!'))
{
let neg = self.chars.get(self.pos + 2) == Some(&'!');
self.pos += 3; let inner = self.parse_alt()?;
if !self.eat(')') {
return Err(RegexError::new("unterminated lookbehind `(?<=`"));
}
return Ok(Node::LookBehind {
neg,
inner: Box::new(inner),
});
} else if matches!(self.chars.get(self.pos + 1), Some('i' | 'm' | 's' | '-')) {
return self.parse_modifier_group();
} else if self.chars.get(self.pos + 1) == Some(&'<') {
self.pos += 2; let name = self.parse_group_name()?;
for (prev, path) in &self.decl_paths {
if *prev == name && !paths_mutually_exclusive(path, &self.branch_path) {
return Err(RegexError::new(alloc::format!(
"duplicate capture group name `{name}`"
)));
}
}
self.decl_paths
.push((name.clone(), self.branch_path.clone()));
self.group_count += 1;
self.group_names.push((self.group_count, name));
Some(self.group_count)
} else {
return Err(RegexError::new("invalid group extension `(?…)`"));
}
} else {
self.group_count += 1;
Some(self.group_count)
};
let inner = self.parse_alt()?;
if !self.eat(')') {
return Err(RegexError::new("unterminated group `(`"));
}
Ok(Node::Group {
index,
inner: Box::new(inner),
})
}
fn parse_modifier_group(&mut self) -> Result<Node, RegexError> {
self.pos += 1; let read_flags = |p: &mut Self| -> Result<(bool, bool, bool), RegexError> {
let (mut i, mut m, mut s) = (false, false, false);
loop {
match p.peek() {
Some('i') => {
if i {
return Err(RegexError::new("duplicate modifier flag `i`"));
}
i = true;
p.pos += 1;
}
Some('m') => {
if m {
return Err(RegexError::new("duplicate modifier flag `m`"));
}
m = true;
p.pos += 1;
}
Some('s') => {
if s {
return Err(RegexError::new("duplicate modifier flag `s`"));
}
s = true;
p.pos += 1;
}
Some('-') | Some(':') => break,
_ => return Err(RegexError::new("invalid inline modifier")),
}
}
Ok((i, m, s))
};
let (add_i, add_m, add_s) = read_flags(self)?;
let had_dash = self.eat('-');
let (rem_i, rem_m, rem_s) = if had_dash {
read_flags(self)?
} else {
(false, false, false)
};
if !self.eat(':') {
return Err(RegexError::new("expected `:` in inline modifier group"));
}
if (add_i && rem_i) || (add_m && rem_m) || (add_s && rem_s) {
return Err(RegexError::new("modifier flag both added and removed"));
}
if !(add_i || add_m || add_s || rem_i || rem_m || rem_s) {
return Err(RegexError::new("empty inline modifier group"));
}
let merge = |add: bool, rem: bool| -> Option<bool> {
if add {
Some(true)
} else if rem {
Some(false)
} else {
None
}
};
let inner = self.parse_alt()?;
if !self.eat(')') {
return Err(RegexError::new("unterminated inline modifier group"));
}
Ok(Node::Modifier {
ignore_case: merge(add_i, rem_i),
multiline: merge(add_m, rem_m),
dotall: merge(add_s, rem_s),
inner: Box::new(inner),
})
}
fn parse_group_name(&mut self) -> Result<alloc::string::String, RegexError> {
let mut name = alloc::string::String::new();
let mut first = true;
loop {
let ch = match self.peek() {
Some('>') => {
self.pos += 1;
break;
}
Some('\\') if self.chars.get(self.pos + 1) == Some(&'u') => {
self.pos += 2; let cp = self.parse_unicode_escape()?;
char::from_u32(cp)
.ok_or_else(|| RegexError::new("invalid code point in group name"))?
}
Some(c) => {
self.pos += 1;
c
}
None => return Err(RegexError::new("unterminated group name")),
};
let ok = if first {
is_regexp_id_start(ch)
} else {
is_regexp_id_part(ch)
};
if !ok {
return Err(RegexError::new("invalid character in capture group name"));
}
name.push(ch);
first = false;
}
if name.is_empty() {
return Err(RegexError::new("empty capture group name"));
}
Ok(name)
}
fn parse_escape(&mut self) -> Result<Node, RegexError> {
self.pos += 1; let Some(c) = self.bump() else {
return Err(RegexError::new("trailing backslash"));
};
Ok(match c {
'd' => class_shorthand(Shorthand::Digit),
'D' => class_shorthand(Shorthand::NotDigit),
'w' => class_shorthand(Shorthand::Word),
'W' => class_shorthand(Shorthand::NotWord),
's' => class_shorthand(Shorthand::Space),
'S' => class_shorthand(Shorthand::NotSpace),
'b' => Node::WordBoundary { neg: false },
'B' => Node::WordBoundary { neg: true },
d if d.is_ascii_digit() && d != '0' => {
let mut n = (d as u8 - b'0') as usize;
if self.unicode {
while let Some(nd) = self.peek().and_then(|c| c.to_digit(10)) {
n = n.saturating_mul(10).saturating_add(nd as usize);
self.pos += 1;
}
}
self.numeric_refs.push(n);
Node::Backref(n)
}
'k' if self.pattern_has_named_groups && self.chars.get(self.pos) == Some(&'<') => {
self.eat('<');
let name = self.parse_group_name()?;
self.named_refs.push(name.clone());
Node::NamedBackref(name)
}
'u' => Node::Char(self.parse_unicode_escape()?),
'x' => Node::Char(self.parse_hex_escape(2)?),
'p' if self.unicode => {
class_shorthand(Shorthand::Property(self.parse_property(false)?, false))
}
'P' if self.unicode => {
class_shorthand(Shorthand::Property(self.parse_property(true)?, true))
}
'c' if self.peek().is_some_and(|c| c.is_ascii_alphabetic()) => {
let letter = self.bump().unwrap();
Node::Char((letter.to_ascii_uppercase() as u32 - 'A' as u32) + 1)
}
'c' if self.unicode => {
return Err(RegexError::new("invalid `\\c` control escape"));
}
'c' => Node::Char('\\' as u32),
'f' | 'n' | 'r' | 't' | 'v' => Node::Char(escape_char(c) as u32),
'0' if !self.peek().is_some_and(|c| c.is_ascii_digit()) => Node::Char(0),
other => {
if self.unicode && !is_u_identity_escape(other) {
return Err(RegexError::new("invalid identity escape in unicode mode"));
}
Node::Char(escape_char(other) as u32)
}
})
}
fn parse_property(&mut self, negated: bool) -> Result<super::props::PropEscape, RegexError> {
if !self.eat('{') {
return Err(RegexError::new("expected `{` after `\\p`"));
}
let mut name = alloc::string::String::new();
let mut value: Option<alloc::string::String> = None;
loop {
match self.bump() {
Some('}') => break,
Some('=') if value.is_none() => value = Some(alloc::string::String::new()),
Some(c) => match &mut value {
Some(v) => v.push(c),
None => name.push(c),
},
None => return Err(RegexError::new("unterminated `\\p{…}`")),
}
}
let is_lone = value.is_none();
let resolved = match value {
Some(value) => {
if name.is_empty() || value.is_empty() {
None
} else {
super::props::resolve_pair(&name, &value)
}
}
None => {
if name.is_empty() {
None
} else {
super::props::resolve_lone(&name)
}
}
};
let v_mode = self.unicode_sets;
let in_neg_class = self.in_negated_class;
resolved.ok_or_else(|| {
if is_lone && super::props::is_property_of_strings(&name) {
if negated || in_neg_class || !v_mode {
RegexError::new(
"a property of strings is only valid, un-negated, with the `v` flag",
)
} else {
RegexError::unsupported("unsupported `\\p{…}` property of strings")
}
} else {
RegexError::new("invalid `\\p{…}` property escape")
}
})
}
fn parse_unicode_escape(&mut self) -> Result<u32, RegexError> {
if self.eat('{') {
let mut v: u32 = 0;
let mut any = false;
while let Some(c) = self.peek() {
if c == '}' {
self.pos += 1;
break;
}
let d = c
.to_digit(16)
.ok_or_else(|| RegexError::new("invalid `\\u{…}` escape"))?;
v = v.saturating_mul(16).saturating_add(d);
self.pos += 1;
any = true;
}
if !any {
return Err(RegexError::new("empty `\\u{}` escape"));
}
if v > 0x10_FFFF {
return Err(RegexError::new("escape is not a valid code point"));
}
return Ok(v);
}
let hi = self.parse_hex_digits(4)?;
if self.unicode
&& (0xD800..=0xDBFF).contains(&hi)
&& self.chars.get(self.pos) == Some(&'\\')
&& self.chars.get(self.pos + 1) == Some(&'u')
{
let save = self.pos;
self.pos += 2; let lo = self.parse_hex_digits(4)?;
if (0xDC00..=0xDFFF).contains(&lo) {
return Ok(0x10000 + ((hi - 0xD800) << 10) + (lo - 0xDC00));
}
self.pos = save;
}
Ok(hi)
}
fn parse_hex_escape(&mut self, n: usize) -> Result<u32, RegexError> {
self.parse_hex_digits(n)
}
fn parse_hex_digits(&mut self, n: usize) -> Result<u32, RegexError> {
let mut v: u32 = 0;
for _ in 0..n {
let c = self
.bump()
.ok_or_else(|| RegexError::new("incomplete hex escape"))?;
let d = c
.to_digit(16)
.ok_or_else(|| RegexError::new("invalid hex digit in escape"))?;
v = v * 16 + d;
}
Ok(v)
}
fn parse_class(&mut self) -> Result<Node, RegexError> {
self.pos += 1; let neg = self.eat('^');
let mut items = Vec::new();
loop {
match self.peek() {
None => return Err(RegexError::new("unterminated character class `[`")),
Some(']') => {
self.pos += 1;
break;
}
Some('\\') => {
self.pos += 1;
let Some(e) = self.bump() else {
return Err(RegexError::new("trailing backslash in class"));
};
let is_class_escape = matches!(e, 'd' | 'D' | 'w' | 'W' | 's' | 'S')
|| (self.unicode && matches!(e, 'p' | 'P'));
if self.unicode
&& is_class_escape
&& self.peek() == Some('-')
&& self.chars.get(self.pos + 1).is_some_and(|&n| n != ']')
{
return Err(RegexError::new(
"invalid character class range with a class-escape endpoint",
));
}
match e {
'd' => items.push(ClassItem::Shorthand(Shorthand::Digit)),
'D' => items.push(ClassItem::Shorthand(Shorthand::NotDigit)),
'w' => items.push(ClassItem::Shorthand(Shorthand::Word)),
'W' => items.push(ClassItem::Shorthand(Shorthand::NotWord)),
's' => items.push(ClassItem::Shorthand(Shorthand::Space)),
'S' => items.push(ClassItem::Shorthand(Shorthand::NotSpace)),
'p' if self.unicode => {
items.push(ClassItem::Shorthand(Shorthand::Property(
self.parse_property(false)?,
false,
)));
self.reject_property_range_endpoint()?;
}
'P' if self.unicode => {
items.push(ClassItem::Shorthand(Shorthand::Property(
self.parse_property(true)?,
true,
)));
self.reject_property_range_endpoint()?;
}
'u' => {
let ch = self.parse_unicode_escape()?;
self.push_class_member(&mut items, ch)?;
}
'x' => {
let ch = self.parse_hex_escape(2)?;
self.push_class_member(&mut items, ch)?;
}
other => {
self.push_class_member(&mut items, escape_char(other) as u32)?;
}
}
}
Some(c) => {
self.pos += 1;
self.push_class_member(&mut items, c as u32)?;
}
}
}
Ok(Node::Class { neg, items })
}
fn reject_property_range_endpoint(&self) -> Result<(), RegexError> {
if self.peek() == Some('-') && self.chars.get(self.pos + 1).is_some_and(|&n| n != ']') {
return Err(RegexError::new(
"invalid character class range with a class-escape endpoint",
));
}
Ok(())
}
fn push_class_member(&mut self, items: &mut Vec<ClassItem>, c: u32) -> Result<(), RegexError> {
if self.peek() == Some('-') && self.chars.get(self.pos + 1).is_some_and(|&n| n != ']') {
if self.unicode
&& self.chars.get(self.pos + 1) == Some(&'\\')
&& self
.chars
.get(self.pos + 2)
.is_some_and(|&n| matches!(n, 'd' | 'D' | 'w' | 'W' | 's' | 'S' | 'p' | 'P'))
{
return Err(RegexError::new(
"invalid character class range with a class-escape endpoint",
));
}
self.pos += 1; let hi = if self.peek() == Some('\\') {
self.pos += 1;
match self.bump() {
Some('u') => self.parse_unicode_escape().unwrap_or(c),
Some('x') => self.parse_hex_escape(2).unwrap_or(c),
Some(e) => escape_char(e) as u32,
None => '\\' as u32,
}
} else {
self.bump().map_or(c, |ch| ch as u32)
};
if c > hi {
return Err(RegexError::new(
"character class range out of order (low greater than high)",
));
}
items.push(ClassItem::Range(c, hi));
} else {
items.push(ClassItem::Char(c));
}
Ok(())
}
fn parse_class_set(&mut self) -> Result<Node, RegexError> {
self.depth += 1;
if self.depth > MAX_PARSE_DEPTH {
self.depth -= 1;
return Err(RegexError::new("pattern nested too deeply"));
}
let r = self.parse_class_set_inner();
self.depth -= 1;
r
}
fn parse_class_set_inner(&mut self) -> Result<Node, RegexError> {
self.pos += 1; let neg = self.eat('^');
let saved_neg_class = self.in_negated_class;
self.in_negated_class = saved_neg_class || neg;
let set = self.parse_class_set_expression();
self.in_negated_class = saved_neg_class;
let set = set?;
if !self.eat(']') {
return Err(RegexError::new("unterminated character class `[`"));
}
Ok(Node::ClassSet {
neg,
set: Box::new(set),
})
}
fn parse_class_set_expression(&mut self) -> Result<ClassSetExpr, RegexError> {
if self.peek() == Some(']') {
return Ok(ClassSetExpr::Items(Vec::new()));
}
let first = self.parse_class_set_operand()?;
match self.peek_set_operator() {
SetOp::Intersection => {
let mut operands = alloc::vec![first];
while self.eat_set_operator(SetOp::Intersection)? {
operands.push(self.parse_class_set_operand()?);
}
Ok(ClassSetExpr::Intersection(operands))
}
SetOp::Difference => {
let mut operands = alloc::vec![first];
while self.eat_set_operator(SetOp::Difference)? {
operands.push(self.parse_class_set_operand()?);
}
Ok(ClassSetExpr::Difference(operands))
}
SetOp::Union => {
let mut union: Vec<ClassSetExpr> = Vec::new();
let mut pending = Some(first);
loop {
match self.peek() {
Some(']') | None => break,
Some('-') if self.chars.get(self.pos + 1) != Some(&'-') => {
let lo = match pending.take() {
Some(ClassSetExpr::Items(ref items))
if matches!(items.as_slice(), [ClassItem::Char(_)]) =>
{
if let [ClassItem::Char(c)] = items.as_slice() {
*c
} else {
unreachable!()
}
}
_ => return Err(RegexError::new("invalid class set range")),
};
self.pos += 1; let hi = self.parse_class_set_char_operand()?;
if hi < lo {
return Err(RegexError::new("class set range out of order"));
}
union.push(ClassSetExpr::Items(alloc::vec![ClassItem::Range(lo, hi)]));
}
_ => {
if let Some(p) = pending.take() {
union.push(p);
}
pending = Some(self.parse_class_set_operand()?);
}
}
}
if let Some(p) = pending.take() {
union.push(p);
}
Ok(ClassSetExpr::Union(union))
}
}
}
fn peek_set_operator(&self) -> SetOp {
if self.peek() == Some('&') && self.chars.get(self.pos + 1) == Some(&'&') {
SetOp::Intersection
} else if self.peek() == Some('-') && self.chars.get(self.pos + 1) == Some(&'-') {
SetOp::Difference
} else {
SetOp::Union
}
}
fn eat_set_operator(&mut self, op: SetOp) -> Result<bool, RegexError> {
if self.peek_set_operator() == op {
self.pos += 2; if self.peek() == Some(']') {
return Err(RegexError::new("missing operand after set operator"));
}
Ok(true)
} else {
Ok(false)
}
}
fn parse_class_set_operand(&mut self) -> Result<ClassSetExpr, RegexError> {
match self.peek() {
Some('[') => {
let nested = self.parse_class_set()?;
let Node::ClassSet { neg, set } = nested else {
unreachable!("parse_class_set returns a ClassSet node");
};
Ok(if neg {
ClassSetExpr::Negated(set)
} else {
*set
})
}
Some('\\') => self.parse_class_set_escape(),
_ => {
let c = self.parse_class_set_char_operand()?;
Ok(ClassSetExpr::Items(alloc::vec![ClassItem::Char(c)]))
}
}
}
fn parse_class_set_escape(&mut self) -> Result<ClassSetExpr, RegexError> {
self.pos += 1; let Some(e) = self.peek() else {
return Err(RegexError::new("trailing backslash in class"));
};
match e {
'q' => {
self.pos += 1; self.parse_q_strings()
}
'd' | 'D' | 'w' | 'W' | 's' | 'S' => {
self.pos += 1;
let sh = match e {
'd' => Shorthand::Digit,
'D' => Shorthand::NotDigit,
'w' => Shorthand::Word,
'W' => Shorthand::NotWord,
's' => Shorthand::Space,
_ => Shorthand::NotSpace,
};
Ok(ClassSetExpr::Items(alloc::vec![ClassItem::Shorthand(sh)]))
}
'p' | 'P' => {
self.pos += 1; let prop = self.parse_property(e == 'P')?;
Ok(ClassSetExpr::Items(alloc::vec![ClassItem::Shorthand(
Shorthand::Property(prop, e == 'P'),
)]))
}
_ => {
let c = self.parse_class_set_char_after_backslash()?;
Ok(ClassSetExpr::Items(alloc::vec![ClassItem::Char(c)]))
}
}
}
fn parse_q_strings(&mut self) -> Result<ClassSetExpr, RegexError> {
if !self.eat('{') {
return Err(RegexError::new("expected `{` after `\\q`"));
}
let mut strings: Vec<Vec<u32>> = Vec::new();
let mut cur: Vec<u32> = Vec::new();
loop {
match self.peek() {
None => return Err(RegexError::new("unterminated `\\q{…}`")),
Some('}') => {
self.pos += 1;
strings.push(core::mem::take(&mut cur));
break;
}
Some('|') => {
self.pos += 1;
strings.push(core::mem::take(&mut cur));
}
Some('\\') => {
let c = self.parse_class_set_char_after_backslash_consuming()?;
cur.push(c);
}
Some(_) => {
let c = self.parse_class_set_char_operand()?;
cur.push(c);
}
}
}
Ok(ClassSetExpr::Strings(strings))
}
fn parse_class_set_char_operand(&mut self) -> Result<u32, RegexError> {
match self.peek() {
Some('\\') => {
self.pos += 1;
self.parse_class_set_char_after_backslash()
}
Some(c) => {
if matches!(c, '(' | ')' | '[' | ']' | '{' | '}' | '/' | '|') {
return Err(RegexError::new("unescaped reserved character in class set"));
}
if is_class_set_reserved_double(c) && self.chars.get(self.pos + 1) == Some(&c) {
return Err(RegexError::new("reserved double punctuator in class set"));
}
self.pos += 1;
Ok(c as u32)
}
None => Err(RegexError::new("unterminated character class `[`")),
}
}
fn parse_class_set_char_after_backslash(&mut self) -> Result<u32, RegexError> {
let Some(e) = self.bump() else {
return Err(RegexError::new("trailing backslash in class"));
};
Ok(match e {
'u' => self.parse_unicode_escape()?,
'x' => self.parse_hex_escape(2)?,
'f' | 'n' | 'r' | 't' | 'v' => escape_char(e) as u32,
'0' if !self.peek().is_some_and(|c| c.is_ascii_digit()) => 0,
'c' if self.peek().is_some_and(|c| c.is_ascii_alphabetic()) => {
let letter = self.bump().unwrap();
(letter.to_ascii_uppercase() as u32 - 'A' as u32) + 1
}
'b' => 0x08, other => {
if is_class_set_identity_escape(other) {
other as u32
} else {
return Err(RegexError::new("invalid class set escape"));
}
}
})
}
fn parse_class_set_char_after_backslash_consuming(&mut self) -> Result<u32, RegexError> {
self.pos += 1; self.parse_class_set_char_after_backslash()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum SetOp {
Union,
Intersection,
Difference,
}
fn is_class_set_reserved_double(c: char) -> bool {
matches!(
c,
'&' | '!'
| '#'
| '$'
| '%'
| '*'
| '+'
| ','
| '.'
| ':'
| ';'
| '<'
| '='
| '>'
| '?'
| '@'
| '^'
| '`'
| '~'
)
}
fn is_class_set_identity_escape(c: char) -> bool {
matches!(
c,
'(' | ')'
| '['
| ']'
| '{'
| '}'
| '/'
| '-'
| '\\'
| '|'
| '&'
| '!'
| '#'
| '$'
| '%'
| '*'
| '+'
| ','
| '.'
| ':'
| ';'
| '<'
| '='
| '>'
| '?'
| '@'
| '^'
| '`'
| '~'
)
}
fn class_shorthand(s: Shorthand) -> Node {
Node::Class {
neg: false,
items: alloc::vec![ClassItem::Shorthand(s)],
}
}
fn is_regexp_id_start(ch: char) -> bool {
ch == '$' || ch == '_' || crate::lexer::is_identifier_start_char(ch)
}
fn is_regexp_id_part(ch: char) -> bool {
ch == '$' || ch == '_' || ch == '\u{200C}' || ch == '\u{200D}' || is_id_continue(ch)
}
fn is_id_continue(ch: char) -> bool {
if ch.is_ascii() {
return ch.is_ascii_alphanumeric() || ch == '_';
}
#[cfg(feature = "intl")]
{
use intl::unicode::category::GeneralCategory as Gc;
let gc = intl::unicode::general_category(ch);
gc.is_letter()
|| gc.is_mark()
|| matches!(
gc,
Gc::LetterNumber | Gc::DecimalNumber | Gc::ConnectorPunctuation
)
}
#[cfg(not(feature = "intl"))]
{
ch.is_alphanumeric()
}
}
fn is_u_identity_escape(c: char) -> bool {
matches!(
c,
'^' | '$' | '\\' | '.' | '*' | '+' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '/'
)
}
fn escape_char(c: char) -> char {
match c {
'n' => '\n',
't' => '\t',
'r' => '\r',
'f' => '\u{0C}',
'v' => '\u{0B}',
'0' => '\0',
other => other, }
}