mod minimizer;
mod nfa;
mod small_set;
mod state;
pub use state::{State, StateAttributes};
use super::{error::CompilerError, JsonPathQuery, Label};
use crate::debug;
use nfa::NondeterministicAutomaton;
use smallvec::SmallVec;
use std::{fmt::Display, ops::Index};
#[derive(Debug, PartialEq, Eq)]
pub struct Automaton<'q> {
states: Vec<StateTable<'q>>,
}
type Transition<'q> = (&'q Label, State);
#[derive(Debug)]
pub struct StateTable<'q> {
attributes: StateAttributes,
transitions: SmallVec<[Transition<'q>; 2]>,
fallback_state: State,
}
impl<'q> Default for StateTable<'q> {
#[inline]
fn default() -> Self {
Self {
attributes: StateAttributes::default(),
transitions: Default::default(),
fallback_state: State(0),
}
}
}
impl<'q> PartialEq for StateTable<'q> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.fallback_state == other.fallback_state
&& self.transitions.len() == other.transitions.len()
&& self
.transitions
.iter()
.all(|x| other.transitions.contains(x))
&& other
.transitions
.iter()
.all(|x| self.transitions.contains(x))
}
}
impl<'q> Eq for StateTable<'q> {}
impl<'q> Index<State> for Automaton<'q> {
type Output = StateTable<'q>;
#[inline(always)]
fn index(&self, index: State) -> &Self::Output {
&self.states[index.0 as usize]
}
}
impl<'q> Automaton<'q> {
#[inline]
pub fn new(query: &'q JsonPathQuery) -> Result<Self, CompilerError> {
let nfa = NondeterministicAutomaton::new(query)?;
debug!("NFA: {}", nfa);
Automaton::minimize(nfa)
}
#[must_use]
#[inline(always)]
pub fn is_empty_query(&self) -> bool {
self.states.len() == 2
}
#[must_use]
#[inline(always)]
#[allow(clippy::unused_self)]
pub fn rejecting_state(&self) -> State {
State(0)
}
#[must_use]
#[inline(always)]
#[allow(clippy::unused_self)]
pub fn initial_state(&self) -> State {
State(1)
}
#[must_use]
#[inline(always)]
pub fn is_accepting(&self, state: State) -> bool {
self[state].attributes.is_accepting()
}
#[must_use]
#[inline(always)]
pub fn has_transition_to_accepting(&self, state: State) -> bool {
self[state].attributes.has_transition_to_accepting()
}
#[must_use]
#[inline(always)]
pub fn is_rejecting(&self, state: State) -> bool {
self[state].attributes.is_rejecting()
}
#[must_use]
#[inline(always)]
pub fn is_unitary(&self, state: State) -> bool {
self[state].attributes.is_unitary()
}
fn minimize(nfa: NondeterministicAutomaton<'q>) -> Result<Self, CompilerError> {
minimizer::minimize(nfa)
}
}
impl<'q> StateTable<'q> {
#[must_use]
#[inline(always)]
pub fn fallback_state(&self) -> State {
self.fallback_state
}
#[must_use]
#[inline(always)]
pub fn transitions(&self) -> &[Transition<'q>] {
&self.transitions
}
}
impl<'q> Display for Automaton<'q> {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "digraph {{")?;
for (i, state) in self.states.iter().enumerate() {
let mut color_one = "fillcolor=\"white;0.5";
let mut color_two = ":white\"";
let mut shape = "shape=circle";
if state.attributes.is_accepting() {
shape = "shape=doublecircle";
}
if state.attributes.is_unitary() {
color_one = "fillcolor=\"darkgoldenrod2;0.5";
}
if state.attributes.has_transition_to_accepting() {
color_two = ":dodgerblue\"";
}
if state.attributes.is_rejecting() {
color_one = "fillcolor=gray";
color_two = "";
}
let attrs = vec![
shape,
"style=filled",
"gradientangle=45",
color_one,
color_two,
]
.join(" ");
writeln!(f, "node [{attrs}]; {i}")?;
}
for (i, transitions) in self.states.iter().enumerate() {
for (label, state) in transitions.transitions.iter() {
writeln!(f, " {i} -> {} [label=\"{}\"]", state.0, label.display(),)?
}
writeln!(f, " {i} -> {} [label=\"*\"]", transitions.fallback_state.0)?;
}
write!(f, "}}")?;
Ok(())
}
}