json 2.12.2 → 3.0.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 +222 -8
- data/LEGAL +12 -0
- data/README.md +34 -87
- data/ext/json/ext/fbuffer/fbuffer.h +75 -86
- data/ext/json/ext/generator/extconf.rb +5 -26
- data/ext/json/ext/generator/generator.c +560 -688
- data/ext/json/ext/json.h +196 -0
- data/ext/json/ext/parser/extconf.rb +34 -2
- data/ext/json/ext/parser/parser.c +2177 -673
- data/ext/json/ext/simd/conf.rb +24 -0
- data/ext/json/ext/simd/simd.h +208 -0
- data/ext/json/ext/vendor/fast_float_parser.h +814 -0
- data/ext/json/ext/vendor/fpconv.c +13 -12
- data/json.gemspec +2 -3
- data/lib/json/common.rb +200 -389
- data/lib/json/ext/generator/state.rb +8 -37
- data/lib/json/ext.rb +25 -1
- data/lib/json/truffle_ruby/generator.rb +186 -128
- data/lib/json/version.rb +1 -1
- data/lib/json.rb +107 -242
- metadata +7 -19
- data/ext/json/ext/generator/simd.h +0 -112
- data/lib/json/add/bigdecimal.rb +0 -58
- data/lib/json/add/complex.rb +0 -51
- data/lib/json/add/core.rb +0 -12
- data/lib/json/add/date.rb +0 -54
- data/lib/json/add/date_time.rb +0 -67
- data/lib/json/add/exception.rb +0 -49
- data/lib/json/add/ostruct.rb +0 -54
- data/lib/json/add/range.rb +0 -54
- data/lib/json/add/rational.rb +0 -49
- data/lib/json/add/regexp.rb +0 -48
- data/lib/json/add/set.rb +0 -48
- data/lib/json/add/struct.rb +0 -52
- data/lib/json/add/symbol.rb +0 -52
- data/lib/json/add/time.rb +0 -52
- data/lib/json/generic_object.rb +0 -75
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
#include "
|
|
1
|
+
#include "../json.h"
|
|
2
2
|
#include "../fbuffer/fbuffer.h"
|
|
3
3
|
#include "../vendor/fpconv.c"
|
|
4
4
|
|
|
5
5
|
#include <math.h>
|
|
6
6
|
#include <ctype.h>
|
|
7
7
|
|
|
8
|
-
#include "simd.h"
|
|
8
|
+
#include "../simd/simd.h"
|
|
9
9
|
|
|
10
10
|
/* ruby api and some helpers */
|
|
11
11
|
|
|
@@ -21,21 +21,20 @@ typedef struct JSON_Generator_StateStruct {
|
|
|
21
21
|
long depth;
|
|
22
22
|
long buffer_initial_length;
|
|
23
23
|
|
|
24
|
+
bool allow_duplicate_key;
|
|
25
|
+
bool as_json_single_arg;
|
|
24
26
|
bool allow_nan;
|
|
25
27
|
bool ascii_only;
|
|
26
28
|
bool script_safe;
|
|
27
29
|
bool strict;
|
|
30
|
+
VALUE sort_keys;
|
|
28
31
|
} JSON_Generator_State;
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
#define RB_UNLIKELY(cond) (cond)
|
|
32
|
-
#endif
|
|
33
|
-
|
|
34
|
-
static VALUE mJSON, cState, cFragment, mString_Extend, eGeneratorError, eNestingError, Encoding_UTF_8;
|
|
33
|
+
static VALUE mJSON, cState, cFragment, eGeneratorError, eNestingError, Encoding_UTF_8, default_sort_keys_proc;
|
|
35
34
|
|
|
36
|
-
static ID i_to_s, i_to_json, i_new,
|
|
37
|
-
static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan,
|
|
38
|
-
sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe,
|
|
35
|
+
static ID i_to_s, i_to_json, i_new, i_encode;
|
|
36
|
+
static VALUE sym_indent, sym_space, sym_space_before, sym_object_nl, sym_array_nl, sym_max_nesting, sym_allow_nan, sym_allow_duplicate_key,
|
|
37
|
+
sym_ascii_only, sym_depth, sym_buffer_initial_length, sym_script_safe, sym_strict, sym_as_json, sym_sort_keys;
|
|
39
38
|
|
|
40
39
|
|
|
41
40
|
#define GET_STATE_TO(self, state) \
|
|
@@ -55,8 +54,11 @@ struct generate_json_data {
|
|
|
55
54
|
JSON_Generator_State *state;
|
|
56
55
|
VALUE obj;
|
|
57
56
|
generator_func func;
|
|
57
|
+
long depth;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
static SIMD_Implementation simd_impl;
|
|
61
|
+
|
|
60
62
|
static VALUE cState_from_state_s(VALUE self, VALUE opts);
|
|
61
63
|
static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func, VALUE io);
|
|
62
64
|
static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
|
|
@@ -66,9 +68,6 @@ static void generate_json_string(FBuffer *buffer, struct generate_json_data *dat
|
|
|
66
68
|
static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
|
|
67
69
|
static void generate_json_false(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
|
|
68
70
|
static void generate_json_true(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
|
|
69
|
-
#ifdef RUBY_INTEGER_UNIFICATION
|
|
70
|
-
static void generate_json_integer(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
|
|
71
|
-
#endif
|
|
72
71
|
static void generate_json_fixnum(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
|
|
73
72
|
static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
|
|
74
73
|
static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, VALUE obj);
|
|
@@ -76,23 +75,18 @@ static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *d
|
|
|
76
75
|
|
|
77
76
|
static int usascii_encindex, utf8_encindex, binary_encindex;
|
|
78
77
|
|
|
79
|
-
|
|
80
|
-
RBIMPL_ATTR_NORETURN()
|
|
81
|
-
#endif
|
|
82
|
-
static void raise_generator_error_str(VALUE invalid_object, VALUE str)
|
|
78
|
+
NORETURN(static void) raise_generator_error_str(VALUE invalid_object, VALUE str)
|
|
83
79
|
{
|
|
80
|
+
rb_enc_associate_index(str, utf8_encindex);
|
|
84
81
|
VALUE exc = rb_exc_new_str(eGeneratorError, str);
|
|
85
82
|
rb_ivar_set(exc, rb_intern("@invalid_object"), invalid_object);
|
|
86
83
|
rb_exc_raise(exc);
|
|
87
84
|
}
|
|
88
85
|
|
|
89
|
-
#ifdef RBIMPL_ATTR_NORETURN
|
|
90
|
-
RBIMPL_ATTR_NORETURN()
|
|
91
|
-
#endif
|
|
92
86
|
#ifdef RBIMPL_ATTR_FORMAT
|
|
93
87
|
RBIMPL_ATTR_FORMAT(RBIMPL_PRINTF_FORMAT, 2, 3)
|
|
94
88
|
#endif
|
|
95
|
-
static void raise_generator_error(VALUE invalid_object, const char *fmt, ...)
|
|
89
|
+
NORETURN(static void) raise_generator_error(VALUE invalid_object, const char *fmt, ...)
|
|
96
90
|
{
|
|
97
91
|
va_list args;
|
|
98
92
|
va_start(args, fmt);
|
|
@@ -127,18 +121,12 @@ typedef struct _search_state {
|
|
|
127
121
|
#endif /* HAVE_SIMD */
|
|
128
122
|
} search_state;
|
|
129
123
|
|
|
130
|
-
|
|
131
|
-
#define FORCE_INLINE __attribute__((always_inline))
|
|
132
|
-
#else
|
|
133
|
-
#define FORCE_INLINE
|
|
134
|
-
#endif
|
|
135
|
-
|
|
136
|
-
static inline FORCE_INLINE void search_flush(search_state *search)
|
|
124
|
+
ALWAYS_INLINE(static) void search_flush(search_state *search)
|
|
137
125
|
{
|
|
138
126
|
// Do not remove this conditional without profiling, specifically escape-heavy text.
|
|
139
127
|
// escape_UTF8_char_basic will advance search->ptr and search->cursor (effectively a search_flush).
|
|
140
|
-
// For back-to-back characters that need to be escaped,
|
|
141
|
-
// will be called just before calling escape_UTF8_char_basic. There will be no
|
|
128
|
+
// For back-to-back characters that need to be escaped, specifically for the SIMD code paths, this method
|
|
129
|
+
// will be called just before calling escape_UTF8_char_basic. There will be no characters to append for the
|
|
142
130
|
// consecutive characters that need to be escaped. While the fbuffer_append is a no-op if
|
|
143
131
|
// nothing needs to be flushed, we can save a few memory references with this conditional.
|
|
144
132
|
if (search->ptr > search->cursor) {
|
|
@@ -160,8 +148,6 @@ static const unsigned char escape_table_basic[256] = {
|
|
|
160
148
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
161
149
|
};
|
|
162
150
|
|
|
163
|
-
static unsigned char (*search_escape_basic_impl)(search_state *);
|
|
164
|
-
|
|
165
151
|
static inline unsigned char search_escape_basic(search_state *search)
|
|
166
152
|
{
|
|
167
153
|
while (search->ptr < search->end) {
|
|
@@ -176,7 +162,7 @@ static inline unsigned char search_escape_basic(search_state *search)
|
|
|
176
162
|
return 0;
|
|
177
163
|
}
|
|
178
164
|
|
|
179
|
-
static
|
|
165
|
+
ALWAYS_INLINE(static) void escape_UTF8_char_basic(search_state *search)
|
|
180
166
|
{
|
|
181
167
|
const unsigned char ch = (unsigned char)*search->ptr;
|
|
182
168
|
switch (ch) {
|
|
@@ -217,11 +203,39 @@ static inline FORCE_INLINE void escape_UTF8_char_basic(search_state *search)
|
|
|
217
203
|
* Everything else (should be UTF-8) is just passed through and
|
|
218
204
|
* appended to the result.
|
|
219
205
|
*/
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
#if defined(HAVE_SIMD_NEON)
|
|
209
|
+
static inline unsigned char search_escape_basic_neon(search_state *search);
|
|
210
|
+
#elif defined(HAVE_SIMD_SSE2)
|
|
211
|
+
static inline unsigned char search_escape_basic_sse2(search_state *search);
|
|
212
|
+
#endif
|
|
213
|
+
|
|
214
|
+
static inline unsigned char search_escape_basic(search_state *search);
|
|
215
|
+
|
|
220
216
|
static inline void convert_UTF8_to_JSON(search_state *search)
|
|
221
217
|
{
|
|
222
|
-
|
|
218
|
+
#ifdef HAVE_SIMD
|
|
219
|
+
#if defined(HAVE_SIMD_NEON)
|
|
220
|
+
while (search_escape_basic_neon(search)) {
|
|
221
|
+
escape_UTF8_char_basic(search);
|
|
222
|
+
}
|
|
223
|
+
#elif defined(HAVE_SIMD_SSE2)
|
|
224
|
+
if (simd_impl == SIMD_SSE2) {
|
|
225
|
+
while (search_escape_basic_sse2(search)) {
|
|
226
|
+
escape_UTF8_char_basic(search);
|
|
227
|
+
}
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
while (search_escape_basic(search)) {
|
|
231
|
+
escape_UTF8_char_basic(search);
|
|
232
|
+
}
|
|
233
|
+
#endif
|
|
234
|
+
#else
|
|
235
|
+
while (search_escape_basic(search)) {
|
|
223
236
|
escape_UTF8_char_basic(search);
|
|
224
237
|
}
|
|
238
|
+
#endif /* HAVE_SIMD */
|
|
225
239
|
}
|
|
226
240
|
|
|
227
241
|
static inline void escape_UTF8_char(search_state *search, unsigned char ch_len)
|
|
@@ -263,8 +277,10 @@ static inline void escape_UTF8_char(search_state *search, unsigned char ch_len)
|
|
|
263
277
|
|
|
264
278
|
#ifdef HAVE_SIMD
|
|
265
279
|
|
|
266
|
-
static
|
|
280
|
+
ALWAYS_INLINE(static) char *copy_remaining_bytes(search_state *search, unsigned long vec_len, unsigned long len)
|
|
267
281
|
{
|
|
282
|
+
RBIMPL_ASSERT_OR_ASSUME(len < vec_len);
|
|
283
|
+
|
|
268
284
|
// Flush the buffer so everything up until the last 'len' characters are unflushed.
|
|
269
285
|
search_flush(search);
|
|
270
286
|
|
|
@@ -274,19 +290,25 @@ static inline FORCE_INLINE char *copy_remaining_bytes(search_state *search, unsi
|
|
|
274
290
|
char *s = (buf->ptr + buf->len);
|
|
275
291
|
|
|
276
292
|
// Pad the buffer with dummy characters that won't need escaping.
|
|
277
|
-
// This seem
|
|
278
|
-
|
|
293
|
+
// This seem wasteful at first sight, but memset of vector length is very fast.
|
|
294
|
+
// This is a space as it can be directly represented as an immediate on AArch64.
|
|
295
|
+
memset(s, ' ', vec_len);
|
|
279
296
|
|
|
280
297
|
// Optimistically copy the remaining 'len' characters to the output FBuffer. If there are no characters
|
|
281
298
|
// to escape, then everything ends up in the correct spot. Otherwise it was convenient temporary storage.
|
|
282
|
-
|
|
299
|
+
if (vec_len == 16) {
|
|
300
|
+
RBIMPL_ASSERT_OR_ASSUME(len >= SIMD_MINIMUM_THRESHOLD);
|
|
301
|
+
json_fast_memcpy16(s, search->ptr, len);
|
|
302
|
+
} else {
|
|
303
|
+
MEMCPY(s, search->ptr, char, len);
|
|
304
|
+
}
|
|
283
305
|
|
|
284
306
|
return s;
|
|
285
307
|
}
|
|
286
308
|
|
|
287
309
|
#ifdef HAVE_SIMD_NEON
|
|
288
310
|
|
|
289
|
-
static
|
|
311
|
+
ALWAYS_INLINE(static) unsigned char neon_next_match(search_state *search)
|
|
290
312
|
{
|
|
291
313
|
uint64_t mask = search->matches_mask;
|
|
292
314
|
uint32_t index = trailing_zeros64(mask) >> 2;
|
|
@@ -304,28 +326,6 @@ static inline FORCE_INLINE unsigned char neon_next_match(search_state *search)
|
|
|
304
326
|
return 1;
|
|
305
327
|
}
|
|
306
328
|
|
|
307
|
-
// See: https://community.arm.com/arm-community-blogs/b/servers-and-cloud-computing-blog/posts/porting-x86-vector-bitmask-optimizations-to-arm-neon
|
|
308
|
-
static inline FORCE_INLINE uint64_t neon_match_mask(uint8x16_t matches)
|
|
309
|
-
{
|
|
310
|
-
const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(matches), 4);
|
|
311
|
-
const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
|
|
312
|
-
return mask & 0x8888888888888888ull;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
static inline FORCE_INLINE uint64_t neon_rules_update(const char *ptr)
|
|
316
|
-
{
|
|
317
|
-
uint8x16_t chunk = vld1q_u8((const unsigned char *)ptr);
|
|
318
|
-
|
|
319
|
-
// Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33
|
|
320
|
-
// https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
|
|
321
|
-
const uint8x16_t too_low_or_dbl_quote = vcltq_u8(veorq_u8(chunk, vdupq_n_u8(2)), vdupq_n_u8(33));
|
|
322
|
-
|
|
323
|
-
uint8x16_t has_backslash = vceqq_u8(chunk, vdupq_n_u8('\\'));
|
|
324
|
-
uint8x16_t needs_escape = vorrq_u8(too_low_or_dbl_quote, has_backslash);
|
|
325
|
-
|
|
326
|
-
return neon_match_mask(needs_escape);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
329
|
static inline unsigned char search_escape_basic_neon(search_state *search)
|
|
330
330
|
{
|
|
331
331
|
if (RB_UNLIKELY(search->has_matches)) {
|
|
@@ -333,7 +333,7 @@ static inline unsigned char search_escape_basic_neon(search_state *search)
|
|
|
333
333
|
if (search->matches_mask > 0) {
|
|
334
334
|
return neon_next_match(search);
|
|
335
335
|
} else {
|
|
336
|
-
// neon_next_match will only advance search->ptr up to the last matching character.
|
|
336
|
+
// neon_next_match will only advance search->ptr up to the last matching character.
|
|
337
337
|
// Skip over any characters in the last chunk that occur after the last match.
|
|
338
338
|
search->has_matches = false;
|
|
339
339
|
search->ptr = search->chunk_end;
|
|
@@ -342,67 +342,61 @@ static inline unsigned char search_escape_basic_neon(search_state *search)
|
|
|
342
342
|
|
|
343
343
|
/*
|
|
344
344
|
* The code below implements an SIMD-based algorithm to determine if N bytes at a time
|
|
345
|
-
* need to be escaped.
|
|
346
|
-
*
|
|
345
|
+
* need to be escaped.
|
|
346
|
+
*
|
|
347
347
|
* Assume the ptr = "Te\sting!" (the double quotes are included in the string)
|
|
348
|
-
*
|
|
348
|
+
*
|
|
349
349
|
* The explanation will be limited to the first 8 bytes of the string for simplicity. However
|
|
350
350
|
* the vector insructions may work on larger vectors.
|
|
351
|
-
*
|
|
351
|
+
*
|
|
352
352
|
* First, we load three constants 'lower_bound', 'backslash' and 'dblquote" in vector registers.
|
|
353
|
-
*
|
|
354
|
-
* lower_bound: [20 20 20 20 20 20 20 20]
|
|
355
|
-
* backslash: [5C 5C 5C 5C 5C 5C 5C 5C]
|
|
356
|
-
* dblquote: [22 22 22 22 22 22 22 22]
|
|
357
|
-
*
|
|
358
|
-
* Next we load the first chunk of the ptr:
|
|
353
|
+
*
|
|
354
|
+
* lower_bound: [20 20 20 20 20 20 20 20]
|
|
355
|
+
* backslash: [5C 5C 5C 5C 5C 5C 5C 5C]
|
|
356
|
+
* dblquote: [22 22 22 22 22 22 22 22]
|
|
357
|
+
*
|
|
358
|
+
* Next we load the first chunk of the ptr:
|
|
359
359
|
* [22 54 65 5C 73 74 69 6E] (" T e \ s t i n)
|
|
360
|
-
*
|
|
360
|
+
*
|
|
361
361
|
* First we check if any byte in chunk is less than 32 (0x20). This returns the following vector
|
|
362
362
|
* as no bytes are less than 32 (0x20):
|
|
363
363
|
* [0 0 0 0 0 0 0 0]
|
|
364
|
-
*
|
|
364
|
+
*
|
|
365
365
|
* Next, we check if any byte in chunk is equal to a backslash:
|
|
366
366
|
* [0 0 0 FF 0 0 0 0]
|
|
367
|
-
*
|
|
367
|
+
*
|
|
368
368
|
* Finally we check if any byte in chunk is equal to a double quote:
|
|
369
|
-
* [FF 0 0 0 0 0 0 0]
|
|
370
|
-
*
|
|
369
|
+
* [FF 0 0 0 0 0 0 0]
|
|
370
|
+
*
|
|
371
371
|
* Now we have three vectors where each byte indicates if the corresponding byte in chunk
|
|
372
372
|
* needs to be escaped. We combine these vectors with a series of logical OR instructions.
|
|
373
373
|
* This is the needs_escape vector and it is equal to:
|
|
374
|
-
* [FF 0 0 FF 0 0 0 0]
|
|
375
|
-
*
|
|
374
|
+
* [FF 0 0 FF 0 0 0 0]
|
|
375
|
+
*
|
|
376
376
|
* Next we compute the bitwise AND between each byte and 0x1 and compute the horizontal sum of
|
|
377
377
|
* the values in the vector. This computes how many bytes need to be escaped within this chunk.
|
|
378
|
-
*
|
|
378
|
+
*
|
|
379
379
|
* Finally we compute a mask that indicates which bytes need to be escaped. If the mask is 0 then,
|
|
380
380
|
* no bytes need to be escaped and we can continue to the next chunk. If the mask is not 0 then we
|
|
381
381
|
* have at least one byte that needs to be escaped.
|
|
382
382
|
*/
|
|
383
|
-
while (search->ptr + sizeof(uint8x16_t) <= search->end) {
|
|
384
|
-
uint64_t mask = neon_rules_update(search->ptr);
|
|
385
383
|
|
|
386
|
-
|
|
387
|
-
search->ptr += sizeof(uint8x16_t);
|
|
388
|
-
continue;
|
|
389
|
-
}
|
|
390
|
-
search->matches_mask = mask;
|
|
384
|
+
if (string_scan_simd_neon(&search->ptr, search->end, &search->matches_mask)) {
|
|
391
385
|
search->has_matches = true;
|
|
392
386
|
search->chunk_base = search->ptr;
|
|
393
387
|
search->chunk_end = search->ptr + sizeof(uint8x16_t);
|
|
394
388
|
return neon_next_match(search);
|
|
395
389
|
}
|
|
396
390
|
|
|
397
|
-
// There are fewer than 16 bytes left.
|
|
391
|
+
// There are fewer than 16 bytes left.
|
|
398
392
|
unsigned long remaining = (search->end - search->ptr);
|
|
399
393
|
if (remaining >= SIMD_MINIMUM_THRESHOLD) {
|
|
400
394
|
char *s = copy_remaining_bytes(search, sizeof(uint8x16_t), remaining);
|
|
401
395
|
|
|
402
|
-
uint64_t mask =
|
|
396
|
+
uint64_t mask = compute_chunk_mask_neon(s);
|
|
403
397
|
|
|
404
398
|
if (!mask) {
|
|
405
|
-
// Nothing to escape, ensure search_flush doesn't do anything by setting
|
|
399
|
+
// Nothing to escape, ensure search_flush doesn't do anything by setting
|
|
406
400
|
// search->cursor to search->ptr.
|
|
407
401
|
fbuffer_consumed(search->buffer, remaining);
|
|
408
402
|
search->ptr = search->end;
|
|
@@ -428,12 +422,7 @@ static inline unsigned char search_escape_basic_neon(search_state *search)
|
|
|
428
422
|
|
|
429
423
|
#ifdef HAVE_SIMD_SSE2
|
|
430
424
|
|
|
431
|
-
|
|
432
|
-
#define _mm_cmple_epu8(a, b) _mm_cmpge_epu8(b, a)
|
|
433
|
-
#define _mm_cmpgt_epu8(a, b) _mm_xor_si128(_mm_cmple_epu8(a, b), _mm_set1_epi8(-1))
|
|
434
|
-
#define _mm_cmplt_epu8(a, b) _mm_cmpgt_epu8(b, a)
|
|
435
|
-
|
|
436
|
-
static inline FORCE_INLINE unsigned char sse2_next_match(search_state *search)
|
|
425
|
+
ALWAYS_INLINE(static) unsigned char sse2_next_match(search_state *search)
|
|
437
426
|
{
|
|
438
427
|
int mask = search->matches_mask;
|
|
439
428
|
int index = trailing_zeros(mask);
|
|
@@ -457,26 +446,14 @@ static inline FORCE_INLINE unsigned char sse2_next_match(search_state *search)
|
|
|
457
446
|
#define TARGET_SSE2
|
|
458
447
|
#endif
|
|
459
448
|
|
|
460
|
-
static
|
|
461
|
-
{
|
|
462
|
-
__m128i chunk = _mm_loadu_si128((__m128i const*)ptr);
|
|
463
|
-
|
|
464
|
-
// Trick: c < 32 || c == 34 can be factored as c ^ 2 < 33
|
|
465
|
-
// https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
|
|
466
|
-
__m128i too_low_or_dbl_quote = _mm_cmplt_epu8(_mm_xor_si128(chunk, _mm_set1_epi8(2)), _mm_set1_epi8(33));
|
|
467
|
-
__m128i has_backslash = _mm_cmpeq_epi8(chunk, _mm_set1_epi8('\\'));
|
|
468
|
-
__m128i needs_escape = _mm_or_si128(too_low_or_dbl_quote, has_backslash);
|
|
469
|
-
return _mm_movemask_epi8(needs_escape);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
static inline TARGET_SSE2 FORCE_INLINE unsigned char search_escape_basic_sse2(search_state *search)
|
|
449
|
+
ALWAYS_INLINE(static) TARGET_SSE2 unsigned char search_escape_basic_sse2(search_state *search)
|
|
473
450
|
{
|
|
474
451
|
if (RB_UNLIKELY(search->has_matches)) {
|
|
475
452
|
// There are more matches if search->matches_mask > 0.
|
|
476
453
|
if (search->matches_mask > 0) {
|
|
477
454
|
return sse2_next_match(search);
|
|
478
455
|
} else {
|
|
479
|
-
// sse2_next_match will only advance search->ptr up to the last matching character.
|
|
456
|
+
// sse2_next_match will only advance search->ptr up to the last matching character.
|
|
480
457
|
// Skip over any characters in the last chunk that occur after the last match.
|
|
481
458
|
search->has_matches = false;
|
|
482
459
|
if (RB_UNLIKELY(search->chunk_base + sizeof(__m128i) >= search->end)) {
|
|
@@ -487,29 +464,22 @@ static inline TARGET_SSE2 FORCE_INLINE unsigned char search_escape_basic_sse2(se
|
|
|
487
464
|
}
|
|
488
465
|
}
|
|
489
466
|
|
|
490
|
-
|
|
491
|
-
int needs_escape_mask = sse2_update(search->ptr);
|
|
492
|
-
|
|
493
|
-
if (needs_escape_mask == 0) {
|
|
494
|
-
search->ptr += sizeof(__m128i);
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
|
|
467
|
+
if (string_scan_simd_sse2(&search->ptr, search->end, &search->matches_mask)) {
|
|
498
468
|
search->has_matches = true;
|
|
499
|
-
search->matches_mask = needs_escape_mask;
|
|
500
469
|
search->chunk_base = search->ptr;
|
|
470
|
+
search->chunk_end = search->ptr + sizeof(__m128i);
|
|
501
471
|
return sse2_next_match(search);
|
|
502
472
|
}
|
|
503
473
|
|
|
504
|
-
// There are fewer than 16 bytes left.
|
|
474
|
+
// There are fewer than 16 bytes left.
|
|
505
475
|
unsigned long remaining = (search->end - search->ptr);
|
|
506
476
|
if (remaining >= SIMD_MINIMUM_THRESHOLD) {
|
|
507
477
|
char *s = copy_remaining_bytes(search, sizeof(__m128i), remaining);
|
|
508
478
|
|
|
509
|
-
int needs_escape_mask =
|
|
479
|
+
int needs_escape_mask = compute_chunk_mask_sse2(s);
|
|
510
480
|
|
|
511
481
|
if (needs_escape_mask == 0) {
|
|
512
|
-
// Nothing to escape, ensure search_flush doesn't do anything by setting
|
|
482
|
+
// Nothing to escape, ensure search_flush doesn't do anything by setting
|
|
513
483
|
// search->cursor to search->ptr.
|
|
514
484
|
fbuffer_consumed(search->buffer, remaining);
|
|
515
485
|
search->ptr = search->end;
|
|
@@ -638,7 +608,8 @@ static inline unsigned char search_ascii_only_escape(search_state *search, const
|
|
|
638
608
|
return 0;
|
|
639
609
|
}
|
|
640
610
|
|
|
641
|
-
static inline void full_escape_UTF8_char(search_state *search, unsigned char ch_len)
|
|
611
|
+
static inline void full_escape_UTF8_char(search_state *search, unsigned char ch_len)
|
|
612
|
+
{
|
|
642
613
|
const unsigned char ch = (unsigned char)*search->ptr;
|
|
643
614
|
switch (ch_len) {
|
|
644
615
|
case 1: {
|
|
@@ -668,7 +639,7 @@ static inline void full_escape_UTF8_char(search_state *search, unsigned char ch_
|
|
|
668
639
|
|
|
669
640
|
uint32_t wchar = 0;
|
|
670
641
|
|
|
671
|
-
switch(ch_len) {
|
|
642
|
+
switch (ch_len) {
|
|
672
643
|
case 2:
|
|
673
644
|
wchar = ch & 0x1F;
|
|
674
645
|
break;
|
|
@@ -723,288 +694,6 @@ static void convert_UTF8_to_ASCII_only_JSON(search_state *search, const unsigned
|
|
|
723
694
|
}
|
|
724
695
|
}
|
|
725
696
|
|
|
726
|
-
/*
|
|
727
|
-
* Document-module: JSON::Ext::Generator
|
|
728
|
-
*
|
|
729
|
-
* This is the JSON generator implemented as a C extension. It can be
|
|
730
|
-
* configured to be used by setting
|
|
731
|
-
*
|
|
732
|
-
* JSON.generator = JSON::Ext::Generator
|
|
733
|
-
*
|
|
734
|
-
* with the method generator= in JSON.
|
|
735
|
-
*
|
|
736
|
-
*/
|
|
737
|
-
|
|
738
|
-
/* Explanation of the following: that's the only way to not pollute
|
|
739
|
-
* standard library's docs with GeneratorMethods::<ClassName> which
|
|
740
|
-
* are uninformative and take a large place in a list of classes
|
|
741
|
-
*/
|
|
742
|
-
|
|
743
|
-
/*
|
|
744
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods
|
|
745
|
-
* :nodoc:
|
|
746
|
-
*/
|
|
747
|
-
|
|
748
|
-
/*
|
|
749
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods::Array
|
|
750
|
-
* :nodoc:
|
|
751
|
-
*/
|
|
752
|
-
|
|
753
|
-
/*
|
|
754
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods::Bignum
|
|
755
|
-
* :nodoc:
|
|
756
|
-
*/
|
|
757
|
-
|
|
758
|
-
/*
|
|
759
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods::FalseClass
|
|
760
|
-
* :nodoc:
|
|
761
|
-
*/
|
|
762
|
-
|
|
763
|
-
/*
|
|
764
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods::Fixnum
|
|
765
|
-
* :nodoc:
|
|
766
|
-
*/
|
|
767
|
-
|
|
768
|
-
/*
|
|
769
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods::Float
|
|
770
|
-
* :nodoc:
|
|
771
|
-
*/
|
|
772
|
-
|
|
773
|
-
/*
|
|
774
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods::Hash
|
|
775
|
-
* :nodoc:
|
|
776
|
-
*/
|
|
777
|
-
|
|
778
|
-
/*
|
|
779
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods::Integer
|
|
780
|
-
* :nodoc:
|
|
781
|
-
*/
|
|
782
|
-
|
|
783
|
-
/*
|
|
784
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods::NilClass
|
|
785
|
-
* :nodoc:
|
|
786
|
-
*/
|
|
787
|
-
|
|
788
|
-
/*
|
|
789
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods::Object
|
|
790
|
-
* :nodoc:
|
|
791
|
-
*/
|
|
792
|
-
|
|
793
|
-
/*
|
|
794
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods::String
|
|
795
|
-
* :nodoc:
|
|
796
|
-
*/
|
|
797
|
-
|
|
798
|
-
/*
|
|
799
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods::String::Extend
|
|
800
|
-
* :nodoc:
|
|
801
|
-
*/
|
|
802
|
-
|
|
803
|
-
/*
|
|
804
|
-
* Document-module: JSON::Ext::Generator::GeneratorMethods::TrueClass
|
|
805
|
-
* :nodoc:
|
|
806
|
-
*/
|
|
807
|
-
|
|
808
|
-
/*
|
|
809
|
-
* call-seq: to_json(state = nil)
|
|
810
|
-
*
|
|
811
|
-
* Returns a JSON string containing a JSON object, that is generated from
|
|
812
|
-
* this Hash instance.
|
|
813
|
-
* _state_ is a JSON::State object, that can also be used to configure the
|
|
814
|
-
* produced JSON string output further.
|
|
815
|
-
*/
|
|
816
|
-
static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self)
|
|
817
|
-
{
|
|
818
|
-
rb_check_arity(argc, 0, 1);
|
|
819
|
-
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
|
|
820
|
-
return cState_partial_generate(Vstate, self, generate_json_object, Qfalse);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
/*
|
|
824
|
-
* call-seq: to_json(state = nil)
|
|
825
|
-
*
|
|
826
|
-
* Returns a JSON string containing a JSON array, that is generated from
|
|
827
|
-
* this Array instance.
|
|
828
|
-
* _state_ is a JSON::State object, that can also be used to configure the
|
|
829
|
-
* produced JSON string output further.
|
|
830
|
-
*/
|
|
831
|
-
static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self) {
|
|
832
|
-
rb_check_arity(argc, 0, 1);
|
|
833
|
-
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
|
|
834
|
-
return cState_partial_generate(Vstate, self, generate_json_array, Qfalse);
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
#ifdef RUBY_INTEGER_UNIFICATION
|
|
838
|
-
/*
|
|
839
|
-
* call-seq: to_json(*)
|
|
840
|
-
*
|
|
841
|
-
* Returns a JSON string representation for this Integer number.
|
|
842
|
-
*/
|
|
843
|
-
static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self)
|
|
844
|
-
{
|
|
845
|
-
rb_check_arity(argc, 0, 1);
|
|
846
|
-
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
|
|
847
|
-
return cState_partial_generate(Vstate, self, generate_json_integer, Qfalse);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
#else
|
|
851
|
-
/*
|
|
852
|
-
* call-seq: to_json(*)
|
|
853
|
-
*
|
|
854
|
-
* Returns a JSON string representation for this Integer number.
|
|
855
|
-
*/
|
|
856
|
-
static VALUE mFixnum_to_json(int argc, VALUE *argv, VALUE self)
|
|
857
|
-
{
|
|
858
|
-
rb_check_arity(argc, 0, 1);
|
|
859
|
-
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
|
|
860
|
-
return cState_partial_generate(Vstate, self, generate_json_fixnum, Qfalse);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
/*
|
|
864
|
-
* call-seq: to_json(*)
|
|
865
|
-
*
|
|
866
|
-
* Returns a JSON string representation for this Integer number.
|
|
867
|
-
*/
|
|
868
|
-
static VALUE mBignum_to_json(int argc, VALUE *argv, VALUE self)
|
|
869
|
-
{
|
|
870
|
-
rb_check_arity(argc, 0, 1);
|
|
871
|
-
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
|
|
872
|
-
return cState_partial_generate(Vstate, self, generate_json_bignum, Qfalse);
|
|
873
|
-
}
|
|
874
|
-
#endif
|
|
875
|
-
|
|
876
|
-
/*
|
|
877
|
-
* call-seq: to_json(*)
|
|
878
|
-
*
|
|
879
|
-
* Returns a JSON string representation for this Float number.
|
|
880
|
-
*/
|
|
881
|
-
static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self)
|
|
882
|
-
{
|
|
883
|
-
rb_check_arity(argc, 0, 1);
|
|
884
|
-
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
|
|
885
|
-
return cState_partial_generate(Vstate, self, generate_json_float, Qfalse);
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
/*
|
|
889
|
-
* call-seq: String.included(modul)
|
|
890
|
-
*
|
|
891
|
-
* Extends _modul_ with the String::Extend module.
|
|
892
|
-
*/
|
|
893
|
-
static VALUE mString_included_s(VALUE self, VALUE modul) {
|
|
894
|
-
VALUE result = rb_funcall(modul, i_extend, 1, mString_Extend);
|
|
895
|
-
rb_call_super(1, &modul);
|
|
896
|
-
return result;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
/*
|
|
900
|
-
* call-seq: to_json(*)
|
|
901
|
-
*
|
|
902
|
-
* This string should be encoded with UTF-8 A call to this method
|
|
903
|
-
* returns a JSON string encoded with UTF16 big endian characters as
|
|
904
|
-
* \u????.
|
|
905
|
-
*/
|
|
906
|
-
static VALUE mString_to_json(int argc, VALUE *argv, VALUE self)
|
|
907
|
-
{
|
|
908
|
-
rb_check_arity(argc, 0, 1);
|
|
909
|
-
VALUE Vstate = cState_from_state_s(cState, argc == 1 ? argv[0] : Qnil);
|
|
910
|
-
return cState_partial_generate(Vstate, self, generate_json_string, Qfalse);
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
/*
|
|
914
|
-
* call-seq: to_json_raw_object()
|
|
915
|
-
*
|
|
916
|
-
* This method creates a raw object hash, that can be nested into
|
|
917
|
-
* other data structures and will be generated as a raw string. This
|
|
918
|
-
* method should be used, if you want to convert raw strings to JSON
|
|
919
|
-
* instead of UTF-8 strings, e. g. binary data.
|
|
920
|
-
*/
|
|
921
|
-
static VALUE mString_to_json_raw_object(VALUE self)
|
|
922
|
-
{
|
|
923
|
-
VALUE ary;
|
|
924
|
-
VALUE result = rb_hash_new();
|
|
925
|
-
rb_hash_aset(result, rb_funcall(mJSON, i_create_id, 0), rb_class_name(rb_obj_class(self)));
|
|
926
|
-
ary = rb_funcall(self, i_unpack, 1, rb_str_new2("C*"));
|
|
927
|
-
rb_hash_aset(result, rb_utf8_str_new_lit("raw"), ary);
|
|
928
|
-
return result;
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
/*
|
|
932
|
-
* call-seq: to_json_raw(*args)
|
|
933
|
-
*
|
|
934
|
-
* This method creates a JSON text from the result of a call to
|
|
935
|
-
* to_json_raw_object of this String.
|
|
936
|
-
*/
|
|
937
|
-
static VALUE mString_to_json_raw(int argc, VALUE *argv, VALUE self)
|
|
938
|
-
{
|
|
939
|
-
VALUE obj = mString_to_json_raw_object(self);
|
|
940
|
-
Check_Type(obj, T_HASH);
|
|
941
|
-
return mHash_to_json(argc, argv, obj);
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
/*
|
|
945
|
-
* call-seq: json_create(o)
|
|
946
|
-
*
|
|
947
|
-
* Raw Strings are JSON Objects (the raw bytes are stored in an array for the
|
|
948
|
-
* key "raw"). The Ruby String can be created by this module method.
|
|
949
|
-
*/
|
|
950
|
-
static VALUE mString_Extend_json_create(VALUE self, VALUE o)
|
|
951
|
-
{
|
|
952
|
-
VALUE ary;
|
|
953
|
-
Check_Type(o, T_HASH);
|
|
954
|
-
ary = rb_hash_aref(o, rb_str_new2("raw"));
|
|
955
|
-
return rb_funcall(ary, i_pack, 1, rb_str_new2("C*"));
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
/*
|
|
959
|
-
* call-seq: to_json(*)
|
|
960
|
-
*
|
|
961
|
-
* Returns a JSON string for true: 'true'.
|
|
962
|
-
*/
|
|
963
|
-
static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self)
|
|
964
|
-
{
|
|
965
|
-
rb_check_arity(argc, 0, 1);
|
|
966
|
-
return rb_utf8_str_new("true", 4);
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
/*
|
|
970
|
-
* call-seq: to_json(*)
|
|
971
|
-
*
|
|
972
|
-
* Returns a JSON string for false: 'false'.
|
|
973
|
-
*/
|
|
974
|
-
static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self)
|
|
975
|
-
{
|
|
976
|
-
rb_check_arity(argc, 0, 1);
|
|
977
|
-
return rb_utf8_str_new("false", 5);
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
/*
|
|
981
|
-
* call-seq: to_json(*)
|
|
982
|
-
*
|
|
983
|
-
* Returns a JSON string for nil: 'null'.
|
|
984
|
-
*/
|
|
985
|
-
static VALUE mNilClass_to_json(int argc, VALUE *argv, VALUE self)
|
|
986
|
-
{
|
|
987
|
-
rb_check_arity(argc, 0, 1);
|
|
988
|
-
return rb_utf8_str_new("null", 4);
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
/*
|
|
992
|
-
* call-seq: to_json(*)
|
|
993
|
-
*
|
|
994
|
-
* Converts this object to a string (calling #to_s), converts
|
|
995
|
-
* it to a JSON string, and returns the result. This is a fallback, if no
|
|
996
|
-
* special method #to_json was defined for some object.
|
|
997
|
-
*/
|
|
998
|
-
static VALUE mObject_to_json(int argc, VALUE *argv, VALUE self)
|
|
999
|
-
{
|
|
1000
|
-
VALUE state;
|
|
1001
|
-
VALUE string = rb_funcall(self, i_to_s, 0);
|
|
1002
|
-
rb_scan_args(argc, argv, "01", &state);
|
|
1003
|
-
Check_Type(string, T_STRING);
|
|
1004
|
-
state = cState_from_state_s(cState, state);
|
|
1005
|
-
return cState_partial_generate(state, string, generate_json_string, Qfalse);
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
697
|
static void State_mark(void *ptr)
|
|
1009
698
|
{
|
|
1010
699
|
JSON_Generator_State *state = ptr;
|
|
@@ -1014,6 +703,7 @@ static void State_mark(void *ptr)
|
|
|
1014
703
|
rb_gc_mark_movable(state->object_nl);
|
|
1015
704
|
rb_gc_mark_movable(state->array_nl);
|
|
1016
705
|
rb_gc_mark_movable(state->as_json);
|
|
706
|
+
rb_gc_mark_movable(state->sort_keys);
|
|
1017
707
|
}
|
|
1018
708
|
|
|
1019
709
|
static void State_compact(void *ptr)
|
|
@@ -1025,34 +715,27 @@ static void State_compact(void *ptr)
|
|
|
1025
715
|
state->object_nl = rb_gc_location(state->object_nl);
|
|
1026
716
|
state->array_nl = rb_gc_location(state->array_nl);
|
|
1027
717
|
state->as_json = rb_gc_location(state->as_json);
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
static void State_free(void *ptr)
|
|
1031
|
-
{
|
|
1032
|
-
JSON_Generator_State *state = ptr;
|
|
1033
|
-
ruby_xfree(state);
|
|
718
|
+
state->sort_keys = rb_gc_location(state->sort_keys);
|
|
1034
719
|
}
|
|
1035
720
|
|
|
1036
721
|
static size_t State_memsize(const void *ptr)
|
|
1037
722
|
{
|
|
723
|
+
#ifdef HAVE_RUBY_TYPED_EMBEDDABLE
|
|
724
|
+
return 0;
|
|
725
|
+
#else
|
|
1038
726
|
return sizeof(JSON_Generator_State);
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
#ifndef HAVE_RB_EXT_RACTOR_SAFE
|
|
1042
|
-
# undef RUBY_TYPED_FROZEN_SHAREABLE
|
|
1043
|
-
# define RUBY_TYPED_FROZEN_SHAREABLE 0
|
|
1044
727
|
#endif
|
|
728
|
+
}
|
|
1045
729
|
|
|
1046
730
|
static const rb_data_type_t JSON_Generator_State_type = {
|
|
1047
|
-
"JSON/Generator/State",
|
|
1048
|
-
{
|
|
731
|
+
.wrap_struct_name = "JSON/Generator/State",
|
|
732
|
+
.function = {
|
|
1049
733
|
.dmark = State_mark,
|
|
1050
|
-
.dfree =
|
|
734
|
+
.dfree = RUBY_DEFAULT_FREE,
|
|
1051
735
|
.dsize = State_memsize,
|
|
1052
736
|
.dcompact = State_compact,
|
|
1053
737
|
},
|
|
1054
|
-
|
|
1055
|
-
RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE,
|
|
738
|
+
.flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE,
|
|
1056
739
|
};
|
|
1057
740
|
|
|
1058
741
|
static void state_init(JSON_Generator_State *state)
|
|
@@ -1082,20 +765,27 @@ static void vstate_spill(struct generate_json_data *data)
|
|
|
1082
765
|
RB_OBJ_WRITTEN(vstate, Qundef, state->object_nl);
|
|
1083
766
|
RB_OBJ_WRITTEN(vstate, Qundef, state->array_nl);
|
|
1084
767
|
RB_OBJ_WRITTEN(vstate, Qundef, state->as_json);
|
|
768
|
+
RB_OBJ_WRITTEN(vstate, Qundef, state->sort_keys);
|
|
1085
769
|
}
|
|
1086
770
|
|
|
1087
|
-
static inline VALUE
|
|
771
|
+
static inline VALUE json_call_to_json(struct generate_json_data *data, VALUE obj)
|
|
1088
772
|
{
|
|
1089
773
|
if (RB_UNLIKELY(!data->vstate)) {
|
|
1090
774
|
vstate_spill(data);
|
|
1091
775
|
}
|
|
1092
|
-
|
|
776
|
+
GET_STATE(data->vstate);
|
|
777
|
+
state->depth = data->depth;
|
|
778
|
+
VALUE tmp = rb_funcall(obj, i_to_json, 1, data->vstate);
|
|
779
|
+
// no need to restore state->depth, vstate is just a temporary State
|
|
780
|
+
return tmp;
|
|
1093
781
|
}
|
|
1094
782
|
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
};
|
|
783
|
+
static VALUE
|
|
784
|
+
json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key)
|
|
785
|
+
{
|
|
786
|
+
VALUE proc_args[2] = {object, is_key};
|
|
787
|
+
return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil);
|
|
788
|
+
}
|
|
1099
789
|
|
|
1100
790
|
static VALUE
|
|
1101
791
|
convert_string_subclass(VALUE key)
|
|
@@ -1112,6 +802,158 @@ convert_string_subclass(VALUE key)
|
|
|
1112
802
|
return key_to_s;
|
|
1113
803
|
}
|
|
1114
804
|
|
|
805
|
+
static bool enc_utf8_compatible_p(int enc_idx)
|
|
806
|
+
{
|
|
807
|
+
if (enc_idx == usascii_encindex) return true;
|
|
808
|
+
if (enc_idx == utf8_encindex) return true;
|
|
809
|
+
return false;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
static VALUE encode_json_string_try(VALUE str)
|
|
813
|
+
{
|
|
814
|
+
return rb_funcall(str, i_encode, 1, Encoding_UTF_8);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
|
|
818
|
+
{
|
|
819
|
+
raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0));
|
|
820
|
+
return Qundef;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
static inline int json_str_coderange(VALUE str) {
|
|
824
|
+
int coderange = RB_ENC_CODERANGE(str);
|
|
825
|
+
if (coderange == RUBY_ENC_CODERANGE_UNKNOWN) {
|
|
826
|
+
coderange = rb_enc_str_coderange(str);
|
|
827
|
+
}
|
|
828
|
+
return coderange;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
static inline bool valid_json_string_p(VALUE str)
|
|
832
|
+
{
|
|
833
|
+
int coderange = json_str_coderange(str);
|
|
834
|
+
|
|
835
|
+
if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) {
|
|
836
|
+
return true;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
if (RB_LIKELY(coderange == ENC_CODERANGE_VALID)) {
|
|
840
|
+
return enc_utf8_compatible_p(RB_ENCODING_GET_INLINED(str));
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
return false;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
NOINLINE(static) VALUE convert_invalid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
|
|
847
|
+
{
|
|
848
|
+
if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) {
|
|
849
|
+
VALUE coerced_str = json_call_as_json(data->state, str, Qfalse);
|
|
850
|
+
if (coerced_str != str) {
|
|
851
|
+
if (RB_TYPE_P(coerced_str, T_STRING)) {
|
|
852
|
+
if (!valid_json_string_p(coerced_str)) {
|
|
853
|
+
raise_generator_error(str, "source sequence is illegal/malformed utf-8");
|
|
854
|
+
}
|
|
855
|
+
} else {
|
|
856
|
+
// as_json could return another type than T_STRING
|
|
857
|
+
if (is_key) {
|
|
858
|
+
raise_generator_error(coerced_str, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(coerced_str));
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
return coerced_str;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
if (RB_ENCODING_GET_INLINED(str) == binary_encindex) {
|
|
867
|
+
VALUE utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex);
|
|
868
|
+
switch (rb_enc_str_coderange(utf8_string)) {
|
|
869
|
+
case ENC_CODERANGE_7BIT:
|
|
870
|
+
return utf8_string;
|
|
871
|
+
case ENC_CODERANGE_VALID:
|
|
872
|
+
// For historical reason, we silently reinterpret binary strings as UTF-8 if it would work.
|
|
873
|
+
// TODO: Raise in 3.0.0
|
|
874
|
+
rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0");
|
|
875
|
+
return utf8_string;
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
ALWAYS_INLINE(static) VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key)
|
|
884
|
+
{
|
|
885
|
+
if (RB_LIKELY(valid_json_string_p(str))) {
|
|
886
|
+
return str;
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
return convert_invalid_encoding(data, str, as_json_called, is_key);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
894
|
+
{
|
|
895
|
+
fbuffer_append_char(buffer, '"');
|
|
896
|
+
|
|
897
|
+
long len;
|
|
898
|
+
search_state search;
|
|
899
|
+
search.buffer = buffer;
|
|
900
|
+
RSTRING_GETMEM(obj, search.ptr, len);
|
|
901
|
+
search.cursor = search.ptr;
|
|
902
|
+
search.end = search.ptr + len;
|
|
903
|
+
|
|
904
|
+
#ifdef HAVE_SIMD
|
|
905
|
+
search.matches_mask = 0;
|
|
906
|
+
search.has_matches = false;
|
|
907
|
+
search.chunk_base = NULL;
|
|
908
|
+
search.chunk_end = NULL;
|
|
909
|
+
#endif /* HAVE_SIMD */
|
|
910
|
+
|
|
911
|
+
switch (json_str_coderange(obj)) {
|
|
912
|
+
case ENC_CODERANGE_7BIT:
|
|
913
|
+
case ENC_CODERANGE_VALID:
|
|
914
|
+
if (RB_UNLIKELY(data->state->ascii_only)) {
|
|
915
|
+
convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table);
|
|
916
|
+
} else if (RB_UNLIKELY(data->state->script_safe)) {
|
|
917
|
+
convert_UTF8_to_script_safe_JSON(&search);
|
|
918
|
+
} else {
|
|
919
|
+
convert_UTF8_to_JSON(&search);
|
|
920
|
+
}
|
|
921
|
+
break;
|
|
922
|
+
default:
|
|
923
|
+
raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
|
|
924
|
+
break;
|
|
925
|
+
}
|
|
926
|
+
fbuffer_append_char(buffer, '"');
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
930
|
+
{
|
|
931
|
+
obj = ensure_valid_encoding(data, obj, false, false);
|
|
932
|
+
raw_generate_json_string(buffer, data, obj);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
struct hash_foreach_arg {
|
|
936
|
+
VALUE hash;
|
|
937
|
+
struct generate_json_data *data;
|
|
938
|
+
int first_key_type;
|
|
939
|
+
bool first;
|
|
940
|
+
bool mixed_keys_encountered;
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
NOINLINE(static) void
|
|
944
|
+
json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg)
|
|
945
|
+
{
|
|
946
|
+
if (arg->mixed_keys_encountered) {
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
arg->mixed_keys_encountered = true;
|
|
950
|
+
|
|
951
|
+
JSON_Generator_State *state = arg->data->state;
|
|
952
|
+
if (!state->allow_duplicate_key) {
|
|
953
|
+
rb_funcall(mJSON, rb_intern("on_mixed_keys_hash"), 1, arg->hash);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
1115
957
|
static int
|
|
1116
958
|
json_object_i(VALUE key, VALUE val, VALUE _arg)
|
|
1117
959
|
{
|
|
@@ -1121,22 +963,34 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
|
|
|
1121
963
|
FBuffer *buffer = data->buffer;
|
|
1122
964
|
JSON_Generator_State *state = data->state;
|
|
1123
965
|
|
|
1124
|
-
long depth =
|
|
1125
|
-
int
|
|
966
|
+
long depth = data->depth;
|
|
967
|
+
int key_type = rb_type(key);
|
|
968
|
+
|
|
969
|
+
if (arg->first) {
|
|
970
|
+
arg->first = false;
|
|
971
|
+
arg->first_key_type = key_type;
|
|
972
|
+
}
|
|
973
|
+
else {
|
|
974
|
+
fbuffer_append_char(buffer, ',');
|
|
975
|
+
}
|
|
1126
976
|
|
|
1127
|
-
if (arg->iter > 0) fbuffer_append_char(buffer, ',');
|
|
1128
977
|
if (RB_UNLIKELY(data->state->object_nl)) {
|
|
1129
978
|
fbuffer_append_str(buffer, data->state->object_nl);
|
|
1130
979
|
}
|
|
1131
980
|
if (RB_UNLIKELY(data->state->indent)) {
|
|
1132
|
-
|
|
1133
|
-
fbuffer_append_str(buffer, data->state->indent);
|
|
1134
|
-
}
|
|
981
|
+
fbuffer_append_str_repeat(buffer, data->state->indent, depth);
|
|
1135
982
|
}
|
|
1136
983
|
|
|
1137
984
|
VALUE key_to_s;
|
|
1138
|
-
|
|
985
|
+
bool as_json_called = false;
|
|
986
|
+
|
|
987
|
+
start:
|
|
988
|
+
switch (key_type) {
|
|
1139
989
|
case T_STRING:
|
|
990
|
+
if (RB_UNLIKELY(arg->first_key_type != T_STRING)) {
|
|
991
|
+
json_inspect_hash_with_mixed_keys(arg);
|
|
992
|
+
}
|
|
993
|
+
|
|
1140
994
|
if (RB_LIKELY(RBASIC_CLASS(key) == rb_cString)) {
|
|
1141
995
|
key_to_s = key;
|
|
1142
996
|
} else {
|
|
@@ -1144,15 +998,31 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
|
|
|
1144
998
|
}
|
|
1145
999
|
break;
|
|
1146
1000
|
case T_SYMBOL:
|
|
1001
|
+
if (RB_UNLIKELY(arg->first_key_type != T_SYMBOL)) {
|
|
1002
|
+
json_inspect_hash_with_mixed_keys(arg);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1147
1005
|
key_to_s = rb_sym2str(key);
|
|
1148
1006
|
break;
|
|
1149
1007
|
default:
|
|
1008
|
+
if (data->state->strict) {
|
|
1009
|
+
if (RTEST(data->state->as_json) && !as_json_called) {
|
|
1010
|
+
key = json_call_as_json(data->state, key, Qtrue);
|
|
1011
|
+
key_type = rb_type(key);
|
|
1012
|
+
as_json_called = true;
|
|
1013
|
+
goto start;
|
|
1014
|
+
} else {
|
|
1015
|
+
raise_generator_error(key, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(key));
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1150
1018
|
key_to_s = rb_convert_type(key, T_STRING, "String", "to_s");
|
|
1151
1019
|
break;
|
|
1152
1020
|
}
|
|
1153
1021
|
|
|
1022
|
+
key_to_s = ensure_valid_encoding(data, key_to_s, as_json_called, true);
|
|
1023
|
+
|
|
1154
1024
|
if (RB_LIKELY(RBASIC_CLASS(key_to_s) == rb_cString)) {
|
|
1155
|
-
|
|
1025
|
+
raw_generate_json_string(buffer, data, key_to_s);
|
|
1156
1026
|
} else {
|
|
1157
1027
|
generate_json(buffer, data, key_to_s);
|
|
1158
1028
|
}
|
|
@@ -1161,46 +1031,48 @@ json_object_i(VALUE key, VALUE val, VALUE _arg)
|
|
|
1161
1031
|
if (RB_UNLIKELY(state->space)) fbuffer_append_str(buffer, data->state->space);
|
|
1162
1032
|
generate_json(buffer, data, val);
|
|
1163
1033
|
|
|
1164
|
-
arg->iter++;
|
|
1165
1034
|
return ST_CONTINUE;
|
|
1166
1035
|
}
|
|
1167
1036
|
|
|
1168
1037
|
static inline long increase_depth(struct generate_json_data *data)
|
|
1169
1038
|
{
|
|
1170
1039
|
JSON_Generator_State *state = data->state;
|
|
1171
|
-
long depth = ++
|
|
1040
|
+
long depth = ++data->depth;
|
|
1172
1041
|
if (RB_UNLIKELY(depth > state->max_nesting && state->max_nesting)) {
|
|
1173
|
-
rb_raise(eNestingError, "nesting of %ld is too deep", --
|
|
1042
|
+
rb_raise(eNestingError, "nesting of %ld is too deep. Did you try to serialize objects with circular references?", --data->depth);
|
|
1174
1043
|
}
|
|
1175
1044
|
return depth;
|
|
1176
1045
|
}
|
|
1177
1046
|
|
|
1178
1047
|
static void generate_json_object(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1179
1048
|
{
|
|
1180
|
-
|
|
1049
|
+
if (RB_UNLIKELY(data->state->sort_keys)) {
|
|
1050
|
+
obj = rb_proc_call_with_block(data->state->sort_keys, 1, &obj, Qnil);
|
|
1051
|
+
Check_Type(obj, T_HASH);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1181
1054
|
long depth = increase_depth(data);
|
|
1182
1055
|
|
|
1183
1056
|
if (RHASH_SIZE(obj) == 0) {
|
|
1184
1057
|
fbuffer_append(buffer, "{}", 2);
|
|
1185
|
-
--data->
|
|
1058
|
+
--data->depth;
|
|
1186
1059
|
return;
|
|
1187
1060
|
}
|
|
1188
1061
|
|
|
1189
1062
|
fbuffer_append_char(buffer, '{');
|
|
1190
1063
|
|
|
1191
1064
|
struct hash_foreach_arg arg = {
|
|
1065
|
+
.hash = obj,
|
|
1192
1066
|
.data = data,
|
|
1193
|
-
.
|
|
1067
|
+
.first = true,
|
|
1194
1068
|
};
|
|
1195
1069
|
rb_hash_foreach(obj, json_object_i, (VALUE)&arg);
|
|
1196
1070
|
|
|
1197
|
-
depth = --data->
|
|
1071
|
+
depth = --data->depth;
|
|
1198
1072
|
if (RB_UNLIKELY(data->state->object_nl)) {
|
|
1199
1073
|
fbuffer_append_str(buffer, data->state->object_nl);
|
|
1200
1074
|
if (RB_UNLIKELY(data->state->indent)) {
|
|
1201
|
-
|
|
1202
|
-
fbuffer_append_str(buffer, data->state->indent);
|
|
1203
|
-
}
|
|
1075
|
+
fbuffer_append_str_repeat(buffer, data->state->indent, depth);
|
|
1204
1076
|
}
|
|
1205
1077
|
}
|
|
1206
1078
|
fbuffer_append_char(buffer, '}');
|
|
@@ -1208,125 +1080,41 @@ static void generate_json_object(FBuffer *buffer, struct generate_json_data *dat
|
|
|
1208
1080
|
|
|
1209
1081
|
static void generate_json_array(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1210
1082
|
{
|
|
1211
|
-
int i, j;
|
|
1212
1083
|
long depth = increase_depth(data);
|
|
1213
1084
|
|
|
1214
1085
|
if (RARRAY_LEN(obj) == 0) {
|
|
1215
1086
|
fbuffer_append(buffer, "[]", 2);
|
|
1216
|
-
--data->
|
|
1087
|
+
--data->depth;
|
|
1217
1088
|
return;
|
|
1218
1089
|
}
|
|
1219
1090
|
|
|
1220
1091
|
fbuffer_append_char(buffer, '[');
|
|
1221
1092
|
if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl);
|
|
1222
|
-
for(i = 0; i < RARRAY_LEN(obj); i++) {
|
|
1093
|
+
for (int i = 0; i < RARRAY_LEN(obj); i++) {
|
|
1223
1094
|
if (i > 0) {
|
|
1224
1095
|
fbuffer_append_char(buffer, ',');
|
|
1225
1096
|
if (RB_UNLIKELY(data->state->array_nl)) fbuffer_append_str(buffer, data->state->array_nl);
|
|
1226
1097
|
}
|
|
1227
1098
|
if (RB_UNLIKELY(data->state->indent)) {
|
|
1228
|
-
|
|
1229
|
-
fbuffer_append_str(buffer, data->state->indent);
|
|
1230
|
-
}
|
|
1099
|
+
fbuffer_append_str_repeat(buffer, data->state->indent, depth);
|
|
1231
1100
|
}
|
|
1232
1101
|
generate_json(buffer, data, RARRAY_AREF(obj, i));
|
|
1233
1102
|
}
|
|
1234
|
-
data->
|
|
1103
|
+
data->depth = --depth;
|
|
1235
1104
|
if (RB_UNLIKELY(data->state->array_nl)) {
|
|
1236
1105
|
fbuffer_append_str(buffer, data->state->array_nl);
|
|
1237
1106
|
if (RB_UNLIKELY(data->state->indent)) {
|
|
1238
|
-
|
|
1239
|
-
fbuffer_append_str(buffer, data->state->indent);
|
|
1240
|
-
}
|
|
1107
|
+
fbuffer_append_str_repeat(buffer, data->state->indent, depth);
|
|
1241
1108
|
}
|
|
1242
1109
|
}
|
|
1243
1110
|
fbuffer_append_char(buffer, ']');
|
|
1244
1111
|
}
|
|
1245
1112
|
|
|
1246
|
-
static inline int enc_utf8_compatible_p(int enc_idx)
|
|
1247
|
-
{
|
|
1248
|
-
if (enc_idx == usascii_encindex) return 1;
|
|
1249
|
-
if (enc_idx == utf8_encindex) return 1;
|
|
1250
|
-
return 0;
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
static VALUE encode_json_string_try(VALUE str)
|
|
1254
|
-
{
|
|
1255
|
-
return rb_funcall(str, i_encode, 1, Encoding_UTF_8);
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
static VALUE encode_json_string_rescue(VALUE str, VALUE exception)
|
|
1259
|
-
{
|
|
1260
|
-
raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0));
|
|
1261
|
-
return Qundef;
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
static inline VALUE ensure_valid_encoding(VALUE str)
|
|
1265
|
-
{
|
|
1266
|
-
int encindex = RB_ENCODING_GET(str);
|
|
1267
|
-
VALUE utf8_string;
|
|
1268
|
-
if (RB_UNLIKELY(!enc_utf8_compatible_p(encindex))) {
|
|
1269
|
-
if (encindex == binary_encindex) {
|
|
1270
|
-
utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex);
|
|
1271
|
-
switch (rb_enc_str_coderange(utf8_string)) {
|
|
1272
|
-
case ENC_CODERANGE_7BIT:
|
|
1273
|
-
return utf8_string;
|
|
1274
|
-
case ENC_CODERANGE_VALID:
|
|
1275
|
-
// For historical reason, we silently reinterpret binary strings as UTF-8 if it would work.
|
|
1276
|
-
// TODO: Raise in 3.0.0
|
|
1277
|
-
rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0");
|
|
1278
|
-
return utf8_string;
|
|
1279
|
-
break;
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
str = rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str);
|
|
1284
|
-
}
|
|
1285
|
-
return str;
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1289
|
-
{
|
|
1290
|
-
obj = ensure_valid_encoding(obj);
|
|
1291
|
-
|
|
1292
|
-
fbuffer_append_char(buffer, '"');
|
|
1293
|
-
|
|
1294
|
-
long len;
|
|
1295
|
-
search_state search;
|
|
1296
|
-
search.buffer = buffer;
|
|
1297
|
-
RSTRING_GETMEM(obj, search.ptr, len);
|
|
1298
|
-
search.cursor = search.ptr;
|
|
1299
|
-
search.end = search.ptr + len;
|
|
1300
|
-
|
|
1301
|
-
#ifdef HAVE_SIMD
|
|
1302
|
-
search.matches_mask = 0;
|
|
1303
|
-
search.has_matches = false;
|
|
1304
|
-
search.chunk_base = NULL;
|
|
1305
|
-
#endif /* HAVE_SIMD */
|
|
1306
|
-
|
|
1307
|
-
switch(rb_enc_str_coderange(obj)) {
|
|
1308
|
-
case ENC_CODERANGE_7BIT:
|
|
1309
|
-
case ENC_CODERANGE_VALID:
|
|
1310
|
-
if (RB_UNLIKELY(data->state->ascii_only)) {
|
|
1311
|
-
convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table);
|
|
1312
|
-
} else if (RB_UNLIKELY(data->state->script_safe)) {
|
|
1313
|
-
convert_UTF8_to_script_safe_JSON(&search);
|
|
1314
|
-
} else {
|
|
1315
|
-
convert_UTF8_to_JSON(&search);
|
|
1316
|
-
}
|
|
1317
|
-
break;
|
|
1318
|
-
default:
|
|
1319
|
-
raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
|
|
1320
|
-
break;
|
|
1321
|
-
}
|
|
1322
|
-
fbuffer_append_char(buffer, '"');
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
1113
|
static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1326
1114
|
{
|
|
1327
1115
|
VALUE tmp;
|
|
1328
1116
|
if (rb_respond_to(obj, i_to_json)) {
|
|
1329
|
-
tmp =
|
|
1117
|
+
tmp = json_call_to_json(data, obj);
|
|
1330
1118
|
Check_Type(tmp, T_STRING);
|
|
1331
1119
|
fbuffer_append_str(buffer, tmp);
|
|
1332
1120
|
} else {
|
|
@@ -1336,15 +1124,6 @@ static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *d
|
|
|
1336
1124
|
}
|
|
1337
1125
|
}
|
|
1338
1126
|
|
|
1339
|
-
static inline void generate_json_symbol(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1340
|
-
{
|
|
1341
|
-
if (data->state->strict) {
|
|
1342
|
-
generate_json_string(buffer, data, rb_sym2str(obj));
|
|
1343
|
-
} else {
|
|
1344
|
-
generate_json_fallback(buffer, data, obj);
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
1127
|
static void generate_json_null(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1349
1128
|
{
|
|
1350
1129
|
fbuffer_append(buffer, "null", 4);
|
|
@@ -1368,18 +1147,8 @@ static void generate_json_fixnum(FBuffer *buffer, struct generate_json_data *dat
|
|
|
1368
1147
|
static void generate_json_bignum(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1369
1148
|
{
|
|
1370
1149
|
VALUE tmp = rb_funcall(obj, i_to_s, 0);
|
|
1371
|
-
fbuffer_append_str(buffer, tmp);
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
#ifdef RUBY_INTEGER_UNIFICATION
|
|
1375
|
-
static void generate_json_integer(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1376
|
-
{
|
|
1377
|
-
if (FIXNUM_P(obj))
|
|
1378
|
-
generate_json_fixnum(buffer, data, obj);
|
|
1379
|
-
else
|
|
1380
|
-
generate_json_bignum(buffer, data, obj);
|
|
1150
|
+
fbuffer_append_str(buffer, StringValue(tmp));
|
|
1381
1151
|
}
|
|
1382
|
-
#endif
|
|
1383
1152
|
|
|
1384
1153
|
static void generate_json_float(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1385
1154
|
{
|
|
@@ -1389,11 +1158,11 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
|
|
|
1389
1158
|
/* for NaN and Infinity values we either raise an error or rely on Float#to_s. */
|
|
1390
1159
|
if (!allow_nan) {
|
|
1391
1160
|
if (data->state->strict && data->state->as_json) {
|
|
1392
|
-
VALUE casted_obj =
|
|
1161
|
+
VALUE casted_obj = json_call_as_json(data->state, obj, Qfalse);
|
|
1393
1162
|
if (casted_obj != obj) {
|
|
1394
1163
|
increase_depth(data);
|
|
1395
1164
|
generate_json(buffer, data, casted_obj);
|
|
1396
|
-
data->
|
|
1165
|
+
data->depth--;
|
|
1397
1166
|
return;
|
|
1398
1167
|
}
|
|
1399
1168
|
}
|
|
@@ -1406,12 +1175,11 @@ static void generate_json_float(FBuffer *buffer, struct generate_json_data *data
|
|
|
1406
1175
|
}
|
|
1407
1176
|
|
|
1408
1177
|
/* This implementation writes directly into the buffer. We reserve
|
|
1409
|
-
* the
|
|
1178
|
+
* the 32 characters that fpconv_dtoa states as its maximum.
|
|
1410
1179
|
*/
|
|
1411
|
-
fbuffer_inc_capa(buffer,
|
|
1180
|
+
fbuffer_inc_capa(buffer, 32);
|
|
1412
1181
|
char* d = buffer->ptr + buffer->len;
|
|
1413
1182
|
int len = fpconv_dtoa(value, d);
|
|
1414
|
-
|
|
1415
1183
|
/* fpconv_dtoa converts a float to its shortest string representation,
|
|
1416
1184
|
* but it adds a ".0" if this is a plain integer.
|
|
1417
1185
|
*/
|
|
@@ -1425,7 +1193,7 @@ static void generate_json_fragment(FBuffer *buffer, struct generate_json_data *d
|
|
|
1425
1193
|
fbuffer_append_str(buffer, fragment);
|
|
1426
1194
|
}
|
|
1427
1195
|
|
|
1428
|
-
static void
|
|
1196
|
+
static inline void generate_json_general(FBuffer *buffer, struct generate_json_data *data, VALUE obj, bool fallback)
|
|
1429
1197
|
{
|
|
1430
1198
|
bool as_json_called = false;
|
|
1431
1199
|
start:
|
|
@@ -1441,7 +1209,13 @@ start:
|
|
|
1441
1209
|
} else if (RB_FLONUM_P(obj)) {
|
|
1442
1210
|
generate_json_float(buffer, data, obj);
|
|
1443
1211
|
} else if (RB_STATIC_SYM_P(obj)) {
|
|
1444
|
-
|
|
1212
|
+
if (data->state->strict) {
|
|
1213
|
+
obj = rb_sym2str(obj);
|
|
1214
|
+
JSON_ASSERT(RBASIC_CLASS(obj) == rb_cString);
|
|
1215
|
+
goto generate_string;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
generate_json_fallback(buffer, data, obj);
|
|
1445
1219
|
} else {
|
|
1446
1220
|
goto general;
|
|
1447
1221
|
}
|
|
@@ -1452,22 +1226,38 @@ start:
|
|
|
1452
1226
|
generate_json_bignum(buffer, data, obj);
|
|
1453
1227
|
break;
|
|
1454
1228
|
case T_HASH:
|
|
1455
|
-
if (klass != rb_cHash) goto general;
|
|
1229
|
+
if (fallback && klass != rb_cHash) goto general;
|
|
1456
1230
|
generate_json_object(buffer, data, obj);
|
|
1457
1231
|
break;
|
|
1458
1232
|
case T_ARRAY:
|
|
1459
|
-
if (klass != rb_cArray) goto general;
|
|
1233
|
+
if (fallback && klass != rb_cArray) goto general;
|
|
1460
1234
|
generate_json_array(buffer, data, obj);
|
|
1461
1235
|
break;
|
|
1462
1236
|
case T_STRING:
|
|
1463
|
-
if (klass != rb_cString) goto general;
|
|
1464
|
-
|
|
1237
|
+
if (fallback && klass != rb_cString) goto general;
|
|
1238
|
+
|
|
1239
|
+
generate_string:
|
|
1240
|
+
if (RB_LIKELY(valid_json_string_p(obj))) {
|
|
1241
|
+
raw_generate_json_string(buffer, data, obj);
|
|
1242
|
+
} else if (as_json_called) {
|
|
1243
|
+
raise_generator_error(obj, "source sequence is illegal/malformed utf-8");
|
|
1244
|
+
} else {
|
|
1245
|
+
obj = ensure_valid_encoding(data, obj, false, false);
|
|
1246
|
+
as_json_called = true;
|
|
1247
|
+
goto start;
|
|
1248
|
+
}
|
|
1465
1249
|
break;
|
|
1466
1250
|
case T_SYMBOL:
|
|
1467
|
-
|
|
1251
|
+
if (data->state->strict) {
|
|
1252
|
+
obj = rb_sym2str(obj);
|
|
1253
|
+
JSON_ASSERT(RBASIC_CLASS(obj) == rb_cString);
|
|
1254
|
+
goto generate_string;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
generate_json_fallback(buffer, data, obj);
|
|
1468
1258
|
break;
|
|
1469
1259
|
case T_FLOAT:
|
|
1470
|
-
if (klass != rb_cFloat) goto general;
|
|
1260
|
+
if (fallback && klass != rb_cFloat) goto general;
|
|
1471
1261
|
generate_json_float(buffer, data, obj);
|
|
1472
1262
|
break;
|
|
1473
1263
|
case T_STRUCT:
|
|
@@ -1478,7 +1268,7 @@ start:
|
|
|
1478
1268
|
general:
|
|
1479
1269
|
if (data->state->strict) {
|
|
1480
1270
|
if (RTEST(data->state->as_json) && !as_json_called) {
|
|
1481
|
-
obj =
|
|
1271
|
+
obj = json_call_as_json(data->state, obj, Qfalse);
|
|
1482
1272
|
as_json_called = true;
|
|
1483
1273
|
goto start;
|
|
1484
1274
|
} else {
|
|
@@ -1491,45 +1281,50 @@ start:
|
|
|
1491
1281
|
}
|
|
1492
1282
|
}
|
|
1493
1283
|
|
|
1284
|
+
static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1285
|
+
{
|
|
1286
|
+
generate_json_general(buffer, data, obj, true);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
static void generate_json_no_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj)
|
|
1290
|
+
{
|
|
1291
|
+
generate_json_general(buffer, data, obj, false);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1494
1294
|
static VALUE generate_json_try(VALUE d)
|
|
1495
1295
|
{
|
|
1496
1296
|
struct generate_json_data *data = (struct generate_json_data *)d;
|
|
1497
1297
|
|
|
1498
1298
|
data->func(data->buffer, data, data->obj);
|
|
1499
1299
|
|
|
1500
|
-
return
|
|
1300
|
+
return fbuffer_finalize(data->buffer);
|
|
1501
1301
|
}
|
|
1502
1302
|
|
|
1503
|
-
static VALUE
|
|
1303
|
+
static VALUE generate_json_ensure(VALUE d)
|
|
1504
1304
|
{
|
|
1505
1305
|
struct generate_json_data *data = (struct generate_json_data *)d;
|
|
1506
1306
|
fbuffer_free(data->buffer);
|
|
1507
1307
|
|
|
1508
|
-
rb_exc_raise(exc);
|
|
1509
|
-
|
|
1510
1308
|
return Qundef;
|
|
1511
1309
|
}
|
|
1512
1310
|
|
|
1513
|
-
static VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, VALUE io)
|
|
1311
|
+
static inline VALUE cState_partial_generate(VALUE self, VALUE obj, generator_func func, VALUE io)
|
|
1514
1312
|
{
|
|
1515
1313
|
GET_STATE(self);
|
|
1516
1314
|
|
|
1517
1315
|
char stack_buffer[FBUFFER_STACK_SIZE];
|
|
1518
|
-
FBuffer buffer = {
|
|
1519
|
-
|
|
1520
|
-
};
|
|
1521
|
-
fbuffer_stack_init(&buffer, state->buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE);
|
|
1316
|
+
FBuffer buffer = { 0 };
|
|
1317
|
+
fbuffer_init(&buffer, state->buffer_initial_length, io, stack_buffer, FBUFFER_STACK_SIZE);
|
|
1522
1318
|
|
|
1523
1319
|
struct generate_json_data data = {
|
|
1524
1320
|
.buffer = &buffer,
|
|
1525
|
-
.vstate = self
|
|
1321
|
+
.vstate = Qfalse, // don't use self as it may be frozen and its depth is mutated when calling to_json
|
|
1526
1322
|
.state = state,
|
|
1323
|
+
.depth = state->depth,
|
|
1527
1324
|
.obj = obj,
|
|
1528
1325
|
.func = func
|
|
1529
1326
|
};
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
return fbuffer_finalize(&buffer);
|
|
1327
|
+
return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
|
|
1533
1328
|
}
|
|
1534
1329
|
|
|
1535
1330
|
/* call-seq:
|
|
@@ -1545,10 +1340,16 @@ static VALUE cState_generate(int argc, VALUE *argv, VALUE self)
|
|
|
1545
1340
|
rb_check_arity(argc, 1, 2);
|
|
1546
1341
|
VALUE obj = argv[0];
|
|
1547
1342
|
VALUE io = argc > 1 ? argv[1] : Qnil;
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1343
|
+
return cState_partial_generate(self, obj, generate_json, io);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/* :nodoc: */
|
|
1347
|
+
static VALUE cState_generate_no_fallback(int argc, VALUE *argv, VALUE self)
|
|
1348
|
+
{
|
|
1349
|
+
rb_check_arity(argc, 1, 2);
|
|
1350
|
+
VALUE obj = argv[0];
|
|
1351
|
+
VALUE io = argc > 1 ? argv[1] : Qnil;
|
|
1352
|
+
return cState_partial_generate(self, obj, generate_json_no_fallback, io);
|
|
1552
1353
|
}
|
|
1553
1354
|
|
|
1554
1355
|
static VALUE cState_initialize(int argc, VALUE *argv, VALUE self)
|
|
@@ -1573,12 +1374,15 @@ static VALUE cState_init_copy(VALUE obj, VALUE orig)
|
|
|
1573
1374
|
if (!objState) rb_raise(rb_eArgError, "unallocated JSON::State");
|
|
1574
1375
|
|
|
1575
1376
|
MEMCPY(objState, origState, JSON_Generator_State, 1);
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1377
|
+
|
|
1378
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->indent);
|
|
1379
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->space);
|
|
1380
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->space_before);
|
|
1381
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->object_nl);
|
|
1382
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->array_nl);
|
|
1383
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->as_json);
|
|
1384
|
+
RB_OBJ_WRITTEN(obj, Qundef, objState->sort_keys);
|
|
1385
|
+
|
|
1582
1386
|
return obj;
|
|
1583
1387
|
}
|
|
1584
1388
|
|
|
@@ -1629,6 +1433,7 @@ static VALUE string_config(VALUE config)
|
|
|
1629
1433
|
*/
|
|
1630
1434
|
static VALUE cState_indent_set(VALUE self, VALUE indent)
|
|
1631
1435
|
{
|
|
1436
|
+
rb_check_frozen(self);
|
|
1632
1437
|
GET_STATE(self);
|
|
1633
1438
|
RB_OBJ_WRITE(self, &state->indent, string_config(indent));
|
|
1634
1439
|
return Qnil;
|
|
@@ -1654,6 +1459,7 @@ static VALUE cState_space(VALUE self)
|
|
|
1654
1459
|
*/
|
|
1655
1460
|
static VALUE cState_space_set(VALUE self, VALUE space)
|
|
1656
1461
|
{
|
|
1462
|
+
rb_check_frozen(self);
|
|
1657
1463
|
GET_STATE(self);
|
|
1658
1464
|
RB_OBJ_WRITE(self, &state->space, string_config(space));
|
|
1659
1465
|
return Qnil;
|
|
@@ -1677,6 +1483,7 @@ static VALUE cState_space_before(VALUE self)
|
|
|
1677
1483
|
*/
|
|
1678
1484
|
static VALUE cState_space_before_set(VALUE self, VALUE space_before)
|
|
1679
1485
|
{
|
|
1486
|
+
rb_check_frozen(self);
|
|
1680
1487
|
GET_STATE(self);
|
|
1681
1488
|
RB_OBJ_WRITE(self, &state->space_before, string_config(space_before));
|
|
1682
1489
|
return Qnil;
|
|
@@ -1702,6 +1509,7 @@ static VALUE cState_object_nl(VALUE self)
|
|
|
1702
1509
|
*/
|
|
1703
1510
|
static VALUE cState_object_nl_set(VALUE self, VALUE object_nl)
|
|
1704
1511
|
{
|
|
1512
|
+
rb_check_frozen(self);
|
|
1705
1513
|
GET_STATE(self);
|
|
1706
1514
|
RB_OBJ_WRITE(self, &state->object_nl, string_config(object_nl));
|
|
1707
1515
|
return Qnil;
|
|
@@ -1725,6 +1533,7 @@ static VALUE cState_array_nl(VALUE self)
|
|
|
1725
1533
|
*/
|
|
1726
1534
|
static VALUE cState_array_nl_set(VALUE self, VALUE array_nl)
|
|
1727
1535
|
{
|
|
1536
|
+
rb_check_frozen(self);
|
|
1728
1537
|
GET_STATE(self);
|
|
1729
1538
|
RB_OBJ_WRITE(self, &state->array_nl, string_config(array_nl));
|
|
1730
1539
|
return Qnil;
|
|
@@ -1748,6 +1557,7 @@ static VALUE cState_as_json(VALUE self)
|
|
|
1748
1557
|
*/
|
|
1749
1558
|
static VALUE cState_as_json_set(VALUE self, VALUE as_json)
|
|
1750
1559
|
{
|
|
1560
|
+
rb_check_frozen(self);
|
|
1751
1561
|
GET_STATE(self);
|
|
1752
1562
|
RB_OBJ_WRITE(self, &state->as_json, rb_convert_type(as_json, T_DATA, "Proc", "to_proc"));
|
|
1753
1563
|
return Qnil;
|
|
@@ -1779,7 +1589,21 @@ static VALUE cState_max_nesting(VALUE self)
|
|
|
1779
1589
|
|
|
1780
1590
|
static long long_config(VALUE num)
|
|
1781
1591
|
{
|
|
1782
|
-
return RTEST(num) ?
|
|
1592
|
+
return RTEST(num) ? NUM2LONG(num) : 0;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
// depth must never be negative; reject early with a clear error.
|
|
1596
|
+
static long depth_config(VALUE num)
|
|
1597
|
+
{
|
|
1598
|
+
if (!RTEST(num)) return 0;
|
|
1599
|
+
long d = NUM2LONG(num);
|
|
1600
|
+
if (RB_UNLIKELY(d < 0)) {
|
|
1601
|
+
rb_raise(rb_eArgError, "depth must be >= 0 (got %ld)", d);
|
|
1602
|
+
}
|
|
1603
|
+
if (RB_UNLIKELY(d > INT_MAX)) {
|
|
1604
|
+
rb_raise(rb_eArgError, "depth is too large (got %ld)", d);
|
|
1605
|
+
}
|
|
1606
|
+
return d;
|
|
1783
1607
|
}
|
|
1784
1608
|
|
|
1785
1609
|
/*
|
|
@@ -1790,6 +1614,7 @@ static long long_config(VALUE num)
|
|
|
1790
1614
|
*/
|
|
1791
1615
|
static VALUE cState_max_nesting_set(VALUE self, VALUE depth)
|
|
1792
1616
|
{
|
|
1617
|
+
rb_check_frozen(self);
|
|
1793
1618
|
GET_STATE(self);
|
|
1794
1619
|
state->max_nesting = long_config(depth);
|
|
1795
1620
|
return Qnil;
|
|
@@ -1815,6 +1640,7 @@ static VALUE cState_script_safe(VALUE self)
|
|
|
1815
1640
|
*/
|
|
1816
1641
|
static VALUE cState_script_safe_set(VALUE self, VALUE enable)
|
|
1817
1642
|
{
|
|
1643
|
+
rb_check_frozen(self);
|
|
1818
1644
|
GET_STATE(self);
|
|
1819
1645
|
state->script_safe = RTEST(enable);
|
|
1820
1646
|
return Qnil;
|
|
@@ -1846,6 +1672,7 @@ static VALUE cState_strict(VALUE self)
|
|
|
1846
1672
|
*/
|
|
1847
1673
|
static VALUE cState_strict_set(VALUE self, VALUE enable)
|
|
1848
1674
|
{
|
|
1675
|
+
rb_check_frozen(self);
|
|
1849
1676
|
GET_STATE(self);
|
|
1850
1677
|
state->strict = RTEST(enable);
|
|
1851
1678
|
return Qnil;
|
|
@@ -1870,6 +1697,7 @@ static VALUE cState_allow_nan_p(VALUE self)
|
|
|
1870
1697
|
*/
|
|
1871
1698
|
static VALUE cState_allow_nan_set(VALUE self, VALUE enable)
|
|
1872
1699
|
{
|
|
1700
|
+
rb_check_frozen(self);
|
|
1873
1701
|
GET_STATE(self);
|
|
1874
1702
|
state->allow_nan = RTEST(enable);
|
|
1875
1703
|
return Qnil;
|
|
@@ -1894,11 +1722,67 @@ static VALUE cState_ascii_only_p(VALUE self)
|
|
|
1894
1722
|
*/
|
|
1895
1723
|
static VALUE cState_ascii_only_set(VALUE self, VALUE enable)
|
|
1896
1724
|
{
|
|
1725
|
+
rb_check_frozen(self);
|
|
1897
1726
|
GET_STATE(self);
|
|
1898
1727
|
state->ascii_only = RTEST(enable);
|
|
1899
1728
|
return Qnil;
|
|
1900
1729
|
}
|
|
1901
1730
|
|
|
1731
|
+
static VALUE cState_set_default_sort_keys_proc(VALUE self, VALUE proc)
|
|
1732
|
+
{
|
|
1733
|
+
if (!rb_obj_is_proc(proc)) {
|
|
1734
|
+
rb_raise(rb_eTypeError, "sort_key_proc must be a Proc");
|
|
1735
|
+
}
|
|
1736
|
+
return default_sort_keys_proc = proc;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
static VALUE normalize_sort_keys(VALUE value)
|
|
1740
|
+
{
|
|
1741
|
+
if (rb_obj_is_proc(value)) {
|
|
1742
|
+
return value;
|
|
1743
|
+
} else if (value == Qtrue) {
|
|
1744
|
+
return default_sort_keys_proc;
|
|
1745
|
+
} else if (RTEST(value)) {
|
|
1746
|
+
rb_raise(rb_eTypeError, "The `sort_keys` argument must be a boolean or a Proc");
|
|
1747
|
+
} else {
|
|
1748
|
+
return Qfalse;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
/*
|
|
1753
|
+
* call-seq: sort_keys
|
|
1754
|
+
*
|
|
1755
|
+
* Get the value of sort_keys.
|
|
1756
|
+
*/
|
|
1757
|
+
static VALUE cState_sort_keys_p(VALUE self)
|
|
1758
|
+
{
|
|
1759
|
+
GET_STATE(self);
|
|
1760
|
+
return state->sort_keys;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
/*
|
|
1764
|
+
* call-seq: sort_keys=(value)
|
|
1765
|
+
*
|
|
1766
|
+
* value is a boolean or a proc. If the value is the boolean true, object keys
|
|
1767
|
+
* will be sorted lexicographically in ascending order.
|
|
1768
|
+
*
|
|
1769
|
+
* If the value is a proc, it receives the entire Hash and must return a Hash
|
|
1770
|
+
* with its pairs in the desired order, allowing for arbitrary sorting.
|
|
1771
|
+
*/
|
|
1772
|
+
static VALUE cState_sort_keys_set(VALUE self, VALUE value)
|
|
1773
|
+
{
|
|
1774
|
+
rb_check_frozen(self);
|
|
1775
|
+
GET_STATE(self);
|
|
1776
|
+
RB_OBJ_WRITE(self, &state->sort_keys, normalize_sort_keys(value));
|
|
1777
|
+
return Qnil;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
static VALUE cState_allow_duplicate_key_p(VALUE self)
|
|
1781
|
+
{
|
|
1782
|
+
GET_STATE(self);
|
|
1783
|
+
return state->allow_duplicate_key ? Qtrue : Qfalse;
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1902
1786
|
/*
|
|
1903
1787
|
* call-seq: depth
|
|
1904
1788
|
*
|
|
@@ -1918,8 +1802,9 @@ static VALUE cState_depth(VALUE self)
|
|
|
1918
1802
|
*/
|
|
1919
1803
|
static VALUE cState_depth_set(VALUE self, VALUE depth)
|
|
1920
1804
|
{
|
|
1805
|
+
rb_check_frozen(self);
|
|
1921
1806
|
GET_STATE(self);
|
|
1922
|
-
state->depth =
|
|
1807
|
+
state->depth = depth_config(depth);
|
|
1923
1808
|
return Qnil;
|
|
1924
1809
|
}
|
|
1925
1810
|
|
|
@@ -1951,33 +1836,63 @@ static void buffer_initial_length_set(JSON_Generator_State *state, VALUE buffer_
|
|
|
1951
1836
|
*/
|
|
1952
1837
|
static VALUE cState_buffer_initial_length_set(VALUE self, VALUE buffer_initial_length)
|
|
1953
1838
|
{
|
|
1839
|
+
rb_check_frozen(self);
|
|
1954
1840
|
GET_STATE(self);
|
|
1955
1841
|
buffer_initial_length_set(state, buffer_initial_length);
|
|
1956
1842
|
return Qnil;
|
|
1957
1843
|
}
|
|
1958
1844
|
|
|
1845
|
+
struct configure_state_data {
|
|
1846
|
+
JSON_Generator_State *state;
|
|
1847
|
+
VALUE vstate; // Ruby object that owns the state, or Qfalse if stack-allocated
|
|
1848
|
+
VALUE unknown_keywords;
|
|
1849
|
+
};
|
|
1850
|
+
|
|
1851
|
+
static inline void state_write_value(struct configure_state_data *data, VALUE *field, VALUE value)
|
|
1852
|
+
{
|
|
1853
|
+
if (RTEST(data->vstate)) {
|
|
1854
|
+
RB_OBJ_WRITE(data->vstate, field, value);
|
|
1855
|
+
} else {
|
|
1856
|
+
*field = value;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1959
1860
|
static int configure_state_i(VALUE key, VALUE val, VALUE _arg)
|
|
1960
1861
|
{
|
|
1961
|
-
|
|
1862
|
+
struct configure_state_data *data = (struct configure_state_data *)_arg;
|
|
1863
|
+
JSON_Generator_State *state = data->state;
|
|
1962
1864
|
|
|
1963
|
-
if (key == sym_indent) { state->indent
|
|
1964
|
-
else if (key == sym_space) { state->space
|
|
1965
|
-
else if (key == sym_space_before) { state->space_before
|
|
1966
|
-
else if (key == sym_object_nl) { state->object_nl
|
|
1967
|
-
else if (key == sym_array_nl) { state->array_nl
|
|
1865
|
+
if (key == sym_indent) { state_write_value(data, &state->indent, string_config(val)); }
|
|
1866
|
+
else if (key == sym_space) { state_write_value(data, &state->space, string_config(val)); }
|
|
1867
|
+
else if (key == sym_space_before) { state_write_value(data, &state->space_before, string_config(val)); }
|
|
1868
|
+
else if (key == sym_object_nl) { state_write_value(data, &state->object_nl, string_config(val)); }
|
|
1869
|
+
else if (key == sym_array_nl) { state_write_value(data, &state->array_nl, string_config(val)); }
|
|
1968
1870
|
else if (key == sym_max_nesting) { state->max_nesting = long_config(val); }
|
|
1969
1871
|
else if (key == sym_allow_nan) { state->allow_nan = RTEST(val); }
|
|
1970
1872
|
else if (key == sym_ascii_only) { state->ascii_only = RTEST(val); }
|
|
1971
|
-
else if (key == sym_depth) { state->depth =
|
|
1873
|
+
else if (key == sym_depth) { state->depth = depth_config(val); }
|
|
1972
1874
|
else if (key == sym_buffer_initial_length) { buffer_initial_length_set(state, val); }
|
|
1973
1875
|
else if (key == sym_script_safe) { state->script_safe = RTEST(val); }
|
|
1974
|
-
else if (key == sym_escape_slash) { state->script_safe = RTEST(val); }
|
|
1975
1876
|
else if (key == sym_strict) { state->strict = RTEST(val); }
|
|
1976
|
-
else if (key ==
|
|
1877
|
+
else if (key == sym_allow_duplicate_key) { state->allow_duplicate_key = RTEST(val); }
|
|
1878
|
+
else if (key == sym_as_json) {
|
|
1879
|
+
VALUE proc = RTEST(val) ? rb_convert_type(val, T_DATA, "Proc", "to_proc") : Qfalse;
|
|
1880
|
+
state->as_json_single_arg = proc && rb_proc_arity(proc) == 1;
|
|
1881
|
+
state_write_value(data, &state->as_json, proc);
|
|
1882
|
+
}
|
|
1883
|
+
else if (key == sym_sort_keys) {
|
|
1884
|
+
state_write_value(data, &state->sort_keys, normalize_sort_keys(val));
|
|
1885
|
+
}
|
|
1886
|
+
else {
|
|
1887
|
+
if (!data->unknown_keywords) {
|
|
1888
|
+
data->unknown_keywords = rb_obj_hide(rb_ary_new());
|
|
1889
|
+
}
|
|
1890
|
+
rb_ary_push(data->unknown_keywords, key);
|
|
1891
|
+
}
|
|
1977
1892
|
return ST_CONTINUE;
|
|
1978
1893
|
}
|
|
1979
1894
|
|
|
1980
|
-
static void configure_state(JSON_Generator_State *state, VALUE config)
|
|
1895
|
+
static void configure_state(JSON_Generator_State *state, VALUE vstate, VALUE config)
|
|
1981
1896
|
{
|
|
1982
1897
|
if (!RTEST(config)) return;
|
|
1983
1898
|
|
|
@@ -1985,45 +1900,58 @@ static void configure_state(JSON_Generator_State *state, VALUE config)
|
|
|
1985
1900
|
|
|
1986
1901
|
if (!RHASH_SIZE(config)) return;
|
|
1987
1902
|
|
|
1903
|
+
struct configure_state_data data = {
|
|
1904
|
+
.state = state,
|
|
1905
|
+
.vstate = vstate,
|
|
1906
|
+
.unknown_keywords = Qfalse,
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1988
1909
|
// We assume in most cases few keys are set so it's faster to go over
|
|
1989
1910
|
// the provided keys than to check all possible keys.
|
|
1990
|
-
rb_hash_foreach(config, configure_state_i, (VALUE)
|
|
1911
|
+
rb_hash_foreach(config, configure_state_i, (VALUE)&data);
|
|
1912
|
+
|
|
1913
|
+
raise_argument_error_on_unknown_keywords(data.unknown_keywords);
|
|
1991
1914
|
}
|
|
1992
1915
|
|
|
1993
1916
|
static VALUE cState_configure(VALUE self, VALUE opts)
|
|
1994
1917
|
{
|
|
1918
|
+
rb_check_frozen(self);
|
|
1995
1919
|
GET_STATE(self);
|
|
1996
|
-
configure_state(state, opts);
|
|
1920
|
+
configure_state(state, self, opts);
|
|
1997
1921
|
return self;
|
|
1998
1922
|
}
|
|
1999
1923
|
|
|
2000
|
-
static VALUE
|
|
1924
|
+
static VALUE cState_m_do_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io, generator_func func)
|
|
2001
1925
|
{
|
|
2002
1926
|
JSON_Generator_State state = {0};
|
|
2003
1927
|
state_init(&state);
|
|
2004
|
-
configure_state(&state, opts);
|
|
1928
|
+
configure_state(&state, Qfalse, opts);
|
|
2005
1929
|
|
|
2006
1930
|
char stack_buffer[FBUFFER_STACK_SIZE];
|
|
2007
|
-
FBuffer buffer = {
|
|
2008
|
-
|
|
2009
|
-
};
|
|
2010
|
-
fbuffer_stack_init(&buffer, state.buffer_initial_length, stack_buffer, FBUFFER_STACK_SIZE);
|
|
1931
|
+
FBuffer buffer = { 0 };
|
|
1932
|
+
fbuffer_init(&buffer, state.buffer_initial_length, io, stack_buffer, FBUFFER_STACK_SIZE);
|
|
2011
1933
|
|
|
2012
1934
|
struct generate_json_data data = {
|
|
2013
1935
|
.buffer = &buffer,
|
|
2014
1936
|
.vstate = Qfalse,
|
|
2015
1937
|
.state = &state,
|
|
1938
|
+
.depth = state.depth,
|
|
2016
1939
|
.obj = obj,
|
|
2017
|
-
.func =
|
|
1940
|
+
.func = func,
|
|
2018
1941
|
};
|
|
2019
|
-
|
|
1942
|
+
return rb_ensure(generate_json_try, (VALUE)&data, generate_json_ensure, (VALUE)&data);
|
|
1943
|
+
}
|
|
2020
1944
|
|
|
2021
|
-
|
|
1945
|
+
static VALUE cState_m_generate(VALUE klass, VALUE obj, VALUE opts, VALUE io)
|
|
1946
|
+
{
|
|
1947
|
+
return cState_m_do_generate(klass, obj, opts, io, generate_json);
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
static VALUE cState_m_generate_no_fallback(VALUE klass, VALUE obj, VALUE opts, VALUE io)
|
|
1951
|
+
{
|
|
1952
|
+
return cState_m_do_generate(klass, obj, opts, io, generate_json_no_fallback);
|
|
2022
1953
|
}
|
|
2023
1954
|
|
|
2024
|
-
/*
|
|
2025
|
-
*
|
|
2026
|
-
*/
|
|
2027
1955
|
void Init_generator(void)
|
|
2028
1956
|
{
|
|
2029
1957
|
#ifdef HAVE_RB_EXT_RACTOR_SAFE
|
|
@@ -2041,6 +1969,8 @@ void Init_generator(void)
|
|
|
2041
1969
|
VALUE mExt = rb_define_module_under(mJSON, "Ext");
|
|
2042
1970
|
VALUE mGenerator = rb_define_module_under(mExt, "Generator");
|
|
2043
1971
|
|
|
1972
|
+
rb_global_variable(&default_sort_keys_proc);
|
|
1973
|
+
|
|
2044
1974
|
rb_global_variable(&eGeneratorError);
|
|
2045
1975
|
eGeneratorError = rb_path2class("JSON::GeneratorError");
|
|
2046
1976
|
|
|
@@ -2050,6 +1980,8 @@ void Init_generator(void)
|
|
|
2050
1980
|
cState = rb_define_class_under(mGenerator, "State", rb_cObject);
|
|
2051
1981
|
rb_define_alloc_func(cState, cState_s_allocate);
|
|
2052
1982
|
rb_define_singleton_method(cState, "from_state", cState_from_state_s, 1);
|
|
1983
|
+
rb_define_singleton_method(cState, "default_sort_keys_proc=", cState_set_default_sort_keys_proc, 1);
|
|
1984
|
+
|
|
2053
1985
|
rb_define_method(cState, "initialize", cState_initialize, -1);
|
|
2054
1986
|
rb_define_alias(cState, "initialize", "initialize"); // avoid method redefinition warnings
|
|
2055
1987
|
rb_define_private_method(cState, "_configure", cState_configure, 1);
|
|
@@ -2072,9 +2004,6 @@ void Init_generator(void)
|
|
|
2072
2004
|
rb_define_method(cState, "script_safe", cState_script_safe, 0);
|
|
2073
2005
|
rb_define_method(cState, "script_safe?", cState_script_safe, 0);
|
|
2074
2006
|
rb_define_method(cState, "script_safe=", cState_script_safe_set, 1);
|
|
2075
|
-
rb_define_alias(cState, "escape_slash", "script_safe");
|
|
2076
|
-
rb_define_alias(cState, "escape_slash?", "script_safe?");
|
|
2077
|
-
rb_define_alias(cState, "escape_slash=", "script_safe=");
|
|
2078
2007
|
rb_define_method(cState, "strict", cState_strict, 0);
|
|
2079
2008
|
rb_define_method(cState, "strict?", cState_strict, 0);
|
|
2080
2009
|
rb_define_method(cState, "strict=", cState_strict_set, 1);
|
|
@@ -2088,51 +2017,14 @@ void Init_generator(void)
|
|
|
2088
2017
|
rb_define_method(cState, "buffer_initial_length", cState_buffer_initial_length, 0);
|
|
2089
2018
|
rb_define_method(cState, "buffer_initial_length=", cState_buffer_initial_length_set, 1);
|
|
2090
2019
|
rb_define_method(cState, "generate", cState_generate, -1);
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
VALUE mGeneratorMethods = rb_define_module_under(mGenerator, "GeneratorMethods");
|
|
2096
|
-
|
|
2097
|
-
VALUE mObject = rb_define_module_under(mGeneratorMethods, "Object");
|
|
2098
|
-
rb_define_method(mObject, "to_json", mObject_to_json, -1);
|
|
2099
|
-
|
|
2100
|
-
VALUE mHash = rb_define_module_under(mGeneratorMethods, "Hash");
|
|
2101
|
-
rb_define_method(mHash, "to_json", mHash_to_json, -1);
|
|
2102
|
-
|
|
2103
|
-
VALUE mArray = rb_define_module_under(mGeneratorMethods, "Array");
|
|
2104
|
-
rb_define_method(mArray, "to_json", mArray_to_json, -1);
|
|
2105
|
-
|
|
2106
|
-
#ifdef RUBY_INTEGER_UNIFICATION
|
|
2107
|
-
VALUE mInteger = rb_define_module_under(mGeneratorMethods, "Integer");
|
|
2108
|
-
rb_define_method(mInteger, "to_json", mInteger_to_json, -1);
|
|
2109
|
-
#else
|
|
2110
|
-
VALUE mFixnum = rb_define_module_under(mGeneratorMethods, "Fixnum");
|
|
2111
|
-
rb_define_method(mFixnum, "to_json", mFixnum_to_json, -1);
|
|
2112
|
-
|
|
2113
|
-
VALUE mBignum = rb_define_module_under(mGeneratorMethods, "Bignum");
|
|
2114
|
-
rb_define_method(mBignum, "to_json", mBignum_to_json, -1);
|
|
2115
|
-
#endif
|
|
2116
|
-
VALUE mFloat = rb_define_module_under(mGeneratorMethods, "Float");
|
|
2117
|
-
rb_define_method(mFloat, "to_json", mFloat_to_json, -1);
|
|
2118
|
-
|
|
2119
|
-
VALUE mString = rb_define_module_under(mGeneratorMethods, "String");
|
|
2120
|
-
rb_define_singleton_method(mString, "included", mString_included_s, 1);
|
|
2121
|
-
rb_define_method(mString, "to_json", mString_to_json, -1);
|
|
2122
|
-
rb_define_method(mString, "to_json_raw", mString_to_json_raw, -1);
|
|
2123
|
-
rb_define_method(mString, "to_json_raw_object", mString_to_json_raw_object, 0);
|
|
2020
|
+
rb_define_method(cState, "_generate_no_fallback", cState_generate_no_fallback, -1);
|
|
2021
|
+
rb_define_method(cState, "sort_keys", cState_sort_keys_p, 0);
|
|
2022
|
+
rb_define_method(cState, "sort_keys=", cState_sort_keys_set, 1);
|
|
2124
2023
|
|
|
2125
|
-
|
|
2126
|
-
rb_define_method(mString_Extend, "json_create", mString_Extend_json_create, 1);
|
|
2024
|
+
rb_define_private_method(cState, "allow_duplicate_key?", cState_allow_duplicate_key_p, 0);
|
|
2127
2025
|
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
VALUE mFalseClass = rb_define_module_under(mGeneratorMethods, "FalseClass");
|
|
2132
|
-
rb_define_method(mFalseClass, "to_json", mFalseClass_to_json, -1);
|
|
2133
|
-
|
|
2134
|
-
VALUE mNilClass = rb_define_module_under(mGeneratorMethods, "NilClass");
|
|
2135
|
-
rb_define_method(mNilClass, "to_json", mNilClass_to_json, -1);
|
|
2026
|
+
rb_define_singleton_method(cState, "generate", cState_m_generate, 3);
|
|
2027
|
+
rb_define_singleton_method(cState, "_generate_no_fallback", cState_m_generate_no_fallback, 3);
|
|
2136
2028
|
|
|
2137
2029
|
rb_global_variable(&Encoding_UTF_8);
|
|
2138
2030
|
Encoding_UTF_8 = rb_const_get(rb_path2class("Encoding"), rb_intern("UTF_8"));
|
|
@@ -2140,10 +2032,6 @@ void Init_generator(void)
|
|
|
2140
2032
|
i_to_s = rb_intern("to_s");
|
|
2141
2033
|
i_to_json = rb_intern("to_json");
|
|
2142
2034
|
i_new = rb_intern("new");
|
|
2143
|
-
i_pack = rb_intern("pack");
|
|
2144
|
-
i_unpack = rb_intern("unpack");
|
|
2145
|
-
i_create_id = rb_intern("create_id");
|
|
2146
|
-
i_extend = rb_intern("extend");
|
|
2147
2035
|
i_encode = rb_intern("encode");
|
|
2148
2036
|
|
|
2149
2037
|
sym_indent = ID2SYM(rb_intern("indent"));
|
|
@@ -2157,9 +2045,10 @@ void Init_generator(void)
|
|
|
2157
2045
|
sym_depth = ID2SYM(rb_intern("depth"));
|
|
2158
2046
|
sym_buffer_initial_length = ID2SYM(rb_intern("buffer_initial_length"));
|
|
2159
2047
|
sym_script_safe = ID2SYM(rb_intern("script_safe"));
|
|
2160
|
-
sym_escape_slash = ID2SYM(rb_intern("escape_slash"));
|
|
2161
2048
|
sym_strict = ID2SYM(rb_intern("strict"));
|
|
2162
2049
|
sym_as_json = ID2SYM(rb_intern("as_json"));
|
|
2050
|
+
sym_allow_duplicate_key = ID2SYM(rb_intern("allow_duplicate_key"));
|
|
2051
|
+
sym_sort_keys = ID2SYM(rb_intern("sort_keys"));
|
|
2163
2052
|
|
|
2164
2053
|
usascii_encindex = rb_usascii_encindex();
|
|
2165
2054
|
utf8_encindex = rb_utf8_encindex();
|
|
@@ -2167,22 +2056,5 @@ void Init_generator(void)
|
|
|
2167
2056
|
|
|
2168
2057
|
rb_require("json/ext/generator/state");
|
|
2169
2058
|
|
|
2170
|
-
|
|
2171
|
-
switch(find_simd_implementation()) {
|
|
2172
|
-
#ifdef HAVE_SIMD
|
|
2173
|
-
#ifdef HAVE_SIMD_NEON
|
|
2174
|
-
case SIMD_NEON:
|
|
2175
|
-
search_escape_basic_impl = search_escape_basic_neon;
|
|
2176
|
-
break;
|
|
2177
|
-
#endif /* HAVE_SIMD_NEON */
|
|
2178
|
-
#ifdef HAVE_SIMD_SSE2
|
|
2179
|
-
case SIMD_SSE2:
|
|
2180
|
-
search_escape_basic_impl = search_escape_basic_sse2;
|
|
2181
|
-
break;
|
|
2182
|
-
#endif /* HAVE_SIMD_SSE2 */
|
|
2183
|
-
#endif /* HAVE_SIMD */
|
|
2184
|
-
default:
|
|
2185
|
-
search_escape_basic_impl = search_escape_basic;
|
|
2186
|
-
break;
|
|
2187
|
-
}
|
|
2059
|
+
simd_impl = find_simd_implementation();
|
|
2188
2060
|
}
|