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.
@@ -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 < 4)) {
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 24 characters.
436
- * Make sure to pass a pointer to at least 24 bytes of memory.
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[24 + 1] // plus null terminator
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[24])
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
- *Dir["lib/**/*.rb"],
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, self.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; end
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
- # responsability of the caller to ensure the string contains valid JSON.
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 state.generate(obj) if State === opts
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::generate(obj, :allow_nan => true, :max_nesting => false)
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::pretty_generate(obj, :allow_nan => true, :max_nesting => false)
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 = ::JSON::TruffleRuby::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?(::JSON::JSON_LOADED)
44
+ JSON_LOADED = true unless defined?(JSON::JSON_LOADED)
45
45
  end
data/lib/json/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSON
4
- VERSION = '2.11.3'
4
+ VERSION = '2.13.0'
5
5
  end
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.11.3
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-04-25 00:00:00.000000000 Z
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