use std::cell::RefCell;
use std::time::{Duration, Instant};
use super::Rune;
use super::charset::is_word_byte;
use super::compile::{Inst, MAX_MEMO_LOOPS, Op, Program};
use super::fold::{canonicalize, is_fold_word_rune};
use super::units::{RUNE_SELF, decode, decode_last, decode_last_rune, decode_rune, unit_at, unit_before};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MatchError {
Time,
Memory,
}
impl std::fmt::Display for MatchError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
MatchError::Time => "ecmaregex: the match ran for longer than its limit",
MatchError::Memory => "ecmaregex: the match needed more backtracking memory than its limit",
})
}
}
impl std::error::Error for MatchError {}
pub(crate) const CHECK_EVERY: i64 = 1 << 12;
pub const MAX_STACK: usize = 1 << 20;
const STOP_NONE: u8 = 0;
const STOP_TIME: u8 = 1;
const STOP_MEMORY: u8 = 2;
const E_CHOICE: u8 = 0;
const E_REP: u8 = 1;
const E_REP_LAZY: u8 = 2;
const E_LOOK: u8 = 3;
const E_LOOK_NEG: u8 = 4;
const E_MARK: u8 = 5;
const E_TMP: u8 = 6;
const E_CAP: u8 = 7;
const E_COUNTER: u8 = 8;
#[derive(Clone, Copy)]
struct Entry {
kind: u8,
pc: i32,
pos: usize,
a: usize,
}
const NONE: usize = usize::MAX;
#[derive(Default)]
struct State {
stack: Vec<Entry>,
caps: Vec<usize>,
tmp: Vec<usize>,
counts: Vec<usize>,
limit: Option<Duration>,
deadline: Option<Instant>,
steps: i64,
stop: u8,
memo: Vec<u64>,
memo_on: bool,
memo_off: bool,
width: usize,
checks: usize,
points: usize,
}
pub const MAX_MEMO_WORDS: usize = 1 << 20;
const MEMO_STEPS_PER_POSITION: usize = 64;
const MAX_RETAINED_MEMO: usize = 1 << 16;
const MAX_MARK_DEPTH: usize = MAX_STACK / 2;
const MAX_RETAINED_STACK: usize = 1 << 16;
thread_local! {
static STATE: RefCell<State> = RefCell::new(State::default());
#[cfg(test)]
static MEMO_FROM_THE_START: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
#[cfg(test)]
pub(crate) fn set_memo_from_the_start(on: bool) {
MEMO_FROM_THE_START.with(|m| m.set(on));
}
#[inline(always)]
fn memo_from_the_start() -> bool {
#[cfg(test)]
if MEMO_FROM_THE_START.with(|m| m.get()) {
return true;
}
cfg!(corvus_memo_from_the_start)
}
impl State {
fn remember(&mut self) {
let words = (((self.points * self.width) << MAX_MEMO_LOOPS) + 63) >> 6;
if words > MAX_MEMO_WORDS {
self.memo_off = true;
return;
}
if self.checks * (CHECK_EVERY as usize) < words + MEMO_STEPS_PER_POSITION * self.width && !memo_from_the_start()
{
return;
}
self.memo.clear();
self.memo.resize(words, 0);
self.memo_on = true;
}
#[cold]
#[inline(never)]
fn check(&mut self, depth: usize) -> bool {
if !self.memo_on && !self.memo_off {
self.checks += 1;
self.remember();
}
let Some(limit) = self.limit else { return true };
if depth > MAX_STACK {
self.stop = STOP_MEMORY;
return false;
}
let now = Instant::now();
match self.deadline {
None => self.deadline = Some(now + limit),
Some(deadline) => {
if now > deadline {
self.stop = STOP_TIME;
return false;
}
}
}
true
}
#[inline(always)]
fn undo(&mut self, e: Entry) {
let k = e.pc as usize;
match e.kind {
E_TMP => self.tmp[k] = e.pos,
E_CAP => (self.caps[2 * k], self.caps[2 * k + 1]) = (e.pos, e.a),
E_COUNTER => (self.counts[2 * k], self.counts[2 * k + 1]) = (e.pos, e.a),
_ => {}
}
}
}
impl Program {
#[inline]
fn memo_index(&self, st: &State, m: i32, pos: usize) -> usize {
let pt = &self.points[(m - 1) as usize];
let mut idx = ((m - 1) as usize * st.width + pos) << MAX_MEMO_LOOPS;
for k in 0..MAX_MEMO_LOOPS {
let l = pt.loops[k];
if l >= 0 && st.counts[2 * l as usize] > pt.mins[k] as usize && st.counts[2 * l as usize + 1] == pos {
idx |= 1 << k;
}
}
idx
}
pub(crate) fn is_match(&self, text: &str, limit: Option<Duration>) -> Result<bool, MatchError> {
STATE.with(|cell| match cell.try_borrow_mut() {
Ok(mut st) => self.match_with(&mut st, text, limit),
Err(_) => self.match_with(&mut State::default(), text, limit),
})
}
fn match_with(&self, st: &mut State, text: &str, limit: Option<Duration>) -> Result<bool, MatchError> {
st.caps.clear();
st.caps.resize(2 * self.groups, NONE);
st.tmp.clear();
st.tmp.resize(self.groups, 0);
st.counts.clear();
st.counts.resize(2 * self.loops, 0);
(st.limit, st.deadline, st.steps, st.stop) = (limit.filter(|l| !l.is_zero()), None, CHECK_EVERY, STOP_NONE);
(st.memo_on, st.memo_off, st.checks) = (false, self.points.is_empty(), 0);
(st.width, st.points) = (text.len() + 1, self.points.len());
if !st.memo_off && memo_from_the_start() {
st.remember();
}
let ok = self.search(st, text);
let stop = st.stop;
if st.stack.capacity() > MAX_RETAINED_STACK {
st.stack = Vec::new();
}
if st.memo.capacity() > MAX_RETAINED_MEMO {
st.memo = Vec::new();
}
match stop {
STOP_TIME => Err(MatchError::Time),
STOP_MEMORY => Err(MatchError::Memory),
_ => Ok(ok),
}
}
fn search(&self, st: &mut State, text: &str) -> bool {
let input = text.as_bytes();
if input.len() < self.need {
return false;
}
if self.anchored {
return self.run(st, input, 0);
}
if !self.prefix.is_empty() {
let mut at = 0;
loop {
let Some(i) = text[at..].find(&self.prefix) else { return false };
at += i;
if input.len() - at < self.need {
return false;
}
if self.run(st, input, at) {
return true;
}
if st.stop != STOP_NONE {
return false;
}
at += 1;
while !text.is_char_boundary(at) {
at += 1;
}
}
}
let mut at = 0;
loop {
if input.len() - at < self.need {
return false;
}
if at == input.len() {
return self.first.is_none() && self.run(st, input, at);
}
let (mut r, mut w) = (input[at] as Rune, 1);
if input[at] >= RUNE_SELF {
(r, w) = if self.units { unit_at(input, at) } else { decode_rune(&input[at..]) };
}
if self.first.as_ref().is_none_or(|first| first.contains(r)) && self.run(st, input, at) {
return true;
}
if st.stop != STOP_NONE {
return false;
}
at += w;
}
}
fn run(&self, st: &mut State, input: &[u8], start: usize) -> bool {
let mut stack = std::mem::take(&mut st.stack);
stack.clear();
let matched = self.run_on(st, &mut stack, input, start);
st.stack = stack;
matched
}
fn run_on(&self, st: &mut State, stack: &mut Vec<Entry>, input: &[u8], start: usize) -> bool {
let insts = &self.insts[..];
let quick = &self.quick[..];
let units = self.units;
let len = input.len();
let (mut pc, mut pos) = (0usize, start);
let mut steps = st.steps;
let mut memo = st.memo_on;
'run: loop {
steps -= 1;
if steps < 0 {
if !st.check(stack.len()) {
st.steps = 0;
return false;
}
steps = CHECK_EVERY;
memo = st.memo_on;
}
let i = &insts[pc];
match i.op {
Op::Char => {
if pos < len {
let c = input[pos];
if c < RUNE_SELF {
if c as Rune == i.r {
pos += 1;
pc += 1;
continue 'run;
}
} else {
let (r, w) = if units { unit_at(input, pos) } else { decode_rune(&input[pos..]) };
if r == i.r {
pos += w;
pc += 1;
continue 'run;
}
}
}
}
Op::Set => {
if pos < len {
let set = set_of(i);
let c = input[pos];
if c < RUNE_SELF {
if set.has_ascii(c) {
pos += 1;
pc += 1;
continue 'run;
}
} else {
let (r, w) = if units { unit_at(input, pos) } else { decode_rune(&input[pos..]) };
if set.contains(r) {
pos += w;
pc += 1;
continue 'run;
}
}
}
}
Op::Lit => {
let n = i.lit.len();
if len - pos >= n && input[pos..pos + n] == *i.lit {
pos += n;
pc += 1;
continue 'run;
}
}
Op::CharBack | Op::SetBack => {
if pos > 0 {
let (mut r, mut w) = (input[pos - 1] as Rune, 1);
if input[pos - 1] >= RUNE_SELF {
(r, w) = if units { unit_before(input, pos) } else { decode_last_rune(&input[..pos]) };
}
if if i.op == Op::CharBack { r == i.r } else { set_of(i).contains(r) } {
pos -= w;
pc += 1;
continue 'run;
}
}
}
Op::Fail => {}
Op::Bol => {
if pos == 0 {
pc += 1;
continue 'run;
}
}
Op::Eol => {
if pos == len {
pc += 1;
continue 'run;
}
}
Op::WordBoundary | Op::NotWordBoundary => {
let before = pos > 0 && is_word_byte(input[pos - 1]);
let after = pos < len && is_word_byte(input[pos]);
if (before != after) == (i.op == Op::WordBoundary) {
pc += 1;
continue 'run;
}
}
Op::LineStart => {
if pos == 0 || line_terminator_before(input, pos) {
pc += 1;
continue 'run;
}
}
Op::LineEnd => {
if pos == len || line_terminator_at(input, pos) {
pc += 1;
continue 'run;
}
}
Op::WordBoundaryFold | Op::NotWordBoundaryFold => {
let before = pos > 0 && is_fold_word_rune(decode_last_rune(&input[..pos]).0);
let after = pos < len && is_fold_word_rune(decode_rune(&input[pos..]).0);
if (before != after) == (i.op == Op::WordBoundaryFold) {
pc += 1;
continue 'run;
}
}
Op::Jmp => {
pc = i.x as usize;
continue 'run;
}
Op::Split => 'split: {
if memo && i.m != 0 {
let idx = self.memo_index(st, i.m, pos);
if (st.memo[idx >> 6] >> (idx & 63)) & 1 != 0 {
break 'split;
}
if stack.len() < MAX_MARK_DEPTH {
stack.push(Entry { kind: E_MARK, pc: 0, pos: idx, a: 0 });
}
}
if !quick[i.y as usize].allows(input, pos) {
if !quick[i.x as usize].allows(input, pos) {
break 'split;
}
pc = i.x as usize;
continue 'run;
}
if !quick[i.x as usize].allows(input, pos) {
pc = i.y as usize;
continue 'run;
}
stack.push(Entry { kind: E_CHOICE, pc: i.y, pos, a: 0 });
pc = i.x as usize;
continue 'run;
}
Op::SaveTmp => {
let a = i.a as usize;
stack.push(Entry { kind: E_TMP, pc: i.a, pos: st.tmp[a], a: 0 });
st.tmp[a] = pos;
pc += 1;
continue 'run;
}
Op::Capture => {
let a = i.a as usize;
stack.push(Entry { kind: E_CAP, pc: i.a, pos: st.caps[2 * a], a: st.caps[2 * a + 1] });
if i.flag {
(st.caps[2 * a], st.caps[2 * a + 1]) = (pos, st.tmp[a]);
} else {
(st.caps[2 * a], st.caps[2 * a + 1]) = (st.tmp[a], pos);
}
pc += 1;
continue 'run;
}
Op::Clear => {
steps -= (i.b - i.a) as i64;
for g in i.a..=i.b {
let k = g as usize;
if st.caps[2 * k] != NONE {
stack.push(Entry { kind: E_CAP, pc: g, pos: st.caps[2 * k], a: st.caps[2 * k + 1] });
(st.caps[2 * k], st.caps[2 * k + 1]) = (NONE, NONE);
}
}
pc += 1;
continue 'run;
}
Op::Backref => {
let a = i.a as usize;
let (from, to) = (st.caps[2 * a], st.caps[2 * a + 1]);
if from == NONE {
pc += 1;
continue 'run;
}
let n = to - from;
if len - pos >= n && input[pos..pos + n] == input[from..to] {
steps -= (n >> 3) as i64;
pos += n;
pc += 1;
continue 'run;
}
}
Op::BackrefBack => {
let a = i.a as usize;
let (from, to) = (st.caps[2 * a], st.caps[2 * a + 1]);
if from == NONE {
pc += 1;
continue 'run;
}
let n = to - from;
if pos >= n && input[pos - n..pos] == input[from..to] {
steps -= (n >> 3) as i64;
pos -= n;
pc += 1;
continue 'run;
}
}
Op::BackrefFold | Op::BackrefUnits => {
let a = i.a as usize;
let (mut from, to) = (st.caps[2 * a], st.caps[2 * a + 1]);
if from == NONE {
pc += 1;
continue 'run;
}
steps -= ((to - from) >> 3) as i64;
let (mut at, mut ok) = (pos, true);
while from < to && ok {
let (want, w) = decode(input, from, units);
from += w;
if at >= len {
ok = false;
break;
}
let (got, w) = decode(input, at, units);
at += w;
ok = got == want
|| i.op == Op::BackrefFold && canonicalize(got, i.flag) == canonicalize(want, i.flag);
}
if ok {
pos = at;
pc += 1;
continue 'run;
}
}
Op::BackrefFoldBack | Op::BackrefUnitsBack => {
let a = i.a as usize;
let (from, mut to) = (st.caps[2 * a], st.caps[2 * a + 1]);
if from == NONE {
pc += 1;
continue 'run;
}
steps -= ((to - from) >> 3) as i64;
let (mut at, mut ok) = (pos, true);
while from < to && ok {
let (want, w) = decode_last(input, to, units);
to -= w;
if at == 0 {
ok = false;
break;
}
let (got, w) = decode_last(input, at, units);
at -= w;
ok = got == want
|| i.op == Op::BackrefFoldBack && canonicalize(got, i.flag) == canonicalize(want, i.flag);
}
if ok {
pos = at;
pc += 1;
continue 'run;
}
}
Op::Look => {
if quick[pc].allows(input, pos) {
stack.push(Entry { kind: if i.flag { E_LOOK_NEG } else { E_LOOK }, pc: i.x, pos, a: 0 });
pc += 1;
continue 'run;
}
}
Op::LookOk => {
let mut f = stack.len() - 1;
while stack[f].kind != E_LOOK {
f -= 1;
}
pos = stack[f].pos;
let mut keep = f;
for k in f + 1..stack.len() {
if stack[k].kind >= E_TMP {
stack[keep] = stack[k];
keep += 1;
}
}
stack.truncate(keep);
pc += 1;
continue 'run;
}
Op::LookFail => {
while let Some(e) = stack.pop() {
if e.kind == E_LOOK_NEG {
break;
}
st.undo(e);
}
}
Op::Rep => 'rep: {
let (mut at, mut n, mut floor) = (pos, 0i32, pos);
if let Some(set) = &i.set {
while n != i.max && at < len {
let c = input[at];
if c < RUNE_SELF {
if !set.has_ascii(c) {
break;
}
at += 1;
} else {
let (r, w) = if units { unit_at(input, at) } else { decode_rune(&input[at..]) };
if !set.contains(r) {
break;
}
at += w;
}
n += 1;
if n == i.min {
floor = at;
}
}
} else {
while n != i.max && at < len {
let (mut r, mut w) = (input[at] as Rune, 1);
if input[at] >= RUNE_SELF {
(r, w) = if units { unit_at(input, at) } else { decode_rune(&input[at..]) };
}
if r != i.r {
break;
}
at += w;
n += 1;
if n == i.min {
floor = at;
}
}
}
steps -= ((at - pos) >> 3) as i64;
if n >= i.min {
if memo && i.m != 0 {
let idx = self.memo_index(st, i.m, at);
if (st.memo[idx >> 6] >> (idx & 63)) & 1 != 0 {
if at > floor {
stack.push(Entry { kind: E_REP, pc: (pc + 1) as i32, pos: at, a: floor });
}
break 'rep;
}
if at == floor && stack.len() < MAX_MARK_DEPTH {
stack.push(Entry { kind: E_MARK, pc: 0, pos: idx, a: 0 });
}
}
if at > floor {
stack.push(Entry { kind: E_REP, pc: (pc + 1) as i32, pos: at, a: floor });
}
pos = at;
pc += 1;
continue 'run;
}
}
Op::RepLazy => 'rep: {
let (mut at, mut n) = (pos, 0i32);
while n < i.min {
let w = one(i, input, at, units);
if w == 0 {
break;
}
at += w;
n += 1;
}
steps -= (n >> 3) as i64;
if n == i.min {
if memo && i.m != 0 {
let idx = self.memo_index(st, i.m, at);
if (st.memo[idx >> 6] >> (idx & 63)) & 1 != 0 {
if n != i.max {
stack.push(Entry { kind: E_REP_LAZY, pc: pc as i32, pos: at, a: n as usize });
}
break 'rep;
}
if n == i.max && stack.len() < MAX_MARK_DEPTH {
stack.push(Entry { kind: E_MARK, pc: 0, pos: idx, a: 0 });
}
}
if n != i.max {
stack.push(Entry { kind: E_REP_LAZY, pc: pc as i32, pos: at, a: n as usize });
}
pos = at;
pc += 1;
continue 'run;
}
}
Op::LoopInit => {
let a = i.a as usize;
stack.push(Entry { kind: E_COUNTER, pc: i.a, pos: st.counts[2 * a], a: st.counts[2 * a + 1] });
(st.counts[2 * a], st.counts[2 * a + 1]) = (0, NONE);
pc += 1;
continue 'run;
}
Op::Loop => 'looping: {
let a = i.a as usize;
let n = st.counts[2 * a] as i64;
let (min, need, left) = (i.min as i64, i.need as i64, (len - pos) as i64);
if left < need || n < min && i.iter != 0 && left - need < (min - n) * i.iter as i64 {
break 'looping;
}
if n > min && pos == st.counts[2 * a + 1] {
break 'looping;
}
if n < min {
pc += 1;
continue 'run;
}
if i.max >= 0 && n >= i.max as i64 {
pc = i.x as usize;
continue 'run;
}
if memo && i.m != 0 {
let idx = self.memo_index(st, i.m, pos);
if (st.memo[idx >> 6] >> (idx & 63)) & 1 != 0 {
break 'looping;
}
if stack.len() < MAX_MARK_DEPTH {
stack.push(Entry { kind: E_MARK, pc: 0, pos: idx, a: 0 });
}
}
if !quick[i.x as usize].allows(input, pos) {
pc += 1;
} else if !quick[pc + 1].allows(input, pos) {
pc = i.x as usize;
} else if i.flag {
stack.push(Entry { kind: E_CHOICE, pc: (pc + 1) as i32, pos, a: 0 });
pc = i.x as usize;
} else {
stack.push(Entry { kind: E_CHOICE, pc: i.x, pos, a: 0 });
pc += 1;
}
continue 'run;
}
Op::LoopIter => {
let a = i.a as usize;
stack.push(Entry { kind: E_COUNTER, pc: i.a, pos: st.counts[2 * a], a: st.counts[2 * a + 1] });
st.counts[2 * a] += 1;
st.counts[2 * a + 1] = pos;
pc += 1;
continue 'run;
}
Op::Match => {
st.steps = steps;
return true;
}
}
loop {
let Some(&e) = stack.last() else {
st.steps = steps;
return false;
};
let top = stack.len() - 1;
match e.kind {
E_CHOICE => {
(pc, pos) = (e.pc as usize, e.pos);
stack.pop();
break;
}
E_REP => {
let rep = &insts[(e.pc - 1) as usize];
let marked = memo && rep.m != 0;
if marked {
let idx = self.memo_index(st, rep.m, e.pos);
st.memo[idx >> 6] |= 1 << (idx & 63);
}
let mut w = 1;
if input[e.pos - 1] >= RUNE_SELF {
w = if units {
unit_before(input, e.pos).1
} else {
decode_last_rune(&input[e.a..e.pos]).1
};
}
let at = e.pos - w;
stack[top].pos = at;
(pc, pos) = (e.pc as usize, at);
let last = at <= e.a;
let mut pass = !quick[pc].allows(input, pos);
if marked && !pass {
let idx = self.memo_index(st, rep.m, pos);
if (st.memo[idx >> 6] >> (idx & 63)) & 1 != 0 {
pass = true;
} else if last {
stack[top] = Entry { kind: E_MARK, pc: 0, pos: idx, a: 0 };
break;
}
}
if last {
stack.pop();
}
if pass {
steps -= 1;
continue;
}
break;
}
E_REP_LAZY => {
let rep = &insts[e.pc as usize];
let marked = memo && rep.m != 0;
if marked {
let idx = self.memo_index(st, rep.m, e.pos);
st.memo[idx >> 6] |= 1 << (idx & 63);
}
let w = one(rep, input, e.pos, units);
if w == 0 {
stack.pop();
continue;
}
let (at, taken) = (e.pos + w, e.a + 1);
(stack[top].pos, stack[top].a) = (at, taken);
(pc, pos) = (e.pc as usize + 1, at);
let last = taken as i64 == rep.max as i64;
if marked {
let idx = self.memo_index(st, rep.m, pos);
if (st.memo[idx >> 6] >> (idx & 63)) & 1 != 0 {
if last {
stack.pop();
}
steps -= 1;
continue;
}
if last {
stack[top] = Entry { kind: E_MARK, pc: 0, pos: idx, a: 0 };
break;
}
}
if last {
stack.pop();
}
break;
}
E_MARK => {
st.memo[e.pos >> 6] |= 1 << (e.pos & 63);
stack.pop();
}
E_LOOK => {
stack.pop();
}
E_LOOK_NEG => {
(pc, pos) = (e.pc as usize, e.pos);
stack.pop();
break;
}
_ => {
st.undo(e);
stack.pop();
}
}
}
}
}
}
#[inline(always)]
fn set_of(i: &Inst) -> &super::charset::CharSet {
i.set.as_deref().expect("a set instruction has a class")
}
fn line_terminator_before(input: &[u8], pos: usize) -> bool {
match input[pos - 1] {
b'\n' | b'\r' => true,
0xA8 | 0xA9 => pos >= 3 && input[pos - 3] == 0xE2 && input[pos - 2] == 0x80,
_ => false,
}
}
fn line_terminator_at(input: &[u8], pos: usize) -> bool {
match input[pos] {
b'\n' | b'\r' => true,
0xE2 => pos + 2 < input.len() && input[pos + 1] == 0x80 && (input[pos + 2] == 0xA8 || input[pos + 2] == 0xA9),
_ => false,
}
}
#[inline]
fn one(i: &Inst, input: &[u8], at: usize, units: bool) -> usize {
if at >= input.len() {
return 0;
}
let (mut r, mut w) = (input[at] as Rune, 1);
if input[at] >= RUNE_SELF {
(r, w) = if units { unit_at(input, at) } else { decode_rune(&input[at..]) };
}
match &i.set {
Some(set) => {
if !set.contains(r) {
return 0;
}
}
None => {
if r != i.r {
return 0;
}
}
}
w
}