From: Stephen Dolan Date: Fri, 28 Dec 2012 15:04:16 +0000 (+0000) Subject: Fold operation (code/docs/test) X-Git-Tag: jq-1.3~52 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=417899f9a02f8957e30fc6252cf0e02b713c4c4a;p=jq Fold operation (code/docs/test) --- diff --git a/compile.c b/compile.c index 139b64b..178b046 100644 --- a/compile.c +++ b/compile.c @@ -306,6 +306,17 @@ block gen_collect(block expr) { gen_op_var_bound(LOADV, array_var)); } +block gen_fold(const char* varname, block init, block fold) { + block loop = BLOCK(fold, gen_op_var_unbound(STOREV, varname), gen_op_simple(BACKTRACK)); + return BLOCK(gen_op_simple(DUP), + init, + block_bind(gen_op_var_unbound(STOREV, varname), + BLOCK(gen_op_target(FORK, loop), + loop, + gen_op_var_unbound(LOADV, varname)), + OP_HAS_VARIABLE)); +} + block gen_assign(block expr) { block result_var = block_bind(gen_op_var_unbound(STOREV, "result"), gen_noop(), OP_HAS_VARIABLE); diff --git a/compile.h b/compile.h index e74afce..ce1b308 100644 --- a/compile.h +++ b/compile.h @@ -33,6 +33,7 @@ block gen_call(const char* name, block body); block gen_subexp(block a); block gen_both(block a, block b); block gen_collect(block expr); +block gen_fold(const char* varname, block init, block body); block gen_assign(block expr); block gen_definedor(block a, block b); block gen_condbranch(block iftrue, block iffalse); diff --git a/docs/content/3.manual/manual.yml b/docs/content/3.manual/manual.yml index fd91ef1..1de1a38 100644 --- a/docs/content/3.manual/manual.yml +++ b/docs/content/3.manual/manual.yml @@ -841,7 +841,7 @@ sections: input: '{}' output: [42] - - title: Variables and Functions + - title: Advanced features body: | Variables are an absolute necessity in most programming languages, but they're relegated to an "advanced feature" in jq. @@ -858,6 +858,10 @@ sections: (many jq functions such as `map` and `find` are in fact written in jq). + Finally, jq has a `fold` operation, which is very powerful but a + bit tricky. Again, it's mostly used internally, to define some + useful bits of jq's standard library. + entries: - title: Variables body: | @@ -962,6 +966,39 @@ sections: - program: 'def addvalue(f): f as $x | map(. + $x); addvalue(.[0])' input: '[[1,2],[10,20]]' output: ['[[1,2,1,2], [10,20,1,2]]'] + + - title: Fold + body: | + + The `fold` syntax in jq allows you to combine all of the + results of an expression by accumulating them into a single + answer. As an example, we'll pass `[3,2,1]` to this expression: + + fold 0 as $sum (.[] | $sum + .) + + The variable `$sum` is first given the value `0`. The body + of the fold (i.e. `.[] | $sum + .`) is evaluated. `.[]` + produces three results, `3`, `2`, and `1`. For the first + one, `$sum + .` gives `3`. + + Having produced this answer, jq backtracks to find the next + result as per usual. However, this time, `$sum` is set to + the previous value of the body, so `$sum + .` gives + `5`. After the final backtracking, `$sum + .` gives + `6`. This final value is used as the value of the entire + `fold` expression, so the above filter returns `6`. + + More formally, in order to evaluate `fold INIT as $VAR + (BODY)`, jq first sets `$VAR` to the value of `INIT`. It + then runs through `BODY`. Each time `BODY` produces a value, + `$VAR` is set to that value and jq backtracks to find the + next one. When `BODY` stops producing values, the final + value of `$VAR` is the result of the entire expression. + + examples: + - program: 'fold 0 as $sum (.[] | $sum + .)' + input: '[10,2,5,3]' + output: ['20'] - title: Assignment diff --git a/lexer.l b/lexer.l index 12851de..fd0e120 100644 --- a/lexer.l +++ b/lexer.l @@ -49,6 +49,7 @@ struct lexer_param; "and" { return AND; } "or" { return OR; } "end" { return END; } +"fold" { return FOLD; } "//" { return DEFINEDOR; } "|=" { return SETPIPE; } "+=" { return SETPLUS; } diff --git a/parser.y b/parser.y index 397982d..5cc2640 100644 --- a/parser.y +++ b/parser.y @@ -58,6 +58,7 @@ struct lexer_param; %token THEN "then" %token ELSE "else" %token ELSE_IF "elif" +%token FOLD "fold" %token END "end" %token AND "and" %token OR "or" @@ -201,6 +202,11 @@ Term "as" '$' IDENT '|' Exp { jv_free($4); } | +"fold" Term "as" '$' IDENT '(' Exp ')' { + $$ = gen_fold(jv_string_value($5), $2, $7); + jv_free($5); +} | + "if" Exp "then" Exp ElseBody { $$ = gen_cond($2, $4, $5); } | diff --git a/testdata b/testdata index c3352af..bb8e677 100644 --- a/testdata +++ b/testdata @@ -331,6 +331,10 @@ def fac: if . == 1 then 1 else . * (. - 1 | fac) end; [.[] | fac] # [] # 1001 +fold 0 as $s (.[] | $s + .) +[1,2,4] +7 + # # Assignment #