Slice arrays
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
use std::{fmt, str::FromStr};
|
use std::{fmt, str::FromStr};
|
||||||
|
|
||||||
use serde::{
|
use serde::{
|
||||||
Deserialize, Deserializer,
|
Deserialize, Deserializer, Serialize, Serializer,
|
||||||
de::{self, Visitor},
|
de::{self, Visitor},
|
||||||
};
|
};
|
||||||
use smartstring::{LazyCompact, SmartString};
|
use smartstring::{LazyCompact, SmartString};
|
||||||
@@ -49,6 +49,13 @@ pub enum PathSegment {
|
|||||||
|
|
||||||
/// Go to an element of the current list
|
/// Go to an element of the current list
|
||||||
Index(i64),
|
Index(i64),
|
||||||
|
|
||||||
|
/// Go to a slice of the current list
|
||||||
|
Range {
|
||||||
|
start: i64,
|
||||||
|
end: i64,
|
||||||
|
inclusive: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A path to aPathSegment::Field inside a nested object,
|
/// A path to aPathSegment::Field inside a nested object,
|
||||||
@@ -63,6 +70,39 @@ pub struct ObjectPath {
|
|||||||
pub segments: Vec<PathSegment>,
|
pub segments: Vec<PathSegment>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ObjectPath {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
for seg in &self.segments {
|
||||||
|
match seg {
|
||||||
|
PathSegment::Root => write!(f, "$")?,
|
||||||
|
PathSegment::Field { name, args: None } => write!(f, ".{name}")?,
|
||||||
|
PathSegment::Field {
|
||||||
|
name,
|
||||||
|
args: Some(a),
|
||||||
|
} => write!(f, ".{name}({a})")?,
|
||||||
|
PathSegment::Index(i) => write!(f, "[{i}]")?,
|
||||||
|
PathSegment::Range {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
inclusive: false,
|
||||||
|
} => write!(f, "[{start}..{end}]")?,
|
||||||
|
PathSegment::Range {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
inclusive: true,
|
||||||
|
} => write!(f, "[{start}..={end}]")?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for ObjectPath {
|
||||||
|
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for ObjectPath {
|
impl<'de> Deserialize<'de> for ObjectPath {
|
||||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
struct PathVisitor;
|
struct PathVisitor;
|
||||||
|
|||||||
@@ -87,6 +87,15 @@ enum State {
|
|||||||
/// We are indexing an array, waiting for a number
|
/// We are indexing an array, waiting for a number
|
||||||
Index,
|
Index,
|
||||||
|
|
||||||
|
/// We parsed the start index, waiting for `]` or the first `.` of `..`
|
||||||
|
IndexAfterStart(i64),
|
||||||
|
|
||||||
|
/// We saw one `.` after the start index, waiting for the second `.`
|
||||||
|
IndexRangeDot1(i64),
|
||||||
|
|
||||||
|
/// We saw `..`, waiting for the end index (optionally prefixed with `=`)
|
||||||
|
IndexRangeDot2(i64),
|
||||||
|
|
||||||
/// We are indexing an array, waiting for a close-bracket
|
/// We are indexing an array, waiting for a close-bracket
|
||||||
IndexClose,
|
IndexClose,
|
||||||
}
|
}
|
||||||
@@ -164,8 +173,7 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
self.segments.push(PathSegment::Index(idx));
|
self.state = State::IndexAfterStart(idx);
|
||||||
self.state = State::IndexClose;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(State::Index, (p, Token::Root))
|
(State::Index, (p, Token::Root))
|
||||||
@@ -175,6 +183,49 @@ impl Parser {
|
|||||||
return Err(PathParseError::Syntax { position: *p });
|
return Err(PathParseError::Syntax { position: *p });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(State::IndexAfterStart(idx), (_, Token::SqbClose)) => {
|
||||||
|
self.segments.push(PathSegment::Index(idx));
|
||||||
|
self.state = State::Selected;
|
||||||
|
}
|
||||||
|
(State::IndexAfterStart(idx), (_, Token::Dot)) => {
|
||||||
|
self.state = State::IndexRangeDot1(idx);
|
||||||
|
}
|
||||||
|
(State::IndexAfterStart(_), (p, _)) => {
|
||||||
|
return Err(PathParseError::Syntax { position: *p });
|
||||||
|
}
|
||||||
|
|
||||||
|
(State::IndexRangeDot1(idx), (_, Token::Dot)) => {
|
||||||
|
self.state = State::IndexRangeDot2(idx);
|
||||||
|
}
|
||||||
|
(State::IndexRangeDot1(_), (p, _)) => {
|
||||||
|
return Err(PathParseError::Syntax { position: *p });
|
||||||
|
}
|
||||||
|
|
||||||
|
(State::IndexRangeDot2(start), (p, Token::Ident(ident))) => {
|
||||||
|
let (end_str, inclusive) = if let Some(stripped) = ident.strip_prefix('=') {
|
||||||
|
(stripped, true)
|
||||||
|
} else {
|
||||||
|
(*ident, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
let end: i64 = i64::from_str(end_str).map_err(|_err| {
|
||||||
|
PathParseError::InvalidIndexString {
|
||||||
|
position: *p,
|
||||||
|
str: (*ident).into(),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self.segments.push(PathSegment::Range {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
inclusive,
|
||||||
|
});
|
||||||
|
self.state = State::IndexClose;
|
||||||
|
}
|
||||||
|
(State::IndexRangeDot2(_), (p, _)) => {
|
||||||
|
return Err(PathParseError::Syntax { position: *p });
|
||||||
|
}
|
||||||
|
|
||||||
(State::IndexClose, (_, Token::SqbClose)) => self.state = State::Selected,
|
(State::IndexClose, (_, Token::SqbClose)) => self.state = State::Selected,
|
||||||
(State::IndexClose, (p, _)) => {
|
(State::IndexClose, (p, _)) => {
|
||||||
return Err(PathParseError::Syntax { position: *p });
|
return Err(PathParseError::Syntax { position: *p });
|
||||||
@@ -187,6 +238,9 @@ impl Parser {
|
|||||||
State::Start => Err(PathParseError::Syntax { position: 0 }),
|
State::Start => Err(PathParseError::Syntax { position: 0 }),
|
||||||
State::Dot => Err(PathParseError::Syntax { position }),
|
State::Dot => Err(PathParseError::Syntax { position }),
|
||||||
State::Index => Err(PathParseError::Syntax { position }),
|
State::Index => Err(PathParseError::Syntax { position }),
|
||||||
|
State::IndexAfterStart(_) => Err(PathParseError::Syntax { position }),
|
||||||
|
State::IndexRangeDot1(_) => Err(PathParseError::Syntax { position }),
|
||||||
|
State::IndexRangeDot2(_) => Err(PathParseError::Syntax { position }),
|
||||||
State::IndexClose => Err(PathParseError::Syntax { position }),
|
State::IndexClose => Err(PathParseError::Syntax { position }),
|
||||||
State::Selected => Ok(()),
|
State::Selected => Ok(()),
|
||||||
}?;
|
}?;
|
||||||
@@ -387,4 +441,46 @@ mod tests {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: range
|
||||||
|
|
||||||
|
fn range(start: i64, end: i64, inclusive: bool) -> PathSegment {
|
||||||
|
PathSegment::Range {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
inclusive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exclusive_range() {
|
||||||
|
parse_test(
|
||||||
|
"$.a[0..5]",
|
||||||
|
Ok(&[PathSegment::Root, field("a"), range(0, 5, false)]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inclusive_range() {
|
||||||
|
parse_test(
|
||||||
|
"$.a[1..=2]",
|
||||||
|
Ok(&[PathSegment::Root, field("a"), range(1, 2, true)]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn range_with_negative_end() {
|
||||||
|
parse_test(
|
||||||
|
"$.a[0..-1]",
|
||||||
|
Ok(&[PathSegment::Root, field("a"), range(0, -1, false)]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn range_with_negative_start() {
|
||||||
|
parse_test(
|
||||||
|
"$.a[-3..-1]",
|
||||||
|
Ok(&[PathSegment::Root, field("a"), range(-3, -1, false)]),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,6 +140,40 @@ impl PileValue {
|
|||||||
|
|
||||||
out = e.get(state, idx).await?;
|
out = e.get(state, idx).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PathSegment::Range {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
inclusive,
|
||||||
|
} => {
|
||||||
|
let e = match out.map(|x| x.list_extractor()) {
|
||||||
|
Some(e) => e,
|
||||||
|
None => {
|
||||||
|
out = None;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let len = e.len(state).await? as i64;
|
||||||
|
|
||||||
|
let start_idx = if *start >= 0 { *start } else { len + start };
|
||||||
|
let end_idx = if *end >= 0 { *end } else { len + end };
|
||||||
|
let end_idx = if *inclusive { end_idx + 1 } else { end_idx };
|
||||||
|
|
||||||
|
let start_idx = start_idx.max(0) as usize;
|
||||||
|
let end_idx = (end_idx.max(0) as usize).min(len as usize);
|
||||||
|
|
||||||
|
let mut items = Vec::new();
|
||||||
|
for i in start_idx..end_idx {
|
||||||
|
match e.get(state, i).await? {
|
||||||
|
Some(v) => items.push(v),
|
||||||
|
None => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: lazy view?
|
||||||
|
out = Some(PileValue::Array(Arc::new(items)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user