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 |