Skip to content

Commit

Permalink
Add halt, halt_error builtins (fix #386)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicowilliams committed Feb 26, 2017
1 parent 6bac4ed commit 8ea21a5
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 7 deletions.
23 changes: 22 additions & 1 deletion docs/content/3.manual/manual.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ sections:
problem or system error, 3 if there was a jq program compile
error, or 0 if the jq program ran.
Another way to set the exit status is with the `halt_error`
builtin function.
* `--arg name value`:
This option passes a value to the jq program as a predefined
Expand Down Expand Up @@ -982,7 +985,25 @@ sections:
Produces an error, just like `.a` applied to values other than
null and objects would, but with the given message as the
error's value.
error's value. Errors can be caught with try/catch; see below.
- title: "`halt`"
body: |
Stops the jq program with no further outputs. jq will exit
with exit status `0`.
- title: "`halt_error`, `halt_error(exit_code)`"
body: |
Stops the jq program with no further outputs. The input will
be printed on `stderr` as raw output (i.e., strings will not
have double quotes) with no decoration, not even a newline.
The given `exit_code` (defaulting to `5`) will be jq's exit
status.
For example, `"Error: somthing went wrong\n"|halt_error(1)`.
- title: "`$__loc__`"
body: |
Expand Down
17 changes: 16 additions & 1 deletion jq.1.prebuilt
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ Prepend \fBdirectory\fR to the search list for modules\. If this option is used
.IP
Sets the exit status of jq to 0 if the last output values was neither \fBfalse\fR nor \fBnull\fR, 1 if the last output value was either \fBfalse\fR or \fBnull\fR, or 4 if no valid result was ever produced\. Normally jq exits with 2 if there was any usage problem or system error, 3 if there was a jq program compile error, or 0 if the jq program ran\.
.
.IP
Another way to set the exit status is with the \fBhalt_error\fR builtin function\.
.
.IP "\(bu" 4
\fB\-\-arg name value\fR:
.
Expand Down Expand Up @@ -1017,7 +1020,19 @@ jq \'[1,2,empty,3]\'
.IP "" 0
.
.SS "error(message)"
Produces an error, just like \fB\.a\fR applied to values other than null and objects would, but with the given message as the error\'s value\.
Produces an error, just like \fB\.a\fR applied to values other than null and objects would, but with the given message as the error\'s value\. Errors can be caught with try/catch; see below\.
.
.SS "halt"
Stops the jq program with no further outputs\. jq will exit with exit status \fB0\fR\.
.
.SS "halt_error, halt_error(exit_code)"
Stops the jq program with no further outputs\. The input will be printed on \fBstderr\fR as raw output (i\.e\., strings will not have double quotes) with no decoration, not even a newline\.
.
.P
The given \fBexit_code\fR (defaulting to \fB5\fR) will be jq\'s exit status\.
.
.P
For example, \fB"Error: somthing went wrong\en"|halt_error(1)\fR\.
.
.SS "$__loc__"
Produces an object with a "file" key and a "line" key, with the filename and line number where \fB$__loc__\fR occurs, as values\.
Expand Down
15 changes: 15 additions & 0 deletions src/builtin.c
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,19 @@ static jv f_env(jq_state *jq, jv input) {
return env;
}

static jv f_halt(jq_state *jq, jv input) {
jv_free(input);
jq_halt(jq, jv_invalid(), jv_invalid());
return jv_true();
}

static jv f_halt_error(jq_state *jq, jv input, jv a) {
if (jv_get_kind(a) != JV_KIND_NUMBER)
return type_error(input, "halt_error/1: number required"); \
jq_halt(jq, a, input);
return jv_true();
}

static jv f_get_search_list(jq_state *jq, jv input) {
jv_free(input);
return jq_get_lib_dirs(jq);
Expand Down Expand Up @@ -1467,6 +1480,8 @@ static const struct cfunction function_list[] = {
{(cfunction_ptr)f_error, "error", 2},
{(cfunction_ptr)f_format, "format", 2},
{(cfunction_ptr)f_env, "env", 1},
{(cfunction_ptr)f_halt, "halt", 1},
{(cfunction_ptr)f_halt_error, "halt_error", 2},
{(cfunction_ptr)f_get_search_list, "get_search_list", 1},
{(cfunction_ptr)f_get_prog_origin, "get_prog_origin", 1},
{(cfunction_ptr)f_get_jq_origin, "get_jq_origin", 1},
Expand Down
1 change: 1 addition & 0 deletions src/builtin.jq
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
def halt_error: halt_error(5);
def error: error(.);
def map(f): [.[] | f];
def select(f): if f then . else empty end;
Expand Down
41 changes: 41 additions & 0 deletions src/execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ struct jq_state {
int initial_execution;
unsigned next_label;

int halted;
jv exit_code;
jv error_message;

jv attrs;
jq_input_cb input_cb;
void *input_cb_data;
Expand Down Expand Up @@ -285,6 +289,9 @@ static void jq_reset(jq_state *jq) {
jv_free(jq->error);
jq->error = jv_null();

jq->halted = 0;
jv_free(jq->exit_code);
jv_free(jq->error_message);
if (jv_get_kind(jq->path) != JV_KIND_INVALID)
jv_free(jq->path);
jq->path = jv_null();
Expand Down Expand Up @@ -320,6 +327,11 @@ jv jq_next(jq_state *jq) {
jq->initial_execution = 0;
assert(jv_get_kind(jq->error) == JV_KIND_NULL);
while (1) {
if (jq->halted) {
if (jq->debug_trace_enabled)
printf("\t<halted>\n");
return jv_invalid();
}
uint16_t opcode = *pc;
raising = 0;

Expand Down Expand Up @@ -932,6 +944,10 @@ jq_state *jq_init(void) {
jq->curr_frame = 0;
jq->error = jv_null();

jq->halted = 0;
jq->exit_code = jv_invalid();
jq->error_message = jv_invalid();

jq->err_cb = default_err_cb;
jq->err_cb_data = stderr;

Expand Down Expand Up @@ -1163,3 +1179,28 @@ void jq_get_debug_cb(jq_state *jq, jq_msg_cb *cb, void **data) {
*cb = jq->debug_cb;
*data = jq->debug_cb_data;
}

void
jq_halt(jq_state *jq, jv exit_code, jv error_message)
{
assert(!jq->halted);
jq->halted = 1;
jq->exit_code = exit_code;
jq->error_message = error_message;
}

int
jq_halted(jq_state *jq)
{
return jq->halted;
}

jv jq_get_exit_code(jq_state *jq)
{
return jv_copy(jq->exit_code);
}

jv jq_get_error_message(jq_state *jq)
{
return jv_copy(jq->error_message);
}
5 changes: 5 additions & 0 deletions src/jq.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ void jq_start(jq_state *, jv value, int);
jv jq_next(jq_state *);
void jq_teardown(jq_state **);

void jq_halt(jq_state *, jv, jv);
int jq_halted(jq_state *);
jv jq_get_exit_code(jq_state *);
jv jq_get_error_message(jq_state *);

typedef jv (*jq_input_cb)(jq_state *, void *);
void jq_set_input_cb(jq_state *, jq_input_cb, void *);
void jq_get_input_cb(jq_state *, jq_input_cb *, void **);
Expand Down
33 changes: 28 additions & 5 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,11 @@ enum {
RAW_NO_LF = 1024,
UNBUFFERED_OUTPUT = 2048,
EXIT_STATUS = 4096,
SEQ = 8192,
RUN_TESTS = 16384,
EXIT_STATUS_EXACT = 8192,
SEQ = 16384,
RUN_TESTS = 32768,
/* debugging only */
DUMP_DISASM = 32768,
DUMP_DISASM = 65536,
};
static int options = 0;

Expand Down Expand Up @@ -182,7 +183,29 @@ static int process(jq_state *jq, jv value, int flags, int dumpopts) {
if (options & UNBUFFERED_OUTPUT)
fflush(stdout);
}
if (jv_invalid_has_msg(jv_copy(result))) {
if (jq_halted(jq)) {
// jq program invoked `halt` or `halt_error`
options |= EXIT_STATUS_EXACT;
jv exit_code = jq_get_exit_code(jq);
if (!jv_is_valid(exit_code))
ret = 0;
else if (jv_get_kind(exit_code) == JV_KIND_NUMBER)
ret = jv_number_value(exit_code);
else
ret = 5;
jv_free(exit_code);
jv error_message = jq_get_error_message(jq);
if (jv_get_kind(error_message) == JV_KIND_STRING) {
fprintf(stderr, "%s", jv_string_value(error_message));
} else if (jv_get_kind(error_message) == JV_KIND_NULL) {
// Halt with no output
} else if (jv_is_valid(error_message)) {
error_message = jv_dump_string(jv_copy(error_message), 0);
fprintf(stderr, "%s\n", jv_string_value(error_message));
} // else no message on stderr; use --debug-trace to see a message
fflush(stderr);
jv_free(error_message);
} else if (jv_invalid_has_msg(jv_copy(result))) {
// Uncaught jq exception
jv msg = jv_invalid_get_msg(jv_copy(result));
jv input_pos = jq_util_input_get_position(jq);
Expand Down Expand Up @@ -623,7 +646,7 @@ int main(int argc, char* argv[]) {
jq_teardown(&jq);
if (ret >= 10 && (options & EXIT_STATUS))
return ret - 10;
if (ret >= 10)
if (ret >= 10 && !(options & EXIT_STATUS_EXACT))
return 0;
return ret;
}
2 changes: 2 additions & 0 deletions tests/setup
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ JQ=$JQBASEDIR/jq
if [ -z "${NO_VALGRIND-}" ] && which valgrind > /dev/null; then
VALGRIND="valgrind --error-exitcode=1 --leak-check=full \
--suppressions=$JQTESTDIR/onig.supp"
VG_EXIT0=--error-exitcode=0
Q=-q
else
VALGRIND=
VG_EXIT0=
Q=
fi

Expand Down
33 changes: 33 additions & 0 deletions tests/shtest
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,37 @@ if ! $VALGRIND $Q $JQ -L tests/modules -ne 'import "test_bind_order" as check; c
exit 1
fi

## Halt

if ! $VALGRIND $Q $JQ -n halt; then
echo "jq halt didn't work as expected" 1>&2
exit 1
fi
if $VALGRIND $Q $VG_EXIT0 $JQ -n 'halt_error(1)'; then
echo "jq halt_error(1) didn't work as expected" 1>&2
exit 1
elif [ $? -ne 1 ]; then
echo "jq halt_error(1) had wrong error code" 1>&2
exit 1
fi
if $VALGRIND $Q $VG_EXIT0 $JQ -n 'halt_error(11)'; then
echo "jq halt_error(11) didn't work as expected" 1>&2
exit 1
elif [ $? -ne 11 ]; then
echo "jq halt_error(11) had wrong error code" 1>&2
exit 1
fi
if [ -n "`$VALGRIND $Q $JQ -n 'halt_error(1)' 2>&1`" ]; then
echo "jq halt_error(1) had unexpected output" 1>&2
exit 1
fi
if [ -n "`$VALGRIND $Q $JQ -n '"xyz\n"|halt_error(1)' 2>/dev/null`" ]; then
echo "jq halt_error(1) had unexpected output on stdout" 1>&2
exit 1
fi
if [ "`$VALGRIND $Q $JQ -n '"xyz\n"|halt_error(1)' 2>&1`" != xyz ]; then
echo "jq halt_error(1) had unexpected output" 1>&2
exit 1
fi

exit 0

0 comments on commit 8ea21a5

Please sign in to comment.