diff --git a/docs/content/manual/dev/manual.yml b/docs/content/manual/dev/manual.yml index c98b3ca259..7995edf2a9 100644 --- a/docs/content/manual/dev/manual.yml +++ b/docs/content/manual/dev/manual.yml @@ -3057,9 +3057,12 @@ sections: Note that `nth(n; expr)` doesn't support negative values of `n`. examples: - - program: '[first(range(.)), last(range(.)), nth(./2; range(.))]' + - program: '[first(range(.)), last(range(.)), nth(5; range(.))]' input: '10' output: ['[0,9,5]'] + - program: '[first(empty), last(empty), nth(5; empty)]' + input: 'null' + output: ['[]'] - title: "`first`, `last`, `nth(n)`" body: | diff --git a/jq.1.prebuilt b/jq.1.prebuilt index 1d1424f68d..a263a6eeab 100644 --- a/jq.1.prebuilt +++ b/jq.1.prebuilt @@ -1,5 +1,5 @@ . -.TH "JQ" "1" "August 2024" "" "" +.TH "JQ" "1" "September 2024" "" "" . .SH "NAME" \fBjq\fR \- Command\-line JSON processor @@ -3460,9 +3460,13 @@ The \fBnth(n; expr)\fR function extracts the nth value output by \fBexpr\fR\. No . .nf -jq \'[first(range(\.)), last(range(\.)), nth(\./2; range(\.))]\' +jq \'[first(range(\.)), last(range(\.)), nth(5; range(\.))]\' 10 => [0,9,5] + +jq \'[first(empty), last(empty), nth(5; empty)]\' + null +=> [] . .fi . diff --git a/src/builtin.c b/src/builtin.c index da4a770fac..c33dfe5bdd 100644 --- a/src/builtin.c +++ b/src/builtin.c @@ -43,10 +43,7 @@ void *alloca (size_t); #include "locfile.h" #include "jv_unicode.h" #include "jv_alloc.h" -#include "jv_dtoa.h" -#include "jv_dtoa_tsd.h" #include "jv_private.h" -#include "util.h" #define BINOP(name) \ @@ -1879,6 +1876,32 @@ BINOPS #undef LIBM_DD #undef LIBM_DA +// This is a hack to make last(empty) yield no output values without using boxing. +static block gen_last_1() { + block last_var = gen_op_var_fresh(STOREV, "last"); + block is_empty_var = gen_op_var_fresh(STOREV, "is_empty"); + block init = BLOCK(gen_op_simple(DUP), + gen_const(jv_null()), + last_var, + gen_op_simple(DUP), + gen_const(jv_true()), + is_empty_var); + block call_arg = BLOCK(gen_call("arg", gen_noop()), + gen_op_simple(DUP), + gen_op_bound(STOREV, last_var), + gen_const(jv_false()), + gen_op_bound(STOREV, is_empty_var), + gen_op_simple(BACKTRACK)); + block if_empty = gen_op_simple(BACKTRACK); + return BLOCK(init, + gen_op_target(FORK, call_arg), + call_arg, + BLOCK(gen_op_bound(LOADVN, is_empty_var), + gen_op_target(JUMP_F, if_empty), + if_empty, + gen_op_bound(LOADVN, last_var))); +} + struct bytecoded_builtin { const char* name; block code; }; static block bind_bytecoded_builtins(block b) { block builtins = gen_noop(); @@ -1895,6 +1918,7 @@ static block bind_bytecoded_builtins(block b) { } { struct bytecoded_builtin builtin_def_1arg[] = { + {"last", gen_last_1()}, {"path", BLOCK(gen_op_simple(PATH_BEGIN), gen_call("arg", gen_noop()), gen_op_simple(PATH_END))}, diff --git a/src/builtin.jq b/src/builtin.jq index aa33cd4b75..385098f96d 100644 --- a/src/builtin.jq +++ b/src/builtin.jq @@ -166,7 +166,6 @@ def all(condition): all(.[]; condition); def any(condition): any(.[]; condition); def all: all(.[]; .); def any: any(.[]; .); -def last(g): reduce g as $item (null; $item); def nth($n; g): if $n < 0 then error("nth doesn't support negative indices") else label $out | foreach g as $item ($n + 1; . - 1; if . <= 0 then $item, break $out else empty end) end; diff --git a/tests/jq.test b/tests/jq.test index 446ee36952..5542ba5aca 100644 --- a/tests/jq.test +++ b/tests/jq.test @@ -338,6 +338,10 @@ null 10 [0,9] +[first(range(.)), last(range(.))] +0 +[] + [nth(0,5,9,10,15; range(.)), try nth(-1; range(.)) catch .] 10 [0,5,9,"nth doesn't support negative indices"] diff --git a/tests/man.test b/tests/man.test index 6c5eba390a..faae1861cc 100644 --- a/tests/man.test +++ b/tests/man.test @@ -877,10 +877,14 @@ false [0,1,2,3,4,5,6,7,8,9] [0,1,2] -[first(range(.)), last(range(.)), nth(./2; range(.))] +[first(range(.)), last(range(.)), nth(5; range(.))] 10 [0,9,5] +[first(empty), last(empty), nth(5; empty)] +null +[] + [range(.)]|[first, last, nth(5)] 10 [0,9,5]