use crate::parser::charclass::CharClass;
use crate::parser::quantifier::Quantifier;
use crate::parser::sequence::{Sequence, SequenceElement};
use std::collections::HashMap;
fn quantifier_bounds(q: &Quantifier) -> (usize, usize) {
match q {
Quantifier::ZeroOrMore | Quantifier::ZeroOrMoreLazy => (0, usize::MAX),
Quantifier::OneOrMore | Quantifier::OneOrMoreLazy => (1, usize::MAX),
Quantifier::ZeroOrOne | Quantifier::ZeroOrOneLazy => (0, 1),
Quantifier::Exactly(n) => (*n, *n),
Quantifier::AtLeast(n) => (*n, usize::MAX),
Quantifier::Between(n, m) => (*n, *m),
}
}
#[derive(Clone, Debug)]
pub struct LazyDFA {
instructions: Vec<Instruction>,
state_cache: HashMap<Vec<usize>, StateId>,
transition_cache: HashMap<(StateId, u8), StateId>,
next_state_id: StateId,
accept_states: HashMap<StateId, bool>,
}
type StateId = u32;
#[derive(Debug, Clone)]
enum Instruction {
Match(MatchType),
Split { first: usize, second: usize },
Jump(usize),
Accept,
}
#[derive(Debug, Clone)]
enum MatchType {
Literal(char),
Word,
Digit,
Whitespace,
Any,
Class(CharClass),
}
impl MatchType {
#[inline]
fn matches(&self, ch: char) -> bool {
match self {
MatchType::Literal(c) => ch == *c,
MatchType::Word => ch.is_alphanumeric() || ch == '_',
MatchType::Digit => ch.is_ascii_digit(),
MatchType::Whitespace => ch.is_whitespace(),
MatchType::Any => ch != '\n',
MatchType::Class(cc) => cc.matches(ch),
}
}
#[inline]
fn matches_byte(&self, byte: u8) -> bool {
if !byte.is_ascii() {
return false;
}
let ch = byte as char;
match self {
MatchType::Literal(c) => ch == *c,
MatchType::Word => ch.is_ascii_alphanumeric() || ch == '_',
MatchType::Digit => ch.is_ascii_digit(),
MatchType::Whitespace => matches!(ch, ' ' | '\t' | '\n' | '\r'),
MatchType::Any => ch != '\n',
MatchType::Class(cc) => cc.matches(ch),
}
}
}
impl LazyDFA {
pub fn try_compile(seq: &Sequence) -> Option<Self> {
let mut compiler = NFACompiler::new();
for elem in &seq.elements {
compiler.compile_element(elem)?;
}
compiler.add_accept();
Some(LazyDFA {
instructions: compiler.instructions,
state_cache: HashMap::new(),
transition_cache: HashMap::new(),
next_state_id: 1,
accept_states: HashMap::new(),
})
}
pub fn find(&mut self, text: &str) -> Option<(usize, usize)> {
let bytes = text.as_bytes();
for start in 0..text.len() {
if !text.is_char_boundary(start) {
continue;
}
let mut nfa_states = vec![0];
self.epsilon_closure(&mut nfa_states);
let mut dfa_state = self.get_or_create_dfa_state(&nfa_states);
let mut pos = start;
let mut last_match = None;
if self.is_accepting_state(dfa_state) {
last_match = Some(pos);
}
while pos < text.len() {
let byte = bytes[pos];
if let Some(&next_state) = self.transition_cache.get(&(dfa_state, byte)) {
dfa_state = next_state;
} else {
let nfa_states = self.get_nfa_states(dfa_state);
let mut next_nfa_states = Vec::new();
for &nfa_state in &nfa_states {
if let Some(Instruction::Match(match_type)) =
self.instructions.get(nfa_state)
{
if match_type.matches_byte(byte) {
next_nfa_states.push(nfa_state + 1);
}
}
}
if next_nfa_states.is_empty() {
break;
}
self.epsilon_closure(&mut next_nfa_states);
let next_dfa_state = self.get_or_create_dfa_state(&next_nfa_states);
self.transition_cache
.insert((dfa_state, byte), next_dfa_state);
dfa_state = next_dfa_state;
}
pos += 1;
if self.is_accepting_state(dfa_state) {
last_match = Some(pos);
}
}
if let Some(end) = last_match {
return Some((start, end));
}
}
None
}
fn get_or_create_dfa_state(&mut self, nfa_states: &[usize]) -> StateId {
let mut sorted_states = nfa_states.to_vec();
sorted_states.sort_unstable();
sorted_states.dedup();
if let Some(&state_id) = self.state_cache.get(&sorted_states) {
return state_id;
}
let state_id = self.next_state_id;
self.next_state_id += 1;
let is_accept = sorted_states
.iter()
.any(|&s| matches!(self.instructions.get(s), Some(Instruction::Accept)));
self.state_cache.insert(sorted_states.clone(), state_id);
self.accept_states.insert(state_id, is_accept);
state_id
}
fn get_nfa_states(&self, dfa_state: StateId) -> Vec<usize> {
for (nfa_states, &state_id) in &self.state_cache {
if state_id == dfa_state {
return nfa_states.clone();
}
}
vec![]
}
fn epsilon_closure(&self, states: &mut Vec<usize>) {
let mut i = 0;
while i < states.len() {
let state = states[i];
if let Some(instr) = self.instructions.get(state) {
match instr {
Instruction::Split { first, second } => {
if !states.contains(first) {
states.push(*first);
}
if !states.contains(second) {
states.push(*second);
}
}
Instruction::Jump(target) if !states.contains(target) => {
states.push(*target);
}
_ => {}
}
}
i += 1;
}
}
fn is_accepting_state(&self, state: StateId) -> bool {
self.accept_states.get(&state).copied().unwrap_or(false)
}
}
struct NFACompiler {
instructions: Vec<Instruction>,
}
impl NFACompiler {
fn new() -> Self {
NFACompiler {
instructions: Vec::new(),
}
}
fn compile_element(&mut self, elem: &SequenceElement) -> Option<()> {
match elem {
SequenceElement::Char(ch) => {
self.instructions
.push(Instruction::Match(MatchType::Literal(*ch)));
}
SequenceElement::Dot => {
self.instructions.push(Instruction::Match(MatchType::Any));
}
SequenceElement::CharClass(cc) => {
let match_type = Self::charclass_to_match_type(cc);
self.instructions.push(Instruction::Match(match_type));
}
SequenceElement::QuantifiedChar(ch, q) => {
self.compile_quantified(MatchType::Literal(*ch), q)?;
}
SequenceElement::QuantifiedCharClass(cc, q) => {
let match_type = Self::charclass_to_match_type(cc);
self.compile_quantified(match_type, q)?;
}
SequenceElement::Literal(s) => {
for ch in s.chars() {
self.instructions
.push(Instruction::Match(MatchType::Literal(ch)));
}
}
_ => return None, }
Some(())
}
fn compile_quantified(&mut self, match_type: MatchType, quantifier: &Quantifier) -> Option<()> {
let (min, max) = quantifier_bounds(quantifier);
match (min, max) {
(0, usize::MAX) => {
let split_pos = self.instructions.len();
let match_pos = split_pos + 1;
let after_pos = split_pos + 3;
self.instructions.push(Instruction::Split {
first: match_pos,
second: after_pos,
});
self.instructions.push(Instruction::Match(match_type));
self.instructions.push(Instruction::Jump(split_pos));
}
(1, usize::MAX) => {
let match_pos = self.instructions.len();
let split_pos = match_pos + 1;
self.instructions
.push(Instruction::Match(match_type.clone()));
self.instructions.push(Instruction::Split {
first: match_pos,
second: split_pos + 1,
});
}
(0, 1) => {
let split_pos = self.instructions.len();
let match_pos = split_pos + 1;
let after_pos = match_pos + 1;
self.instructions.push(Instruction::Split {
first: match_pos,
second: after_pos,
});
self.instructions.push(Instruction::Match(match_type));
}
_ => {
for _ in 0..min {
self.instructions
.push(Instruction::Match(match_type.clone()));
}
if max > min && max != usize::MAX {
for _ in 0..(max - min) {
let split_pos = self.instructions.len();
let match_pos = split_pos + 1;
let after_pos = match_pos + 1;
self.instructions.push(Instruction::Split {
first: match_pos,
second: after_pos,
});
self.instructions
.push(Instruction::Match(match_type.clone()));
}
}
}
}
Some(())
}
fn add_accept(&mut self) {
self.instructions.push(Instruction::Accept);
}
fn charclass_to_match_type(cc: &CharClass) -> MatchType {
if !cc.negated {
if cc.chars.is_empty() && cc.ranges.len() == 1 && cc.ranges[0] == ('0', '9') {
return MatchType::Digit;
}
if cc.ranges.contains(&('a', 'z'))
&& cc.ranges.contains(&('A', 'Z'))
&& cc.ranges.contains(&('0', '9'))
&& cc.chars.contains(&'_')
{
return MatchType::Word;
}
} else if cc.chars.len() == 1 && cc.chars[0] == '\n' && cc.ranges.is_empty() {
return MatchType::Any;
}
MatchType::Class(cc.clone())
}
}