diff --git a/crates/rsonpath-syntax/src/builder.rs b/crates/rsonpath-syntax/src/builder.rs index 0fd6e187..b3f122bb 100644 --- a/crates/rsonpath-syntax/src/builder.rs +++ b/crates/rsonpath-syntax/src/builder.rs @@ -417,6 +417,8 @@ impl From for JsonPathQuery { /// ``` pub struct SliceBuilder { inner: Slice, + /// We need to track if start is explicit because the default depends on step sign. + start_was_explicitly_given: bool, } impl SliceBuilder { @@ -432,6 +434,7 @@ impl SliceBuilder { pub fn new() -> Self { Self { inner: Slice::default(), + start_was_explicitly_given: false, } } @@ -439,6 +442,7 @@ impl SliceBuilder { #[inline] pub fn with_start>(&mut self, start: N) -> &mut Self { self.inner.start = start.into().into(); + self.start_was_explicitly_given = true; self } @@ -462,6 +466,14 @@ impl SliceBuilder { #[inline] #[must_use] pub fn to_slice(&mut self) -> Slice { + if !self.start_was_explicitly_given { + if self.inner.step.is_forward() { + self.inner.start = Slice::DEFAULT_START_FORWARDS; + } else { + self.inner.start = Slice::default_start_backwards(); + } + } + self.inner.clone() } } @@ -469,8 +481,8 @@ impl SliceBuilder { impl From for Slice { #[inline] #[must_use] - fn from(value: SliceBuilder) -> Self { - value.inner + fn from(mut value: SliceBuilder) -> Self { + value.to_slice() } } @@ -805,3 +817,31 @@ impl From for LogicalExpr { value.current } } + +#[cfg(test)] +mod tests { + use super::SliceBuilder; + use crate::{Index, Slice, Step}; + + #[test] + fn slice_builder_default_start_forward() { + let mut builder = SliceBuilder::new(); + builder.with_end(3).with_step(4); + let slice: Slice = builder.into(); + + assert_eq!(slice.start(), Index::FromStart(0.into())); + assert_eq!(slice.end(), Some(Index::FromStart(3.into()))); + assert_eq!(slice.step(), Step::Forward(4.into())); + } + + #[test] + fn slice_builder_default_start_backward() { + let mut builder = SliceBuilder::new(); + builder.with_end(3).with_step(-4); + let slice: Slice = builder.into(); + + assert_eq!(slice.start(), Index::FromEnd(1.try_into().unwrap())); + assert_eq!(slice.end(), Some(Index::FromStart(3.into()))); + assert_eq!(slice.step(), Step::Backward(4.try_into().unwrap())); + } +} diff --git a/crates/rsonpath-syntax/src/lib.rs b/crates/rsonpath-syntax/src/lib.rs index 29022067..cda70a08 100644 --- a/crates/rsonpath-syntax/src/lib.rs +++ b/crates/rsonpath-syntax/src/lib.rs @@ -542,8 +542,13 @@ pub struct Slice { } impl Slice { - const DEFAULT_START: Index = Index::FromStart(num::JsonUInt::ZERO); - const DEFAULT_STEP: Step = Step::Forward(num::JsonUInt::ONE); + pub(crate) const DEFAULT_START_FORWARDS: Index = Index::FromStart(num::JsonUInt::ZERO); + /// This is not const because the required NonZeroU64::MIN is from Rust 1.70. + #[inline(always)] + pub(crate) fn default_start_backwards() -> Index { + Index::FromEnd(1.try_into().expect("const 1 is nonzero")) + } + pub(crate) const DEFAULT_STEP: Step = Step::Forward(num::JsonUInt::ONE); /// Create a new [`Slice`] from given bounds and step. #[inline(always)] @@ -1180,7 +1185,9 @@ impl Display for Step { impl Display for Slice { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.start != Self::DEFAULT_START { + if (self.step.is_forward() && self.start != Self::DEFAULT_START_FORWARDS) + || (self.step.is_backward() && self.start != Self::default_start_backwards()) + { write!(f, "{}", self.start)?; } write!(f, ":")?; diff --git a/crates/rsonpath-syntax/src/parser.rs b/crates/rsonpath-syntax/src/parser.rs index b203c458..f281b6cb 100644 --- a/crates/rsonpath-syntax/src/parser.rs +++ b/crates/rsonpath-syntax/src/parser.rs @@ -336,6 +336,11 @@ fn slice_selector(q: &str) -> IResult<&str, Selector, InternalParseError> { }; } + // Fixup the bounds - if start was not given and step is negative, the default must be reversed. + if slice.step.is_backward() && opt_start.is_none() { + slice.start = crate::Slice::default_start_backwards(); + } + Ok((rest, Selector::Slice(slice))) } @@ -1020,8 +1025,8 @@ mod tests { #[test_case("-3:-4:-5", Index::FromEnd(3.try_into().unwrap()), Some(Index::FromEnd(4.try_into().unwrap())), Step::Backward(5.try_into().unwrap()); "test m3cm4cm5")] #[test_case(":4:5", Index::FromStart(0.into()), Some(Index::FromStart(4.into())), Step::Forward(5.into()); "test c4c5")] #[test_case(":-4:5", Index::FromStart(0.into()), Some(Index::FromEnd(4.try_into().unwrap())), Step::Forward(5.into()); "test cm4c5")] - #[test_case(":4:-5", Index::FromStart(0.into()), Some(Index::FromStart(4.into())), Step::Backward(5.try_into().unwrap()); "test c4cm5")] - #[test_case(":-4:-5", Index::FromStart(0.into()), Some(Index::FromEnd(4.try_into().unwrap())), Step::Backward(5.try_into().unwrap()); "test cm4cm5")] + #[test_case(":4:-5", Index::FromEnd(1.try_into().unwrap()), Some(Index::FromStart(4.into())), Step::Backward(5.try_into().unwrap()); "test c4cm5")] + #[test_case(":-4:-5", Index::FromEnd(1.try_into().unwrap()), Some(Index::FromEnd(4.try_into().unwrap())), Step::Backward(5.try_into().unwrap()); "test cm4cm5")] #[test_case("3::5", Index::FromStart(3.into()), None, Step::Forward(5.into()); "test 3cc5")] #[test_case("-3::5", Index::FromEnd(3.try_into().unwrap()), None, Step::Forward(5.into()); "test m3cc5")] #[test_case("3::-5", Index::FromStart(3.into()), None, Step::Backward(5.try_into().unwrap()); "test 3ccm5")] @@ -1039,9 +1044,12 @@ mod tests { #[test_case(":4", Index::FromStart(0.into()), Some(Index::FromStart(4.into())), Step::Forward(1.into()); "test c4")] #[test_case(":-4", Index::FromStart(0.into()), Some(Index::FromEnd(4.try_into().unwrap())), Step::Forward(1.into()); "test cm4")] #[test_case("::5", Index::FromStart(0.into()), None, Step::Forward(5.into()); "test cc5")] - #[test_case("::-5", Index::FromStart(0.into()), None, Step::Backward(5.try_into().unwrap()); "test ccm5")] + #[test_case("::-5", Index::FromEnd(1.try_into().unwrap()), None, Step::Backward(5.try_into().unwrap()); "test ccm5")] #[test_case("::", Index::FromStart(0.into()), None, Step::Forward(1.into()); "test cc")] - fn full_positive_slice(input: &str, exp_start: Index, exp_end: Option, exp_step: Step) { + #[test_case("::-1", Index::FromEnd(1.try_into().unwrap()), None, Step::Backward(1.try_into().unwrap()); "test ccm1")] + #[test_case("0::-1", Index::FromStart(0.into()), None, Step::Backward(1.try_into().unwrap()); "test 0ccm1")] + #[test_case("0:0:-1", Index::FromStart(0.into()), Some(Index::FromStart(0.into())), Step::Backward(1.try_into().unwrap()); "test 0c0cm1")] + fn slice(input: &str, exp_start: Index, exp_end: Option, exp_step: Step) { let (rest, selector) = super::slice_selector(input).expect("should parse"); assert_eq!("", rest); match selector {