diff --git a/jsonpath-compliance-test-suite b/jsonpath-compliance-test-suite index 9235770..b139469 160000 --- a/jsonpath-compliance-test-suite +++ b/jsonpath-compliance-test-suite @@ -1 +1 @@ -Subproject commit 923577060d75a8111fb3a91bdc0ab8eb2d748f1b +Subproject commit b139469a6bdd83f19ad9e544c4c14db09162f580 diff --git a/src/ast.rs b/src/ast.rs index 14679e7..785c6c2 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -54,6 +54,9 @@ pub enum Selector { Union(Vec), DotName(String), DotWildcard, + DescendantDotName(String), + DescendantDotWildcard, + DescendantUnion(Vec), } #[derive(Debug)] @@ -85,6 +88,34 @@ impl Selector { Value::Array(a) => Box::new(a.iter()), _ => Box::new(std::iter::empty()), }, + Selector::DescendantDotName(name) => { + Self::traverse(input, move |n: &'a Value| Box::new(n.get(name).into_iter())) + } + Selector::DescendantDotWildcard => { + Self::traverse(input, move |n: &'a Value| Box::new(iter::once(n))) + } + Selector::DescendantUnion(indices) => Self::traverse(input, move |n: &'a Value| { + Box::new(indices.iter().flat_map(move |i| i.find(n))) + }), + } + } + + // traverse applies the given closure to all the descendants of the input value and + // returns a nodelist. + fn traverse<'a, F>(input: &'a Value, f: F) -> NodeList<'a> + where + F: Fn(&'a Value) -> NodeList<'a> + Copy + 'a, + { + match input { + Value::Object(m) => Box::new( + m.into_iter() + .flat_map(move |(_k, v)| f(v).chain(Self::traverse::<'a>(v, f))), + ), + Value::Array(a) => Box::new( + a.iter() + .flat_map(move |v| f(v).chain(Self::traverse::<'a>(v, f))), + ), + _ => Box::new(std::iter::empty()), } } } diff --git a/src/grammar.pest b/src/grammar.pest index 40caaf2..64fc5f9 100644 --- a/src/grammar.pest +++ b/src/grammar.pest @@ -3,7 +3,7 @@ selector = ${ SOI ~ rootSelector ~ matchers ~ EOI } matchers = ${ matcher* } rootSelector = @{ "$" } -matcher = !{ dotChild | union } +matcher = !{ dotChild | union | wildcardedIndex | descendant } dotChild = _{ wildcardedDotChild | namedDotChild } wildcardedDotChild = { ".*" } @@ -17,7 +17,7 @@ char = { | '\u{80}'..'\u{10FFFF}' } -union = { "[" ~ unionElement ~ ("," ~ unionElement)* ~ "]" } +union = !{ "[" ~ unionElement ~ ("," ~ unionElement)* ~ "]" } unionElement = _{ unionChild | unionArraySlice | unionArrayIndex } unionChild = ${ doubleQuotedString | singleQuotedString } unionArrayIndex = @{ integer } @@ -27,6 +27,12 @@ sliceStart = @{ integer } sliceEnd = @{ integer } sliceStep = @{ integer } +wildcardedIndex = { "[" ~ "*" ~ "]" } + +descendant = ${ ".." ~ descendantVariant } +descendantVariant = _{ childName | wildcard | "[" ~ wildcard ~ "]" | union } +wildcard = { "*" } + doubleQuotedString = _{ "\"" ~ doubleInner ~ "\"" } doubleInner = @{ doubleChar* } doubleChar = { diff --git a/src/parser.rs b/src/parser.rs index 8032499..3fb6855 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -40,6 +40,8 @@ fn parse_selector(matcher_rule: pest::iterators::Pair) -> Result Selector::DotWildcard, Rule::namedDotChild => Selector::DotName(parse_child_name(r)), Rule::union => Selector::Union(parse_union_indices(r)?), + Rule::wildcardedIndex => Selector::DotWildcard, + Rule::descendant => parse_descendant(r)?, _ => panic!("invalid parse tree {:?}", r), }) } @@ -115,6 +117,16 @@ fn parse_union_array_slice( })) } +fn parse_descendant(matcher_rule: pest::iterators::Pair) -> Result { + let r = matcher_rule.into_inner().next().unwrap(); + + Ok(match r.as_rule() { + Rule::childName => Selector::DescendantDotName(r.as_str().to_owned()), + Rule::wildcard => Selector::DescendantDotWildcard, + _ => Selector::DescendantUnion(parse_union_indices(r)?), + }) +} + fn unescape(contents: &str) -> String { let s = format!(r#""{}""#, contents); serde_json::from_str(&s).unwrap() diff --git a/tests/cts.rs b/tests/cts.rs index 865aa1c..5e235ee 100644 --- a/tests/cts.rs +++ b/tests/cts.rs @@ -90,10 +90,18 @@ mod tests { assert!(false, "find failed") // should not happen } } else { - if !t.invalid_selector { + if t.invalid_selector { + // print failure message + println!( + "{}: parsing `{}` failed with: {}", + t.name, + t.selector, + path.err().expect("should be an error") + ); + } else { assert!( path.is_ok(), - "{}: parsing {} should have succeeded but failed: {}", + "{}: parsing `{}` should have succeeded but failed: {}", t.name, t.selector, path.err().expect("should be an error")