From cf4b48c7ba30cb30e116b523cff036ea481459f6 Mon Sep 17 00:00:00 2001 From: "Leonid S. Usov" Date: Fri, 19 Oct 2018 21:57:41 +0300 Subject: Save literal value of the parsed number to preserve it for the output Extend jv_number to use decNumber for storing number literals. Any math operations on the numbers will truncate them to double precision. Comparisons when both numbers are literal numbers will compare them without truncation. Delay conversion of numbers to doubles until a math operation is performed, to preserve precision. A literal jv_number will only need conversion to double once, and will reuse the resultant double on subsequent conversions. Outputting literal jv_numbers preserves the original precision. Add strong pthread requirement to manage contexts/allocations for converting numbers between their decNumber, string, and double formats. --- .gitignore | 1 + Makefile.am | 12 +- configure.ac | 14 +- docs/content/manual/v1.6/manual.yml | 48 ++ src/builtin.c | 29 +- src/execute.c | 15 +- src/jq_test.c | 73 ++- src/jv.c | 651 ++++++++++++++++------ src/jv.h | 5 +- src/jv_aux.c | 39 +- src/jv_dtoa_tsd.c | 46 ++ src/jv_dtoa_tsd.h | 4 + src/jv_parse.c | 23 +- src/jv_print.c | 37 +- src/jv_type_private.h | 7 + src/parser.c | 1029 ++++++++++++++++++----------------- src/parser.h | 16 +- src/parser.y | 24 +- tests/jq.test | 39 ++ tests/local.supp | 14 + tests/setup | 3 +- 21 files changed, 1374 insertions(+), 755 deletions(-) create mode 100644 src/jv_dtoa_tsd.c create mode 100644 src/jv_dtoa_tsd.h create mode 100644 src/jv_type_private.h create mode 100644 tests/local.supp diff --git a/.gitignore b/.gitignore index 7a53e6ec..acdf76c2 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ tests/*.trs cscope.in.out cscope.out cscope.po.out +jq.dSYM diff --git a/Makefile.am b/Makefile.am index 0dd1906f..9ff75274 100644 --- a/Makefile.am +++ b/Makefile.am @@ -11,6 +11,8 @@ LIBJQ_SRC = src/builtin.c src/bytecode.c src/compile.c src/execute.c \ src/jq_test.c src/jv.c src/jv_alloc.c src/jv_aux.c \ src/jv_dtoa.c src/jv_file.c src/jv_parse.c src/jv_print.c \ src/jv_unicode.c src/linker.c src/locfile.c src/util.c \ + src/decNumber/decContext.c src/decNumber/decNumber.c \ + src/jv_dtoa_tsd.c \ ${LIBJQ_INCS} ### C build options @@ -186,9 +188,13 @@ EXTRA_DIST = $(DOC_FILES) $(man_MANS) $(TESTS) $(TEST_LOG_COMPILER) \ tests/modules/test_bind_order.jq \ tests/modules/test_bind_order0.jq \ tests/modules/test_bind_order1.jq \ - tests/modules/test_bind_order2.jq tests/onig.supp \ - tests/onig.test tests/optional.test tests/setup \ - tests/torture/input0.json tests/utf8-truncate.jq + tests/modules/test_bind_order2.jq \ + tests/onig.supp tests/local.supp \ + tests/onig.test tests/setup tests/torture/input0.json \ + tests/optional.test tests/optionaltest \ + tests/utf8-truncate.jq tests/utf8test \ + tests/base64.test tests/base64test \ + tests/jq-f-test.sh tests/shtest # README.md is expected in Github projects, good stuff in it, so we'll # distribute it and install it with the package in the doc directory. diff --git a/configure.ac b/configure.ac index 47102697..2d6bf1cb 100644 --- a/configure.ac +++ b/configure.ac @@ -136,17 +136,9 @@ AC_CHECK_MEMBER([struct tm.tm_gmtoff], [AC_DEFINE([HAVE_TM_TM_GMT_OFF],1,[Define AC_CHECK_MEMBER([struct tm.__tm_gmtoff], [AC_DEFINE([HAVE_TM___TM_GMT_OFF],1,[Define to 1 if the system has the __tm_gmt_off field in struct tm])], [], [[#include ]]) -AC_ARG_ENABLE([pthread-tls], - [AC_HELP_STRING([--enable-pthread-tls], - [Enable use of pthread thread local storage])], - [], - [enable_pthread_tls=no]) - -if test $enable_pthread_tls = yes; then - AC_FIND_FUNC([pthread_key_create], [pthread], [#include ], [NULL, NULL]) - AC_FIND_FUNC([pthread_once], [pthread], [#include ], [NULL, NULL]) - AC_FIND_FUNC([atexit], [pthread], [#include ], [NULL]) -fi +AC_FIND_FUNC([pthread_key_create], [pthread], [#include ], [NULL, NULL]) +AC_FIND_FUNC([pthread_once], [pthread], [#include ], [NULL, NULL]) +AC_FIND_FUNC([atexit], [pthread], [#include ], [NULL]) dnl libm math.h functions AC_CHECK_MATH_FUNC(acos) diff --git a/docs/content/manual/v1.6/manual.yml b/docs/content/manual/v1.6/manual.yml index b495d1e1..04e19048 100644 --- a/docs/content/manual/v1.6/manual.yml +++ b/docs/content/manual/v1.6/manual.yml @@ -292,11 +292,37 @@ sections: program can be a useful way of formatting JSON output from, say, `curl`. + An important point about the identity filter is that it + guarantees to preserve the literal decimal representation + of values. This is particularly important when dealing with numbers + which can't be losslessly converted to an IEEE754 double precision + representation. + + jq doesn't truncate the literal numbers to double unless there + is a need to make arithmetic operations with the number. + Comparisions are carried out over the untruncated big decimal + representation of the number. + + jq will also try to maintain the original decimal precision of the provided + number literal. See below for examples. + examples: - program: '.' input: '"Hello, world!"' output: ['"Hello, world!"'] + - program: '. | tojson' + input: '12345678909876543212345' + output: ['"12345678909876543212345"'] + + - program: 'map([., . == 1]) | tojson' + input: '[1, 1.000, 1.0, 100e-2]' + output: ['"[[1,true],[1.000,true],[1.0,true],[1.00,true]]"'] + + - program: '. as $big | [$big, $big + 1] | map(. > 10000000000000000000000000000000)' + input: '10000000000000000000000000000001' + output: ['[true, false]'] + - title: "Object Identifier-Index: `.foo`, `.foo.bar`" body: | @@ -512,6 +538,16 @@ sections: expression that takes an input, ignores it, and returns 42 instead. + Numbers in jq are internally represented by their IEEE754 double + precision approximation. Any arithmetic operation with numbers, + whether they are literals or results of previous filters, will + produce a double precision floating point result. + + However, when parsing a literal jq will store the original literal + string. If no mutation is applied to this value then it will make + to the output in its original form, even if conversion to double + would result in a loss. + entries: - title: "Array construction: `[]`" body: | @@ -630,6 +666,18 @@ sections: try to add a string to an object you'll get an error message and no result. + Please note that all numbers are converted to IEEE754 double precision + floating point representation. Arithmetic and logical operators are working + with these converted doubles. Results of all such operations are also limited + to the double precision. + + The only exception to this behaviour of number is a snapshot of original number + literal. When a number which originally was provided as a literal is never + mutated until the end of the program then it is printed to the output in its + original literal form. This also includes cases when the original literal + would be truncated when converted to the IEEE754 double precision floating point + number. + entries: - title: "Addition: `+`" body: | diff --git a/src/builtin.c b/src/builtin.c index f52f56e2..b67f9c8d 100644 --- a/src/builtin.c +++ b/src/builtin.c @@ -90,8 +90,11 @@ static jv f_plus(jq_state *jq, jv input, jv a, jv b) { jv_free(b); return a; } else if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) { - return jv_number(jv_number_value(a) + + jv r = jv_number(jv_number_value(a) + jv_number_value(b)); + jv_free(a); + jv_free(b); + return r; } else if (jv_get_kind(a) == JV_KIND_STRING && jv_get_kind(b) == JV_KIND_STRING) { return jv_string_concat(a, b); } else if (jv_get_kind(a) == JV_KIND_ARRAY && jv_get_kind(b) == JV_KIND_ARRAY) { @@ -274,7 +277,10 @@ static jv f_rtrimstr(jq_state *jq, jv input, jv right) { static jv f_minus(jq_state *jq, jv input, jv a, jv b) { jv_free(input); if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) { - return jv_number(jv_number_value(a) - jv_number_value(b)); + jv r = jv_number(jv_number_value(a) - jv_number_value(b)); + jv_free(a); + jv_free(b); + return r; } else if (jv_get_kind(a) == JV_KIND_ARRAY && jv_get_kind(b) == JV_KIND_ARRAY) { jv out = jv_array(); jv_array_foreach(a, i, x) { @@ -302,7 +308,10 @@ static jv f_multiply(jq_state *jq, jv input, jv a, jv b) { jv_kind bk = jv_get_kind(b); jv_free(input); if (ak == JV_KIND_NUMBER && bk == JV_KIND_NUMBER) { - return jv_number(jv_number_value(a) * jv_number_value(b)); + jv r = jv_number(jv_number_value(a) * jv_number_value(b)); + jv_free(a); + jv_free(b); + return r; } else if ((ak == JV_KIND_STRING && bk == JV_KIND_NUMBER) || (ak == JV_KIND_NUMBER && bk == JV_KIND_STRING)) { jv str = a; @@ -336,7 +345,10 @@ static jv f_divide(jq_state *jq, jv input, jv a, jv b) { if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) { if (jv_number_value(b) == 0.0) return type_error2(a, b, "cannot be divided because the divisor is zero"); - return jv_number(jv_number_value(a) / jv_number_value(b)); + jv r = jv_number(jv_number_value(a) / jv_number_value(b)); + jv_free(a); + jv_free(b); + return r; } else if (jv_get_kind(a) == JV_KIND_STRING && jv_get_kind(b) == JV_KIND_STRING) { return jv_string_split(a, b); } else { @@ -349,7 +361,10 @@ static jv f_mod(jq_state *jq, jv input, jv a, jv b) { if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) { if ((intmax_t)jv_number_value(b) == 0) return type_error2(a, b, "cannot be divided (remainder) because the divisor is zero"); - return jv_number((intmax_t)jv_number_value(a) % (intmax_t)jv_number_value(b)); + jv r = jv_number((intmax_t)jv_number_value(a) % (intmax_t)jv_number_value(b)); + jv_free(a); + jv_free(b); + return r; } else { return type_error2(a, b, "cannot be divided (remainder)"); } @@ -440,7 +455,9 @@ static jv f_length(jq_state *jq, jv input) { } else if (jv_get_kind(input) == JV_KIND_STRING) { return jv_number(jv_string_length_codepoints(input)); } else if (jv_get_kind(input) == JV_KIND_NUMBER) { - return jv_number(fabs(jv_number_value(input))); + jv r = jv_number(fabs(jv_number_value(input))); + jv_free(input); + return r; } else if (jv_get_kind(input) == JV_KIND_NULL) { jv_free(input); return jv_number(0); diff --git a/src/execute.c b/src/execute.c index 65c6bc77..fd2ab2c7 100644 --- a/src/execute.c +++ b/src/execute.c @@ -509,21 +509,25 @@ jv jq_next(jq_state *jq) { uint16_t v = *pc++; jv* var = frame_local_var(jq, v, level); jv max = stack_pop(jq); - if (raising) goto do_backtrack; + if (raising) { + jv_free(max); + goto do_backtrack; + } if (jv_get_kind(*var) != JV_KIND_NUMBER || jv_get_kind(max) != JV_KIND_NUMBER) { set_error(jq, jv_invalid_with_msg(jv_string_fmt("Range bounds must be numeric"))); jv_free(max); goto do_backtrack; - } else if (jv_number_value(jv_copy(*var)) >= jv_number_value(jv_copy(max))) { + } else if (jv_number_value(*var) >= jv_number_value(max)) { /* finished iterating */ + jv_free(max); goto do_backtrack; } else { - jv curr = jv_copy(*var); + jv curr = *var; *var = jv_number(jv_number_value(*var) + 1); struct stack_pos spos = stack_get_pos(jq); - stack_push(jq, jv_copy(max)); + stack_push(jq, max); stack_save(jq, pc - 3, spos); stack_push(jq, curr); @@ -1010,6 +1014,9 @@ jq_state *jq_init(void) { jq->attrs = jv_object(); jq->path = jv_null(); jq->value_at_path = jv_null(); + + jq->nomem_handler = NULL; + jq->nomem_handler_data = NULL; return jq; } diff --git a/src/jq_test.c b/src/jq_test.c index 7a396b94..2b40d4d6 100644 --- a/src/jq_test.c +++ b/src/jq_test.c @@ -6,20 +6,32 @@ #include "jq.h" static void jv_test(); -static void run_jq_tests(jv, int, FILE *); +static void run_jq_tests(jv, int, FILE *, int, int); int jq_testsuite(jv libdirs, int verbose, int argc, char* argv[]) { FILE *testdata = stdin; + int skip = -1; + int take = -1; jv_test(); if (argc > 0) { - testdata = fopen(argv[0], "r"); - if (!testdata) { - perror("fopen"); - exit(1); + for(int i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--skip")) { + skip = atoi(argv[i+1]); + i++; + } else if (!strcmp(argv[i], "--take")) { + take = atoi(argv[i+1]); + i++; + } else { + testdata = fopen(argv[i], "r"); + if (!testdata) { + perror("fopen"); + exit(1); + } + } } } - run_jq_tests(libdirs, verbose, testdata); + run_jq_tests(libdirs, verbose, testdata, skip, take); return 0; } @@ -53,7 +65,7 @@ static void test_err_cb(void *data, jv e) { jv_free(e); } -static void run_jq_tests(jv lib_dirs, int verbose, FILE *testdata) { +static void run_jq_tests(jv lib_dirs, int verbose, FILE *testdata, int skip, int take) { char prog[4096]; char buf[4096]; struct err_data err_msg; @@ -63,6 +75,9 @@ static void run_jq_tests(jv lib_dirs, int verbose, FILE *testdata) { int check_msg = 0; jq_state *jq = NULL; + int tests_to_skip = skip; + int tests_to_take = take; + jq = jq_init(); assert(jq); if (jv_get_kind(lib_dirs) == JV_KIND_NULL) @@ -80,6 +95,34 @@ static void run_jq_tests(jv lib_dirs, int verbose, FILE *testdata) { continue; } if (prog[strlen(prog)-1] == '\n') prog[strlen(prog)-1] = 0; + + if (skip > 0) { + skip--; + + // skip past test data + while (fgets(buf, sizeof(buf), testdata)) { + lineno++; + if (buf[0] == '\n' || (buf[0] == '\r' && buf[1] == '\n')) + break; + } + + must_fail = 0; + check_msg = 0; + + continue; + } else if (skip == 0) { + printf("Skipped %d tests\n", tests_to_skip); + skip = -1; + } + + if (take > 0) { + take--; + } else if (take == 0) { + printf("Hit the number of tests limit (%d), breaking\n", tests_to_take); + take = -1; + break; + } + printf("Testing '%s' at line number %u\n", prog, lineno); int pass = 1; tests++; @@ -179,7 +222,21 @@ static void run_jq_tests(jv lib_dirs, int verbose, FILE *testdata) { passed+=pass; } jq_teardown(&jq); - printf("%d of %d tests passed (%d malformed)\n", passed,tests,invalid); + + int total_skipped = tests_to_skip > 0 ? tests_to_skip : 0; + + if (skip > 0) { + total_skipped = tests_to_skip - skip; + } + + printf("%d of %d tests passed (%d malformed, %d skipped)\n", + passed, tests, invalid, total_skipped); + + if (skip > 0) { + printf("WARN: skipped past the end of file, exiting with status 2\n"); + exit(2); + } + if (passed != tests) exit(1); } diff --git a/src/jv.c b/src/jv.c index 2f87bab5..d9799933 100644 --- a/src/jv.c +++ b/src/jv.c @@ -14,6 +14,15 @@ #include "jv_unicode.h" #include "util.h" +#include "jv_dtoa.h" +#include "jv_dtoa_tsd.h" + +// this means that we will manage the space for the struct +#define DECNUMDIGITS 1 +#include "decNumber/decNumber.h" + +#include "jv_type_private.h" + /* * Internal refcounting helpers */ @@ -38,14 +47,33 @@ static int jvp_refcnt_unshared(jv_refcnt* c) { return c->count == 1; } -/* - * Simple values (true, false, null) - */ +#define KIND_MASK 0xF +#define PFLAGS_MASK 0xF0 +#define PTYPE_MASK 0x70 + +typedef enum { + JVP_PAYLOAD_NONE = 0, + JVP_PAYLOAD_ALLOCATED = 0x80, +} payload_flags; + +#define JVP_MAKE_PFLAGS(ptype, allocated) ((((ptype) << 4) & PTYPE_MASK) | ((allocated) ? JVP_PAYLOAD_ALLOCATED : 0)) +#define JVP_MAKE_FLAGS(kind, pflags) ((kind & KIND_MASK) | (pflags & PFLAGS_MASK)) + +#define JVP_FLAGS(j) ((j).kind_flags) +#define JVP_KIND(j) (JVP_FLAGS(j) & KIND_MASK) -#define KIND_MASK 0xf +#define JVP_HAS_FLAGS(j, flags) (JVP_FLAGS(j) == flags) +#define JVP_HAS_KIND(j, kind) (JVP_KIND(j) == kind) + +#define JVP_IS_ALLOCATED(j) (j.kind_flags & JVP_PAYLOAD_ALLOCATED) + +#define JVP_FLAGS_NULL JVP_MAKE_FLAGS(JV_KIND_NULL, JVP_PAYLOAD_NONE) +#define JVP_FLAGS_INVALID JVP_MAKE_FLAGS(JV_KIND_INVALID, JVP_PAYLOAD_NONE) +#define JVP_FLAGS_FALSE JVP_MAKE_FLAGS(JV_KIND_FALSE, JVP_PAYLOAD_NONE) +#define JVP_FLAGS_TRUE JVP_MAKE_FLAGS(JV_KIND_TRUE, JVP_PAYLOAD_NONE) jv_kind jv_get_kind(jv x) { - return x.kind_flags & KIND_MASK; + return JVP_KIND(x); } const char* jv_kind_name(jv_kind k) { @@ -63,10 +91,10 @@ const char* jv_kind_name(jv_kind k) { return ""; } -static const jv JV_NULL = {JV_KIND_NULL, 0, 0, 0, {0}}; -static const jv JV_INVALID = {JV_KIND_INVALID, 0, 0, 0, {0}}; -static const jv JV_FALSE = {JV_KIND_FALSE, 0, 0, 0, {0}}; -static const jv JV_TRUE = {JV_KIND_TRUE, 0, 0, 0, {0}}; +const jv JV_NULL = {JVP_FLAGS_NULL, 0, 0, 0, {0}}; +const jv JV_INVALID = {JVP_FLAGS_INVALID, 0, 0, 0, {0}}; +const jv JV_FALSE = {JVP_FLAGS_FALSE, 0, 0, 0, {0}}; +const jv JV_TRUE = {JVP_FLAGS_TRUE, 0, 0, 0, {0}}; jv jv_true() { return JV_TRUE; @@ -88,19 +116,21 @@ jv jv_bool(int x) { * Invalid objects, with optional error messages */ +#define JVP_FLAGS_INVALID_MSG JVP_MAKE_FLAGS(JV_KIND_INVALID, JVP_PAYLOAD_ALLOCATED) + typedef struct { jv_refcnt refcnt; jv errmsg; } jvp_invalid; jv jv_invalid_with_msg(jv err) { - if (jv_get_kind(err) == JV_KIND_NULL) + if (JVP_HAS_KIND(err, JV_KIND_NULL)) return JV_INVALID; jvp_invalid* i = jv_mem_alloc(sizeof(jvp_invalid)); i->refcnt = JV_REFCNT_INIT; i->errmsg = err; - jv x = {JV_KIND_INVALID, 0, 0, 0, {&i->refcnt}}; + jv x = {JVP_FLAGS_INVALID_MSG, 0, 0, 0, {&i->refcnt}}; return x; } @@ -109,26 +139,30 @@ jv jv_invalid() { } jv jv_invalid_get_msg(jv inv) { - assert(jv_get_kind(inv) == JV_KIND_INVALID); + assert(JVP_HAS_KIND(inv, JV_KIND_INVALID)); + jv x; - if (inv.u.ptr == 0) - x = jv_null(); - else + if (JVP_HAS_FLAGS(inv, JVP_FLAGS_INVALID_MSG)) { x = jv_copy(((jvp_invalid*)inv.u.ptr)->errmsg); + } + else { + x = jv_null(); + } + jv_free(inv); return x; } int jv_invalid_has_msg(jv inv) { - jv msg = jv_invalid_get_msg(inv); - int r = jv_get_kind(msg) != JV_KIND_NULL; - jv_free(msg); + assert(JVP_HAS_KIND(inv, JV_KIND_INVALID)); + int r = JVP_HAS_FLAGS(inv, JVP_FLAGS_INVALID_MSG); + jv_free(inv); return r; } static void jvp_invalid_free(jv x) { - assert(jv_get_kind(x) == JV_KIND_INVALID); - if (x.u.ptr != 0 && jvp_refcnt_dec(x.u.ptr)) { + assert(JVP_HAS_KIND(x, JV_KIND_INVALID)); + if (JVP_HAS_FLAGS(x, JVP_FLAGS_INVALID_MSG) && jvp_refcnt_dec(x.u.ptr)) { jv_free(((jvp_invalid*)x.u.ptr)->errmsg); jv_mem_free(x.u.ptr); } @@ -138,20 +172,265 @@ static void jvp_invalid_free(jv x) { * Numbers */ +enum { + JVP_NUMBER_NATIVE = 0, + JVP_NUMBER_DECIMAL = 1 +}; + +#define JV_NUMBER_SIZE_INIT (0) +#define JV_NUMBER_SIZE_CONVERTED (1) + +#define JVP_FLAGS_NUMBER_NATIVE JVP_MAKE_FLAGS(JV_KIND_NUMBER, JVP_MAKE_PFLAGS(JVP_NUMBER_NATIVE, 0)) +#define JVP_FLAGS_NUMBER_NATIVE_STR JVP_MAKE_FLAGS(JV_KIND_NUMBER, JVP_MAKE_PFLAGS(JVP_NUMBER_NATIVE, 1)) +#define JVP_FLAGS_NUMBER_LITERAL JVP_MAKE_FLAGS(JV_KIND_NUMBER, JVP_MAKE_PFLAGS(JVP_NUMBER_DECIMAL, 1)) + +#define STR(x) #x +#define XSTR(x) STR(x) +#define DBL_MAX_STR XSTR(DBL_MAX) +#define DBL_MIN_STR "-" XSTR(DBL_MAX) + +// the decimal precision of binary double +#define BIN64_DEC_PRECISION (17) +#define DEC_NUMBER_STRING_GUARD (14) + +#include + +static pthread_key_t dec_ctx_key; +static pthread_key_t dec_ctx_dbl_key; +static pthread_once_t dec_ctx_once = PTHREAD_ONCE_INIT; + +#define DEC_CONTEXT() tsd_dec_ctx_get(&dec_ctx_key) +#define DEC_CONTEXT_TO_DOUBLE() tsd_dec_ctx_get(&dec_ctx_dbl_key) + +// atexit finalizer to clean up the tsd dec contexts if main() exits +// without having called pthread_exit() +static void tsd_dec_ctx_fini() { + jv_mem_free(pthread_getspecific(dec_ctx_key)); + jv_mem_free(pthread_getspecific(dec_ctx_dbl_key)); + pthread_setspecific(dec_ctx_key, NULL); + pthread_setspecific(dec_ctx_dbl_key, NULL); +} + +static void tsd_dec_ctx_init() { + if (pthread_key_create(&dec_ctx_key, jv_mem_free) != 0) { + fprintf(stderr, "error: cannot create thread specific key"); + abort(); + } + if (pthread_key_create(&dec_ctx_dbl_key, jv_mem_free) != 0) { + fprintf(stderr, "error: cannot create thread specific key"); + abort(); + } + atexit(tsd_dec_ctx_fini); +} + +static decContext* tsd_dec_ctx_get(pthread_key_t *key) { + pthread_once(&dec_ctx_once, tsd_dec_ctx_init); // cannot fail + decContext *ctx = (decContext*)pthread_getspecific(*key); + if (ctx) { + return ctx; + } + + decContext _ctx = { + 0, + DEC_MAX_EMAX, + DEC_MIN_EMAX, + DEC_ROUND_HALF_UP, + 0, /*no errors*/ + 0, /*status*/ + 0, /*no clamping*/ + }; + if (key == &dec_ctx_key) { + _ctx.digits = DEC_MAX_DIGITS; + } else if (key == &dec_ctx_dbl_key) { + _ctx.digits = BIN64_DEC_PRECISION; + } + + ctx = malloc(sizeof(decContext)); + if (ctx) { + *ctx = _ctx; + if (pthread_setspecific(*key, ctx) != 0) { + fprintf(stderr, "error: cannot store thread specific data"); + abort(); + } + } + return ctx; +} + +typedef struct { + jv_refcnt refcnt; + double num_double; + char * literal_data; + decNumber num_decimal; // must be the last field in the structure for memory management +} jvp_literal_number; + +typedef struct { + decNumber number; + decNumberUnit units[1]; +} decNumberSingle; + +typedef struct { + decNumber number; + decNumberUnit units[BIN64_DEC_PRECISION]; +} decNumberDoublePrecision; + + +static inline int jvp_number_is_literal(jv n) { + assert(JVP_HAS_KIND(n, JV_KIND_NUMBER)); + return JVP_HAS_FLAGS(n, JVP_FLAGS_NUMBER_LITERAL); +} + +static jvp_literal_number* jvp_literal_number_ptr(jv j) { + assert(JVP_HAS_FLAGS(j, JVP_FLAGS_NUMBER_LITERAL)); + return (jvp_literal_number*)j.u.ptr; +} + +static decNumber* jvp_dec_number_ptr(jv j) { + assert(JVP_HAS_FLAGS(j, JVP_FLAGS_NUMBER_LITERAL)); + return &(((jvp_literal_number*)j.u.ptr)->num_decimal); +} + +static jvp_literal_number* jvp_literal_number_alloc(unsigned literal_length) { + + /* The number of units needed is ceil(DECNUMDIGITS/DECDPUN) */ + int units = ((literal_length+DECDPUN-1)/DECDPUN); + + jvp_literal_number* n = jv_mem_alloc( + sizeof(jvp_literal_number) + + sizeof(decNumberUnit) * units + ); + + return n; +} + +static jv jvp_literal_number_new(const char * literal) { + + jvp_literal_number * n = jvp_literal_number_alloc(strlen(literal)); + + n->refcnt = JV_REFCNT_INIT; + n->literal_data = NULL; + decContext *ctx = DEC_CONTEXT(); + decNumberFromString(&n->num_decimal, literal, ctx); + n->num_double = NAN; + + if (ctx->status & DEC_Conversion_syntax) { + jv_mem_free(n); + return JV_INVALID; + } + + jv r = {JVP_FLAGS_NUMBER_LITERAL, 0, 0, JV_NUMBER_SIZE_INIT, {&n->refcnt}}; + return r; +} + +static double jvp_literal_number_to_double(jv j) { + assert(JVP_HAS_FLAGS(j, JVP_FLAGS_NUMBER_LITERAL)); + + decNumber *p_dec_number = jvp_dec_number_ptr(j); + decNumberDoublePrecision dec_double; + char literal[BIN64_DEC_PRECISION + DEC_NUMBER_STRING_GUARD + 1]; + + // reduce the number to the shortest possible form + // while also making sure than no more than BIN64_DEC_PRECISION + // digits are used (dec_context_to_double) + decNumberReduce(&dec_double.number, p_dec_number, DEC_CONTEXT_TO_DOUBLE()); + + decNumberToString(&dec_double.number, literal); + + char *end; + return jvp_strtod(tsd_dtoa_context_get(), literal, &end); +} + + +static int jvp_number_equal(jv a, jv b) { + return jvp_number_cmp(a, b) == 0; +} + +static const char* jvp_literal_number_literal(jv n) { + assert(JVP_HAS_FLAGS(n, JVP_FLAGS_NUMBER_LITERAL)); + decNumber *pdec = jvp_dec_number_ptr(n); + jvp_literal_number* plit = jvp_literal_number_ptr(n); + + if (decNumberIsNaN(pdec)) { + return "null"; + } + + if (decNumberIsInfinite(pdec)) { + // For backward compatibiltiy. + if (decNumberIsNegative(pdec)) { + return DBL_MIN_STR; + } else { + return DBL_MAX_STR; + } + } + + if (plit->literal_data == NULL) { + int len = jvp_dec_number_ptr(n)->digits + 14; + plit->literal_data = jv_mem_alloc(len); + + // Preserve the actual precision as we have parsed it + // don't do decNumberTrim(pdec); + + decNumberToString(pdec, plit->literal_data); + } + + return plit->literal_data; +} + +int jv_number_has_literal(jv n) { + assert(JVP_HAS_KIND(n, JV_KIND_NUMBER)); + return JVP_HAS_FLAGS(n, JVP_FLAGS_NUMBER_LITERAL); +} + +const char* jv_number_get_literal(jv n) { + assert(JVP_HAS_KIND(n, JV_KIND_NUMBER)); + + if (JVP_HAS_FLAGS(n, JVP_FLAGS_NUMBER_LITERAL)) { + return jvp_literal_number_literal(n); + } else { + return NULL; + } +} + +static void jvp_number_free(jv j) { + assert(JVP_HAS_KIND(j, JV_KIND_NUMBER)); + if (JVP_HAS_FLAGS(j, JVP_FLAGS_NUMBER_LITERAL) && jvp_refcnt_dec(j.u.ptr)) { + jvp_literal_number* n = jvp_literal_number_ptr(j); + if (n->literal_data) { + jv_mem_free(n->literal_data); + } + jv_mem_free(n); + } +} + +jv jv_number_with_literal(const char * literal) { + return jvp_literal_number_new(literal); +} + jv jv_number(double x) { - jv j = {JV_KIND_NUMBER, 0, 0, 0, {.number = x}}; + jv j = {JVP_FLAGS_NUMBER_NATIVE, 0, 0, 0, {.number = x}}; return j; } double jv_number_value(jv j) { - assert(jv_get_kind(j) == JV_KIND_NUMBER); - return j.u.number; + assert(JVP_HAS_KIND(j, JV_KIND_NUMBER)); + if (JVP_HAS_FLAGS(j, JVP_FLAGS_NUMBER_LITERAL)) { + jvp_literal_number* n = jvp_literal_number_ptr(j); + + if (j.size != JV_NUMBER_SIZE_CONVERTED) { + n->num_double = jvp_literal_number_to_double(j); + j.size = JV_NUMBER_SIZE_CONVERTED; + } + + return n->num_double; + } else { + return j.u.number; + } } int jv_is_integer(jv j){ - if(jv_get_kind(j) != JV_KIND_NUMBER){ + if(!JVP_HAS_KIND(j, JV_KIND_NUMBER)){ return 0; } + double x = jv_number_value(j); double ipart; @@ -160,11 +439,53 @@ int jv_is_integer(jv j){ return fabs(fpart) < DBL_EPSILON; } +int jvp_number_is_nan(jv n) { + assert(JVP_HAS_KIND(n, JV_KIND_NUMBER)); + + if (JVP_HAS_FLAGS(n, JVP_FLAGS_NUMBER_LITERAL)) { + decNumber *pdec = jvp_dec_number_ptr(n); + return decNumberIsNaN(pdec); + } else { + return n.u.number != n.u.number; + } +} + +int jvp_number_cmp(jv a, jv b) { + assert(JVP_HAS_KIND(a, JV_KIND_NUMBER)); + assert(JVP_HAS_KIND(b, JV_KIND_NUMBER)); + + if(JVP_HAS_FLAGS(a, JVP_FLAGS_NUMBER_LITERAL) && JVP_HAS_FLAGS(b, JVP_FLAGS_NUMBER_LITERAL)) { + decNumberSingle res; + decNumberCompare(&res.number, + jvp_dec_number_ptr(a), + jvp_dec_number_ptr(b), + DEC_CONTEXT() + ); + if (decNumberIsZero(&res.number)) { + return 0; + } else if (decNumberIsNegative(&res.number)) { + return -1; + } else { + return 1; + } + } else { + double da = jv_number_value(a), db = jv_number_value(b); + if (da < db) { + return -1; + } else if (da == db) { + return 0; + } else { + return 1; + } + } +} + /* * Arrays (internal helpers) */ #define ARRAY_SIZE_ROUND_UP(n) (((n)*3)/2) +#define JVP_FLAGS_ARRAY JVP_MAKE_FLAGS(JV_KIND_ARRAY, JVP_PAYLOAD_ALLOCATED) static int imax(int a, int b) { if (a>b) return a; @@ -179,7 +500,7 @@ typedef struct { } jvp_array; static jvp_array* jvp_array_ptr(jv a) { - assert(jv_get_kind(a) == JV_KIND_ARRAY); + assert(JVP_HAS_KIND(a, JV_KIND_ARRAY)); return (jvp_array*)a.u.ptr; } @@ -192,12 +513,12 @@ static jvp_array* jvp_array_alloc(unsigned size) { } static jv jvp_array_new(unsigned size) { - jv r = {JV_KIND_ARRAY, 0, 0, 0, {&jvp_array_alloc(size)->refcnt}}; + jv r = {JVP_FLAGS_ARRAY, 0, 0, 0, {&jvp_array_alloc(size)->refcnt}}; return r; } static void jvp_array_free(jv a) { - assert(jv_get_kind(a) == JV_KIND_ARRAY); + assert(JVP_HAS_KIND(a, JV_KIND_ARRAY)); if (jvp_refcnt_dec(a.u.ptr)) { jvp_array* array = jvp_array_ptr(a); for (int i=0; ilength; i++) { @@ -208,17 +529,17 @@ static void jvp_array_free(jv a) { } static int jvp_array_length(jv a) { - assert(jv_get_kind(a) == JV_KIND_ARRAY); + assert(JVP_HAS_KIND(a, JV_KIND_ARRAY)); return a.size; } static int jvp_array_offset(jv a) { - assert(jv_get_kind(a) == JV_KIND_ARRAY); + assert(JVP_HAS_KIND(a, JV_KIND_ARRAY)); return a.offset; } static jv* jvp_array_read(jv a, int i) { - assert(jv_get_kind(a) == JV_KIND_ARRAY); + assert(JVP_HAS_KIND(a, JV_KIND_ARRAY)); if (i >= 0 && i < jvp_array_length(a)) { jvp_array* array = jvp_array_ptr(a); assert(i + jvp_array_offset(a) < array->length); @@ -255,7 +576,7 @@ static jv* jvp_array_write(jv* a, int i) { } new_array->length = new_length; jvp_array_free(*a); - jv new_jv = {JV_KIND_ARRAY, 0, 0, new_length, {&new_array->refcnt}}; + jv new_jv = {JVP_FLAGS_ARRAY, 0, 0, new_length, {&new_array->refcnt}}; *a = new_jv; return &new_array->elements[i]; } @@ -286,8 +607,33 @@ static void jvp_clamp_slice_params(int len, int *pstart, int *pend) if (*pend < *pstart) *pend = *pstart; } + +static int jvp_array_contains(jv a, jv b) { + int r = 1; + jv_array_foreach(b, bi, belem) { + int ri = 0; + jv_array_foreach(a, ai, aelem) { + if (jv_contains(aelem, jv_copy(belem))) { + ri = 1; + break; + } + } + jv_free(belem); + if (!ri) { + r = 0; + break; + } + } + return r; +} + + +/* + * Public + */ + static jv jvp_array_slice(jv a, int start, int end) { - assert(jv_get_kind(a) == JV_KIND_ARRAY); + assert(JVP_HAS_KIND(a, JV_KIND_ARRAY)); int len = jvp_array_length(a); jvp_clamp_slice_params(len, &start, &end); assert(0 <= start && start <= end && end <= len); @@ -324,14 +670,14 @@ jv jv_array() { } int jv_array_length(jv j) { - assert(jv_get_kind(j) == JV_KIND_ARRAY); + assert(JVP_HAS_KIND(j, JV_KIND_ARRAY)); int len = jvp_array_length(j); jv_free(j); return len; } jv jv_array_get(jv j, int idx) { - assert(jv_get_kind(j) == JV_KIND_ARRAY); + assert(JVP_HAS_KIND(j, JV_KIND_ARRAY)); jv* slot = jvp_array_read(j, idx); jv val; if (slot) { @@ -344,7 +690,7 @@ jv jv_array_get(jv j, int idx) { } jv jv_array_set(jv j, int idx, jv val) { - assert(jv_get_kind(j) == JV_KIND_ARRAY); + assert(JVP_HAS_KIND(j, JV_KIND_ARRAY)); if (idx < 0) idx = jvp_array_length(j) + idx; @@ -366,8 +712,8 @@ jv jv_array_append(jv j, jv val) { } jv jv_array_concat(jv a, jv b) { - assert(jv_get_kind(a) == JV_KIND_ARRAY); - assert(jv_get_kind(b) == JV_KIND_ARRAY); + assert(JVP_HAS_KIND(a, JV_KIND_ARRAY)); + assert(JVP_HAS_KIND(b, JV_KIND_ARRAY)); // FIXME: could be faster jv_array_foreach(b, i, elem) { @@ -378,44 +724,22 @@ jv jv_array_concat(jv a, jv b) { } jv jv_array_slice(jv a, int start, int end) { - assert(jv_get_kind(a) == JV_KIND_ARRAY); + assert(JVP_HAS_KIND(a, JV_KIND_ARRAY)); // copy/free of a coalesced return jvp_array_slice(a, start, end); } -int jv_array_contains(jv a, jv b) { - int r = 1; - jv_array_foreach(b, bi, belem) { - int ri = 0; - jv_array_foreach(a, ai, aelem) { - if (jv_contains(aelem, jv_copy(belem))) { - ri = 1; - break; - } - } - jv_free(belem); - if (!ri) { - r = 0; - break; - } - } - jv_free(a); - jv_free(b); - return r; -} - jv jv_array_indexes(jv a, jv b) { jv res = jv_array(); int idx = -1; jv_array_foreach(a, ai, aelem) { + jv_free(aelem); jv_array_foreach(b, bi, belem) { - // quieten compiler warnings about aelem not being used... by - // using it - if ((bi == 0 && !jv_equal(jv_copy(aelem), jv_copy(belem))) || - (bi > 0 && !jv_equal(jv_array_get(jv_copy(a), ai + bi), jv_copy(belem)))) + if (!jv_equal(jv_array_get(jv_copy(a), ai + bi), jv_copy(belem))) idx = -1; else if (bi == 0 && idx == -1) idx = ai; + jv_free(belem); } if (idx > -1) res = jv_array_append(res, jv_number(idx)); @@ -426,11 +750,12 @@ jv jv_array_indexes(jv a, jv b) { return res; } - /* * Strings (internal helpers) */ +#define JVP_FLAGS_STRING JVP_MAKE_FLAGS(JV_KIND_STRING, JVP_PAYLOAD_ALLOCATED) + typedef struct { jv_refcnt refcnt; uint32_t hash; @@ -442,7 +767,7 @@ typedef struct { } jvp_string; static jvp_string* jvp_string_ptr(jv a) { - assert(jv_get_kind(a) == JV_KIND_STRING); + assert(JVP_HAS_KIND(a, JV_KIND_STRING)); return (jvp_string*)a.u.ptr; } @@ -474,7 +799,7 @@ static jv jvp_string_copy_replace_bad(const char* data, uint32_t length) { length = out - s->data; s->data[length] = 0; s->length_hashed = length << 1; - jv r = {JV_KIND_STRING, 0, 0, 0, {&s->refcnt}}; + jv r = {JVP_FLAGS_STRING, 0, 0, 0, {&s->refcnt}}; return r; } @@ -485,7 +810,7 @@ static jv jvp_string_new(const char* data, uint32_t length) { if (data != NULL) memcpy(s->data, data, length); s->data[length] = 0; - jv r = {JV_KIND_STRING, 0, 0, 0, {&s->refcnt}}; + jv r = {JVP_FLAGS_STRING, 0, 0, 0, {&s->refcnt}}; return r; } @@ -493,7 +818,7 @@ static jv jvp_string_empty_new(uint32_t length) { jvp_string* s = jvp_string_alloc(length); s->length_hashed = 0; memset(s->data, 0, length); - jv r = {JV_KIND_STRING, 0, 0, 0, {&s->refcnt}}; + jv r = {JVP_FLAGS_STRING, 0, 0, 0, {&s->refcnt}}; return r; } @@ -536,7 +861,7 @@ static jv jvp_string_append(jv string, const char* data, uint32_t len) { memcpy(news->data + currlen, data, len); news->data[currlen + len] = 0; jvp_string_free(string); - jv r = {JV_KIND_STRING, 0, 0, 0, {&news->refcnt}}; + jv r = {JVP_FLAGS_STRING, 0, 0, 0, {&news->refcnt}}; return r; } } @@ -603,9 +928,10 @@ static uint32_t jvp_string_hash(jv jstr) { return h1; } + static int jvp_string_equal(jv a, jv b) { - assert(jv_get_kind(a) == JV_KIND_STRING); - assert(jv_get_kind(b) == JV_KIND_STRING); + assert(JVP_HAS_KIND(a, JV_KIND_STRING)); + assert(JVP_HAS_KIND(b, JV_KIND_STRING)); jvp_string* stra = jvp_string_ptr(a); jvp_string* strb = jvp_string_ptr(b); if (jvp_string_length(stra) != jvp_string_length(strb)) return 0; @@ -632,14 +958,14 @@ jv jv_string(const char* str) { } int jv_string_length_bytes(jv j) { - assert(jv_get_kind(j) == JV_KIND_STRING); + assert(JVP_HAS_KIND(j, JV_KIND_STRING)); int r = jvp_string_length(jvp_string_ptr(j)); jv_free(j); return r; } int jv_string_length_codepoints(jv j) { - assert(jv_get_kind(j) == JV_KIND_STRING); + assert(JVP_HAS_KIND(j, JV_KIND_STRING)); const char* i = jv_string_value(j); const char* end = i + jv_string_length_bytes(jv_copy(j)); int c = 0, len = 0; @@ -650,8 +976,8 @@ int jv_string_length_codepoints(jv j) { jv jv_string_indexes(jv j, jv k) { - assert(jv_get_kind(j) == JV_KIND_STRING); - assert(jv_get_kind(k) == JV_KIND_STRING); + assert(JVP_HAS_KIND(j, JV_KIND_STRING)); + assert(JVP_HAS_KIND(k, JV_KIND_STRING)); const char *jstr = jv_string_value(j); const char *idxstr = jv_string_value(k); const char *p; @@ -672,8 +998,8 @@ jv jv_string_indexes(jv j, jv k) { } jv jv_string_split(jv j, jv sep) { - assert(jv_get_kind(j) == JV_KIND_STRING); - assert(jv_get_kind(sep) == JV_KIND_STRING); + assert(JVP_HAS_KIND(j, JV_KIND_STRING)); + assert(JVP_HAS_KIND(sep, JV_KIND_STRING)); const char *jstr = jv_string_value(j); const char *jend = jstr + jv_string_length_bytes(jv_copy(j)); const char *sepstr = jv_string_value(sep); @@ -704,7 +1030,7 @@ jv jv_string_split(jv j, jv sep) { } jv jv_string_explode(jv j) { - assert(jv_get_kind(j) == JV_KIND_STRING); + assert(JVP_HAS_KIND(j, JV_KIND_STRING)); const char* i = jv_string_value(j); int len = jv_string_length_bytes(jv_copy(j)); const char* end = i + len; @@ -717,7 +1043,7 @@ jv jv_string_explode(jv j) { } jv jv_string_implode(jv j) { - assert(jv_get_kind(j) == JV_KIND_ARRAY); + assert(JVP_HAS_KIND(j, JV_KIND_ARRAY)); int len = jv_array_length(jv_copy(j)); jv s = jv_string_empty(len); int i; @@ -726,8 +1052,9 @@ jv jv_string_implode(jv j) { for (i = 0; i < len; i++) { jv n = jv_array_get(jv_copy(j), i); - assert(jv_get_kind(n) == JV_KIND_NUMBER); + assert(JVP_HAS_KIND(n, JV_KIND_NUMBER)); int nv = jv_number_value(n); + jv_free(n); if (nv > 0x10FFFF) nv = 0xFFFD; // U+FFFD REPLACEMENT CHARACTER s = jv_string_append_codepoint(s, nv); @@ -738,19 +1065,19 @@ jv jv_string_implode(jv j) { } unsigned long jv_string_hash(jv j) { - assert(jv_get_kind(j) == JV_KIND_STRING); + assert(JVP_HAS_KIND(j, JV_KIND_STRING)); uint32_t hash = jvp_string_hash(j); jv_free(j); return hash; } const char* jv_string_value(jv j) { - assert(jv_get_kind(j) == JV_KIND_STRING); + assert(JVP_HAS_KIND(j, JV_KIND_STRING)); return jvp_string_ptr(j)->data; } jv jv_string_slice(jv j, int start, int end) { - assert(jv_get_kind(j) == JV_KIND_STRING); + assert(JVP_HAS_KIND(j, JV_KIND_STRING)); const char *s = jv_string_value(j); int len = jv_string_length_bytes(jv_copy(j)); int i; @@ -861,6 +1188,8 @@ jv jv_string_fmt(const char* fmt, ...) { * Objects (internal helpers) */ +#define JVP_FLAGS_OBJECT JVP_MAKE_FLAGS(JV_KIND_OBJECT, JVP_PAYLOAD_ALLOCATED) + struct object_slot { int next; /* next slot with same hash, for collisions */ uint32_t hash; @@ -897,22 +1226,22 @@ static jv jvp_object_new(int size) { for (int i=0; irefcnt}}; + jv r = {JVP_FLAGS_OBJECT, 0, 0, size, {&obj->refcnt}}; return r; } static jvp_object* jvp_object_ptr(jv o) { - assert(jv_get_kind(o) == JV_KIND_OBJECT); + assert(JVP_HAS_KIND(o, JV_KIND_OBJECT)); return (jvp_object*)o.u.ptr; } static uint32_t jvp_object_mask(jv o) { - assert(jv_get_kind(o) == JV_KIND_OBJECT); + assert(JVP_HAS_KIND(o, JV_KIND_OBJECT)); return (o.size * 2) - 1; } static int jvp_object_size(jv o) { - assert(jv_get_kind(o) == JV_KIND_OBJECT); + assert(JVP_HAS_KIND(o, JV_KIND_OBJECT)); return o.size; } @@ -960,7 +1289,7 @@ static struct object_slot* jvp_object_add_slot(jv object, jv key, int* bucket) { } static jv* jvp_object_read(jv object, jv key) { - assert(jv_get_kind(key) == JV_KIND_STRING); + assert(JVP_HAS_KIND(key, JV_KIND_STRING)); int* bucket = jvp_object_find_bucket(object, key); struct object_slot* slot = jvp_object_find_slot(object, key, bucket); if (slot == 0) return 0; @@ -968,7 +1297,7 @@ static jv* jvp_object_read(jv object, jv key) { } static void jvp_object_free(jv o) { - assert(jv_get_kind(o) == JV_KIND_OBJECT); + assert(JVP_HAS_KIND(o, JV_KIND_OBJECT)); if (jvp_refcnt_dec(o.u.ptr)) { for (int i=0; istring; - assert(jv_get_kind(s) == JV_KIND_STRING); + assert(JVP_HAS_KIND(s, JV_KIND_STRING)); return jv_copy(s); } @@ -1243,34 +1569,36 @@ jv jv_object_iter_value(jv object, int iter) { * Memory management */ jv jv_copy(jv j) { - if (jv_get_kind(j) == JV_KIND_ARRAY || - jv_get_kind(j) == JV_KIND_STRING || - jv_get_kind(j) == JV_KIND_OBJECT || - (jv_get_kind(j) == JV_KIND_INVALID && j.u.ptr != 0)) { + if (JVP_IS_ALLOCATED(j)) { jvp_refcnt_inc(j.u.ptr); } return j; } void jv_free(jv j) { - if (jv_get_kind(j) == JV_KIND_ARRAY) { - jvp_array_free(j); - } else if (jv_get_kind(j) == JV_KIND_STRING) { - jvp_string_free(j); - } else if (jv_get_kind(j) == JV_KIND_OBJECT) { - jvp_object_free(j); - } else if (jv_get_kind(j) == JV_KIND_INVALID) { - jvp_invalid_free(j); + switch(JVP_KIND(j)) { + case JV_KIND_ARRAY: + jvp_array_free(j); + break; + case JV_KIND_STRING: + jvp_string_free(j); + break; + case JV_KIND_OBJECT: + jvp_object_free(j); + break; + case JV_KIND_INVALID: + jvp_invalid_free(j); + break; + case JV_KIND_NUMBER: + jvp_number_free(j); + break; } } int jv_get_refcnt(jv j) { - switch (jv_get_kind(j)) { - case JV_KIND_ARRAY: - case JV_KIND_STRING: - case JV_KIND_OBJECT: + if (JVP_IS_ALLOCATED(j)) { return j.u.ptr->count; - default: + } else { return 1; } } @@ -1283,14 +1611,17 @@ int jv_equal(jv a, jv b) { int r; if (jv_get_kind(a) != jv_get_kind(b)) { r = 0; - } else if (jv_get_kind(a) == JV_KIND_NUMBER) { - r = jv_number_value(a) == jv_number_value(b); - } else if (a.kind_flags == b.kind_flags && + } else if (JVP_IS_ALLOCATED(a) && + JVP_IS_ALLOCATED(b) && + a.kind_flags == b.kind_flags && a.size == b.size && a.u.ptr == b.u.ptr) { r = 1; } else { switch (jv_get_kind(a)) { + case JV_KIND_NUMBER: + r = jvp_number_equal(a, b); + break; case JV_KIND_ARRAY: r = jvp_array_equal(a, b); break; @@ -1317,18 +1648,10 @@ int jv_identical(jv a, jv b) { || a.size != b.size) { r = 0; } else { - switch (jv_get_kind(a)) { - case JV_KIND_ARRAY: - case JV_KIND_STRING: - case JV_KIND_OBJECT: + if (JVP_IS_ALLOCATED(a) /* b has the same flags */) { r = a.u.ptr == b.u.ptr; - break; - case JV_KIND_NUMBER: - r = memcmp(&a.u.number, &b.u.number, sizeof(a.u.number)) == 0; - break; - default: - r = 1; - break; + } else { + r = memcmp(&a.u.ptr, &b.u.ptr, sizeof(a.u)) == 0; } } jv_free(a); @@ -1340,11 +1663,11 @@ int jv_contains(jv a, jv b) { int r = 1; if (jv_get_kind(a) != jv_get_kind(b)) { r = 0; - } else if (jv_get_kind(a) == JV_KIND_OBJECT) { - r = jv_object_contains(jv_copy(a), jv_copy(b)); - } else if (jv_get_kind(a) == JV_KIND_ARRAY) { - r = jv_array_contains(jv_copy(a), jv_copy(b)); - } else if (jv_get_kind(a) == JV_KIND_STRING) { + } else if (JVP_HAS_KIND(a, JV_KIND_OBJECT)) { + r = jvp_object_contains(a, b); + } else if (JVP_HAS_KIND(a, JV_KIND_ARRAY)) { + r = jvp_array_contains(a, b); + } else if (JVP_HAS_KIND(a, JV_KIND_STRING)) { int b_len = jv_string_length_bytes(jv_copy(b)); if (b_len != 0) { r = _jq_memmem(jv_string_value(a), jv_string_length_bytes(jv_copy(a)), diff --git a/src/jv.h b/src/jv.h index d111c80b..8c96f822 100644 --- a/src/jv.h +++ b/src/jv.h @@ -54,16 +54,19 @@ jv jv_invalid_with_msg(jv); jv jv_invalid_get_msg(jv); int jv_invalid_has_msg(jv); - jv jv_null(void); jv jv_true(void); jv jv_false(void); jv jv_bool(int); jv jv_number(double); +jv jv_number_with_literal(const char*); double jv_number_value(jv); int jv_is_integer(jv); +int jv_number_has_literal(jv n); +const char* jv_number_get_literal(jv); + jv jv_array(void); jv jv_array_sized(int); int jv_array_length(jv); diff --git a/src/jv_aux.c b/src/jv_aux.c index 129cd043..eca2345f 100644 --- a/src/jv_aux.c +++ b/src/jv_aux.c @@ -2,6 +2,16 @@ #include #include #include "jv_alloc.h" +#include "jv_type_private.h" + +// making this static verbose function here +// until we introduce a less confusing naming scheme +// of jv_* API with regards to the memory management +static double jv_number_get_value_and_consume(jv number) { + double value = jv_number_value(number); + jv_free(number); + return value; +} static int parse_slice(jv j, jv slice, int* pstart, int* pend) { // Array slices @@ -32,6 +42,8 @@ static int parse_slice(jv j, jv slice, int* pstart, int* pend) { } else { double dstart = jv_number_value(start_jv); double dend = jv_number_value(end_jv); + jv_free(start_jv); + jv_free(end_jv); if (dstart < 0) dstart += len; if (dend < 0) dend += len; if (dstart < 0) dstart = 0; @@ -69,6 +81,7 @@ jv jv_get(jv t, jv k) { jv_free(v); v = jv_null(); } + jv_free(k); } else { jv_free(t); jv_free(k); @@ -135,6 +148,7 @@ jv jv_set(jv t, jv k, jv v) { (jv_get_kind(t) == JV_KIND_ARRAY || isnull)) { if (isnull) t = jv_array(); t = jv_array_set(t, (int)jv_number_value(k), v); + jv_free(k); } else if (jv_get_kind(k) == JV_KIND_OBJECT && (jv_get_kind(t) == JV_KIND_ARRAY || isnull)) { if (isnull) t = jv_array(); @@ -202,6 +216,7 @@ jv jv_has(jv t, jv k) { jv_get_kind(k) == JV_KIND_NUMBER) { jv elem = jv_array_get(t, (int)jv_number_value(k)); ret = jv_bool(jv_is_valid(elem)); + jv_free(k); jv_free(elem); } else { ret = jv_invalid_with_msg(jv_string_fmt("Cannot check whether %s has a %s key", @@ -240,6 +255,7 @@ static jv jv_dels(jv t, jv keys) { ends = jv_array_append(ends, jv_number(end)); } else { jv_free(new_array); + jv_free(key); new_array = jv_invalid_with_msg(jv_string_fmt("Start and end indices of an array slice must be numbers")); goto arr_out; } @@ -258,7 +274,7 @@ static jv jv_dels(jv t, jv keys) { jv_array_foreach(t, i, elem) { int del = 0; while (neg_idx < jv_array_length(jv_copy(neg_keys))) { - int delidx = len + (int)jv_number_value(jv_array_get(jv_copy(neg_keys), neg_idx)); + int delidx = len + (int)jv_number_get_value_and_consume(jv_array_get(jv_copy(neg_keys), neg_idx)); if (i == delidx) { del = 1; } @@ -268,7 +284,7 @@ static jv jv_dels(jv t, jv keys) { neg_idx++; } while (nonneg_idx < jv_array_length(jv_copy(nonneg_keys))) { - int delidx = (int)jv_number_value(jv_array_get(jv_copy(nonneg_keys), nonneg_idx)); + int delidx = (int)jv_number_get_value_and_consume(jv_array_get(jv_copy(nonneg_keys), nonneg_idx)); if (i == delidx) { del = 1; } @@ -278,8 +294,8 @@ static jv jv_dels(jv t, jv keys) { nonneg_idx++; } for (int sidx=0; !del && sidx +#include +#include + +#include "jv_dtoa_tsd.h" +#include "jv_dtoa.h" +#include "jv_alloc.h" + + +static pthread_key_t dtoa_ctx_key; +static pthread_once_t dtoa_ctx_once = PTHREAD_ONCE_INIT; + +static void tsd_dtoa_ctx_dtor(struct dtoa_context *ctx) { + if (ctx) { + jvp_dtoa_context_free(ctx); + jv_mem_free(ctx); + } +} + +static void tsd_dtoa_ctx_fini() { + struct dtoa_context *ctx = pthread_getspecific(dtoa_ctx_key); + tsd_dtoa_ctx_dtor(ctx); + pthread_setspecific(dtoa_ctx_key, NULL); +} + +static void tsd_dtoa_ctx_init() { + if (pthread_key_create(&dtoa_ctx_key, tsd_dtoa_ctx_dtor) != 0) { + fprintf(stderr, "error: cannot create thread specific key"); + abort(); + } + atexit(tsd_dtoa_ctx_fini); +} + +inline struct dtoa_context *tsd_dtoa_context_get() { + pthread_once(&dtoa_ctx_once, tsd_dtoa_ctx_init); // cannot fail + struct dtoa_context *ctx = (struct dtoa_context*)pthread_getspecific(dtoa_ctx_key); + if (!ctx) { + ctx = malloc(sizeof(struct dtoa_context)); + jvp_dtoa_context_init(ctx); + if (pthread_setspecific(dtoa_ctx_key, ctx) != 0) { + fprintf(stderr, "error: cannot set thread specific data"); + abort(); + } + } + return ctx; +} \ No newline at end of file diff --git a/src/jv_dtoa_tsd.h b/src/jv_dtoa_tsd.h new file mode 100644 index 00000000..2431d08e --- /dev/null +++ b/src/jv_dtoa_tsd.h @@ -0,0 +1,4 @@ +#ifndef JV_DTOA_TSD_H +#define JV_DTOA_TSD_H +struct dtoa_context *tsd_dtoa_context_get(); +#endif diff --git a/src/jv_parse.c b/src/jv_parse.c index 51ad9f09..d709e416 100644 --- a/src/jv_parse.c +++ b/src/jv_parse.c @@ -124,14 +124,19 @@ static void parser_free(struct jv_parser* p) { static pfunc value(struct jv_parser* p, jv val) { if ((p->flags & JV_PARSE_STREAMING)) { - if (jv_is_valid(p->next) || p->last_seen == JV_LAST_VALUE) + if (jv_is_valid(p->next) || p->last_seen == JV_LAST_VALUE) { + jv_free(val); return "Expected separator between values"; + } if (p->stacklen > 0) p->last_seen = JV_LAST_VALUE; else p->last_seen = JV_LAST_NONE; } else { - if (jv_is_valid(p->next)) return "Expected separator between values"; + if (jv_is_valid(p->next)) { + jv_free(val); + return "Expected separator between values"; + } } jv_free(p->next); p->next = val; @@ -256,8 +261,12 @@ static pfunc stream_token(struct jv_parser* p, char ch) { break; case ':': - if (p->stacklen == 0 || jv_get_kind(jv_array_get(jv_copy(p->path), p->stacklen - 1)) == JV_KIND_NUMBER) + last = jv_invalid(); + if (p->stacklen == 0 || jv_get_kind(last = jv_array_get(jv_copy(p->path), p->stacklen - 1)) == JV_KIND_NUMBER) { + jv_free(last); return "':' not as part of an object"; + } + jv_free(last); if (!jv_is_valid(p->next) || p->last_seen == JV_LAST_NONE) return "Expected string key before ':'"; if (jv_get_kind(p->next) != JV_KIND_STRING) @@ -492,11 +501,11 @@ static pfunc check_literal(struct jv_parser* p) { } else { // FIXME: better parser p->tokenbuf[p->tokenpos] = 0; - char* end = 0; - double d = jvp_strtod(&p->dtoa, p->tokenbuf, &end); - if (end == 0 || *end != 0) + jv number = jv_number_with_literal(p->tokenbuf); + if (jv_get_kind(number) == JV_KIND_INVALID) { return "Invalid numeric literal"; - TRY(value(p, jv_number(d))); + } + TRY(value(p, number)); } p->tokenpos = 0; return 0; diff --git a/src/jv_print.c b/src/jv_print.c index 5ebc01e6..6c848035 100644 --- a/src/jv_print.c +++ b/src/jv_print.c @@ -11,8 +11,10 @@ #include "jv.h" #include "jv_dtoa.h" +#include "jv_dtoa_tsd.h" #include "jv_unicode.h" #include "jv_alloc.h" +#include "jv_type_private.h" #ifndef MAX_PRINT_DEPTH #define MAX_PRINT_DEPTH (256) @@ -229,15 +231,24 @@ static void jv_dump_term(struct dtoa_context* C, jv x, int flags, int indent, FI put_str("true", F, S, flags & JV_PRINT_ISATTY); break; case JV_KIND_NUMBER: { - double d = jv_number_value(x); - if (d != d) { - // JSON doesn't have NaN, so we'll render it as "null" - put_str("null", F, S, flags & JV_PRINT_ISATTY); + if (jvp_number_is_nan(x)) { + jv_dump_term(C, jv_null(), flags, indent, F, S); } else { - // Normalise infinities to something we can print in valid JSON - if (d > DBL_MAX) d = DBL_MAX; - if (d < -DBL_MAX) d = -DBL_MAX; - put_str(jvp_dtoa_fmt(C, buf, d), F, S, flags & JV_PRINT_ISATTY); + const char * literal_data = jv_number_get_literal(x); + if (literal_data) { + put_str(literal_data, F, S, flags & JV_PRINT_ISATTY); + } else { + double d = jv_number_value(x); + if (d != d) { + // JSON doesn't have NaN, so we'll render it as "null" + put_str("null", F, S, flags & JV_PRINT_ISATTY); + } else { + // Normalise infinities to something we can print in valid JSON + if (d > DBL_MAX) d = DBL_MAX; + if (d < -DBL_MAX) d = -DBL_MAX; + put_str(jvp_dtoa_fmt(C, buf, d), F, S, flags & JV_PRINT_ISATTY); + } + } } break; } @@ -357,10 +368,7 @@ static void jv_dump_term(struct dtoa_context* C, jv x, int flags, int indent, FI } void jv_dumpf(jv x, FILE *f, int flags) { - struct dtoa_context C; - jvp_dtoa_context_init(&C); - jv_dump_term(&C, x, flags, 0, f, 0); - jvp_dtoa_context_free(&C); + jv_dump_term(tsd_dtoa_context_get(), x, flags, 0, f, 0); } void jv_dump(jv x, int flags) { @@ -376,11 +384,8 @@ void jv_show(jv x, int flags) { } jv jv_dump_string(jv x, int flags) { - struct dtoa_context C; - jvp_dtoa_context_init(&C); jv s = jv_string(""); - jv_dump_term(&C, x, flags, 0, 0, &s); - jvp_dtoa_context_free(&C); + jv_dump_term(tsd_dtoa_context_get(), x, flags, 0, 0, &s); return s; } diff --git a/src/jv_type_private.h b/src/jv_type_private.h new file mode 100644 index 00000000..5996282b --- /dev/null +++ b/src/jv_type_private.h @@ -0,0 +1,7 @@ +#ifndef JV_TYPE_PRIVATE +#define JV_TYPE_PRIVATE + +int jvp_number_cmp(jv, jv); +int jvp_number_is_nan(jv); + +#endif //JV_TYPE_PRIVATE diff --git a/src/parser.c b/src/parser.c index d9210a98..b6574e52 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1,8 +1,9 @@ -/* A Bison parser, made by GNU Bison 3.0.4. */ +/* A Bison parser, made by GNU Bison 3.3.2. */ /* Bison implementation for Yacc-like parsers in C - Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc. + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2019 Free Software Foundation, + Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -40,11 +41,14 @@ define necessary library symbols; they are noted "INFRINGES ON USER NAME SPACE" below. */ +/* Undocumented macros, especially those whose name start with YY_, + are private implementation details. Do not rely on them. */ + /* Identify Bison output. */ #define YYBISON 1 /* Bison version. */ -#define YYBISON_VERSION "3.0.4" +#define YYBISON_VERSION "3.3.2" /* Skeleton name. */ #define YYSKELETON_NAME "yacc.c" @@ -61,8 +65,8 @@ -/* Copy the first part of user declarations. */ -#line 1 "src/parser.y" /* yacc.c:339 */ +/* First part of user prologue. */ +#line 1 "src/parser.y" /* yacc.c:337 */ #include #include @@ -73,13 +77,16 @@ #define YYMALLOC jv_mem_alloc #define YYFREE jv_mem_free -#line 77 "src/parser.c" /* yacc.c:339 */ - +#line 81 "src/parser.c" /* yacc.c:337 */ # ifndef YY_NULLPTR -# if defined __cplusplus && 201103L <= __cplusplus -# define YY_NULLPTR nullptr +# if defined __cplusplus +# if 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif # else -# define YY_NULLPTR 0 +# define YY_NULLPTR ((void*)0) # endif # endif @@ -103,7 +110,7 @@ extern int yydebug; #endif /* "%code requires" blocks. */ -#line 11 "src/parser.y" /* yacc.c:355 */ +#line 11 "src/parser.y" /* yacc.c:352 */ #include "locfile.h" struct lexer_param; @@ -120,7 +127,7 @@ struct lexer_param; } \ } while (0) -#line 124 "src/parser.c" /* yacc.c:355 */ +#line 131 "src/parser.c" /* yacc.c:352 */ /* Token type. */ #ifndef YYTOKENTYPE @@ -226,12 +233,12 @@ struct lexer_param; union YYSTYPE { -#line 31 "src/parser.y" /* yacc.c:355 */ +#line 31 "src/parser.y" /* yacc.c:352 */ jv literal; block blk; -#line 235 "src/parser.c" /* yacc.c:355 */ +#line 242 "src/parser.c" /* yacc.c:352 */ }; typedef union YYSTYPE YYSTYPE; @@ -259,8 +266,8 @@ int yyparse (block* answer, int* errors, struct locfile* locations, struct lexer #endif /* !YY_YY_SRC_PARSER_H_INCLUDED */ -/* Copy the second part of user declarations. */ -#line 124 "src/parser.y" /* yacc.c:358 */ +/* Second part of user prologue. */ +#line 124 "src/parser.y" /* yacc.c:354 */ #include "lexer.h" struct lexer_param { @@ -312,7 +319,7 @@ static jv check_object_key(block k) { char errbuf[15]; return jv_string_fmt("Cannot use %s (%s) as object key", jv_kind_name(block_const_kind(k)), - jv_dump_string_trunc(jv_copy(block_const(k)), errbuf, sizeof(errbuf))); + jv_dump_string_trunc(block_const(k), errbuf, sizeof(errbuf))); } return jv_invalid(); } @@ -356,19 +363,25 @@ static block constant_fold(block a, block b, int op) { jv res = jv_invalid(); if (block_const_kind(a) == JV_KIND_NUMBER) { - double na = jv_number_value(block_const(a)); - double nb = jv_number_value(block_const(b)); + jv jv_a = block_const(a); + jv jv_b = block_const(b); + + double na = jv_number_value(jv_a); + double nb = jv_number_value(jv_b); + + int cmp = jv_cmp(jv_a, jv_b); + switch (op) { case '+': res = jv_number(na + nb); break; case '-': res = jv_number(na - nb); break; case '*': res = jv_number(na * nb); break; case '/': res = jv_number(na / nb); break; - case EQ: res = (na == nb ? jv_true() : jv_false()); break; - case NEQ: res = (na != nb ? jv_true() : jv_false()); break; - case '<': res = (na < nb ? jv_true() : jv_false()); break; - case '>': res = (na > nb ? jv_true() : jv_false()); break; - case LESSEQ: res = (na <= nb ? jv_true() : jv_false()); break; - case GREATEREQ: res = (na >= nb ? jv_true() : jv_false()); break; + case EQ: res = (cmp == 0 ? jv_true() : jv_false()); break; + case NEQ: res = (cmp != 0 ? jv_true() : jv_false()); break; + case '<': res = (cmp < 0 ? jv_true() : jv_false()); break; + case '>': res = (cmp > 0 ? jv_true() : jv_false()); break; + case LESSEQ: res = (cmp <= 0 ? jv_true() : jv_false()); break; + case GREATERE