json 2.11.3 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGES.md +24 -0
- data/README.md +13 -0
- data/ext/json/ext/fbuffer/fbuffer.h +38 -4
- data/ext/json/ext/generator/extconf.rb +6 -0
- data/ext/json/ext/generator/generator.c +317 -19
- data/ext/json/ext/parser/extconf.rb +5 -1
- data/ext/json/ext/parser/parser.c +266 -124
- data/ext/json/ext/simd/conf.rb +20 -0
- data/ext/json/ext/simd/simd.h +187 -0
- data/ext/json/ext/vendor/fpconv.c +10 -10
- data/json.gemspec +2 -3
- data/lib/json/common.rb +8 -6
- data/lib/json/ext.rb +2 -2
- data/lib/json/version.rb +1 -1
- data/lib/json.rb +33 -0
- metadata +4 -2
@@ -0,0 +1,187 @@
|
|
1
|
+
typedef enum {
|
2
|
+
SIMD_NONE,
|
3
|
+
SIMD_NEON,
|
4
|
+
SIMD_SSE2
|
5
|
+
} SIMD_Implementation;
|
6
|
+
|
7
|
+
#ifdef JSON_ENABLE_SIMD
|
8
|
+
|
9
|
+
#ifdef __clang__
|
10
|
+
#if __has_builtin(__builtin_ctzll)
|
11
|
+
#define HAVE_BUILTIN_CTZLL 1
|
12
|
+
#else
|
13
|
+
#define HAVE_BUILTIN_CTZLL 0
|
14
|
+
#endif
|
15
|
+
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
|
16
|
+
#define HAVE_BUILTIN_CTZLL 1
|
17
|
+
#else
|
18
|
+
#define HAVE_BUILTIN_CTZLL 0
|
19
|
+
#endif
|
20
|
+
|
21
|
+
static inline uint32_t trailing_zeros64(uint64_t input)
|
22
|
+
{
|
23
|
+
#if HAVE_BUILTIN_CTZLL
|
24
|
+
return __builtin_ctzll(input);
|
25
|
+
#else
|
26
|
+
uint32_t trailing_zeros = 0;
|
27
|
+
uint64_t temp = input;
|
28
|
+
while ((temp & 1) == 0 && temp > 0) {
|
29
|
+
trailing_zeros++;
|
30
|
+
temp >>= 1;
|
31
|
+
}
|
32
|
+
return trailing_zeros;
|
33
|
+
#endif
|
34
|
+
}
|
35
|
+
|
36
|
+
static inline int trailing_zeros(int input)
|
37
|
+
{
|
38
|
+
#if HAVE_BUILTIN_CTZLL
|
39
|
+
return __builtin_ctz(input);
|
40
|
+
#else
|
41
|
+
int trailing_zeros = 0;
|
42
|
+
int temp = input;
|
43
|
+
while ((temp & 1) == 0 && temp > 0) {
|
44
|
+
trailing_zeros++;
|
45
|
+
temp >>= 1;
|
46
|
+
}
|
47
|
+
return trailing_zeros;
|
48
|
+
#endif
|
49
|
+
}
|
50
|
+
|
51
|
+
#if (defined(__GNUC__ ) || defined(__clang__))
|
52
|
+
#define FORCE_INLINE __attribute__((always_inline))
|
53
|
+
#else
|
54
|
+
#define FORCE_INLINE
|
55
|
+
#endif
|
56
|
+
|
57
|
+
|
58
|
+
#define SIMD_MINIMUM_THRESHOLD 6
|
59
|
+
|
60
|
+
#if defined(__ARM_NEON) || defined(__ARM_NEON__) || defined(__aarch64__) || defined(_M_ARM64)
|
61
|
+
#include <arm_neon.h>
|
62
|
+
|
63
|
+
#define FIND_SIMD_IMPLEMENTATION_DEFINED 1
|
64
|
+
static inline SIMD_Implementation find_simd_implementation(void)
|
65
|
+
{
|
66
|
+
return SIMD_NEON;
|
67
|
+
}
|
68
|
+
|
69
|
+
#define HAVE_SIMD 1
|
70
|
+
#define HAVE_SIMD_NEON 1
|
71
|
+
|
72
|
+
// See: https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon
|
73
|
+
static inline FORCE_INLINE uint64_t neon_match_mask(uint8x16_t matches)
|
74
|
+
{
|
75
|
+
const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(matches), 4);
|
76
|
+
const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
|
77
|
+
return mask & 0x8888888888888888ull;
|
78
|
+
}
|
79
|
+
|
80
|
+
static inline FORCE_INLINE uint64_t compute_chunk_mask_neon(const char *ptr)
|
81
|
+
{
|
82
|
+
uint8x16_t chunk = vld1q_u8((const unsigned char *)ptr);
|
83
|
+
|
84
|
+
// Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33
|
85
|
+
// https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
|
86
|
+
const uint8x16_t too_low_or_dbl_quote = vcltq_u8(veorq_u8(chunk, vdupq_n_u8(2)), vdupq_n_u8(33));
|
87
|
+
|
88
|
+
uint8x16_t has_backslash = vceqq_u8(chunk, vdupq_n_u8('\\'));
|
89
|
+
uint8x16_t needs_escape = vorrq_u8(too_low_or_dbl_quote, has_backslash);
|
90
|
+
return neon_match_mask(needs_escape);
|
91
|
+
}
|
92
|
+
|
93
|
+
static inline FORCE_INLINE int string_scan_simd_neon(const char **ptr, const char *end, uint64_t *mask)
|
94
|
+
{
|
95
|
+
while (*ptr + sizeof(uint8x16_t) <= end) {
|
96
|
+
uint64_t chunk_mask = compute_chunk_mask_neon(*ptr);
|
97
|
+
if (chunk_mask) {
|
98
|
+
*mask = chunk_mask;
|
99
|
+
return 1;
|
100
|
+
}
|
101
|
+
*ptr += sizeof(uint8x16_t);
|
102
|
+
}
|
103
|
+
return 0;
|
104
|
+
}
|
105
|
+
|
106
|
+
uint8x16x4_t load_uint8x16_4(const unsigned char *table) {
|
107
|
+
uint8x16x4_t tab;
|
108
|
+
tab.val[0] = vld1q_u8(table);
|
109
|
+
tab.val[1] = vld1q_u8(table+16);
|
110
|
+
tab.val[2] = vld1q_u8(table+32);
|
111
|
+
tab.val[3] = vld1q_u8(table+48);
|
112
|
+
return tab;
|
113
|
+
}
|
114
|
+
|
115
|
+
#endif /* ARM Neon Support.*/
|
116
|
+
|
117
|
+
#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || defined(_M_AMD64)
|
118
|
+
|
119
|
+
#ifdef HAVE_X86INTRIN_H
|
120
|
+
#include <x86intrin.h>
|
121
|
+
|
122
|
+
#define HAVE_SIMD 1
|
123
|
+
#define HAVE_SIMD_SSE2 1
|
124
|
+
|
125
|
+
#ifdef HAVE_CPUID_H
|
126
|
+
#define FIND_SIMD_IMPLEMENTATION_DEFINED 1
|
127
|
+
|
128
|
+
#if defined(__clang__) || defined(__GNUC__)
|
129
|
+
#define TARGET_SSE2 __attribute__((target("sse2")))
|
130
|
+
#else
|
131
|
+
#define TARGET_SSE2
|
132
|
+
#endif
|
133
|
+
|
134
|
+
#define _mm_cmpge_epu8(a, b) _mm_cmpeq_epi8(_mm_max_epu8(a, b), a)
|
135
|
+
#define _mm_cmple_epu8(a, b) _mm_cmpge_epu8(b, a)
|
136
|
+
#define _mm_cmpgt_epu8(a, b) _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1))
|
137
|
+
#define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a)
|
138
|
+
|
139
|
+
static inline TARGET_SSE2 FORCE_INLINE int compute_chunk_mask_sse2(const char *ptr)
|
140
|
+
{
|
141
|
+
__m128i chunk = _mm_loadu_si128((__m128i const*)ptr);
|
142
|
+
// Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33
|
143
|
+
// https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
|
144
|
+
__m128i too_low_or_dbl_quote = _mm_cmplt_epu8(_mm_xor_si128(chunk, _mm_set1_epi8(2)), _mm_set1_epi8(33));
|
145
|
+
__m128i has_backslash = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\\'));
|
146
|
+
__m128i needs_escape = _mm_or_si128(too_low_or_dbl_quote, has_backslash);
|
147
|
+
return _mm_movemask_epi8(needs_escape);
|
148
|
+
}
|
149
|
+
|
150
|
+
static inline TARGET_SSE2 FORCE_INLINE int string_scan_simd_sse2(const char **ptr, const char *end, int *mask)
|
151
|
+
{
|
152
|
+
while (*ptr + sizeof(__m128i) <= end) {
|
153
|
+
int chunk_mask = compute_chunk_mask_sse2(*ptr);
|
154
|
+
if (chunk_mask) {
|
155
|
+
*mask = chunk_mask;
|
156
|
+
return 1;
|
157
|
+
}
|
158
|
+
*ptr += sizeof(__m128i);
|
159
|
+
}
|
160
|
+
|
161
|
+
return 0;
|
162
|
+
}
|
163
|
+
|
164
|
+
#include <cpuid.h>
|
165
|
+
#endif /* HAVE_CPUID_H */
|
166
|
+
|
167
|
+
static inline SIMD_Implementation find_simd_implementation(void)
|
168
|
+
{
|
169
|
+
// TODO Revisit. I think the SSE version now only uses SSE2 instructions.
|
170
|
+
if (__builtin_cpu_supports("sse2")) {
|
171
|
+
return SIMD_SSE2;
|
172
|
+
}
|
173
|
+
|
174
|
+
return SIMD_NONE;
|
175
|
+
}
|
176
|
+
|
177
|
+
#endif /* HAVE_X86INTRIN_H */
|
178
|
+
#endif /* X86_64 Support */
|
179
|
+
|
180
|
+
#endif /* JSON_ENABLE_SIMD */
|
181
|
+
|
182
|
+
#ifndef FIND_SIMD_IMPLEMENTATION_DEFINED
|
183
|
+
static inline SIMD_Implementation find_simd_implementation(void)
|
184
|
+
{
|
185
|
+
return SIMD_NONE;
|
186
|
+
}
|
187
|
+
#endif
|
@@ -41,7 +41,7 @@ typedef struct Fp {
|
|
41
41
|
int exp;
|
42
42
|
} Fp;
|
43
43
|
|
44
|
-
static Fp powers_ten[] = {
|
44
|
+
static const Fp powers_ten[] = {
|
45
45
|
{ 18054884314459144840U, -1220 }, { 13451937075301367670U, -1193 },
|
46
46
|
{ 10022474136428063862U, -1166 }, { 14934650266808366570U, -1140 },
|
47
47
|
{ 11127181549972568877U, -1113 }, { 16580792590934885855U, -1087 },
|
@@ -92,7 +92,7 @@ static Fp find_cachedpow10(int exp, int* k)
|
|
92
92
|
{
|
93
93
|
const double one_log_ten = 0.30102999566398114;
|
94
94
|
|
95
|
-
int approx = -(exp + npowers) * one_log_ten;
|
95
|
+
int approx = (int)(-(exp + npowers) * one_log_ten);
|
96
96
|
int idx = (approx - firstpower) / steppowers;
|
97
97
|
|
98
98
|
while(1) {
|
@@ -123,7 +123,7 @@ static Fp find_cachedpow10(int exp, int* k)
|
|
123
123
|
#define absv(n) ((n) < 0 ? -(n) : (n))
|
124
124
|
#define minv(a, b) ((a) < (b) ? (a) : (b))
|
125
125
|
|
126
|
-
static uint64_t tens[] = {
|
126
|
+
static const uint64_t tens[] = {
|
127
127
|
10000000000000000000U, 1000000000000000000U, 100000000000000000U,
|
128
128
|
10000000000000000U, 1000000000000000U, 100000000000000U,
|
129
129
|
10000000000000U, 1000000000000U, 100000000000U,
|
@@ -244,7 +244,7 @@ static int generate_digits(Fp* fp, Fp* upper, Fp* lower, char* digits, int* K)
|
|
244
244
|
uint64_t part2 = upper->frac & (one.frac - 1);
|
245
245
|
|
246
246
|
int idx = 0, kappa = 10;
|
247
|
-
uint64_t* divp;
|
247
|
+
const uint64_t* divp;
|
248
248
|
/* 1000000000 */
|
249
249
|
for(divp = tens + 10; kappa > 0; divp++) {
|
250
250
|
|
@@ -268,7 +268,7 @@ static int generate_digits(Fp* fp, Fp* upper, Fp* lower, char* digits, int* K)
|
|
268
268
|
}
|
269
269
|
|
270
270
|
/* 10 */
|
271
|
-
uint64_t* unit = tens + 18;
|
271
|
+
const uint64_t* unit = tens + 18;
|
272
272
|
|
273
273
|
while(true) {
|
274
274
|
part2 *= 10;
|
@@ -340,7 +340,7 @@ static int emit_digits(char* digits, int ndigits, char* dest, int K, bool neg)
|
|
340
340
|
}
|
341
341
|
|
342
342
|
/* write decimal w/o scientific notation */
|
343
|
-
if(K < 0 && (K > -7 || exp <
|
343
|
+
if(K < 0 && (K > -7 || exp < 10)) {
|
344
344
|
int offset = ndigits - absv(K);
|
345
345
|
/* fp < 1.0 -> write leading zero */
|
346
346
|
if(offset <= 0) {
|
@@ -432,8 +432,8 @@ static int filter_special(double fp, char* dest)
|
|
432
432
|
*
|
433
433
|
* Input:
|
434
434
|
* fp -> the double to convert, dest -> destination buffer.
|
435
|
-
* The generated string will never be longer than
|
436
|
-
* Make sure to pass a pointer to at least
|
435
|
+
* The generated string will never be longer than 28 characters.
|
436
|
+
* Make sure to pass a pointer to at least 28 bytes of memory.
|
437
437
|
* The emitted string will not be null terminated.
|
438
438
|
*
|
439
439
|
* Output:
|
@@ -443,7 +443,7 @@ static int filter_special(double fp, char* dest)
|
|
443
443
|
*
|
444
444
|
* void print(double d)
|
445
445
|
* {
|
446
|
-
* char buf[
|
446
|
+
* char buf[28 + 1] // plus null terminator
|
447
447
|
* int str_len = fpconv_dtoa(d, buf);
|
448
448
|
*
|
449
449
|
* buf[str_len] = '\0';
|
@@ -451,7 +451,7 @@ static int filter_special(double fp, char* dest)
|
|
451
451
|
* }
|
452
452
|
*
|
453
453
|
*/
|
454
|
-
static int fpconv_dtoa(double d, char dest[
|
454
|
+
static int fpconv_dtoa(double d, char dest[28])
|
455
455
|
{
|
456
456
|
char digits[18];
|
457
457
|
|
data/json.gemspec
CHANGED
@@ -44,15 +44,14 @@ spec = Gem::Specification.new do |s|
|
|
44
44
|
"LEGAL",
|
45
45
|
"README.md",
|
46
46
|
"json.gemspec",
|
47
|
-
|
48
|
-
]
|
47
|
+
] + Dir.glob("lib/**/*.rb", base: File.expand_path("..", __FILE__))
|
49
48
|
|
50
49
|
if java_ext
|
51
50
|
s.platform = 'java'
|
52
51
|
s.files += Dir["lib/json/ext/**/*.jar"]
|
53
52
|
else
|
54
53
|
s.extensions = Dir["ext/json/**/extconf.rb"]
|
55
|
-
s.files += Dir["ext/json/**/*.{c,h}"]
|
54
|
+
s.files += Dir["ext/json/**/*.{c,h,rb}"]
|
56
55
|
end
|
57
56
|
end
|
58
57
|
|
data/lib/json/common.rb
CHANGED
@@ -172,7 +172,7 @@ module JSON
|
|
172
172
|
end
|
173
173
|
end
|
174
174
|
self.state = generator::State
|
175
|
-
const_set :State,
|
175
|
+
const_set :State, state
|
176
176
|
ensure
|
177
177
|
$VERBOSE = old
|
178
178
|
end
|
@@ -230,7 +230,9 @@ module JSON
|
|
230
230
|
class JSONError < StandardError; end
|
231
231
|
|
232
232
|
# This exception is raised if a parser error occurs.
|
233
|
-
class ParserError < JSONError
|
233
|
+
class ParserError < JSONError
|
234
|
+
attr_reader :line, :column
|
235
|
+
end
|
234
236
|
|
235
237
|
# This exception is raised if the nesting of parsed data structures is too
|
236
238
|
# deep.
|
@@ -266,7 +268,7 @@ module JSON
|
|
266
268
|
# to string interpolation.
|
267
269
|
#
|
268
270
|
# Note: no validation is performed on the provided string. It is the
|
269
|
-
#
|
271
|
+
# responsibility of the caller to ensure the string contains valid JSON.
|
270
272
|
Fragment = Struct.new(:json) do
|
271
273
|
def initialize(json)
|
272
274
|
unless string = String.try_convert(json)
|
@@ -488,7 +490,7 @@ module JSON
|
|
488
490
|
# }
|
489
491
|
#
|
490
492
|
def pretty_generate(obj, opts = nil)
|
491
|
-
return
|
493
|
+
return opts.generate(obj) if State === opts
|
492
494
|
|
493
495
|
options = PRETTY_GENERATE_OPTIONS
|
494
496
|
|
@@ -1070,7 +1072,7 @@ module ::Kernel
|
|
1070
1072
|
end
|
1071
1073
|
|
1072
1074
|
objs.each do |obj|
|
1073
|
-
puts JSON
|
1075
|
+
puts JSON.generate(obj, :allow_nan => true, :max_nesting => false)
|
1074
1076
|
end
|
1075
1077
|
nil
|
1076
1078
|
end
|
@@ -1085,7 +1087,7 @@ module ::Kernel
|
|
1085
1087
|
end
|
1086
1088
|
|
1087
1089
|
objs.each do |obj|
|
1088
|
-
puts JSON
|
1090
|
+
puts JSON.pretty_generate(obj, :allow_nan => true, :max_nesting => false)
|
1089
1091
|
end
|
1090
1092
|
nil
|
1091
1093
|
end
|
data/lib/json/ext.rb
CHANGED
@@ -34,12 +34,12 @@ module JSON
|
|
34
34
|
|
35
35
|
if RUBY_ENGINE == 'truffleruby'
|
36
36
|
require 'json/truffle_ruby/generator'
|
37
|
-
JSON.generator =
|
37
|
+
JSON.generator = JSON::TruffleRuby::Generator
|
38
38
|
else
|
39
39
|
require 'json/ext/generator'
|
40
40
|
JSON.generator = Generator
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
JSON_LOADED = true unless defined?(
|
44
|
+
JSON_LOADED = true unless defined?(JSON::JSON_LOADED)
|
45
45
|
end
|
data/lib/json/version.rb
CHANGED
data/lib/json.rb
CHANGED
@@ -127,6 +127,24 @@ require 'json/common'
|
|
127
127
|
#
|
128
128
|
# ---
|
129
129
|
#
|
130
|
+
# Option +allow_duplicate_key+ specifies whether duplicate keys in objects
|
131
|
+
# should be ignored or cause an error to be raised:
|
132
|
+
#
|
133
|
+
# When not specified:
|
134
|
+
# # The last value is used and a deprecation warning emitted.
|
135
|
+
# JSON.parse('{"a": 1, "a":2}') => {"a" => 2}
|
136
|
+
# # waring: detected duplicate keys in JSON object.
|
137
|
+
# # This will raise an error in json 3.0 unless enabled via `allow_duplicate_key: true`
|
138
|
+
#
|
139
|
+
# When set to `+true+`
|
140
|
+
# # The last value is used.
|
141
|
+
# JSON.parse('{"a": 1, "a":2}') => {"a" => 2}
|
142
|
+
#
|
143
|
+
# When set to `+false+`, the future default:
|
144
|
+
# JSON.parse('{"a": 1, "a":2}') => duplicate key at line 1 column 1 (JSON::ParserError)
|
145
|
+
#
|
146
|
+
# ---
|
147
|
+
#
|
130
148
|
# Option +allow_nan+ (boolean) specifies whether to allow
|
131
149
|
# NaN, Infinity, and MinusInfinity in +source+;
|
132
150
|
# defaults to +false+.
|
@@ -143,8 +161,23 @@ require 'json/common'
|
|
143
161
|
# ruby = JSON.parse(source, {allow_nan: true})
|
144
162
|
# ruby # => [NaN, Infinity, -Infinity]
|
145
163
|
#
|
164
|
+
# ---
|
165
|
+
#
|
166
|
+
# Option +allow_trailing_comma+ (boolean) specifies whether to allow
|
167
|
+
# trailing commas in objects and arrays;
|
168
|
+
# defaults to +false+.
|
169
|
+
#
|
170
|
+
# With the default, +false+:
|
171
|
+
# JSON.parse('[1,]') # unexpected character: ']' at line 1 column 4 (JSON::ParserError)
|
172
|
+
#
|
173
|
+
# When enabled:
|
174
|
+
# JSON.parse('[1,]', allow_trailing_comma: true) # => [1]
|
175
|
+
#
|
146
176
|
# ====== Output Options
|
147
177
|
#
|
178
|
+
# Option +freeze+ (boolean) specifies whether the returned objects will be frozen;
|
179
|
+
# defaults to +false+.
|
180
|
+
#
|
148
181
|
# Option +symbolize_names+ (boolean) specifies whether returned \Hash keys
|
149
182
|
# should be Symbols;
|
150
183
|
# defaults to +false+ (use Strings).
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: json
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Florian Frank
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-07-17 00:00:00.000000000 Z
|
11
11
|
dependencies: []
|
12
12
|
description: This is a JSON implementation as a Ruby extension in C.
|
13
13
|
email: flori@ping.de
|
@@ -28,6 +28,8 @@ files:
|
|
28
28
|
- ext/json/ext/generator/generator.c
|
29
29
|
- ext/json/ext/parser/extconf.rb
|
30
30
|
- ext/json/ext/parser/parser.c
|
31
|
+
- ext/json/ext/simd/conf.rb
|
32
|
+
- ext/json/ext/simd/simd.h
|
31
33
|
- ext/json/ext/vendor/fpconv.c
|
32
34
|
- ext/json/ext/vendor/jeaiii-ltoa.h
|
33
35
|
- json.gemspec
|