Skip to content

Commit

Permalink
Short-circuit on errors in try ... catch ....
Browse files Browse the repository at this point in the history
  • Loading branch information
01mf02 committed Sep 28, 2024
1 parent 72c1020 commit c83749a
Show file tree
Hide file tree
Showing 3 changed files with 15 additions and 36 deletions.
26 changes: 0 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -672,32 +672,6 @@ it requires completely separate logic from `foreach/2` and `reduce`
in both the parser and the interpreter.


## Error handling

In jq, the `try f catch g` expression breaks out of the `f` stream as
soon as an error occurs, ceding control to `g` after that. This is
mentioned in its manual as a possible mechanism for breaking out of
loops
([here](https://jqlang.github.io/jq/manual/#breaking-out-of-control-structures)). jaq
however doesn't interrupt the `f` stream, but instead sends _each_
error value emitted to the `g` filter; the result is a stream of
values emitted from `f` with values emitted from `g` interspersed
where errors occurred.

Consider the following example: this expression is `true` in jq,
because the first `error(2)` interrupts the stream:

```jq
[try (1, error(2), 3, error(4)) catch .] == [1, 2]
```

In jaq however, this holds:

```jq
[try (1, error(2), 3, error(4)) catch .] == [1, 2, 3, 4]
```


## Miscellaneous

* Slurping: When files are slurped in (via the `-s` / `--slurp` option),
Expand Down
15 changes: 11 additions & 4 deletions jaq-core/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,10 +439,17 @@ impl<'s, F> Compiler<&'s str, F> {
t
}
Num(n) => n.parse().map_or_else(|_| Term::Num(n.into()), Term::Int),
TryCatch(t, c) => Term::TryCatch(
self.iterm(*t),
self.iterm_tr(c.map_or_else(|| Call("!empty", Vec::new()), |c| *c)),
),
// map `try f catch g` to `label $x | try f catch (g, break $x)`
// and `try f` or `f?` to `label $x | try f catch ( break $x)`
TryCatch(t, c) => {
let (label, break_) = (Local::Label(""), Break(""));
let catch = match c {
None => break_,
Some(c) => BinOp(c, parse::BinaryOp::Comma, break_.into()),
};
let tc = self.with(label, |c| Term::TryCatch(c.iterm(*t), c.iterm(catch)));
Term::Label(self.lut.insert_term(tc))
}
Fold(name, xs, x, args) => {
let arity = args.len();
let fold = match name {
Expand Down
10 changes: 4 additions & 6 deletions jaq-core/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ fn alt() {
fn try_() {
give(json!(0), ".?", json!(0));
give(json!(0), r#"(-"a")?, 1"#, json!(1));
give(json!(0), r#"[(1, -"a", 2)?]"#, json!([1, 2]));
give(json!(0), r#"[(1, -"a", 2)?]"#, json!([1]));
}

#[test]
Expand Down Expand Up @@ -235,12 +235,10 @@ yields!(
[1, 2]
);

// This behaviour diverges from jq. In jaq, a `try` will propagate all
// errors in the stream to the `catch` filter.
yields!(
try_catch_does_not_short_circuit,
try_catch_short_circuit,
"[try (\"1\", \"2\", {}[0], \"4\") catch .]",
["1", "2", "cannot index {} with 0", "4"]
["1", "2", "cannot index {} with 0"]
);
yields!(
try_catch_nested,
Expand All @@ -252,7 +250,7 @@ yields!(
"[(try (1,2,3[0]) catch (3,4)) | . - 1]",
[0, 1, 2, 3]
);
yields!(try_without_catch, "[try (1,2,3[0],4)]", [1, 2, 4]);
yields!(try_without_catch, "[try (1,2,3[0],4)]", [1, 2]);
yields!(
try_catch_prefix_operation,
r#"(try -[] catch .) | . > "" and . < []"#,
Expand Down

0 comments on commit c83749a

Please sign in to comment.