Skip to content

Commit a1fe7f8

Browse files
committed
Descendant selectors
1 parent e210fad commit a1fe7f8

File tree

4 files changed

+41
-3
lines changed

4 files changed

+41
-3
lines changed

src/ast.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ pub enum Selector {
5454
Union(Vec<UnionElement>),
5555
DotName(String),
5656
DotWildcard,
57+
DescendantDotName(String),
58+
DescendantDotWildcard,
59+
DescendantUnionElement(UnionElement),
5760
}
5861

5962
#[derive(Debug)]
@@ -85,6 +88,20 @@ impl Selector {
8588
Value::Array(a) => Box::new(a.iter()),
8689
_ => Box::new(std::iter::empty()),
8790
},
91+
Selector::DescendantDotName(name) => Self::traverse(input, move |n: &'a Value| Box::new(n.get(name).into_iter())),
92+
Selector::DescendantDotWildcard => Self::traverse(input, move |n: &'a Value| Box::new(iter::once(n))),
93+
Selector::DescendantUnionElement(element) => Self::traverse(input, move |n: &'a Value| element.find(n)),
94+
}
95+
}
96+
97+
// traverse applies the given closure to all the descendants of the input value and
98+
// returns a nodelist.
99+
fn traverse<'a, F>(input: &'a Value, f: F) -> NodeList<'a>
100+
where F: Fn(&'a Value) -> NodeList<'a> + Copy + 'a {
101+
match input {
102+
Value::Object(m) => Box::new(m.into_iter().flat_map(move |(_k, v)| (&f)(v).chain(Self::traverse::<'a>(v, f)))),
103+
Value::Array(a) => Box::new(a.iter().flat_map(move |v| (&f)(v).chain(Self::traverse::<'a>(v, f)))),
104+
_ => Box::new(std::iter::empty()),
88105
}
89106
}
90107
}

src/grammar.pest

Lines changed: 5 additions & 1 deletion
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 | descendant }
77

88
dotChild = _{ wildcardedDotChild | namedDotChild }
99
wildcardedDotChild = { ".*" }
@@ -27,6 +27,10 @@ sliceStart = @{ integer }
2727
sliceEnd = @{ integer }
2828
sliceStep = @{ integer }
2929

30+
descendant = ${ ".." ~ descendantVariant }
31+
descendantVariant = _{ childName | wildcard | "[" ~ unionElement ~ "]" }
32+
wildcard = { "*" }
33+
3034
doubleQuotedString = _{ "\"" ~ doubleInner ~ "\"" }
3135
doubleInner = @{ doubleChar* }
3236
doubleChar = {

src/parser.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ fn parse_selector(matcher_rule: pest::iterators::Pair<Rule>) -> Result<Selector,
4040
Rule::wildcardedDotChild => Selector::DotWildcard,
4141
Rule::namedDotChild => Selector::DotName(parse_child_name(r)),
4242
Rule::union => Selector::Union(parse_union_indices(r)?),
43+
Rule::descendant => parse_descendant(r)?,
4344
_ => panic!("invalid parse tree {:?}", r),
4445
})
4546
}
@@ -115,6 +116,19 @@ fn parse_union_array_slice(
115116
}))
116117
}
117118

119+
fn parse_descendant(matcher_rule: pest::iterators::Pair<Rule>) -> Result<Selector, ParseIntError> {
120+
let r = matcher_rule.into_inner().next().unwrap();
121+
122+
Ok(match r.as_rule() {
123+
Rule::childName => Selector::DescendantDotName(r.as_str().to_owned()),
124+
Rule::wildcard => Selector::DescendantDotWildcard,
125+
Rule::unionChild => Selector::DescendantUnionElement(parse_union_child(r)),
126+
Rule::unionArraySlice => Selector::DescendantUnionElement(parse_union_array_slice(r)?),
127+
Rule::unionArrayIndex => Selector::DescendantUnionElement(parse_union_array_index(r)?),
128+
_ => panic!("invalid descendant {:?}", r),
129+
})
130+
}
131+
118132
fn unescape(contents: &str) -> String {
119133
let s = format!(r#""{}""#, contents);
120134
serde_json::from_str(&s).unwrap()

tests/cts.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,13 @@ mod tests {
9090
assert!(false, "find failed") // should not happen
9191
}
9292
} else {
93-
if !t.invalid_selector {
93+
if t.invalid_selector {
94+
// print failure message
95+
println!("{}: parsing `{}` failed with: {}", t.name, t.selector, path.err().expect("should be an error"));
96+
} else {
9497
assert!(
9598
path.is_ok(),
96-
"{}: parsing {} should have succeeded but failed: {}",
99+
"{}: parsing `{}` should have succeeded but failed: {}",
97100
t.name,
98101
t.selector,
99102
path.err().expect("should be an error")

0 commit comments

Comments
 (0)