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

Commit 3ee433e

Browse files
committed
Array slicing
1 parent 25d82b7 commit 3ee433e

File tree

6 files changed

+313
-8
lines changed

6 files changed

+313
-8
lines changed

DEVELOPING.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,25 @@ test to `true`, for example:
4747
</pre>
4848

4949
When one or more tests are focussed in this way, the test suite will fail with the message
50-
"testcase(s) still focussed" even if all the tests pass.
50+
"testcase(s) still focussed" if and only if all the focussed tests pass.
5151
This prevents pull requests being merged in which tests are accidentally left focussed.
5252

53+
To skip one or more tests, edit [cts.json](tests/cts.json) and set the `skip` property of the relevant
54+
test to `true`, for example:
55+
<pre>
56+
}, {
57+
"name": "wildcarded child",
58+
<b>"skip": true,</b>
59+
"selector": "$.*",
60+
"document": {"a" : "A", "b" : "B"},
61+
"result": ["A", "B"]
62+
}, {
63+
</pre>
64+
65+
When one or more tests are skipped in this way, the test suite will fail with the message
66+
"testcase(s) still skipped" if and only if all the tests pass and none are focussed.
67+
This prevents pull requests being merged in which tests are accidentally left skipped.
68+
5369
To see details of which tests run, use:
5470
```
5571
cargo test -- --show-output

src/ast.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
*/
66

77
use serde_json::Value;
8+
use std::cmp::Ordering;
9+
use std::iter;
810

911
/// A path is a tree of selector nodes.
1012
///
@@ -57,9 +59,17 @@ pub enum Selector {
5759
#[derive(Debug)]
5860
pub enum UnionElement {
5961
Name(String),
62+
Slice(Slice),
6063
Index(i64),
6164
}
6265

66+
#[derive(Debug)]
67+
pub struct Slice {
68+
pub start: Option<i64>, // FIXME: usize?
69+
pub end: Option<i64>, // FIXME: usize?
70+
pub step: Option<i64>, // FIXME: usize?
71+
}
72+
6373
type Iter<'a> = Box<dyn Iterator<Item = &'a Value> + 'a>;
6474

6575
impl Path {
@@ -89,11 +99,60 @@ impl UnionElement {
8999
pub fn get<'a>(&self, v: &'a Value) -> Iter<'a> {
90100
match self {
91101
UnionElement::Name(name) => Box::new(v.get(name).into_iter()),
102+
UnionElement::Slice(slice) => {
103+
if let Value::Array(arr) = v {
104+
let step = slice.step.unwrap_or(1);
105+
106+
let len = arr.len() as i64;
107+
108+
let start = slice
109+
.start
110+
.map(|s| if s < 0 { s + len } else { s })
111+
.unwrap_or(if step > 0 { 0 } else { len - 1 });
112+
113+
let end = slice
114+
.end
115+
.map(|e| if e < 0 { e + len } else { e })
116+
.unwrap_or(if step > 0 { len } else { -1 });
117+
118+
Box::new(array_slice(arr, start, end, step, len))
119+
} else {
120+
Box::new(iter::empty())
121+
}
122+
}
92123
UnionElement::Index(num) => Box::new(v.get(abs_index(*num, v)).into_iter()),
93124
}
94125
}
95126
}
96127

128+
fn array_slice(arr: &[Value], start: i64, end: i64, step: i64, len: i64) -> Iter<'_> {
129+
let mut sl = vec![];
130+
match step.cmp(&0) {
131+
Ordering::Greater => {
132+
let st = if start < 0 { 0 } else { start }; // avoid CPU attack
133+
let e = if end > len { len } else { end }; // avoid CPU attack
134+
for i in (st..e).step_by(step as usize) {
135+
if 0 <= i && i < len {
136+
sl.push(&arr[i as usize]);
137+
}
138+
}
139+
}
140+
141+
Ordering::Less => {
142+
let strt = if start > len { len } else { start }; // avoid CPU attack
143+
let e = if end < -1 { -1 } else { end }; // avoid CPU attack
144+
for i in (-strt..-e).step_by(-step as usize) {
145+
if 0 <= -i && -i < len {
146+
sl.push(&arr[-i as usize]);
147+
}
148+
}
149+
}
150+
151+
Ordering::Equal => (),
152+
}
153+
Box::new(sl.into_iter())
154+
}
155+
97156
fn abs_index(index: i64, node: &Value) -> usize {
98157
if index >= 0 {
99158
index as usize

src/grammar.pest

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ selector = _{ SOI ~ rootSelector ~ matchers ~ EOI }
33
matchers = ${ matcher* }
44
rootSelector = { "$" }
55

6-
matcher = { dotChild | union }
6+
matcher = !{ dotChild | union }
77

88
dotChild = _{ wildcardedDotChild | namedDotChild }
99
wildcardedDotChild = { ".*" }
@@ -18,9 +18,14 @@ char = {
1818
}
1919

2020
union = { "[" ~ unionElement ~ ("," ~ unionElement)* ~ "]" }
21-
unionElement = _{ unionChild | unionArrayIndex } // TODO: add unionArraySlice
22-
unionChild = { doubleQuotedString | singleQuotedString }
23-
unionArrayIndex = { "-" ? ~ ( "0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* ) }
21+
unionElement = _{ unionChild | unionArraySlice | unionArrayIndex }
22+
unionChild = ${ doubleQuotedString | singleQuotedString }
23+
unionArrayIndex = @{ integer }
24+
integer = _{ "-" ? ~ ( "0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* ) }
25+
unionArraySlice = { sliceStart ? ~ ":" ~ sliceEnd ? ~ ( ":" ~ sliceStep ? ) ? }
26+
sliceStart = @{ integer }
27+
sliceEnd = @{ integer }
28+
sliceStep = @{ integer }
2429

2530
doubleQuotedString = _{ "\"" ~ doubleInner ~ "\"" }
2631
doubleInner = @{ doubleChar* }

src/parser.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ fn parse_union_indices(matcher_rule: pest::iterators::Pair<Rule>) -> Vec<UnionEl
5050
.into_inner()
5151
.map(|r| match r.as_rule() {
5252
Rule::unionChild => parse_union_child(r),
53+
Rule::unionArraySlice => parse_union_array_slice(r),
5354
Rule::unionArrayIndex => parse_union_array_index(r),
5455
_ => panic!("invalid parse tree {:?}", r),
5556
})
@@ -71,6 +72,31 @@ fn parse_union_array_index(matcher_rule: pest::iterators::Pair<Rule>) -> UnionEl
7172
UnionElement::Index(i)
7273
}
7374

75+
fn parse_union_array_slice(matcher_rule: pest::iterators::Pair<Rule>) -> UnionElement {
76+
let mut start: Option<i64> = None;
77+
let mut end: Option<i64> = None;
78+
let mut step: Option<i64> = None;
79+
for r in matcher_rule.into_inner() {
80+
match r.as_rule() {
81+
Rule::sliceStart => {
82+
start = Some(r.as_str().parse().unwrap());
83+
}
84+
85+
Rule::sliceEnd => {
86+
end = Some(r.as_str().parse().unwrap());
87+
}
88+
89+
Rule::sliceStep => {
90+
step = Some(r.as_str().parse().unwrap());
91+
}
92+
93+
_ => panic!("invalid parse tree {:?}", r),
94+
}
95+
}
96+
97+
UnionElement::Slice(Slice { start, end, step })
98+
}
99+
74100
fn unescape(contents: &str) -> String {
75101
let s = format!(r#""{}""#, contents);
76102
serde_json::from_str(&s).unwrap()

tests/cts.json

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,20 @@
481481
"name": "union child, single quotes, incomplete escape",
482482
"selector": "$['\\']",
483483
"invalid_selector": true
484+
}, {
485+
"name": "union",
486+
"selector": "$[0,2]",
487+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
488+
"result": [0, 2]
489+
}, {
490+
"name": "union with whitespace",
491+
"selector": "$[ 0 , 1 ]",
492+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
493+
"result": [0, 1]
494+
}, {
495+
"name": "empty union",
496+
"selector": "$[]",
497+
"invalid_selector": true
484498
}, {
485499
"name": "union array access",
486500
"selector": "$[0]",
@@ -524,5 +538,183 @@
524538
"name": "union array access, leading -0",
525539
"selector": "$[-01]",
526540
"invalid_selector": true
527-
}
541+
}, {
542+
"name": "union array slice",
543+
"selector": "$[1:3]",
544+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
545+
"result": [1, 2]
546+
}, {
547+
"name": "union array slice with step",
548+
"selector": "$[1:6:2]",
549+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
550+
"result": [1, 3, 5]
551+
}, {
552+
"name": "union array slice with everything omitted, short form",
553+
"selector": "$[:]",
554+
"document": [0, 1, 2, 3],
555+
"result": [0, 1, 2, 3]
556+
}, {
557+
"name": "union array slice with everything omitted, long form",
558+
"selector": "$[::]",
559+
"document": [0, 1, 2, 3],
560+
"result": [0, 1, 2, 3]
561+
}, {
562+
"name": "union array slice with start omitted",
563+
"selector": "$[:2]",
564+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
565+
"result": [0, 1]
566+
}, {
567+
"name": "union array slice with start and end omitted",
568+
"selector": "$[::2]",
569+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
570+
"result": [0, 2, 4, 6, 8]
571+
}, {
572+
"name": "union array slice, last index",
573+
"selector": "$[-1]",
574+
"document": [0, 1, 2, 3],
575+
"result": [3]
576+
}, {
577+
"name": "union array slice, overflowed index",
578+
"selector": "$[4]",
579+
"document": [0, 1, 2, 3],
580+
"result": []
581+
}, {
582+
"name": "union array slice, underflowed index",
583+
"selector": "$[-5]",
584+
"document": [0, 1, 2, 3],
585+
"result": []
586+
}, {
587+
"name": "union array slice, negative step with default start and end",
588+
"selector": "$[::-1]",
589+
"document": [0, 1, 2, 3],
590+
"result": [3, 2, 1, 0]
591+
}, {
592+
"name": "union array slice, negative step with default start",
593+
"selector": "$[:0:-1]",
594+
"document": [0, 1, 2, 3],
595+
"result": [3, 2, 1]
596+
}, {
597+
"name": "union array slice, negative step with default end",
598+
"selector": "$[2::-1]",
599+
"document": [0, 1, 2, 3],
600+
"result": [2, 1, 0]
601+
}, {
602+
"name": "union array slice, larger negative step",
603+
"selector": "$[::-2]",
604+
"document": [0, 1, 2, 3],
605+
"result": [3, 1]
606+
}, {
607+
"name": "union array slice, negative range with default step",
608+
"selector": "$[-1:-3]",
609+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
610+
"result": []
611+
}, {
612+
"name": "union array slice, negative range with negative step",
613+
"selector": "$[-1:-3:-1]",
614+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
615+
"result": [9, 8]
616+
}, {
617+
"name": "union array slice, negative range with larger negative step",
618+
"selector": "$[-1:-6:-2]",
619+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
620+
"result": [9, 7, 5]
621+
}, {
622+
"name": "union array slice, larger negative range with larger negative step",
623+
"selector": "$[-1:-7:-2]",
624+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
625+
"result": [9, 7, 5]
626+
}, {
627+
"name": "union array slice, negative from, positive to",
628+
"selector": "$[-5:7]",
629+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
630+
"result": [5, 6]
631+
}, {
632+
"name": "union array slice, negative from",
633+
"selector": "$[-2:]",
634+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
635+
"result": [8, 9]
636+
}, {
637+
"name": "union array slice, positive from, negative to",
638+
"selector": "$[1:-1]",
639+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
640+
"result": [1, 2, 3, 4, 5, 6, 7, 8]
641+
}, {
642+
"name": "union array slice, negative from, positive to, negative step",
643+
"selector": "$[-1:1:-1]",
644+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
645+
"result": [9, 8, 7, 6, 5, 4, 3, 2]
646+
}, {
647+
"name": "union array slice, positive from, negative to, negative step",
648+
"selector": "$[7:-5:-1]",
649+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
650+
"result": [7, 6]
651+
}, {
652+
"name": "union array slice, too many colons",
653+
"selector": "$[1:2:3:4]",
654+
"invalid_selector": true
655+
}, {
656+
"name": "union array slice, non-integer array index",
657+
"selector": "$[1:2:a]",
658+
"invalid_selector": true
659+
}, {
660+
"name": "union array slice, zero step",
661+
"selector": "$[1:2:0]",
662+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
663+
"result": []
664+
}, {
665+
"name": "union array slice, empty range",
666+
"selector": "$[2:2]",
667+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
668+
"result": []
669+
}, {
670+
"name": "union array slice, default indices with empty array",
671+
"selector": "$[:]",
672+
"document": [],
673+
"result": []
674+
}, {
675+
"name": "union array slice, negative step with empty array",
676+
"selector": "$[::-1]",
677+
"document": [],
678+
"result": []
679+
}, {
680+
"name": "union array slice, maximal range with positive step",
681+
"selector": "$[0:10]",
682+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
683+
"result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
684+
}, {
685+
"name": "union array slice, maximal range with negative step",
686+
"selector": "$[9:0:-1]",
687+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
688+
"result": [9, 8, 7, 6, 5, 4, 3, 2, 1]
689+
}, {
690+
"name": "union array slice, excessively large to value",
691+
"selector": "$[2:113667776004]",
692+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
693+
"result": [2, 3, 4, 5, 6, 7, 8, 9]
694+
}, {
695+
"name": "union array slice, excessively small from value",
696+
"selector": "$[-113667776004:1]",
697+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
698+
"result": [0]
699+
}, {
700+
"name": "union array slice, excessively large from value with negative step",
701+
"selector": "$[113667776004:0:-1]",
702+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
703+
"result": [9, 8, 7, 6, 5, 4, 3, 2, 1]
704+
}, {
705+
"name": "union array slice, excessively small to value with negative step",
706+
"selector": "$[3:-113667776004:-1]",
707+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
708+
"result": [3, 2, 1, 0]
709+
}, {
710+
"name": "union array slice, excessively large step",
711+
"selector": "$[1:10:113667776004]",
712+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
713+
"result": [1]
714+
}, {
715+
"name": "union array slice, excessively small step",
716+
"selector": "$[-1:-10:-113667776004]",
717+
"document": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
718+
"result": [9]
719+
}
528720
]}

0 commit comments

Comments
 (0)