use std::sync::{Arc, OnceLock, RwLock};
use crate::dfa::{CacheCeilingExceeded, EagerDfa, EagerScanBudgetExceeded, LazyDfa};
use crate::error::Result;
use crate::hir::Hir;
use crate::literal::{extract_literals, Prefilter, ReverseSuffixSearch};
use crate::nfa::tagged::TaggedNfaEngine;
use crate::nfa::{self, Nfa};
use crate::vm::backtracking::{BudgetExhausted, CaptureSlots};
use crate::vm::{
BacktrackingVm, CodepointClassMatcher, OnePass, PikeVm, PikeVmContext, ShiftOr, ShiftOrWide,
};
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
use crate::jit;
use super::dfa_pool::LazyDfaPool;
use super::{needs_boundary_aware_empty_match, select_engine, select_engine_from_hir, EngineType};
fn with_lazy_dfa<T>(
pool: &LazyDfaPool,
dfa: Option<&mut LazyDfa>,
search: impl FnOnce(&mut LazyDfa) -> T,
) -> T {
match dfa {
Some(lazy) => search(lazy),
None => pool.with(search),
}
}
fn lazy_dfa_or_pikevm<T>(
dfa: &mut LazyDfa,
search: impl FnOnce(&mut LazyDfa) -> std::result::Result<T, CacheCeilingExceeded>,
fallback: impl FnOnce(&PikeVm) -> T,
) -> T {
match search(dfa) {
Ok(result) => result,
Err(_) => fallback(&PikeVm::from_arc(dfa.nfa_arc())),
}
}
fn eager_scan_fallback(nfa: &Arc<Nfa>) -> LazyDfa {
let mut fallback = LazyDfa::new((**nfa).clone());
fallback.set_cache_limit(usize::MAX);
fallback
}
pub struct CompiledRegex {
inner: CompiledInner,
prefilter: Prefilter,
prefix_offset: usize,
simple_eager_scan: bool,
reverse_suffix: Option<ReverseSuffixSearch>,
capture_nfa: RwLock<Option<Nfa>>,
one_pass: OnceLock<Option<OnePass>>,
capture_vm: RwLock<Option<PikeVm>>,
capture_ctx: RwLock<Option<PikeVmContext>>,
backtracking_vm: Option<BacktrackingVm>,
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
backtracking_jit: Option<jit::BacktrackingJit>,
}
impl std::fmt::Debug for CompiledRegex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CompiledRegex")
.field("engine", &self.engine_name())
.field("prefilter", &self.prefilter)
.field("reverse_suffix", &self.reverse_suffix.is_some())
.finish_non_exhaustive()
}
}
#[allow(clippy::large_enum_variant)]
enum CompiledInner {
PikeVm(PikeVm),
ShiftOr(ShiftOr),
ShiftOrWide(ShiftOrWide),
LazyDfa(LazyDfaPool),
EagerDfa(EagerDfa, Arc<Nfa>),
CodepointClass(CodepointClassMatcher),
BacktrackingVm(BacktrackingVm),
TaggedNfaInterp(TaggedNfaEngine),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
Jit(jit::CompiledRegex),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
TaggedNfaJit(jit::TaggedNfaJit),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
Backtracking(jit::BacktrackingJit),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
JitShiftOr(jit::JitShiftOr),
}
struct PrefilterDrive {
attempts: usize,
first_candidate: usize,
}
const MAX_ATTEMPTS: usize = 64;
const MIN_SELECTIVITY: usize = 8;
enum OnePassSearch<T> {
Match(T),
NoMatch,
GaveUp(usize),
NotApplicable,
}
impl PrefilterDrive {
fn new() -> Self {
Self {
attempts: 0,
first_candidate: 0,
}
}
fn give_up(&mut self, candidate: usize) -> bool {
if self.attempts == 0 {
self.first_candidate = candidate;
}
self.attempts += 1;
self.attempts > MAX_ATTEMPTS
&& self.attempts * MIN_SELECTIVITY > candidate - self.first_candidate + 1
}
}
impl CompiledRegex {
pub fn engine_name(&self) -> &'static str {
match &self.inner {
CompiledInner::PikeVm(_) => "PikeVm",
CompiledInner::ShiftOr(_) => "ShiftOr",
CompiledInner::ShiftOrWide(_) => "ShiftOrWide",
CompiledInner::LazyDfa(_) => "LazyDfa",
CompiledInner::EagerDfa(_, _) => "EagerDfa",
CompiledInner::CodepointClass(_) => "CodepointClass",
CompiledInner::BacktrackingVm(_) => "BacktrackingVm",
CompiledInner::TaggedNfaInterp(_) => "TaggedNfa",
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Jit(_) => "Jit",
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::TaggedNfaJit(_) => "TaggedNfaJit",
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Backtracking(_) => "BacktrackingJit",
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::JitShiftOr(_) => "JitShiftOr",
}
}
fn one_pass(&self) -> Option<&OnePass> {
self.one_pass
.get_or_init(|| {
self.capture_nfa
.read()
.unwrap()
.as_ref()
.and_then(OnePass::compile)
})
.as_ref()
}
fn get_or_init_capture_vm(&self) {
if self.capture_vm.read().unwrap().is_some() {
return;
}
if let Some(nfa) = self.capture_nfa.read().unwrap().as_ref() {
let vm = PikeVm::new(nfa.clone());
let ctx = vm.create_context();
*self.capture_vm.write().unwrap() = Some(vm);
*self.capture_ctx.write().unwrap() = Some(ctx);
}
}
#[inline]
pub(crate) fn scans_for_required_literal(&self) -> bool {
self.reverse_suffix.is_some()
}
pub fn is_match(&self, input: &[u8]) -> bool {
if self.prefix_offset != 0 || self.reverse_suffix.is_some() {
return self.find(input).is_some();
}
if self.prefilter.is_full_match() {
return self.prefilter.find_full_match(input, 0).is_some();
}
if !self.prefilter.is_none() {
if self.engine_searches_single_pass() {
let first = self.prefilter.find_candidates(input).next();
return match first {
Some(first) => self.find_engine_from_boundary(input, first, None).is_some(),
None => false,
};
}
let mut drive = PrefilterDrive::new();
for candidate in self.prefilter.find_candidates(input) {
if self.is_match_at(input, candidate) {
return true;
}
if drive.give_up(candidate) {
return self
.find_engine_from_boundary(input, candidate, None)
.is_some();
}
}
return false;
}
match &self.inner {
CompiledInner::PikeVm(vm) => vm.is_match(input),
CompiledInner::ShiftOr(so) => so.is_match(input),
CompiledInner::ShiftOrWide(so) => so.is_match(input),
CompiledInner::LazyDfa(pool) => pool.with(|dfa| {
lazy_dfa_or_pikevm(
dfa,
|d| d.find(input).map(|found| found.is_some()),
|vm| vm.is_match(input),
)
}),
CompiledInner::EagerDfa(dfa, nfa) => match dfa.find(input) {
Ok(found) => found.is_some(),
Err(EagerScanBudgetExceeded) => {
let mut fallback = eager_scan_fallback(nfa);
lazy_dfa_or_pikevm(
&mut fallback,
|d| d.find(input).map(|found| found.is_some()),
|vm| vm.is_match(input),
)
}
},
CompiledInner::CodepointClass(matcher) => matcher.is_match(input),
CompiledInner::BacktrackingVm(vm) => vm.find(input).is_some(),
CompiledInner::TaggedNfaInterp(engine) => engine.is_match(input),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Jit(jit) => jit.is_match(input),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::TaggedNfaJit(engine) => engine.is_match(input),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Backtracking(jit) => jit.is_match(input),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::JitShiftOr(jit) => jit.find(input).is_some(),
}
}
#[inline]
fn engine_searches_single_pass(&self) -> bool {
match &self.inner {
CompiledInner::PikeVm(_)
| CompiledInner::BacktrackingVm(_)
| CompiledInner::TaggedNfaInterp(_)
| CompiledInner::CodepointClass(_) => true,
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Jit(_)
| CompiledInner::TaggedNfaJit(_)
| CompiledInner::Backtracking(_) => true,
_ => false,
}
}
#[inline]
pub fn is_full_match_prefilter(&self) -> bool {
self.prefilter.is_full_match()
}
#[inline]
pub fn find_full_matches<'a>(
&'a self,
input: &'a [u8],
) -> crate::literal::FullMatchIter<'a, 'a> {
self.prefilter.find_full_matches(input)
}
pub fn find(&self, input: &[u8]) -> Option<(usize, usize)> {
self.find_from(input, 0)
}
pub fn find_from(&self, input: &[u8], from: usize) -> Option<(usize, usize)> {
self.find_from_with(input, from, None)
}
pub fn find_from_with(
&self,
input: &[u8],
from: usize,
dfa: Option<&mut LazyDfa>,
) -> Option<(usize, usize)> {
if from > input.len() {
return None;
}
if self.simple_eager_scan {
if let CompiledInner::EagerDfa(eager, _) = &self.inner {
return Self::find_from_simple_scan(eager, input, from);
}
}
self.find_from_generic(input, from, dfa)
}
fn find_from_simple_scan(
eager: &EagerDfa,
input: &[u8],
from: usize,
) -> Option<(usize, usize)> {
let mut from = from;
loop {
let (start, end) = eager.find_from_simple(input, from)?;
if crate::nfa::is_utf8_boundary(input, start) {
return Some((start, end));
}
from = start + 1;
}
}
fn find_from_generic(
&self,
input: &[u8],
from: usize,
mut dfa: Option<&mut LazyDfa>,
) -> Option<(usize, usize)> {
let mut from = from;
loop {
let (start, end) = self.find_from_inner(input, from, dfa.as_deref_mut())?;
if crate::nfa::is_utf8_boundary(input, start) {
return Some((start, end));
}
from = start + 1;
}
}
fn find_from_inner(
&self,
input: &[u8],
from: usize,
mut dfa: Option<&mut LazyDfa>,
) -> Option<(usize, usize)> {
if let Some(search) = &self.reverse_suffix {
return search.find(input, from, |start| self.find_at_pos(input, start, None));
}
if self.prefilter.is_full_match() {
return self.prefilter.find_full_match(input, from);
}
if !self.prefilter.is_none() {
let scan_from = from.saturating_sub(self.prefix_offset);
let match_start = |candidate: usize| {
candidate
.checked_add(self.prefix_offset)
.filter(|&start| start >= from && start <= input.len())
};
if self.engine_searches_single_pass() {
let first = self
.prefilter
.find_candidates_from(input, scan_from)
.find_map(match_start)?;
return self.find_engine_from(input, first, dfa);
}
let mut drive = PrefilterDrive::new();
for candidate in self.prefilter.find_candidates_from(input, scan_from) {
let Some(start) = match_start(candidate) else {
continue;
};
if let Some(result) = self.find_at_pos(input, start, dfa.as_deref_mut()) {
return Some(result);
}
if drive.give_up(start) {
return self.find_engine_from(input, start, dfa);
}
}
return None;
}
self.find_engine_from(input, from, dfa)
}
fn find_engine_from_boundary(
&self,
input: &[u8],
from: usize,
mut dfa: Option<&mut LazyDfa>,
) -> Option<(usize, usize)> {
let mut from = from;
loop {
let (start, end) = self.find_engine_from(input, from, dfa.as_deref_mut())?;
if crate::nfa::is_utf8_boundary(input, start) {
return Some((start, end));
}
from = start + 1;
}
}
fn find_engine_from(
&self,
input: &[u8],
from: usize,
dfa: Option<&mut LazyDfa>,
) -> Option<(usize, usize)> {
match &self.inner {
CompiledInner::PikeVm(vm) => vm.find_from(input, from),
CompiledInner::ShiftOr(so) => so.find_at(input, from),
CompiledInner::ShiftOrWide(so) => so.find_at(input, from),
CompiledInner::LazyDfa(pool) => with_lazy_dfa(pool, dfa, |lazy| {
lazy_dfa_or_pikevm(
lazy,
|d| d.find_from(input, from),
|vm| vm.find_from(input, from),
)
}),
CompiledInner::EagerDfa(dfa, nfa) => match dfa.find_from(input, from) {
Ok(result) => result,
Err(EagerScanBudgetExceeded) => {
let mut fallback = eager_scan_fallback(nfa);
lazy_dfa_or_pikevm(
&mut fallback,
|d| d.find_from(input, from),
|vm| vm.find_from(input, from),
)
}
},
CompiledInner::CodepointClass(matcher) => matcher.find_from(input, from),
CompiledInner::BacktrackingVm(vm) => vm.find_at(input, from),
CompiledInner::TaggedNfaInterp(engine) => engine.find_at(input, from),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Jit(jit) => jit.find_from(input, from),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::TaggedNfaJit(engine) => engine.find_at(input, from),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Backtracking(jit) => jit.find_from(input, from),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::JitShiftOr(jit) => jit.find_from(input, from),
}
}
pub fn captures(&self, input: &[u8]) -> Option<Vec<Option<(usize, usize)>>> {
self.captures_from(input, 0)
}
pub fn captures_from(&self, input: &[u8], from: usize) -> Option<Vec<Option<(usize, usize)>>> {
self.captures_from_with(input, from, None)
}
pub fn captures_from_with(
&self,
input: &[u8],
from: usize,
dfa: Option<&mut LazyDfa>,
) -> Option<Vec<Option<(usize, usize)>>> {
if from > input.len() {
return None;
}
let from = if self.reverse_suffix.is_some() {
self.find_from_with(input, from, None)?.0
} else {
from
};
match &self.inner {
CompiledInner::PikeVm(vm) => vm.captures_from(input, from),
CompiledInner::CodepointClass(matcher) => matcher.captures_from(input, from),
CompiledInner::BacktrackingVm(vm) => {
vm.captures_from(input, from)
}
CompiledInner::TaggedNfaInterp(engine) => {
engine.captures_from(input, from)
}
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::TaggedNfaJit(engine) => {
engine.captures_from(input, from)
}
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Backtracking(jit) => {
jit.captures_from(input, from)
}
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Jit(_) => {
if let Some(ref backtracking_vm) = self.backtracking_vm {
return backtracking_vm.captures_from(input, from);
}
self.captures_two_pass(input, from, dfa)
}
CompiledInner::ShiftOr(_)
| CompiledInner::ShiftOrWide(_)
| CompiledInner::LazyDfa(_)
| CompiledInner::EagerDfa(_, _) => {
if let Some(ref backtracking_vm) = self.backtracking_vm {
return backtracking_vm.captures_from(input, from);
}
self.captures_two_pass(input, from, dfa)
}
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::JitShiftOr(_) => {
if let Some(ref backtracking_jit) = self.backtracking_jit {
return backtracking_jit.captures_from(input, from);
}
self.captures_two_pass(input, from, dfa)
}
}
}
pub fn try_captures_from(
&self,
input: &[u8],
from: usize,
limit: u64,
) -> std::result::Result<Option<CaptureSlots>, BudgetExhausted> {
if from > input.len() {
return Ok(None);
}
if let Some(ref vm) = self.backtracking_vm {
return vm.try_captures_from(input, from, limit);
}
match &self.inner {
CompiledInner::BacktrackingVm(vm) => vm.try_captures_from(input, from, limit),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Backtracking(jit) => jit.try_captures_from(input, from, limit),
_ => Ok(self.captures_from(input, from)),
}
}
pub fn try_find_from(
&self,
input: &[u8],
from: usize,
limit: u64,
) -> std::result::Result<Option<(usize, usize)>, BudgetExhausted> {
if from > input.len() {
return Ok(None);
}
match &self.inner {
CompiledInner::BacktrackingVm(vm) => vm.try_find_at(input, from, limit),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Backtracking(jit) => Ok(jit
.try_captures_from(input, from, limit)?
.and_then(|c| c[0])),
_ => Ok(self.find_from(input, from)),
}
}
fn captures_two_pass(
&self,
input: &[u8],
from: usize,
dfa: Option<&mut LazyDfa>,
) -> Option<Vec<Option<(usize, usize)>>> {
let mut from = from;
if let Some(one_pass) = self.one_pass() {
match self.captures_one_pass(one_pass, input, from) {
OnePassSearch::Match(slots) => return Some(slots),
OnePassSearch::NoMatch => return None,
OnePassSearch::GaveUp(resume) => from = resume,
OnePassSearch::NotApplicable => {}
}
}
let (match_start, match_end) = self.find_from_with(input, from, dfa)?;
if let Some(one_pass) = self.one_pass() {
if let Some(slots) = one_pass.captures_at(input, match_start) {
if slots.first().copied().flatten() == Some((match_start, match_end)) {
return Some(slots);
}
}
}
self.get_or_init_capture_vm();
let vm_ref = self.capture_vm.read().unwrap();
let vm = match vm_ref.as_ref() {
Some(vm) => vm,
None => return Some(vec![Some((match_start, match_end))]),
};
let mut ctx_ref = self.capture_ctx.write().unwrap();
let ctx = ctx_ref.as_mut()?;
vm.captures_with_context(input, ctx, match_start)
}
fn captures_one_pass(
&self,
one_pass: &OnePass,
input: &[u8],
from: usize,
) -> OnePassSearch<Vec<Option<(usize, usize)>>> {
if self.prefilter.is_full_match() || self.prefix_offset != 0 {
return OnePassSearch::NotApplicable;
}
let candidates = self
.prefilter
.find_candidates_from(input, from)
.chain(std::iter::once(input.len()));
let mut scratch = vec![None; one_pass.slot_count()];
let mut slots = vec![None; one_pass.slot_count()];
let mut drive = PrefilterDrive::new();
for candidate in candidates {
if !crate::nfa::is_utf8_boundary(input, candidate) {
continue;
}
if one_pass.captures_at_into(input, candidate, &mut scratch, &mut slots) {
return OnePassSearch::Match(slots);
}
if drive.give_up(candidate) {
return OnePassSearch::GaveUp(candidate);
}
}
OnePassSearch::NoMatch
}
fn is_match_at(&self, input: &[u8], pos: usize) -> bool {
self.find_at_pos(input, pos, None).is_some()
}
fn find_at_pos(
&self,
input: &[u8],
pos: usize,
dfa: Option<&mut LazyDfa>,
) -> Option<(usize, usize)> {
if pos > input.len() {
return None;
}
match &self.inner {
CompiledInner::PikeVm(vm) => vm.find_at(input, pos),
CompiledInner::ShiftOr(so) => so.try_match_at(input, pos),
CompiledInner::ShiftOrWide(so) => so.try_match_at(input, pos),
CompiledInner::LazyDfa(pool) => with_lazy_dfa(pool, dfa, |lazy| {
lazy_dfa_or_pikevm(
lazy,
|d| {
d.find_at(input, pos)
.map(|found| found.map(|end| (pos, end)))
},
|vm| vm.find_at(input, pos),
)
}),
CompiledInner::EagerDfa(dfa, _) => dfa.find_at(input, pos).map(|end| (pos, end)),
CompiledInner::CodepointClass(matcher) => {
let slice = &input[pos..];
matcher.find(slice).map(|(s, e)| (pos + s, pos + e))
}
CompiledInner::BacktrackingVm(vm) => vm.find_at(input, pos),
CompiledInner::TaggedNfaInterp(engine) => engine.match_at(input, pos),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Jit(jit) => jit.find_at(input, pos),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::TaggedNfaJit(engine) => engine.match_at(input, pos),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::Backtracking(jit) => jit.find_at(input, pos),
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
CompiledInner::JitShiftOr(jit) => jit.try_match_at(input, pos),
}
}
pub(crate) fn checkout_lazy_dfa(&self) -> Option<LazyDfa> {
match &self.inner {
CompiledInner::LazyDfa(pool) => Some(pool.checkout()),
_ => None,
}
}
pub(crate) fn checkin_lazy_dfa(&self, dfa: LazyDfa) {
if let CompiledInner::LazyDfa(pool) = &self.inner {
pool.checkin(dfa);
}
}
}
pub struct PooledDfa<'r> {
regex: &'r CompiledRegex,
dfa: Option<LazyDfa>,
}
impl<'r> PooledDfa<'r> {
pub fn checkout(regex: &'r CompiledRegex) -> Self {
Self {
regex,
dfa: regex.checkout_lazy_dfa(),
}
}
pub fn get(&mut self) -> Option<&mut LazyDfa> {
self.dfa.as_mut()
}
}
impl Drop for PooledDfa<'_> {
fn drop(&mut self) {
if std::thread::panicking() {
return;
}
if let Some(dfa) = self.dfa.take() {
self.regex.checkin_lazy_dfa(dfa);
}
}
}
impl std::fmt::Debug for PooledDfa<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PooledDfa")
.field("held", &self.dfa.is_some())
.finish_non_exhaustive()
}
}
pub fn compile(nfa: Nfa) -> Result<CompiledRegex> {
let engine = select_engine(&nfa);
let (inner, capture_nfa) = match engine {
EngineType::PikeVm => (CompiledInner::PikeVm(PikeVm::new(nfa)), None),
EngineType::TaggedNfa => {
(
CompiledInner::TaggedNfaInterp(TaggedNfaEngine::new(nfa)),
None,
)
}
EngineType::BacktrackingVm => {
(CompiledInner::PikeVm(PikeVm::new(nfa)), None)
}
EngineType::ShiftOr | EngineType::ShiftOrWide => {
let capture_nfa = Some(nfa.clone());
(
CompiledInner::LazyDfa(LazyDfaPool::new(LazyDfa::new(nfa))),
capture_nfa,
)
}
EngineType::LazyDfa => {
let capture_nfa = Some(nfa.clone());
(
CompiledInner::LazyDfa(LazyDfaPool::new(LazyDfa::new(nfa))),
capture_nfa,
)
}
#[cfg(feature = "jit")]
EngineType::Jit => {
let capture_nfa = Some(nfa.clone());
(
CompiledInner::LazyDfa(LazyDfaPool::new(LazyDfa::new(nfa))),
capture_nfa,
)
}
};
Ok(CompiledRegex {
inner,
prefilter: Prefilter::None, prefix_offset: 0,
simple_eager_scan: false,
reverse_suffix: None,
capture_nfa: RwLock::new(capture_nfa),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm: None,
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
backtracking_jit: None,
})
}
fn compile_tagged_nfa_interp(hir: &Hir, nfa: Nfa) -> CompiledRegex {
let literals = extract_literals(hir);
let prefilter = Prefilter::from_literals(&literals);
CompiledRegex {
inner: CompiledInner::TaggedNfaInterp(TaggedNfaEngine::new(nfa)),
prefilter,
prefix_offset: literals.prefix_offset,
simple_eager_scan: false,
reverse_suffix: ReverseSuffixSearch::new(hir),
capture_nfa: RwLock::new(None),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm: None,
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
backtracking_jit: None,
}
}
pub fn compile_from_hir(hir: &Hir) -> Result<CompiledRegex> {
if let Some(ref codepoint_class) = hir.props.codepoint_class {
return Ok(CompiledRegex {
inner: CompiledInner::CodepointClass(CodepointClassMatcher::new(
codepoint_class.clone(),
)),
prefilter: Prefilter::None,
prefix_offset: 0,
simple_eager_scan: false,
reverse_suffix: None,
capture_nfa: RwLock::new(None),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm: None,
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
backtracking_jit: None,
});
}
if hir.props.has_lookaround && !hir.props.has_non_greedy {
return Ok(compile_tagged_nfa_interp(hir, nfa::compile(hir)?));
}
let literals = extract_literals(hir);
let mut prefilter = Prefilter::from_literals(&literals);
let needs_backtracking = hir.props.has_backrefs;
let engine = select_engine_from_hir(hir);
if engine == EngineType::PikeVm {
prefilter = prefilter.into_candidate_only();
}
let (inner, capture_nfa) = match engine {
EngineType::PikeVm => {
let nfa = nfa::compile(hir)?;
(CompiledInner::PikeVm(PikeVm::new(nfa)), None)
}
EngineType::TaggedNfa => {
return Ok(compile_tagged_nfa_interp(hir, nfa::compile(hir)?));
}
EngineType::BacktrackingVm => {
(
CompiledInner::BacktrackingVm(BacktrackingVm::new(hir)),
None,
)
}
EngineType::ShiftOr => {
let shift_or = if hir.props.has_anchors {
ShiftOr::from_hir_with_anchors(hir)
} else {
ShiftOr::from_hir(hir)
};
match shift_or {
Some(so) => {
let capture_nfa = nfa::compile(hir)?;
(CompiledInner::ShiftOr(so), Some(capture_nfa))
}
None => {
let nfa = nfa::compile(hir)?;
let capture_nfa = Some(nfa.clone());
(
CompiledInner::LazyDfa(LazyDfaPool::new(LazyDfa::new(nfa))),
capture_nfa,
)
}
}
}
EngineType::ShiftOrWide => {
match ShiftOrWide::from_hir(hir) {
Some(so) => {
let capture_nfa = nfa::compile(hir)?;
(CompiledInner::ShiftOrWide(so), Some(capture_nfa))
}
None => {
let nfa = nfa::compile(hir)?;
let capture_nfa = Some(nfa.clone());
(
CompiledInner::LazyDfa(LazyDfaPool::new(LazyDfa::new(nfa))),
capture_nfa,
)
}
}
}
EngineType::LazyDfa => {
let nfa = nfa::compile(hir)?;
let capture_nfa = Some(nfa.clone());
if hir.props.has_large_unicode_class || hir.props.has_anchors {
(
CompiledInner::LazyDfa(LazyDfaPool::new(LazyDfa::new(nfa))),
capture_nfa,
)
} else {
let mut lazy = LazyDfa::new(nfa);
let nfa_arc = lazy.nfa_arc();
match EagerDfa::from_lazy(&mut lazy) {
Ok(eager) => (CompiledInner::EagerDfa(eager, nfa_arc), capture_nfa),
Err(_) => {
(
CompiledInner::LazyDfa(LazyDfaPool::new(LazyDfa::new(
(*nfa_arc).clone(),
))),
capture_nfa,
)
}
}
}
}
#[cfg(feature = "jit")]
EngineType::Jit => {
let nfa = nfa::compile(hir)?;
let capture_nfa = Some(nfa.clone());
if hir.props.has_large_unicode_class || hir.props.has_anchors {
(
CompiledInner::LazyDfa(LazyDfaPool::new(LazyDfa::new(nfa))),
capture_nfa,
)
} else {
let mut lazy = LazyDfa::new(nfa);
let nfa_arc = lazy.nfa_arc();
match EagerDfa::from_lazy(&mut lazy) {
Ok(eager) => (CompiledInner::EagerDfa(eager, nfa_arc), capture_nfa),
Err(_) => {
(
CompiledInner::LazyDfa(LazyDfaPool::new(LazyDfa::new(
(*nfa_arc).clone(),
))),
capture_nfa,
)
}
}
}
}
};
let backtracking_vm = if needs_backtracking {
Some(BacktrackingVm::new(hir))
} else {
None
};
let simple_eager_scan = prefilter.is_none()
&& literals.prefix_offset == 0
&& matches!(&inner, CompiledInner::EagerDfa(dfa, _) if dfa.is_simple_scan());
Ok(CompiledRegex {
inner,
prefilter,
prefix_offset: literals.prefix_offset,
simple_eager_scan,
reverse_suffix: None,
capture_nfa: RwLock::new(capture_nfa),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm,
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
backtracking_jit: None,
})
}
pub fn compile_with_pikevm(hir: &Hir) -> Result<CompiledRegex> {
let literals = extract_literals(hir);
let prefilter = Prefilter::from_literals(&literals).into_candidate_only();
let nfa = nfa::compile(hir)?;
Ok(CompiledRegex {
inner: CompiledInner::PikeVm(PikeVm::new(nfa)),
prefilter,
prefix_offset: literals.prefix_offset,
simple_eager_scan: false,
reverse_suffix: None,
capture_nfa: RwLock::new(None),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm: None,
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
backtracking_jit: None,
})
}
pub fn compile_with_jit(hir: &Hir) -> Result<CompiledRegex> {
if let Some(ref codepoint_class) = hir.props.codepoint_class {
return Ok(CompiledRegex {
inner: CompiledInner::CodepointClass(CodepointClassMatcher::new(
codepoint_class.clone(),
)),
prefilter: Prefilter::None,
prefix_offset: 0,
simple_eager_scan: false,
reverse_suffix: None,
capture_nfa: RwLock::new(None),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm: None,
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
backtracking_jit: None,
});
}
if hir.props.has_non_greedy && !hir.props.has_backrefs {
return compile_with_pikevm(hir);
}
if needs_boundary_aware_empty_match(hir) && !hir.props.has_backrefs {
return compile_with_pikevm(hir);
}
if hir.props.has_multiline_anchors
&& crate::hir::matches_empty(&hir.expr)
&& !hir.props.has_backrefs
{
return compile_from_hir(hir);
}
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
if hir.props.has_large_unicode_class && !hir.props.has_backrefs {
let literals = extract_literals(hir);
let prefilter = Prefilter::from_literals(&literals);
let nfa = nfa::compile(hir)?;
match jit::compile_tagged_nfa(&nfa) {
Ok(engine) => {
return Ok(CompiledRegex {
inner: CompiledInner::TaggedNfaJit(engine),
prefilter,
prefix_offset: literals.prefix_offset,
simple_eager_scan: false,
reverse_suffix: ReverseSuffixSearch::new(hir),
capture_nfa: RwLock::new(None),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm: None,
backtracking_jit: None,
});
}
Err(_e) => {
#[cfg(debug_assertions)]
eprintln!("[regexr] TaggedNfaJit failed for large unicode class, falling back to interpreter: {}", _e);
return Ok(compile_tagged_nfa_interp(hir, nfa));
}
}
}
#[cfg(not(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64"))))]
if hir.props.has_large_unicode_class && !hir.props.has_backrefs {
return Ok(compile_tagged_nfa_interp(hir, nfa::compile(hir)?));
}
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
if hir.props.has_backrefs && !hir.props.has_lookaround {
let literals = extract_literals(hir);
let prefilter = Prefilter::from_literals(&literals);
match jit::compile_backtracking(hir) {
Ok(jit_regex) => {
return Ok(CompiledRegex {
inner: CompiledInner::Backtracking(jit_regex),
prefilter,
prefix_offset: literals.prefix_offset,
simple_eager_scan: false,
reverse_suffix: None,
capture_nfa: RwLock::new(None),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm: None,
#[cfg(all(
feature = "jit",
any(target_arch = "x86_64", target_arch = "aarch64")
))]
backtracking_jit: None,
});
}
Err(_) => {
return Ok(CompiledRegex {
inner: CompiledInner::BacktrackingVm(BacktrackingVm::new(hir)),
prefilter,
prefix_offset: literals.prefix_offset,
simple_eager_scan: false,
reverse_suffix: None,
capture_nfa: RwLock::new(None),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm: None,
backtracking_jit: None,
});
}
}
}
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
if hir.props.has_lookaround {
let literals = extract_literals(hir);
let prefilter = Prefilter::from_literals(&literals);
let nfa = nfa::compile(hir)?;
match jit::compile_tagged_nfa(&nfa) {
Ok(engine) => {
return Ok(CompiledRegex {
inner: CompiledInner::TaggedNfaJit(engine),
prefilter,
prefix_offset: literals.prefix_offset,
simple_eager_scan: false,
reverse_suffix: ReverseSuffixSearch::new(hir),
capture_nfa: RwLock::new(None),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm: None,
backtracking_jit: None,
});
}
Err(_e) => {
#[cfg(debug_assertions)]
eprintln!(
"[regexr] TaggedNfaJit failed, falling back to interpreter: {}",
_e
);
return Ok(compile_tagged_nfa_interp(hir, nfa));
}
}
}
#[cfg(not(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64"))))]
if hir.props.has_lookaround || hir.props.has_non_greedy {
return Ok(compile_tagged_nfa_interp(hir, nfa::compile(hir)?));
}
#[cfg(not(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64"))))]
if hir.props.has_backrefs {
let literals = extract_literals(hir);
return Ok(CompiledRegex {
inner: CompiledInner::BacktrackingVm(BacktrackingVm::new(hir)),
prefilter: Prefilter::from_literals(&literals),
prefix_offset: literals.prefix_offset,
simple_eager_scan: false,
reverse_suffix: None,
capture_nfa: RwLock::new(None),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm: None,
});
}
if crate::engine::selector::hir_has_alternation(&hir.expr) {
return compile_with_pikevm(hir);
}
if crate::engine::selector::hir_contains_alternation(&hir.expr) {
return compile_from_hir(hir);
}
if crate::vm::is_class_run_shape(hir) {
return compile_from_hir(hir);
}
if hir.props.has_word_boundary && !hir.props.has_backrefs {
let literals = extract_literals(hir);
if Prefilter::from_literals(&literals).is_effective() {
return compile_from_hir(hir);
}
}
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
{
use crate::vm::is_shift_or_compatible;
let literals = extract_literals(hir);
let prefilter = Prefilter::from_literals(&literals);
if !prefilter.is_effective() && is_shift_or_compatible(hir) {
let shift_or = if hir.props.has_anchors {
crate::vm::ShiftOr::from_hir_with_anchors(hir)
} else {
crate::vm::ShiftOr::from_hir(hir)
};
if let Some(shift_or) = shift_or {
if let Some(jit_shift_or) = jit::JitShiftOr::compile(&shift_or) {
let capture_nfa = if hir.props.capture_count > 0 {
nfa::compile(hir).ok()
} else {
None
};
let needs_backtracking = hir.props.has_backrefs;
let backtracking_vm = if needs_backtracking {
Some(BacktrackingVm::new(hir))
} else {
None
};
let backtracking_jit = if needs_backtracking {
jit::compile_backtracking(hir).ok()
} else {
None
};
return Ok(CompiledRegex {
inner: CompiledInner::JitShiftOr(jit_shift_or),
prefilter,
prefix_offset: literals.prefix_offset,
simple_eager_scan: false,
reverse_suffix: None,
capture_nfa: RwLock::new(capture_nfa),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm,
backtracking_jit,
});
}
}
}
}
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
{
let literals = extract_literals(hir);
let prefilter = Prefilter::from_literals(&literals);
let nfa = nfa::compile(hir)?;
let capture_nfa = Some(nfa.clone());
let mut dfa = LazyDfa::new(nfa);
let backtracking_vm = if hir.props.has_backrefs {
Some(BacktrackingVm::new(hir))
} else {
None
};
match jit::compile_dfa(&mut dfa) {
Ok(jit_regex) => {
return Ok(CompiledRegex {
inner: CompiledInner::Jit(jit_regex),
prefilter,
prefix_offset: literals.prefix_offset,
simple_eager_scan: false,
reverse_suffix: None,
capture_nfa: RwLock::new(capture_nfa),
one_pass: OnceLock::new(),
capture_vm: RwLock::new(None),
capture_ctx: RwLock::new(None),
backtracking_vm,
backtracking_jit: None,
});
}
Err(_) => {
}
}
}
compile_from_hir(hir)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hir::translate;
use crate::nfa::compile as nfa_compile;
use crate::parser::parse;
fn make_regex(pattern: &str) -> CompiledRegex {
let ast = parse(pattern).unwrap();
let hir = translate(&ast).unwrap();
compile_from_hir(&hir).unwrap()
}
fn make_regex_legacy(pattern: &str) -> CompiledRegex {
let ast = parse(pattern).unwrap();
let hir = translate(&ast).unwrap();
let nfa = nfa_compile(&hir).unwrap();
compile(nfa).unwrap()
}
const END_FIRST_PATTERNS: &[&str] = &[
r"\w+(?=ing\b)",
r"\w*(?=ing)",
r"[a-z]+(?=xy)",
r"(?u:\w+(?=ing))",
];
const END_FIRST_INPUTS: &[&str] = &[
"",
"a",
" ",
"ing",
" ing",
"ings",
"inging",
"sing ing",
"singing",
"singing ringing",
"ing ing ing",
"xy",
" xyxy",
"abxyxy",
"xyzing",
"naïveing",
"ï ing",
"ä¸ing ä¸",
"ä¸inging",
];
fn compiled(pattern: &str, jit: bool) -> CompiledRegex {
let hir = translate(&parse(pattern).unwrap()).unwrap();
if jit {
compile_with_jit(&hir).unwrap()
} else {
compile_from_hir(&hir).unwrap()
}
}
#[test]
fn end_first_search_is_wired_at_every_tagged_site() {
for pattern in END_FIRST_PATTERNS {
for jit in [false, true] {
assert!(
compiled(pattern, jit).reverse_suffix.is_some(),
"gate declined {pattern:?} (jit={jit})"
);
}
}
for pattern in [
r"\w+(?=x)",
r"\w+(?!ing)",
r"\w+?(?=ing)",
r"\w+(?=ing|ed)",
r"(?:abcde|c)(?=d)",
r"\w+ing",
r"hello",
] {
for jit in [false, true] {
assert!(
compiled(pattern, jit).reverse_suffix.is_none(),
"gate accepted {pattern:?} (jit={jit})"
);
}
}
}
#[test]
fn end_first_search_agrees_with_the_forward_scan() {
for pattern in END_FIRST_PATTERNS {
for jit in [false, true] {
let fast = compiled(pattern, jit);
let mut forward = compiled(pattern, jit);
assert!(fast.reverse_suffix.is_some(), "{pattern:?}");
forward.reverse_suffix = None;
for input in END_FIRST_INPUTS {
let bytes = input.as_bytes();
for from in 0..=bytes.len() {
assert_eq!(
fast.find_from(bytes, from),
forward.find_from(bytes, from),
"pattern={pattern:?} jit={jit} input={input:?} from={from}"
);
}
}
}
}
}
#[test]
fn every_entry_point_agrees_with_the_end_first_search() {
for pattern in END_FIRST_PATTERNS {
for jit in [false, true] {
let fast = compiled(pattern, jit);
assert!(fast.reverse_suffix.is_some(), "{pattern:?}");
for input in END_FIRST_INPUTS {
let bytes = input.as_bytes();
let context = format!("pattern={pattern:?} jit={jit} input={input:?}");
assert_eq!(
fast.is_match(bytes),
fast.find(bytes).is_some(),
"is_match disagrees: {context}"
);
for from in 0..=bytes.len() {
let found = fast.find_from(bytes, from);
assert_eq!(
fast.captures_from(bytes, from)
.map(|slots| slots.first().copied().flatten()),
found.map(Some),
"captures_from disagrees: {context} from={from}"
);
}
}
}
}
}
#[test]
fn end_first_search_finds_the_leftmost_match() {
for jit in [false, true] {
let re = compiled(r"\w+(?=ing\b)", jit);
assert_eq!(re.find(b"singing"), Some((0, 4)), "jit={jit}");
assert_eq!(re.find_from(b"singing", 4), None, "jit={jit}");
assert_eq!(re.find(b"ings"), None, "jit={jit}");
assert_eq!(compiled(r"[a-z]+(?=xy)", jit).find(b" xyxy"), Some((1, 3)));
}
}
#[test]
fn test_is_match() {
let re = make_regex("hello");
assert!(re.is_match(b"hello world"));
assert!(!re.is_match(b"goodbye"));
}
#[test]
fn one_pass_is_built_lazily_and_memoized() {
let re = make_regex(r"(\d+)-(\d+)");
let first = re.one_pass().expect("pattern is one-pass eligible");
let second = re.one_pass().expect("second call must still resolve");
assert!(
std::ptr::eq(first, second),
"one_pass must be memoized, not recompiled per call"
);
let caps = re.captures(b"phone: 123-456").expect("must match");
assert_eq!(caps[0], Some((7, 14)));
assert_eq!(caps[1], Some((7, 10)));
assert_eq!(caps[2], Some((11, 14)));
}
#[test]
fn one_pass_is_none_without_a_capture_nfa() {
let patterns = [
"hello",
r"(\d+)-(\d+)",
r"(?=foo)bar",
r"(\w)\1",
r"\b\w+\b",
r"a|b|c",
];
let mut saw_without_capture_nfa = false;
for pattern in patterns {
let re = make_regex(pattern);
if re.capture_nfa.read().unwrap().is_none() {
saw_without_capture_nfa = true;
assert!(
re.one_pass().is_none(),
"{pattern}: no capture NFA, so there is nothing to build a \
one-pass engine from"
);
}
}
assert!(
saw_without_capture_nfa,
"no pattern exercised the capture-NFA-less path, so this test proved \
nothing — pick patterns that reach those construction sites"
);
}
#[test]
fn test_find() {
let re = make_regex("world");
assert_eq!(re.find(b"hello world"), Some((6, 11)));
}
#[test]
fn test_alternation() {
let re = make_regex("cat|dog");
assert!(re.is_match(b"I have a cat"));
assert!(re.is_match(b"I have a dog"));
assert!(!re.is_match(b"I have a bird"));
}
#[test]
fn test_class() {
let re = make_regex("[0-9]+");
assert!(re.is_match(b"abc123def"));
assert!(!re.is_match(b"abcdef"));
}
#[test]
fn test_legacy_api() {
let re = make_regex_legacy("hello");
assert!(re.is_match(b"hello world"));
assert!(!re.is_match(b"goodbye"));
}
#[test]
fn test_prefilter_single_literal() {
let re = make_regex("hello");
assert!(re.is_match(b"say hello world"));
assert!(re.is_match(b"hello"));
assert!(!re.is_match(b"goodbye"));
}
#[test]
fn test_prefilter_literal_extraction() {
let ast = parse("needle").unwrap();
let hir = translate(&ast).unwrap();
let lits = crate::literal::extract_literals(&hir);
assert_eq!(lits.prefixes.len(), 1, "Should have 1 prefix");
assert_eq!(lits.prefixes[0], b"needle", "Prefix should be 'needle'");
}
#[test]
fn test_prefilter_with_dot_star() {
let re = make_regex("hello.*world");
assert!(re.is_match(b"hello world"));
assert!(re.is_match(b"helloworld"));
assert!(re.is_match(b"hello to the world"));
assert!(re.is_match(b"say hello world"));
assert!(re.is_match(b"say hello to the world"));
assert!(!re.is_match(b"hello"));
assert!(!re.is_match(b"world"));
}
#[test]
fn test_prefilter_alternation() {
let re = make_regex("cat|dog|bird");
assert!(re.is_match(b"I have a cat"));
assert!(re.is_match(b"I have a dog"));
assert!(re.is_match(b"I have a bird"));
assert!(!re.is_match(b"I have a fish"));
}
#[test]
fn test_prefilter_find_position() {
let re = make_regex("needle");
let haystack = b"xxxxxxxxxxxxxxxxxneedlexxxxxxxx";
let result = re.find(haystack);
assert_eq!(result, Some((17, 23)));
}
#[test]
fn test_prefilter_large_input() {
let re = make_regex("needle");
let mut haystack = vec![b'x'; 10000];
haystack[5000..5006].copy_from_slice(b"needle");
assert_eq!(re.find(&haystack), Some((5000, 5006)));
}
#[test]
fn test_prefilter_no_match() {
let re = make_regex("needle");
let haystack = vec![b'x'; 10000];
assert_eq!(re.find(&haystack), None);
assert!(!re.is_match(&haystack));
}
#[test]
fn test_prefilter_multiple_matches() {
let re = make_regex("ab");
assert_eq!(re.find(b"xxxxabxxxxabxxxx"), Some((4, 6)));
}
#[test]
fn test_no_prefilter_class_start() {
let re = make_regex("[abc]hello");
assert!(re.is_match(b"ahello"));
assert!(re.is_match(b"bhello"));
assert!(!re.is_match(b"dhello"));
}
#[cfg(all(feature = "jit", any(target_arch = "x86_64", target_arch = "aarch64")))]
mod tagged_nfa_integration {
use super::*;
use crate::engine::compile_with_jit;
fn make_jit_regex(pattern: &str) -> CompiledRegex {
let ast = parse(pattern).unwrap();
let hir = translate(&ast).unwrap();
compile_with_jit(&hir).unwrap()
}
#[test]
fn test_backref_simple() {
let re = make_jit_regex(r"(a)\1");
assert!(re.is_match(b"aa"));
assert!(!re.is_match(b"ab"));
assert_eq!(re.find(b"aa"), Some((0, 2)));
}
#[test]
fn test_backref_captures() {
let re = make_jit_regex(r"(abc)\1");
let caps = re.captures(b"abcabc").unwrap();
assert_eq!(caps.len(), 2); assert_eq!(caps[0], Some((0, 6))); assert_eq!(caps[1], Some((0, 3))); }
#[test]
fn test_positive_lookahead() {
let re = make_jit_regex(r"a(?=b)");
assert!(re.is_match(b"ab"));
assert!(!re.is_match(b"ac"));
assert_eq!(re.find(b"ab"), Some((0, 1))); }
#[test]
fn test_negative_lookahead() {
let re = make_jit_regex(r"a(?!b)");
assert!(re.is_match(b"ac"));
assert!(!re.is_match(b"ab"));
assert_eq!(re.find(b"ac"), Some((0, 1)));
}
#[test]
fn test_positive_lookbehind() {
let re = make_jit_regex(r"(?<=a)b");
assert!(re.is_match(b"ab"));
assert!(!re.is_match(b"cb"));
assert_eq!(re.find(b"ab"), Some((1, 2))); }
#[test]
fn test_negative_lookbehind() {
let re = make_jit_regex(r"(?<!a)b");
assert!(re.is_match(b"cb"));
assert!(!re.is_match(b"ab"));
assert_eq!(re.find(b"cb"), Some((1, 2)));
}
#[test]
fn test_non_greedy_star() {
let re = make_jit_regex(r"a*?b");
assert_eq!(re.find(b"b"), Some((0, 1))); assert_eq!(re.find(b"ab"), Some((0, 2))); assert_eq!(re.find(b"aaab"), Some((0, 4))); }
#[test]
fn test_non_greedy_plus() {
let re = make_jit_regex(r"a+?b");
assert_eq!(re.find(b"ab"), Some((0, 2))); assert_eq!(re.find(b"aaab"), Some((0, 4))); assert_eq!(re.find(b"b"), None); }
#[test]
fn test_complex_lookahead_with_capture() {
let re = make_jit_regex(r"(foo)(?=bar)");
assert!(re.is_match(b"foobar"));
assert!(!re.is_match(b"foobaz"));
let caps = re.captures(b"foobar").unwrap();
assert_eq!(caps[0], Some((0, 3))); assert_eq!(caps[1], Some((0, 3))); }
#[test]
fn test_nested_backrefs() {
let re = make_jit_regex(r"((a)(b))\1");
assert!(re.is_match(b"abab"));
assert!(!re.is_match(b"abba"));
assert_eq!(re.find(b"abab"), Some((0, 4)));
}
#[test]
fn test_find_at_with_backref() {
let re = make_jit_regex(r"(x)\1");
let input = b"axxbxx";
assert_eq!(re.find(input), Some((1, 3)));
}
}
}