1
0
mirror of https://github.com/stedolan/jq.git synced 2024-05-11 05:55:39 +00:00

Add label $name | EXP; fix break

This is to fix the problem where `break` is dynamic, not lexical.

With this it should be possible to do this sort of thing:

    label $break | inputs | if ... then $break|error else . end

This is a backwards-incompatible change for master, but the previous
`break` hadn't shipped yet.

Still needed:

 - testing
This commit is contained in:
Nicolas Williams
2014-12-28 00:32:06 -06:00
parent cbfc0d6130
commit 7dc34b92af
11 changed files with 225 additions and 33 deletions

View File

@@ -989,7 +989,6 @@ static block bind_bytecoded_builtins(block b) {
static const char* const jq_builtins[] = { static const char* const jq_builtins[] = {
"def error: error(.);", "def error: error(.);",
"def break: error(\"break\");",
"def map(f): [.[] | f];", "def map(f): [.[] | f];",
"def map_values(f): .[] |= f;", "def map_values(f): .[] |= f;",
"def select(f): if f then . else empty end;", "def select(f): if f then . else empty end;",
@@ -1024,14 +1023,14 @@ static const char* const jq_builtins[] = {
"def paths: path(recurse(if (type|. == \"array\" or . == \"object\") then .[] else empty end))|select(length > 0);", "def paths: path(recurse(if (type|. == \"array\" or . == \"object\") then .[] else empty end))|select(length > 0);",
"def paths(node_filter): . as $dot|paths|select(. as $p|$dot|getpath($p)|node_filter);", "def paths(node_filter): . as $dot|paths|select(. as $p|$dot|getpath($p)|node_filter);",
"def any(generator; condition):" "def any(generator; condition):"
" [foreach generator as $i" " [label | foreach generator as $i"
" (false;" " (false;"
" if . then break elif $i | condition then true else . end;" " if . then break elif $i | condition then true else . end;"
" if . then . else empty end)] | length == 1;", " if . then . else empty end)] | length == 1;",
"def any(condition): any(.[]; condition);", "def any(condition): any(.[]; condition);",
"def any: any(.);", "def any: any(.);",
"def all(generator; condition): " "def all(generator; condition): "
" [foreach generator as $i" " [label | foreach generator as $i"
" (true;" " (true;"
" if .|not then break elif $i | condition then . else false end;" " if .|not then break elif $i | condition then . else false end;"
" if .|not then . else empty end)] | length == 0;", " if .|not then . else empty end)] | length == 0;",
@@ -1133,13 +1132,13 @@ static const char* const jq_builtins[] = {
"def while(cond; update): " "def while(cond; update): "
" def _while: " " def _while: "
" if cond then ., (update | _while) else empty end; " " if cond then ., (update | _while) else empty end; "
" try _while catch if .==\"break\" then empty else . end;", " _while;",
"def until(cond; next): " "def until(cond; next): "
" def _until: " " def _until: "
" if cond then . else (next|_until) end;" " if cond then . else (next|_until) end;"
" _until;", " _until;",
"def limit($n; exp): if $n < 0 then exp else foreach exp as $item ([$n, null]; if .[0] < 1 then break else [.[0] -1, $item] end; .[1]) end;", "def limit($n; exp): if $n < 0 then exp else label | foreach exp as $item ([$n, null]; if .[0] < 1 then break else [.[0] -1, $item] end; .[1]) end;",
"def first(g): foreach g as $item ([false, null]; if .[0]==true then break else [true, $item] end; .[1]);", "def first(g): label | foreach g as $item ([false, null]; if .[0]==true then break else [true, $item] end; .[1]);",
"def last(g): reduce g as $item (null; $item);", "def last(g): reduce g as $item (null; $item);",
"def nth($n; g): if $n < 0 then error(\"nth doesn't support negative indices\") else last(limit($n + 1; g)) end;", "def nth($n; g): if $n < 0 then error(\"nth doesn't support negative indices\") else last(limit($n + 1; g)) end;",
"def first: .[0];", "def first: .[0];",
@@ -1157,12 +1156,12 @@ static const char* const jq_builtins[] = {
" end;", " end;",
"def in(xs): . as $x | xs | has($x);", "def in(xs): . as $x | xs | has($x);",
"def inside(xs): . as $x | xs | contains($x);", "def inside(xs): . as $x | xs | contains($x);",
"def input: try _input catch if .==\"break\" then empty else . end;", "def input: _input;",
"def repeat(exp): " "def repeat(exp): "
" def _repeat: " " def _repeat: "
" exp, _repeat;" " exp, _repeat;"
" try _repeat catch if .==\"break\" then empty else . end;", " _repeat;",
"def inputs: repeat(_input);", "def inputs: try repeat(_input) catch if .==\"break\" then empty else .|error end;",
// # like ruby's downcase - only characters A to Z are affected // # like ruby's downcase - only characters A to Z are affected
"def ascii_downcase:" "def ascii_downcase:"
" explode | map( if 65 <= . and . <= 90 then . + 32 else . end) | implode;", " explode | map( if 65 <= . and . <= 90 then . + 32 else . end) | implode;",

View File

@@ -25,6 +25,9 @@ enum {
OP_HAS_UFUNC = 64, OP_HAS_UFUNC = 64,
OP_IS_CALL_PSEUDO = 128, OP_IS_CALL_PSEUDO = 128,
OP_HAS_BINDING = 1024, OP_HAS_BINDING = 1024,
// NOTE: Not actually part of any op -- a pseudo-op flag for special
// handling of `break`.
OP_BIND_WILDCARD = 2048,
}; };
struct opcode_description { struct opcode_description {
opcode op; opcode op;

View File

@@ -241,6 +241,7 @@ int block_has_only_binders_and_imports(block binders, int bindflags) {
int block_has_only_binders(block binders, int bindflags) { int block_has_only_binders(block binders, int bindflags) {
bindflags |= OP_HAS_BINDING; bindflags |= OP_HAS_BINDING;
bindflags &= ~OP_BIND_WILDCARD;
for (inst* curr = binders.first; curr; curr = curr->next) { for (inst* curr = binders.first; curr; curr = curr->next) {
if ((opcode_describe(curr->op)->flags & bindflags) != bindflags && curr->op != MODULEMETA) { if ((opcode_describe(curr->op)->flags & bindflags) != bindflags && curr->op != MODULEMETA) {
return 0; return 0;
@@ -291,11 +292,12 @@ static int block_count_refs(block binder, block body) {
return nrefs; return nrefs;
} }
static int block_bind_subblock(block binder, block body, int bindflags) { static int block_bind_subblock(block binder, block body, int bindflags, int break_distance) {
assert(block_is_single(binder)); assert(block_is_single(binder));
assert((opcode_describe(binder.first->op)->flags & bindflags) == bindflags); assert((opcode_describe(binder.first->op)->flags & bindflags) == (bindflags & ~OP_BIND_WILDCARD));
assert(binder.first->symbol); assert(binder.first->symbol);
assert(binder.first->bound_by == 0 || binder.first->bound_by == binder.first); assert(binder.first->bound_by == 0 || binder.first->bound_by == binder.first);
assert(break_distance >= 0);
binder.first->bound_by = binder.first; binder.first->bound_by = binder.first;
if (binder.first->nformals == -1) if (binder.first->nformals == -1)
@@ -303,9 +305,16 @@ static int block_bind_subblock(block binder, block body, int bindflags) {
int nrefs = 0; int nrefs = 0;
for (inst* i = body.first; i; i = i->next) { for (inst* i = body.first; i; i = i->next) {
int flags = opcode_describe(i->op)->flags; int flags = opcode_describe(i->op)->flags;
if ((flags & bindflags) == bindflags && if ((flags & bindflags) == (bindflags & ~OP_BIND_WILDCARD) && i->bound_by == 0 &&
i->bound_by == 0 && (!strcmp(i->symbol, binder.first->symbol) ||
!strcmp(i->symbol, binder.first->symbol)) { // Check for break/break2/break3; see parser.y
((bindflags & OP_BIND_WILDCARD) && i->symbol[0] == '*' &&
break_distance <= 3 && (i->symbol[1] == '1' + break_distance) &&
i->symbol[2] == '\0'))) {
if (bindflags & OP_BIND_WILDCARD) {
jv_mem_free(i->symbol);
i->symbol = strdup(binder.first->symbol);
}
// bind this instruction // bind this instruction
if (i->op == CALL_JQ && i->nactuals == -1) if (i->op == CALL_JQ && i->nactuals == -1)
i->nactuals = block_count_actuals(i->arglist); i->nactuals = block_count_actuals(i->arglist);
@@ -313,11 +322,17 @@ static int block_bind_subblock(block binder, block body, int bindflags) {
i->bound_by = binder.first; i->bound_by = binder.first;
nrefs++; nrefs++;
} }
} else if ((flags & bindflags) == (bindflags & ~OP_BIND_WILDCARD) && i->bound_by != 0 &&
!strncmp(binder.first->symbol, "*anonlabel", sizeof("*anonlabel") - 1) &&
!strncmp(i->symbol, "*anonlabel", sizeof("*anonlabel") - 1)) {
// Increment the break distance required for this binder to match
// a break whenever we come across a STOREV of *anonlabel...
break_distance++;
} }
// binding recurses into closures // binding recurses into closures
nrefs += block_bind_subblock(binder, i->subfn, bindflags); nrefs += block_bind_subblock(binder, i->subfn, bindflags, break_distance);
// binding recurses into argument list // binding recurses into argument list
nrefs += block_bind_subblock(binder, i->arglist, bindflags); nrefs += block_bind_subblock(binder, i->arglist, bindflags, break_distance);
} }
return nrefs; return nrefs;
} }
@@ -327,7 +342,7 @@ static int block_bind_each(block binder, block body, int bindflags) {
bindflags |= OP_HAS_BINDING; bindflags |= OP_HAS_BINDING;
int nrefs = 0; int nrefs = 0;
for (inst* curr = binder.first; curr; curr = curr->next) { for (inst* curr = binder.first; curr; curr = curr->next) {
nrefs += block_bind_subblock(inst_block(curr), body, bindflags); nrefs += block_bind_subblock(inst_block(curr), body, bindflags, 0);
} }
return nrefs; return nrefs;
} }
@@ -354,7 +369,7 @@ block block_bind_library(block binder, block body, int bindflags, const char* li
strcpy(tname, matchname); strcpy(tname, matchname);
strcpy(tname+matchlen,cname); strcpy(tname+matchlen,cname);
curr->symbol = tname; curr->symbol = tname;
nrefs += block_bind_subblock(inst_block(curr), body, bindflags); nrefs += block_bind_subblock(inst_block(curr), body, bindflags, 0);
curr->symbol = cname; curr->symbol = cname;
free(tname); free(tname);
} }
@@ -483,13 +498,13 @@ block gen_function(const char* name, block formals, block body) {
i->op = CLOSURE_PARAM; i->op = CLOSURE_PARAM;
body = gen_var_binding(gen_call(i->symbol, gen_noop()), i->symbol, body); body = gen_var_binding(gen_call(i->symbol, gen_noop()), i->symbol, body);
} }
block_bind_subblock(inst_block(i), body, OP_IS_CALL_PSEUDO | OP_HAS_BINDING); block_bind_subblock(inst_block(i), body, OP_IS_CALL_PSEUDO | OP_HAS_BINDING, 0);
} }
i->subfn = body; i->subfn = body;
i->symbol = strdup(name); i->symbol = strdup(name);
i->arglist = formals; i->arglist = formals;
block b = inst_block(i); block b = inst_block(i);
block_bind_subblock(b, b, OP_IS_CALL_PSEUDO | OP_HAS_BINDING); block_bind_subblock(b, b, OP_IS_CALL_PSEUDO | OP_HAS_BINDING, 0);
return b; return b;
} }
@@ -707,10 +722,7 @@ block gen_foreach(const char* varname, block source, block init, block update, b
// want to output it, so we backtrack. // want to output it, so we backtrack.
gen_op_simple(BACKTRACK)); gen_op_simple(BACKTRACK));
inst_set_target(output, foreach); // make that JUMP go bast the BACKTRACK at the end of the loop inst_set_target(output, foreach); // make that JUMP go bast the BACKTRACK at the end of the loop
block handler = gen_cond(gen_call("_equal", BLOCK(gen_lambda(gen_const(jv_string("break"))), gen_lambda(gen_noop()))), return foreach;
gen_op_simple(BACKTRACK),
gen_call("error", gen_noop()));
return gen_try(foreach, handler);
} }
block gen_definedor(block a, block b) { block gen_definedor(block a, block b) {
@@ -790,20 +802,45 @@ block gen_var_binding(block var, const char* name, block body) {
body, OP_HAS_VARIABLE)); body, OP_HAS_VARIABLE));
} }
// Like gen_var_binding(), but bind `break`'s wildcard unbound variable
static block gen_wildvar_binding(block var, const char* name, block body) {
return BLOCK(gen_op_simple(DUP), var,
block_bind(gen_op_unbound(STOREV, name),
body, OP_HAS_VARIABLE | OP_BIND_WILDCARD));
}
block gen_cond(block cond, block iftrue, block iffalse) { block gen_cond(block cond, block iftrue, block iffalse) {
return BLOCK(gen_op_simple(DUP), cond, return BLOCK(gen_op_simple(DUP), cond,
gen_condbranch(BLOCK(gen_op_simple(POP), iftrue), gen_condbranch(BLOCK(gen_op_simple(POP), iftrue),
BLOCK(gen_op_simple(POP), iffalse))); BLOCK(gen_op_simple(POP), iffalse)));
} }
block gen_try_handler(block handler) {
// Quite a pain just to hide jq's internal errors.
return gen_cond(// `if type=="object" and .__jq
gen_and(gen_call("_equal",
BLOCK(gen_lambda(gen_const(jv_string("object"))),
gen_lambda(gen_noop()))),
BLOCK(gen_subexp(gen_const(jv_string("__jq"))),
gen_noop(),
gen_op_simple(INDEX))),
// `then error`
gen_call("error", gen_noop()),
// `else HANDLER end`
handler);
}
block gen_try(block exp, block handler) { block gen_try(block exp, block handler) {
/* /*
* Produce: * Produce something like:
* FORK_OPT <address of handler> * FORK_OPT <address of handler>
* <exp> * <exp>
* JUMP <end of handler> * JUMP <end of handler>
* <handler> * <handler>
* *
* If this is not an internal try/catch, then catch and re-raise
* internal errors to prevent them from leaking.
*
* The handler will only execute if we backtrack to the FORK_OPT with * The handler will only execute if we backtrack to the FORK_OPT with
* an error (exception). If <exp> produces no value then FORK_OPT * an error (exception). If <exp> produces no value then FORK_OPT
* will backtrack (propagate the `empty`, as it were. If <exp> * will backtrack (propagate the `empty`, as it were. If <exp>
@@ -817,6 +854,25 @@ block gen_try(block exp, block handler) {
return BLOCK(gen_op_target(FORK_OPT, exp), exp, handler); return BLOCK(gen_op_target(FORK_OPT, exp), exp, handler);
} }
block gen_label(const char *label, block exp) {
block cond = gen_call("_equal",
BLOCK(gen_lambda(gen_noop()),
gen_lambda(gen_op_unbound(LOADV, label))));
return gen_wildvar_binding(gen_op_simple(GENLABEL), label,
BLOCK(gen_op_simple(POP),
// try exp catch if . == $label
// then empty
// else error end
//
// Can't use gen_binop(), as that's firmly
// stuck in parser.y as it refers to things
// like EQ.
gen_try(exp,
gen_cond(cond,
gen_op_simple(BACKTRACK),
gen_call("error", gen_noop())))));
}
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);
@@ -855,7 +911,10 @@ static int expand_call_arglist(block* b) {
for (inst* curr; (curr = block_take(b));) { for (inst* curr; (curr = block_take(b));) {
if (opcode_describe(curr->op)->flags & OP_HAS_BINDING) { if (opcode_describe(curr->op)->flags & OP_HAS_BINDING) {
if (!curr->bound_by) { if (!curr->bound_by) {
locfile_locate(curr->locfile, curr->source, "jq: error: %s/%d is not defined", curr->symbol, block_count_actuals(curr->arglist)); if (curr->symbol[0] == '*' && curr->symbol[1] >= '1' && curr->symbol[1] <= '3' && curr->symbol[2] == '\0')
locfile_locate(curr->locfile, curr->source, "jq: error: break used outside labeled control structure");
else
locfile_locate(curr->locfile, curr->source, "jq: error: %s/%d is not defined", curr->symbol, block_count_actuals(curr->arglist));
errors++; errors++;
// don't process this instruction if it's not well-defined // don't process this instruction if it's not well-defined
ret = BLOCK(ret, inst_block(curr)); ret = BLOCK(ret, inst_block(curr));

View File

@@ -50,7 +50,9 @@ 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_handler(block handler);
block gen_try(block exp, block handler); block gen_try(block exp, block handler);
block gen_label(const char *label, block exp);
block gen_cbinding(const struct cfunction* functions, int nfunctions, block b); block gen_cbinding(const struct cfunction* functions, int nfunctions, block b);

View File

@@ -1740,6 +1740,55 @@ sections:
input: 'true' input: 'true'
output: ['"some exception"'] output: ['"some exception"']
- title: Breaking out of control structures
body: |
A convenient use of try/catch is to break out of control
structures like `reduce`, `foreach`, `while`, and so on.
For example:
# Repeat an expression until it raises "break" as an
# error, then stop repeating without re-raising the error.
# But if the error caught is not "break" then re-raise it.
try repeat(exp) catch .=="break" then empty else error;
jq has a syntax for lexical labels to "break" or "go (back) to":
label | ... break ...
The `break` builtin will cause the program to return to act as
though the nearest `label` (to the left) produced `empty`.
The `break2` builtin will return the program to the second
nearest `label`, then `empty`.
The `break3` builtin will return the program to the third
nearest `label`, then `empty`.
A variation on this is named labels:
label $name | ... break $name
The `break $name` form returns to the named label and produces
`empty`.
In all cases the relationship between the `break` and
corresponding `label` is lexical: the label has to be
"visible" from the break.
To break out of a `reduce`, for example:
label | reduce .[] as $item (null; if .==false then break else ... end)
The following jq program produces a syntax error:
break
because:
jq: error: break used outside labeled control structure
- title: "`?` operator" - title: "`?` operator"
body: | body: |
@@ -2171,8 +2220,7 @@ sections:
This is mostly useful only for constructing `reduce`- and This is mostly useful only for constructing `reduce`- and
`limit`-like functions. But it is much more general, as it `limit`-like functions. But it is much more general, as it
allows for partial reductions (see the example below), as well allows for partial reductions (see the example below).
as breaking out of the "loop" with `break`.
examples: examples:
- program: '[foreach .[] as $item - program: '[foreach .[] as $item
@@ -2199,8 +2247,14 @@ sections:
def recurse(f): def r: ., (f | select(. != null) | r); r; def recurse(f): def r: ., (f | select(. != null) | r); r;
def while(cond; update): def while(cond; update):
def w: if cond then ., (update | _while) else empty end; def _while:
try _while catch if . == "break" then empty else . end; if cond then ., (update | _while) else empty end;
_while;
def repeat(exp):
def _repeat:
exp, _repeat;
_repeat;
- title: Generators and iterators - title: Generators and iterators
body: | body: |

View File

@@ -37,6 +37,7 @@ struct jq_state {
int subexp_nest; int subexp_nest;
int debug_trace_enabled; int debug_trace_enabled;
int initial_execution; int initial_execution;
unsigned next_label;
jv attrs; jv attrs;
jq_input_cb input_cb; jq_input_cb input_cb;
@@ -351,6 +352,11 @@ jv jq_next(jq_state *jq) {
break; break;
} }
case GENLABEL: {
stack_push(jq, JV_OBJECT(jv_string("__jq"), jv_number(jq->next_label++)));
break;
}
case DUP: { case DUP: {
jv v = stack_pop(jq); jv v = stack_pop(jq);
stack_push(jq, jv_copy(v)); stack_push(jq, jv_copy(v));
@@ -838,6 +844,7 @@ jq_state *jq_init(void) {
return NULL; return NULL;
jq->bc = 0; jq->bc = 0;
jq->next_label = 0;
stack_init(&jq->stk); stack_init(&jq->stk);
jq->stk_top = 0; jq->stk_top = 0;

View File

@@ -57,6 +57,10 @@ struct lexer_param;
"//" { return DEFINEDOR; } "//" { return DEFINEDOR; }
"try" { return TRY; } "try" { return TRY; }
"catch" { return CATCH; } "catch" { return CATCH; }
"label" { return LABEL; }
"break" { return BREAK; }
"break2" { return BREAK2; }
"break3" { return BREAK3; }
"|=" { return SETPIPE; } "|=" { return SETPIPE; }
"+=" { return SETPLUS; } "+=" { return SETPLUS; }
"-=" { return SETMINUS; } "-=" { return SETMINUS; }

1
main.c
View File

@@ -351,6 +351,7 @@ int main(int argc, char* argv[]) {
options |= EXIT_STATUS; options |= EXIT_STATUS;
if (!short_opts) continue; if (!short_opts) continue;
} }
// FIXME: For --arg* we should check that the varname is acceptable
if (isoption(argv[i], 0, "arg", &short_opts)) { if (isoption(argv[i], 0, "arg", &short_opts)) {
if (i >= argc - 2) { if (i >= argc - 2) {
fprintf(stderr, "%s: --arg takes two parameters (e.g. -a varname value)\n", progname); fprintf(stderr, "%s: --arg takes two parameters (e.g. -a varname value)\n", progname);

View File

@@ -40,3 +40,4 @@ OP(TOP, NONE, 0, 0)
OP(CLOSURE_PARAM_REGULAR, DEFINITION, 0, 0) OP(CLOSURE_PARAM_REGULAR, DEFINITION, 0, 0)
OP(DEPS, CONSTANT, 0, 0) OP(DEPS, CONSTANT, 0, 0)
OP(MODULEMETA, CONSTANT, 0, 0) OP(MODULEMETA, CONSTANT, 0, 0)
OP(GENLABEL, NONE, 0, 1)

View File

@@ -70,6 +70,10 @@ struct lexer_param;
%token OR "or" %token OR "or"
%token TRY "try" %token TRY "try"
%token CATCH "catch" %token CATCH "catch"
%token LABEL "label"
%token BREAK "break"
%token BREAK2 "break2"
%token BREAK3 "break3"
%token SETPIPE "|=" %token SETPIPE "|="
%token SETPLUS "+=" %token SETPLUS "+="
%token SETMINUS "-=" %token SETMINUS "-="
@@ -149,6 +153,8 @@ int yylex(YYSTYPE* yylval, YYLTYPE* yylloc, block* answer, int* errors,
return tok; return tok;
} }
static unsigned int next_label = 0;
static block gen_dictpair(block k, block v) { static block gen_dictpair(block k, block v) {
return BLOCK(gen_subexp(k), gen_subexp(v), gen_op_simple(INSERT)); return BLOCK(gen_subexp(k), gen_subexp(v), gen_op_simple(INSERT));
} }
@@ -335,7 +341,7 @@ Term "as" '$' IDENT '|' Exp {
"try" Exp "catch" Exp { "try" Exp "catch" Exp {
//$$ = BLOCK(gen_op_target(FORK_OPT, $2), $2, $4); //$$ = BLOCK(gen_op_target(FORK_OPT, $2), $2, $4);
$$ = gen_try($2, $4); $$ = gen_try($2, gen_try_handler($4));
} | } |
"try" Exp { "try" Exp {
//$$ = BLOCK(gen_op_target(FORK_OPT, $2), $2, gen_op_simple(BACKTRACK)); //$$ = BLOCK(gen_op_target(FORK_OPT, $2), $2, gen_op_simple(BACKTRACK));
@@ -346,6 +352,19 @@ Term "as" '$' IDENT '|' Exp {
$$ = $2; $$ = $2;
} | } |
"label" '|' Exp {
jv v = jv_string_fmt("*anonlabel%u", next_label++);
$$ = gen_location(@$, locations, gen_label(jv_string_value(v), $3));
jv_free(v);
} |
"label" '$' IDENT '|' Exp {
jv v = jv_string_fmt("*label-%s", jv_string_value($3));
$$ = gen_location(@$, locations, gen_label(jv_string_value(v), $5));
jv_free($3);
jv_free(v);
} |
// This rule conflicts with all the other rules using the '?' operator. // This rule conflicts with all the other rules using the '?' operator.
// It doesn't matter which bison prefers: all of them result in the same // It doesn't matter which bison prefers: all of them result in the same
// syntax and semantics, but the more specific rules optimize better // syntax and semantics, but the more specific rules optimize better
@@ -570,6 +589,29 @@ Term:
REC { REC {
$$ = gen_call("recurse", gen_noop()); $$ = gen_call("recurse", gen_noop());
} | } |
BREAK {
$$ = gen_location(@$, locations,
BLOCK(gen_op_unbound(LOADV, "*1"), // impossible symbol
gen_call("error", gen_noop())));
} |
BREAK2 {
$$ = gen_location(@$, locations,
BLOCK(gen_op_unbound(LOADV, "*2"), // impossible symbol
gen_call("error", gen_noop())));
} |
BREAK3 {
$$ = gen_location(@$, locations,
BLOCK(gen_op_unbound(LOADV, "*3"), // impossible symbol
gen_call("error", gen_noop())));
} |
BREAK '$' IDENT {
jv v = jv_string_fmt("*label-%s", jv_string_value($3)); // impossible symbol
$$ = gen_location(@$, locations,
BLOCK(gen_op_unbound(LOADV, jv_string_value(v)),
gen_call("error", gen_noop())));
jv_free(v);
jv_free($3);
} |
Term FIELD '?' { Term FIELD '?' {
$$ = gen_index_opt($1, gen_const($2)); $$ = gen_index_opt($1, gen_const($2));
} | } |

View File

@@ -247,15 +247,35 @@ null
1 1
[1,2,4,8,16,32,64] [1,2,4,8,16,32,64]
[while(.<100; .*2|if . > 10 then break else . end)] [label | while(.<100; .*2|if . > 10 then break else . end)]
1 1
[1,2,4,8] [1,2,4,8]
[(label $here | .[] | if .>1 then break $here else . end), "hi!"]
[0,1,2]
[0,1,"hi!"]
[(label $here | .[] | if .>1 then break $here else . end), "hi!"]
[0,2,1]
[0,"hi!"]
(label | (label | 2 | break2)), 1
null
1
%%FAIL
break
jq: error: break used outside labeled control structure
%%FAIL
. as $foo | break $foo
jq: error: *label-foo/0 is not defined
[.[]|[.,1]|until(.[0] < 1; [.[0] - 1, .[1] * .[0]])|.[1]] [.[]|[.,1]|until(.[0] < 1; [.[0] - 1, .[1] * .[0]])|.[1]]
[1,2,3,4,5] [1,2,3,4,5]
[1,2,6,24,120] [1,2,6,24,120]
[foreach .[] as $item ([3, null]; if .[0] < 1 then break else [.[0] -1, $item] end; .[1])] [label | foreach .[] as $item ([3, null]; if .[0] < 1 then break else [.[0] -1, $item] end; .[1])]
[11,22,33,44,55,66,77,88,99] [11,22,33,44,55,66,77,88,99]
[11,22,33] [11,22,33]