Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions regex-syntax/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ unicode-perl = []
unicode-script = []
unicode-segment = []

look-behinds = []

[dependencies]
arbitrary = { version = "1.3.0", features = ["derive"], optional = true }

Expand Down
69 changes: 69 additions & 0 deletions regex-syntax/src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ pub enum ErrorKind {
///
/// The span of this error corresponds to the unclosed parenthesis.
GroupUnclosed,
/// An unclosed look-around, e.g., `(?<ab`.
///
/// The span of this error corresponds to the unclosed parenthesis.
#[cfg(feature = "look-behinds")]
LookAroundUnclosed,
/// An unopened group, e.g., `ab)`.
GroupUnopened,
/// The nest limit was exceeded. The limit stored here is the limit
Expand Down Expand Up @@ -186,7 +191,19 @@ pub enum ErrorKind {
/// not necessarily limited to, `(?=re)`, `(?!re)`, `(?<=re)` and
/// `(?<!re)`. Note that all of these syntaxes are otherwise invalid; this
/// error is used to improve the user experience.
#[cfg(not(feature = "look-behinds"))]
UnsupportedLookAround,
/// When syntax similar to PCRE's look-ahead is used, this error is
/// returned. Some example syntaxes that are rejected include, but are
/// not necessarily limited to, `(?=re)` and `(?!re)`.
/// Note that all of these syntaxes are otherwise invalid; this
/// error is used to improve the user experience.
#[cfg(feature = "look-behinds")]
UnsupportedLookAhead,
/// When a capture group is used in a look-behind assertion, this error is
/// returned. Look-behind assertions do not support capturing groups.
#[cfg(feature = "look-behinds")]
UnsupportedCaptureInLookBehind,
}

#[cfg(feature = "std")]
Expand Down Expand Up @@ -251,6 +268,8 @@ impl core::fmt::Display for ErrorKind {
GroupNameInvalid => write!(f, "invalid capture group character"),
GroupNameUnexpectedEof => write!(f, "unclosed capture group name"),
GroupUnclosed => write!(f, "unclosed group"),
#[cfg(feature = "look-behinds")]
LookAroundUnclosed => write!(f, "unclosed look-around"),
GroupUnopened => write!(f, "unopened group"),
NestLimitExceeded(limit) => write!(
f,
Expand Down Expand Up @@ -301,11 +320,18 @@ impl core::fmt::Display for ErrorKind {
UnsupportedBackreference => {
write!(f, "backreferences are not supported")
}
#[cfg(not(feature = "look-behinds"))]
UnsupportedLookAround => write!(
f,
"look-around, including look-ahead and look-behind, \
is not supported"
),
#[cfg(feature = "look-behinds")]
UnsupportedLookAhead => write!(f, "look-aheads are not supported"),
#[cfg(feature = "look-behinds")]
UnsupportedCaptureInLookBehind => {
write!(f, "capture groups are not supported in look-behinds")
}
}
}
}
Expand Down Expand Up @@ -477,6 +503,9 @@ pub enum Ast {
Dot(Box<Span>),
/// A single zero-width assertion.
Assertion(Box<Assertion>),
/// A single look-around regular expression.
#[cfg(feature = "look-behinds")]
LookAround(Box<LookAround>),
/// A single Unicode character class, e.g., `\pL` or `\p{Greek}`.
ClassUnicode(Box<ClassUnicode>),
/// A single perl character class, e.g., `\d` or `\W`.
Expand Down Expand Up @@ -521,6 +550,12 @@ impl Ast {
Ast::Assertion(Box::new(e))
}

/// Create a "look-around" AST item.
#[cfg(feature = "look-behinds")]
pub fn lookaround(e: LookAround) -> Ast {
Ast::LookAround(Box::new(e))
}

/// Create a "Unicode class" AST item.
pub fn class_unicode(e: ClassUnicode) -> Ast {
Ast::ClassUnicode(Box::new(e))
Expand Down Expand Up @@ -564,6 +599,8 @@ impl Ast {
Ast::Literal(ref x) => &x.span,
Ast::Dot(ref span) => span,
Ast::Assertion(ref x) => &x.span,
#[cfg(feature = "look-behinds")]
Ast::LookAround(ref x) => &x.span,
Ast::ClassUnicode(ref x) => &x.span,
Ast::ClassPerl(ref x) => &x.span,
Ast::ClassBracketed(ref x) => &x.span,
Expand Down Expand Up @@ -598,6 +635,8 @@ impl Ast {
| Ast::Group(_)
| Ast::Alternation(_)
| Ast::Concat(_) => true,
#[cfg(feature = "look-behinds")]
Ast::LookAround(_) => true,
}
}
}
Expand Down Expand Up @@ -1342,6 +1381,30 @@ pub enum AssertionKind {
WordBoundaryEndHalf,
}

/// A single zero-width look-around.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg(feature = "look-behinds")]
pub struct LookAround {
/// The span of this look-around.
pub span: Span,
/// The look-around kind, e.g. negative/positive look-behind.
pub kind: LookAroundKind,
/// The regular expression inside the look-around.
pub ast: Box<Ast>,
}

/// A look-around kind.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[cfg(feature = "look-behinds")]
pub enum LookAroundKind {
/// `(?<=...)`
PositiveLookBehind,
/// `(?<!...)`
NegativeLookBehind,
}

/// A repetition operation applied to a regular expression.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
Expand Down Expand Up @@ -1647,6 +1710,8 @@ impl Drop for Ast {
| Ast::ClassBracketed(_) => return,
Ast::Repetition(ref x) if !x.ast.has_subexprs() => return,
Ast::Group(ref x) if !x.ast.has_subexprs() => return,
#[cfg(feature = "look-behinds")]
Ast::LookAround(ref x) if !x.ast.has_subexprs() => return,
Ast::Alternation(ref x) if x.asts.is_empty() => return,
Ast::Concat(ref x) if x.asts.is_empty() => return,
_ => {}
Expand All @@ -1673,6 +1738,10 @@ impl Drop for Ast {
Ast::Group(ref mut x) => {
stack.push(mem::replace(&mut x.ast, empty_ast()));
}
#[cfg(feature = "look-behinds")]
Ast::LookAround(ref mut x) => {
stack.push(mem::replace(&mut x.ast, empty_ast()));
}
Ast::Alternation(ref mut x) => {
stack.extend(x.asts.drain(..));
}
Expand Down
Loading
Loading