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_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) {
|
block gen_cbinding(const struct cfunction* cfunctions, int ncfunctions, block code) {
|
||||||
for (int cfunc=0; cfunc<ncfunctions; cfunc++) {
|
for (int cfunc=0; cfunc<ncfunctions; cfunc++) {
|
||||||
inst* i = inst_new(CLOSURE_CREATE_C);
|
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_var_binding(block var, const char* name, block body);
|
||||||
|
|
||||||
block gen_cond(block cond, block iftrue, block iffalse);
|
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);
|
block gen_cbinding(const struct cfunction* functions, int nfunctions, block b);
|
||||||
|
|
||||||
|
|||||||
@@ -1453,6 +1453,7 @@ sections:
|
|||||||
- program: '.[] == 1'
|
- program: '.[] == 1'
|
||||||
input: '[1, 1.0, "1", "banana"]'
|
input: '[1, 1.0, "1", "banana"]'
|
||||||
output: ['true', 'true', 'false', 'false']
|
output: ['true', 'true', 'false', 'false']
|
||||||
|
|
||||||
- title: if-then-else
|
- title: if-then-else
|
||||||
body: |
|
body: |
|
||||||
|
|
||||||
@@ -1561,6 +1562,28 @@ sections:
|
|||||||
input: '{}'
|
input: '{}'
|
||||||
output: [42]
|
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
|
- title: Advanced features
|
||||||
body: |
|
body: |
|
||||||
Variables are an absolute necessity in most programming languages, but
|
Variables are an absolute necessity in most programming languages, but
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ struct jq_state {
|
|||||||
|
|
||||||
jq_err_cb err_cb;
|
jq_err_cb err_cb;
|
||||||
void *err_cb_data;
|
void *err_cb_data;
|
||||||
|
jv error;
|
||||||
|
|
||||||
struct stack stk;
|
struct stack stk;
|
||||||
stack_ptr curr_frame;
|
stack_ptr curr_frame;
|
||||||
@@ -241,6 +242,8 @@ static void jq_reset(jq_state *jq) {
|
|||||||
assert(jq->fork_top == 0);
|
assert(jq->fork_top == 0);
|
||||||
assert(jq->curr_frame == 0);
|
assert(jq->curr_frame == 0);
|
||||||
stack_reset(&jq->stk);
|
stack_reset(&jq->stk);
|
||||||
|
jv_free(jq->error);
|
||||||
|
jq->error = jv_null();
|
||||||
|
|
||||||
if (jv_get_kind(jq->path) != JV_KIND_INVALID)
|
if (jv_get_kind(jq->path) != JV_KIND_INVALID)
|
||||||
jv_free(jq->path);
|
jv_free(jq->path);
|
||||||
@@ -250,21 +253,12 @@ static void jq_reset(jq_state *jq) {
|
|||||||
|
|
||||||
static void print_error(jq_state *jq, jv value) {
|
static void print_error(jq_state *jq, jv value) {
|
||||||
assert(!jv_is_valid(value));
|
assert(!jv_is_valid(value));
|
||||||
jv msg = jv_invalid_get_msg(value);
|
if (jq->err_cb) {
|
||||||
jv msg2;
|
// callback must jv_free() its jv argument
|
||||||
if (jv_get_kind(msg) == JV_KIND_STRING)
|
jq->err_cb(jq->err_cb_data, jv_invalid_get_msg(jv_copy(value)));
|
||||||
msg2 = jv_string_fmt("jq: error: %s", jv_string_value(msg));
|
}
|
||||||
else
|
jv_free(jq->error);
|
||||||
msg2 = jv_string_fmt("jq: error: <unknown>");
|
jq->error = value;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
#define ON_BACKTRACK(op) ((op)+NUM_OPCODES)
|
#define ON_BACKTRACK(op) ((op)+NUM_OPCODES)
|
||||||
|
|
||||||
@@ -278,6 +272,7 @@ jv jq_next(jq_state *jq) {
|
|||||||
|
|
||||||
int backtracking = !jq->initial_execution;
|
int backtracking = !jq->initial_execution;
|
||||||
jq->initial_execution = 0;
|
jq->initial_execution = 0;
|
||||||
|
assert(jv_get_kind(jq->error) == JV_KIND_NULL);
|
||||||
while (1) {
|
while (1) {
|
||||||
uint16_t opcode = *pc;
|
uint16_t opcode = *pc;
|
||||||
|
|
||||||
@@ -312,6 +307,8 @@ jv jq_next(jq_state *jq) {
|
|||||||
if (backtracking) {
|
if (backtracking) {
|
||||||
opcode = ON_BACKTRACK(opcode);
|
opcode = ON_BACKTRACK(opcode);
|
||||||
backtracking = 0;
|
backtracking = 0;
|
||||||
|
if (!jv_is_valid(jq->error) && opcode != ON_BACKTRACK(FORK_OPT))
|
||||||
|
goto do_backtrack;
|
||||||
}
|
}
|
||||||
pc++;
|
pc++;
|
||||||
|
|
||||||
@@ -606,18 +603,36 @@ jv jq_next(jq_state *jq) {
|
|||||||
case BACKTRACK: {
|
case BACKTRACK: {
|
||||||
pc = stack_restore(jq);
|
pc = stack_restore(jq);
|
||||||
if (!pc) {
|
if (!pc) {
|
||||||
|
if (!jv_is_valid(jq->error)) {
|
||||||
|
jv error = jq->error;
|
||||||
|
jq->error = jv_null();
|
||||||
|
return error;
|
||||||
|
}
|
||||||
return jv_invalid();
|
return jv_invalid();
|
||||||
}
|
}
|
||||||
backtracking = 1;
|
backtracking = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case FORK_OPT:
|
||||||
case FORK: {
|
case FORK: {
|
||||||
stack_save(jq, pc - 1, stack_get_pos(jq));
|
stack_save(jq, pc - 1, stack_get_pos(jq));
|
||||||
pc++; // skip offset this time
|
pc++; // skip offset this time
|
||||||
break;
|
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): {
|
case ON_BACKTRACK(FORK): {
|
||||||
uint16_t offset = *pc++;
|
uint16_t offset = *pc++;
|
||||||
pc += offset;
|
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 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 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;
|
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"));
|
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->stk_top = 0;
|
||||||
jq->fork_top = 0;
|
jq->fork_top = 0;
|
||||||
jq->curr_frame = 0;
|
jq->curr_frame = 0;
|
||||||
|
jq->error = jv_null();
|
||||||
|
|
||||||
jq->err_cb = NULL;
|
jq->err_cb = NULL;
|
||||||
jq->err_cb_data = NULL;
|
jq->err_cb_data = NULL;
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ struct lexer_param;
|
|||||||
"end" { return END; }
|
"end" { return END; }
|
||||||
"reduce" { return REDUCE; }
|
"reduce" { return REDUCE; }
|
||||||
"//" { return DEFINEDOR; }
|
"//" { return DEFINEDOR; }
|
||||||
|
"try" { return TRY; }
|
||||||
|
"catch" { return CATCH; }
|
||||||
"|=" { return SETPIPE; }
|
"|=" { return SETPIPE; }
|
||||||
"+=" { return SETPLUS; }
|
"+=" { return SETPLUS; }
|
||||||
"-=" { return SETMINUS; }
|
"-=" { return SETMINUS; }
|
||||||
|
|||||||
@@ -124,6 +124,12 @@ static int process(jq_state *jq, jv value, int flags) {
|
|||||||
if (options & UNBUFFERED_OUTPUT)
|
if (options & UNBUFFERED_OUTPUT)
|
||||||
fflush(stdout);
|
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);
|
jv_free(result);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ OP(INDEX_OPT, NONE, 2, 1)
|
|||||||
OP(EACH, NONE, 1, 1)
|
OP(EACH, NONE, 1, 1)
|
||||||
OP(EACH_OPT, NONE, 1, 1)
|
OP(EACH_OPT, NONE, 1, 1)
|
||||||
OP(FORK, BRANCH, 0, 0)
|
OP(FORK, BRANCH, 0, 0)
|
||||||
|
OP(FORK_OPT, BRANCH, 0, 0)
|
||||||
OP(JUMP, BRANCH, 0, 0)
|
OP(JUMP, BRANCH, 0, 0)
|
||||||
OP(JUMP_F,BRANCH, 1, 0)
|
OP(JUMP_F,BRANCH, 1, 0)
|
||||||
OP(BACKTRACK, NONE, 0, 0)
|
OP(BACKTRACK, NONE, 0, 0)
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ struct lexer_param;
|
|||||||
%token END "end"
|
%token END "end"
|
||||||
%token AND "and"
|
%token AND "and"
|
||||||
%token OR "or"
|
%token OR "or"
|
||||||
|
%token TRY "try"
|
||||||
|
%token CATCH "catch"
|
||||||
%token SETPIPE "|="
|
%token SETPIPE "|="
|
||||||
%token SETPLUS "+="
|
%token SETPLUS "+="
|
||||||
%token SETMINUS "-="
|
%token SETMINUS "-="
|
||||||
@@ -240,6 +242,20 @@ Term "as" '$' IDENT '|' Exp {
|
|||||||
$$ = $2;
|
$$ = $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 {
|
Exp '=' Exp {
|
||||||
$$ = gen_call("_assign", BLOCK(gen_lambda($1), gen_lambda($3)));
|
$$ = gen_call("_assign", BLOCK(gen_lambda($1), gen_lambda($3)));
|
||||||
} |
|
} |
|
||||||
|
|||||||
@@ -665,6 +665,11 @@ def inc(x): x |= .+1; inc(.[].a)
|
|||||||
{}
|
{}
|
||||||
[true, true, false]
|
[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
|
# string operations
|
||||||
[.[]|startswith("foo")]
|
[.[]|startswith("foo")]
|
||||||
["fo", "foo", "barfoo", "foobar", "barfoob"]
|
["fo", "foo", "barfoo", "foobar", "barfoob"]
|
||||||
|
|||||||
Reference in new issue
Block a user