Skip to content

Commit 29d6095

Browse files
authored
Merge pull request #431 from V0ldek/v0ldek/separate-parser-crate
Parsing the slice selector
2 parents 9575571 + 969bbc7 commit 29d6095

21 files changed

Lines changed: 685 additions & 278 deletions

File tree

.github/workflows/test-codegen.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,5 @@ jobs:
7171
name: rsonpath-test-documents
7272
path: |
7373
crates/rsonpath-test/documents
74-
crates/rsonpath-test/tests
74+
crates/rsonpath-test/tests/generated
7575
retention-days: 1

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "crates/rsonpath-benchmarks"]
22
path = crates/rsonpath-benchmarks
33
url = git@github.com:V0ldek/rsonpath-benchmarks.git
4+
[submodule "crates/rsonpath-test/jsonpath-compliance-test-suite"]
5+
path = crates/rsonpath-test/jsonpath-compliance-test-suite
6+
url = https://github.com/jsonpath-standard/jsonpath-compliance-test-suite.git

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/rsonpath-benchmarks

crates/rsonpath-lib/src/automaton.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl Display for TransitionLabel<'_> {
7676
#[inline(always)]
7777
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7878
match self {
79-
TransitionLabel::ObjectMember(name) => write!(f, "{}", name.quoted()),
79+
TransitionLabel::ObjectMember(name) => write!(f, "{}", name.unquoted()),
8080
TransitionLabel::ArrayIndex(index) => write!(f, "{}", index.as_u64()),
8181
}
8282
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,14 @@ impl<'q> NondeterministicAutomaton<'q> {
7777
Selector::Wildcard => Ok(Direct(Transition::Wildcard)),
7878
Selector::Index(Index::FromStart(index)) => Ok(Direct(Transition::Labelled((*index).into()))),
7979
Selector::Index(Index::FromEnd(_)) => Err(UnsupportedFeatureError::indexing_from_end().into()),
80+
Selector::Slice(_) => Err(UnsupportedFeatureError::slice_selector().into()),
8081
},
8182
Segment::Descendant(selectors) if selectors.len() == 1 => match selectors.first() {
8283
Selector::Name(name) => Ok(Recursive(Transition::Labelled(name.into()))),
8384
Selector::Wildcard => Ok(Recursive(Transition::Wildcard)),
8485
Selector::Index(Index::FromStart(index)) => Ok(Recursive(Transition::Labelled((*index).into()))),
8586
Selector::Index(Index::FromEnd(_)) => Err(UnsupportedFeatureError::indexing_from_end().into()),
87+
Selector::Slice(_) => Err(UnsupportedFeatureError::slice_selector().into()),
8688
},
8789
_ => Err(UnsupportedFeatureError::multiple_selectors().into()),
8890
})

crates/rsonpath-lib/src/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ impl UnsupportedFeatureError {
146146
Self::untracked("Indexing from End")
147147
}
148148

149+
/// Slice Selector &ndash; supporting slice selectors.
150+
/// <https://github.com/V0ldek/rsonpath/issues/152>
151+
#[must_use]
152+
#[inline(always)]
153+
pub fn slice_selector() -> Self {
154+
Self::tracked(152, "Slice Selector")
155+
}
156+
149157
/// Returns the issue number on GitHub corresponding to the unsupported feature.
150158
/// Is [`None`] if the feature is not planned.
151159
#[must_use]

crates/rsonpath-syntax/src/builder.rs

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Utility for building a [`JsonPathQuery`](`crate::JsonPathQuery`)
22
//! programmatically.
3-
use crate::{num::JsonInt, str::JsonString, Index, JsonPathQuery, Segment, Selector, Selectors};
3+
use crate::{num::JsonInt, str::JsonString, Index, JsonPathQuery, Segment, Selector, Selectors, SliceBuilder};
44

55
/// Builder for [`JsonPathQuery`] instances.
66
///
@@ -13,12 +13,13 @@ use crate::{num::JsonInt, str::JsonString, Index, JsonPathQuery, Segment, Select
1313
/// .descendant_name("b")
1414
/// .child_wildcard()
1515
/// .child_name("c")
16-
/// .descendant_wildcard();
16+
/// .descendant_wildcard()
17+
/// .child_slice(|x| x.with_start(3).with_end(-7).with_step(2));
1718
///
1819
/// // Can also use `builder.build()` as a non-consuming version.
1920
/// let query: JsonPathQuery = builder.into();
2021
///
21-
/// assert_eq!(query.to_string(), "$['a']..['b'][*]['c']..[*]");
22+
/// assert_eq!(query.to_string(), "$['a']..['b'][*]['c']..[*][3:-7:2]");
2223
/// ```
2324
pub struct JsonPathQueryBuilder {
2425
segments: Vec<Segment>,
@@ -133,6 +134,17 @@ impl JsonPathQueryBuilder {
133134
self.child(|x| x.index(idx))
134135
}
135136

137+
/// Add a child segment with a single slice selector.
138+
///
139+
/// This is a shorthand for `.child(|x| x.slice(slice_builder))`.
140+
#[inline(always)]
141+
pub fn child_slice<F>(&mut self, slice_builder: F) -> &mut Self
142+
where
143+
F: FnOnce(&mut SliceBuilder) -> &mut SliceBuilder,
144+
{
145+
self.child(|x| x.slice(slice_builder))
146+
}
147+
136148
/// Add a descendant segment with a single name selector.
137149
///
138150
/// This is a shorthand for `.descendant(|x| x.name(name))`.
@@ -157,6 +169,17 @@ impl JsonPathQueryBuilder {
157169
self.descendant(|x| x.index(idx))
158170
}
159171

172+
/// Add a descendant segment with a single slice selector.
173+
///
174+
/// This is a shorthand for `.descendant(|x| x.slice(slice_builder))`.
175+
#[inline(always)]
176+
pub fn descendant_slice<F>(&mut self, slice_builder: F) -> &mut Self
177+
where
178+
F: FnOnce(&mut SliceBuilder) -> &mut SliceBuilder,
179+
{
180+
self.descendant(|x| x.slice(slice_builder))
181+
}
182+
160183
/// Produce a [`JsonPathQuery`] from the builder.
161184
///
162185
/// This clones all data in the builder to create the query.
@@ -225,6 +248,47 @@ impl JsonPathSelectorsBuilder {
225248
self
226249
}
227250

251+
/// Add a slice selector based on a given start, end, and step integers.
252+
///
253+
/// The result is a [`Selector::Slice`] with given `start`, `end`, and `step`.
254+
///
255+
/// ## Examples
256+
///
257+
/// ```rust
258+
/// # use rsonpath_syntax::{Selector, SliceBuilder, Index, Step, num::{JsonNonZeroUInt, JsonUInt}, builder::JsonPathQueryBuilder};
259+
/// let mut builder = JsonPathQueryBuilder::new();
260+
/// builder.child(|x| x
261+
/// .slice(|s| s.with_start(10).with_end(-20).with_step(5))
262+
/// .slice(|s| s.with_start(-20).with_step(-30)));
263+
/// let result = builder.into_query();
264+
///
265+
/// assert_eq!(result.segments().len(), 1);
266+
/// let segment = &result.segments()[0];
267+
/// let selectors = segment.selectors().as_slice();
268+
/// match (&selectors[0], &selectors[1]) {
269+
/// (Selector::Slice(s1), Selector::Slice(s2)) => {
270+
/// assert_eq!(s1.start(), Index::FromStart(10.into()));
271+
/// assert_eq!(s1.end(), Some(Index::FromEnd(JsonNonZeroUInt::try_from(20).unwrap())));
272+
/// assert_eq!(s1.step(), Step::Forward(5.into()));
273+
/// assert_eq!(s2.start(), Index::FromEnd(JsonNonZeroUInt::try_from(20).unwrap()));
274+
/// assert_eq!(s2.end(), None);
275+
/// assert_eq!(s2.step(), Step::Backward(JsonNonZeroUInt::try_from(30).unwrap()));
276+
/// }
277+
/// _ => unreachable!()
278+
/// }
279+
/// ```
280+
#[inline(always)]
281+
pub fn slice<F>(&mut self, slice_builder: F) -> &mut Self
282+
where
283+
F: FnOnce(&mut SliceBuilder) -> &mut SliceBuilder,
284+
{
285+
let mut slice = SliceBuilder::new();
286+
slice_builder(&mut slice);
287+
let slice = slice.into();
288+
self.selectors.push(Selector::Slice(slice));
289+
self
290+
}
291+
228292
/// Add a wildcard selector.
229293
#[inline(always)]
230294
pub fn wildcard(&mut self) -> &mut Self {

crates/rsonpath-syntax/src/error.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ pub(crate) enum SyntaxErrorKind {
126126
NegativeZeroInteger,
127127
LeadingZeros,
128128
IndexParseError(JsonIntParseError),
129+
SliceStartParseError(JsonIntParseError),
130+
SliceEndParseError(JsonIntParseError),
131+
SliceStepParseError(JsonIntParseError),
129132
}
130133

131134
impl SyntaxError {
@@ -266,9 +269,12 @@ impl SyntaxError {
266269
suggestion.remove(start_idx + offset, remove_len);
267270
}
268271
}
269-
SyntaxErrorKind::InvalidSelector | SyntaxErrorKind::IndexParseError(_) | SyntaxErrorKind::EmptySelector => {
270-
suggestion.invalidate()
271-
}
272+
SyntaxErrorKind::InvalidSelector
273+
| SyntaxErrorKind::IndexParseError(_)
274+
| SyntaxErrorKind::SliceStartParseError(_)
275+
| SyntaxErrorKind::SliceStepParseError(_)
276+
| SyntaxErrorKind::SliceEndParseError(_)
277+
| SyntaxErrorKind::EmptySelector => suggestion.invalidate(),
272278
}
273279

274280
// Generic notes.
@@ -660,6 +666,9 @@ impl SyntaxErrorKind {
660666
Self::NegativeZeroInteger => "negative zero used as an integer".to_string(),
661667
Self::LeadingZeros => "integer with leading zeros".to_string(),
662668
Self::IndexParseError(_) => "invalid index value".to_string(),
669+
Self::SliceStartParseError(_) => "invalid slice start".to_string(),
670+
Self::SliceEndParseError(_) => "invalid slice end".to_string(),
671+
Self::SliceStepParseError(_) => "invalid slice step value".to_string(),
663672
}
664673
}
665674

@@ -686,6 +695,9 @@ impl SyntaxErrorKind {
686695
Self::NegativeZeroInteger => "negative zero is not allowed".to_string(),
687696
Self::LeadingZeros => "leading zeros are not allowed".to_string(),
688697
Self::IndexParseError(inner) => format!("this index value is invalid; {inner}"),
698+
Self::SliceStartParseError(inner) => format!("this start index is invalid; {inner}"),
699+
Self::SliceEndParseError(inner) => format!("this end index is invalid; {inner}"),
700+
Self::SliceStepParseError(inner) => format!("this step value is invalid; {inner}"),
689701
}
690702
}
691703
}

0 commit comments

Comments
 (0)