Patch for gemmi: Keep numbers in JSON file as strings. Adapted from this commit of the bundled fork of sajson in gemmi: https://github.com/project-gemmi/gemmi/commit/fccbca4f6040364ba708613e1429c2251872240d diff -ur a/include/sajson.h b/include/sajson.h --- a/include/sajson.h +++ b/include/sajson.h @@ -411,43 +411,6 @@ }; } // namespace internal -namespace integer_storage { -enum { word_length = 1 }; - -inline int load(const size_t* location) { - int value; - memcpy(&value, location, sizeof(value)); - return value; -} - -inline void store(size_t* location, int value) { - // NOTE: Most modern compilers optimize away this constant-size - // memcpy into a single instruction. If any don't, and treat - // punning through a union as legal, they can be special-cased. - static_assert( - sizeof(value) <= sizeof(*location), - "size_t must not be smaller than int"); - memcpy(location, &value, sizeof(value)); -} -} // namespace integer_storage - -namespace double_storage { -enum { word_length = sizeof(double) / sizeof(size_t) }; - -inline double load(const size_t* location) { - double value; - memcpy(&value, location, sizeof(double)); - return value; -} - -inline void store(size_t* location, double value) { - // NOTE: Most modern compilers optimize away this constant-size - // memcpy into a single instruction. If any don't, and treat - // punning through a union as legal, they can be special-cased. - memcpy(location, &value, sizeof(double)); -} -} // namespace double_storage - /// Represents a JSON value. First, call get_type() to check its type, /// which determines which methods are available. /// @@ -585,70 +548,10 @@ return length; } - /// If a numeric value was parsed as a 32-bit integer, returns it. - /// Only legal if get_type() is TYPE_INTEGER. - int get_integer_value() const { - assert_tag(tag::integer); - return integer_storage::load(payload); - } - - /// If a numeric value was parsed as a double, returns it. - /// Only legal if get_type() is TYPE_DOUBLE. - double get_double_value() const { - assert_tag(tag::double_); - return double_storage::load(payload); - } - - /// Returns a numeric value as a double-precision float. - /// Only legal if get_type() is TYPE_INTEGER or TYPE_DOUBLE. - double get_number_value() const { - assert_tag_2(tag::integer, tag::double_); - if (value_tag == tag::integer) { - return get_integer_value(); - } else { - return get_double_value(); - } - } - - /// Returns true and writes to the output argument if the numeric value - /// fits in a 53-bit integer. This is useful for timestamps and other - /// situations where integral values with greater than 32-bit precision - /// are used, as 64-bit values are not understood by all JSON - /// implementations or languages. - /// Returns false if the value is not an integer or not in range. - /// Only legal if get_type() is TYPE_INTEGER or TYPE_DOUBLE. - bool get_int53_value(int64_t* out) const { - // Make sure the output variable is always defined to avoid any - // possible situation like - // https://gist.github.com/chadaustin/2c249cb850619ddec05b23ca42cf7a18 - *out = 0; - - assert_tag_2(tag::integer, tag::double_); - switch (value_tag) { - case tag::integer: - *out = get_integer_value(); - return true; - case tag::double_: { - double v = get_double_value(); - if (v < -(1LL << 53) || v > (1LL << 53)) { - return false; - } - int64_t as_int = static_cast(v); - if (as_int != v) { - return false; - } - *out = as_int; - return true; - } - default: - return false; - } - } - /// Returns the length of the string. /// Only legal if get_type() is TYPE_STRING. size_t get_string_length() const { - assert_tag(tag::string); + assert_tag_3(tag::string, tag::integer, tag::double_); return payload[1] - payload[0]; } @@ -659,7 +562,7 @@ /// embedded NULs. /// Only legal if get_type() is TYPE_STRING. const char* as_cstring() const { - assert_tag(tag::string); + assert_tag_3(tag::string, tag::integer, tag::double_); return text + payload[0]; } @@ -667,7 +570,7 @@ /// Returns a string's value as a std::string. /// Only legal if get_type() is TYPE_STRING. std::string as_string() const { - assert_tag(tag::string); + assert_tag_3(tag::string, tag::integer, tag::double_); return std::string(text + payload[0], text + payload[1]); } #endif @@ -690,6 +593,10 @@ assert(e1 == value_tag || e2 == value_tag); } + void assert_tag_3(tag e1, tag e2, tag e3) const { + assert(e1 == value_tag || e2 == value_tag || e3 == value_tag); + } + void assert_in_bounds(size_t i) const { assert(i < get_length()); } const tag value_tag; @@ -2059,6 +1966,8 @@ std::pair parse_number(char* p) { using internal::tag; + size_t start = p - input.get_data(); + // Assume 32-bit, two's complement integers. static constexpr unsigned RISKY = INT_MAX / 10u; unsigned max_digit_after_risky = INT_MAX % 10u; @@ -2235,23 +2144,18 @@ u = 0u - u; } } + + bool success; + size_t* out = allocator.reserve(2, &success); + if (SAJSON_UNLIKELY(!success)) { + return std::make_pair(oom(p, "number"), tag::null); + } + out[0] = start; + out[1] = p - input.get_data(); + if (try_double) { - bool success; - size_t* out - = allocator.reserve(double_storage::word_length, &success); - if (SAJSON_UNLIKELY(!success)) { - return std::make_pair(oom(p, "double"), tag::null); - } - double_storage::store(out, d); return std::make_pair(p, tag::double_); } else { - bool success; - size_t* out - = allocator.reserve(integer_storage::word_length, &success); - if (SAJSON_UNLIKELY(!success)) { - return std::make_pair(oom(p, "integer"), tag::null); - } - integer_storage::store(out, static_cast(u)); return std::make_pair(p, tag::integer); } }