Add try EXP catch EXP

This commit is contained in:
Nicolas Williams committed 2014-07-06 01:29:42 -05:00
1 parent 9113282c27
commit 7fce34292e
9 files changed
+108 -15

No files matched your search

+21
View File
@@ -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);
+1
View File
@@ -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);
+23
View File
@@ -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
+33 -15
View File
@@ -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;
+2
View File
@@ -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; }
+6
View File
@@ -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;
}
+1
View File
@@ -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)
+16
View File
@@ -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)));
} |
+5
View File
@@ -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"]