array and object destructuring (fix #533)

`. as [$i, $j, $k] | ...`
`. as {a: $i, b: $j} | ...`
`. as [[[$i]], {a: $j}] | ...`
`foreach . as [$i, $j, $k] (...)`
`reduce . as {a: $i, b: $j} (...)`
This commit is contained in:
David Tolnay authored and Nicolas Williams committed 2015-06-09 17:29:48 -05:00
1 parent 2e96fb2c9b
commit d3343d5113
6 files changed
+1148 -869

No files matched your search

+70 -35
View File
@@ -675,15 +675,25 @@ block gen_collect(block expr) {
gen_op_bound(LOADVN, array_var));
}
block gen_reduce(const char* varname, block source, block init, block body) {
static block bind_matcher(block matcher, block body) {
// cannot call block_bind(matcher, body) because that requires
// block_has_only_binders(matcher), which is not true here as matchers
// may also contain code to extract the correct elements
for (inst* i = matcher.first; i; i = i->next) {
if (i->op == STOREV && !i->bound_by)
block_bind_subblock(inst_block(i), body, OP_HAS_VARIABLE, 0);
}
return BLOCK(matcher, body);
}
block gen_reduce(block source, block matcher, block init, block body) {
block res_var = gen_op_var_fresh(STOREV, "reduce");
block loop = BLOCK(gen_op_simple(DUPN),
source,
block_bind(gen_op_unbound(STOREV, varname),
BLOCK(gen_op_bound(LOADVN, res_var),
body,
gen_op_bound(STOREV, res_var)),
OP_HAS_VARIABLE),
bind_matcher(matcher,
BLOCK(gen_op_bound(LOADVN, res_var),
body,
gen_op_bound(STOREV, res_var))),
gen_op_simple(BACKTRACK));
return BLOCK(gen_op_simple(DUP),
init,
@@ -693,37 +703,36 @@ block gen_reduce(const char* varname, block source, block init, block body) {
gen_op_bound(LOADVN, res_var));
}
block gen_foreach(const char* varname, block source, block init, block update, block extract) {
block gen_foreach(block source, block matcher, block init, block update, block extract) {
block output = gen_op_targetlater(JUMP);
block state_var = gen_op_var_fresh(STOREV, "foreach");
block loop = BLOCK(gen_op_simple(DUPN),
// get a value from the source expression:
source,
// bind the $varname to that value for all the code in
// this block_bind() to see:
block_bind(gen_op_unbound(STOREV, varname),
// load the loop state variable
BLOCK(gen_op_bound(LOADVN, state_var),
// generate updated state
update,
// save the updated state for value extraction
gen_op_simple(DUP),
// save new state
gen_op_bound(STOREV, state_var),
// extract an output...
extract,
// ...and output it by jumping
// past the BACKTRACK that comes
// right after the loop body,
// which in turn is there
// because...
//
// (Incidentally, extract can also
// backtrack, e.g., if it calls
// empty, in which case we don't
// get here.)
output),
OP_HAS_VARIABLE));
// destructure the value into variable(s) for all the code
// in the body to see
bind_matcher(matcher,
// load the loop state variable
BLOCK(gen_op_bound(LOADVN, state_var),
// generate updated state
update,
// save the updated state for value extraction
gen_op_simple(DUP),
// save new state
gen_op_bound(STOREV, state_var),
// extract an output...
extract,
// ...and output it by jumping
// past the BACKTRACK that comes
// right after the loop body,
// which in turn is there
// because...
//
// (Incidentally, extract can also
// backtrack, e.g., if it calls
// empty, in which case we don't
// get here.)
output)));
block foreach = BLOCK(gen_op_simple(DUP),
init,
state_var,
@@ -809,13 +818,39 @@ block gen_or(block a, block b) {
}
block gen_var_binding(block var, const char* name, block body) {
return gen_destructure(var, gen_op_unbound(STOREV, name), body);
}
block gen_array_matcher(block left, block curr) {
int index;
if (block_is_noop(left))
index = 0;
else {
// `left` was returned by this function, so the third inst is the
// constant containing the previously used index
assert(left.first->op == DUP);
assert(left.first->next->op == SUBEXP_BEGIN);
assert(left.first->next->next->op == LOADK);
index = 1 + (int) jv_number_value(left.first->next->next->imm.constant);
}
// `left` goes at the end so that the const index is in a predictable place
return BLOCK(gen_op_simple(DUP), gen_subexp(gen_const(jv_number(index))),
gen_op_simple(INDEX), curr, left);
}
block gen_object_matcher(block name, block curr) {
return BLOCK(gen_op_simple(DUP), gen_subexp(name), gen_op_simple(INDEX),
curr);
}
block gen_destructure(block var, block matcher, block body) {
// var bindings can be added after coding the program; leave the TOP first.
block top = gen_noop();
if (body.first && body.first->op == TOP)
top = inst_block(block_take(&body));
return BLOCK(top, gen_op_simple(DUP), var,
block_bind(gen_op_unbound(STOREV, name),
body, OP_HAS_VARIABLE));
return BLOCK(top, gen_op_simple(DUP), var, bind_matcher(matcher, body));
}
// Like gen_var_binding(), but bind `break`'s wildcard unbound variable
+5 -2
View File
@@ -41,14 +41,17 @@ block gen_subexp(block a);
block gen_both(block a, block b);
block gen_const_object(block expr);
block gen_collect(block expr);
block gen_reduce(const char* varname, block source, block init, block body);
block gen_foreach(const char* varname, block source, block init, block update, block extract);
block gen_reduce(block source, block matcher, block init, block body);
block gen_foreach(block source, block matcher, block init, block update, block extract);
block gen_definedor(block a, block b);
block gen_condbranch(block iftrue, block iffalse);
block gen_and(block a, block b);
block gen_or(block a, block b);
block gen_var_binding(block var, const char* name, block body);
block gen_array_matcher(block left, block curr);
block gen_object_matcher(block name, block curr);
block gen_destructure(block var, block matcher, block body);
block gen_cond(block cond, block iftrue, block iffalse);
block gen_try_handler(block handler);
+8
View File
@@ -2147,6 +2147,11 @@ sections:
with `$x` set to that value. Thus `as` functions as something of a
foreach loop.
Multiple variables may be declared using a single `as` expression by
providing a pattern that matches the structure of the input:
. as {realnames: $names, posts: [$first, $second]} | ...
Variables are scoped over the rest of the expression that defines
them, so
@@ -2171,6 +2176,9 @@ sections:
- program: '. as $i|[(.*2|. as $i| $i), $i]'
input: '5'
output: ['[10,5]']
- program: '. as [$a, $b, {c: $c}] | $a + $b + $c'
input: '[2, 3, {c: 4, d: 5}]'
output: ['9']
- title: 'Defining Functions'
body: |
+967 -819
View File
File diff suppressed because it is too large. Load diff
+51 -13
View File
@@ -107,7 +107,7 @@ struct lexer_param;
%type <blk> Exp Term MkDict MkDictPair ExpD ElseBody QQString
%type <blk> FuncDef FuncDefs String Import Imports Param Params
%type <blk> Arg Args Module
%type <blk> Arg Args Module Pattern ArrayPats ObjPats ObjPat
%type <literal> Keyword
%{
#include "lexer.h"
@@ -308,24 +308,20 @@ FuncDef Exp %prec ';' {
$$ = block_bind_referenced($1, $2, OP_IS_CALL_PSEUDO);
} |
Term "as" '$' IDENT '|' Exp {
$$ = gen_var_binding($1, jv_string_value($4), $6);
jv_free($4);
Term "as" Pattern '|' Exp {
$$ = gen_destructure($1, $3, $5);
} |
"reduce" Term "as" '$' IDENT '(' Exp ';' Exp ')' {
$$ = gen_reduce(jv_string_value($5), $2, $7, $9);
jv_free($5);
"reduce" Term "as" Pattern '(' Exp ';' Exp ')' {
$$ = gen_reduce($2, $4, $6, $8);
} |
"foreach" Term "as" '$' IDENT '(' Exp ';' Exp ';' Exp ')' {
$$ = gen_foreach(jv_string_value($5), $2, $7, $9, $11);
jv_free($5);
"foreach" Term "as" Pattern '(' Exp ';' Exp ';' Exp ')' {
$$ = gen_foreach($2, $4, $6, $8, $10);
} |
"foreach" Term "as" '$' IDENT '(' Exp ';' Exp ')' {
$$ = gen_foreach(jv_string_value($5), $2, $7, $9, gen_noop());
jv_free($5);
"foreach" Term "as" Pattern '(' Exp ';' Exp ')' {
$$ = gen_foreach($2, $4, $6, $8, gen_noop());
} |
"if" Exp "then" Exp ElseBody {
@@ -736,6 +732,48 @@ Exp {
$$ = gen_lambda($1);
}
Pattern:
'$' IDENT {
$$ = gen_op_unbound(STOREV, jv_string_value($2));
jv_free($2);
} |
'[' ArrayPats ']' {
$$ = BLOCK($2, gen_op_simple(POP));
} |
'{' ObjPats '}' {
$$ = BLOCK($2, gen_op_simple(POP));
}
ArrayPats:
Pattern {
$$ = gen_array_matcher(gen_noop(), $1);
} |
ArrayPats ',' Pattern {
$$ = gen_array_matcher($1, $3);
}
ObjPats:
ObjPat {
$$ = $1;
} |
ObjPats ',' ObjPat {
$$ = BLOCK($1, $3);
}
ObjPat:
IDENT ':' Pattern {
$$ = gen_object_matcher(gen_const($1), $3);
} |
Keyword ':' Pattern {
$$ = gen_object_matcher(gen_const($1), $3);
} |
String ':' Pattern {
$$ = gen_object_matcher($1, $3);
} |
'(' Exp ')' ':' Pattern {
$$ = gen_object_matcher($2, $5);
}
Keyword:
"as" {
$$ = jv_string("as");
+47
View File
@@ -271,6 +271,14 @@ jq: error: *label-foo/0 is not defined at <top-level>, line 1:
null
[0,1,2,3,4]
[foreach .[] as [$i, $j] (0; . + $i - $j)]
[[2,1], [5,3], [6,4]]
[1,3,5]
[foreach .[] as {a:$a} (0; . + $a; -.)]
[{"a":1}, {"b":2}, {"a":3, "b":4}]
[-1, -1, -4]
[limit(3; .[])]
[11,22,33,44,55,66,77,88,99]
[11,22,33]
@@ -373,6 +381,37 @@ null
null
[1,1,1]
[1, {c:3, d:4}] as [$a, {c:$b, b:$c}] | $a, $b, $c
null
1
3
null
. as {as: $kw, "str": $str, ("e"+"x"+"p"): $exp} | [$kw, $str, $exp]
{"as": 1, "str": 2, "exp": 3}
[1, 2, 3]
.[] as [$a, $b] | [$b, $a]
[[1], [1, 2, 3]]
[null, 1]
[2, 1]
. as $i | . as [$i] | $i
[0]
0
. as [$i] | . as $i | $i
[0]
[0]
%%FAIL
. as [] | null
jq: error: syntax error, unexpected ']', expecting '$' or '[' or '{' (Unix shell quoting issues?) at <top-level>, line 1:
%%FAIL
. as {} | null
jq: error: syntax error, unexpected '}' (Unix shell quoting issues?) at <top-level>, line 1:
# [.,(.[] | {x:.},.),.,.[]]
#
@@ -610,6 +649,14 @@ reduce .[] as $x (0; . + $x)
[1,2,4]
7
reduce .[] as [$i, {j:$j}] (0; . + $i - $j)
[[2,{"j":1}], [5,{"j":3}], [6,{"j":4}]]
5
reduce [[1,2,10], [3,4,10]][] as [$i,$j] (0; . + $i * $j)
null
14
. as $dot|any($dot[];not)
[1,2,3,4,true,false,1,2,3,4,5]
true