diff --git a/jaq-core/src/compile.rs b/jaq-core/src/compile.rs index 13ede0fdd..2365d9196 100644 --- a/jaq-core/src/compile.rs +++ b/jaq-core/src/compile.rs @@ -68,6 +68,8 @@ pub(crate) enum Tailrec { pub(crate) enum Term { /// Identity (`.`) Id, + /// Recursion (`..`) + Recurse, ToString, Int(isize), @@ -627,7 +629,7 @@ impl<'s, F> Compiler<&'s str, F> { use parse::Term::*; match t { Id => Term::Id, - Recurse => self.term(Call("!recurse", Vec::new()), &Tr::new()), + Recurse => Term::Recurse, Arr(t) => Term::Arr(self.iterm(t.map_or_else(|| Call("!empty", Vec::new()), |t| *t))), Neg(t) => Term::Neg(self.iterm(*t)), Pipe(l, None, r) => Term::Pipe(self.iterm(*l), None, self.iterm_tr(*r, tr)), diff --git a/jaq-core/src/filter.rs b/jaq-core/src/filter.rs index d2c636659..e5176a589 100644 --- a/jaq-core/src/filter.rs +++ b/jaq-core/src/filter.rs @@ -249,6 +249,25 @@ fn lazy_is_lazy() { assert_eq!(iter.next(), Some(0)); } +/// Runs `def recurse: ., (.[]? | recurse); v | recurse`. +fn recurse_run<'a, V: ValT + 'a>(v: V) -> ValXs<'a, V> { + let id = core::iter::once(Ok(v.clone())); + Box::new(id.chain(v.values().flat_map(Result::ok).flat_map(recurse_run))) +} + +/// Runs `def recurse: (.[]? | recurse), .; v | recurse |= f`. +/// +/// This uses a `recurse` different from that of `recurse_run`; in particular, +/// `recurse_run` yields values from root to leafs, whereas +/// this function performs updates from leafs to root. +/// Performing updates from root to leafs can easily lead to +/// nontermination or other weird effects, such as +/// trying to update values that have been deleted. +fn recurse_update<'a, V: ValT + 'a>(v: V, f: &dyn Update<'a, V>) -> ValXs<'a, V> { + use crate::path::Opt::Optional; + box_iter::then(v.map_values(Optional, |v| recurse_update(v, f)), f) +} + /// Combination of context and input value. pub type Cv<'c, V> = (Ctx<'c, V>, V); @@ -309,6 +328,7 @@ impl> FilterT for Id { use core::iter::once; match &lut.terms[self.0] { Ast::Id => box_once(Ok(cv.1)), + Ast::Recurse => recurse_run(cv.1), Ast::ToString => box_once(match cv.1.as_str() { Some(_) => Ok(cv.1), None => Ok(Self::V::from(cv.1.to_string())), @@ -485,6 +505,7 @@ impl> FilterT for Id { Ast::TryCatch(..) | Ast::Label(..) => err, Ast::Id => f(cv.1), + Ast::Recurse => recurse_update(cv.1, &f), Ast::Path(l, path) => { let path = path.map_ref(|i| { let cv = cv.clone(); diff --git a/jaq-core/src/load/mod.rs b/jaq-core/src/load/mod.rs index 496f8b432..b5b8525cb 100644 --- a/jaq-core/src/load/mod.rs +++ b/jaq-core/src/load/mod.rs @@ -175,10 +175,7 @@ impl<'s, P: Default> Loader<&'s str, P, ReadFn

> { /// /// The prelude is normally initialised with filters like `map` or `true`. pub fn new(prelude: impl IntoIterator>) -> Self { - let defs = [ - Def::new("!recurse", Vec::new(), Term::recurse("!recurse")), - Def::new("!empty", Vec::new(), Term::empty()), - ]; + let defs = [Def::new("!empty", Vec::new(), Term::empty())]; let prelude = Module { body: defs.into_iter().chain(prelude).collect(), diff --git a/jaq-core/src/load/parse.rs b/jaq-core/src/load/parse.rs index c08e65a06..28fb12d43 100644 --- a/jaq-core/src/load/parse.rs +++ b/jaq-core/src/load/parse.rs @@ -179,21 +179,6 @@ impl Term { Self::Str(None, [StrPart::Str(s)].into()) } - /// `..`, also known as `recurse/0`, is defined as `., (.[]? | ..)`. - pub(crate) fn recurse(recurse: S) -> Self { - // `[]?` - let path = (path::Part::Range(None, None), path::Opt::Optional); - // `.[]?` (returns array/object elements or nothing instead) - let path = Term::Path(Term::Id.into(), Path(Vec::from([path]))); - - // `..` - let f = Term::Call(recurse, Vec::new()); - // .[]? | .. - let pipe = Term::Pipe(path.into(), None, f.into()); - // ., (.[]? | ..) - Term::BinOp(Term::Id.into(), BinaryOp::Comma, pipe.into()) - } - /// `{}[]` returns zero values. pub(crate) fn empty() -> Self { // `[]` diff --git a/jaq-std/tests/defs.rs b/jaq-std/tests/defs.rs index 3452db4d5..e8a1b8e45 100644 --- a/jaq-std/tests/defs.rs +++ b/jaq-std/tests/defs.rs @@ -172,7 +172,7 @@ yields!(limit_inf_sumr, "[limit(3; 0 + recurse(.+1))]", [0, 1, 2]); yields!(limit_inf_path, "[limit(2; [1] | .[repeat(0)])]", [1, 1]); #[test] -fn recurse() { +fn recurse_obj() { let x = json!({"a":0,"b":[1]}); gives(x.clone(), "recurse", [x, json!(0), json!([1]), json!(1)]); @@ -181,19 +181,21 @@ fn recurse() { let y = [json!(2), json!(4), json!(16)]; gives(json!(2), "recurse(. * .; . < 20)", y); +} +#[test] +fn recurse_arr() { let x = json!([[[0], 1], 2, [3, [4]]]); let y = json!([[[1], 2], 3, [4, [5]]]); give(x.clone(), "(.. | scalars) |= .+1", y); let f = ".. |= if . < [] then .+1 else . + [42] end"; - let y = json!([[[1, 43], 2, 43], 3, [4, [5, 43], 43], 43]); - // jq gives: `[[[1, 42], 2, 42], 3, [4, [5, 42], 42], 42]` + let y = json!([[[1, 42], 2, 42], 3, [4, [5, 42], 42], 42]); give(x.clone(), f, y); let f = ".. |= if . < [] then .+1 else [42] + . end"; - let y = json!([43, [43, [43, 1], 2], 3, [43, 4, [43, 5]]]); + let y = json!([42, [42, [42, 1], 2], 3, [42, 4, [42, 5]]]); // jq fails here with: "Cannot index number with number" give(x.clone(), f, y); }