use super::TransitionLabel;
use crate::query::{error::CompilerError, JsonPathQuery, JsonPathQueryNode, JsonPathQueryNodeType};
use std::{fmt::Display, ops::Index};
#[derive(Debug, PartialEq, Eq)]
pub(super) struct NondeterministicAutomaton<'q> {
pub(super) ordered_states: Vec<NfaState<'q>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum NfaState<'q> {
Direct(Transition<'q>),
Recursive(Transition<'q>),
Accepting,
}
use NfaState::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Transition<'q> {
Labelled(TransitionLabel<'q>),
Wildcard,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub(super) struct NfaStateId(pub(super) u8);
impl NfaStateId {
pub(super) fn next(&self) -> Result<Self, CompilerError> {
self.0
.checked_add(1)
.ok_or(CompilerError::QueryTooComplex(None))
.map(Self)
}
}
impl<'q> NondeterministicAutomaton<'q> {
pub(super) 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(name, _) => Some(Ok(Recursive(Transition::Labelled(name.into())))),
JsonPathQueryNode::Child(name, _) => Some(Ok(Direct(Transition::Labelled(name.into())))),
JsonPathQueryNode::AnyChild(_) => Some(Ok(Direct(Transition::Wildcard))),
JsonPathQueryNode::AnyDescendant(_) => Some(Ok(Recursive(Transition::Wildcard))),
JsonPathQueryNode::ArrayIndexChild(index, _) => Some(Ok(Direct(Transition::Labelled((*index).into())))),
JsonPathQueryNode::ArrayIndexDescendant(index, _) => {
Some(Ok(Recursive(Transition::Labelled((*index).into()))))
}
})
.collect();
let mut states = states_result?;
states.push(Accepting);
let accepting_state: Result<u8, _> = (states.len() - 1).try_into();
if let Err(err) = accepting_state {
Err(CompilerError::QueryTooComplex(Some(err)))
} else {
Ok(NondeterministicAutomaton { ordered_states: states })
}
}
pub(super) fn accepting_state(&self) -> NfaStateId {
NfaStateId((self.ordered_states.len() - 1) as u8)
}
}
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]
}
}
impl Display for NfaStateId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "NFA({})", self.0)
}
}
impl<'q> Display for NondeterministicAutomaton<'q> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let all_labels: Vec<_> = self
.ordered_states
.iter()
.filter_map(|s| match s {
Direct(Transition::Labelled(label)) | Recursive(Transition::Labelled(label)) => Some(*label),
_ => None,
})
.collect();
for (i, state) in self.ordered_states.iter().enumerate() {
match state {
Direct(Transition::Labelled(label)) => {
writeln!(f, "s{i}.{} -> s{};", label, i + 1)?;
}
Direct(Transition::Wildcard) => {
for label in &all_labels {
writeln!(f, "s{i}.{} -> s{};", label, i + 1)?;
}
writeln!(f, "s{i}.X -> s{};", i + 1)?;
}
Recursive(Transition::Labelled(label)) => {
writeln!(f, "s{i}.{} -> s{i}, s{};", label, i + 1)?;
for label in all_labels.iter().filter(|&l| l != label) {
writeln!(f, "s{i}.{} -> s{i};", label)?;
}
writeln!(f, "s{i}.X -> s{i};")?;
}
Recursive(Transition::Wildcard) => {
for label in &all_labels {
writeln!(f, "s{i}.{} -> s{i}, s{};", label, i + 1)?;
}
writeln!(f, "s{i}.X -> s{i}, s{};", i + 1)?;
}
Accepting => (),
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::query::{builder::JsonPathQueryBuilder, JsonString};
#[test]
fn nfa_test() {
let label_a = JsonString::new("a");
let label_b = JsonString::new("b");
let label_c = JsonString::new("c");
let label_d = JsonString::new("d");
let query = JsonPathQueryBuilder::new()
.child(label_a.clone())
.child(label_b.clone())
.descendant(label_c.clone())
.descendant(label_d.clone())
.any_child()
.any_child()
.any_descendant()
.any_descendant()
.build();
let expected_automaton = NondeterministicAutomaton {
ordered_states: vec![
NfaState::Direct(Transition::Labelled((&label_a).into())),
NfaState::Direct(Transition::Labelled((&label_b).into())),
NfaState::Recursive(Transition::Labelled((&label_c).into())),
NfaState::Recursive(Transition::Labelled((&label_d).into())),
NfaState::Direct(Transition::Wildcard),
NfaState::Direct(Transition::Wildcard),
NfaState::Recursive(Transition::Wildcard),
NfaState::Recursive(Transition::Wildcard),
NfaState::Accepting,
],
};
let automaton = NondeterministicAutomaton::new(&query).unwrap();
assert_eq!(expected_automaton, automaton);
}
}