use rustc_hash::FxHashMap;
use super::class::ClassSet;
use super::parser::{AnchorKind, Expr};
pub type StateId = u32;
pub type ClassId = u32;
#[derive(Debug, Clone)]
pub enum State {
Char { class: ClassId, next: StateId },
Split(StateId, StateId),
Assert { kind: AnchorKind, next: StateId },
Match,
}
#[derive(Debug, Clone)]
pub struct Program {
pub states: Vec<State>,
pub classes: Vec<ClassSet>,
pub start: StateId,
}
pub fn compile(ast: &Expr) -> Result<Program, String> {
let mut b = Builder {
states: Vec::new(),
classes: Vec::new(),
intern: FxHashMap::default(),
};
let match_state = b.add(State::Match);
let frag = b.lower(ast, match_state);
Ok(Program {
start: frag.entry,
states: b.states,
classes: b.classes,
})
}
struct Frag {
entry: StateId,
}
struct Builder {
states: Vec<State>,
classes: Vec<ClassSet>,
intern: FxHashMap<Vec<(u32, u32)>, ClassId>,
}
impl Builder {
fn add(&mut self, s: State) -> StateId {
let id = self.states.len() as StateId;
self.states.push(s);
id
}
fn intern_class(&mut self, set: ClassSet) -> ClassId {
if let Some(&id) = self.intern.get(set.ranges()) {
return id;
}
let id = self.classes.len() as ClassId;
let key = set.ranges().to_vec();
self.classes.push(set);
self.intern.insert(key, id);
id
}
fn lower(&mut self, ast: &Expr, out: StateId) -> Frag {
match ast {
Expr::Empty => Frag { entry: out },
Expr::Class(set) => {
let class = self.intern_class(set.clone());
let entry = self.add(State::Char { class, next: out });
Frag { entry }
}
Expr::Concat(parts) => {
let mut next = out;
for p in parts.iter().rev() {
next = self.lower(p, next).entry;
}
Frag { entry: next }
}
Expr::Alt(branches) => {
if branches.is_empty() {
return Frag { entry: out };
}
let mut iter = branches.iter().rev();
let last = iter.next().unwrap();
let mut acc = self.lower(last, out).entry;
for branch in iter {
let b_entry = self.lower(branch, out).entry;
acc = self.add(State::Split(b_entry, acc));
}
Frag { entry: acc }
}
Expr::Quant(body, min, max) => self.lower_quant(body, *min, *max, out),
Expr::Anchor(kind) => {
let entry = self.add(State::Assert { kind: *kind, next: out });
Frag { entry }
}
}
}
fn lower_quant(
&mut self,
body: &Expr,
min: u32,
max: Option<u32>,
out: StateId,
) -> Frag {
let tail = match max {
None => {
let split_id = self.add(State::Split(0, out));
let body_entry = self.lower(body, split_id).entry;
self.states[split_id as usize] = State::Split(body_entry, out);
split_id
}
Some(m) if m == min => out,
Some(m) => {
let mut cur = out;
for _ in 0..(m - min) {
let body_frag = self.lower(body, cur);
cur = self.add(State::Split(body_frag.entry, out));
}
cur
}
};
let mut entry = tail;
for _ in 0..min {
entry = self.lower(body, entry).entry;
}
Frag { entry }
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::parser::parse;
fn build(src: &str) -> Program {
compile(&parse(src).unwrap()).unwrap()
}
#[test]
fn empty_pattern_is_one_match_state() {
let p = build("");
assert!(matches!(p.states[p.start as usize], State::Match));
}
#[test]
fn literal_has_char_into_match() {
let p = build("a");
let State::Char { next, .. } = p.states[p.start as usize] else {
panic!("expected Char start");
};
assert!(matches!(p.states[next as usize], State::Match));
}
#[test]
fn alternation_emits_splits() {
let p = build("a|b|c");
let split_count = p.states.iter().filter(|s| matches!(s, State::Split(..))).count();
assert_eq!(split_count, 2);
}
#[test]
fn class_dedup() {
let p = build("[a-z][a-z]");
assert_eq!(p.classes.len(), 1);
}
#[test]
fn star_emits_one_split() {
let p = build("a*");
assert!(p.states.iter().filter(|s| matches!(s, State::Split(..))).count() >= 1);
}
}