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..033614deb9 100644 --- a/src/builtin.c +++ b/src/builtin.c @@ -1879,6 +1879,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(); @@ -1898,6 +1924,7 @@ static block bind_bytecoded_builtins(block b) { {"path", BLOCK(gen_op_simple(PATH_BEGIN), gen_call("arg", gen_noop()), gen_op_simple(PATH_END))}, + {"last", gen_last_1()}, }; for (unsigned i=0; i