Add try EXP catch EXP
This commit is contained in:
1 parent
9113282c27
commit
7fce34292e
9 files changed
+108
-15
No files matched your search
@@ -443,6 +443,27 @@ block gen_cond(block cond, block iftrue, block iffalse) {
|
||||
BLOCK(gen_op_simple(POP), iffalse)));
|
||||
}
|
||||
|
||||
block gen_try(block exp, block handler) {
|
||||
/*
|
||||
* Produce:
|
||||
* FORK_OPT <address of handler>
|
||||
* <exp>
|
||||
* JUMP <end of handler>
|
||||
* <handler>
|
||||
*
|
||||
* The handler will only execute if we backtrack to the FORK_OPT with
|
||||
* an error (exception). If <exp> produces no value then FORK_OPT
|
||||
* will backtrack (propagate the `empty`, as it were. If <exp>
|
||||
* produces a value then we'll execute whatever bytecode follows this
|
||||
* sequence.
|
||||
*/
|
||||
if (!handler.first && !handler.last)
|
||||
// A hack to deal with `.` as the handler; we could use a real NOOP here
|
||||
handler = BLOCK(gen_op_simple(DUP), gen_op_simple(POP), handler);
|
||||
exp = BLOCK(exp, gen_op_target(JUMP, handler));
|
||||
return BLOCK(gen_op_target(FORK_OPT, exp), exp, handler);
|
||||
}
|
||||
|
||||
block gen_cbinding(const struct cfunction* cfunctions, int ncfunctions, block code) {
|
||||
for (int cfunc=0; cfunc<ncfunctions; cfunc++) {
|
||||
inst* i = inst_new(CLOSURE_CREATE_C);
|
||||
|
||||
@@ -40,6 +40,7 @@ block gen_or(block a, block b);
|
||||
block gen_var_binding(block var, const char* name, block body);
|
||||
|
||||
block gen_cond(block cond, block iftrue, block iffalse);
|
||||
block gen_try(block exp, block handler);
|
||||
|
||||
block gen_cbinding(const struct cfunction* functions, int nfunctions, block b);
|
||||
|
||||
|
||||
@@ -1453,6 +1453,7 @@ sections:
|
||||
- program: '.[] == 1'
|
||||
input: '[1, 1.0, "1", "banana"]'
|
||||
output: ['true', 'true', 'false', 'false']
|
||||
|
||||
- title: if-then-else
|
||||
body: |
|
||||
|
||||
@@ -1561,6 +1562,28 @@ sections:
|
||||
input: '{}'
|
||||
output: [42]
|
||||
|
||||
- title: try-catch
|
||||
body: |
|
||||
|
||||
Errors can be caught by using `try EXP catch EXP`. The first
|
||||
expression is executed, and if it fails then the second is
|
||||
executed with the error message. The output of the handler,
|
||||
if any, is output as if it had been the output of the
|
||||
expression to try.
|
||||
|
||||
The `try EXP` form uses `empty` as the exception handler.
|
||||
|
||||
examples:
|
||||
- program: 'try .a catch ". is not an object"'
|
||||
input: 'true'
|
||||
output: [". is not an object"]
|
||||
- program: '[.[]|try .a]'
|
||||
input: '[{}, true, {"a":1}'
|
||||
output: ['[null, 1]']
|
||||
- program: 'try error("some exception") catch .'
|
||||
input: 'true'
|
||||
output: ['"some exception"']
|
||||
|
||||
- title: Advanced features
|
||||
body: |
|
||||
Variables are an absolute necessity in most programming languages, but
|
||||
|
||||
@@ -23,6 +23,7 @@ struct jq_state {
|
||||
|
||||
jq_err_cb err_cb;
|
||||
void *err_cb_data;
|
||||
jv error;
|
||||
|
||||
struct stack stk;
|
||||
stack_ptr curr_frame;
|
||||
@@ -241,6 +242,8 @@ static void jq_reset(jq_state *jq) {
|
||||
assert(jq->fork_top == 0);
|
||||
assert(jq->curr_frame == 0);
|
||||
stack_reset(&jq->stk);
|
||||
jv_free(jq->error);
|
||||
jq->error = jv_null();
|
||||
|
||||
if (jv_get_kind(jq->path) != JV_KIND_INVALID)
|
||||
jv_free(jq->path);
|
||||
@@ -250,21 +253,12 @@ static void jq_reset(jq_state *jq) {
|
||||
|
||||
static void print_error(jq_state *jq, jv value) {
|
||||
assert(!jv_is_valid(value));
|
||||
jv msg = jv_invalid_get_msg(value);
|
||||
jv msg2;
|
||||
if (jv_get_kind(msg) == JV_KIND_STRING)
|
||||
msg2 = jv_string_fmt("jq: error: %s", jv_string_value(msg));
|
||||
else
|
||||
msg2 = jv_string_fmt("jq: error: <unknown>");
|
||||
jv_free(msg);
|
||||
|
||||
if (jq->err_cb)
|
||||
jq->err_cb(jq->err_cb_data, msg2);
|
||||
else if (jv_get_kind(msg2) == JV_KIND_STRING)
|
||||
fprintf(stderr, "%s\n", jv_string_value(msg2));
|
||||
else
|
||||
fprintf(stderr, "jq: error: %s\n", strerror(ENOMEM));
|
||||
jv_free(msg2);
|
||||
if (jq->err_cb) {
|
||||
// callback must jv_free() its jv argument
|
||||
jq->err_cb(jq->err_cb_data, jv_invalid_get_msg(jv_copy(value)));
|
||||
}
|
||||
jv_free(jq->error);
|
||||
jq->error = value;
|
||||
}
|
||||
#define ON_BACKTRACK(op) ((op)+NUM_OPCODES)
|
||||
|
||||
@@ -278,6 +272,7 @@ jv jq_next(jq_state *jq) {
|
||||
|
||||
int backtracking = !jq->initial_execution;
|
||||
jq->initial_execution = 0;
|
||||
assert(jv_get_kind(jq->error) == JV_KIND_NULL);
|
||||
while (1) {
|
||||
uint16_t opcode = *pc;
|
||||
|
||||
@@ -312,6 +307,8 @@ jv jq_next(jq_state *jq) {
|
||||
if (backtracking) {
|
||||
opcode = ON_BACKTRACK(opcode);
|
||||
backtracking = 0;
|
||||
if (!jv_is_valid(jq->error) && opcode != ON_BACKTRACK(FORK_OPT))
|
||||
goto do_backtrack;
|
||||
}
|
||||
pc++;
|
||||
|
||||
@@ -606,18 +603,36 @@ jv jq_next(jq_state *jq) {
|
||||
case BACKTRACK: {
|
||||
pc = stack_restore(jq);
|
||||
if (!pc) {
|
||||
if (!jv_is_valid(jq->error)) {
|
||||
jv error = jq->error;
|
||||
jq->error = jv_null();
|
||||
return error;
|
||||
}
|
||||
return jv_invalid();
|
||||
}
|
||||
backtracking = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
case FORK_OPT:
|
||||
case FORK: {
|
||||
stack_save(jq, pc - 1, stack_get_pos(jq));
|
||||
pc++; // skip offset this time
|
||||
break;
|
||||
}
|
||||
|
||||
case ON_BACKTRACK(FORK_OPT): {
|
||||
if (jv_is_valid(jq->error)) {
|
||||
// `try EXP ...` backtracked here (no value, `empty`), so we backtrack more
|
||||
jv_free(stack_pop(jq));
|
||||
goto do_backtrack;
|
||||
}
|
||||
// `try EXP ...` exception caught in EXP
|
||||
jv_free(stack_pop(jq)); // free the input
|
||||
stack_push(jq, jv_invalid_get_msg(jq->error)); // push the error's message
|
||||
jq->error = jv_null();
|
||||
/* fallthru */
|
||||
}
|
||||
case ON_BACKTRACK(FORK): {
|
||||
uint16_t offset = *pc++;
|
||||
pc += offset;
|
||||
@@ -644,6 +659,8 @@ jv jq_next(jq_state *jq) {
|
||||
case 3: top = ((func_3)function->fptr)(in[0], in[1], in[2]); break;
|
||||
case 4: top = ((func_4)function->fptr)(in[0], in[1], in[2], in[3]); break;
|
||||
case 5: top = ((func_5)function->fptr)(in[0], in[1], in[2], in[3], in[4]); break;
|
||||
// FIXME: a) up to 7 arguments (input + 6), b) should assert
|
||||
// because the compiler should not generate this error.
|
||||
default: return jv_invalid_with_msg(jv_string("Function takes too many arguments"));
|
||||
}
|
||||
|
||||
@@ -739,6 +756,7 @@ jq_state *jq_init(void) {
|
||||
jq->stk_top = 0;
|
||||
jq->fork_top = 0;
|
||||
jq->curr_frame = 0;
|
||||
jq->error = jv_null();
|
||||
|
||||
jq->err_cb = NULL;
|
||||
jq->err_cb_data = NULL;
|
||||
|
||||
@@ -52,6 +52,8 @@ struct lexer_param;
|
||||
"end" { return END; }
|
||||
"reduce" { return REDUCE; }
|
||||
"//" { return DEFINEDOR; }
|
||||
"try" { return TRY; }
|
||||
"catch" { return CATCH; }
|
||||
"|=" { return SETPIPE; }
|
||||
"+=" { return SETPLUS; }
|
||||
"-=" { return SETMINUS; }
|
||||
|
||||
@@ -124,6 +124,12 @@ static int process(jq_state *jq, jv value, int flags) {
|
||||
if (options & UNBUFFERED_OUTPUT)
|
||||
fflush(stdout);
|
||||
}
|
||||
if (jv_invalid_has_msg(jv_copy(result))) {
|
||||
// Uncaught jq exception
|
||||
jv msg = jv_invalid_get_msg(jv_copy(result));
|
||||
fprintf(stderr, "jq: error: %s\n", jv_string_value(msg));
|
||||
jv_free(msg);
|
||||
}
|
||||
jv_free(result);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ OP(INDEX_OPT, NONE, 2, 1)
|
||||
OP(EACH, NONE, 1, 1)
|
||||
OP(EACH_OPT, NONE, 1, 1)
|
||||
OP(FORK, BRANCH, 0, 0)
|
||||
OP(FORK_OPT, BRANCH, 0, 0)
|
||||
OP(JUMP, BRANCH, 0, 0)
|
||||
OP(JUMP_F,BRANCH, 1, 0)
|
||||
OP(BACKTRACK, NONE, 0, 0)
|
||||
|
||||
@@ -66,6 +66,8 @@ struct lexer_param;
|
||||
%token END "end"
|
||||
%token AND "and"
|
||||
%token OR "or"
|
||||
%token TRY "try"
|
||||
%token CATCH "catch"
|
||||
%token SETPIPE "|="
|
||||
%token SETPLUS "+="
|
||||
%token SETMINUS "-="
|
||||
@@ -240,6 +242,20 @@ Term "as" '$' IDENT '|' Exp {
|
||||
$$ = $2;
|
||||
} |
|
||||
|
||||
"try" Exp "catch" Exp {
|
||||
//$$ = BLOCK(gen_op_target(FORK_OPT, $2), $2, $4);
|
||||
$$ = gen_try($2, $4);
|
||||
} |
|
||||
"try" Exp {
|
||||
//$$ = BLOCK(gen_op_target(FORK_OPT, $2), $2, gen_op_simple(BACKTRACK));
|
||||
$$ = gen_try($2, gen_op_simple(BACKTRACK));
|
||||
} |
|
||||
"try" Exp "catch" error {
|
||||
FAIL(@$, "Possibly unterminated 'try' statement");
|
||||
$$ = $2;
|
||||
} |
|
||||
|
||||
|
||||
Exp '=' Exp {
|
||||
$$ = gen_call("_assign", BLOCK(gen_lambda($1), gen_lambda($3)));
|
||||
} |
|
||||
|
||||
@@ -665,6 +665,11 @@ def inc(x): x |= .+1; inc(.[].a)
|
||||
{}
|
||||
[true, true, false]
|
||||
|
||||
# Try/catch and general `?` operator
|
||||
[.[]|try if . == 0 then error("foo") elif . == 1 then .a elif . == 2 then empty else . end catch .]
|
||||
[0,1,2,3]
|
||||
["foo","Cannot index number with string \"a\"",3]
|
||||
|
||||
# string operations
|
||||
[.[]|startswith("foo")]
|
||||
["fo", "foo", "barfoo", "foobar", "barfoob"]
|
||||
|
||||
Reference in new issue
Block a user