]> granicus.if.org Git - jq/commitdiff
Add `foreach` and `limit`
authorNicolas Williams <nico@cryptonector.com>
Sun, 6 Jul 2014 08:24:29 +0000 (03:24 -0500)
committerNicolas Williams <nico@cryptonector.com>
Mon, 7 Jul 2014 00:40:05 +0000 (19:40 -0500)
builtin.c
compile.c
compile.h
docs/content/3.manual/manual.yml
lexer.l
parser.y
tests/all.test

index 6af998988cfe6c3e64c1064e287f0a1066dc9912..bda2c9ea7c1842c7bcea04679d0379b6b293f151 100644 (file)
--- a/builtin.c
+++ b/builtin.c
@@ -963,6 +963,7 @@ static const char* const jq_builtins[] = {
   "     def _while: "
   "         if cond then ., (update | _while) else empty end; "
   "     _while;",
+  "def limit(n; exp): if n < 0 then exp else foreach exp as $item ([n, null]; if .[0] < 1 then empty else [.[0] -1, $item] end; .[1]) end;",
 };
 #undef LIBM_DD
 
index 788b8d73fe0eecb07d4203457f4e35653a01f8d3..2a1b8b89cc0d8803dbb023ffde901e39aa4da784 100644 (file)
--- a/compile.c
+++ b/compile.c
@@ -374,6 +374,41 @@ 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 output = gen_op_targetlater(JUMP);
+  block state_var = gen_op_var_fresh(STOREV, "foreach");
+  block loop = BLOCK(gen_op_simple(DUP),
+                     // 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
+                                      output),
+                                OP_HAS_VARIABLE));
+  block foreach = BLOCK(gen_op_simple(DUP),
+                        init,
+                        state_var,
+                        gen_op_target(FORK, loop),
+                        loop,
+                        // At this point `foreach`'s input will be on
+                        // top of the stack, and we don't want to output
+                        // it, so we backtrack.
+                        gen_op_simple(BACKTRACK));
+  inst_set_target(output, foreach);
+  return foreach;
+}
+
 block gen_definedor(block a, block b) {
   // var found := false
   block found_var = gen_op_var_fresh(STOREV, "found");
index 1343ab0266dd4720ed7dfefac53fc2cb652c0763..0640a9616dc242637d5fd8571fd8044c87414240 100644 (file)
--- a/compile.h
+++ b/compile.h
@@ -32,6 +32,7 @@ block gen_subexp(block a);
 block gen_both(block a, block b);
 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_definedor(block a, block b);
 block gen_condbranch(block iftrue, block iffalse);
 block gen_and(block a, block b);
index ac0e70e0956308cc3f02a1d0464897623f6beddd..54e727b328d28df79fd686d6b277463b6efd4fd3 100644 (file)
@@ -1777,6 +1777,42 @@ sections:
             input: '[10,2,5,3]'
             output: ['20']
 
+      - title: `limit(n; exp)`
+        body: |
+
+          The `limit` function extracts up to `n` outputs from `exp`.
+
+        examples:
+          - program: '[limit(3;.[])]'
+            input: '[0,1,2,3,4,5,6,7,8,9]'
+            output: ['[0,1,2]']
+
+      - title: `foreach`
+        body: |
+
+          The `foreach` syntax is similar to `reduce`, but intended to
+          allow the construction of `limit` and reducers that produce
+          intermediate results (see example).
+
+          The form is `foreach EXP as $var (INIT; UPDATE; EXTRACT)`.
+          Like `reduce`, `INIT` is evaluated once to produce a state
+          value, then each output of `EXP` is bound to `$var`, `UPDATE`
+          is evaluated for each output of `EXP` with the current state
+          and with `$var` visible.  Each value output by `UPDATE`
+          replaces the previous state.  Finally, `EXTRACT` is evaluated
+          for each new state to extract an output of `foreach`.
+
+          This is mostly useful only for constructing `reduce`- and
+          `limit`-like functions.
+
+        examples:
+          - program: '[foreach .[] as $item
+                        ([[],[]];
+                        if $item == null then [[],.[0]] else [(.[0] + [$item]),[]] end;
+                        if $item == null then .[1] else empty end)]'
+            input: '[1,2,3,4,null,"a","b",null]'
+            output: ['[[1,2,3,4],["a","b"]]']
+
       - title: Recursion
         body: |
 
diff --git a/lexer.l b/lexer.l
index a66867d0bc800121386dd8d857754fb6e04561f3..b51ab1fcf5b20c43162472c21e493889e0426873 100644 (file)
--- a/lexer.l
+++ b/lexer.l
@@ -51,6 +51,7 @@ struct lexer_param;
 "or" { return OR; }
 "end" { return END; }
 "reduce" { return REDUCE; }
+"foreach" { return FOREACH; }
 "//" { return DEFINEDOR; }
 "try" { return TRY; }
 "catch" { return CATCH; }
index eb63381a43d0bc988729ab4a88b557d3a683d2a8..7c53d28563e5487c90e7c0413c432a181345bcc0 100644 (file)
--- a/parser.y
+++ b/parser.y
@@ -63,6 +63,7 @@ struct lexer_param;
 %token ELSE "else"
 %token ELSE_IF "elif"
 %token REDUCE "reduce"
+%token FOREACH "foreach"
 %token END "end"
 %token AND "and"
 %token OR "or"
@@ -234,6 +235,11 @@ Term "as" '$' IDENT '|' Exp {
   jv_free($5);
 } |
 
+"foreach" Term "as" '$' IDENT '(' Exp ';' Exp ';' Exp ')' {
+  $$ = gen_foreach(jv_string_value($5), $2, $7, $9, $11);
+  jv_free($5);
+} |
+
 "if" Exp "then" Exp ElseBody {
   $$ = gen_cond($2, $4, $5);
 } |
index 1fdb10a02178132a8ea2c7281080fb6da234bfd4..e68998b790b7d128a101e8574c12d591260ac918 100644 (file)
@@ -226,6 +226,14 @@ null
 1
 [1,2,4,8,16,32,64]
 
+[foreach .[] as $item ([3, null]; if .[0] < 1 then empty else [.[0] -1, $item] end; .[1])]
+[11,22,33,44,55,66,77,88,99]
+[11,22,33]
+
+[limit(3; .[])]
+[11,22,33,44,55,66,77,88,99]
+[11,22,33]
+
 #
 # Slices
 #