use std::{fmt::Display, ops::Index};
use super::{error::CompilerError, JsonPathQuery, JsonPathQueryNode, JsonPathQueryNodeType, Label};
use crate::{debug, error::UnsupportedFeatureError};
use smallvec::SmallVec;
mod minimizer;
mod superstate;
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct NondeterministicAutomaton<'q> {
ordered_states: Vec<NfaState<'q>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum NfaState<'q> {
Direct(&'q Label),
Recursive(&'q Label),
Accepting,
}
use NfaState::*;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub(crate) struct NfaStateId(u8);
impl NfaStateId {
pub(crate) fn next(&self) -> Self {
Self(self.0 + 1)
}
}
impl Display for NfaStateId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "NFA({})", self.0)
}
}
impl From<u8> for NfaStateId {
fn from(i: u8) -> Self {
Self(i)
}
}
impl<'q> NondeterministicAutomaton<'q> {
fn new(query: &'q JsonPathQuery) -> Result<Self, CompilerError> {
debug_assert!(query.root().is_root());
let states_result: Result<Vec<NfaState>, CompilerError> = query
.root()
.iter()
.filter_map(|node| match node {
JsonPathQueryNode::Root(_) => None,
JsonPathQueryNode::Descendant(label, _) => Some(Ok(Recursive(label))),
JsonPathQueryNode::Child(label, _) => Some(Ok(Direct(label))),
JsonPathQueryNode::AnyChild(_) => Some(Err(
UnsupportedFeatureError::wildcard_child_selector().into(),
)),
})
.collect();
let mut states = states_result?;
states.push(Accepting);
Ok(NondeterministicAutomaton {
ordered_states: states,
})
}
}
impl<'q> Index<NfaStateId> for NondeterministicAutomaton<'q> {
type Output = NfaState<'q>;
fn index(&self, index: NfaStateId) -> &Self::Output {
&self.ordered_states[index.0 as usize]
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct State(u8);
impl Display for State {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "DFA({})", self.0)
}
}
impl From<u8> for State {
#[inline(always)]
fn from(i: u8) -> Self {
Self(i)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Automaton<'q> {
states: Vec<TransitionTable<'q>>,
}
#[derive(Debug)]
pub struct TransitionTable<'q> {
transitions: SmallVec<[(&'q Label, State); 2]>,
fallback_state: State,
}
impl<'q> PartialEq for TransitionTable<'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 TransitionTable<'q> {}
impl<'q> Index<State> for Automaton<'q> {
type Output = TransitionTable<'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);
Ok(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 accepting_state(&self) -> State {
State((self.states.len() - 1) as u8)
}
#[must_use]
#[inline(always)]
pub fn is_accepting(&self, state: State) -> bool {
state == self.accepting_state()
}
#[must_use]
#[inline(always)]
pub fn is_rejecting(&self, state: State) -> bool {
state == self.rejecting_state()
}
fn minimize(nfa: NondeterministicAutomaton<'q>) -> Self {
minimizer::minimize(nfa)
}
}
impl<'q> TransitionTable<'q> {
#[must_use]
#[inline(always)]
pub fn fallback_state(&self) -> State {
self.fallback_state
}
#[must_use]
#[inline(always)]
pub fn transitions(&self) -> &SmallVec<[(&'q Label, State); 2]> {
&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() {
for transition in state.transitions.iter() {
writeln!(
f,
" {i} -> {} [label=\"{}\"]",
transition.1 .0,
transition.0.display(),
)?;
}
writeln!(f, " {i} -> {} [label=\"*\"]", state.fallback_state.0)?;
}
write!(f, "}}")?;
Ok(())
}
}
impl<'q> Display for NondeterministicAutomaton<'q> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut dir = 1;
let mut rec = 1;
for state in &self.ordered_states {
match state {
Direct(label) => {
write!(f, "d{dir} --{}-> ", label.display())?;
dir += 1;
}
Recursive(label) => {
write!(f, "r{rec} --{}-> ", label.display())?;
rec += 1;
}
Accepting => {
write!(f, "acc")?;
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use smallvec::smallvec;
#[test]
fn child_and_descendant_test() {
let label_a = Label::new("a");
let label_b = Label::new("b");
let label_c = Label::new("c");
let label_d = Label::new("d");
let label_x = Label::new("x");
let nfa = NondeterministicAutomaton {
ordered_states: vec![
NfaState::Direct(&label_x),
NfaState::Recursive(&label_a),
NfaState::Direct(&label_b),
NfaState::Direct(&label_a),
NfaState::Direct(&label_b),
NfaState::Direct(&label_c),
NfaState::Recursive(&label_d),
NfaState::Accepting,
],
};
let result = Automaton::minimize(nfa);
let expected = Automaton {
states: vec![
TransitionTable {
transitions: smallvec![],
fallback_state: State(0),
},
TransitionTable {
transitions: smallvec![(&label_x, State(2))],
fallback_state: State(0),
},
TransitionTable {
transitions: smallvec![(&label_a, State(3))],
fallback_state: State(2),
},
TransitionTable {
transitions: smallvec![(&label_a, State(3)), (&label_b, State(4))],
fallback_state: State(2),
},
TransitionTable {
transitions: smallvec![(&label_a, State(5))],
fallback_state: State(2),
},
TransitionTable {
transitions: smallvec![(&label_a, State(3)), (&label_b, State(6))],
fallback_state: State(2),
},
TransitionTable {
transitions: smallvec![(&label_a, State(5)), (&label_c, State(7))],
fallback_state: State(2),
},
TransitionTable {
transitions: smallvec![(&label_d, State(8))],
fallback_state: State(7),
},
TransitionTable {
transitions: smallvec![(&label_d, State(8))],
fallback_state: State(7),
},
],
};
assert_eq!(result, expected);
}
}