Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion jaq-core/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ pub(crate) enum Tailrec {
pub(crate) enum Term<T = TermId> {
/// Identity (`.`)
Id,
/// Recursion (`..`)
Recurse,
ToString,

Int(isize),
Expand Down Expand Up @@ -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)),
Expand Down
21 changes: 21 additions & 0 deletions jaq-core/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -309,6 +328,7 @@ impl<F: FilterT<F>> FilterT<F> 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())),
Expand Down Expand Up @@ -485,6 +505,7 @@ impl<F: FilterT<F>> FilterT<F> 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();
Expand Down
5 changes: 1 addition & 4 deletions jaq-core/src/load/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,7 @@ impl<'s, P: Default> Loader<&'s str, P, ReadFn<P>> {
///
/// The prelude is normally initialised with filters like `map` or `true`.
pub fn new(prelude: impl IntoIterator<Item = Def<&'s str>>) -> 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(),
Expand Down
15 changes: 0 additions & 15 deletions jaq-core/src/load/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,21 +179,6 @@ impl<S> Term<S> {
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 {
// `[]`
Expand Down
10 changes: 6 additions & 4 deletions jaq-std/tests/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]);

Expand All @@ -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);
}
Expand Down