use std::collections::HashMap;
use super::parse::{Assert, DUPINF, Look, Node, Set, to_lower};
use super::{Code, flags};
const MAX_INSTS: usize = 1 << 20;
const MAX_VISITED: usize = 1 << 26;
#[derive(Clone, Copy, Debug)]
enum Inst {
Set(usize),
Assert(Assert),
Look(Look, usize),
Save(usize),
Backref(usize),
Split(usize, usize),
Jmp(usize),
Mark(usize),
Progress(usize),
Match,
}
#[derive(Debug, Default)]
struct Prog {
insts: Vec<Inst>,
}
#[derive(Debug)]
pub(super) struct Program {
progs: Vec<Prog>,
sets: Vec<Set>,
groups: usize,
marks: usize,
backrefs: bool,
nlanch: bool,
icase: bool,
}
struct Compiler {
progs: Vec<Prog>,
sets: Vec<Set>,
marks: usize,
backrefs: bool,
}
impl Compiler {
fn emit(&mut self, prog: usize, inst: Inst) -> Result<usize, Code> {
let insts = &mut self.progs[prog].insts;
if insts.len() >= MAX_INSTS {
return Err(Code::TooBig);
}
insts.push(inst);
Ok(insts.len() - 1)
}
fn here(&self, prog: usize) -> usize {
self.progs[prog].insts.len()
}
fn patch(&mut self, prog: usize, at: usize, inst: Inst) {
self.progs[prog].insts[at] = inst;
}
fn node(&mut self, prog: usize, node: &Node, set_of: &mut SetIds) -> Result<(), Code> {
match node {
Node::Empty => {}
Node::Set(set) => {
let id = set_of.get(set, &mut self.sets);
self.emit(prog, Inst::Set(id))?;
}
Node::Assert(assert) => {
self.emit(prog, Inst::Assert(*assert))?;
}
Node::Look(look, inner) => {
let sub = self.progs.len();
self.progs.push(Prog::default());
self.node(sub, inner, set_of)?;
self.emit(sub, Inst::Match)?;
self.emit(prog, Inst::Look(*look, sub))?;
}
Node::Group(number, inner) => {
if *number > 0 {
self.emit(prog, Inst::Save(2 * number))?;
}
self.node(prog, inner, set_of)?;
if *number > 0 {
self.emit(prog, Inst::Save(2 * number + 1))?;
}
}
Node::Backref(number) => {
self.backrefs = true;
self.emit(prog, Inst::Backref(*number))?;
}
Node::Concat(items) => {
for item in items {
self.node(prog, item, set_of)?;
}
}
Node::Alt(branches) => {
let mut jumps = Vec::new();
for (at, branch) in branches.iter().enumerate() {
if at + 1 < branches.len() {
let split = self.emit(prog, Inst::Split(0, 0))?;
self.node(prog, branch, set_of)?;
jumps.push(self.emit(prog, Inst::Jmp(0))?);
let next = self.here(prog);
self.patch(prog, split, Inst::Split(split + 1, next));
} else {
self.node(prog, branch, set_of)?;
}
}
let end = self.here(prog);
for jump in jumps {
self.patch(prog, jump, Inst::Jmp(end));
}
}
Node::Repeat(inner, min, max, _) => {
for _ in 0..*min {
self.node(prog, inner, set_of)?;
}
if *max == DUPINF {
let mark = self.marks;
self.marks += 1;
let split = self.emit(prog, Inst::Split(0, 0))?;
self.emit(prog, Inst::Mark(mark))?;
self.node(prog, inner, set_of)?;
self.emit(prog, Inst::Progress(mark))?;
self.emit(prog, Inst::Jmp(split))?;
let end = self.here(prog);
self.patch(prog, split, Inst::Split(split + 1, end));
} else {
let mut splits = Vec::new();
for _ in *min..*max {
splits.push(self.emit(prog, Inst::Split(0, 0))?);
self.node(prog, inner, set_of)?;
}
let end = self.here(prog);
for split in splits {
self.patch(prog, split, Inst::Split(split + 1, end));
}
}
}
}
Ok(())
}
}
#[derive(Default)]
struct SetIds {
ids: HashMap<*const Set, usize>,
}
impl SetIds {
fn get(&mut self, set: &Set, sets: &mut Vec<Set>) -> usize {
*self.ids.entry(std::ptr::from_ref(set)).or_insert_with(|| {
sets.push(set.clone());
sets.len() - 1
})
}
}
enum Job {
Try(usize, usize),
Slot(usize, Option<usize>),
Mark(usize, Option<usize>),
}
struct Search<'a> {
program: &'a Program,
s: &'a [u32],
looks: Vec<Vec<u8>>,
}
fn is_word(c: u32) -> bool {
char::from_u32(c).is_some_and(|c| c.is_ascii_alphanumeric() || c == '_')
}
impl Program {
pub(super) fn new(node: &Node, groups: usize, cflags: u32) -> Result<Self, Code> {
let mut compiler =
Compiler { progs: vec![Prog::default()], sets: Vec::new(), marks: 0, backrefs: false };
let mut set_of = SetIds::default();
compiler.node(0, node, &mut set_of)?;
compiler.emit(0, Inst::Match)?;
Ok(Program {
progs: compiler.progs,
sets: compiler.sets,
groups,
marks: compiler.marks,
backrefs: compiler.backrefs,
nlanch: cflags & flags::NLANCH != 0,
icase: cflags & flags::ICASE != 0,
})
}
pub(super) fn first(&self, s: &[u32], from: usize) -> Option<usize> {
let mut search = Search { program: self, s, looks: vec![Vec::new(); self.progs.len()] };
let mut visited = search.visited(0);
(from..=s.len()).find(|&start| search.run(0, start, None, visited.as_mut(), None))
}
pub(super) fn ends(&self, s: &[u32], start: usize) -> Vec<bool> {
let mut search = Search { program: self, s, looks: vec![Vec::new(); self.progs.len()] };
let mut visited = search.visited(0);
let mut ends = vec![false; s.len() + 1];
search.run(0, start, None, visited.as_mut(), Some(&mut ends));
ends
}
}
impl Search<'_> {
fn visited(&self, prog: usize) -> Option<Vec<u64>> {
let bits = self.program.progs[prog].insts.len().checked_mul(self.s.len() + 1)?;
(!self.program.backrefs && bits <= MAX_VISITED).then(|| vec![0; bits.div_ceil(64)])
}
fn holds(&self, assert: Assert, pos: usize) -> bool {
let s = self.s;
let newline = u32::from(b'\n');
let word_before = pos > 0 && is_word(s[pos - 1]);
let word_after = pos < s.len() && is_word(s[pos]);
match assert {
Assert::Bol => pos == 0 || (self.program.nlanch && s[pos - 1] == newline),
Assert::Eol => pos == s.len() || (self.program.nlanch && s[pos] == newline),
Assert::Bos => pos == 0,
Assert::Eos => pos == s.len(),
Assert::WordStart => !word_before && word_after,
Assert::WordEnd => word_before && !word_after,
Assert::Boundary => word_before != word_after,
Assert::NotBoundary => word_before == word_after,
}
}
fn look(&mut self, look: Look, prog: usize, pos: usize) -> bool {
if self.looks[prog].is_empty() {
self.looks[prog] = vec![0; self.s.len() + 1];
}
let known = self.looks[prog][pos];
let found = if known != 0 {
known == 2
} else {
let found = match look {
Look::Ahead | Look::NotAhead => {
let mut visited = self.visited(prog);
self.run(prog, pos, None, visited.as_mut(), None)
}
Look::Behind | Look::NotBehind => {
let mut visited = self.visited(prog);
(0..=pos)
.rev()
.any(|start| self.run(prog, start, Some(pos), visited.as_mut(), None))
}
};
self.looks[prog][pos] = if found { 2 } else { 1 };
found
};
matches!(look, Look::Ahead | Look::Behind) == found
}
fn group(slots: &[Option<usize>], number: usize) -> Option<(usize, usize)> {
Some((slots.get(2 * number).copied()??, slots.get(2 * number + 1).copied()??))
}
fn backref(&self, slots: &[Option<usize>], number: usize, pos: usize) -> Option<usize> {
let (start, end) = Self::group(slots, number)?;
let len = end.checked_sub(start)?;
let here = self.s.get(pos..pos + len)?;
let same = if self.program.icase {
here.iter().zip(&self.s[start..end]).all(|(&a, &b)| to_lower(a) == to_lower(b))
} else {
here == &self.s[start..end]
};
same.then_some(pos + len)
}
fn run(
&mut self,
prog: usize,
start: usize,
end: Option<usize>,
mut visited: Option<&mut Vec<u64>>,
mut ends: Option<&mut [bool]>,
) -> bool {
let program = self.program;
let insts = &program.progs[prog].insts;
let width = self.s.len() + 1;
let mut slots: Vec<Option<usize>> = vec![None; 2 * program.groups + 2];
let mut marks: Vec<Option<usize>> = vec![None; program.marks];
let mut jobs = vec![Job::Try(0, start)];
while let Some(job) = jobs.pop() {
let (mut pc, mut pos) = match job {
Job::Try(pc, pos) => (pc, pos),
Job::Slot(slot, old) => {
slots[slot] = old;
continue;
}
Job::Mark(mark, old) => {
marks[mark] = old;
continue;
}
};
loop {
if let Some(visited) = visited.as_deref_mut() {
let bit = pc * width + pos;
if visited[bit / 64] & (1 << (bit % 64)) != 0 {
break;
}
visited[bit / 64] |= 1 << (bit % 64);
}
match insts[pc] {
Inst::Set(id) => {
if pos < self.s.len() && program.sets[id].contains(self.s[pos]) {
pc += 1;
pos += 1;
} else {
break;
}
}
Inst::Assert(assert) => {
if !self.holds(assert, pos) {
break;
}
pc += 1;
}
Inst::Look(look, sub) => {
if !self.look(look, sub, pos) {
break;
}
pc += 1;
}
Inst::Save(slot) => {
jobs.push(Job::Slot(slot, slots[slot]));
slots[slot] = Some(pos);
pc += 1;
}
Inst::Backref(number) => match self.backref(&slots, number, pos) {
Some(next) => {
pc += 1;
pos = next;
}
None => break,
},
Inst::Split(first, second) => {
jobs.push(Job::Try(second, pos));
pc = first;
}
Inst::Jmp(to) => pc = to,
Inst::Mark(mark) => {
jobs.push(Job::Mark(mark, marks[mark]));
marks[mark] = Some(pos);
pc += 1;
}
Inst::Progress(mark) => {
if marks[mark] == Some(pos) {
break;
}
pc += 1;
}
Inst::Match => {
if let Some(ends) = ends.as_deref_mut() {
ends[pos] = true;
break;
}
if end.is_none_or(|end| end == pos) {
return true;
}
break;
}
}
}
}
false
}
}