summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeonid S. Usov <leonid@practi.net>2018-10-19 21:57:41 +0300
committerWilliam Langford <wlangfor@gmail.com>2019-10-22 14:11:04 -0400
commitcf4b48c7ba30cb30e116b523cff036ea481459f6 (patch)
treeb6d5a0ed3286e77d7d1fb84e8bec3c33686dd96e
parentb6be13d5de6dd7d8aad5fd871eb6b0b30fc7d7f6 (diff)
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.
-rw-r--r--.gitignore1
-rw-r--r--Makefile.am12
-rw-r--r--configure.ac14
-rw-r--r--docs/content/manual/v1.6/manual.yml48
-rw-r--r--src/builtin.c29
-rw-r--r--src/execute.c15
-rw-r--r--src/jq_test.c73
-rw-r--r--src/jv.c651
-rw-r--r--src/jv.h5
-rw-r--r--src/jv_aux.c39
-rw-r--r--src/jv_dtoa_tsd.c46
-rw-r--r--src/jv_dtoa_tsd.h4
-rw-r--r--src/jv_parse.c23
-rw-r--r--src/jv_print.c37
-rw-r--r--src/jv_type_private.h7
-rw-r--r--src/parser.c1029
-rw-r--r--src/parser.h16
-rw-r--r--src/parser.y24
-rw-r--r--tests/jq.test39
-rw-r--r--tests/local.supp14
-rwxr-xr-xtests/setup3
21 files changed, 1374 insertions, 755 deletions
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 <time.h>]])
-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 <pthread.h>], [NULL, NULL])
- AC_FIND_FUNC([pthread_once], [pthread], [#include <pthread.h>], [NULL, NULL])
- AC_FIND_FUNC([atexit], [pthread], [#include <stdlib.h>], [NULL])
-fi
+AC_FIND_FUNC([pthread_key_create], [pthread], [#include <pthread.h>], [NULL, NULL])
+AC_FIND_FUNC([pthread_once], [pthread], [#include <pthread.h>], [NULL, NULL])
+AC_FIND_FUNC([atexit], [pthread], [#include <stdlib.h>], [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 "<unknown>";
}
-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 <pthread.h>
+
+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,