From 1397cafb546c42daff1b9d1d3d1d4dbe2695dbe5 Mon Sep 17 00:00:00 2001 From: Marko Mikulicic Date: Thu, 1 Oct 2020 18:14:05 +0200 Subject: [PATCH 1/2] Array indexing --- src/matchers.rs | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/matchers.rs b/src/matchers.rs index 288df1e..32a01bb 100644 --- a/src/matchers.rs +++ b/src/matchers.rs @@ -58,6 +58,40 @@ impl Matcher for Child { } } +/// Selects an array item by index. +/// +/// If the index is negative, it references element len-abs(index). +pub struct ArrayIndex { + index: i64, +} + +impl ArrayIndex { + pub fn new(index: i64) -> Self { + ArrayIndex { index } + } +} + +impl Matcher for ArrayIndex { + fn select<'a>(&self, node: &'a Value) -> Iter<'a> { + let len = if let Value::Array(a) = node { + a.len() + } else { + 0 + }; + let idx = if self.index >= 0 { + self.index as usize + } else { + let abs = (-self.index) as usize; + if abs < len { + len - abs + } else { + return Box::new(iter::empty()); + } + }; + Box::new(node.get(idx).into_iter()) + } +} + /// Applies a sequence of selectors on the same node and returns /// a concatenation of the results. pub struct Union { @@ -96,4 +130,44 @@ mod tests { let r: Vec<&Value> = s.select(&j).collect(); assert_eq!(format!("{:?}", r), "[Number(1), Number(2)]"); } + + #[test] + fn array_index() { + let s = ArrayIndex::new(1); + let j = json!([1, 2]); + let r: Vec<&Value> = s.select(&j).collect(); + assert_eq!(format!("{:?}", r), "[Number(2)]"); + } + + #[test] + fn array_index_zero() { + let s = ArrayIndex::new(0); + let j = json!([1, 2]); + let r: Vec<&Value> = s.select(&j).collect(); + assert_eq!(format!("{:?}", r), "[Number(1)]"); + } + + #[test] + fn array_index_oob() { + let s = ArrayIndex::new(4); + let j = json!([1, 2]); + let r: Vec<&Value> = s.select(&j).collect(); + assert_eq!(r.len(), 0); + } + + #[test] + fn array_index_negative() { + let s = ArrayIndex::new(-1); + let j = json!([1, 2]); + let r: Vec<&Value> = s.select(&j).collect(); + assert_eq!(format!("{:?}", r), "[Number(2)]"); + } + + #[test] + fn array_index_negative_oob() { + let s = ArrayIndex::new(-10); + let j = json!([1, 2]); + let r: Vec<&Value> = s.select(&j).collect(); + assert_eq!(r.len(), 0); + } } From bff8b3c38822ec93654f06affea4bccbec0cb3c4 Mon Sep 17 00:00:00 2001 From: Marko Mikulicic Date: Thu, 1 Oct 2020 14:11:04 +0200 Subject: [PATCH 2/2] Implement array access --- src/grammar.pest | 3 ++- src/matchers.rs | 23 +++++++++++++---------- src/parser.rs | 23 ++++++++++++++++++++--- tests/cts.json | 45 ++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 79 insertions(+), 15 deletions(-) diff --git a/src/grammar.pest b/src/grammar.pest index 8b4e767..476a6eb 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -18,8 +18,9 @@ char = { } union = { "[" ~ unionElement ~ ("," ~ unionElement)* ~ "]" } -unionElement = _{ unionChild } +unionElement = _{ unionChild | unionArrayIndex } // TODO: add unionArraySlice unionChild = { doubleQuotedString | singleQuotedString } +unionArrayIndex = { "-" ? ~ ( "0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* ) } doubleQuotedString = _{ "\"" ~ doubleInner ~ "\"" } doubleInner = @{ doubleChar* } diff --git a/src/matchers.rs b/src/matchers.rs index 32a01bb..1605656 100644 --- a/src/matchers.rs +++ b/src/matchers.rs @@ -73,20 +73,15 @@ impl ArrayIndex { impl Matcher for ArrayIndex { fn select<'a>(&self, node: &'a Value) -> Iter<'a> { - let len = if let Value::Array(a) = node { - a.len() - } else { - 0 - }; let idx = if self.index >= 0 { self.index as usize } else { - let abs = (-self.index) as usize; - if abs < len { - len - abs + let len = if let Value::Array(a) = node { + a.len() as i64 } else { - return Box::new(iter::empty()); - } + 0 + }; + (len + self.index) as usize }; Box::new(node.get(idx).into_iter()) } @@ -163,6 +158,14 @@ mod tests { assert_eq!(format!("{:?}", r), "[Number(2)]"); } + #[test] + fn array_index_negative_extreme() { + let s = ArrayIndex::new(-2); + let j = json!([1, 2]); + let r: Vec<&Value> = s.select(&j).collect(); + assert_eq!(format!("{:?}", r), "[Number(1)]"); + } + #[test] fn array_index_negative_oob() { let s = ArrayIndex::new(-10); diff --git a/src/parser.rs b/src/parser.rs index 7096728..454c48e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -75,10 +75,18 @@ fn parse_dot_child_matcher( fn parse_union(matcher_rule: pest::iterators::Pair) -> Vec> { let mut ms: Vec> = Vec::new(); for r in matcher_rule.into_inner() { - if let Rule::unionChild = r.as_rule() { - for m in parse_union_child(r) { - ms.push(m) + match r.as_rule() { + Rule::unionChild => { + for m in parse_union_child(r) { + ms.push(m) + } + } + Rule::unionArrayIndex => { + for m in parse_union_array_index(r) { + ms.push(m) + } } + _ => {} } } vec![Box::new(matchers::Union::new(ms))] @@ -102,6 +110,15 @@ fn parse_union_child(matcher_rule: pest::iterators::Pair) -> Vec, +) -> Vec> { + let mut ms: Vec> = Vec::new(); + let i = matcher_rule.as_str().parse().unwrap(); + ms.push(Box::new(matchers::ArrayIndex::new(i))); + ms +} + const ESCAPED: &str = "\"'\\/bfnrt"; const UNESCAPED: &str = "\"'\\/\u{0008}\u{000C}\u{000A}\u{000D}\u{0009}"; diff --git a/tests/cts.json b/tests/cts.json index ee6afd3..82419d4 100644 --- a/tests/cts.json +++ b/tests/cts.json @@ -461,5 +461,48 @@ "name": "union child, single quotes, incomplete escape", "selector": "$['\\']", "invalid_selector": true + }, { + "name": "union array access", + "selector": "$[0]", + "document": ["first", "second"], + "result": ["first"] + }, { + "name": "union array access, 1", + "selector": "$[1]", + "document": ["first", "second"], + "result": ["second"] + }, { + "name": "union array access, out of bound", + "selector": "$[2]", + "document": ["first", "second"], + "result": [] + }, { + "name": "union array access, negative", + "selector": "$[-1]", + "document": ["first", "second"], + "result": ["second"] + }, { + "name": "union array access, more negative", + "selector": "$[-2]", + "document": ["first", "second"], + "result": ["first"] + }, { + "name": "union array access, negative out of bound", + "selector": "$[-3]", + "document": ["first", "second"], + "result": [] + }, { + "name": "union array access, on object", + "selector": "$[0]", + "document": {"foo": 1}, + "result": [] + }, { + "name": "union array access, leading 0", + "selector": "$[01]", + "invalid_selector": true + }, { + "name": "union array access, leading -0", + "selector": "$[-01]", + "invalid_selector": true } -]} \ No newline at end of file +]}