From a7b22539a479400fe8b3fc40840ca587044ecbc2 Mon Sep 17 00:00:00 2001 From: itchyny Date: Wed, 13 Nov 2024 20:35:50 +0900 Subject: [PATCH] fix: make last(empty) yield no output values (#3179) --- docs/content/manual/dev/manual.yml | 5 ++++- jq.1.prebuilt | 6 +++++- src/builtin.c | 28 ++++++++++++++++++++++++++++ src/builtin.jq | 1 - tests/jq.test | 4 ++++ tests/man.test | 6 +++++- 6 files changed, 46 insertions(+), 4 deletions(-) diff --git a/docs/content/manual/dev/manual.yml b/docs/content/manual/dev/manual.yml index 82061f8234..895deab9e5 100644 --- a/docs/content/manual/dev/manual.yml +++ b/docs/content/manual/dev/manual.yml @@ -3069,9 +3069,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 e391bf3c89..1a2c3cbc82 100644 --- a/jq.1.prebuilt +++ b/jq.1.prebuilt @@ -3475,9 +3475,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..dbd72f3de1 100644 --- a/src/builtin.c +++ b/src/builtin.c @@ -1879,6 +1879,33 @@ BINOPS #undef LIBM_DD #undef LIBM_DA +// This is a hack to make last(g) yield no output values, +// if g yields 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 +1925,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