Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support pick(last); add to jq.test #2717

Closed
wants to merge 8 commits into from
92 changes: 46 additions & 46 deletions src/builtin.jq
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ def nulls: select(. == null);
def values: select(. != null);
def scalars: select(type|. != "array" and . != "object");
def join($x): reduce .[] as $i (null;
(if .==null then "" else .+$x end) +
($i | if type=="boolean" or type=="number" then tostring else .//"" end)
) // "";
(if .==null then "" else .+$x end) +
($i | if type=="boolean" or type=="number" then tostring else .//"" end)
) // "";
def _flatten($x): reduce .[] as $i ([]; if $i | type == "array" and $x != 0 then . + ($i | _flatten($x-1)) else . + [$i] end);
def flatten($x): if $x < 0 then error("flatten depth must not be negative") else _flatten($x) end;
def flatten: _flatten(-1);
Expand All @@ -75,25 +75,25 @@ def fromdate: fromdateiso8601;
def todate: todateiso8601;
def match(re; mode): _match_impl(re; mode; false)|.[];
def match($val): ($val|type) as $vt | if $vt == "string" then match($val; null)
elif $vt == "array" and ($val | length) > 1 then match($val[0]; $val[1])
elif $vt == "array" and ($val | length) > 0 then match($val[0]; null)
else error( $vt + " not a string or array") end;
elif $vt == "array" and ($val | length) > 1 then match($val[0]; $val[1])
elif $vt == "array" and ($val | length) > 0 then match($val[0]; null)
else error( $vt + " not a string or array") end;
def test(re; mode): _match_impl(re; mode; true);
def test($val): ($val|type) as $vt | if $vt == "string" then test($val; null)
elif $vt == "array" and ($val | length) > 1 then test($val[0]; $val[1])
elif $vt == "array" and ($val | length) > 0 then test($val[0]; null)
else error( $vt + " not a string or array") end;
elif $vt == "array" and ($val | length) > 1 then test($val[0]; $val[1])
elif $vt == "array" and ($val | length) > 0 then test($val[0]; null)
else error( $vt + " not a string or array") end;
def capture(re; mods): match(re; mods) | reduce ( .captures | .[] | select(.name != null) | { (.name) : .string } ) as $pair ({}; . + $pair);
def capture($val): ($val|type) as $vt | if $vt == "string" then capture($val; null)
elif $vt == "array" and ($val | length) > 1 then capture($val[0]; $val[1])
elif $vt == "array" and ($val | length) > 0 then capture($val[0]; null)
else error( $vt + " not a string or array") end;
elif $vt == "array" and ($val | length) > 1 then capture($val[0]; $val[1])
elif $vt == "array" and ($val | length) > 0 then capture($val[0]; null)
else error( $vt + " not a string or array") end;
def scan($re; $flags):
match($re; "g" + $flags)
| if (.captures|length > 0)
then [ .captures | .[] | .string ]
else .string
end;
| if (.captures|length > 0)
then [ .captures | .[] | .string ]
else .string
end;
def scan($re): scan($re; null);
#
# If input is an array, then emit a stream of successive subarrays of length n (or less),
Expand All @@ -118,17 +118,17 @@ def split($re; flags): [ splits($re; flags) ];
# If s contains capture variables, then create a capture object and pipe it to s, bearing
# in mind that s could be a stream
def sub($re; s; $flags):
. as $in
| (reduce match($re; $flags) as $edit
({result: [], previous: 0};
$in[ .previous: ($edit | .offset) ] as $gap
# create the "capture" objects (one per item in s)
| [reduce ( $edit | .captures | .[] | select(.name != null) | { (.name) : .string } ) as $pair
. as $in
| (reduce match($re; $flags) as $edit
({result: [], previous: 0};
$in[ .previous: ($edit | .offset) ] as $gap
# create the "capture" objects (one per item in s)
| [reduce ( $edit | .captures | .[] | select(.name != null) | { (.name) : .string } ) as $pair
({}; . + $pair) | s ] as $inserts
| reduce range(0; $inserts|length) as $ix (.; .result[$ix] += $gap + $inserts[$ix])
| .previous = ($edit | .offset + .length ) )
| .result[] + $in[.previous:] )
// $in;
| reduce range(0; $inserts|length) as $ix (.; .result[$ix] += $gap + $inserts[$ix])
| .previous = ($edit | .offset + .length ) )
| .result[] + $in[.previous:] )
// $in;
#
def sub($re; s): sub($re; s; "");
#
Expand All @@ -138,17 +138,17 @@ def gsub($re; s): sub($re; s; "g");
########################################################################
# generic iterator/generator
def while(cond; update):
def _while:
if cond then ., (update | _while) else empty end;
_while;
def _while:
if cond then ., (update | _while) else empty end;
_while;
def until(cond; next):
def _until:
if cond then . else (next|_until) end;
_until;
def _until:
if cond then . else (next|_until) end;
_until;
def limit($n; exp):
if $n > 0 then label $out | foreach exp as $item ($n; .-1; $item, if . <= 0 then break $out else empty end)
elif $n == 0 then empty
else exp end;
if $n > 0 then label $out | foreach exp as $item ($n; .-1; $item, if . <= 0 then break $out else empty end)
elif $n == 0 then empty
else exp end;
# range/3, with a `by` expression argument
def range($init; $upto; $by):
if $by > 0 then $init|while(. < $upto; . + $by)
Expand All @@ -170,15 +170,15 @@ def first: .[0];
def last: .[-1];
def nth($n): .[$n];
def combinations:
if length == 0 then [] else
.[0][] as $x
| (.[1:] | combinations) as $y
| [$x] + $y
end;
if length == 0 then []
else .[0][] as $x
| (.[1:] | combinations) as $y
| [$x] + $y
end;
def combinations(n):
. as $dot
| [range(n) | $dot]
| combinations;
. as $dot
| [range(n) | $dot]
| combinations;
# transpose a possibly jagged matrix, quickly;
# rows are padded with nulls so the result is always rectangular.
def transpose:
Expand All @@ -192,9 +192,9 @@ def transpose:
def in(xs): . as $x | xs | has($x);
def inside(xs): . as $x | xs | contains($x);
def repeat(exp):
def _repeat:
exp, _repeat;
_repeat;
def _repeat:
exp, _repeat;
_repeat;
def inputs: try repeat(input) catch if .=="break" then empty else error end;
# like ruby's downcase - only characters A to Z are affected
def ascii_downcase:
Expand Down
8 changes: 8 additions & 0 deletions src/execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,14 @@ jv jq_next(jq_state *jq) {
set_error(jq, jv_invalid_with_msg(msg));
goto do_backtrack;
}
// $array | .[-1]
if (jv_get_kind(k) == JV_KIND_NUMBER && jv_get_kind(t) == JV_KIND_ARRAY) {
int idx = jv_number_value(k);
if (idx < 0) {
jv_free(k);
k = jv_number(jv_array_length(jv_copy(t)) + idx);
}
}
jv v = jv_get(t, jv_copy(k));
if (jv_is_valid(v)) {
path_append(jq, k, jv_copy(v));
Expand Down
49 changes: 48 additions & 1 deletion tests/jq.test
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,28 @@ del(.[1], .[-6], .[2], .[-3:9])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 3, 5, 6, 9]

# negative index
setpath([-1]; 1)
[0]
[1]

pick(.a.b.c)
null
{"a":{"b":{"c":null}}}

pick(first)
[1,2]
[1]

pick(first|first)
[[10,20],30]
[[10]]

# negative indices in path expressions (since last/1 is .[-1])
pick(last)
[[10,20],30]
[null,30]

#
# Assignment
#
Expand Down Expand Up @@ -1819,13 +1841,38 @@ tojson | fromjson
{"a":nan}
{"a":null}

emanuele6 marked this conversation as resolved.
Show resolved Hide resolved

# calling input/0, or debug/0 in a test doesn't crash jq

try input catch .
null
"break"

pick(.a.b.c)
null
{"a":{"b":{"c":null}}}

pick(first)
[1,2]
[1]

pick(first|first)
[[10,20],30]
[[10]]

pick(last)
[[10,20],30]
[null,30]

# input and inputs
[input?]
null
[]

[inputs]
null
[]

# debug should emit its input
debug
1
1