"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "builtin.c" between
jq-1.5.tar.gz and jq-1.6.tar.gz

About: jq is a lightweight and flexible command-line JSON processor.

builtin.c  (jq-1.5):builtin.c  (jq-1.6)
#define _BSD_SOURCE #define _BSD_SOURCE
#define _GNU_SOURCE #define _GNU_SOURCE
#define _XOPEN_SOURCE #ifndef __sun__
# define _XOPEN_SOURCE
# define _XOPEN_SOURCE_EXTENDED 1
#else
# define _XPG6
# define __EXTENSIONS__
#endif
#include <sys/time.h> #include <sys/time.h>
#include <stdlib.h> #include <stdlib.h>
#include <stddef.h> #include <stddef.h>
#ifdef HAVE_ALLOCA_H #ifdef HAVE_ALLOCA_H
# include <alloca.h> # include <alloca.h>
#elif !defined alloca #elif !defined alloca
# ifdef __GNUC__ # ifdef __GNUC__
# define alloca __builtin_alloca # define alloca __builtin_alloca
# elif defined _MSC_VER # elif defined _MSC_VER
# include <malloc.h> # include <malloc.h>
skipping to change at line 26 skipping to change at line 32
# ifdef __cplusplus # ifdef __cplusplus
extern "C" extern "C"
# endif # endif
void *alloca (size_t); void *alloca (size_t);
# endif # endif
#endif #endif
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <limits.h> #include <limits.h>
#include <math.h> #include <math.h>
#ifdef HAVE_ONIGURUMA #ifdef HAVE_LIBONIG
#include <oniguruma.h> #include <oniguruma.h>
#endif #endif
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include "builtin.h" #include "builtin.h"
#include "compile.h" #include "compile.h"
#include "jq_parser.h" #include "jq_parser.h"
#include "bytecode.h" #include "bytecode.h"
#include "linker.h" #include "linker.h"
#include "locfile.h" #include "locfile.h"
#include "jv_unicode.h" #include "jv_unicode.h"
#include "jv_alloc.h"
static jv type_error(jv bad, const char* msg) { static jv type_error(jv bad, const char* msg) {
char errbuf[15]; char errbuf[15];
jv err = jv_invalid_with_msg(jv_string_fmt("%s (%s) %s", jv err = jv_invalid_with_msg(jv_string_fmt("%s (%s) %s",
jv_kind_name(jv_get_kind(bad)), jv_kind_name(jv_get_kind(bad)),
jv_dump_string_trunc(jv_copy(bad), errbuf, sizeof(errbuf)), jv_dump_string_trunc(jv_copy(bad), errbuf, sizeof(errbuf)),
msg)); msg));
jv_free(bad); jv_free(bad);
return err; return err;
} }
skipping to change at line 62 skipping to change at line 69
jv_kind_name(jv_get_kind(bad1)), jv_kind_name(jv_get_kind(bad1)),
jv_dump_string_trunc(jv_copy(bad1), errbuf1, sizeof(errbuf1)), jv_dump_string_trunc(jv_copy(bad1), errbuf1, sizeof(errbuf1)),
jv_kind_name(jv_get_kind(bad2)), jv_kind_name(jv_get_kind(bad2)),
jv_dump_string_trunc(jv_copy(bad2), errbuf2, sizeof(errbuf2)), jv_dump_string_trunc(jv_copy(bad2), errbuf2, sizeof(errbuf2)),
msg)); msg));
jv_free(bad1); jv_free(bad1);
jv_free(bad2); jv_free(bad2);
return err; return err;
} }
static inline jv ret_error(jv bad, jv msg) {
jv_free(bad);
return jv_invalid_with_msg(msg);
}
static inline jv ret_error2(jv bad1, jv bad2, jv msg) {
jv_free(bad1);
jv_free(bad2);
return jv_invalid_with_msg(msg);
}
static jv f_plus(jq_state *jq, jv input, jv a, jv b) { static jv f_plus(jq_state *jq, jv input, jv a, jv b) {
jv_free(input); jv_free(input);
if (jv_get_kind(a) == JV_KIND_NULL) { if (jv_get_kind(a) == JV_KIND_NULL) {
jv_free(a); jv_free(a);
return b; return b;
} else if (jv_get_kind(b) == JV_KIND_NULL) { } else if (jv_get_kind(b) == JV_KIND_NULL) {
jv_free(b); jv_free(b);
return a; return a;
} else if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBE R) { } else if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBE R) {
return jv_number(jv_number_value(a) + return jv_number(jv_number_value(a) +
skipping to change at line 97 skipping to change at line 115
return type_error(input, "number required"); \ return type_error(input, "number required"); \
} \ } \
jv ret = jv_number(name(jv_number_value(input))); \ jv ret = jv_number(name(jv_number_value(input))); \
jv_free(input); \ jv_free(input); \
return ret; \ return ret; \
} }
#define LIBM_DD_NO(name) #define LIBM_DD_NO(name)
#define LIBM_DDD(name) \ #define LIBM_DDD(name) \
static jv f_ ## name(jq_state *jq, jv input, jv a, jv b) { \ static jv f_ ## name(jq_state *jq, jv input, jv a, jv b) { \
if (jv_get_kind(a) != JV_KIND_NUMBER || jv_get_kind(b) != JV_KIND_NUMBER) \
return type_error(input, "number required"); \
jv_free(input); \ jv_free(input); \
if (jv_get_kind(a) != JV_KIND_NUMBER) { \
jv_free(b); \
return type_error(a, "number required"); \
} \
if (jv_get_kind(b) != JV_KIND_NUMBER) { \
jv_free(a); \
return type_error(b, "number required"); \
} \
jv ret = jv_number(name(jv_number_value(a), jv_number_value(b))); \ jv ret = jv_number(name(jv_number_value(a), jv_number_value(b))); \
jv_free(a); \ jv_free(a); \
jv_free(b); \ jv_free(b); \
return ret; \ return ret; \
} }
#define LIBM_DDD_NO(name) #define LIBM_DDD_NO(name)
#define LIBM_DDDD(name) \
static jv f_ ## name(jq_state *jq, jv input, jv a, jv b, jv c) { \
jv_free(input); \
if (jv_get_kind(a) != JV_KIND_NUMBER) { \
jv_free(b); \
jv_free(c); \
return type_error(a, "number required"); \
} \
if (jv_get_kind(b) != JV_KIND_NUMBER) { \
jv_free(a); \
jv_free(c); \
return type_error(b, "number required"); \
} \
if (jv_get_kind(c) != JV_KIND_NUMBER) { \
jv_free(a); \
jv_free(b); \
return type_error(c, "number required"); \
} \
jv ret = jv_number(name(jv_number_value(a), jv_number_value(b), jv_number_valu
e(c))); \
jv_free(a); \
jv_free(b); \
jv_free(c); \
return ret; \
}
#define LIBM_DDDD_NO(name)
#include "libm.h" #include "libm.h"
#undef LIBM_DDDD_NO
#undef LIBM_DDD_NO #undef LIBM_DDD_NO
#undef LIBM_DD_NO #undef LIBM_DD_NO
#undef LIBM_DDDD
#undef LIBM_DDD #undef LIBM_DDD
#undef LIBM_DD #undef LIBM_DD
#ifdef HAVE_FREXP
static jv f_frexp(jq_state *jq, jv input) {
if (jv_get_kind(input) != JV_KIND_NUMBER) {
return type_error(input, "number required");
}
int exp;
double d = frexp(jv_number_value(input), &exp);
jv ret = JV_ARRAY(jv_number(d), jv_number(exp));
jv_free(input);
return ret;
}
#endif
#ifdef HAVE_MODF
static jv f_modf(jq_state *jq, jv input) {
if (jv_get_kind(input) != JV_KIND_NUMBER) {
return type_error(input, "number required");
}
double i;
jv ret = JV_ARRAY(jv_number(modf(jv_number_value(input), &i)));
jv_free(input);
return jv_array_append(ret, jv_number(i));
}
#endif
#ifdef HAVE_LGAMMA_R
static jv f_lgamma_r(jq_state *jq, jv input) {
if (jv_get_kind(input) != JV_KIND_NUMBER) {
return type_error(input, "number required");
}
int sign;
jv ret = JV_ARRAY(jv_number(lgamma_r(jv_number_value(input), &sign)));
jv_free(input);
return jv_array_append(ret, jv_number(sign));
}
#endif
static jv f_negate(jq_state *jq, jv input) { static jv f_negate(jq_state *jq, jv input) {
if (jv_get_kind(input) != JV_KIND_NUMBER) { if (jv_get_kind(input) != JV_KIND_NUMBER) {
return type_error(input, "cannot be negated"); return type_error(input, "cannot be negated");
} }
jv ret = jv_number(-jv_number_value(input)); jv ret = jv_number(-jv_number_value(input));
jv_free(input); jv_free(input);
return ret; return ret;
} }
static jv f_startswith(jq_state *jq, jv a, jv b) { static jv f_startswith(jq_state *jq, jv a, jv b) {
if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING) if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING)
return jv_invalid_with_msg(jv_string("startswith() requires string inputs")) ; return ret_error2(a, b, jv_string("startswith() requires string inputs"));
int alen = jv_string_length_bytes(jv_copy(a)); int alen = jv_string_length_bytes(jv_copy(a));
int blen = jv_string_length_bytes(jv_copy(b)); int blen = jv_string_length_bytes(jv_copy(b));
jv ret; jv ret;
if (blen <= alen && memcmp(jv_string_value(a), jv_string_value(b), blen) == 0) if (blen <= alen && memcmp(jv_string_value(a), jv_string_value(b), blen) == 0)
ret = jv_true(); ret = jv_true();
else else
ret = jv_false(); ret = jv_false();
jv_free(a); jv_free(a);
jv_free(b); jv_free(b);
return ret; return ret;
} }
static jv f_endswith(jq_state *jq, jv a, jv b) { static jv f_endswith(jq_state *jq, jv a, jv b) {
if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING) if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING)
return jv_invalid_with_msg(jv_string("endswith() requires string inputs")); return ret_error2(a, b, jv_string("endswith() requires string inputs"));
const char *astr = jv_string_value(a); const char *astr = jv_string_value(a);
const char *bstr = jv_string_value(b); const char *bstr = jv_string_value(b);
size_t alen = jv_string_length_bytes(jv_copy(a)); size_t alen = jv_string_length_bytes(jv_copy(a));
size_t blen = jv_string_length_bytes(jv_copy(b)); size_t blen = jv_string_length_bytes(jv_copy(b));
jv ret;; jv ret;;
if (alen < blen || if (alen < blen ||
memcmp(astr + (alen - blen), bstr, blen) != 0) memcmp(astr + (alen - blen), bstr, blen) != 0)
ret = jv_false(); ret = jv_false();
else else
skipping to change at line 369 skipping to change at line 456
} }
static jv f_tostring(jq_state *jq, jv input) { static jv f_tostring(jq_state *jq, jv input) {
if (jv_get_kind(input) == JV_KIND_STRING) { if (jv_get_kind(input) == JV_KIND_STRING) {
return input; return input;
} else { } else {
return jv_dump_string(input, 0); return jv_dump_string(input, 0);
} }
} }
static jv f_utf8bytelength(jq_state *jq, jv input) {
if (jv_get_kind(input) != JV_KIND_STRING)
return type_error(input, "only strings have UTF-8 byte length");
return jv_number(jv_string_length_bytes(input));
}
#define CHARS_ALPHANUM "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123 456789" #define CHARS_ALPHANUM "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123 456789"
static const unsigned char BASE64_ENCODE_TABLE[64 + 1] = CHARS_ALPHANUM "+/";
static const unsigned char BASE64_INVALID_ENTRY = 0xFF;
static const unsigned char BASE64_DECODE_TABLE[255] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x
FF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF,
62, // +
0xFF, 0xFF, 0xFF,
63, // /
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // 0-9
0xFF, 0xFF, 0xFF,
99, // =
0xFF, 0xFF, 0xFF,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, // A-Z
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45
, 46, 47, 48, 49, 50, 51, // a-z
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x
FF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x
FF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x
FF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
static jv escape_string(jv input, const char* escapings) { static jv escape_string(jv input, const char* escapings) {
assert(jv_get_kind(input) == JV_KIND_STRING); assert(jv_get_kind(input) == JV_KIND_STRING);
const char* lookup[128] = {0}; const char* lookup[128] = {0};
const char* p = escapings; const char* p = escapings;
lookup[0] = "\\0"; lookup[0] = "\\0";
while (*p) { while (*p) {
lookup[(int)*p] = p+1; lookup[(int)*p] = p+1;
p++; p++;
p += strlen(p); p += strlen(p);
skipping to change at line 523 skipping to change at line 633
jv_free(line); jv_free(line);
return type_error(x, "can not be escaped for shell"); return type_error(x, "can not be escaped for shell");
} }
} }
jv_free(input); jv_free(input);
return line; return line;
} else if (!strcmp(fmt_s, "base64")) { } else if (!strcmp(fmt_s, "base64")) {
jv_free(fmt); jv_free(fmt);
input = f_tostring(jq, input); input = f_tostring(jq, input);
jv line = jv_string(""); jv line = jv_string("");
const char b64[64 + 1] = CHARS_ALPHANUM "+/";
const unsigned char* data = (const unsigned char*)jv_string_value(input); const unsigned char* data = (const unsigned char*)jv_string_value(input);
int len = jv_string_length_bytes(jv_copy(input)); int len = jv_string_length_bytes(jv_copy(input));
for (int i=0; i<len; i+=3) { for (int i=0; i<len; i+=3) {
uint32_t code = 0; uint32_t code = 0;
int n = len - i >= 3 ? 3 : len-i; int n = len - i >= 3 ? 3 : len-i;
for (int j=0; j<3; j++) { for (int j=0; j<3; j++) {
code <<= 8; code <<= 8;
code |= j < n ? (unsigned)data[i+j] : 0; code |= j < n ? (unsigned)data[i+j] : 0;
} }
char buf[4]; char buf[4];
for (int j=0; j<4; j++) { for (int j=0; j<4; j++) {
buf[j] = b64[(code >> (18 - j*6)) & 0x3f]; buf[j] = BASE64_ENCODE_TABLE[(code >> (18 - j*6)) & 0x3f];
} }
if (n < 3) buf[3] = '='; if (n < 3) buf[3] = '=';
if (n < 2) buf[2] = '='; if (n < 2) buf[2] = '=';
line = jv_string_append_buf(line, buf, sizeof(buf)); line = jv_string_append_buf(line, buf, sizeof(buf));
} }
jv_free(input); jv_free(input);
return line; return line;
} else if (!strcmp(fmt_s, "base64d")) {
jv_free(fmt);
input = f_tostring(jq, input);
const unsigned char* data = (const unsigned char*)jv_string_value(input);
int len = jv_string_length_bytes(jv_copy(input));
size_t decoded_len = (3 * len) / 4; // 3 usable bytes for every 4 bytes of i
nput
char *result = jv_mem_calloc(decoded_len, sizeof(char));
memset(result, 0, decoded_len * sizeof(char));
uint32_t ri = 0;
int input_bytes_read=0;
uint32_t code = 0;
for (int i=0; i<len && data[i] != '='; i++) {
if (BASE64_DECODE_TABLE[data[i]] == BASE64_INVALID_ENTRY) {
free(result);
return type_error(input, "is not valid base64 data");
}
code <<= 6;
code |= BASE64_DECODE_TABLE[data[i]];
input_bytes_read++;
if (input_bytes_read == 4) {
result[ri++] = (code >> 16) & 0xFF;
result[ri++] = (code >> 8) & 0xFF;
result[ri++] = code & 0xFF;
input_bytes_read = 0;
code = 0;
}
}
if (input_bytes_read == 3) {
result[ri++] = (code >> 10) & 0xFF;
result[ri++] = (code >> 2) & 0xFF;
} else if (input_bytes_read == 2) {
result[ri++] = (code >> 4) & 0xFF;
} else if (input_bytes_read == 1) {
free(result);
return type_error(input, "trailing base64 byte found");
}
jv line = jv_string_sized(result, ri);
jv_free(input);
free(result);
return line;
} else { } else {
jv_free(input); jv_free(input);
return jv_invalid_with_msg(jv_string_concat(fmt, jv_string(" is not a valid format"))); return jv_invalid_with_msg(jv_string_concat(fmt, jv_string(" is not a valid format")));
} }
} }
static jv f_keys(jq_state *jq, jv input) { static jv f_keys(jq_state *jq, jv input) {
if (jv_get_kind(input) == JV_KIND_OBJECT || jv_get_kind(input) == JV_KIND_ARRA Y) { if (jv_get_kind(input) == JV_KIND_OBJECT || jv_get_kind(input) == JV_KIND_ARRA Y) {
return jv_keys(input); return jv_keys(input);
} else { } else {
skipping to change at line 593 skipping to change at line 745
static jv f_group_by_impl(jq_state *jq, jv input, jv keys) { static jv f_group_by_impl(jq_state *jq, jv input, jv keys) {
if (jv_get_kind(input) == JV_KIND_ARRAY && if (jv_get_kind(input) == JV_KIND_ARRAY &&
jv_get_kind(keys) == JV_KIND_ARRAY && jv_get_kind(keys) == JV_KIND_ARRAY &&
jv_array_length(jv_copy(input)) == jv_array_length(jv_copy(keys))) { jv_array_length(jv_copy(input)) == jv_array_length(jv_copy(keys))) {
return jv_group(input, keys); return jv_group(input, keys);
} else { } else {
return type_error2(input, keys, "cannot be sorted, as they are not both arra ys"); return type_error2(input, keys, "cannot be sorted, as they are not both arra ys");
} }
} }
#ifdef HAVE_ONIGURUMA #ifdef HAVE_LIBONIG
static int f_match_name_iter(const UChar* name, const UChar *name_end, int ngrou ps, static int f_match_name_iter(const UChar* name, const UChar *name_end, int ngrou ps,
int *groups, regex_t *reg, void *arg) { int *groups, regex_t *reg, void *arg) {
jv captures = *(jv*)arg; jv captures = *(jv*)arg;
for (int i = 0; i < ngroups; ++i) { for (int i = 0; i < ngroups; ++i) {
jv cap = jv_array_get(jv_copy(captures),groups[i]-1); jv cap = jv_array_get(jv_copy(captures),groups[i]-1);
if (jv_get_kind(cap) == JV_KIND_OBJECT) { if (jv_get_kind(cap) == JV_KIND_OBJECT) {
cap = jv_object_set(cap, jv_string("name"), jv_string_sized((const char*)n ame, name_end-name)); cap = jv_object_set(cap, jv_string("name"), jv_string_sized((const char*)n ame, name_end-name));
captures = jv_array_set(captures,groups[i]-1,cap); captures = jv_array_set(captures,groups[i]-1,cap);
} else { } else {
jv_free(cap); jv_free(cap);
skipping to change at line 797 skipping to change at line 949
} while (global && start != end); } while (global && start != end);
onig_region_free(region,1); onig_region_free(region,1);
region = NULL; region = NULL;
if (region) if (region)
onig_region_free(region,1); onig_region_free(region,1);
onig_free(reg); onig_free(reg);
jv_free(input); jv_free(input);
jv_free(regex); jv_free(regex);
return result; return result;
} }
#else /* ! HAVE_ONIGURUMA */ #else /* !HAVE_LIBONIG */
static jv f_match(jq_state *jq, jv input, jv regex, jv modifiers, jv testmode) { static jv f_match(jq_state *jq, jv input, jv regex, jv modifiers, jv testmode) {
return jv_invalid_with_msg(jv_string("jq was compiled without ONIGURUMA regex libary. match/test/sub and related functions are not available.")); return jv_invalid_with_msg(jv_string("jq was compiled without ONIGURUMA regex libary. match/test/sub and related functions are not available."));
} }
#endif /* HAVE_ONIGURUMA */ #endif /* HAVE_LIBONIG */
static jv minmax_by(jv values, jv keys, int is_min) { static jv minmax_by(jv values, jv keys, int is_min) {
if (jv_get_kind(values) != JV_KIND_ARRAY) if (jv_get_kind(values) != JV_KIND_ARRAY)
return type_error2(values, keys, "cannot be iterated over"); return type_error2(values, keys, "cannot be iterated over");
if (jv_get_kind(keys) != JV_KIND_ARRAY) if (jv_get_kind(keys) != JV_KIND_ARRAY)
return type_error2(values, keys, "cannot be iterated over"); return type_error2(values, keys, "cannot be iterated over");
if (jv_array_length(jv_copy(values)) != jv_array_length(jv_copy(keys))) if (jv_array_length(jv_copy(values)) != jv_array_length(jv_copy(keys)))
return type_error2(values, keys, "have wrong length"); return type_error2(values, keys, "have wrong length");
if (jv_array_length(jv_copy(values)) == 0) { if (jv_array_length(jv_copy(values)) == 0) {
skipping to change at line 908 skipping to change at line 1060
return jv_number(NAN); return jv_number(NAN);
} }
static jv f_error(jq_state *jq, jv input, jv msg) { static jv f_error(jq_state *jq, jv input, jv msg) {
jv_free(input); jv_free(input);
return jv_invalid_with_msg(msg); return jv_invalid_with_msg(msg);
} }
// FIXME Should autoconf check for this! // FIXME Should autoconf check for this!
#ifndef WIN32 #ifndef WIN32
extern const char **environ; extern char **environ;
#endif #endif
static jv f_env(jq_state *jq, jv input) { static jv f_env(jq_state *jq, jv input) {
jv_free(input); jv_free(input);
jv env = jv_object(); jv env = jv_object();
const char *var, *val; const char *var, *val;
for (const char **e = environ; *e != NULL; e++) { for (char **e = environ; *e != NULL; e++) {
var = e[0]; var = e[0];
val = strchr(e[0], '='); val = strchr(e[0], '=');
if (val == NULL) if (val == NULL)
env = jv_object_set(env, jv_string(var), jv_null()); env = jv_object_set(env, jv_string(var), jv_null());
else if (var - val < INT_MAX) else if (var - val < INT_MAX)
env = jv_object_set(env, jv_string_sized(var, val - var), jv_string(val + 1)); env = jv_object_set(env, jv_string_sized(var, val - var), jv_string(val + 1));
} }
return env; 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) {
jv_free(a);
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) { static jv f_get_search_list(jq_state *jq, jv input) {
jv_free(input); jv_free(input);
return jq_get_lib_dirs(jq); return jq_get_lib_dirs(jq);
} }
static jv f_get_prog_origin(jq_state *jq, jv input) { static jv f_get_prog_origin(jq_state *jq, jv input) {
jv_free(input); jv_free(input);
return jq_get_prog_origin(jq); return jq_get_prog_origin(jq);
} }
static jv f_get_jq_origin(jq_state *jq, jv input) { static jv f_get_jq_origin(jq_state *jq, jv input) {
jv_free(input); jv_free(input);
return jq_get_jq_origin(jq); return jq_get_jq_origin(jq);
} }
static jv f_string_split(jq_state *jq, jv a, jv b) { static jv f_string_split(jq_state *jq, jv a, jv b) {
if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING) { if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING) {
jv_free(a); return ret_error2(a, b, jv_string("split input and separator must be strings
jv_free(b); "));
return jv_invalid_with_msg(jv_string("split input and separator must be stri
ngs"));
} }
return jv_string_split(a, b); return jv_string_split(a, b);
} }
static jv f_string_explode(jq_state *jq, jv a) { static jv f_string_explode(jq_state *jq, jv a) {
if (jv_get_kind(a) != JV_KIND_STRING) { if (jv_get_kind(a) != JV_KIND_STRING) {
jv_free(a); return ret_error(a, jv_string("explode input must be a string"));
return jv_invalid_with_msg(jv_string("explode input must be a string"));
} }
return jv_string_explode(a); return jv_string_explode(a);
} }
static jv f_string_indexes(jq_state *jq, jv a, jv b) { static jv f_string_indexes(jq_state *jq, jv a, jv b) {
return jv_string_indexes(a, b); return jv_string_indexes(a, b);
} }
static jv f_string_implode(jq_state *jq, jv a) { static jv f_string_implode(jq_state *jq, jv a) {
if (jv_get_kind(a) != JV_KIND_ARRAY) { if (jv_get_kind(a) != JV_KIND_ARRAY) {
jv_free(a); return ret_error(a, jv_string("implode input must be an array"));
return jv_invalid_with_msg(jv_string("implode input must be an array"));
} }
return jv_string_implode(a); return jv_string_implode(a);
} }
static jv f_setpath(jq_state *jq, jv a, jv b, jv c) { return jv_setpath(a, b, c) ; } static jv f_setpath(jq_state *jq, jv a, jv b, jv c) { return jv_setpath(a, b, c) ; }
static jv f_getpath(jq_state *jq, jv a, jv b) { return jv_getpath(a, b); } extern jv _jq_path_append(jq_state *, jv, jv, jv);
static jv f_getpath(jq_state *jq, jv a, jv b) {
return _jq_path_append(jq, a, b, jv_getpath(jv_copy(a), jv_copy(b)));
}
static jv f_delpaths(jq_state *jq, jv a, jv b) { return jv_delpaths(a, b); } static jv f_delpaths(jq_state *jq, jv a, jv b) { return jv_delpaths(a, b); }
static jv f_has(jq_state *jq, jv a, jv b) { return jv_has(a, b); } static jv f_has(jq_state *jq, jv a, jv b) { return jv_has(a, b); }
static jv f_modulemeta(jq_state *jq, jv a) { static jv f_modulemeta(jq_state *jq, jv a) {
if (jv_get_kind(a) != JV_KIND_STRING) { if (jv_get_kind(a) != JV_KIND_STRING) {
jv_free(a); return ret_error(a, jv_string("modulemeta input module name must be a string
return jv_invalid_with_msg(jv_string("modulemeta input module name must be a "));
string"));
} }
return load_module_meta(jq, a); return load_module_meta(jq, a);
} }
static jv f_input(jq_state *jq, jv input) { static jv f_input(jq_state *jq, jv input) {
jv_free(input); jv_free(input);
jq_input_cb cb; jq_input_cb cb;
void *data; void *data;
jq_get_input_cb(jq, &cb, &data); jq_get_input_cb(jq, &cb, &data);
if (cb == NULL) if (cb == NULL)
skipping to change at line 1007 skipping to change at line 1172
jq_msg_cb cb; jq_msg_cb cb;
void *data; void *data;
jq_get_debug_cb(jq, &cb, &data); jq_get_debug_cb(jq, &cb, &data);
if (cb != NULL) if (cb != NULL)
cb(data, jv_copy(input)); cb(data, jv_copy(input));
return input; return input;
} }
static jv f_stderr(jq_state *jq, jv input) { static jv f_stderr(jq_state *jq, jv input) {
jv_dumpf(jv_copy(input), stderr, 0); jv_dumpf(jv_copy(input), stderr, 0);
fprintf(stderr, "\n");
return input; return input;
} }
static jv tm2jv(struct tm *tm) { static jv tm2jv(struct tm *tm) {
return JV_ARRAY(jv_number(tm->tm_year + 1900), return JV_ARRAY(jv_number(tm->tm_year + 1900),
jv_number(tm->tm_mon), jv_number(tm->tm_mon),
jv_number(tm->tm_mday), jv_number(tm->tm_mday),
jv_number(tm->tm_hour), jv_number(tm->tm_hour),
jv_number(tm->tm_min), jv_number(tm->tm_min),
jv_number(tm->tm_sec), jv_number(tm->tm_sec),
skipping to change at line 1037 skipping to change at line 1201
* adjustment is, but you have to #define _BSD_SOURCE to get this * adjustment is, but you have to #define _BSD_SOURCE to get this
* field of struct tm on some systems. * field of struct tm on some systems.
* *
* This is all to blame on POSIX, of course. * This is all to blame on POSIX, of course.
* *
* Our wrapper tries to use timegm() if available, or mktime() and * Our wrapper tries to use timegm() if available, or mktime() and
* correct for its side-effects if possible. * correct for its side-effects if possible.
* *
* Returns (time_t)-2 if mktime()'s side-effects cannot be corrected. * Returns (time_t)-2 if mktime()'s side-effects cannot be corrected.
*/ */
static time_t my_mktime(struct tm *tm) { static time_t my_timegm(struct tm *tm) {
#ifdef HAVE_TIMEGM #ifdef HAVE_TIMEGM
return timegm(tm); return timegm(tm);
#else /* HAVE_TIMEGM */ #else /* HAVE_TIMEGM */
char *tz;
tz = (tz = getenv("TZ")) != NULL ? strdup(tz) : NULL;
if (tz != NULL)
setenv("TZ", "", 1);
time_t t = mktime(tm);
if (tz != NULL)
setenv("TZ", tz, 1);
return t;
#endif /* !HAVE_TIMEGM */
}
static time_t my_mktime(struct tm *tm) {
time_t t = mktime(tm); time_t t = mktime(tm);
if (t == (time_t)-1) if (t == (time_t)-1)
return t; return t;
#ifdef HAVE_TM_TM_GMT_OFF #ifdef HAVE_TM_TM_GMT_OFF
return t + tm.tm_gmtoff; return t + tm->tm_gmtoff;
#elif defined(HAVE_TM_TM_GMT_OFF) #elif HAVE_TM___TM_GMT_OFF
return t + tm.__tm_gmtoff; return t + tm->__tm_gmtoff;
#else #else
return (time_t)-2; /* Not supported */ return (time_t)-2; /* Not supported */
#endif #endif
#endif /* !HAVE_TIMEGM */ }
/* Compute and set tm_wday */
static void set_tm_wday(struct tm *tm) {
/*
* https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Gauss.27
s_algorithm
* https://cs.uwaterloo.ca/~alopez-o/math-faq/node73.html
*
* Tested with dates from 1900-01-01 through 2100-01-01. This
* algorithm produces the wrong day-of-the-week number for dates in
* the range 1900-01-01..1900-02-28, and for 2100-01-01..2100-02-28.
* Since this is only needed on OS X and *BSD, we might just document
* this.
*/
int century = (1900 + tm->tm_year) / 100;
int year = (1900 + tm->tm_year) % 100;
if (tm->tm_mon < 2)
year--;
/*
* The month value in the wday computation below is shifted so that
* March is 1, April is 2, .., January is 11, and February is 12.
*/
int mon = tm->tm_mon - 1;
if (mon < 1)
mon += 12;
int wday =
(tm->tm_mday + (int)floor((2.6 * mon - 0.2)) + year + (int)floor(year / 4.0)
+ (int)floor(century / 4.0) - 2 * century) % 7;
if (wday < 0)
wday += 7;
#if 0
/* See commentary above */
assert(wday == tm->tm_wday || tm->tm_wday == 8);
#endif
tm->tm_wday = wday;
}
/*
* Compute and set tm_yday.
*
*/
static void set_tm_yday(struct tm *tm) {
static const int d[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}
;
int mon = tm->tm_mon;
int year = 1900 + tm->tm_year;
int leap_day = 0;
if (tm->tm_mon > 1 &&
((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
leap_day = 1;
/* Bound check index into d[] */
if (mon < 0)
mon = -mon;
if (mon > 11)
mon %= 12;
int yday = d[mon] + leap_day + tm->tm_mday - 1;
assert(yday == tm->tm_yday || tm->tm_yday == 367);
tm->tm_yday = yday;
} }
#ifdef HAVE_STRPTIME #ifdef HAVE_STRPTIME
static jv f_strptime(jq_state *jq, jv a, jv b) { static jv f_strptime(jq_state *jq, jv a, jv b) {
if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING) if (jv_get_kind(a) != JV_KIND_STRING || jv_get_kind(b) != JV_KIND_STRING) {
return jv_invalid_with_msg(jv_string("strptime/1 requires string inputs and return ret_error2(a, b, jv_string("strptime/1 requires string inputs and arg
arguments")); uments"));
}
struct tm tm; struct tm tm;
memset(&tm, 0, sizeof(tm)); memset(&tm, 0, sizeof(tm));
tm.tm_wday = 8; // sentinel
tm.tm_yday = 367; // sentinel
const char *input = jv_string_value(a); const char *input = jv_string_value(a);
const char *fmt = jv_string_value(b); const char *fmt = jv_string_value(b);
const char *end = strptime(input, fmt, &tm); const char *end = strptime(input, fmt, &tm);
if (end == NULL || (*end != '\0' && !isspace(*end))) { if (end == NULL || (*end != '\0' && !isspace(*end))) {
jv e = jv_invalid_with_msg(jv_string_fmt("date \"%s\" does not match format return ret_error2(a, b, jv_string_fmt("date \"%s\" does not match format \"%
\"%s\"", input, fmt)); s\"", input, fmt));
jv_free(a);
jv_free(b);
return e;
} }
jv_free(b); jv_free(b);
if (tm.tm_wday == 0 && tm.tm_yday == 0 && my_mktime(&tm) == (time_t)-2) { /*
jv_free(a); * This is OS X or some *BSD whose strptime() is just not that
return jv_invalid_with_msg(jv_string("strptime/1 not supported on this platf * helpful!
orm")); *
} * We don't know that the format string did involve parsing a
* year, or a month (if tm->tm_mon == 0). But with our invalid
* day-of-week and day-of-year sentinel checks above, the worst
* this can do is produce garbage.
*/
#ifdef __APPLE__
/*
* Apple has made it worse, and different versions of the OS have different
* behaviors. Some versions just don't touch the fields, but others do, and
* sometimes provide wrong answers, at that! We can't tell at compile-time
* which behavior the target system will have, so instead we always use our
* functions to set these on OS X, and document that %u and %j are
* unsupported on OS X.
*/
set_tm_wday(&tm);
set_tm_yday(&tm);
#else
if (tm.tm_wday == 8 && tm.tm_mday != 0 && tm.tm_mon >= 0 && tm.tm_mon <= 11)
set_tm_wday(&tm);
if (tm.tm_yday == 367 && tm.tm_mday != 0 && tm.tm_mon >= 0 && tm.tm_mon <= 11)
set_tm_yday(&tm);
#endif
jv r = tm2jv(&tm); jv r = tm2jv(&tm);
if (*end != '\0') if (*end != '\0')
r = jv_array_append(r, jv_string(end)); r = jv_array_append(r, jv_string(end));
jv_free(a); // must come after `*end` because `end` is a pointer into `a`'s st ring jv_free(a); // must come after `*end` because `end` is a pointer into `a`'s st ring
return r; return r;
} }
#else #else
static jv f_strptime(jq_state *jq, jv a, jv b) { static jv f_strptime(jq_state *jq, jv a, jv b) {
jv_free(a); jv_free(a);
jv_free(b); jv_free(b);
return jv_invalid_with_msg(jv_string("strptime/1 not implemented on this platf orm")); return jv_invalid_with_msg(jv_string("strptime/1 not implemented on this platf orm"));
} }
#endif #endif
#define TO_TM_FIELD(t, j, i) \ #define TO_TM_FIELD(t, j, i) \
do { \ do { \
jv n = jv_array_get(jv_copy(j), (i)); \ jv n = jv_array_get(jv_copy(j), (i)); \
if (jv_get_kind(n) != (JV_KIND_NUMBER)) \ if (jv_get_kind(n) != (JV_KIND_NUMBER)) { \
jv_free(j); \
return 0; \ return 0; \
} \
t = jv_number_value(n); \ t = jv_number_value(n); \
jv_free(n); \ jv_free(n); \
} while (0) } while (0)
static int jv2tm(jv a, struct tm *tm) { static int jv2tm(jv a, struct tm *tm) {
memset(tm, 0, sizeof(*tm)); memset(tm, 0, sizeof(*tm));
TO_TM_FIELD(tm->tm_year, a, 0); TO_TM_FIELD(tm->tm_year, a, 0);
tm->tm_year -= 1900; tm->tm_year -= 1900;
TO_TM_FIELD(tm->tm_mon, a, 1); TO_TM_FIELD(tm->tm_mon, a, 1);
TO_TM_FIELD(tm->tm_mday, a, 2); TO_TM_FIELD(tm->tm_mday, a, 2);
skipping to change at line 1127 skipping to change at line 1383
// hope it is okay to initialize them to zero, because the standard does not // hope it is okay to initialize them to zero, because the standard does not
// provide an alternative. // provide an alternative.
return 1; return 1;
} }
#undef TO_TM_FIELD #undef TO_TM_FIELD
static jv f_mktime(jq_state *jq, jv a) { static jv f_mktime(jq_state *jq, jv a) {
if (jv_get_kind(a) != JV_KIND_ARRAY) if (jv_get_kind(a) != JV_KIND_ARRAY)
return jv_invalid_with_msg(jv_string("mktime requires array inputs")); return ret_error(a, jv_string("mktime requires array inputs"));
if (jv_array_length(jv_copy(a)) < 6) if (jv_array_length(jv_copy(a)) < 6)
return jv_invalid_with_msg(jv_string("mktime requires parsed datetime inputs ")); return ret_error(a, jv_string("mktime requires parsed datetime inputs"));
struct tm tm; struct tm tm;
if (!jv2tm(a, &tm)) if (!jv2tm(a, &tm))
return jv_invalid_with_msg(jv_string("mktime requires parsed datetime inputs ")); return jv_invalid_with_msg(jv_string("mktime requires parsed datetime inputs "));
time_t t = my_mktime(&tm); time_t t = my_mktime(&tm);
if (t == (time_t)-1) if (t == (time_t)-1)
return jv_invalid_with_msg(jv_string("invalid gmtime representation")); return jv_invalid_with_msg(jv_string("invalid gmtime representation"));
if (t == (time_t)-2) if (t == (time_t)-2)
return jv_invalid_with_msg(jv_string("mktime not supported on this platform" )); return jv_invalid_with_msg(jv_string("mktime not supported on this platform" ));
return jv_number(t); return jv_number(t);
} }
#ifdef HAVE_GMTIME_R #ifdef HAVE_GMTIME_R
static jv f_gmtime(jq_state *jq, jv a) { static jv f_gmtime(jq_state *jq, jv a) {
if (jv_get_kind(a) != JV_KIND_NUMBER) if (jv_get_kind(a) != JV_KIND_NUMBER)
return jv_invalid_with_msg(jv_string("gmtime() requires numeric inputs")); return ret_error(a, jv_string("gmtime() requires numeric inputs"));
struct tm tm, *tmp; struct tm tm, *tmp;
memset(&tm, 0, sizeof(tm)); memset(&tm, 0, sizeof(tm));
double fsecs = jv_number_value(a); double fsecs = jv_number_value(a);
time_t secs = fsecs; time_t secs = fsecs;
jv_free(a); jv_free(a);
tmp = gmtime_r(&secs, &tm); tmp = gmtime_r(&secs, &tm);
if (tmp == NULL) if (tmp == NULL)
return jv_invalid_with_msg(jv_string("errror converting number of seconds si nce epoch to datetime")); return jv_invalid_with_msg(jv_string("errror converting number of seconds si nce epoch to datetime"));
a = tm2jv(tmp); a = tm2jv(tmp);
return jv_array_set(a, 5, jv_number(jv_number_value(jv_array_get(jv_copy(a), 5 )) + (fsecs - floor(fsecs)))); return jv_array_set(a, 5, jv_number(jv_number_value(jv_array_get(jv_copy(a), 5 )) + (fsecs - floor(fsecs))));
} }
#elif defined HAVE_GMTIME #elif defined HAVE_GMTIME
static jv f_gmtime(jq_state *jq, jv a) { static jv f_gmtime(jq_state *jq, jv a) {
if (jv_get_kind(a) != JV_KIND_NUMBER) if (jv_get_kind(a) != JV_KIND_NUMBER)
return jv_invalid_with_msg(jv_string("gmtime requires numeric inputs")); return ret_error(a, jv_string("gmtime requires numeric inputs"));
struct tm tm, *tmp; struct tm tm, *tmp;
memset(&tm, 0, sizeof(tm)); memset(&tm, 0, sizeof(tm));
double fsecs = jv_number_value(a); double fsecs = jv_number_value(a);
time_t secs = fsecs; time_t secs = fsecs;
jv_free(a); jv_free(a);
tmp = gmtime(&secs); tmp = gmtime(&secs);
if (tmp == NULL) if (tmp == NULL)
return jv_invalid_with_msg(jv_string("errror converting number of seconds si nce epoch to datetime")); return jv_invalid_with_msg(jv_string("errror converting number of seconds si nce epoch to datetime"));
a = tm2jv(tmp); a = tm2jv(tmp);
return jv_array_set(a, 5, jv_number(jv_number_value(jv_array_get(jv_copy(a), 5 )) + (fsecs - floor(fsecs)))); return jv_array_set(a, 5, jv_number(jv_number_value(jv_array_get(jv_copy(a), 5 )) + (fsecs - floor(fsecs))));
} }
#else #else
static jv f_gmtime(jq_state *jq, jv a) { static jv f_gmtime(jq_state *jq, jv a) {
jv_free(a); jv_free(a);
return jv_invalid_with_msg(jv_string("gmtime not implemented on this platform" )); return jv_invalid_with_msg(jv_string("gmtime not implemented on this platform" ));
} }
#endif #endif
#ifdef HAVE_LOCALTIME_R
static jv f_localtime(jq_state *jq, jv a) {
if (jv_get_kind(a) != JV_KIND_NUMBER)
return ret_error(a, jv_string("localtime() requires numeric inputs"));
struct tm tm, *tmp;
memset(&tm, 0, sizeof(tm));
double fsecs = jv_number_value(a);
time_t secs = fsecs;
jv_free(a);
tmp = localtime_r(&secs, &tm);
if (tmp == NULL)
return jv_invalid_with_msg(jv_string("error converting number of seconds sin
ce epoch to datetime"));
a = tm2jv(tmp);
return jv_array_set(a, 5, jv_number(jv_number_value(jv_array_get(jv_copy(a), 5
)) + (fsecs - floor(fsecs))));
}
#elif defined HAVE_GMTIME
static jv f_localtime(jq_state *jq, jv a) {
if (jv_get_kind(a) != JV_KIND_NUMBER)
return ret_error(a, jv_string("localtime requires numeric inputs"));
struct tm tm, *tmp;
memset(&tm, 0, sizeof(tm));
double fsecs = jv_number_value(a);
time_t secs = fsecs;
jv_free(a);
tmp = localtime(&secs);
if (tmp == NULL)
return jv_invalid_with_msg(jv_string("error converting number of seconds sin
ce epoch to datetime"));
a = tm2jv(tmp);
return jv_array_set(a, 5, jv_number(jv_number_value(jv_array_get(jv_copy(a), 5
)) + (fsecs - floor(fsecs))));
}
#else
static jv f_localtime(jq_state *jq, jv a) {
jv_free(a);
return jv_invalid_with_msg(jv_string("localtime not implemented on this platfo
rm"));
}
#endif
#ifdef HAVE_STRFTIME #ifdef HAVE_STRFTIME
static jv f_strftime(jq_state *jq, jv a, jv b) { static jv f_strftime(jq_state *jq, jv a, jv b) {
if (jv_get_kind(a) == JV_KIND_NUMBER) { if (jv_get_kind(a) == JV_KIND_NUMBER) {
a = f_gmtime(jq, a); a = f_gmtime(jq, a);
} else if (jv_get_kind(a) != JV_KIND_ARRAY) { } else if (jv_get_kind(a) != JV_KIND_ARRAY) {
return jv_invalid_with_msg(jv_string("strftime/1 requires parsed datetime in return ret_error2(a, b, jv_string("strftime/1 requires parsed datetime input
puts")); s"));
} else if (jv_get_kind(b) != JV_KIND_STRING) {
return ret_error2(a, b, jv_string("strftime/1 requires a string format"));
} }
struct tm tm; struct tm tm;
if (!jv2tm(a, &tm)) if (!jv2tm(a, &tm))
return jv_invalid_with_msg(jv_string("strftime/1 requires parsed datetime in return ret_error(b, jv_string("strftime/1 requires parsed datetime inputs"))
puts")); \ ;
const char *fmt = jv_string_value(b); const char *fmt = jv_string_value(b);
size_t alloced = strlen(fmt) + 100; size_t alloced = strlen(fmt) + 100;
char *buf = alloca(alloced); char *buf = alloca(alloced);
size_t n = strftime(buf, alloced, fmt, &tm); size_t n = strftime(buf, alloced, fmt, &tm);
jv_free(b); jv_free(b);
/* POSIX doesn't provide errno values for strftime() failures; weird */ /* POSIX doesn't provide errno values for strftime() failures; weird */
if (n == 0 || n > alloced) if (n == 0 || n > alloced)
return jv_invalid_with_msg(jv_string("strftime/1: unknown system failure")); return jv_invalid_with_msg(jv_string("strftime/1: unknown system failure"));
return jv_string(buf); return jv_string(buf);
} }
#else #else
static jv f_strftime(jq_state *jq, jv a) { static jv f_strftime(jq_state *jq, jv a, jv b) {
jv_free(a); jv_free(a);
jv_free(b); jv_free(b);
return jv_invalid_with_msg(jv_string("strftime/1 not implemented on this platf orm")); return jv_invalid_with_msg(jv_string("strftime/1 not implemented on this platf orm"));
} }
#endif #endif
#ifdef HAVE_STRFTIME
static jv f_strflocaltime(jq_state *jq, jv a, jv b) {
if (jv_get_kind(a) == JV_KIND_NUMBER) {
a = f_localtime(jq, a);
} else if (jv_get_kind(a) != JV_KIND_ARRAY) {
return ret_error2(a, b, jv_string("strflocaltime/1 requires parsed datetime
inputs"));
} else if (jv_get_kind(b) != JV_KIND_STRING) {
return ret_error2(a, b, jv_string("strflocaltime/1 requires a string format"
));
}
struct tm tm;
if (!jv2tm(a, &tm))
return jv_invalid_with_msg(jv_string("strflocaltime/1 requires parsed dateti
me inputs"));
const char *fmt = jv_string_value(b);
size_t alloced = strlen(fmt) + 100;
char *buf = alloca(alloced);
size_t n = strftime(buf, alloced, fmt, &tm);
jv_free(b);
/* POSIX doesn't provide errno values for strftime() failures; weird */
if (n == 0 || n > alloced)
return jv_invalid_with_msg(jv_string("strflocaltime/1: unknown system failur
e"));
return jv_string(buf);
}
#else
static jv f_strflocaltime(jq_state *jq, jv a, jv b) {
jv_free(a);
jv_free(b);
return jv_invalid_with_msg(jv_string("strflocaltime/1 not implemented on this
platform"));
}
#endif
#ifdef HAVE_GETTIMEOFDAY #ifdef HAVE_GETTIMEOFDAY
static jv f_now(jq_state *jq, jv a) { static jv f_now(jq_state *jq, jv a) {
jv_free(a); jv_free(a);
struct timeval tv; struct timeval tv;
if (gettimeofday(&tv, NULL) == -1) if (gettimeofday(&tv, NULL) == -1)
return jv_number(time(NULL)); return jv_number(time(NULL));
return jv_number(tv.tv_sec + tv.tv_usec / 1000000.0); return jv_number(tv.tv_sec + tv.tv_usec / 1000000.0);
} }
#else #else
static jv f_now(jq_state *jq, jv a) { static jv f_now(jq_state *jq, jv a) {
jv_free(a); jv_free(a);
return jv_number(time(NULL)); return jv_number(time(NULL));
} }
#endif #endif
static jv f_current_filename(jq_state *jq) { static jv f_current_filename(jq_state *jq, jv a) {
jv_free(a);
jv r = jq_util_input_get_current_filename(jq); jv r = jq_util_input_get_current_filename(jq);
if (jv_is_valid(r)) if (jv_is_valid(r))
return r; return r;
jv_free(r); jv_free(r);
return jv_null(); return jv_null();
} }
static jv f_current_line(jq_state *jq) { static jv f_current_line(jq_state *jq, jv a) {
jv_free(a);
return jq_util_input_get_current_line(jq); return jq_util_input_get_current_line(jq);
} }
#define LIBM_DD(name) \ #define LIBM_DD(name) \
{(cfunction_ptr)f_ ## name, "_" #name, 1}, {(cfunction_ptr)f_ ## name, #name, 1},
#define LIBM_DD_NO(name) #define LIBM_DD_NO(name)
#define LIBM_DDD(name) \ #define LIBM_DDD(name) \
{(cfunction_ptr)f_ ## name, "_" #name, 3}, {(cfunction_ptr)f_ ## name, #name, 3},
#define LIBM_DDD_NO(name) #define LIBM_DDD_NO(name)
#define LIBM_DDDD(name) \
{(cfunction_ptr)f_ ## name, #name, 4},
#define LIBM_DDDD_NO(name)
static const struct cfunction function_list[] = { static const struct cfunction function_list[] = {
#include "libm.h" #include "libm.h"
#ifdef HAVE_FREXP
{(cfunction_ptr)f_frexp,"frexp", 1},
#endif
#ifdef HAVE_MODF
{(cfunction_ptr)f_modf,"modf", 1},
#endif
#ifdef HAVE_LGAMMA_R
{(cfunction_ptr)f_lgamma_r,"lgamma_r", 1},
#endif
{(cfunction_ptr)f_plus, "_plus", 3}, {(cfunction_ptr)f_plus, "_plus", 3},
{(cfunction_ptr)f_negate, "_negate", 1}, {(cfunction_ptr)f_negate, "_negate", 1},
{(cfunction_ptr)f_minus, "_minus", 3}, {(cfunction_ptr)f_minus, "_minus", 3},
{(cfunction_ptr)f_multiply, "_multiply", 3}, {(cfunction_ptr)f_multiply, "_multiply", 3},
{(cfunction_ptr)f_divide, "_divide", 3}, {(cfunction_ptr)f_divide, "_divide", 3},
{(cfunction_ptr)f_mod, "_mod", 3}, {(cfunction_ptr)f_mod, "_mod", 3},
{(cfunction_ptr)f_dump, "tojson", 1}, {(cfunction_ptr)f_dump, "tojson", 1},
{(cfunction_ptr)f_json_parse, "fromjson", 1}, {(cfunction_ptr)f_json_parse, "fromjson", 1},
{(cfunction_ptr)f_tonumber, "tonumber", 1}, {(cfunction_ptr)f_tonumber, "tonumber", 1},
{(cfunction_ptr)f_tostring, "tostring", 1}, {(cfunction_ptr)f_tostring, "tostring", 1},
skipping to change at line 1274 skipping to change at line 1616
{(cfunction_ptr)f_delpaths, "delpaths", 2}, {(cfunction_ptr)f_delpaths, "delpaths", 2},
{(cfunction_ptr)f_has, "has", 2}, {(cfunction_ptr)f_has, "has", 2},
{(cfunction_ptr)f_equal, "_equal", 3}, {(cfunction_ptr)f_equal, "_equal", 3},
{(cfunction_ptr)f_notequal, "_notequal", 3}, {(cfunction_ptr)f_notequal, "_notequal", 3},
{(cfunction_ptr)f_less, "_less", 3}, {(cfunction_ptr)f_less, "_less", 3},
{(cfunction_ptr)f_greater, "_greater", 3}, {(cfunction_ptr)f_greater, "_greater", 3},
{(cfunction_ptr)f_lesseq, "_lesseq", 3}, {(cfunction_ptr)f_lesseq, "_lesseq", 3},
{(cfunction_ptr)f_greatereq, "_greatereq", 3}, {(cfunction_ptr)f_greatereq, "_greatereq", 3},
{(cfunction_ptr)f_contains, "contains", 2}, {(cfunction_ptr)f_contains, "contains", 2},
{(cfunction_ptr)f_length, "length", 1}, {(cfunction_ptr)f_length, "length", 1},
{(cfunction_ptr)f_utf8bytelength, "utf8bytelength", 1},
{(cfunction_ptr)f_type, "type", 1}, {(cfunction_ptr)f_type, "type", 1},
{(cfunction_ptr)f_isinfinite, "isinfinite", 1}, {(cfunction_ptr)f_isinfinite, "isinfinite", 1},
{(cfunction_ptr)f_isnan, "isnan", 1}, {(cfunction_ptr)f_isnan, "isnan", 1},
{(cfunction_ptr)f_isnormal, "isnormal", 1}, {(cfunction_ptr)f_isnormal, "isnormal", 1},
{(cfunction_ptr)f_infinite, "infinite", 1}, {(cfunction_ptr)f_infinite, "infinite", 1},
{(cfunction_ptr)f_nan, "nan", 1}, {(cfunction_ptr)f_nan, "nan", 1},
{(cfunction_ptr)f_sort, "sort", 1}, {(cfunction_ptr)f_sort, "sort", 1},
{(cfunction_ptr)f_sort_by_impl, "_sort_by_impl", 2}, {(cfunction_ptr)f_sort_by_impl, "_sort_by_impl", 2},
{(cfunction_ptr)f_group_by_impl, "_group_by_impl", 2}, {(cfunction_ptr)f_group_by_impl, "_group_by_impl", 2},
{(cfunction_ptr)f_min, "min", 1}, {(cfunction_ptr)f_min, "min", 1},
{(cfunction_ptr)f_max, "max", 1}, {(cfunction_ptr)f_max, "max", 1},
{(cfunction_ptr)f_min_by_impl, "_min_by_impl", 2}, {(cfunction_ptr)f_min_by_impl, "_min_by_impl", 2},
{(cfunction_ptr)f_max_by_impl, "_max_by_impl", 2}, {(cfunction_ptr)f_max_by_impl, "_max_by_impl", 2},
{(cfunction_ptr)f_error, "error", 2}, {(cfunction_ptr)f_error, "error", 2},
{(cfunction_ptr)f_format, "format", 2}, {(cfunction_ptr)f_format, "format", 2},
{(cfunction_ptr)f_env, "env", 1}, {(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_search_list, "get_search_list", 1},
{(cfunction_ptr)f_get_prog_origin, "get_prog_origin", 1}, {(cfunction_ptr)f_get_prog_origin, "get_prog_origin", 1},
{(cfunction_ptr)f_get_jq_origin, "get_jq_origin", 1}, {(cfunction_ptr)f_get_jq_origin, "get_jq_origin", 1},
{(cfunction_ptr)f_match, "_match_impl", 4}, {(cfunction_ptr)f_match, "_match_impl", 4},
{(cfunction_ptr)f_modulemeta, "modulemeta", 1}, {(cfunction_ptr)f_modulemeta, "modulemeta", 1},
{(cfunction_ptr)f_input, "_input", 1}, {(cfunction_ptr)f_input, "_input", 1},
{(cfunction_ptr)f_debug, "debug", 1}, {(cfunction_ptr)f_debug, "debug", 1},
{(cfunction_ptr)f_stderr, "stderr", 1}, {(cfunction_ptr)f_stderr, "stderr", 1},
{(cfunction_ptr)f_strptime, "strptime", 2}, {(cfunction_ptr)f_strptime, "strptime", 2},
{(cfunction_ptr)f_strftime, "strftime", 2}, {(cfunction_ptr)f_strftime, "strftime", 2},
{(cfunction_ptr)f_strflocaltime, "strflocaltime", 2},
{(cfunction_ptr)f_mktime, "mktime", 1}, {(cfunction_ptr)f_mktime, "mktime", 1},
{(cfunction_ptr)f_gmtime, "gmtime", 1}, {(cfunction_ptr)f_gmtime, "gmtime", 1},
{(cfunction_ptr)f_localtime, "localtime", 1},
{(cfunction_ptr)f_now, "now", 1}, {(cfunction_ptr)f_now, "now", 1},
{(cfunction_ptr)f_current_filename, "input_filename", 1}, {(cfunction_ptr)f_current_filename, "input_filename", 1},
{(cfunction_ptr)f_current_line, "input_line_number", 1}, {(cfunction_ptr)f_current_line, "input_line_number", 1},
}; };
#undef LIBM_DDDD_NO
#undef LIBM_DDD_NO #undef LIBM_DDD_NO
#undef LIBM_DD_NO #undef LIBM_DD_NO
#undef LIBM_DDDD
#undef LIBM_DDD #undef LIBM_DDD
#undef LIBM_DD #undef LIBM_DD
struct bytecoded_builtin { const char* name; block code; }; struct bytecoded_builtin { const char* name; block code; };
static block bind_bytecoded_builtins(block b) { static block bind_bytecoded_builtins(block b) {
block builtins = gen_noop(); block builtins = gen_noop();
{ {
struct bytecoded_builtin builtin_defs[] = { struct bytecoded_builtin builtin_defs[] = {
{"empty", gen_op_simple(BACKTRACK)}, {"empty", gen_op_simple(BACKTRACK)},
{"not", gen_condbranch(gen_const(jv_false()), {"not", gen_condbranch(gen_const(jv_false()),
skipping to change at line 1340 skipping to change at line 1689
}; };
for (unsigned i=0; i<sizeof(builtin_def_1arg)/sizeof(builtin_def_1arg[0]); i ++) { for (unsigned i=0; i<sizeof(builtin_def_1arg)/sizeof(builtin_def_1arg[0]); i ++) {
builtins = BLOCK(builtins, gen_function(builtin_def_1arg[i].name, builtins = BLOCK(builtins, gen_function(builtin_def_1arg[i].name,
gen_param("arg"), gen_param("arg"),
builtin_def_1arg[i].code)); builtin_def_1arg[i].code));
} }
} }
{ {
// Note that we can now define `range` as a jq-coded function // Note that we can now define `range` as a jq-coded function
block rangevar = gen_op_var_fresh(STOREV, "rangevar"); block rangevar = gen_op_var_fresh(STOREV, "rangevar");
block init = BLOCK(gen_op_simple(DUP), gen_call("start", gen_noop()), rangev block rangestart = gen_op_var_fresh(STOREV, "rangestart");
ar); block range = BLOCK(gen_op_simple(DUP),
block range = BLOCK(init, gen_call("start", gen_noop()),
rangestart,
gen_call("end", gen_noop()), gen_call("end", gen_noop()),
gen_op_simple(DUP),
gen_op_bound(LOADV, rangestart),
// Reset rangevar for every value generated by "end"
rangevar,
gen_op_bound(RANGE, rangevar)); gen_op_bound(RANGE, rangevar));
builtins = BLOCK(builtins, gen_function("range", builtins = BLOCK(builtins, gen_function("range",
BLOCK(gen_param("start"), gen_param( "end")), BLOCK(gen_param("start"), gen_param( "end")),
range)); range));
} }
return block_bind(builtins, b, OP_IS_CALL_PSEUDO);
return block_bind_referenced(builtins, b, OP_IS_CALL_PSEUDO);
} }
#define LIBM_DD(name) "def " #name ": _" #name ";", static const char* const jq_builtins =
#define LIBM_DDD(name) "def " #name "(a;b): _" #name "(a;b);", /* Include jq-coded builtins */
#define LIBM_DD_NO(name) "def " #name ": \"Error: " #name "() not found at build #include "src/builtin.inc"
time\"|error;",
#define LIBM_DDD_NO(name) "def " #name "(a;b): \"Error: " #name "() not found at /* Include unsupported math functions next */
build time\"|error;", #define LIBM_DD(name)
#define LIBM_DDD(name)
static const char* const jq_builtins[] = { #define LIBM_DDDD(name)
"def error: error(.);", #define LIBM_DD_NO(name) "def " #name ": \"Error: " #name "/0 not found at build
"def map(f): [.[] | f];", time\"|error;"
"def select(f): if f then . else empty end;", #define LIBM_DDD_NO(name) "def " #name "(a;b): \"Error: " #name "/2 not found at
"def sort_by(f): _sort_by_impl(map([f]));", build time\"|error;"
"def group_by(f): _group_by_impl(map([f]));", #define LIBM_DDDD_NO(name) "def " #name "(a;b;c): \"Error: " #name "/3 not found
"def unique: group_by(.) | map(.[0]);", at build time\"|error;"
"def unique_by(f): group_by(f) | map(.[0]);",
"def max_by(f): _max_by_impl(map([f]));",
"def min_by(f): _min_by_impl(map([f]));",
#include "libm.h" #include "libm.h"
"def add: reduce .[] as $x (null; . + $x);", #ifndef HAVE_FREXP
"def del(f): delpaths([path(f)]);", "def frexp: \"Error: frexp/0 not found found at build time\"|error;"
"def _assign(paths; value): value as $v | reduce path(paths) as $p (.; setpath #endif
($p; $v));", #ifndef HAVE_MODF
"def _modify(paths; update): reduce path(paths) as $p (.; setpath($p; getpath( "def modf: \"Error: modf/0 not found found at build time\"|error;"
$p) | update));", #endif
"def map_values(f): .[] |= f;", #ifndef HAVE_LGAMMA_R
"def lgamma_r: \"Error: lgamma_r/0 not found found at build time\"|error;"
// recurse #endif
"def recurse(f): def r: ., (f | select(. != null) | r); r;", ;
"def recurse(f; cond): def r: ., (f | select(cond) | r); r;",
"def recurse: recurse(.[]?);", #undef LIBM_DDDD_NO
"def recurse_down: recurse;",
"def to_entries: [keys_unsorted[] as $k | {key: $k, value: .[$k]}];",
"def from_entries: map({(.key // .Key // .Name): (if has(\"value\") then .valu
e else .Value end)}) | add | .//={};",
"def with_entries(f): to_entries | map(f) | from_entries;",
"def reverse: [.[length - 1 - range(0;length)]];",
"def indices($i): if type == \"array\" and ($i|type) == \"array\" then .[$i]"
" elif type == \"array\" then .[[$i]]"
" elif type == \"string\" and ($i|type) == \"string\" then _strindices($i)"
" else .[$i] end;",
"def index($i): indices($i) | .[0];", // TODO: optimize
"def rindex($i): indices($i) | .[-1:][0];", // TODO: optimize
"def paths: path(recurse(if (type|. == \"array\" or . == \"object\") then .[]
else empty end))|select(length > 0);",
"def paths(node_filter): . as $dot|paths|select(. as $p|$dot|getpath($p)|node_
filter);",
"def any(generator; condition):"
" [label $out | foreach generator as $i"
" (false;"
" if . then break $out elif $i | condition then true else . e
nd;"
" if . then . else empty end)] | length == 1;",
"def any(condition): any(.[]; condition);",
"def any: any(.);",
"def all(generator; condition): "
" [label $out | foreach generator as $i"
" (true;"
" if .|not then break $out elif $i | condition then . else fa
lse end;"
" if .|not then . else empty end)] | length == 0;",
"def all(condition): all(.[]; condition);",
"def all: all(.);",
"def isfinite: type == \"number\" and (isinfinite | not);",
"def arrays: select(type == \"array\");",
"def objects: select(type == \"object\");",
"def iterables: arrays, objects;",
"def booleans: select(type == \"boolean\");",
"def numbers: select(type == \"number\");",
"def normals: select(isnormal);",
"def finites: select(isfinite);",
"def strings: select(type == \"string\");",
"def nulls: select(type == \"null\");",
"def values: select(. != null);",
"def scalars: select(. == null or . == true or . == false or type == \"number\
" or type == \"string\");",
"def scalars_or_empty: select(. == null or . == true or . == false or type ==
\"number\" or type == \"string\" or ((type==\"array\" or type==\"object\") and l
ength==0));",
"def leaf_paths: paths(scalars);",
"def join($x): reduce .[] as $i (null; (.//\"\") + (if . == null then $i else
$x + $i 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);",
"def range($x): range(0;$x);",
"def fromdateiso8601: strptime(\"%Y-%m-%dT%H:%M:%SZ\")|mktime;",
"def todateiso8601: strftime(\"%Y-%m-%dT%H:%M:%SZ\");",
"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; n
ull)"
" 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; nul
l)"
" 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(.n
ame != null) | { (.name) : .string } ) as $pair ({}; . + $pair);",
"def capture($val): ($val|type) as $vt | if $vt == \"string\" then capture($va
l; 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;",
"def scan(re):"
" match(re; \"g\")"
" | if (.captures|length > 0)"
" then [ .captures | .[] | .string ]"
" else .string"
" end ;",
//
// If input is an array, then emit a stream of successive subarrays of length
n (or less),
// and similarly for strings.
"def _nwise(a; $n): if a|length <= $n then a else a[0:$n] , _nwise(a[$n:]; $n)
end;",
"def _nwise($n): _nwise(.; $n);",
//
// splits/1 produces a stream; split/1 is retained for backward compatibility.
"def splits($re; flags): . as $s"
// # multiple occurrences of "g" are acceptable
" | [ match($re; \"g\" + flags) | (.offset, .offset + .length) ]"
" | [0] + . +[$s|length]"
" | _nwise(2)"
" | $s[.[0]:.[1] ] ;",
"def splits($re): splits($re; null);",
//
// split emits an array for backward compatibility
"def split($re; flags): [ splits($re; flags) ];",
//
// If s contains capture variables, then create a capture object and pipe it t
o s
"def sub($re; s):"
" . as $in"
" | [match($re)]"
" | if length == 0 then $in"
" else .[0]"
" | . as $r"
// # create the \"capture\" object:
" | reduce ( $r | .captures | .[] | select(.name != null) | { (.name) : .st
ring } ) as $pair"
" ({}; . + $pair)"
" | $in[0:$r.offset] + s + $in[$r.offset+$r.length:]"
" end ;",
//
// If s contains capture variables, then create a capture object and pipe it t
o s
"def sub($re; s; flags):"
" def subg: explode | select(. != 103) | implode;"
// # "fla" should be flags with all occurrences of g removed; gs should be non
-nil if flags has a g
" def sub1(fla; gs):"
" def mysub:"
" . as $in"
" | [match($re; fla)]"
" | if length == 0 then $in"
" else .[0] as $edit"
" | ($edit | .offset + .length) as $len"
// # create the "capture" object:
" | reduce ( $edit | .captures | .[] | select(.name != null) | { (.name
) : .string } ) as $pair"
" ({}; . + $pair)"
" | $in[0:$edit.offset]"
" + s"
" + ($in[$len:] | if gs then mysub else . end)"
" end ;"
" mysub ;"
" (flags | index(\"g\")) as $gs"
" | (flags | if $gs then subg else . end) as $fla"
" | sub1($fla; $gs);",
//
"def sub($re; s): sub($re; s; \"\");",
// repeated substitution of re (which may contain named captures)
"def gsub($re; s; flags): sub($re; s; flags + \"g\");",
"def gsub($re; s): sub($re; s; \"g\");",
//#######################################################################
// range/3, with a `by` expression argument
"def range($init; $upto; $by): "
" def _range: "
" if ($by > 0 and . < $upto) or ($by < 0 and . > $upto) then ., ((.+$by
)|_range) else . end; "
" if $by == 0 then $init else $init|_range end | select(($by > 0 and . < $u
pto) or ($by < 0 and . > $upto));",
// generic iterator/generator
"def while(cond; update): "
" 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 limit($n; exp): if $n < 0 then exp else label $out | foreach exp as $item
([$n, null]; if .[0] < 1 then break $out else [.[0] -1, $item] end; .[1]) end;"
,
"def first(g): label $out | foreach g as $item ([false, null]; if .[0]==true t
hen break $out else [true, $item] end; .[1]);",
"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 last(limit($n + 1; g)) end;",
"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;",
"def combinations(n):"
" . 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:"
" if . == [] then []"
" else . as $in"
" | (map(length) | max) as $max"
" | length as $length"
" | reduce range(0; $max) as $j"
" ([]; . + [reduce range(0;$length) as $i ([]; . + [ $in[$i][$j] ] )] )"
" end;",
"def in(xs): . as $x | xs | has($x);",
"def inside(xs): . as $x | xs | contains($x);",
"def input: _input;",
"def repeat(exp): "
" 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:"
" explode | map( if 65 <= . and . <= 90 then . + 32 else . end) | implode;",
// # like ruby's upcase - only characters a to z are affected
"def ascii_upcase:"
" explode | map( if 97 <= . and . <= 122 then . - 32 else . end) | implode;"
,
// Streaming utilities
"def truncate_stream(stream):"
" . as $n | null | stream | . as $input | if (.[0]|length) > $n then setpath(
[0];$input[0][1:]) else empty end;",
"def fromstream(i):"
" foreach i as $item ("
" [null,false,null,false];"
" if ($item[0]|length) == 0 then [null,false,.[2],.[3]]"
" elif ($item|length) == 1 and ($item[0]|length) < 2 then [null,false,.[0],
.[1]]"
" else . end |"
" . as $state |"
" if ($item|length) > 1 and ($item[0]|length) > 0 then"
" [.[0]|setpath(($item|.[0]); ($item|.[1])), "
" true, "
" $state[2], "
" $state[3]] "
" else ."
" end;"
" if ($item[0]|length) == 1 and ($item|length == 1) and .[3] then .[2] else
empty end,"
" if ($item[0]|length) == 0 then $item[1] else empty end"
" );",
"def tostream:\n"
" {string:true,number:true,boolean:true,null:true} as $leaf_types |\n"
" . as $dot |\n"
" if $leaf_types[$dot|type] or length==0 then [[],$dot]\n"
" else\n"
" # We really need a _streaming_ form of `keys`.\n"
" # We can use `range` for arrays, but not for objects.\n"
" keys as $keys |\n"
" $keys[-1] as $last|\n"
" ((# for each key\n"
" $keys[] | . as $key |\n"
" $dot[$key] | . as $dot |\n"
" # recurse on each key/value\n"
" tostream|.[0]|=[$key]+.),\n"
" # then add the closing marker\n"
" [[$last]])\n"
" end;",
// # Assuming the input array is sorted, bsearch/1 returns
// # the index of the target if the target is in the input array; and otherwis
e
// # (-1 - ix), where ix is the insertion point that would leave the array so
rted.
// # If the input is not sorted, bsearch will terminate but with irrelevant re
sults.
"def bsearch(target):"
" if length == 0 then -1"
" elif length == 1 then"
" if target == .[0] then 0 elif target < .[0] then -1 else -2 end"
" else . as $in"
"" // # state variable: [start, end, answer]
"" // # where start and end are the upper and lower offsets to use.
" | [0, length-1, null]"
" | until( .[0] > .[1] ;"
" if .[2] != null then (.[1] = -1)" // # i.e. break
" else"
" ( ( (.[1] + .[0]) / 2 ) | floor ) as $mid"
" | $in[$mid] as $monkey"
" | if $monkey == target then (.[2] = $mid)" // # success
" elif .[0] == .[1] then (.[1] = -1)" // # failure
" elif $monkey < target then (.[0] = ($mid + 1))"
" else (.[1] = ($mid - 1))"
" end"
" end )"
" | if .[2] == null then" // # compute the insertion point
" if $in[ .[0] ] < target then (-2 -.[0])"
" else (-1 -.[0])"
" end"
" else .[2]"
" end"
" end;",
};
#undef LIBM_DDD_NO #undef LIBM_DDD_NO
#undef LIBM_DD_NO #undef LIBM_DD_NO
#undef LIBM_DDDD
#undef LIBM_DDD #undef LIBM_DDD
#undef LIBM_DD #undef LIBM_DD
static block gen_builtin_list(block builtins) {
jv list = jv_array_append(block_list_funcs(builtins, 1), jv_string("builtins/0
"));
return BLOCK(builtins, gen_function("builtins", gen_noop(), gen_const(list)));
}
static int builtins_bind_one(jq_state *jq, block* bb, const char* code) { static int builtins_bind_one(jq_state *jq, block* bb, const char* code) {
struct locfile* src; struct locfile* src;
src = locfile_init(jq, "<builtin>", code, strlen(code)); src = locfile_init(jq, "<builtin>", code, strlen(code));
block funcs; block funcs;
int nerrors = jq_parse_library(src, &funcs); int nerrors = jq_parse_library(src, &funcs);
if (nerrors == 0) { if (nerrors == 0) {
*bb = block_bind_referenced(funcs, *bb, OP_IS_CALL_PSEUDO); *bb = block_bind(funcs, *bb, OP_IS_CALL_PSEUDO);
} }
locfile_free(src); locfile_free(src);
return nerrors; return nerrors;
} }
static int slurp_lib(jq_state *jq, block* bb) { static int slurp_lib(jq_state *jq, block* bb) {
int nerrors = 0; int nerrors = 0;
char* home = getenv("HOME"); char* home = getenv("HOME");
if (home) { // silently ignore no $HOME if (home) { // silently ignore no $HOME
jv filename = jv_string_append_str(jv_string(home), "/.jq"); jv filename = jv_string_append_str(jv_string(home), "/.jq");
skipping to change at line 1668 skipping to change at line 1769
if (jv_is_valid(data)) { if (jv_is_valid(data)) {
nerrors = builtins_bind_one(jq, bb, jv_string_value(data) ); nerrors = builtins_bind_one(jq, bb, jv_string_value(data) );
} }
jv_free(filename); jv_free(filename);
jv_free(data); jv_free(data);
} }
return nerrors; return nerrors;
} }
int builtins_bind(jq_state *jq, block* bb) { int builtins_bind(jq_state *jq, block* bb) {
block builtins = gen_noop();
int nerrors = slurp_lib(jq, bb); int nerrors = slurp_lib(jq, bb);
if (nerrors) { if (nerrors) {
block_free(*bb); block_free(*bb);
return nerrors; return nerrors;
} }
for (int i=(int)(sizeof(jq_builtins)/sizeof(jq_builtins[0]))-1; i>=0; i--) { nerrors = builtins_bind_one(jq, &builtins, jq_builtins);
nerrors = builtins_bind_one(jq, bb, jq_builtins[i]); assert(!nerrors);
assert(!nerrors); builtins = bind_bytecoded_builtins(builtins);
} builtins = gen_cbinding(function_list, sizeof(function_list)/sizeof(function_l
*bb = bind_bytecoded_builtins(*bb); ist[0]), builtins);
*bb = gen_cbinding(function_list, sizeof(function_list)/sizeof(function_list[0 builtins = gen_builtin_list(builtins);
]), *bb); *bb = block_bind(builtins, *bb, OP_IS_CALL_PSEUDO);
*bb = block_drop_unreferenced(*bb);
return nerrors; return nerrors;
} }
 End of changes. 70 change blocks. 
395 lines changed or deleted 487 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)