diff --git a/jaq-interpret/tests/path.rs b/jaq-interpret/tests/path.rs index 900457f04..63402d3a8 100644 --- a/jaq-interpret/tests/path.rs +++ b/jaq-interpret/tests/path.rs @@ -41,6 +41,8 @@ fn index_access() { #[test] fn iter_access() { gives(json!([0, 1, 2]), ".[]", [json!(0), json!(1), json!(2)]); + gives(json!({"a": [1, 2]}), ".a[]", [json!(1), json!(2)]); + gives(json!({"a": [1, 2]}), ".a.[]", [json!(1), json!(2)]); gives(json!({"a": 1, "b": 2}), ".[]", [json!(1), json!(2)]); // TODO: correct this //gives(json!({"b": 2, "a": 1}), ".[]", [json!(2), json!(1)]); diff --git a/jaq-parse/src/filter.rs b/jaq-parse/src/filter.rs index df9370b88..88ec5e301 100644 --- a/jaq-parse/src/filter.rs +++ b/jaq-parse/src/filter.rs @@ -208,9 +208,8 @@ pub fn filter() -> impl Parser, Error = Simple> + // e.g. `.[].a` or `.a` let id = just(Token::Dot).map_with_span(|_, span| (Filter::Id, span)); - let index = super::path::index(with_comma.clone()); - let index_path = index.or_not().chain(atom_path()); - let id_with_path = id.then(index_path.collect()); + let id_path = super::path::part(with_comma.clone()).chain(atom_path()); + let id_with_path = id.then(id_path.or_not().flatten()); let path = atom_with_path.or(id_with_path); diff --git a/jaq-parse/src/path.rs b/jaq-parse/src/path.rs index 856e1ea06..5cd151e0f 100644 --- a/jaq-parse/src/path.rs +++ b/jaq-parse/src/path.rs @@ -22,43 +22,52 @@ where .labelled("object key") } -pub fn index(expr: P) -> impl Parser>, Opt), Error = P::Error> + Clone +fn index(expr: P) -> impl Parser>, Error = P::Error> + Clone where T: From>> + From>>, P: Parser, Error = Simple> + Clone, { - key(expr) - .map_with_span(|id, span| Part::Index((T::from(id), span))) - .then(opt()) + key(expr).map_with_span(|id, span| Part::Index((T::from(id), span))) } -pub fn path(expr: P) -> impl Parser, Error = P::Error> + Clone +/// Match `[]`, `[e]`, `[e:]`, `[e:e]`, `[:e]` (all without brackets). +fn range(expr: P) -> impl Parser>, Error = P::Error> + Clone where - T: From>> + From>>, P: Parser, Error = Simple> + Clone, { - let range = { - let e2 = just(Token::Colon).ignore_then(expr.clone().or_not()); - let starts_with_expr = expr.clone().then(e2.or_not()).map(|(e1, e2)| match e2 { - None => Part::Index(e1), - Some(e2) => Part::Range(Some(e1), e2), - }); - let starts_with_colon = just(Token::Colon) - .ignore_then(expr.clone()) - .map(|e2| Part::Range(None, Some(e2))); + let e2 = just(Token::Colon).ignore_then(expr.clone().or_not()); + let starts_with_expr = expr.clone().then(e2.or_not()).map(|(e1, e2)| match e2 { + None => Part::Index(e1), + Some(e2) => Part::Range(Some(e1), e2), + }); + let starts_with_colon = just(Token::Colon) + .ignore_then(expr.clone()) + .map(|e2| Part::Range(None, Some(e2))); - starts_with_expr - .or(starts_with_colon) - .or_not() - .map(|o| o.unwrap_or(Part::Range(None, None))) - }; - - let ranges = Delim::Brack.around(range).then(opt()).repeated(); + starts_with_expr + .or(starts_with_colon) + .or_not() + .map(|o| o.unwrap_or(Part::Range(None, None))) +} - let dot_id = just(Token::Dot).ignore_then(index(expr)); +/// A path after an atomic filter (that is not the identity filter). +pub fn path(expr: P) -> impl Parser, Error = P::Error> + Clone +where + T: From>> + From>>, + P: Parser, Error = Simple> + Clone, +{ + let range = Delim::Brack.around(range(expr.clone())); + let dot_index = just(Token::Dot).ignore_then(index(expr)); + let dot_range = just(Token::Dot).or_not().ignore_then(range); + dot_index.or(dot_range).then(opt()).repeated() +} - ranges - .clone() - .chain(dot_id.chain(ranges).repeated().flatten()) - .collect() +/// The first part of a path after an identity filter. +pub fn part(expr: P) -> impl Parser>, Opt), Error = P::Error> + Clone +where + T: From>> + From>>, + P: Parser, Error = Simple> + Clone, +{ + let range = Delim::Brack.around(range(expr.clone())); + range.or(index(expr)).then(opt()) }