Skip to content

Commit 85762d2

Browse files
committed
Implement range/3 in C for consistent error handling
This commit extends the `RANGE` opcode to implement `range/3` in C. This changes `range/3` to reject non-number arguments like `range/1` and `range/2`, so fixes #3116.
1 parent 8ba03f7 commit 85762d2

File tree

4 files changed

+46
-30
lines changed

4 files changed

+46
-30
lines changed

src/builtin.c

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,21 +2006,19 @@ static block bind_bytecoded_builtins(block b) {
20062006
}
20072007
}
20082008
{
2009-
// Note that we can now define `range` as a jq-coded function
20102009
block rangevar = gen_op_var_fresh(STOREV, "rangevar");
2011-
block rangestart = gen_op_var_fresh(STOREV, "rangestart");
2012-
block range = BLOCK(gen_op_simple(DUP),
2013-
gen_call("start", gen_noop()),
2014-
rangestart,
2015-
gen_call("end", gen_noop()),
2016-
gen_op_simple(DUP),
2017-
gen_op_bound(LOADV, rangestart),
2018-
// Reset rangevar for every value generated by "end"
2019-
rangevar,
2020-
gen_op_bound(RANGE, rangevar));
2021-
builtins = BLOCK(builtins, gen_function("range",
2022-
BLOCK(gen_param("start"), gen_param("end")),
2023-
range));
2010+
builtins = BLOCK(builtins,
2011+
gen_function("range",
2012+
BLOCK(gen_param_regular("start"),
2013+
gen_param_regular("end"),
2014+
gen_param_regular("step")),
2015+
BLOCK(gen_op_unbound(LOADV, "step"),
2016+
gen_op_simple(DUP),
2017+
gen_op_unbound(LOADV, "end"),
2018+
gen_op_simple(DUP),
2019+
gen_op_unbound(LOADV, "start"),
2020+
rangevar,
2021+
gen_op_bound(RANGE, rangevar))));
20242022
}
20252023
return BLOCK(builtins, b);
20262024
}

src/builtin.jq

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def recurse: recurse(.[]?);
4242
def to_entries: [keys_unsorted[] as $k | {key: $k, value: .[$k]}];
4343
def from_entries: map({(.key // .Key // .name // .Name): (if has("value") then .value else .Value end)}) | add | .//={};
4444
def with_entries(f): to_entries | map(f) | from_entries;
45-
def reverse: [.[length - 1 - range(0;length)]];
45+
def reverse: [.[range(length-1;-1;-1)]];
4646
def indices($i): if type == "array" and ($i|type) == "array" then .[$i]
4747
elif type == "array" then .[[$i]]
4848
elif type == "string" and ($i|type) == "string" then _strindices($i)
@@ -70,6 +70,7 @@ def join($x): reduce .[] as $i (null;
7070
def _flatten($x): reduce .[] as $i ([]; if $i | type == "array" and $x != 0 then . + ($i | _flatten($x-1)) else . + [$i] end);
7171
def flatten($x): if $x < 0 then error("flatten depth must not be negative") else _flatten($x) end;
7272
def flatten: _flatten(-1);
73+
def range($x;$y): range($x;$y;1);
7374
def range($x): range(0;$x);
7475
def fromdateiso8601: strptime("%Y-%m-%dT%H:%M:%SZ")|mktime;
7576
def todateiso8601: strftime("%Y-%m-%dT%H:%M:%SZ");
@@ -157,11 +158,6 @@ def skip($n; expr):
157158
if $n > 0 then foreach expr as $item ($n; . - 1; if . < 0 then $item else empty end)
158159
elif $n == 0 then expr
159160
else error("skip doesn't support negative count") end;
160-
# range/3, with a `by` expression argument
161-
def range($init; $upto; $by):
162-
if $by > 0 then $init|while(. < $upto; . + $by)
163-
elif $by < 0 then $init|while(. > $upto; . + $by)
164-
else empty end;
165161
def first(g): label $out | g | ., break $out;
166162
def isempty(g): first((g|false), true);
167163
def all(generator; condition): isempty(generator|condition and empty);

src/execute.c

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@
1010
#include "bytecode.h"
1111

1212
#include "jv_alloc.h"
13-
#include "jq_parser.h"
1413
#include "locfile.h"
1514
#include "jv.h"
1615
#include "jq.h"
17-
#include "parser.h"
1816
#include "builtin.h"
19-
#include "util.h"
2017
#include "linker.h"
2118

2219
struct jq_state {
@@ -518,24 +515,29 @@ jv jq_next(jq_state *jq) {
518515
uint16_t v = *pc++;
519516
jv* var = frame_local_var(jq, v, level);
520517
jv max = stack_pop(jq);
518+
jv step = stack_pop(jq);
521519
if (raising) {
522520
jv_free(max);
521+
jv_free(step);
523522
goto do_backtrack;
524-
}
525-
if (jv_get_kind(*var) != JV_KIND_NUMBER ||
526-
jv_get_kind(max) != JV_KIND_NUMBER) {
527-
set_error(jq, jv_invalid_with_msg(jv_string_fmt("Range bounds must be numeric")));
523+
}
524+
525+
if (jv_get_kind(*var) != JV_KIND_NUMBER || jv_get_kind(max) != JV_KIND_NUMBER ||
526+
jv_get_kind(step) != JV_KIND_NUMBER) {
527+
set_error(jq, jv_invalid_with_msg(jv_string("Range bounds and step must be numeric")));
528528
jv_free(max);
529+
jv_free(step);
529530
goto do_backtrack;
530-
} else if (jv_number_value(*var) >= jv_number_value(max)) {
531-
/* finished iterating */
531+
} else if (jv_cmp(jv_copy(*var), jv_copy(max)) * jv_cmp(jv_copy(step), jv_number(0)) >= 0) {
532532
jv_free(max);
533+
jv_free(step);
533534
goto do_backtrack;
534535
} else {
535536
jv curr = *var;
536-
*var = jv_number(jv_number_value(*var) + 1);
537+
*var = jv_number(jv_number_value(curr) + jv_number_value(step));
537538

538539
struct stack_pos spos = stack_get_pos(jq);
540+
stack_push(jq, step);
539541
stack_push(jq, max);
540542
stack_save(jq, pc - 3, spos);
541543

tests/jq.test

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,30 @@ null
282282
null
283283
[0,-1,-2,-3,-4]
284284

285+
[range(0;-10;-3)]
286+
null
287+
[0,-3,-6,-9]
288+
289+
[range(0;10;0)]
290+
null
291+
[]
292+
285293
[range(0,1;4,5;1,2)]
286294
null
287295
[0,1,2,3,0,2, 0,1,2,3,4,0,2,4, 1,2,3,1,3, 1,2,3,4,1,3]
288296

297+
try range(.) catch .
298+
null
299+
"Range bounds and step must be numeric"
300+
301+
try range(0;"") catch .
302+
null
303+
"Range bounds and step must be numeric"
304+
305+
try range(0;1;[]) catch .
306+
null
307+
"Range bounds and step must be numeric"
308+
289309
[while(.<100; .*2)]
290310
1
291311
[1,2,4,8,16,32,64]

0 commit comments

Comments
 (0)