diff --git a/.gitattributes b/.gitattributes index 62c91211..bb466b61 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ * eol=lf -/pdf/* -text \ No newline at end of file +/pdf/* -text +/crates/rsonpath-syntax/tests/error_snapshots.rs diff \ No newline at end of file diff --git a/crates/rsonpath-benchmarks b/crates/rsonpath-benchmarks index e841a606..93a0bf03 160000 --- a/crates/rsonpath-benchmarks +++ b/crates/rsonpath-benchmarks @@ -1 +1 @@ -Subproject commit e841a6066ba5ba38924819e17cae8f1f4742445a +Subproject commit 93a0bf03f22f272c8d58fb1ec63f67e8da6182ca diff --git a/crates/rsonpath-lib/src/automaton.rs b/crates/rsonpath-lib/src/automaton.rs index 9d03d5c9..3aeab68f 100644 --- a/crates/rsonpath-lib/src/automaton.rs +++ b/crates/rsonpath-lib/src/automaton.rs @@ -19,79 +19,10 @@ pub struct Automaton<'q> { states: Vec>, } -/// Represent the distinct methods of moving on a match between states. -#[derive(Debug, Copy, PartialEq, Clone, Eq)] -pub enum TransitionLabel<'q> { - /// Transition when a JSON member name matches a [`JsonString`]i. - ObjectMember(&'q JsonString), - /// Transition on the n-th element of an array, with n specified by a [`JsonUInt`]. - ArrayIndex(JsonUInt), -} - -impl<'q> TransitionLabel<'q> { - ///Return the textual [`JsonString`] being wrapped if so. Returns [`None`] otherwise. - #[must_use] - #[inline(always)] - pub fn get_member_name(&self) -> Option<&'q JsonString> { - match self { - TransitionLabel::ObjectMember(name) => Some(name), - TransitionLabel::ArrayIndex(_) => None, - } - } - - ///Return the [`JsonUInt`] being wrapped if so. Returns [`None`] otherwise. - #[must_use] - #[inline(always)] - pub fn get_array_index(&'q self) -> Option<&'q JsonUInt> { - match self { - TransitionLabel::ArrayIndex(name) => Some(name), - TransitionLabel::ObjectMember(_) => None, - } - } - - /// Wraps a [`JsonString`] in a [`TransitionLabel`]. - #[must_use] - #[inline(always)] - pub fn new_object_member(member_name: &'q JsonString) -> Self { - TransitionLabel::ObjectMember(member_name) - } - - /// Wraps a [`JsonUInt`] in a [`TransitionLabel`]. - #[must_use] - #[inline(always)] - pub fn new_array_index(index: JsonUInt) -> Self { - TransitionLabel::ArrayIndex(index) - } -} - -impl<'q> From<&'q JsonString> for TransitionLabel<'q> { - #[must_use] - #[inline(always)] - fn from(member_name: &'q JsonString) -> Self { - TransitionLabel::new_object_member(member_name) - } -} - -impl Display for TransitionLabel<'_> { - #[inline(always)] - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TransitionLabel::ObjectMember(name) => write!(f, "{}", name.unquoted()), - TransitionLabel::ArrayIndex(index) => write!(f, "{}", index.as_u64()), - } - } -} - -impl From for TransitionLabel<'_> { - #[must_use] - #[inline(always)] - fn from(index: JsonUInt) -> Self { - TransitionLabel::new_array_index(index) - } -} - -/// A single transition of an [`Automaton`]. -type Transition<'q> = (TransitionLabel<'q>, State); +/// Transition when a JSON member name matches a [`JsonString`]i. +pub type MemberTransition<'q> = (&'q JsonString, State); +/// Transition on the n-th element of an array, with n specified by a [`JsonUInt`]. +pub type ArrayTransition<'q> = (JsonUInt, State); /// A transition table of a single [`State`] of an [`Automaton`]. /// @@ -100,7 +31,8 @@ type Transition<'q> = (TransitionLabel<'q>, State); #[derive(Debug)] pub struct StateTable<'q> { attributes: StateAttributes, - transitions: SmallVec<[Transition<'q>; 2]>, + member_transitions: SmallVec<[MemberTransition<'q>; 2]>, + array_transitions: SmallVec<[ArrayTransition<'q>; 2]>, fallback_state: State, } @@ -109,7 +41,8 @@ impl<'q> Default for StateTable<'q> { fn default() -> Self { Self { attributes: StateAttributes::default(), - transitions: Default::default(), + member_transitions: SmallVec::default(), + array_transitions: SmallVec::default(), fallback_state: State(0), } } @@ -118,11 +51,17 @@ impl<'q> Default for StateTable<'q> { 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)) - && self.attributes == other.attributes + return self.fallback_state == other.fallback_state + && set_eq(&self.array_transitions, &other.array_transitions) + && set_eq(&self.member_transitions, &other.member_transitions) + && self.attributes == other.attributes; + + #[inline(always)] + fn set_eq>(left: &SmallVec, right: &SmallVec) -> bool { + left.len() == right.len() + && left.iter().all(|x| right.contains(x)) + && right.iter().all(|x| left.contains(x)) + } } } @@ -209,7 +148,7 @@ impl<'q> Automaton<'q> { /// # use rsonpath::automaton::*; /// let query = rsonpath_syntax::parse("$.a").unwrap(); /// let automaton = Automaton::new(&query).unwrap(); - /// let state_2 = automaton[automaton.initial_state()].transitions()[0].1; + /// let state_2 = automaton[automaton.initial_state()].member_transitions()[0].1; /// /// assert!(automaton.is_accepting(state_2)); /// ``` @@ -233,30 +172,7 @@ impl<'q> Automaton<'q> { #[must_use] #[inline(always)] pub fn has_any_array_item_transition(&self, state: State) -> bool { - self[state] - .transitions() - .iter() - .any(|t| matches!(t, (TransitionLabel::ArrayIndex(_), _))) - } - - /// Returns whether the given state is accepting an item in a list. - /// - /// # Example - /// ```rust - /// # use rsonpath::automaton::*; - /// let query = rsonpath_syntax::parse("$[2]").unwrap(); - /// let automaton = Automaton::new(&query).unwrap(); - /// let state = automaton.initial_state(); - /// - /// assert!(automaton.has_any_array_item_transition_to_accepting(state)); - /// ``` - #[must_use] - #[inline(always)] - pub fn has_any_array_item_transition_to_accepting(&self, state: State) -> bool { - self[state].transitions().iter().any(|t| match t { - (TransitionLabel::ArrayIndex(_), s) => self.is_accepting(*s), - _ => false, - }) + self[state].attributes.has_array_index_transition() } /// Returns whether the given state is accepting the first item in a list. @@ -302,10 +218,12 @@ impl<'q> Automaton<'q> { #[must_use] #[inline(always)] pub fn has_array_index_transition_to_accepting(&self, state: State, match_index: &JsonUInt) -> bool { - self[state].transitions().iter().any(|t| match t { - (TransitionLabel::ArrayIndex(i), s) => i.eq(match_index) && self.is_accepting(*s), - _ => false, - }) + let state = &self[state]; + state.attributes.has_array_index_transition_to_accepting() + && state + .array_transitions() + .iter() + .any(|(i, s)| i.eq(match_index) && self.is_accepting(*s)) } /// Returns whether the given state has any transitions @@ -379,14 +297,24 @@ impl<'q> StateTable<'q> { self.fallback_state } - /// Returns the collection of labelled transitions from this state. + /// Returns the collection of labelled array transitions from this state. /// - /// A transition is triggered if the [`TransitionLabel`] is matched and leads + /// A transition is triggered if the [`ArrayTransition`] is matched and leads /// to the contained [`State`]. #[must_use] #[inline(always)] - pub fn transitions(&self) -> &[Transition<'q>] { - &self.transitions + pub fn array_transitions(&self) -> &[ArrayTransition<'q>] { + &self.array_transitions + } + + /// Returns the collection of labelled member transitions from this state. + /// + /// A transition is triggered if the [`MemberTransition`] is matched and leads + /// to the contained [`State`]. + #[must_use] + #[inline(always)] + pub fn member_transitions(&self) -> &[MemberTransition<'q>] { + &self.member_transitions } } @@ -420,8 +348,11 @@ impl<'q> Display for Automaton<'q> { } for (i, transitions) in self.states.iter().enumerate() { - for (label, state) in &transitions.transitions { - writeln!(f, " {i} -> {} [label=\"{}\"]", state.0, label,)? + for (label, state) in &transitions.array_transitions { + writeln!(f, " {i} -> {} [label=\"[{}]\"]", state.0, label.as_u64())? + } + for (label, state) in &transitions.member_transitions { + writeln!(f, " {i} -> {} [label=\"{}\"]", state.0, label.unquoted())? } writeln!(f, " {i} -> {} [label=\"*\"]", transitions.fallback_state.0)?; } diff --git a/crates/rsonpath-lib/src/automaton/minimizer.rs b/crates/rsonpath-lib/src/automaton/minimizer.rs index 5903e60f..d4e83d65 100644 --- a/crates/rsonpath-lib/src/automaton/minimizer.rs +++ b/crates/rsonpath-lib/src/automaton/minimizer.rs @@ -7,9 +7,10 @@ use super::{ nfa::{self, NfaState, NfaStateId}, small_set::{SmallSet, SmallSet256}, state::StateAttributesBuilder, - {Automaton, NondeterministicAutomaton, State as DfaStateId, StateAttributes, StateTable, TransitionLabel}, + {Automaton, NondeterministicAutomaton, State as DfaStateId, StateAttributes, StateTable}, }; use crate::debug; +use rsonpath_syntax::{num::JsonUInt, str::JsonString}; use smallvec::{smallvec, SmallVec}; use vector_map::VecMap; @@ -46,7 +47,7 @@ pub(super) struct Minimizer<'q> { #[derive(Debug)] struct SuperstateTransitionTable<'q> { - labelled: VecMap, SmallSet256>, + labelled: VecMap, SmallSet256>, wildcard: SmallSet256, } @@ -80,7 +81,8 @@ impl<'q> Minimizer<'q> { fn run(mut self) -> Result, CompilerError> { // Rejecting state has no outgoing transitions except for a self-loop. self.dfa_states.push(StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: Self::rejecting_state(), attributes: StateAttributesBuilder::new().rejecting().into(), }); @@ -138,20 +140,27 @@ impl<'q> Minimizer<'q> { debug!("Normalized transitions: {:?}", transitions); // Translate the transitions to the data model expected by TransitionTable. - let translated_transitions: SmallVec<_> = transitions - .labelled - .into_iter() - .map(|(label, state)| (label, self.superstates[&state])) - .collect(); - debug!("Translated transitions: {translated_transitions:?}"); + let mut array_transitions = smallvec![]; + let mut member_transitions = smallvec![]; + + for (label, state) in transitions.labelled { + let state = self.superstates[&state]; + match label { + nfa::TransitionLabel::ArrayIndex(i) => array_transitions.push((i, state)), + nfa::TransitionLabel::ObjectMember(s) => member_transitions.push((s, state)), + } + } + debug!("Translated transitions (array): {array_transitions:?}"); + debug!("Translated transitions (member): {member_transitions:?}"); // If a checkpoint was reached, its singleton superstate is this DFA state's fallback state. // Otherwise, we set the fallback to the rejecting state. let id = self.superstates[¤t_superstate]; let fallback_state = self.superstates[&transitions.wildcard]; - let attributes = self.build_attributes(id, &translated_transitions, fallback_state); + let attributes = self.build_attributes(id, &array_transitions, &member_transitions, fallback_state); let table = &mut self.dfa_states[id.0 as usize]; - table.transitions = translated_transitions; + table.array_transitions = array_transitions; + table.member_transitions = member_transitions; table.fallback_state = fallback_state; table.attributes = attributes; @@ -163,7 +172,8 @@ impl<'q> Minimizer<'q> { fn build_attributes( &self, id: DfaStateId, - transitions: &[(TransitionLabel, DfaStateId)], + array_transitions: &[(JsonUInt, DfaStateId)], + member_transitions: &[(&JsonString, DfaStateId)], fallback: DfaStateId, ) -> StateAttributes { let mut attrs = StateAttributesBuilder::new(); @@ -176,14 +186,25 @@ impl<'q> Minimizer<'q> { debug!("{id} is rejecting"); attrs = attrs.rejecting(); } - if transitions.len() == 1 && fallback == Self::rejecting_state() { + if array_transitions.len() + member_transitions.len() == 1 && fallback == Self::rejecting_state() { debug!("{id} is unitary"); attrs = attrs.unitary(); } - if self.accepting.contains(fallback.0) || transitions.iter().any(|(_, s)| self.accepting.contains(s.0)) { + if self.accepting.contains(fallback.0) + || array_transitions.iter().any(|(_, s)| self.accepting.contains(s.0)) + || member_transitions.iter().any(|(_, s)| self.accepting.contains(s.0)) + { debug!("{id} has transitions to accepting"); attrs = attrs.transitions_to_accepting(); } + if !array_transitions.is_empty() { + debug!("{id} has an array index transition"); + attrs = attrs.has_array_index_transition(); + } + if array_transitions.iter().any(|(_, s)| self.accepting.contains(s.0)) { + debug!("{id} has an accepting array index transition"); + attrs = attrs.has_array_index_transition_to_accepting(); + } attrs.into() } @@ -335,12 +356,14 @@ mod tests { let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::ACCEPTING, }, @@ -361,17 +384,20 @@ mod tests { let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(2), attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::ACCEPTING, }, @@ -384,27 +410,36 @@ mod tests { #[test] fn simple_nonnegative_indexed() { // Query = $[0] - let label = TransitionLabel::ArrayIndex(JsonUInt::ZERO); + let label = JsonUInt::ZERO; let nfa = NondeterministicAutomaton { - ordered_states: vec![NfaState::Direct(nfa::Transition::Labelled(label)), NfaState::Accepting], + ordered_states: vec![ + NfaState::Direct(nfa::Transition::Labelled(label.into())), + NfaState::Accepting, + ], }; let result = minimize(nfa).unwrap(); let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![(label, State(2))], + array_transitions: smallvec![(label, State(2))], + member_transitions: smallvec![], fallback_state: State(0), - attributes: StateAttributes::UNITARY | StateAttributes::TRANSITIONS_TO_ACCEPTING, + attributes: StateAttributes::UNITARY + | StateAttributes::TRANSITIONS_TO_ACCEPTING + | StateAttributes::HAS_ARRAY_INDEX_TRANSITION + | StateAttributes::HAS_ARRAY_INDEX_TRANSITION_TO_ACCEPTING, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::ACCEPTING, }, @@ -425,17 +460,20 @@ mod tests { let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(2), attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(2), attributes: StateAttributes::ACCEPTING | StateAttributes::TRANSITIONS_TO_ACCEPTING, }, @@ -449,18 +487,15 @@ mod tests { fn interstitial_descendant_wildcard() { // Query = $..a.b..*.a..b let label_a = JsonString::new("a"); - let label_a = (&label_a).into(); - let label_b = JsonString::new("b"); - let label_b = (&label_b).into(); let nfa = NondeterministicAutomaton { ordered_states: vec![ - NfaState::Recursive(nfa::Transition::Labelled(label_a)), - NfaState::Direct(nfa::Transition::Labelled(label_b)), + NfaState::Recursive(nfa::Transition::Labelled((&label_a).into())), + NfaState::Direct(nfa::Transition::Labelled((&label_b).into())), NfaState::Recursive(nfa::Transition::Wildcard), - NfaState::Direct(nfa::Transition::Labelled(label_a)), - NfaState::Recursive(nfa::Transition::Labelled(label_b)), + NfaState::Direct(nfa::Transition::Labelled((&label_a).into())), + NfaState::Recursive(nfa::Transition::Labelled((&label_b).into())), NfaState::Accepting, ], }; @@ -469,37 +504,44 @@ mod tests { let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![(label_a, State(2))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(2))], fallback_state: State(1), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(2)), (label_b, State(3))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(2)), (&label_b, State(3))], fallback_state: State(1), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(4), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(5))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(5))], fallback_state: State(4), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_b, State(6))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_b, State(6))], fallback_state: State(5), attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![(label_b, State(6))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_b, State(6))], fallback_state: State(5), attributes: StateAttributes::ACCEPTING | StateAttributes::TRANSITIONS_TO_ACCEPTING, }, @@ -513,18 +555,16 @@ mod tests { fn interstitial_nondescendant_wildcard() { // Query = $..a.b.*.a..b let label_a = JsonString::new("a"); - let label_a = (&label_a).into(); let label_b = JsonString::new("b"); - let label_b = (&label_b).into(); let nfa = NondeterministicAutomaton { ordered_states: vec![ - NfaState::Recursive(nfa::Transition::Labelled(label_a)), - NfaState::Direct(nfa::Transition::Labelled(label_b)), + NfaState::Recursive(nfa::Transition::Labelled((&label_a).into())), + NfaState::Direct(nfa::Transition::Labelled((&label_b).into())), NfaState::Direct(nfa::Transition::Wildcard), - NfaState::Direct(nfa::Transition::Labelled(label_a)), - NfaState::Recursive(nfa::Transition::Labelled(label_b)), + NfaState::Direct(nfa::Transition::Labelled((&label_a).into())), + NfaState::Recursive(nfa::Transition::Labelled((&label_b).into())), NfaState::Accepting, ], }; @@ -533,42 +573,50 @@ mod tests { let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![(label_a, State(2))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(2))], fallback_state: State(1), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(2)), (label_b, State(3))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(2)), (&label_b, State(3))], fallback_state: State(1), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(5))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(5))], fallback_state: State(4), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(6))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(6))], fallback_state: State(1), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(6)), (label_b, State(3))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(6)), (&label_b, State(3))], fallback_state: State(1), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_b, State(7))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_b, State(7))], fallback_state: State(6), attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![(label_b, State(7))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_b, State(7))], fallback_state: State(6), attributes: StateAttributes::ACCEPTING | StateAttributes::TRANSITIONS_TO_ACCEPTING, }, @@ -582,11 +630,10 @@ mod tests { fn simple_multi_accepting() { // Query = $..a.* let label = JsonString::new("a"); - let label = (&label).into(); let nfa = NondeterministicAutomaton { ordered_states: vec![ - NfaState::Recursive(nfa::Transition::Labelled(label)), + NfaState::Recursive(nfa::Transition::Labelled((&label).into())), NfaState::Direct(nfa::Transition::Wildcard), NfaState::Accepting, ], @@ -596,27 +643,32 @@ mod tests { let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![(label, State(2)),], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(2)),], fallback_state: State(1), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label, State(4))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(4))], fallback_state: State(3), attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![(label, State(2))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(2))], fallback_state: State(1), attributes: StateAttributes::ACCEPTING, }, StateTable { - transitions: smallvec![(label, State(4))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(4))], fallback_state: State(3), attributes: StateAttributes::ACCEPTING | StateAttributes::TRANSITIONS_TO_ACCEPTING, }, @@ -629,11 +681,11 @@ mod tests { #[test] fn simple_multi_accepting_nneg_index() { // Query = $..[3] - let label = TransitionLabel::ArrayIndex(JsonUInt::ZERO); + let label = JsonUInt::ZERO; let nfa = NondeterministicAutomaton { ordered_states: vec![ - NfaState::Recursive(nfa::Transition::Labelled(label)), + NfaState::Recursive(nfa::Transition::Labelled(label.into())), NfaState::Accepting, ], }; @@ -642,19 +694,27 @@ mod tests { let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![(label, State(2)),], + array_transitions: smallvec![(label, State(2)),], + member_transitions: smallvec![], fallback_state: State(1), - attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING, + attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING + | StateAttributes::HAS_ARRAY_INDEX_TRANSITION + | StateAttributes::HAS_ARRAY_INDEX_TRANSITION_TO_ACCEPTING, }, StateTable { - transitions: smallvec![(label, State(2))], + array_transitions: smallvec![(label, State(2))], + member_transitions: smallvec![], fallback_state: State(1), - attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING | StateAttributes::ACCEPTING, + attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING + | StateAttributes::HAS_ARRAY_INDEX_TRANSITION + | StateAttributes::HAS_ARRAY_INDEX_TRANSITION_TO_ACCEPTING + | StateAttributes::ACCEPTING, }, ], }; @@ -666,11 +726,10 @@ mod tests { fn chained_wildcard_children() { // Query = $.a.*.*.* let label = JsonString::new("a"); - let label = (&label).into(); let nfa = NondeterministicAutomaton { ordered_states: vec![ - NfaState::Direct(nfa::Transition::Labelled(label)), + NfaState::Direct(nfa::Transition::Labelled((&label).into())), NfaState::Direct(nfa::Transition::Wildcard), NfaState::Direct(nfa::Transition::Wildcard), NfaState::Direct(nfa::Transition::Wildcard), @@ -682,32 +741,38 @@ mod tests { let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![(label, State(2))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(2))], fallback_state: State(0), attributes: StateAttributes::UNITARY, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(3), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(4), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(5), attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::ACCEPTING, }, @@ -721,11 +786,10 @@ mod tests { fn chained_wildcard_children_after_descendant() { // Query = $..a.*.* let label = JsonString::new("a"); - let label = (&label).into(); let nfa = NondeterministicAutomaton { ordered_states: vec![ - NfaState::Recursive(nfa::Transition::Labelled(label)), + NfaState::Recursive(nfa::Transition::Labelled((&label).into())), NfaState::Direct(nfa::Transition::Wildcard), NfaState::Direct(nfa::Transition::Wildcard), NfaState::Accepting, @@ -736,47 +800,56 @@ mod tests { let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![(label, State(2))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(2))], fallback_state: State(1), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label, State(4))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(4))], fallback_state: State(3), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label, State(8))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(8))], fallback_state: State(7), attributes: StateAttributes::EMPTY | StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![(label, State(6))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(6))], fallback_state: State(5), attributes: StateAttributes::EMPTY | StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![(label, State(8))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(8))], fallback_state: State(7), attributes: StateAttributes::ACCEPTING | StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![(label, State(6))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(6))], fallback_state: State(5), attributes: StateAttributes::ACCEPTING | StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![(label, State(2))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(2))], fallback_state: State(1), attributes: StateAttributes::ACCEPTING, }, StateTable { - transitions: smallvec![(label, State(4))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label, State(4))], fallback_state: State(3), attributes: StateAttributes::ACCEPTING, }, @@ -790,29 +863,20 @@ mod tests { fn child_and_descendant() { // Query = $.x..a.b.a.b.c..d let label_a = JsonString::new("a"); - let label_a = (&label_a).into(); - let label_b = JsonString::new("b"); - let label_b = (&label_b).into(); - let label_c = JsonString::new("c"); - let label_c = (&label_c).into(); - let label_d = JsonString::new("d"); - let label_d = (&label_d).into(); - let label_x = JsonString::new("x"); - let label_x = (&label_x).into(); let nfa = NondeterministicAutomaton { ordered_states: vec![ - NfaState::Direct(nfa::Transition::Labelled(label_x)), - NfaState::Recursive(nfa::Transition::Labelled(label_a)), - NfaState::Direct(nfa::Transition::Labelled(label_b)), - NfaState::Direct(nfa::Transition::Labelled(label_a)), - NfaState::Direct(nfa::Transition::Labelled(label_b)), - NfaState::Direct(nfa::Transition::Labelled(label_c)), - NfaState::Recursive(nfa::Transition::Labelled(label_d)), + NfaState::Direct(nfa::Transition::Labelled((&label_x).into())), + NfaState::Recursive(nfa::Transition::Labelled((&label_a).into())), + NfaState::Direct(nfa::Transition::Labelled((&label_b).into())), + NfaState::Direct(nfa::Transition::Labelled((&label_a).into())), + NfaState::Direct(nfa::Transition::Labelled((&label_b).into())), + NfaState::Direct(nfa::Transition::Labelled((&label_c).into())), + NfaState::Recursive(nfa::Transition::Labelled((&label_d).into())), NfaState::Accepting, ], }; @@ -821,47 +885,56 @@ mod tests { let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![(label_x, State(2))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_x, State(2))], fallback_state: State(0), attributes: StateAttributes::UNITARY, }, StateTable { - transitions: smallvec![(label_a, State(3))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(3))], fallback_state: State(2), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(3)), (label_b, State(4))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(3)), (&label_b, State(4))], fallback_state: State(2), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(5))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(5))], fallback_state: State(2), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(3)), (label_b, State(6))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(3)), (&label_b, State(6))], fallback_state: State(2), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(5)), (label_c, State(7))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(5)), (&label_c, State(7))], fallback_state: State(2), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_d, State(8))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_d, State(8))], fallback_state: State(7), attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![(label_d, State(8))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_d, State(8))], fallback_state: State(7), attributes: StateAttributes::ACCEPTING | StateAttributes::TRANSITIONS_TO_ACCEPTING, }, @@ -875,21 +948,16 @@ mod tests { fn child_descendant_and_child_wildcard() { // Query = $.x.*..a.*.b let label_a = JsonString::new("a"); - let label_a = (&label_a).into(); - let label_b = JsonString::new("b"); - let label_b = (&label_b).into(); - let label_x = JsonString::new("x"); - let label_x = (&label_x).into(); let nfa = NondeterministicAutomaton { ordered_states: vec![ - NfaState::Direct(nfa::Transition::Labelled(label_x)), + NfaState::Direct(nfa::Transition::Labelled((&label_x).into())), NfaState::Direct(nfa::Transition::Wildcard), - NfaState::Recursive(nfa::Transition::Labelled(label_a)), + NfaState::Recursive(nfa::Transition::Labelled((&label_a).into())), NfaState::Direct(nfa::Transition::Wildcard), - NfaState::Direct(nfa::Transition::Labelled(label_b)), + NfaState::Direct(nfa::Transition::Labelled((&label_b).into())), NfaState::Accepting, ], }; @@ -898,47 +966,56 @@ mod tests { let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![(label_x, State(2))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_x, State(2))], fallback_state: State(0), attributes: StateAttributes::UNITARY, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(3), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(4))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(4))], fallback_state: State(3), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(6))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(6))], fallback_state: State(5), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_a, State(4)), (label_b, State(8))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(4)), (&label_b, State(8))], fallback_state: State(3), attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![(label_a, State(6)), (label_b, State(7))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(6)), (&label_b, State(7))], fallback_state: State(5), attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![(label_a, State(4)), (label_b, State(8))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(4)), (&label_b, State(8))], fallback_state: State(3), attributes: StateAttributes::ACCEPTING | StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![(label_a, State(4))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(4))], fallback_state: State(3), attributes: StateAttributes::ACCEPTING, }, @@ -952,20 +1029,16 @@ mod tests { fn all_name_and_wildcard_selectors() { // Query = $.a.b..c..d.*..* let label_a = JsonString::new("a"); - let label_a = (&label_a).into(); let label_b = JsonString::new("b"); - let label_b = (&label_b).into(); let label_c = JsonString::new("c"); - let label_c = (&label_c).into(); let label_d = JsonString::new("d"); - let label_d = (&label_d).into(); let nfa = NondeterministicAutomaton { ordered_states: vec![ - NfaState::Direct(nfa::Transition::Labelled(label_a)), - NfaState::Direct(nfa::Transition::Labelled(label_b)), - NfaState::Recursive(nfa::Transition::Labelled(label_c)), - NfaState::Recursive(nfa::Transition::Labelled(label_d)), + NfaState::Direct(nfa::Transition::Labelled((&label_a).into())), + NfaState::Direct(nfa::Transition::Labelled((&label_b).into())), + NfaState::Recursive(nfa::Transition::Labelled((&label_c).into())), + NfaState::Recursive(nfa::Transition::Labelled((&label_d).into())), NfaState::Direct(nfa::Transition::Wildcard), NfaState::Recursive(nfa::Transition::Wildcard), NfaState::Accepting, @@ -974,42 +1047,50 @@ mod tests { let expected = Automaton { states: vec![ StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(0), attributes: StateAttributes::REJECTING, }, StateTable { - transitions: smallvec![(label_a, State(2)),], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_a, State(2)),], fallback_state: State(0), attributes: StateAttributes::UNITARY, }, StateTable { - transitions: smallvec![(label_b, State(3))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_b, State(3))], fallback_state: State(0), attributes: StateAttributes::UNITARY, }, StateTable { - transitions: smallvec![(label_c, State(4))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_c, State(4))], fallback_state: State(3), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_d, State(5))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_d, State(5))], fallback_state: State(4), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![(label_d, State(6))], + array_transitions: smallvec![], + member_transitions: smallvec![(&label_d, State(6))], fallback_state: State(6), attributes: StateAttributes::EMPTY, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(7), attributes: StateAttributes::TRANSITIONS_TO_ACCEPTING, }, StateTable { - transitions: smallvec![], + array_transitions: smallvec![], + member_transitions: smallvec![], fallback_state: State(7), attributes: StateAttributes::ACCEPTING | StateAttributes::TRANSITIONS_TO_ACCEPTING, }, diff --git a/crates/rsonpath-lib/src/automaton/nfa.rs b/crates/rsonpath-lib/src/automaton/nfa.rs index e974fa3b..39217869 100644 --- a/crates/rsonpath-lib/src/automaton/nfa.rs +++ b/crates/rsonpath-lib/src/automaton/nfa.rs @@ -3,8 +3,8 @@ //! a DFA with the minimizer. use crate::error::UnsupportedFeatureError; -use super::{error::CompilerError, TransitionLabel}; -use rsonpath_syntax::JsonPathQuery; +use super::error::CompilerError; +use rsonpath_syntax::{num::JsonUInt, str::JsonString, JsonPathQuery}; use std::{fmt::Display, ops::Index}; /// An NFA representing a query. It is always a directed path @@ -31,12 +31,63 @@ use NfaState::*; /// A transition in the NFA mapped from a [`JsonPathQuery`] selector. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub(super) enum Transition<'q> { - /// A transition matching a specific label. + /// A transition matching a specific member or array index. Labelled(TransitionLabel<'q>), /// A transition matching anything. Wildcard, } +/// Represent the distinct methods of moving on a match between states. +#[derive(Debug, Copy, PartialEq, Clone, Eq)] +pub(super) enum TransitionLabel<'q> { + /// Transition when a JSON member name matches a [`JsonString`]. + ObjectMember(&'q JsonString), + /// Transition on the n-th element of an array, with n specified by a [`JsonUInt`]. + ArrayIndex(JsonUInt), +} + +impl<'q> TransitionLabel<'q> { + /// Wraps a [`JsonString`] in a [`TransitionLabel`]. + #[must_use] + #[inline(always)] + pub(super) fn new_object_member(member_name: &'q JsonString) -> Self { + TransitionLabel::ObjectMember(member_name) + } + + /// Wraps a [`JsonUInt`] in a [`TransitionLabel`]. + #[must_use] + #[inline(always)] + pub(super) fn new_array_index(index: JsonUInt) -> Self { + TransitionLabel::ArrayIndex(index) + } +} + +impl<'q> From<&'q JsonString> for TransitionLabel<'q> { + #[must_use] + #[inline(always)] + fn from(member_name: &'q JsonString) -> Self { + TransitionLabel::new_object_member(member_name) + } +} + +impl Display for TransitionLabel<'_> { + #[inline(always)] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TransitionLabel::ObjectMember(name) => write!(f, "{}", name.unquoted()), + TransitionLabel::ArrayIndex(index) => write!(f, "{}", index.as_u64()), + } + } +} + +impl From for TransitionLabel<'_> { + #[must_use] + #[inline(always)] + fn from(index: JsonUInt) -> Self { + TransitionLabel::new_array_index(index) + } +} + /// State of an [`NondeterministicAutomaton`]. Thin wrapper over a state's /// identifier to distinguish NFA states from DFA states ([`State`](`super::state::State`)). #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] @@ -171,7 +222,6 @@ impl<'q> Display for NondeterministicAutomaton<'q> { mod tests { use super::*; use rsonpath_syntax::builder::JsonPathQueryBuilder; - use rsonpath_syntax::str::JsonString; #[test] fn nfa_test() { diff --git a/crates/rsonpath-lib/src/automaton/state.rs b/crates/rsonpath-lib/src/automaton/state.rs index 682caa45..5a85a807 100644 --- a/crates/rsonpath-lib/src/automaton/state.rs +++ b/crates/rsonpath-lib/src/automaton/state.rs @@ -18,6 +18,11 @@ pub(crate) enum StateAttribute { /// Marks that the [`State`] contains some transition /// (labelled or fallback) to an [`Accepting`](`StateAttribute::Accepting`) state. TransitionsToAccepting = 0x08, + /// Marks that the [`State`] contains some transition labelled with an array index. + HasArrayIndexTransition = 0x10, + /// Marks that the [`State`] contains an array-index labelled transition + /// to an to an [`Accepting`](`StateAttribute::Accepting`) state. + HasArrayIndexTransitionToAccepting = 0x20, } pub(crate) struct StateAttributesBuilder { @@ -51,6 +56,14 @@ impl StateAttributesBuilder { self.set(StateAttribute::TransitionsToAccepting) } + pub(crate) fn has_array_index_transition(self) -> Self { + self.set(StateAttribute::HasArrayIndexTransition) + } + + pub(crate) fn has_array_index_transition_to_accepting(self) -> Self { + self.set(StateAttribute::HasArrayIndexTransitionToAccepting) + } + pub(crate) fn build(self) -> StateAttributes { self.attrs } @@ -93,6 +106,12 @@ impl StateAttributes { /// A state is _unitary_ if it contains exactly one labelled transition /// and its fallback transition is [`Rejecting`](`StateAttributes::is_rejecting`). pub const UNITARY: Self = Self(StateAttribute::Unitary as u8); + /// Marks that the [`State`] contains some transition labelled with an array index. + pub const HAS_ARRAY_INDEX_TRANSITION: Self = Self(StateAttribute::HasArrayIndexTransition as u8); + /// Marks that the [`State`] contains an array-index labelled transition + /// to an to an [`Accepting`](`StateAttributes::is_accepting`) state. + pub const HAS_ARRAY_INDEX_TRANSITION_TO_ACCEPTING: Self = + Self(StateAttribute::HasArrayIndexTransitionToAccepting as u8); /// Check if the the state is accepting. #[inline(always)] @@ -126,6 +145,21 @@ impl StateAttributes { self.is_set(StateAttribute::Unitary) } + /// Marks that the [`State`] contains some transition labelled with an array index. + #[inline(always)] + #[must_use] + pub fn has_array_index_transition(&self) -> bool { + self.is_set(StateAttribute::HasArrayIndexTransition) + } + + /// Marks that the [`State`] contains an array-index labelled transition + /// to an to an [`Accepting`](`StateAttributes::is_accepting`) state. + #[inline(always)] + #[must_use] + pub fn has_array_index_transition_to_accepting(&self) -> bool { + self.is_set(StateAttribute::HasArrayIndexTransitionToAccepting) + } + #[inline(always)] #[must_use] fn is_set(&self, attr: StateAttribute) -> bool { diff --git a/crates/rsonpath-lib/src/engine/head_skipping.rs b/crates/rsonpath-lib/src/engine/head_skipping.rs index c0187635..5bfacc0e 100644 --- a/crates/rsonpath-lib/src/engine/head_skipping.rs +++ b/crates/rsonpath-lib/src/engine/head_skipping.rs @@ -95,22 +95,21 @@ impl<'b, 'q, I: Input, V: Simd> HeadSkip<'b, 'q, I, V, BLOCK_SIZE> { pub(super) fn new(bytes: &'b I, automaton: &'b Automaton<'q>, simd: V) -> Option { let initial_state = automaton.initial_state(); let fallback_state = automaton[initial_state].fallback_state(); - let transitions = automaton[initial_state].transitions(); + let transitions = automaton[initial_state].member_transitions(); - if fallback_state == initial_state && transitions.len() == 1 { - let (label, target_state) = transitions[0]; - - if let Some(member_name) = label.get_member_name() { - debug!("Automaton starts with a descendant search, using memmem heuristic."); - - return Some(Self { - bytes, - state: target_state, - is_accepting: automaton.is_accepting(target_state), - member_name, - simd, - }); - } + if fallback_state == initial_state + && transitions.len() == 1 + && automaton[initial_state].array_transitions().is_empty() + { + let (member_name, target_state) = transitions[0]; + debug!("Automaton starts with a descendant search, using memmem heuristic."); + return Some(Self { + bytes, + state: target_state, + is_accepting: automaton.is_accepting(target_state), + member_name, + simd, + }); } None diff --git a/crates/rsonpath-lib/src/engine/main.rs b/crates/rsonpath-lib/src/engine/main.rs index 27a41077..ddcd3559 100644 --- a/crates/rsonpath-lib/src/engine/main.rs +++ b/crates/rsonpath-lib/src/engine/main.rs @@ -8,7 +8,7 @@ use crate::{ automaton::{ error::CompilerError, - {Automaton, State, TransitionLabel}, + {Automaton, State}, }, classification::{ simd::{self, config_simd, dispatch_simd, Simd, SimdConfiguration}, @@ -163,8 +163,6 @@ struct Executor<'i, 'q, 'r, I, R, V> { next_event: Option, is_list: bool, array_count: JsonUInt, - has_any_array_item_transition: bool, - has_any_array_item_transition_to_accepting: bool, } fn query_executor<'i, 'q, 'r, I, R, V: Simd>( @@ -188,8 +186,6 @@ where next_event: None, is_list: false, array_count: JsonUInt::ZERO, - has_any_array_item_transition: false, - has_any_array_item_transition_to_accepting: false, } } @@ -299,19 +295,14 @@ where if !is_next_opening { let mut any_matched = false; - for &(label, target) in self.automaton[self.state].transitions() { - match label { - TransitionLabel::ArrayIndex(_) => {} - TransitionLabel::ObjectMember(member_name) => { - if self.automaton.is_accepting(target) && self.is_match(idx, member_name)? { - self.record_match_detected_at( - idx + 1, - NodeTypeHint::Atomic, /* since is_next_opening is false */ - )?; - any_matched = true; - break; - } - } + for &(member_name, target) in self.automaton[self.state].member_transitions() { + if self.automaton.is_accepting(target) && self.is_match(idx, member_name)? { + self.record_match_detected_at( + idx + 1, + NodeTypeHint::Atomic, /* since is_next_opening is false */ + )?; + any_matched = true; + break; } } let fallback_state = self.automaton[self.state].fallback_state(); @@ -363,11 +354,12 @@ where } debug!("Incremented array count to {}", self.array_count); - let match_index = self - .automaton - .has_array_index_transition_to_accepting(self.state, &self.array_count); - - if self.is_list && !is_next_opening && match_index { + if self.is_list + && !is_next_opening + && self + .automaton + .has_array_index_transition_to_accepting(self.state, &self.array_count) + { debug!("Accepting on list item."); self.record_match_detected_at(idx + 1, NodeTypeHint::Atomic /* since is_next_opening is false */)?; } @@ -387,29 +379,28 @@ where let colon_idx = self.find_preceding_colon(idx); - for &(label, target) in self.automaton[self.state].transitions() { - match label { - TransitionLabel::ArrayIndex(i) => { - if self.is_list && i.eq(&self.array_count) { + 'trans: { + for &(i, target) in self.automaton[self.state].array_transitions() { + if self.is_list && i.eq(&self.array_count) { + any_matched = true; + self.transition_to(target, bracket_type); + if self.automaton.is_accepting(target) { + debug!("Accept {idx}"); + self.record_match_detected_at(idx, NodeTypeHint::Complex(bracket_type))?; + } + break 'trans; + } + } + + for &(member_name, target) in self.automaton[self.state].member_transitions() { + if let Some(colon_idx) = colon_idx { + if self.is_match(colon_idx, member_name)? { any_matched = true; self.transition_to(target, bracket_type); if self.automaton.is_accepting(target) { - debug!("Accept {idx}"); - self.record_match_detected_at(idx, NodeTypeHint::Complex(bracket_type))?; - } - break; - } - } - TransitionLabel::ObjectMember(member_name) => { - if let Some(colon_idx) = colon_idx { - if self.is_match(colon_idx, member_name)? { - any_matched = true; - self.transition_to(target, bracket_type); - if self.automaton.is_accepting(target) { - self.record_match_detected_at(colon_idx + 1, NodeTypeHint::Complex(bracket_type))?; - } - break; + self.record_match_detected_at(colon_idx + 1, NodeTypeHint::Complex(bracket_type))?; } + break 'trans; } } } @@ -439,14 +430,10 @@ where let mut needs_commas = false; if self.is_list { - self.has_any_array_item_transition = self.automaton.has_any_array_item_transition(self.state); - self.has_any_array_item_transition_to_accepting = - self.automaton.has_any_array_item_transition_to_accepting(self.state); - let fallback = self.automaton[self.state].fallback_state(); let is_fallback_accepting = self.automaton.is_accepting(fallback); - let searching_list = is_fallback_accepting || self.has_any_array_item_transition; + let searching_list = is_fallback_accepting || self.automaton.has_any_array_item_transition(self.state); if searching_list { needs_commas = true; @@ -498,8 +485,6 @@ where self.state = stack_frame.state; self.is_list = stack_frame.is_list; self.array_count = stack_frame.array_count; - self.has_any_array_item_transition = stack_frame.has_any_array_item_transition; - self.has_any_array_item_transition_to_accepting = stack_frame.has_any_array_item_transition_to_accepting; debug!("Restored array count to {}", self.array_count); @@ -514,7 +499,7 @@ where if self.is_list && (self.automaton.is_accepting(self.automaton[self.state].fallback_state()) - || self.has_any_array_item_transition) + || self.automaton.has_any_array_item_transition(self.state)) { classifier.turn_commas_on(idx); } else { @@ -536,7 +521,7 @@ where let fallback = self.automaton[self.state].fallback_state(); let is_fallback_accepting = self.automaton.is_accepting(fallback); - let searching_list = is_fallback_accepting || self.has_any_array_item_transition; + let searching_list = is_fallback_accepting || self.automaton.has_any_array_item_transition(self.state); if target != self.state || target_is_list != self.is_list || searching_list { debug!( @@ -549,8 +534,6 @@ where state: self.state, is_list: self.is_list, array_count: self.array_count, - has_any_array_item_transition: self.has_any_array_item_transition, - has_any_array_item_transition_to_accepting: self.has_any_array_item_transition_to_accepting, }); self.state = target; } @@ -608,8 +591,6 @@ struct StackFrame { state: State, is_list: bool, array_count: JsonUInt, - has_any_array_item_transition: bool, - has_any_array_item_transition_to_accepting: bool, } #[derive(Debug)]