Skip to content

Commit b4b8e3a

Browse files
committed
initial main engine nneg array impl
1 parent 4846ac6 commit b4b8e3a

File tree

7 files changed

+141
-27
lines changed

7 files changed

+141
-27
lines changed

crates/rsonpath-lib/src/engine.rs

+4
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ pub use main::MainEngine as RsonpathEngine;
1717
use self::error::EngineError;
1818
use crate::input::Input;
1919
use crate::query::automaton::Automaton;
20+
use crate::query::NonNegativeArrayIndex;
2021
use crate::query::{error::CompilerError, JsonPathQuery};
2122
use crate::result::QueryResult;
2223

24+
/// A constant index for the common and starting case of the first item.
25+
pub const FIRST_ITEM_INDEX: NonNegativeArrayIndex = NonNegativeArrayIndex::new(0);
26+
2327
/// Trait for an engine that can run its query on a given input.
2428
pub trait Engine {
2529
/// Compute the [`QueryResult`] on given [`Input`].

crates/rsonpath-lib/src/engine/main.rs

+59-7
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//! even on targets that do not support AVX2 SIMD operations.
1010
#[cfg(feature = "head-skip")]
1111
use super::head_skipping::{CanHeadSkip, HeadSkip};
12-
use super::Compiler;
12+
use super::{Compiler, FIRST_ITEM_INDEX};
1313
#[cfg(feature = "head-skip")]
1414
use crate::classification::ResumeClassifierState;
1515
use crate::debug;
@@ -20,7 +20,7 @@ use crate::engine::tail_skipping::TailSkip;
2020
use crate::engine::{Engine, Input};
2121
use crate::query::automaton::{Automaton, State};
2222
use crate::query::error::CompilerError;
23-
use crate::query::{JsonPathQuery, Label};
23+
use crate::query::{JsonPathQuery, Label, NonNegativeArrayIndex};
2424
use crate::result::QueryResult;
2525
use crate::BLOCK_SIZE;
2626
use crate::{
@@ -105,6 +105,7 @@ struct Executor<'q, 'b, I: Input> {
105105
bytes: &'b I,
106106
next_event: Option<Structural>,
107107
is_list: bool,
108+
array_count: NonNegativeArrayIndex,
108109
}
109110

110111
fn query_executor<'q, 'b, I: Input>(
@@ -119,6 +120,7 @@ fn query_executor<'q, 'b, I: Input>(
119120
bytes,
120121
next_event: None,
121122
is_list: false,
123+
array_count: FIRST_ITEM_INDEX,
122124
}
123125
}
124126

@@ -247,14 +249,35 @@ impl<'q, 'b, I: Input> Executor<'q, 'b, I> {
247249
S: StructuralIterator<'b, I, Q, BLOCK_SIZE>,
248250
R: QueryResult,
249251
{
252+
debug!("array_count = {}", self.array_count);
250253
self.next_event = classifier.next();
251254
let is_next_opening = self.next_event.map_or(false, |s| s.is_opening());
252255

253256
if !is_next_opening {
254-
let fallback_state = self.automaton[self.state].fallback_state();
255-
if self.is_list && self.automaton.is_accepting(fallback_state) {
257+
let fallback_accepting = self
258+
.automaton
259+
.is_accepting(self.automaton[self.state].fallback_state());
260+
261+
if self.is_list && fallback_accepting {
256262
result.report(idx);
257263
}
264+
265+
self.array_count = self.array_count.increment();
266+
267+
if let Ok(array_id) = self.array_count.try_into() {
268+
let match_index = self
269+
.automaton
270+
.has_array_index_transition_to_accepting(self.state, &array_id);
271+
272+
let accepting_list = self.automaton.is_accepting_list_item(self.state);
273+
274+
let is_accepting_list_item = self.is_list && accepting_list;
275+
276+
if is_accepting_list_item && match_index {
277+
debug!("Accepting on list item.");
278+
result.report(idx);
279+
}
280+
}
258281
}
259282

260283
Ok(())
@@ -278,7 +301,24 @@ impl<'q, 'b, I: Input> Executor<'q, 'b, I> {
278301
if let Some(colon_idx) = self.find_preceding_colon(idx) {
279302
for &(label, target) in self.automaton[self.state].transitions() {
280303
match label {
281-
TransitionLabel::ArrayIndex(_) => {}
304+
TransitionLabel::ArrayIndex(_i) => {
305+
// TODO: should this really be a no-op?
306+
// if let Ok(array_id) = self.array_count.try_into() {
307+
// if self.is_list
308+
// && self
309+
// .automaton
310+
// .has_accepting_list_item_at_index(self.state, &array_id)
311+
// {
312+
// any_matched = true;
313+
// if self.automaton.is_accepting(target) {
314+
// debug!("Accept Array Index {i}");
315+
// debug!("Accept {idx}");
316+
// result.report(idx);
317+
// }
318+
// break;
319+
// }
320+
// }
321+
}
282322
TransitionLabel::ObjectMember(label) => {
283323
if self.is_match(colon_idx, label)? {
284324
any_matched = true;
@@ -316,9 +356,17 @@ impl<'q, 'b, I: Input> Executor<'q, 'b, I> {
316356
self.is_list = true;
317357

318358
let fallback = self.automaton[self.state].fallback_state();
319-
if self.automaton.is_accepting(fallback) {
320-
classifier.turn_commas_on(idx);
359+
let is_fallback_accepting = self.automaton.is_accepting(fallback);
360+
let wants_first_item = is_fallback_accepting
361+
|| self
362+
.automaton
363+
.has_first_array_index_transition_to_accepting(self.state);
364+
365+
classifier.turn_commas_on(idx);
366+
367+
if wants_first_item {
321368
self.next_event = classifier.next();
369+
322370
match self.next_event {
323371
Some(Structural::Closing(_, close_idx)) => {
324372
if let Some((next_idx, _)) = self.bytes.seek_non_whitespace_forward(idx + 1)
@@ -372,6 +420,7 @@ impl<'q, 'b, I: Input> Executor<'q, 'b, I> {
372420
if let Some(stack_frame) = self.stack.pop_if_at_or_below(*self.depth) {
373421
self.state = stack_frame.state;
374422
self.is_list = stack_frame.is_list;
423+
self.array_count = stack_frame.array_count;
375424

376425
if self.automaton.is_unitary(self.state) {
377426
let bracket_type = self.current_node_bracket_type();
@@ -420,10 +469,12 @@ impl<'q, 'b, I: Input> Executor<'q, 'b, I> {
420469
"push {}, goto {target}, is_list = {target_is_list}",
421470
self.state
422471
);
472+
423473
self.stack.push(StackFrame {
424474
depth: *self.depth,
425475
state: self.state,
426476
is_list: self.is_list,
477+
array_count: self.array_count,
427478
});
428479
self.state = target;
429480
}
@@ -480,6 +531,7 @@ struct StackFrame {
480531
depth: u8,
481532
state: State,
482533
is_list: bool,
534+
array_count: NonNegativeArrayIndex,
483535
}
484536

485537
#[derive(Debug)]

crates/rsonpath-lib/src/engine/recursive.rs

+12-16
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,17 @@ use crate::debug;
1313
use crate::engine::error::EngineError;
1414
#[cfg(feature = "tail-skip")]
1515
use crate::engine::tail_skipping::TailSkip;
16+
use crate::engine::FIRST_ITEM_INDEX;
1617
use crate::engine::{Compiler, Engine};
1718
#[cfg(feature = "head-skip")]
1819
use crate::error::InternalRsonpathError;
1920
use crate::input::Input;
2021
use crate::query::automaton::{Automaton, State, TransitionLabel};
2122
use crate::query::error::CompilerError;
22-
use crate::query::NonNegativeArrayIndex;
2323
use crate::query::{JsonPathQuery, Label};
2424
use crate::result::QueryResult;
2525
use crate::BLOCK_SIZE;
2626

27-
pub(crate) const FIRST_ITEM_INDEX: NonNegativeArrayIndex = NonNegativeArrayIndex::new(0);
28-
2927
/// Recursive implementation of the JSONPath query engine.
3028
pub struct RecursiveEngine<'q> {
3129
automaton: Automaton<'q>,
@@ -188,7 +186,7 @@ impl<'q, 'b, I: Input> ExecutionContext<'q, 'b, I> {
188186
let needs_commas = is_list && (is_fallback_accepting || searching_list);
189187
let needs_colons = !is_list && self.automaton.has_transition_to_accepting(state);
190188

191-
let mut array_count = 0;
189+
let mut array_count = FIRST_ITEM_INDEX;
192190

193191
let config_characters = |classifier: &mut Classifier!(), idx: usize| {
194192
if needs_commas {
@@ -249,23 +247,21 @@ impl<'q, 'b, I: Input> ExecutionContext<'q, 'b, I> {
249247
}
250248

251249
// Once we are in comma search, we have already considered the option that the first item in the list is a match. Iterate on the remaining items.
252-
array_count += 1;
253-
254-
// let is_next_closing = next_event.map_or(false, |s| s.is_closing());
250+
array_count = array_count.increment();
255251

256-
let match_index = self.automaton[state].transitions().iter().any(|t| match t {
257-
(TransitionLabel::ArrayIndex(i), _) => array_count == i.get_index(),
258-
_ => false,
259-
});
252+
if let Ok(array_id) = array_count.try_into() {
253+
let match_index = self
254+
.automaton
255+
.has_array_index_transition_to_accepting(state, &array_id);
260256

261-
if is_accepting_list_item && !is_next_opening && match_index {
262-
debug!("Accepting on list item.");
263-
result.report(idx);
257+
if is_accepting_list_item && !is_next_opening && match_index {
258+
debug!("Accepting on list item.");
259+
result.report(idx);
260+
}
264261
}
265262
}
266263
Some(Structural::Colon(idx)) => {
267264
debug!("Colon");
268-
// debug_assert!(!is_accepting_list_item);
269265

270266
latest_idx = idx;
271267
next_event = classifier.next();
@@ -334,7 +330,7 @@ impl<'q, 'b, I: Input> ExecutionContext<'q, 'b, I> {
334330
}
335331
}
336332
TransitionLabel::ArrayIndex(i) => {
337-
if is_list && (i.get_index() == array_count) {
333+
if is_list && i.eq(&array_count) {
338334
matched = Some(target);
339335
if self.automaton.is_accepting(target) {
340336
debug!("Accept Array Index {i}");

crates/rsonpath-lib/src/query/automaton.rs

+56-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ mod state;
77
pub use state::{State, StateAttributes};
88

99
use super::{error::CompilerError, JsonPathQuery, Label, NonNegativeArrayIndex};
10-
use crate::debug;
10+
use crate::{debug, engine::FIRST_ITEM_INDEX};
1111
use nfa::NondeterministicAutomaton;
1212
use smallvec::SmallVec;
1313
use std::{fmt::Display, ops::Index};
@@ -258,6 +258,61 @@ impl<'q> Automaton<'q> {
258258
})
259259
}
260260

261+
/// Returns whether the given state is accepting the first item in a list.
262+
///
263+
/// # Example
264+
/// ```rust
265+
/// # use rsonpath_lib::query::*;
266+
/// # use rsonpath_lib::query::automaton::*;
267+
/// let query = JsonPathQuery::parse("$[0]").unwrap();
268+
/// let automaton = Automaton::new(&query).unwrap();
269+
/// let state = automaton.initial_state();
270+
///
271+
/// assert!(automaton.has_first_array_index_transition_to_accepting(state));
272+
/// ```
273+
/// ```rust
274+
/// # use rsonpath_lib::query::*;
275+
/// # use rsonpath_lib::query::automaton::*;
276+
/// let query = JsonPathQuery::parse("$[1]").unwrap();
277+
/// let automaton = Automaton::new(&query).unwrap();
278+
/// let state = automaton.initial_state();
279+
///
280+
/// assert!(!automaton.has_first_array_index_transition_to_accepting(state));
281+
/// ```
282+
#[must_use]
283+
#[inline(always)]
284+
pub fn has_first_array_index_transition_to_accepting(&self, state: State) -> bool {
285+
self.has_array_index_transition_to_accepting(state, &FIRST_ITEM_INDEX)
286+
}
287+
288+
/// Returns whether the given state is accepting the item at a given index in a list.
289+
///
290+
/// # Example
291+
/// ```rust
292+
/// # use rsonpath_lib::query::*;
293+
/// # use rsonpath_lib::query::automaton::*;
294+
/// let query = JsonPathQuery::parse("$[1]").unwrap();
295+
/// let automaton = Automaton::new(&query).unwrap();
296+
/// let state = automaton.initial_state();
297+
/// let match_index_1 = NonNegativeArrayIndex::new(1);
298+
/// let match_index_2 = NonNegativeArrayIndex::new(2);
299+
///
300+
/// assert!(automaton.has_array_index_transition_to_accepting(state, &match_index_1));
301+
/// assert!(!automaton.has_array_index_transition_to_accepting(state, &match_index_2));
302+
/// ```
303+
#[must_use]
304+
#[inline(always)]
305+
pub fn has_array_index_transition_to_accepting(
306+
&self,
307+
state: State,
308+
match_index: &NonNegativeArrayIndex,
309+
) -> bool {
310+
self[state].transitions().iter().any(|t| match t {
311+
(TransitionLabel::ArrayIndex(i), s) => i.eq(match_index) && self.is_accepting(*s),
312+
_ => false,
313+
})
314+
}
315+
261316
/// Returns whether the given state has any transitions
262317
/// (labelled or fallback) to an accepting state.
263318
///

crates/rsonpath-lib/src/query/nonnegative_array_index.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::fmt::{self, Display, Formatter};
1414
///
1515
/// assert_eq!(idx.get_index(), 2);
1616
/// ```
17-
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
17+
#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
1818
pub struct NonNegativeArrayIndex(u64);
1919

2020
/// The upper inclusive bound on index values.
@@ -41,6 +41,13 @@ impl NonNegativeArrayIndex {
4141
Self(index)
4242
}
4343

44+
/// Create a new search index from a u64.
45+
#[must_use]
46+
#[inline]
47+
pub fn increment(&self) -> Self {
48+
NonNegativeArrayIndex::new(&self.0 + 1)
49+
}
50+
4451
/// Return the index stored.
4552
#[must_use]
4653
#[inline]
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
[1,2]
1+
[1,2]

crates/rsonpath-lib/tests/engine_correctness_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ macro_rules! count_test_cases {
2323
#[test_case("basic/atomic_descendant.json", "$..*" => 4; "atomic_descendant.json any descendant $..*")]
2424
#[test_case("basic/atomic_descendant.json", "$.b[0]" => 1; "atomic_descendant.json nneg array index")]
2525
#[test_case("basic/atomic_descendant.json", "$.b[1]" => 0; "atomic_descendant.json nonexistent nneg array index")]
26-
#[test_case("basic/atomic_descendant.json", "$..[0]" => 1; "atomic_descendant.json descendant array index")]
26+
#[test_case("basic/atomic_descendant.json", "$..[0]" => 1; "atomic_descendant.json descendant nneg array index")]
2727
#[test_case("basic/atomic_descendant.json", "$.b[0].b" => 1; "atomic_descendant.json nested nneg array index")]
2828
#[test_case("basic/atomic_after_complex.json", "$.a..b" => 1; "atomic_after_complex.json $.a..b")]
2929
#[test_case("basic/atomic_after_complex.json", "$.a[0]" => 1; "atomic_after_complex.json nneg array index")]

0 commit comments

Comments
 (0)