use crate::StateId;
use std::{collections::HashMap, fmt::Debug, ops::RangeBounds};
use tracing::trace;
pub const INIT_STATE: StateId = StateId(0);
pub const ANY_STATE: StateId = StateId(1);
pub(crate) type Input = u16;
const ANY_INPUT: Input = 0x100;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Edge<A: PartialEq + Eq> {
to: StateId,
action: Option<A>,
}
struct OptionalPrefix {
inputs: Vec<Input>,
case_sensitive: bool,
repeat: bool,
}
pub struct DfaBuilder<'a, A: Copy + Debug + PartialEq + Eq> {
dfa: &'a mut Dfa<A>,
state: StateId,
optional_prefixes: Vec<OptionalPrefix>,
optional_states: Vec<StateId>,
last_edges: Vec<(StateId, Input, bool)>,
}
impl<A: Copy + Debug + PartialEq + Eq> DfaBuilder<'_, A> {
fn new(dfa: &mut Dfa<A>, state: StateId) -> DfaBuilder<'_, A> {
DfaBuilder {
dfa,
state,
optional_prefixes: Vec::new(),
optional_states: Vec::new(),
last_edges: Vec::new(),
}
}
pub fn state(&self) -> StateId {
self.state
}
pub fn with(&mut self, action: A) -> &mut Self {
trace!("with; state={:?}, action={:?}", self.state, action);
self.push_optional_prefixes();
for (from, input, case_sensitive) in self.last_edges.clone() {
self.dfa.add_action(from, input, action);
if !case_sensitive && let Some(other) = other_case(input) {
self.dfa.add_action(from, other, action);
}
}
self
}
fn push_optional_prefixes(&mut self) {
for prefix in std::mem::take(&mut self.optional_prefixes) {
let OptionalPrefix {
inputs,
case_sensitive,
repeat,
} = prefix;
let mut sources = vec![self.state];
sources.extend_from_slice(&self.optional_states);
for source in sources {
let mut from = source;
for (i, input) in inputs.iter().enumerate() {
let to = if i == inputs.len() - 1 {
self.last_edges.push((from, *input, case_sensitive));
repeat.then_some(source)
} else {
None
};
from = self.push_edge_from(from, *input, to, case_sensitive);
}
if !repeat && from != self.state && !self.optional_states.contains(&from) {
self.optional_states.push(from);
}
}
}
}
fn push_edge(&mut self, input: Input, to: Option<StateId>, case_sensitive: bool) {
self.push_optional_prefixes();
trace!(
"push_edge; state={:?}, input={}, to={:?}",
self.state,
fmt_input(input),
to
);
let start = self.state;
let to = self.push_edge_from(start, input, to, case_sensitive);
self.last_edges = vec![(start, input, case_sensitive)];
for from in std::mem::take(&mut self.optional_states) {
self.push_edge_from(from, input, Some(to), case_sensitive);
self.last_edges.push((from, input, case_sensitive));
}
self.state = to;
}
fn push_edge_from(
&mut self,
from: StateId,
input: Input,
to: Option<StateId>,
case_sensitive: bool,
) -> StateId {
let to = to.unwrap_or(self.dfa.next_state(&from, &input));
self.dfa.insert_edge(from, input, to, None);
if !case_sensitive && let Some(other) = other_case(input) {
self.dfa.insert_edge(from, other, to, None);
}
to
}
pub fn push(&mut self, input: &str) -> &mut Self {
self.push_inner(input.as_bytes(), true)
}
pub fn push_bytes(&mut self, input: &[u8]) -> &mut Self {
self.push_inner(input, true)
}
pub fn push_ci(&mut self, input: &str) -> &mut Self {
self.push_inner(input.as_bytes(), false)
}
pub fn push_inner(&mut self, input: &[u8], case_sensitive: bool) -> &mut Self {
for b in input {
self.push_edge(Input::from(*b), None, case_sensitive);
}
self
}
pub fn push_any<R: RangeBounds<usize>>(&mut self, range: R) -> &mut Self {
let min_len = match range.start_bound() {
std::ops::Bound::Excluded(n) => n.saturating_add(1),
std::ops::Bound::Included(n) => *n,
std::ops::Bound::Unbounded => 0,
};
let max_len = match range.end_bound() {
std::ops::Bound::Excluded(n) => n.saturating_sub(1),
std::ops::Bound::Included(n) => *n,
std::ops::Bound::Unbounded => min_len,
};
assert!(min_len <= max_len, "Cannot push an empty range");
trace!(
"push_any; state={:?}, min_len={:?}, max_len={:?}",
self.state, min_len, max_len
);
for _ in 0..min_len {
self.push_edge(ANY_INPUT, None, true);
}
for _ in 0..max_len - min_len {
self.optional_prefixes.push(OptionalPrefix {
inputs: vec![ANY_INPUT],
case_sensitive: true,
repeat: false,
});
}
if matches!(range.end_bound(), std::ops::Bound::Unbounded) {
self.push_edge_from(self.state, ANY_INPUT, Some(self.state), true);
self.last_edges.push((self.state, ANY_INPUT, true));
}
self
}
pub fn push_options_ci(&mut self, inputs: &[&str]) -> &mut Self {
self.push_options_inner(inputs, false)
}
pub fn push_options_inner(&mut self, inputs: &[&str], case_sensitive: bool) -> &mut Self {
let Some(longest) = inputs.iter().copied().max_by_key(|input| input.len()) else {
return self;
};
let start = self.state;
self.push_inner(longest.as_bytes(), case_sensitive);
let final_state = self.state;
let mut last_edges = std::mem::take(&mut self.last_edges);
trace!(
"push_options; state={:?}, longest={}, final_state={:?}",
start,
longest.escape_debug(),
final_state
);
for input in inputs.iter().copied().filter(|input| *input != longest) {
assert!(!input.is_empty(), "Cannot push an empty option");
self.state = start;
for (i, b) in input.as_bytes().iter().enumerate() {
let to = if i == input.len() - 1 {
Some(final_state)
} else {
None
};
self.push_edge(Input::from(*b), to, case_sensitive);
}
last_edges.append(&mut self.last_edges);
}
self.last_edges = last_edges;
self
}
pub fn push_optional(&mut self, input: &str, repeat: bool) -> &mut Self {
self.optional_prefixes.push(OptionalPrefix {
inputs: input.bytes().map(Input::from).collect(),
case_sensitive: true,
repeat,
});
self
}
pub fn restart_with(&mut self, input: &str) {
let final_state = input.bytes().map(Input::from).fold(ANY_STATE, |state, b| {
let next = self.dfa.next_state(&state, &b);
self.push_edge_from(state, b, Some(next), false);
next
});
trace!(
"restart_with; input={}, final_state={:?}",
input.escape_debug(),
final_state
);
for (i, b) in input.as_bytes().iter().enumerate() {
let to = if i == input.len() - 1 {
Some(final_state)
} else {
None
};
self.push_edge(Input::from(*b), to, false);
}
}
}
fn other_case(input: Input) -> Option<Input> {
let byte = u8::try_from(input).ok()?;
let other = if byte.is_ascii_lowercase() {
byte.to_ascii_uppercase()
} else {
byte.to_ascii_lowercase()
};
(other != byte).then(|| Input::from(other))
}
pub(crate) fn fmt_input(input: Input) -> String {
match u8::try_from(input) {
Ok(byte) => (byte as char).escape_debug().to_string(),
Err(_) => "<any>".to_string(),
}
}
type EdgeMap<A> = HashMap<StateId, HashMap<Input, Edge<A>>>;
pub(crate) struct Dfa<A: Copy + Debug + PartialEq + Eq> {
num_states: u16,
edges: EdgeMap<A>,
}
impl<A: Copy + Debug + PartialEq + Eq> Dfa<A> {
pub fn new() -> Dfa<A> {
Dfa::with_reserved_states(2)
}
pub fn with_reserved_states(reserved: u16) -> Dfa<A> {
Dfa {
num_states: reserved.max(2),
edges: HashMap::new(),
}
}
pub fn num_states(&self) -> u16 {
self.num_states
}
pub fn start_pattern<'a>(&'a mut self, state: StateId) -> DfaBuilder<'a, A> {
trace!("start_pattern; state={:?}", state);
DfaBuilder::new(self, state)
}
fn new_state(&mut self) -> StateId {
let id = StateId(self.num_states);
self.num_states = self.num_states.strict_add(1);
id
}
fn next_state(&mut self, from: &StateId, input: &Input) -> StateId {
self.edges
.get(from)
.and_then(|es| es.get(input).map(|edge| edge.to))
.unwrap_or_else(|| self.new_state())
}
pub fn insert_edge(&mut self, from: StateId, input: Input, to: StateId, action: Option<A>) {
let edges = self.edges.entry(from).or_default();
let Some(old) = edges.get_mut(&input) else {
let _ = edges.insert(input, Edge { to, action });
return;
};
assert!(
old.to == to,
"Cannot create a transition from {from:?} to {:?} and {to:?}",
old.to
);
match (old.action, action) {
(_, None) => {}
(None, Some(action)) => old.action = Some(action),
(Some(old_action), Some(action)) => assert!(
old_action == action,
"Cannot {action:?} and {old_action:?} on the same transition"
),
}
}
fn add_action(&mut self, from: StateId, input: Input, action: A) {
let Some(edges) = self.edges.get_mut(&from) else {
panic!("State not found");
};
let Some(edge) = edges.get_mut(&input) else {
panic!("Edge not found");
};
if let Some(old_action) = edge.action {
assert!(
old_action == action,
"Cannot {action:?} and {old_action:?} on the same transition"
);
}
edge.action = Some(action);
}
pub fn iter_transitions(
&self,
) -> impl Iterator<Item = (StateId, Input, StateId, Option<A>)> + '_ {
self.edges.iter().flat_map(move |(from, edges)| {
edges
.iter()
.map(move |(input, edge)| (*from, *input, edge.to, edge.action))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn matches_input<A: Copy + Debug + PartialEq + Eq>(
dfa: &Dfa<A>,
from: StateId,
input: &[u8],
) -> bool {
let mut state = from;
for &byte in input {
let Some(edge) = dfa
.edges
.get(&state)
.and_then(|edges| edges.get(&(byte as u16)))
else {
return false;
};
state = edge.to;
}
true
}
fn dfa_with_optional_input(repeatable: bool) -> Dfa<()> {
let mut dfa: Dfa<()> = Dfa::new();
dfa.start_pattern(INIT_STATE)
.push_ci("aaa")
.push_optional("b", repeatable)
.push_ci("c");
dfa
}
#[test]
fn non_repeatable_optional_input_cannot_be_repeated() {
let dfa = dfa_with_optional_input(false);
assert!(matches_input(&dfa, INIT_STATE, b"aaa"));
assert!(matches_input(&dfa, INIT_STATE, b"aaab"));
assert!(matches_input(&dfa, INIT_STATE, b"aaabc"));
assert!(!matches_input(&dfa, INIT_STATE, b"aaabbc"));
}
#[test]
fn repeatable_optional_input_can_be_repeated() {
let dfa = dfa_with_optional_input(true);
assert!(matches_input(&dfa, INIT_STATE, b"aaa"));
assert!(matches_input(&dfa, INIT_STATE, b"aaab"));
assert!(matches_input(&dfa, INIT_STATE, b"aaabc"));
assert!(matches_input(&dfa, INIT_STATE, b"aaabbc"));
}
#[test]
fn optional_input_is_optional() {
let dfa = dfa_with_optional_input(true);
assert!(matches_input(&dfa, INIT_STATE, b"aaa"));
assert!(matches_input(&dfa, INIT_STATE, b"aaac"));
let dfa = dfa_with_optional_input(false);
assert!(matches_input(&dfa, INIT_STATE, b"aaa"));
assert!(matches_input(&dfa, INIT_STATE, b"aaac"));
}
}