Skip to content

Commit

Permalink
Merge pull request #431 from V0ldek/v0ldek/separate-parser-crate
Browse files Browse the repository at this point in the history
Parsing the slice selector
  • Loading branch information
V0ldek committed Jan 15, 2024
2 parents 9575571 + 969bbc7 commit 29d6095
Show file tree
Hide file tree
Showing 21 changed files with 685 additions and 278 deletions.
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 = git@github.com: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

0 comments on commit 29d6095

Please sign in to comment.