Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parsing the slice selector #431

Merged
merged 5 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test-codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,5 @@ jobs:
name: rsonpath-test-documents
path: |
crates/rsonpath-test/documents
crates/rsonpath-test/tests
crates/rsonpath-test/tests/generated
retention-days: 1
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "crates/rsonpath-benchmarks"]
path = crates/rsonpath-benchmarks
url = [email protected]:V0ldek/rsonpath-benchmarks.git
[submodule "crates/rsonpath-test/jsonpath-compliance-test-suite"]
path = crates/rsonpath-test/jsonpath-compliance-test-suite
url = https://github.com/jsonpath-standard/jsonpath-compliance-test-suite.git
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/rsonpath-benchmarks
Submodule rsonpath-benchmarks updated 1 files
+178 −110 Cargo.lock
2 changes: 1 addition & 1 deletion crates/rsonpath-lib/src/automaton.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ 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.quoted()),
TransitionLabel::ObjectMember(name) => write!(f, "{}", name.unquoted()),
TransitionLabel::ArrayIndex(index) => write!(f, "{}", index.as_u64()),
}
}
Expand Down
2 changes: 2 additions & 0 deletions crates/rsonpath-lib/src/automaton/nfa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,14 @@ impl<'q> NondeterministicAutomaton<'q> {
Selector::Wildcard => Ok(Direct(Transition::Wildcard)),
Selector::Index(Index::FromStart(index)) => Ok(Direct(Transition::Labelled((*index).into()))),
Selector::Index(Index::FromEnd(_)) => Err(UnsupportedFeatureError::indexing_from_end().into()),
Selector::Slice(_) => Err(UnsupportedFeatureError::slice_selector().into()),
},
Segment::Descendant(selectors) if selectors.len() == 1 => match selectors.first() {
Selector::Name(name) => Ok(Recursive(Transition::Labelled(name.into()))),
Selector::Wildcard => Ok(Recursive(Transition::Wildcard)),
Selector::Index(Index::FromStart(index)) => Ok(Recursive(Transition::Labelled((*index).into()))),
Selector::Index(Index::FromEnd(_)) => Err(UnsupportedFeatureError::indexing_from_end().into()),
Selector::Slice(_) => Err(UnsupportedFeatureError::slice_selector().into()),
},
_ => Err(UnsupportedFeatureError::multiple_selectors().into()),
})
Expand Down
8 changes: 8 additions & 0 deletions crates/rsonpath-lib/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ impl UnsupportedFeatureError {
Self::untracked("Indexing from End")
}

/// Slice Selector &ndash; supporting slice selectors.
/// <https://github.com/V0ldek/rsonpath/issues/152>
#[must_use]
#[inline(always)]
pub fn slice_selector() -> Self {
Self::tracked(152, "Slice Selector")
}

/// Returns the issue number on GitHub corresponding to the unsupported feature.
/// Is [`None`] if the feature is not planned.
#[must_use]
Expand Down
70 changes: 67 additions & 3 deletions crates/rsonpath-syntax/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Utility for building a [`JsonPathQuery`](`crate::JsonPathQuery`)
//! programmatically.
use crate::{num::JsonInt, str::JsonString, Index, JsonPathQuery, Segment, Selector, Selectors};
use crate::{num::JsonInt, str::JsonString, Index, JsonPathQuery, Segment, Selector, Selectors, SliceBuilder};

/// Builder for [`JsonPathQuery`] instances.
///
Expand All @@ -13,12 +13,13 @@ use crate::{num::JsonInt, str::JsonString, Index, JsonPathQuery, Segment, Select
/// .descendant_name("b")
/// .child_wildcard()
/// .child_name("c")
/// .descendant_wildcard();
/// .descendant_wildcard()
/// .child_slice(|x| x.with_start(3).with_end(-7).with_step(2));
///
/// // Can also use `builder.build()` as a non-consuming version.
/// let query: JsonPathQuery = builder.into();
///
/// assert_eq!(query.to_string(), "$['a']..['b'][*]['c']..[*]");
/// assert_eq!(query.to_string(), "$['a']..['b'][*]['c']..[*][3:-7:2]");
/// ```
pub struct JsonPathQueryBuilder {
segments: Vec<Segment>,
Expand Down Expand Up @@ -133,6 +134,17 @@ impl JsonPathQueryBuilder {
self.child(|x| x.index(idx))
}

/// Add a child segment with a single slice selector.
///
/// This is a shorthand for `.child(|x| x.slice(slice_builder))`.
#[inline(always)]
pub fn child_slice<F>(&mut self, slice_builder: F) -> &mut Self
where
F: FnOnce(&mut SliceBuilder) -> &mut SliceBuilder,
{
self.child(|x| x.slice(slice_builder))
}

/// Add a descendant segment with a single name selector.
///
/// This is a shorthand for `.descendant(|x| x.name(name))`.
Expand All @@ -157,6 +169,17 @@ impl JsonPathQueryBuilder {
self.descendant(|x| x.index(idx))
}

/// Add a descendant segment with a single slice selector.
///
/// This is a shorthand for `.descendant(|x| x.slice(slice_builder))`.
#[inline(always)]
pub fn descendant_slice<F>(&mut self, slice_builder: F) -> &mut Self
where
F: FnOnce(&mut SliceBuilder) -> &mut SliceBuilder,
{
self.descendant(|x| x.slice(slice_builder))
}

/// Produce a [`JsonPathQuery`] from the builder.
///
/// This clones all data in the builder to create the query.
Expand Down Expand Up @@ -225,6 +248,47 @@ impl JsonPathSelectorsBuilder {
self
}

/// Add a slice selector based on a given start, end, and step integers.
///
/// The result is a [`Selector::Slice`] with given `start`, `end`, and `step`.
///
/// ## Examples
///
/// ```rust
/// # use rsonpath_syntax::{Selector, SliceBuilder, Index, Step, num::{JsonNonZeroUInt, JsonUInt}, builder::JsonPathQueryBuilder};
/// let mut builder = JsonPathQueryBuilder::new();
/// builder.child(|x| x
/// .slice(|s| s.with_start(10).with_end(-20).with_step(5))
/// .slice(|s| s.with_start(-20).with_step(-30)));
/// let result = builder.into_query();
///
/// assert_eq!(result.segments().len(), 1);
/// let segment = &result.segments()[0];
/// let selectors = segment.selectors().as_slice();
/// match (&selectors[0], &selectors[1]) {
/// (Selector::Slice(s1), Selector::Slice(s2)) => {
/// assert_eq!(s1.start(), Index::FromStart(10.into()));
/// assert_eq!(s1.end(), Some(Index::FromEnd(JsonNonZeroUInt::try_from(20).unwrap())));
/// assert_eq!(s1.step(), Step::Forward(5.into()));
/// assert_eq!(s2.start(), Index::FromEnd(JsonNonZeroUInt::try_from(20).unwrap()));
/// assert_eq!(s2.end(), None);
/// assert_eq!(s2.step(), Step::Backward(JsonNonZeroUInt::try_from(30).unwrap()));
/// }
/// _ => unreachable!()
/// }
/// ```
#[inline(always)]
pub fn slice<F>(&mut self, slice_builder: F) -> &mut Self
where
F: FnOnce(&mut SliceBuilder) -> &mut SliceBuilder,
{
let mut slice = SliceBuilder::new();
slice_builder(&mut slice);
let slice = slice.into();
self.selectors.push(Selector::Slice(slice));
self
}

/// Add a wildcard selector.
#[inline(always)]
pub fn wildcard(&mut self) -> &mut Self {
Expand Down
18 changes: 15 additions & 3 deletions crates/rsonpath-syntax/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ pub(crate) enum SyntaxErrorKind {
NegativeZeroInteger,
LeadingZeros,
IndexParseError(JsonIntParseError),
SliceStartParseError(JsonIntParseError),
SliceEndParseError(JsonIntParseError),
SliceStepParseError(JsonIntParseError),
}

impl SyntaxError {
Expand Down Expand Up @@ -266,9 +269,12 @@ impl SyntaxError {
suggestion.remove(start_idx + offset, remove_len);
}
}
SyntaxErrorKind::InvalidSelector | SyntaxErrorKind::IndexParseError(_) | SyntaxErrorKind::EmptySelector => {
suggestion.invalidate()
}
SyntaxErrorKind::InvalidSelector
| SyntaxErrorKind::IndexParseError(_)
| SyntaxErrorKind::SliceStartParseError(_)
| SyntaxErrorKind::SliceStepParseError(_)
| SyntaxErrorKind::SliceEndParseError(_)
| SyntaxErrorKind::EmptySelector => suggestion.invalidate(),
}

// Generic notes.
Expand Down Expand Up @@ -660,6 +666,9 @@ impl SyntaxErrorKind {
Self::NegativeZeroInteger => "negative zero used as an integer".to_string(),
Self::LeadingZeros => "integer with leading zeros".to_string(),
Self::IndexParseError(_) => "invalid index value".to_string(),
Self::SliceStartParseError(_) => "invalid slice start".to_string(),
Self::SliceEndParseError(_) => "invalid slice end".to_string(),
Self::SliceStepParseError(_) => "invalid slice step value".to_string(),
}
}

Expand All @@ -686,6 +695,9 @@ impl SyntaxErrorKind {
Self::NegativeZeroInteger => "negative zero is not allowed".to_string(),
Self::LeadingZeros => "leading zeros are not allowed".to_string(),
Self::IndexParseError(inner) => format!("this index value is invalid; {inner}"),
Self::SliceStartParseError(inner) => format!("this start index is invalid; {inner}"),
Self::SliceEndParseError(inner) => format!("this end index is invalid; {inner}"),
Self::SliceStepParseError(inner) => format!("this step value is invalid; {inner}"),
}
}
}
Expand Down
Loading
Loading