Add ?, .[]?, and .. operators

Make XPath-like `//a/b` recursive structure traversal easier in jq,
which then becomes:

    ..|.a?.b?

The `?` operator suppresses errors about . not being an array or object.
The `..` operator is equivalent to calling the new `recurse_down`
built-in, which in turn is equivalent to

    recurse(.[]?)

Note that `..a` is not supported; neither is `...a`.  That could be add
added, but it doesn't seem worth the trouble of saving the need to type
a '|'.
This commit is contained in:
Nicolas Williams committed 2014-02-20 15:33:07 -06:00
1 parent 3d33412e9a
commit fe29d3d3fa
7 files changed
+130 -14

No files matched your search

+1
View File
@@ -672,6 +672,7 @@ static const char* const jq_builtins[] = {
"def _assign(paths; value): value as $v | reduce path(paths) as $p (.; setpath($p; $v));", "def _assign(paths; value): value as $v | reduce path(paths) as $p (.; setpath($p; $v));",
"def _modify(paths; update): reduce path(paths) as $p (.; setpath($p; getpath($p) | update));", "def _modify(paths; update): reduce path(paths) as $p (.; setpath($p; getpath($p) | update));",
"def recurse(f): ., (f | select(. != null) | recurse(f));", "def recurse(f): ., (f | select(. != null) | recurse(f));",
"def recurse_down: recurse(.[]?);",
"def to_entries: [keys[] as $k | {key: $k, value: .[$k]}];", "def to_entries: [keys[] as $k | {key: $k, value: .[$k]}];",
"def from_entries: map({(.key): .value}) | add;", "def from_entries: map({(.key): .value}) | add;",
"def with_entries(f): to_entries | map(f) | from_entries;", "def with_entries(f): to_entries | map(f) | from_entries;",
+48
View File
@@ -217,6 +217,26 @@ sections:
input: '{"foo": 42}' input: '{"foo": 42}'
output: [42] output: [42]
- title: "`.foo?`"
body: |
Just like `.foo`, but does not output even an error when `.`
is not an array or an object.
examples:
- program: '.foo?'
input: '{"foo": 42, "bar": "less interesting data"}'
output: [42]
- program: '.foo?'
input: '{"notfoo": true, "alsonotfoo": false}'
output: ['null']
- program: '.["foo"]?'
input: '{"foo": 42}'
output: [42]
- program: '[.foo?]'
input: '[1,2]'
output: ['[]']
- title: "`.[<string>]`, `.[2]`, `.[10:15]`" - title: "`.[<string>]`, `.[2]`, `.[10:15]`"
body: | body: |
@@ -234,6 +254,10 @@ sections:
the array), or omitted (in which case it refers to the start the array), or omitted (in which case it refers to the start
or end of the array). or end of the array).
The `?` "operator" can also be used with the slice operator,
as in `.[10:15]?`, which outputs values where the inputs are
slice-able.
examples: examples:
- program: '.[0]' - program: '.[0]'
input: '[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]' input: '[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]'
@@ -286,6 +310,12 @@ sections:
input: '{"a": 1, "b": 1}' input: '{"a": 1, "b": 1}'
output: ['1', '1'] output: ['1', '1']
- title: "`.[]?`"
body: |
Like `.[]`, but no errors will be output if . is not an array
or object.
- title: "`,`" - title: "`,`"
body: | body: |
@@ -1022,6 +1052,24 @@ sections:
- '{"foo":[{"foo":[]}]}' - '{"foo":[{"foo":[]}]}'
- '{"foo":[]}' - '{"foo":[]}'
- title: `recurse_down`
body: |
A quieter version of `recurse(.[])`, equivalent to:
def recurse_down: recurse(.[]?);
- title: `..`
body: |
Short-hand for `recurse_down`. This is intended to resemble
the XPath `//` operator. Note that `..a` does not work; use
`..|a` instead.
examples:
- program: '..|.a?'
input: '[[{"a":1}]]'
output: ['1']
- title: "String interpolation - `\(foo)`" - title: "String interpolation - `\(foo)`"
body: | body: |
+17 -8
View File
@@ -503,7 +503,8 @@ jv jq_next(jq_state *jq) {
goto do_backtrack; goto do_backtrack;
} }
case INDEX: { case INDEX:
case INDEX_OPT: {
jv t = stack_pop(jq); jv t = stack_pop(jq);
jv k = stack_pop(jq); jv k = stack_pop(jq);
path_append(jq, jv_copy(k)); path_append(jq, jv_copy(k));
@@ -511,7 +512,10 @@ jv jq_next(jq_state *jq) {
if (jv_is_valid(v)) { if (jv_is_valid(v)) {
stack_push(jq, v); stack_push(jq, v);
} else { } else {
print_error(jq, v); if (opcode == INDEX)
print_error(jq, v);
else
jv_free(v);
goto do_backtrack; goto do_backtrack;
} }
break; break;
@@ -536,16 +540,18 @@ jv jq_next(jq_state *jq) {
} }
case EACH: case EACH:
case EACH_OPT:
stack_push(jq, jv_number(-1)); stack_push(jq, jv_number(-1));
// fallthrough // fallthrough
case ON_BACKTRACK(EACH): { case ON_BACKTRACK(EACH):
case ON_BACKTRACK(EACH_OPT): {
int idx = jv_number_value(stack_pop(jq)); int idx = jv_number_value(stack_pop(jq));
jv container = stack_pop(jq); jv container = stack_pop(jq);
int keep_going, is_last = 0; int keep_going, is_last = 0;
jv key, value; jv key, value;
if (jv_get_kind(container) == JV_KIND_ARRAY) { if (jv_get_kind(container) == JV_KIND_ARRAY) {
if (opcode == EACH) idx = 0; if (opcode == EACH || opcode == EACH_OPT) idx = 0;
else idx = idx + 1; else idx = idx + 1;
int len = jv_array_length(jv_copy(container)); int len = jv_array_length(jv_copy(container));
keep_going = idx < len; keep_going = idx < len;
@@ -555,7 +561,7 @@ jv jq_next(jq_state *jq) {
value = jv_array_get(jv_copy(container), idx); value = jv_array_get(jv_copy(container), idx);
} }
} else if (jv_get_kind(container) == JV_KIND_OBJECT) { } else if (jv_get_kind(container) == JV_KIND_OBJECT) {
if (opcode == EACH) idx = jv_object_iter(container); if (opcode == EACH || opcode == EACH_OPT) idx = jv_object_iter(container);
else idx = jv_object_iter_next(container, idx); else idx = jv_object_iter_next(container, idx);
keep_going = jv_object_iter_valid(container, idx); keep_going = jv_object_iter_valid(container, idx);
if (keep_going) { if (keep_going) {
@@ -563,9 +569,12 @@ jv jq_next(jq_state *jq) {
value = jv_object_iter_value(container, idx); value = jv_object_iter_value(container, idx);
} }
} else { } else {
assert(opcode == EACH); assert(opcode == EACH || opcode == EACH_OPT);
print_error(jq, jv_invalid_with_msg(jv_string_fmt("Cannot iterate over %s", if (opcode == EACH) {
jv_kind_name(jv_get_kind(container))))); print_error(jq,
jv_invalid_with_msg(jv_string_fmt("Cannot iterate over %s",
jv_kind_name(jv_get_kind(container)))));
}
keep_going = 0; keep_going = 0;
} }
+2 -1
View File
@@ -61,7 +61,8 @@ struct lexer_param;
"//=" { return SETDEFINEDOR; } "//=" { return SETDEFINEDOR; }
"<=" { return LESSEQ; } "<=" { return LESSEQ; }
">=" { return GREATEREQ; } ">=" { return GREATEREQ; }
"."|"="|";"|","|":"|"|"|"+"|"-"|"*"|"/"|"%"|"\$"|"<"|">" { return yytext[0];} ".." { return REC; }
"."|"?"|"="|";"|","|":"|"|"|"+"|"-"|"*"|"/"|"%"|"\$"|"<"|">" { return yytext[0];}
"["|"{"|"(" { "["|"{"|"(" {
return enter(yytext[0], YY_START, yyscanner); return enter(yytext[0], YY_START, yyscanner);
+2
View File
@@ -6,7 +6,9 @@ OP(LOADV, VARIABLE, 1, 1)
OP(LOADVN, VARIABLE, 1, 1) OP(LOADVN, VARIABLE, 1, 1)
OP(STOREV, VARIABLE, 1, 0) OP(STOREV, VARIABLE, 1, 0)
OP(INDEX, NONE, 2, 1) OP(INDEX, NONE, 2, 1)
OP(INDEX_OPT, NONE, 2, 1)
OP(EACH, NONE, 1, 1) OP(EACH, NONE, 1, 1)
OP(EACH_OPT, NONE, 1, 1)
OP(FORK, BRANCH, 0, 0) OP(FORK, 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)
+41 -5
View File
@@ -50,6 +50,8 @@ struct lexer_param;
%token <literal> FIELD %token <literal> FIELD
%token <literal> LITERAL %token <literal> LITERAL
%token <literal> FORMAT %token <literal> FORMAT
%token Q "?"
%token REC ".."
%token SETMOD "%=" %token SETMOD "%="
%token EQ "==" %token EQ "=="
%token NEQ "!=" %token NEQ "!="
@@ -137,7 +139,11 @@ static block gen_index(block obj, block key) {
return BLOCK(gen_subexp(key), obj, gen_op_simple(INDEX)); return BLOCK(gen_subexp(key), obj, gen_op_simple(INDEX));
} }
static block gen_slice_index(block obj, block start, block end) { static block gen_index_opt(block obj, block key) {
return BLOCK(gen_subexp(key), obj, gen_op_simple(INDEX_OPT));
}
static block gen_slice_index(block obj, block start, block end, opcode idx_op) {
block key = BLOCK(gen_subexp(gen_const(jv_object())), block key = BLOCK(gen_subexp(gen_const(jv_object())),
gen_subexp(gen_const(jv_string("start"))), gen_subexp(gen_const(jv_string("start"))),
gen_subexp(start), gen_subexp(start),
@@ -145,7 +151,7 @@ static block gen_slice_index(block obj, block start, block end) {
gen_subexp(gen_const(jv_string("end"))), gen_subexp(gen_const(jv_string("end"))),
gen_subexp(end), gen_subexp(end),
gen_op_simple(INSERT)); gen_op_simple(INSERT));
return BLOCK(key, obj, gen_op_simple(INDEX)); return BLOCK(key, obj, gen_op_simple(idx_op));
} }
static block gen_binop(block a, block b, int op) { static block gen_binop(block a, block b, int op) {
@@ -470,6 +476,21 @@ Term:
'.' { '.' {
$$ = gen_noop(); $$ = gen_noop();
} | } |
REC {
$$ = gen_call("recurse_down", gen_noop());
} |
Term FIELD '?' {
$$ = gen_index_opt($1, gen_const($2));
} |
FIELD '?' {
$$ = gen_index_opt(gen_noop(), gen_const($1));
} |
Term '.' String '?' {
$$ = gen_index_opt($1, $3);
} |
'.' String '?' {
$$ = gen_index_opt(gen_noop(), $2);
} |
Term FIELD { Term FIELD {
$$ = gen_index($1, gen_const($2)); $$ = gen_index($1, gen_const($2));
} | } |
@@ -492,20 +513,35 @@ Term '.' String {
$$ = gen_noop(); $$ = gen_noop();
} | } |
/* FIXME: string literals */ /* FIXME: string literals */
Term '[' Exp ']' '?' {
$$ = gen_index_opt($1, $3);
} |
Term '[' Exp ']' { Term '[' Exp ']' {
$$ = gen_index($1, $3); $$ = gen_index($1, $3);
} | } |
Term '[' ']' '?' {
$$ = block_join($1, gen_op_simple(EACH_OPT));
} |
Term '[' ']' { Term '[' ']' {
$$ = block_join($1, gen_op_simple(EACH)); $$ = block_join($1, gen_op_simple(EACH));
} | } |
Term '[' Exp ':' Exp ']' '?' {
$$ = gen_slice_index($1, $3, $5, INDEX_OPT);
} |
Term '[' Exp ':' ']' '?' {
$$ = gen_slice_index($1, $3, gen_const(jv_null()), INDEX_OPT);
} |
Term '[' ':' Exp ']' '?' {
$$ = gen_slice_index($1, gen_const(jv_null()), $4, INDEX_OPT);
} |
Term '[' Exp ':' Exp ']' { Term '[' Exp ':' Exp ']' {
$$ = gen_slice_index($1, $3, $5); $$ = gen_slice_index($1, $3, $5, INDEX);
} | } |
Term '[' Exp ':' ']' { Term '[' Exp ':' ']' {
$$ = gen_slice_index($1, $3, gen_const(jv_null())); $$ = gen_slice_index($1, $3, gen_const(jv_null()), INDEX);
} | } |
Term '[' ':' Exp ']' { Term '[' ':' Exp ']' {
$$ = gen_slice_index($1, gen_const(jv_null()), $4); $$ = gen_slice_index($1, gen_const(jv_null()), $4, INDEX);
} | } |
LITERAL { LITERAL {
$$ = gen_const($1); $$ = gen_const($1);
+19
View File
@@ -127,6 +127,25 @@ null
{"foo": {"bar": 20}} {"foo": {"bar": 20}}
20 20
[.[]|.foo?]
[1,[2],{"foo":3,"bar":4},{},{"foo":5}]
[3,null,5]
[.[]|.foo?.bar?]
[1,[2],[],{"foo":3},{"foo":{"bar":4}},{}]
[4,null]
[..]
[1,[[2]],{ "a":[1]}]
[[1,[[2]],{"a":[1]}],1,[[2]],[2],2,{"a":[1]},[1],1]
[.[]|.[]?]
[1,null,[],[1,[2,[[3]]]],[{}],[{"a":[1,[2]]}]]
[1,[2,[[3]]],{},{"a":[1,[2]]}]
[.[]|.[1:3]?]
[1,null,true,false,"abcdef",{},{"a":1,"b":2},[],[1,2,3,4,5],[1,2]]
[null,"bc",[],[2,3],[2]]
# #
# Multiple outputs, iteration # Multiple outputs, iteration