Skip to content
This repository was archived by the owner on Feb 22, 2024. It is now read-only.

Commit 69d89cc

Browse files
authored
Merge pull request #15 from mkmik/array
Implement array indexing
2 parents 31c6896 + 78896cc commit 69d89cc

File tree

4 files changed

+143
-5
lines changed

4 files changed

+143
-5
lines changed

src/grammar.pest

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ char = {
1818
}
1919

2020
union = { "[" ~ unionElement ~ ("," ~ unionElement)* ~ "]" }
21-
unionElement = _{ unionChild }
21+
unionElement = _{ unionChild | unionArrayIndex } // TODO: add unionArraySlice
2222
unionChild = { doubleQuotedString | singleQuotedString }
23+
unionArrayIndex = { "-" ? ~ ( "0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* ) }
2324

2425
doubleQuotedString = _{ "\"" ~ doubleInner ~ "\"" }
2526
doubleInner = @{ doubleChar* }

src/matchers.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,35 @@ impl Matcher for Child {
5858
}
5959
}
6060

61+
/// Selects an array item by index.
62+
///
63+
/// If the index is negative, it references element len-abs(index).
64+
pub struct ArrayIndex {
65+
index: i64,
66+
}
67+
68+
impl ArrayIndex {
69+
pub fn new(index: i64) -> Self {
70+
ArrayIndex { index }
71+
}
72+
}
73+
74+
impl Matcher for ArrayIndex {
75+
fn select<'a>(&self, node: &'a Value) -> Iter<'a> {
76+
let idx = if self.index >= 0 {
77+
self.index as usize
78+
} else {
79+
let len = if let Value::Array(a) = node {
80+
a.len() as i64
81+
} else {
82+
0
83+
};
84+
(len + self.index) as usize
85+
};
86+
Box::new(node.get(idx).into_iter())
87+
}
88+
}
89+
6190
/// Applies a sequence of selectors on the same node and returns
6291
/// a concatenation of the results.
6392
pub struct Union {
@@ -96,4 +125,52 @@ mod tests {
96125
let r: Vec<&Value> = s.select(&j).collect();
97126
assert_eq!(format!("{:?}", r), "[Number(1), Number(2)]");
98127
}
128+
129+
#[test]
130+
fn array_index() {
131+
let s = ArrayIndex::new(1);
132+
let j = json!([1, 2]);
133+
let r: Vec<&Value> = s.select(&j).collect();
134+
assert_eq!(format!("{:?}", r), "[Number(2)]");
135+
}
136+
137+
#[test]
138+
fn array_index_zero() {
139+
let s = ArrayIndex::new(0);
140+
let j = json!([1, 2]);
141+
let r: Vec<&Value> = s.select(&j).collect();
142+
assert_eq!(format!("{:?}", r), "[Number(1)]");
143+
}
144+
145+
#[test]
146+
fn array_index_oob() {
147+
let s = ArrayIndex::new(4);
148+
let j = json!([1, 2]);
149+
let r: Vec<&Value> = s.select(&j).collect();
150+
assert_eq!(r.len(), 0);
151+
}
152+
153+
#[test]
154+
fn array_index_negative() {
155+
let s = ArrayIndex::new(-1);
156+
let j = json!([1, 2]);
157+
let r: Vec<&Value> = s.select(&j).collect();
158+
assert_eq!(format!("{:?}", r), "[Number(2)]");
159+
}
160+
161+
#[test]
162+
fn array_index_negative_extreme() {
163+
let s = ArrayIndex::new(-2);
164+
let j = json!([1, 2]);
165+
let r: Vec<&Value> = s.select(&j).collect();
166+
assert_eq!(format!("{:?}", r), "[Number(1)]");
167+
}
168+
169+
#[test]
170+
fn array_index_negative_oob() {
171+
let s = ArrayIndex::new(-10);
172+
let j = json!([1, 2]);
173+
let r: Vec<&Value> = s.select(&j).collect();
174+
assert_eq!(r.len(), 0);
175+
}
99176
}

src/parser.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,18 @@ fn parse_dot_child_matcher(
7575
fn parse_union(matcher_rule: pest::iterators::Pair<Rule>) -> Vec<Box<dyn matchers::Matcher>> {
7676
let mut ms: Vec<Box<dyn matchers::Matcher>> = Vec::new();
7777
for r in matcher_rule.into_inner() {
78-
if let Rule::unionChild = r.as_rule() {
79-
for m in parse_union_child(r) {
80-
ms.push(m)
78+
match r.as_rule() {
79+
Rule::unionChild => {
80+
for m in parse_union_child(r) {
81+
ms.push(m)
82+
}
83+
}
84+
Rule::unionArrayIndex => {
85+
for m in parse_union_array_index(r) {
86+
ms.push(m)
87+
}
8188
}
89+
_ => {}
8290
}
8391
}
8492
vec![Box::new(matchers::Union::new(ms))]
@@ -102,6 +110,15 @@ fn parse_union_child(matcher_rule: pest::iterators::Pair<Rule>) -> Vec<Box<dyn m
102110
ms
103111
}
104112

113+
fn parse_union_array_index(
114+
matcher_rule: pest::iterators::Pair<Rule>,
115+
) -> Vec<Box<dyn matchers::Matcher>> {
116+
let mut ms: Vec<Box<dyn matchers::Matcher>> = Vec::new();
117+
let i = matcher_rule.as_str().parse().unwrap();
118+
ms.push(Box::new(matchers::ArrayIndex::new(i)));
119+
ms
120+
}
121+
105122
const ESCAPED: &str = "\"'\\/bfnrt";
106123
const UNESCAPED: &str = "\"'\\/\u{0008}\u{000C}\u{000A}\u{000D}\u{0009}";
107124

tests/cts.json

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,5 +461,48 @@
461461
"name": "union child, single quotes, incomplete escape",
462462
"selector": "$['\\']",
463463
"invalid_selector": true
464+
}, {
465+
"name": "union array access",
466+
"selector": "$[0]",
467+
"document": ["first", "second"],
468+
"result": ["first"]
469+
}, {
470+
"name": "union array access, 1",
471+
"selector": "$[1]",
472+
"document": ["first", "second"],
473+
"result": ["second"]
474+
}, {
475+
"name": "union array access, out of bound",
476+
"selector": "$[2]",
477+
"document": ["first", "second"],
478+
"result": []
479+
}, {
480+
"name": "union array access, negative",
481+
"selector": "$[-1]",
482+
"document": ["first", "second"],
483+
"result": ["second"]
484+
}, {
485+
"name": "union array access, more negative",
486+
"selector": "$[-2]",
487+
"document": ["first", "second"],
488+
"result": ["first"]
489+
}, {
490+
"name": "union array access, negative out of bound",
491+
"selector": "$[-3]",
492+
"document": ["first", "second"],
493+
"result": []
494+
}, {
495+
"name": "union array access, on object",
496+
"selector": "$[0]",
497+
"document": {"foo": 1},
498+
"result": []
499+
}, {
500+
"name": "union array access, leading 0",
501+
"selector": "$[01]",
502+
"invalid_selector": true
503+
}, {
504+
"name": "union array access, leading -0",
505+
"selector": "$[-01]",
506+
"invalid_selector": true
464507
}
465-
]}
508+
]}

0 commit comments

Comments
 (0)