use alloc::boxed::Box;
use alloc::string::String;
#[cfg(feature = "variable-lookbehinds")]
use alloc::sync::Arc;
use alloc::vec;
use alloc::vec::Vec;
use regex_automata::meta::Regex;
use regex_automata::util::look::LookMatcher;
use regex_automata::util::pool::Pool;
use regex_automata::util::primitives::NonMaxUsize;
use regex_automata::Anchored;
use regex_automata::Input;
#[cfg(feature = "variable-lookbehinds")]
pub(crate) type CachePoolFn = alloc::boxed::Box<
dyn Fn() -> regex_automata::hybrid::dfa::Cache
+ Send
+ Sync
+ core::panic::UnwindSafe
+ core::panic::RefUnwindSafe,
>;
use crate::error::RuntimeError;
use crate::input::{Input as HaystackInput, RegexInput};
use crate::Assertion;
use crate::BytesMode;
use crate::Error;
use crate::Formatter;
use crate::Result;
use crate::{codepoint_len, HardRegexRuntimeOptions};
const OPTION_TRACE: u32 = 1 << 0;
pub(crate) const OPTION_NOT_CONTINUED_FROM_PREVIOUS_MATCH: u32 = 1 << 1;
pub(crate) const OPTION_FIND_NOT_EMPTY: u32 = 1 << 2;
const MAX_STACK: usize = 1_000_000;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CaptureGroupRange(pub usize, pub usize);
impl CaptureGroupRange {
pub fn start(&self) -> usize {
self.0
}
pub fn end(&self) -> usize {
self.1
}
pub fn to_option_if_non_empty(self) -> Option<Self> {
if self.start() == self.end() {
None
} else {
Some(self)
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CharClassMatcher {
Codepoint(Box<[(char, char)]>),
Byte(Box<[(u8, u8)]>),
}
impl CharClassMatcher {
#[inline]
fn match_len<S: HaystackInput + ?Sized>(&self, s: &S, ix: usize) -> Option<usize> {
let bytes = s.as_bytes();
if ix >= bytes.len() {
return None;
}
match self {
CharClassMatcher::Byte(ranges) => {
if range_contains(ranges, bytes[ix]) {
Some(1)
} else {
None
}
}
CharClassMatcher::Codepoint(ranges) => {
let len = codepoint_len(bytes[ix]);
let end = ix + len;
if end > bytes.len() {
return None;
}
let c = core::str::from_utf8(&bytes[ix..end]).ok()?.chars().next()?;
if range_contains(ranges, c) {
Some(len)
} else {
None
}
}
}
}
}
#[inline]
fn range_contains<T: Ord + Copy>(ranges: &[(T, T)], needle: T) -> bool {
ranges
.binary_search_by(|&(lo, hi)| {
if needle < lo {
core::cmp::Ordering::Greater
} else if needle > hi {
core::cmp::Ordering::Less
} else {
core::cmp::Ordering::Equal
}
})
.is_ok()
}
#[derive(Clone)]
pub struct Delegate {
pub inner: Regex,
pub pattern: String,
pub capture_groups: Option<CaptureGroupRange>,
}
impl core::fmt::Debug for Delegate {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let Self {
inner: _,
pattern,
capture_groups,
} = self;
f.debug_struct("Delegate")
.field("pattern", pattern)
.field("capture_groups", capture_groups)
.finish()
}
}
pub struct Seek {
pub inner: Regex,
pub pattern: String,
}
impl core::fmt::Debug for Seek {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let Self { inner: _, pattern } = self;
f.debug_struct("Seek").field("pattern", pattern).finish()
}
}
#[cfg(feature = "variable-lookbehinds")]
pub struct ReverseBackwardsDelegate {
pub pattern: String,
pub(crate) dfa: Arc<regex_automata::hybrid::dfa::DFA>,
pub(crate) cache_pool: Pool<regex_automata::hybrid::dfa::Cache, CachePoolFn>,
pub(crate) capture_group_extraction_inner: Option<Regex>,
pub capture_groups: Option<CaptureGroupRange>,
}
#[cfg(feature = "variable-lookbehinds")]
impl Clone for ReverseBackwardsDelegate {
fn clone(&self) -> Self {
let dfa_for_closure = Arc::clone(&self.dfa);
let create: CachePoolFn = alloc::boxed::Box::new(move || dfa_for_closure.create_cache());
Self {
pattern: self.pattern.clone(),
cache_pool: Pool::new(create),
dfa: Arc::clone(&self.dfa),
capture_group_extraction_inner: self.capture_group_extraction_inner.clone(),
capture_groups: self.capture_groups,
}
}
}
#[cfg(feature = "variable-lookbehinds")]
impl core::fmt::Debug for ReverseBackwardsDelegate {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let Self {
pattern,
dfa: _,
cache_pool: _,
capture_group_extraction_inner: _,
capture_groups,
} = self;
f.debug_struct("ReverseBackwardsDelegate")
.field("pattern", pattern)
.field("capture_groups", capture_groups)
.finish()
}
}
#[derive(Debug)]
pub enum Insn {
End,
Any,
AnyNoNL,
AnyNoCRLF,
Assertion(Assertion),
Lit(String), CharClass(CharClassMatcher),
Split(usize, usize),
SplitUnanchored(usize, usize),
Jmp(usize),
Save(usize),
Save0(usize),
SaveCaptureGroupStart(usize),
Restore(usize),
RepeatGr {
lo: usize,
hi: usize,
next: usize,
repeat: usize,
},
RepeatNg {
lo: usize,
hi: usize,
next: usize,
repeat: usize,
},
RepeatEpsilonGr {
lo: usize,
next: usize,
repeat: usize,
check: usize,
},
RepeatEpsilonNg {
lo: usize,
next: usize,
repeat: usize,
check: usize,
},
FailNegativeLookAround,
GoBack(usize),
Backref {
slot: usize,
casei: bool,
unicode: bool,
},
BeginAtomic,
EndAtomic,
Delegate(Delegate),
ContinueFromPreviousMatchEnd {
at_start: bool,
},
BackrefExistsCondition(usize),
Fail,
#[cfg(feature = "variable-lookbehinds")]
BackwardsDelegate(ReverseBackwardsDelegate),
AbsentRepeater(Delegate),
Seek(Seek),
RejectEmptyMatchAtEOFFollowingNewline,
}
#[derive(Debug)]
struct Scratch {
state: State,
inner_slots: Vec<Option<NonMaxUsize>>,
}
fn new_scratch() -> Scratch {
Scratch {
state: State::new(0, MAX_STACK, 0),
inner_slots: Vec::new(),
}
}
#[derive(Debug)]
pub struct Prog {
pub body: Vec<Insn>,
n_saves: usize,
bytes_mode: BytesMode,
pub(crate) seek_pattern: String,
scratch_pool: Pool<Scratch, fn() -> Scratch>,
}
impl Prog {
pub(crate) fn new(
body: Vec<Insn>,
n_saves: usize,
bytes_mode: BytesMode,
seek_pattern: String,
) -> Prog {
Prog {
body,
n_saves,
bytes_mode,
seek_pattern,
scratch_pool: Pool::new(new_scratch),
}
}
#[doc(hidden)]
pub(crate) fn debug_print(&self, writer: &mut Formatter<'_>) -> core::fmt::Result {
for (i, insn) in self.body.iter().enumerate() {
writeln!(writer, "{:3}: {:?}", i, insn)?;
}
Ok(())
}
}
#[derive(Debug)]
struct Branch {
pc: usize,
ix: usize,
nsave: usize,
}
#[derive(Debug)]
struct Save {
slot: usize,
value: usize,
}
#[derive(Debug)]
struct State {
saves: Vec<usize>,
stack: Vec<Branch>,
oldsave: Vec<Save>,
nsave: usize,
explicit_sp: usize,
max_stack: usize,
#[allow(dead_code)]
options: u32,
cut_scratch: Vec<usize>,
}
impl State {
fn new(n_saves: usize, max_stack: usize, options: u32) -> State {
State {
saves: vec![usize::MAX; n_saves],
stack: Vec::new(),
oldsave: Vec::new(),
nsave: 0,
explicit_sp: n_saves,
max_stack,
options,
cut_scratch: Vec::new(),
}
}
fn reset(&mut self, n_saves: usize, options: u32) {
self.saves.clear();
self.saves.resize(n_saves, usize::MAX);
self.stack.clear();
self.oldsave.clear();
self.nsave = 0;
self.explicit_sp = n_saves;
self.options = options;
}
fn push(&mut self, pc: usize, ix: usize) -> Result<()> {
if self.stack.len() < self.max_stack {
let nsave = self.nsave;
self.stack.push(Branch { pc, ix, nsave });
self.nsave = 0;
self.trace_stack("push");
Ok(())
} else {
Err(Error::RuntimeError(RuntimeError::StackOverflow))
}
}
fn pop(&mut self) -> (usize, usize) {
for _ in 0..self.nsave {
let Save { slot, value } = self.oldsave.pop().unwrap();
self.saves[slot] = value;
}
let Branch { pc, ix, nsave } = self.stack.pop().unwrap();
self.nsave = nsave;
self.trace_stack("pop");
(pc, ix)
}
fn save(&mut self, slot: usize, val: usize) {
for i in 0..self.nsave {
if self.oldsave[self.oldsave.len() - i - 1].slot == slot {
self.saves[slot] = val;
return;
}
}
self.oldsave.push(Save {
slot,
value: self.saves[slot],
});
self.nsave += 1;
self.saves[slot] = val;
#[cfg(feature = "std")]
if self.options & OPTION_TRACE != 0 {
println!("saves: {:?}", self.saves);
}
}
fn get(&self, slot: usize) -> usize {
self.saves[slot]
}
fn stack_push(&mut self, val: usize) {
if self.saves.len() == self.explicit_sp {
self.saves.push(self.explicit_sp + 1);
}
let explicit_sp = self.explicit_sp;
let sp = self.get(explicit_sp);
if self.saves.len() == sp {
self.saves.push(val);
} else {
self.save(sp, val);
}
self.save(explicit_sp, sp + 1);
}
fn stack_pop(&mut self) -> usize {
let explicit_sp = self.explicit_sp;
let sp = self.get(explicit_sp) - 1;
let result = self.get(sp);
self.save(explicit_sp, sp);
result
}
fn backtrack_count(&self) -> usize {
self.stack.len()
}
fn backtrack_cut(&mut self, count: usize) {
if self.stack.len() == count {
return;
}
let (oldsave_start, oldsave_end) = {
let mut end = self.oldsave.len() - self.nsave;
for &Branch { nsave, .. } in &self.stack[count + 1..] {
end -= nsave;
}
let start = end - self.stack[count].nsave;
(start, end)
};
let mut saved = core::mem::take(&mut self.cut_scratch);
saved.clear();
for &Save { slot, .. } in &self.oldsave[oldsave_start..oldsave_end] {
saved.push(slot);
}
let mut oldsave_ix = oldsave_end;
for ix in oldsave_end..self.oldsave.len() {
let Save { slot, .. } = self.oldsave[ix];
let new_slot = !saved.contains(&slot);
if new_slot {
saved.push(slot);
self.oldsave.swap(oldsave_ix, ix);
oldsave_ix += 1;
}
}
self.stack.truncate(count);
self.oldsave.truncate(oldsave_ix);
self.nsave = oldsave_ix - oldsave_start;
self.cut_scratch = saved;
}
#[inline]
#[allow(unused_variables)]
fn trace_stack(&self, operation: &str) {
#[cfg(feature = "std")]
if self.options & OPTION_TRACE != 0 {
println!("stack after {}: {:?}", operation, self.stack);
}
}
}
fn codepoint_len_at<S: HaystackInput + ?Sized>(s: &S, ix: usize) -> usize {
codepoint_len(s.as_bytes()[ix])
}
#[inline]
fn advance_one<S: HaystackInput + ?Sized>(s: &S, ix: usize, bytes_mode: BytesMode) -> usize {
match bytes_mode {
BytesMode::Ascii => 1,
_ => codepoint_len_at(s, ix),
}
}
#[inline]
fn prev_ix<S: HaystackInput + ?Sized>(s: &S, ix: usize, bytes_mode: BytesMode) -> usize {
match bytes_mode {
BytesMode::Ascii => ix - 1,
_ => s.prev_codepoint_ix(ix),
}
}
#[inline]
fn matches_literal<S: HaystackInput + ?Sized>(
s: &S,
ix: usize,
end: usize,
literal: &[u8],
) -> bool {
end <= s.len() && &s.as_bytes()[ix..end] == literal
}
fn matches_literal_casei_unicode(text: &str, literal: &str) -> bool {
let mut text_chars = text.chars();
let mut literal_chars = literal.chars();
loop {
match (text_chars.next(), literal_chars.next()) {
(None, None) => return true,
(Some(t), Some(l)) => {
if t == l {
continue;
}
if t.is_ascii() && l.is_ascii() {
if t.eq_ignore_ascii_case(&l) {
continue;
}
return false;
}
if !chars_fold_equal(t, l) {
return false;
}
}
_ => return false,
}
}
}
fn chars_fold_equal(a: char, b: char) -> bool {
use regex_syntax::hir::{ClassUnicode, ClassUnicodeRange};
let mut class = ClassUnicode::new([ClassUnicodeRange::new(a, a)]);
match class.try_case_fold_simple() {
Ok(()) => class
.ranges()
.iter()
.any(|r| r.start() <= b && b <= r.end()),
Err(_) => false,
}
}
fn matches_literal_casei<S: HaystackInput + ?Sized>(
s: &S,
ix: usize,
end: usize,
literal: &[u8],
unicode: bool,
) -> bool {
if end > s.len() {
return false;
}
if matches_literal(s, ix, end, literal) {
return true;
}
if !s.is_char_boundary(ix) || !s.is_char_boundary(end) {
return false;
}
let text_bytes = &s.as_bytes()[ix..end];
if text_bytes.is_ascii() && literal.is_ascii() {
return text_bytes.eq_ignore_ascii_case(literal);
}
if !unicode {
return false;
}
if let (Ok(text_str), Ok(lit_str)) = (
core::str::from_utf8(text_bytes),
core::str::from_utf8(literal),
) {
return matches_literal_casei_unicode(text_str, lit_str);
}
false
}
#[inline]
fn store_capture_groups(
state: &mut State,
inner_slots: &[Option<NonMaxUsize>],
range: CaptureGroupRange,
skip_earlier_captures: bool,
) {
let start_group = range.start();
let end_group = range.end();
for i in 0..(end_group - start_group) {
let slot = (start_group + i) * 2;
if let Some(start) = inner_slots[(i + 1) * 2] {
let end = inner_slots[(i + 1) * 2 + 1].unwrap();
let mut save = !skip_earlier_captures;
if skip_earlier_captures {
let existing_start = state.get(slot);
let existing_end = state.get(slot + 1);
save = (start.get() >= existing_start || existing_start == usize::MAX)
&& (end.get() >= existing_end || existing_end == usize::MAX);
}
if save {
state.save(slot, start.get());
state.save(slot + 1, end.get());
}
}
}
}
pub fn run_trace(prog: &Prog, s: &str, pos: usize) -> Result<Option<Vec<usize>>> {
run(
prog,
&RegexInput::new(s).from_pos(pos),
OPTION_TRACE,
&HardRegexRuntimeOptions::default(),
)
}
pub fn run_default(prog: &Prog, s: &str, pos: usize) -> Result<Option<Vec<usize>>> {
run(
prog,
&RegexInput::new(s).from_pos(pos),
0,
&HardRegexRuntimeOptions::default(),
)
}
pub(crate) fn run<S: HaystackInput + ?Sized>(
prog: &Prog,
input: &RegexInput<'_, S>,
option_flags: u32,
options: &HardRegexRuntimeOptions,
) -> Result<Option<Vec<usize>>> {
run_with(prog, input, option_flags, options, |state| {
state.saves.clone()
})
}
pub(crate) fn run_spans<S: HaystackInput + ?Sized>(
prog: &Prog,
input: &RegexInput<'_, S>,
option_flags: u32,
options: &HardRegexRuntimeOptions,
) -> Result<Option<(usize, usize)>> {
run_with(prog, input, option_flags, options, |state| {
(state.get(0), state.get(1))
})
}
#[allow(clippy::cognitive_complexity)]
fn run_with<S: HaystackInput + ?Sized, T>(
prog: &Prog,
input: &RegexInput<'_, S>,
option_flags: u32,
options: &HardRegexRuntimeOptions,
extract: impl FnOnce(&State) -> T,
) -> Result<Option<T>> {
if input.is_done() {
return Ok(None);
}
let haystack = input.haystack();
let pos = input.effective_start();
let match_range = input.get_range();
let mut scratch_guard = prog.scratch_pool.get();
let Scratch { state, inner_slots } = &mut *scratch_guard;
state.reset(prog.n_saves, option_flags);
inner_slots.clear();
let look_matcher = LookMatcher::new();
#[cfg(feature = "std")]
if option_flags & OPTION_TRACE != 0 {
println!("pos\tinstruction");
}
let mut backtrack_count = 0;
let mut pc = 0;
let mut ix = pos;
let mut slash_z_matched = false;
let mut match_attempt_start = pos;
loop {
'fail: loop {
#[cfg(feature = "std")]
if option_flags & OPTION_TRACE != 0 {
println!("{}\t{} {:?}", ix, pc, prog.body[pc]);
}
match prog.body[pc] {
Insn::End => {
#[cfg(feature = "std")]
if option_flags & OPTION_TRACE != 0 {
println!("saves: {:?}", state.saves);
}
if option_flags & OPTION_FIND_NOT_EMPTY != 0 && ix == match_attempt_start {
break 'fail;
}
if let Some(&slot1) = state.saves.get(1) {
if state.get(0) > slot1 {
state.save(0, slot1);
}
}
if state.get(0) < match_range.start || state.get(1) > match_range.end {
break 'fail;
}
return Ok(Some(extract(state)));
}
Insn::Any => {
if ix < haystack.len() {
ix += advance_one(haystack, ix, prog.bytes_mode);
} else {
break 'fail;
}
}
Insn::AnyNoNL => {
if ix < haystack.len() && haystack.as_bytes()[ix] != b'\n' {
ix += advance_one(haystack, ix, prog.bytes_mode);
} else {
break 'fail;
}
}
Insn::AnyNoCRLF => {
if ix < haystack.len()
&& haystack.as_bytes()[ix] != b'\r'
&& haystack.as_bytes()[ix] != b'\n'
{
ix += advance_one(haystack, ix, prog.bytes_mode);
} else {
break 'fail;
}
}
Insn::Lit(ref val) => {
let ix_end = ix + val.len();
if !matches_literal(haystack, ix, ix_end, val.as_bytes()) {
break 'fail;
}
ix = ix_end
}
Insn::CharClass(ref matcher) => match matcher.match_len(haystack, ix) {
Some(len) => ix += len,
None => break 'fail,
},
Insn::Assertion(assertion) => {
if !match assertion {
Assertion::StartText => input
.start_text_override()
.and_then(|value| {
options.allow_input_assertion_overrides.then_some(value)
})
.unwrap_or_else(|| look_matcher.is_start(haystack.as_bytes(), ix)),
Assertion::EndText => {
let matched = input
.end_text_override()
.and_then(|value| {
options.allow_input_assertion_overrides.then_some(value)
})
.unwrap_or_else(|| look_matcher.is_end(haystack.as_bytes(), ix));
slash_z_matched |= matched;
matched
}
Assertion::EndTextIgnoreTrailingNewlines { crlf } => {
let bytes = haystack.as_bytes();
let matched = if ix == bytes.len() {
true
} else if crlf {
bytes[ix..].iter().all(|&b| b == b'\n' || b == b'\r')
} else {
bytes[ix..].iter().all(|&b| b == b'\n')
};
slash_z_matched |= matched;
matched
}
Assertion::StartLine { crlf: false } => {
look_matcher.is_start_lf(haystack.as_bytes(), ix)
}
Assertion::StartLine { crlf: true } => {
look_matcher.is_start_crlf(haystack.as_bytes(), ix)
}
Assertion::StartLineOniguruma { crlf: false } => {
look_matcher.is_start_lf(haystack.as_bytes(), ix)
&& !(ix > 0 && ix == haystack.len())
}
Assertion::StartLineOniguruma { crlf: true } => {
look_matcher.is_start_crlf(haystack.as_bytes(), ix)
&& !(ix > 0 && ix == haystack.len())
}
Assertion::EndLine { crlf: false } => {
look_matcher.is_end_lf(haystack.as_bytes(), ix)
}
Assertion::EndLine { crlf: true } => {
look_matcher.is_end_crlf(haystack.as_bytes(), ix)
}
Assertion::LeftWordBoundary => look_matcher
.is_word_start_unicode(haystack.as_bytes(), ix)
.unwrap(),
Assertion::RightWordBoundary => look_matcher
.is_word_end_unicode(haystack.as_bytes(), ix)
.unwrap(),
Assertion::LeftWordHalfBoundary => look_matcher
.is_word_start_half_unicode(haystack.as_bytes(), ix)
.unwrap(),
Assertion::RightWordHalfBoundary => look_matcher
.is_word_end_half_unicode(haystack.as_bytes(), ix)
.unwrap(),
Assertion::WordBoundary => look_matcher
.is_word_unicode(haystack.as_bytes(), ix)
.unwrap(),
Assertion::NotWordBoundary => look_matcher
.is_word_unicode_negate(haystack.as_bytes(), ix)
.unwrap(),
} {
break 'fail;
}
}
Insn::Split(x, y) => {
state.push(y, ix)?;
pc = x;
continue;
}
Insn::SplitUnanchored(x, y) => {
if ix > match_range.end {
return Ok(None);
}
match_attempt_start = ix;
slash_z_matched = false;
if input.is_anchored() {
} else {
state.push(y, ix)?;
}
pc = x;
continue;
}
Insn::Jmp(target) => {
pc = target;
continue;
}
Insn::Save(slot) => state.save(slot, ix),
Insn::Save0(slot) => state.save(slot, 0),
Insn::SaveCaptureGroupStart(group) => {
let start_slot = group * 2;
if state.get(start_slot) == usize::MAX || state.get(start_slot + 1) <= ix {
state.save(start_slot, ix);
}
}
Insn::Restore(slot) => ix = state.get(slot),
Insn::RepeatGr {
lo,
hi,
next,
repeat,
} => {
let repcount = state.get(repeat);
if repcount == hi {
pc = next;
continue;
}
state.save(repeat, repcount + 1);
if repcount >= lo {
state.push(next, ix)?;
}
}
Insn::RepeatNg {
lo,
hi,
next,
repeat,
} => {
let repcount = state.get(repeat);
if repcount == hi {
pc = next;
continue;
}
state.save(repeat, repcount + 1);
if repcount >= lo {
state.push(pc + 1, ix)?;
pc = next;
continue;
}
}
Insn::RepeatEpsilonGr {
lo,
next,
repeat,
check,
} => {
let repcount = state.get(repeat);
if repcount > 0 && state.get(check) == ix {
pc = next;
continue;
}
state.save(repeat, repcount + 1);
if repcount >= lo {
state.save(check, ix);
state.push(next, ix)?;
}
}
Insn::RepeatEpsilonNg {
lo,
next,
repeat,
check,
} => {
let repcount = state.get(repeat);
if repcount > 0 && state.get(check) == ix {
pc = next;
continue;
}
state.save(repeat, repcount + 1);
if repcount >= lo {
state.save(check, ix);
state.push(pc + 1, ix)?;
pc = next;
continue;
}
}
Insn::GoBack(count) => {
for _ in 0..count {
if ix == 0 {
break 'fail;
}
ix = prev_ix(haystack, ix, prog.bytes_mode);
}
}
Insn::FailNegativeLookAround => {
loop {
let (popped_pc, _) = state.pop();
if popped_pc == pc + 1 {
break;
}
}
break 'fail;
}
Insn::Backref {
slot,
casei,
unicode,
} => {
let lo = state.get(slot);
if lo == usize::MAX {
break 'fail;
}
let hi = state.get(slot + 1);
if hi == usize::MAX {
break 'fail;
}
let ref_text = &haystack.as_bytes()[lo..hi];
let ix_end = ix + ref_text.len();
if casei {
if !matches_literal_casei(haystack, ix, ix_end, ref_text, unicode) {
break 'fail;
}
} else if !matches_literal(haystack, ix, ix_end, ref_text) {
break 'fail;
}
ix = ix_end;
}
Insn::BackrefExistsCondition(group) => {
let lo = state.get(group * 2);
if lo == usize::MAX {
break 'fail;
}
}
Insn::Fail => {
break 'fail;
}
#[cfg(feature = "variable-lookbehinds")]
Insn::BackwardsDelegate(ReverseBackwardsDelegate {
ref dfa,
ref cache_pool,
pattern: _,
ref capture_group_extraction_inner,
capture_groups,
}) => {
let mut cache_guard = cache_pool.get();
let input = Input::new(haystack.as_bytes())
.anchored(Anchored::Yes)
.range(0..ix);
match dfa.try_search_rev(&mut cache_guard, &input) {
Ok(Some(match_result)) => {
let match_start = match_result.offset();
if let Some(inner) = capture_group_extraction_inner {
if let Some(range) = capture_groups {
let forward_input = Input::new(haystack.as_bytes())
.span(match_start..ix)
.anchored(Anchored::Yes);
inner_slots.resize((range.end() - range.start() + 1) * 2, None);
if inner.search_slots(&forward_input, inner_slots).is_some() {
store_capture_groups(state, inner_slots, range, true);
} else {
break 'fail;
}
} else {
ix = match_start;
}
} else {
ix = match_start;
}
}
_ => break 'fail,
}
}
Insn::BeginAtomic => {
let count = state.backtrack_count();
state.stack_push(count);
}
Insn::EndAtomic => {
let count = state.stack_pop();
state.backtrack_cut(count);
}
Insn::Delegate(Delegate {
ref inner,
pattern: _,
capture_groups,
}) => {
let input = Input::new(haystack.as_bytes())
.span(ix..haystack.len())
.anchored(Anchored::Yes);
if let Some(range) = capture_groups {
inner_slots.resize((range.end() - range.start() + 1) * 2, None);
if inner.search_slots(&input, inner_slots).is_some() {
store_capture_groups(state, inner_slots, range, false);
ix = inner_slots[1].unwrap().get();
} else {
break 'fail;
}
} else {
match inner.search_half(&input) {
Some(m) => ix = m.offset(),
_ => break 'fail,
}
}
}
Insn::AbsentRepeater(ref delegate) => {
let input = Input::new(haystack.as_bytes())
.span(ix..haystack.len())
.anchored(Anchored::Yes);
let delegate_matches_here = delegate.inner.search_half(&input).is_some();
if delegate_matches_here {
} else if ix < haystack.len() {
state.push(pc + 1, ix)?;
ix += advance_one(haystack, ix, prog.bytes_mode);
continue;
} else {
}
}
Insn::ContinueFromPreviousMatchEnd { at_start } => {
let at_previous_match_end = input
.continue_from_previous_match_end_override()
.and_then(|value| options.allow_input_assertion_overrides.then_some(value))
.unwrap_or(
ix == pos
&& option_flags & OPTION_NOT_CONTINUED_FROM_PREVIOUS_MATCH == 0,
);
if !at_previous_match_end {
if at_start && state.stack.len() == 1 && !input.is_anchored() {
return Ok(None);
}
break 'fail;
}
}
Insn::Seek(Seek { ref inner, .. }) => {
if ix > match_range.end {
return Ok(None);
}
if input.is_anchored() {
} else {
let seek_input = Input::new(haystack.as_bytes()).span(ix..match_range.end);
match inner.search(&seek_input) {
None => return Ok(None),
Some(m) => {
let next_seek_start = if m.start() == m.end() {
if m.end() < haystack.len() {
m.end() + advance_one(haystack, m.end(), prog.bytes_mode)
} else {
haystack.len() + 1
}
} else {
m.start() + advance_one(haystack, m.start(), prog.bytes_mode)
};
state.push(pc, next_seek_start)?;
ix = m.start();
}
}
}
match_attempt_start = ix;
slash_z_matched = false;
pc += 1;
continue;
}
Insn::RejectEmptyMatchAtEOFFollowingNewline => {
if ix == haystack.len()
&& ix > 0
&& matches_literal(haystack, ix - 1, ix, b"\n")
&& !slash_z_matched
&& match_attempt_start == ix
{
break 'fail;
}
}
}
pc += 1;
}
#[cfg(feature = "std")]
if option_flags & OPTION_TRACE != 0 {
println!("fail");
}
if state.stack.is_empty() {
return Ok(None);
}
backtrack_count += 1;
if backtrack_count > options.backtrack_limit {
return Err(Error::RuntimeError(RuntimeError::BacktrackLimitExceeded));
}
let (newpc, newix) = state.pop();
pc = newpc;
ix = newix;
}
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck::{quickcheck, Arbitrary, Gen};
#[test]
fn casei_unicode_fold_equality() {
assert!(matches_literal_casei_unicode("ΑΛΦΑ", "αλφα"));
assert!(matches_literal_casei_unicode("σ", "ς"));
assert!(matches_literal_casei_unicode("Å¿", "s"));
assert!(matches_literal_casei_unicode("S", "Å¿"));
assert!(matches_literal_casei_unicode("\u{212A}", "k"));
assert!(matches_literal_casei_unicode("\u{212B}", "Ã¥"));
assert!(matches_literal_casei_unicode("aΛb", "AλB"));
assert!(!matches_literal_casei_unicode("straße", "STRASSE"));
assert!(!matches_literal_casei_unicode("sab", "ſa"));
assert!(!matches_literal_casei_unicode("ſa", "ſab"));
assert!(!matches_literal_casei_unicode("αα", "α"));
}
#[test]
fn state_push_pop() {
let mut state = State::new(1, MAX_STACK, 0);
state.push(0, 0).unwrap();
state.push(1, 1).unwrap();
assert_eq!(state.pop(), (1, 1));
assert_eq!(state.pop(), (0, 0));
assert!(state.stack.is_empty());
state.push(2, 2).unwrap();
assert_eq!(state.pop(), (2, 2));
assert!(state.stack.is_empty());
}
#[test]
fn state_save_override() {
let mut state = State::new(1, MAX_STACK, 0);
state.save(0, 10);
state.push(0, 0).unwrap();
state.save(0, 20);
assert_eq!(state.pop(), (0, 0));
assert_eq!(state.get(0), 10);
}
#[test]
fn state_save_override_twice() {
let mut state = State::new(1, MAX_STACK, 0);
state.save(0, 10);
state.push(0, 0).unwrap();
state.save(0, 20);
state.push(1, 1).unwrap();
state.save(0, 30);
assert_eq!(state.get(0), 30);
assert_eq!(state.pop(), (1, 1));
assert_eq!(state.get(0), 20);
assert_eq!(state.pop(), (0, 0));
assert_eq!(state.get(0), 10);
}
#[test]
fn state_explicit_stack() {
let mut state = State::new(1, MAX_STACK, 0);
state.stack_push(11);
state.stack_push(12);
state.push(100, 101).unwrap();
state.stack_push(13);
assert_eq!(state.stack_pop(), 13);
state.stack_push(14);
assert_eq!(state.pop(), (100, 101));
assert_eq!(state.stack_pop(), 12);
assert_eq!(state.stack_pop(), 11);
}
#[test]
fn state_backtrack_cut_simple() {
let mut state = State::new(2, MAX_STACK, 0);
state.save(0, 1);
state.save(1, 2);
let count = state.backtrack_count();
state.push(0, 0).unwrap();
state.save(0, 3);
assert_eq!(state.backtrack_count(), 1);
state.backtrack_cut(count);
assert_eq!(state.backtrack_count(), 0);
assert_eq!(state.get(0), 3);
assert_eq!(state.get(1), 2);
}
#[test]
fn state_backtrack_cut_complex() {
let mut state = State::new(2, MAX_STACK, 0);
state.save(0, 1);
state.save(1, 2);
state.push(0, 0).unwrap();
state.save(0, 3);
let count = state.backtrack_count();
state.push(1, 1).unwrap();
state.save(0, 4);
state.push(2, 2).unwrap();
state.save(1, 5);
assert_eq!(state.backtrack_count(), 3);
state.backtrack_cut(count);
assert_eq!(state.backtrack_count(), 1);
assert_eq!(state.get(0), 4);
assert_eq!(state.get(1), 5);
state.pop();
assert_eq!(state.backtrack_count(), 0);
assert_eq!(state.get(0), 1);
assert_eq!(state.get(1), 2);
}
#[derive(Clone, Debug)]
enum Operation {
Push,
Pop,
Save(usize, usize),
}
impl Arbitrary for Operation {
fn arbitrary(g: &mut Gen) -> Self {
match g.choose(&[0, 1, 2]) {
Some(0) => Operation::Push,
Some(1) => Operation::Pop,
_ => Operation::Save(
*g.choose(&[0usize, 1, 2, 3, 4]).unwrap(),
usize::arbitrary(g),
),
}
}
}
fn check_saves_for_operations(operations: Vec<Operation>) -> bool {
let slots = operations
.iter()
.map(|o| match o {
&Operation::Save(slot, _) => slot + 1,
_ => 0,
})
.max()
.unwrap_or(0);
if slots == 0 {
return true;
}
let mut stack = Vec::new();
let mut saves = vec![usize::MAX; slots];
let mut state = State::new(slots, MAX_STACK, 0);
let mut expected = Vec::new();
let mut actual = Vec::new();
for operation in operations {
match operation {
Operation::Push => {
stack.push((0, 0, saves.clone()));
state.push(0, 0).unwrap();
}
Operation::Pop => {
if let Some((_, _, previous_saves)) = stack.pop() {
saves = previous_saves;
state.pop();
}
}
Operation::Save(slot, value) => {
saves[slot] = value;
state.save(slot, value);
}
}
expected.push(saves.clone());
let mut actual_saves = vec![usize::MAX; slots];
for (i, item) in actual_saves.iter_mut().enumerate().take(slots) {
*item = state.get(i);
}
actual.push(actual_saves);
}
expected == actual
}
quickcheck! {
fn state_save_quickcheck(operations: Vec<Operation>) -> bool {
check_saves_for_operations(operations)
}
}
}