oj_windows 3.16.15

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.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +44 -0
  3. data/LICENSE +21 -0
  4. data/README.md +164 -0
  5. data/ext/oj_windows/buf.h +85 -0
  6. data/ext/oj_windows/cache.c +339 -0
  7. data/ext/oj_windows/cache.h +22 -0
  8. data/ext/oj_windows/cache8.c +105 -0
  9. data/ext/oj_windows/cache8.h +21 -0
  10. data/ext/oj_windows/circarray.c +64 -0
  11. data/ext/oj_windows/circarray.h +22 -0
  12. data/ext/oj_windows/code.c +214 -0
  13. data/ext/oj_windows/code.h +40 -0
  14. data/ext/oj_windows/compat.c +239 -0
  15. data/ext/oj_windows/custom.c +1074 -0
  16. data/ext/oj_windows/debug.c +126 -0
  17. data/ext/oj_windows/dump.c +1556 -0
  18. data/ext/oj_windows/dump.h +110 -0
  19. data/ext/oj_windows/dump_compat.c +901 -0
  20. data/ext/oj_windows/dump_leaf.c +162 -0
  21. data/ext/oj_windows/dump_object.c +710 -0
  22. data/ext/oj_windows/dump_strict.c +405 -0
  23. data/ext/oj_windows/encode.h +16 -0
  24. data/ext/oj_windows/err.c +57 -0
  25. data/ext/oj_windows/err.h +67 -0
  26. data/ext/oj_windows/extconf.rb +77 -0
  27. data/ext/oj_windows/fast.c +1710 -0
  28. data/ext/oj_windows/intern.c +325 -0
  29. data/ext/oj_windows/intern.h +22 -0
  30. data/ext/oj_windows/mem.c +320 -0
  31. data/ext/oj_windows/mem.h +53 -0
  32. data/ext/oj_windows/mimic_json.c +919 -0
  33. data/ext/oj_windows/object.c +726 -0
  34. data/ext/oj_windows/odd.c +245 -0
  35. data/ext/oj_windows/odd.h +43 -0
  36. data/ext/oj_windows/oj.c +2097 -0
  37. data/ext/oj_windows/oj.h +420 -0
  38. data/ext/oj_windows/parse.c +1317 -0
  39. data/ext/oj_windows/parse.h +113 -0
  40. data/ext/oj_windows/parser.c +1600 -0
  41. data/ext/oj_windows/parser.h +103 -0
  42. data/ext/oj_windows/rails.c +1484 -0
  43. data/ext/oj_windows/rails.h +18 -0
  44. data/ext/oj_windows/reader.c +222 -0
  45. data/ext/oj_windows/reader.h +137 -0
  46. data/ext/oj_windows/resolve.c +80 -0
  47. data/ext/oj_windows/resolve.h +12 -0
  48. data/ext/oj_windows/rxclass.c +144 -0
  49. data/ext/oj_windows/rxclass.h +26 -0
  50. data/ext/oj_windows/saj.c +675 -0
  51. data/ext/oj_windows/saj2.c +584 -0
  52. data/ext/oj_windows/saj2.h +23 -0
  53. data/ext/oj_windows/scp.c +187 -0
  54. data/ext/oj_windows/simd.h +47 -0
  55. data/ext/oj_windows/sparse.c +946 -0
  56. data/ext/oj_windows/stream_writer.c +329 -0
  57. data/ext/oj_windows/strict.c +189 -0
  58. data/ext/oj_windows/string_writer.c +517 -0
  59. data/ext/oj_windows/trace.c +72 -0
  60. data/ext/oj_windows/trace.h +55 -0
  61. data/ext/oj_windows/usual.c +1218 -0
  62. data/ext/oj_windows/usual.h +69 -0
  63. data/ext/oj_windows/util.c +136 -0
  64. data/ext/oj_windows/util.h +20 -0
  65. data/ext/oj_windows/val_stack.c +101 -0
  66. data/ext/oj_windows/val_stack.h +151 -0
  67. data/ext/oj_windows/validate.c +46 -0
  68. data/ext/oj_windows/wab.c +584 -0
  69. data/lib/oj/active_support_helper.rb +39 -0
  70. data/lib/oj/bag.rb +95 -0
  71. data/lib/oj/easy_hash.rb +52 -0
  72. data/lib/oj/error.rb +21 -0
  73. data/lib/oj/json.rb +188 -0
  74. data/lib/oj/mimic.rb +301 -0
  75. data/lib/oj/saj.rb +80 -0
  76. data/lib/oj/schandler.rb +143 -0
  77. data/lib/oj/state.rb +135 -0
  78. data/lib/oj/version.rb +4 -0
  79. data/lib/oj_windows/active_support_helper.rb +39 -0
  80. data/lib/oj_windows/bag.rb +95 -0
  81. data/lib/oj_windows/easy_hash.rb +52 -0
  82. data/lib/oj_windows/error.rb +21 -0
  83. data/lib/oj_windows/json.rb +188 -0
  84. data/lib/oj_windows/mimic.rb +301 -0
  85. data/lib/oj_windows/saj.rb +80 -0
  86. data/lib/oj_windows/schandler.rb +143 -0
  87. data/lib/oj_windows/state.rb +135 -0
  88. data/lib/oj_windows/version.rb +4 -0
  89. data/lib/oj_windows.rb +15 -0
  90. data/pages/Advanced.md +38 -0
  91. data/pages/Compatibility.md +49 -0
  92. data/pages/Custom.md +37 -0
  93. data/pages/Encoding.md +61 -0
  94. data/pages/InstallOptions.md +20 -0
  95. data/pages/JsonGem.md +60 -0
  96. data/pages/Modes.md +94 -0
  97. data/pages/Options.md +339 -0
  98. data/pages/Parser.md +134 -0
  99. data/pages/Rails.md +85 -0
  100. data/pages/Security.md +43 -0
  101. data/pages/WAB.md +12 -0
  102. metadata +242 -0
@@ -0,0 +1,1710 @@
1
+ // Copyright (c) 2012 Peter Ohler. All rights reserved.
2
+ // Licensed under the MIT License. See LICENSE file in the project root for
3
+ // license details.
4
+
5
+ #if !IS_WINDOWS
6
+ #include <sys/resource.h> // for getrlimit() on linux
7
+ #endif
8
+ #include <errno.h>
9
+ #include <math.h>
10
+ #include <stdio.h>
11
+ #include <stdlib.h>
12
+ #include <string.h>
13
+
14
+ #include "dump.h"
15
+ #include "encode.h"
16
+ #include "mem.h"
17
+ #include "oj.h"
18
+
19
+ // maximum to allocate on the stack, arbitrary limit
20
+ #define SMALL_JSON 65536
21
+ #define MAX_STACK 100
22
+ // #define BATCH_SIZE (4096 / sizeof(struct _leaf) - 1)
23
+ #define BATCH_SIZE 100
24
+
25
+ // Support for compaction
26
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
27
+ #define mark rb_gc_mark_movable
28
+ #else
29
+ #define mark rb_gc_mark
30
+ #endif
31
+
32
+ typedef struct _batch {
33
+ struct _batch *next;
34
+ int next_avail;
35
+ struct _leaf leaves[BATCH_SIZE];
36
+ } *Batch;
37
+
38
+ typedef struct _doc {
39
+ Leaf data;
40
+ Leaf *where; // points to current location
41
+ Leaf where_path[MAX_STACK]; // points to head of path
42
+ char *json;
43
+ unsigned long size; // number of leaves/branches in the doc
44
+ VALUE self;
45
+ Batch batches;
46
+ struct _batch batch0;
47
+ } *Doc;
48
+
49
+ typedef struct _parseInfo {
50
+ char *str; // buffer being read from
51
+ char *s; // current position in buffer
52
+ Doc doc;
53
+ void *stack_min;
54
+ } *ParseInfo;
55
+
56
+ static void leaf_init(Leaf leaf, int type);
57
+ static Leaf leaf_new(Doc doc, int type);
58
+ static void leaf_append_element(Leaf parent, Leaf element);
59
+ static VALUE leaf_value(Doc doc, Leaf leaf);
60
+ static void leaf_fixnum_value(Leaf leaf);
61
+ static void leaf_float_value(Leaf leaf);
62
+ static VALUE leaf_array_value(Doc doc, Leaf leaf);
63
+ static VALUE leaf_hash_value(Doc doc, Leaf leaf);
64
+
65
+ static Leaf read_next(ParseInfo pi);
66
+ static Leaf read_obj(ParseInfo pi);
67
+ static Leaf read_array(ParseInfo pi);
68
+ static Leaf read_str(ParseInfo pi);
69
+ static Leaf read_num(ParseInfo pi);
70
+ static Leaf read_true(ParseInfo pi);
71
+ static Leaf read_false(ParseInfo pi);
72
+ static Leaf read_nil(ParseInfo pi);
73
+ static void next_non_white(ParseInfo pi);
74
+ static char *read_quoted_value(ParseInfo pi);
75
+ static void skip_comment(ParseInfo pi);
76
+
77
+ static VALUE protect_open_proc(VALUE x);
78
+ static VALUE parse_json(VALUE clas, char *json, bool given);
79
+ static void each_leaf(Doc doc, VALUE self);
80
+ static int move_step(Doc doc, const char *path, int loc);
81
+ static Leaf get_doc_leaf(Doc doc, const char *path);
82
+ static Leaf get_leaf(Leaf *stack, Leaf *lp, const char *path);
83
+ static void each_value(Doc doc, Leaf leaf);
84
+
85
+ VALUE oj_doc_class = Qundef;
86
+
87
+ // This is only for CentOS 5.4 with Ruby 1.9.3-p0.
88
+ #ifndef HAVE_STPCPY
89
+ char *stpcpy(char *dest, const char *src) {
90
+ size_t cnt = strlen(src);
91
+
92
+ strcpy(dest, src);
93
+
94
+ return dest + cnt;
95
+ }
96
+ #endif
97
+
98
+ inline static void next_non_white(ParseInfo pi) {
99
+ for (; 1; pi->s++) {
100
+ switch (*pi->s) {
101
+ case ' ':
102
+ case '\t':
103
+ case '\f':
104
+ case '\n':
105
+ case '\r': break;
106
+ case '/': skip_comment(pi); break;
107
+ default: return;
108
+ }
109
+ }
110
+ }
111
+
112
+ inline static char *ulong_fill(char *s, size_t num) {
113
+ char buf[32];
114
+ char *b = buf + sizeof(buf) - 1;
115
+
116
+ *b-- = '\0';
117
+ b = oj_longlong_to_string((long long)num, false, b);
118
+ if ('\0' == *b) {
119
+ b--;
120
+ *b = '0';
121
+ }
122
+ for (; '\0' != *b; b++, s++) {
123
+ *s = *b;
124
+ }
125
+ return s;
126
+ }
127
+
128
+ inline static void leaf_init(Leaf leaf, int type) {
129
+ leaf->next = 0;
130
+ leaf->rtype = type;
131
+ leaf->parent_type = T_NONE;
132
+ switch (type) {
133
+ case T_ARRAY:
134
+ case T_HASH:
135
+ leaf->elements = 0;
136
+ leaf->value_type = COL_VAL;
137
+ break;
138
+ case T_NIL:
139
+ leaf->value = Qnil;
140
+ leaf->value_type = RUBY_VAL;
141
+ break;
142
+ case T_TRUE:
143
+ leaf->value = Qtrue;
144
+ leaf->value_type = RUBY_VAL;
145
+ break;
146
+ case T_FALSE:
147
+ leaf->value = Qfalse;
148
+ leaf->value_type = RUBY_VAL;
149
+ break;
150
+ case T_FIXNUM:
151
+ case T_FLOAT:
152
+ case T_STRING:
153
+ default: leaf->value_type = STR_VAL; break;
154
+ }
155
+ }
156
+
157
+ inline static Leaf leaf_new(Doc doc, int type) {
158
+ Leaf leaf;
159
+
160
+ if (0 == doc->batches || BATCH_SIZE == doc->batches->next_avail) {
161
+ Batch b = OJ_R_ALLOC(struct _batch);
162
+
163
+ // Initializes all leaves with a NO_VAL value_type
164
+ memset(b, 0, sizeof(struct _batch));
165
+ b->next = doc->batches;
166
+ doc->batches = b;
167
+ b->next_avail = 0;
168
+ }
169
+ leaf = &doc->batches->leaves[doc->batches->next_avail];
170
+ doc->batches->next_avail++;
171
+ leaf_init(leaf, type);
172
+
173
+ return leaf;
174
+ }
175
+
176
+ inline static void leaf_append_element(Leaf parent, Leaf element) {
177
+ if (0 == parent->elements) {
178
+ parent->elements = element;
179
+ element->next = element;
180
+ } else {
181
+ element->next = parent->elements->next;
182
+ parent->elements->next = element;
183
+ parent->elements = element;
184
+ }
185
+ }
186
+
187
+ static VALUE leaf_value(Doc doc, Leaf leaf) {
188
+ if (RUBY_VAL != leaf->value_type) {
189
+ switch (leaf->rtype) {
190
+ case T_NIL: leaf->value = Qnil; break;
191
+ case T_TRUE: leaf->value = Qtrue; break;
192
+ case T_FALSE: leaf->value = Qfalse; break;
193
+ case T_FIXNUM: leaf_fixnum_value(leaf); break;
194
+ case T_FLOAT: leaf_float_value(leaf); break;
195
+ case T_STRING:
196
+ leaf->value = rb_utf8_str_new_cstr(leaf->str);
197
+ leaf->value_type = RUBY_VAL;
198
+ break;
199
+ case T_ARRAY: return leaf_array_value(doc, leaf); break;
200
+ case T_HASH: return leaf_hash_value(doc, leaf); break;
201
+ default: rb_raise(rb_const_get_at(Oj, rb_intern("Error")), "Unexpected type %02x.", leaf->rtype); break;
202
+ }
203
+ }
204
+ return leaf->value;
205
+ }
206
+
207
+ inline static Doc self_doc(VALUE self) {
208
+ Doc doc = DATA_PTR(self);
209
+
210
+ if (0 == doc) {
211
+ rb_raise(rb_eIOError, "Document already closed or not open.");
212
+ }
213
+ return doc;
214
+ }
215
+
216
+ static void skip_comment(ParseInfo pi) {
217
+ pi->s++; // skip first /
218
+ if ('*' == *pi->s) {
219
+ pi->s++;
220
+ for (; '\0' != *pi->s; pi->s++) {
221
+ if ('*' == *pi->s && '/' == *(pi->s + 1)) {
222
+ pi->s++;
223
+ return;
224
+ } else if ('\0' == *pi->s) {
225
+ raise_error("comment not terminated", pi->str, pi->s);
226
+ }
227
+ }
228
+ } else if ('/' == *pi->s) {
229
+ for (; 1; pi->s++) {
230
+ switch (*pi->s) {
231
+ case '\n':
232
+ case '\r':
233
+ case '\f':
234
+ case '\0': return;
235
+ default: break;
236
+ }
237
+ }
238
+ } else {
239
+ raise_error("invalid comment", pi->str, pi->s);
240
+ }
241
+ }
242
+
243
+ #ifdef RUBINIUS_RUBY
244
+ #define NUM_MAX 0x07FFFFFF
245
+ #else
246
+ #define NUM_MAX (FIXNUM_MAX >> 8)
247
+ #endif
248
+
249
+ static void leaf_fixnum_value(Leaf leaf) {
250
+ char *s = leaf->str;
251
+ int64_t n = 0;
252
+ int neg = 0;
253
+ int big = 0;
254
+
255
+ if ('-' == *s) {
256
+ s++;
257
+ neg = 1;
258
+ } else if ('+' == *s) {
259
+ s++;
260
+ }
261
+ for (; '0' <= *s && *s <= '9'; s++) {
262
+ n = n * 10 + (*s - '0');
263
+ if (NUM_MAX <= n) {
264
+ big = 1;
265
+ }
266
+ }
267
+ if (big) {
268
+ char c = *s;
269
+
270
+ *s = '\0';
271
+ leaf->value = rb_cstr_to_inum(leaf->str, 10, 0);
272
+ *s = c;
273
+ } else {
274
+ if (neg) {
275
+ n = -n;
276
+ }
277
+ leaf->value = rb_ll2inum(n);
278
+ }
279
+ leaf->value_type = RUBY_VAL;
280
+ }
281
+
282
+ static void leaf_float_value(Leaf leaf) {
283
+ leaf->value = rb_float_new(rb_cstr_to_dbl(leaf->str, 1));
284
+ leaf->value_type = RUBY_VAL;
285
+ }
286
+
287
+ static VALUE leaf_array_value(Doc doc, Leaf leaf) {
288
+ volatile VALUE a = rb_ary_new();
289
+
290
+ if (0 != leaf->elements) {
291
+ Leaf first = leaf->elements->next;
292
+ Leaf e = first;
293
+
294
+ do {
295
+ rb_ary_push(a, leaf_value(doc, e));
296
+ e = e->next;
297
+ } while (e != first);
298
+ }
299
+ return a;
300
+ }
301
+
302
+ static VALUE leaf_hash_value(Doc doc, Leaf leaf) {
303
+ volatile VALUE h = rb_hash_new();
304
+
305
+ if (0 != leaf->elements) {
306
+ Leaf first = leaf->elements->next;
307
+ Leaf e = first;
308
+ volatile VALUE key;
309
+
310
+ do {
311
+ key = rb_utf8_str_new_cstr(e->key);
312
+ rb_hash_aset(h, key, leaf_value(doc, e));
313
+ e = e->next;
314
+ } while (e != first);
315
+ }
316
+ return h;
317
+ }
318
+
319
+ static Leaf read_next(ParseInfo pi) {
320
+ Leaf leaf = 0;
321
+
322
+ if ((void *)&leaf < pi->stack_min) {
323
+ rb_raise(rb_eSysStackError, "JSON is too deeply nested");
324
+ }
325
+ next_non_white(pi); // skip white space
326
+ switch (*pi->s) {
327
+ case '{': leaf = read_obj(pi); break;
328
+ case '[': leaf = read_array(pi); break;
329
+ case '"': leaf = read_str(pi); break;
330
+ case '+':
331
+ case '-':
332
+ case '0':
333
+ case '1':
334
+ case '2':
335
+ case '3':
336
+ case '4':
337
+ case '5':
338
+ case '6':
339
+ case '7':
340
+ case '8':
341
+ case '9': leaf = read_num(pi); break;
342
+ case 't': leaf = read_true(pi); break;
343
+ case 'f': leaf = read_false(pi); break;
344
+ case 'n': leaf = read_nil(pi); break;
345
+ case '\0':
346
+ default: break; // returns 0
347
+ }
348
+ pi->doc->size++;
349
+
350
+ return leaf;
351
+ }
352
+
353
+ static Leaf read_obj(ParseInfo pi) {
354
+ Leaf h = leaf_new(pi->doc, T_HASH);
355
+ char *end;
356
+ const char *key = 0;
357
+ Leaf val = 0;
358
+
359
+ pi->s++;
360
+ next_non_white(pi);
361
+ if ('}' == *pi->s) {
362
+ pi->s++;
363
+ return h;
364
+ }
365
+ while (1) {
366
+ next_non_white(pi);
367
+ key = 0;
368
+ val = 0;
369
+ if ('"' != *pi->s || 0 == (key = read_quoted_value(pi))) {
370
+ raise_error("unexpected character", pi->str, pi->s);
371
+ }
372
+ next_non_white(pi);
373
+ if (':' == *pi->s) {
374
+ pi->s++;
375
+ } else {
376
+ raise_error("invalid format, expected :", pi->str, pi->s);
377
+ }
378
+ if (0 == (val = read_next(pi))) {
379
+ // printf("*** '%s'\n", pi->s);
380
+ raise_error("unexpected character", pi->str, pi->s);
381
+ }
382
+ end = pi->s;
383
+ val->key = key;
384
+ val->parent_type = T_HASH;
385
+ leaf_append_element(h, val);
386
+ next_non_white(pi);
387
+ if ('}' == *pi->s) {
388
+ pi->s++;
389
+ *end = '\0';
390
+ break;
391
+ } else if (',' == *pi->s) {
392
+ pi->s++;
393
+ } else {
394
+ // printf("*** '%s'\n", pi->s);
395
+ raise_error("invalid format, expected , or } while in an object", pi->str, pi->s);
396
+ }
397
+ *end = '\0';
398
+ }
399
+ return h;
400
+ }
401
+
402
+ static Leaf read_array(ParseInfo pi) {
403
+ Leaf a = leaf_new(pi->doc, T_ARRAY);
404
+ Leaf e;
405
+ char *end;
406
+ int cnt = 0;
407
+
408
+ pi->s++;
409
+ next_non_white(pi);
410
+ if (']' == *pi->s) {
411
+ pi->s++;
412
+ return a;
413
+ }
414
+ while (1) {
415
+ next_non_white(pi);
416
+ if (0 == (e = read_next(pi))) {
417
+ raise_error("unexpected character", pi->str, pi->s);
418
+ }
419
+ cnt++;
420
+ e->index = cnt;
421
+ e->parent_type = T_ARRAY;
422
+ leaf_append_element(a, e);
423
+ end = pi->s;
424
+ next_non_white(pi);
425
+ if (',' == *pi->s) {
426
+ pi->s++;
427
+ } else if (']' == *pi->s) {
428
+ pi->s++;
429
+ *end = '\0';
430
+ break;
431
+ } else {
432
+ raise_error("invalid format, expected , or ] while in an array", pi->str, pi->s);
433
+ }
434
+ *end = '\0';
435
+ }
436
+ return a;
437
+ }
438
+
439
+ static Leaf read_str(ParseInfo pi) {
440
+ Leaf leaf = leaf_new(pi->doc, T_STRING);
441
+
442
+ leaf->str = read_quoted_value(pi);
443
+
444
+ return leaf;
445
+ }
446
+
447
+ static Leaf read_num(ParseInfo pi) {
448
+ char *start = pi->s;
449
+ int type = T_FIXNUM;
450
+ Leaf leaf;
451
+
452
+ if ('-' == *pi->s) {
453
+ pi->s++;
454
+ }
455
+ // digits
456
+ for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
457
+ }
458
+ if ('.' == *pi->s) {
459
+ type = T_FLOAT;
460
+ pi->s++;
461
+ for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
462
+ }
463
+ }
464
+ if ('e' == *pi->s || 'E' == *pi->s) {
465
+ pi->s++;
466
+ if ('-' == *pi->s || '+' == *pi->s) {
467
+ pi->s++;
468
+ }
469
+ for (; '0' <= *pi->s && *pi->s <= '9'; pi->s++) {
470
+ }
471
+ }
472
+ leaf = leaf_new(pi->doc, type);
473
+ leaf->str = start;
474
+
475
+ return leaf;
476
+ }
477
+
478
+ static Leaf read_true(ParseInfo pi) {
479
+ Leaf leaf = leaf_new(pi->doc, T_TRUE);
480
+
481
+ pi->s++;
482
+ if ('r' != *pi->s || 'u' != *(pi->s + 1) || 'e' != *(pi->s + 2)) {
483
+ raise_error("invalid format, expected 'true'", pi->str, pi->s);
484
+ }
485
+ pi->s += 3;
486
+
487
+ return leaf;
488
+ }
489
+
490
+ static Leaf read_false(ParseInfo pi) {
491
+ Leaf leaf = leaf_new(pi->doc, T_FALSE);
492
+
493
+ pi->s++;
494
+ if ('a' != *pi->s || 'l' != *(pi->s + 1) || 's' != *(pi->s + 2) || 'e' != *(pi->s + 3)) {
495
+ raise_error("invalid format, expected 'false'", pi->str, pi->s);
496
+ }
497
+ pi->s += 4;
498
+
499
+ return leaf;
500
+ }
501
+
502
+ static Leaf read_nil(ParseInfo pi) {
503
+ Leaf leaf = leaf_new(pi->doc, T_NIL);
504
+
505
+ pi->s++;
506
+ if ('u' != *pi->s || 'l' != *(pi->s + 1) || 'l' != *(pi->s + 2)) {
507
+ raise_error("invalid format, expected 'nil'", pi->str, pi->s);
508
+ }
509
+ pi->s += 3;
510
+
511
+ return leaf;
512
+ }
513
+
514
+ static uint32_t read_4hex(ParseInfo pi, const char *h) {
515
+ uint32_t b = 0;
516
+ int i;
517
+
518
+ for (i = 0; i < 4; i++, h++) {
519
+ b = b << 4;
520
+ if ('0' <= *h && *h <= '9') {
521
+ b += *h - '0';
522
+ } else if ('A' <= *h && *h <= 'F') {
523
+ b += *h - 'A' + 10;
524
+ } else if ('a' <= *h && *h <= 'f') {
525
+ b += *h - 'a' + 10;
526
+ } else {
527
+ raise_error("invalid hex character", pi->str, pi->s);
528
+ }
529
+ }
530
+ return b;
531
+ }
532
+
533
+ static char *unicode_to_chars(ParseInfo pi, char *t, uint32_t code) {
534
+ if (0x0000007F >= code) {
535
+ *t++ = (char)code;
536
+ } else if (0x000007FF >= code) {
537
+ *t++ = 0xC0 | (code >> 6);
538
+ *t++ = 0x80 | (0x3F & code);
539
+ } else if (0x0000FFFF >= code) {
540
+ *t++ = 0xE0 | (code >> 12);
541
+ *t++ = 0x80 | ((code >> 6) & 0x3F);
542
+ *t++ = 0x80 | (0x3F & code);
543
+ } else if (0x001FFFFF >= code) {
544
+ *t++ = 0xF0 | (code >> 18);
545
+ *t++ = 0x80 | ((code >> 12) & 0x3F);
546
+ *t++ = 0x80 | ((code >> 6) & 0x3F);
547
+ *t++ = 0x80 | (0x3F & code);
548
+ } else if (0x03FFFFFF >= code) {
549
+ *t++ = 0xF8 | (code >> 24);
550
+ *t++ = 0x80 | ((code >> 18) & 0x3F);
551
+ *t++ = 0x80 | ((code >> 12) & 0x3F);
552
+ *t++ = 0x80 | ((code >> 6) & 0x3F);
553
+ *t++ = 0x80 | (0x3F & code);
554
+ } else if (0x7FFFFFFF >= code) {
555
+ *t++ = 0xFC | (code >> 30);
556
+ *t++ = 0x80 | ((code >> 24) & 0x3F);
557
+ *t++ = 0x80 | ((code >> 18) & 0x3F);
558
+ *t++ = 0x80 | ((code >> 12) & 0x3F);
559
+ *t++ = 0x80 | ((code >> 6) & 0x3F);
560
+ *t++ = 0x80 | (0x3F & code);
561
+ } else {
562
+ raise_error("invalid Unicode character", pi->str, pi->s);
563
+ }
564
+ return t;
565
+ }
566
+
567
+ // Assume the value starts immediately and goes until the quote character is
568
+ // reached again. Do not read the character after the terminating quote.
569
+ static char *read_quoted_value(ParseInfo pi) {
570
+ char *value = 0;
571
+ char *h = pi->s; // head
572
+ char *t = h; // tail
573
+
574
+ h++; // skip quote character
575
+ t++;
576
+ value = h;
577
+ for (; '"' != *h; h++, t++) {
578
+ if ('\0' == *h) {
579
+ pi->s = h;
580
+ raise_error("quoted string not terminated", pi->str, pi->s);
581
+ } else if ('\\' == *h) {
582
+ h++;
583
+ switch (*h) {
584
+ case 'n': *t = '\n'; break;
585
+ case 'r': *t = '\r'; break;
586
+ case 't': *t = '\t'; break;
587
+ case 'f': *t = '\f'; break;
588
+ case 'b': *t = '\b'; break;
589
+ case '"': *t = '"'; break;
590
+ case '/': *t = '/'; break;
591
+ case '\\': *t = '\\'; break;
592
+ case 'u': {
593
+ uint32_t code;
594
+
595
+ h++;
596
+ code = read_4hex(pi, h);
597
+ h += 3;
598
+ if (0x0000D800 <= code && code <= 0x0000DFFF) {
599
+ uint32_t c1 = (code - 0x0000D800) & 0x000003FF;
600
+ uint32_t c2;
601
+
602
+ h++;
603
+ if ('\\' != *h || 'u' != *(h + 1)) {
604
+ pi->s = h;
605
+ raise_error("invalid escaped character", pi->str, pi->s);
606
+ }
607
+ h += 2;
608
+ c2 = read_4hex(pi, h);
609
+ h += 3;
610
+ c2 = (c2 - 0x0000DC00) & 0x000003FF;
611
+ code = ((c1 << 10) | c2) + 0x00010000;
612
+ }
613
+ t = unicode_to_chars(pi, t, code);
614
+ t--;
615
+ break;
616
+ }
617
+ default:
618
+ pi->s = h;
619
+ raise_error("invalid escaped character", pi->str, pi->s);
620
+ break;
621
+ }
622
+ } else if (t != h) {
623
+ *t = *h;
624
+ }
625
+ }
626
+ *t = '\0'; // terminate value
627
+ pi->s = h + 1;
628
+
629
+ return value;
630
+ }
631
+
632
+ // doc support functions
633
+ inline static void doc_init(Doc doc) {
634
+ memset(doc, 0, sizeof(struct _doc));
635
+ doc->where = doc->where_path;
636
+ doc->self = Qundef;
637
+ doc->batches = &doc->batch0;
638
+ }
639
+
640
+ static void doc_free(Doc doc) {
641
+ if (0 != doc) {
642
+ Batch b;
643
+
644
+ while (0 != (b = doc->batches)) {
645
+ doc->batches = doc->batches->next;
646
+ if (&doc->batch0 != b) {
647
+ OJ_R_FREE(b);
648
+ }
649
+ }
650
+ OJ_R_FREE(doc->json);
651
+ OJ_R_FREE(doc);
652
+ }
653
+ }
654
+
655
+ static VALUE protect_open_proc(VALUE x) {
656
+ ParseInfo pi = (ParseInfo)x;
657
+
658
+ pi->doc->data = read_next(pi); // parse
659
+ *pi->doc->where = pi->doc->data;
660
+ pi->doc->where = pi->doc->where_path;
661
+ if (rb_block_given_p()) {
662
+ return rb_yield(pi->doc->self); // caller processing
663
+ }
664
+ return Qnil;
665
+ }
666
+
667
+ static void free_doc_cb(void *x) {
668
+ Doc doc = (Doc)x;
669
+
670
+ if (0 != doc) {
671
+ doc_free(doc);
672
+ }
673
+ }
674
+
675
+ static void mark_leaf(Leaf leaf) {
676
+ if (NULL != leaf) {
677
+ switch (leaf->value_type) {
678
+ case COL_VAL:
679
+ if (NULL != leaf->elements) {
680
+ Leaf first = leaf->elements->next;
681
+ Leaf e = first;
682
+
683
+ do {
684
+ mark_leaf(e);
685
+ e = e->next;
686
+ } while (e != first);
687
+ }
688
+ break;
689
+ case RUBY_VAL: mark(leaf->value); break;
690
+
691
+ default: break;
692
+ }
693
+ }
694
+ }
695
+
696
+ static void mark_doc(void *ptr) {
697
+ if (NULL != ptr) {
698
+ Doc doc = (Doc)ptr;
699
+
700
+ mark(doc->self);
701
+ mark_leaf(doc->data);
702
+ }
703
+ }
704
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
705
+ static void compact_leaf(Leaf leaf) {
706
+ switch (leaf->value_type) {
707
+ case COL_VAL:
708
+ if (NULL != leaf->elements) {
709
+ Leaf first = leaf->elements->next;
710
+ Leaf e = first;
711
+
712
+ do {
713
+ compact_leaf(e);
714
+ e = e->next;
715
+ } while (e != first);
716
+ }
717
+ break;
718
+ case RUBY_VAL: leaf->value = rb_gc_location(leaf->value); break;
719
+
720
+ default: break;
721
+ }
722
+ }
723
+
724
+ static void compact_doc(void *ptr) {
725
+ Doc doc = (Doc)ptr;
726
+
727
+ if (doc) {
728
+ doc->self = rb_gc_location(doc->self);
729
+ compact_leaf(doc->data);
730
+ }
731
+ }
732
+ #endif
733
+
734
+ static const rb_data_type_t oj_doc_type = {
735
+ "oj_windows/doc",
736
+ {
737
+ mark_doc,
738
+ free_doc_cb,
739
+ NULL,
740
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
741
+ compact_doc,
742
+ #endif
743
+ },
744
+ 0,
745
+ 0,
746
+ };
747
+
748
+ static VALUE parse_json(VALUE clas, char *json, bool given) {
749
+ struct _parseInfo pi;
750
+ volatile VALUE result = Qnil;
751
+ Doc doc;
752
+ int ex = 0;
753
+ volatile VALUE self;
754
+
755
+ doc = OJ_R_ALLOC_N(struct _doc, 1);
756
+
757
+ // skip UTF-8 BOM if present
758
+ if (0xEF == (uint8_t)*json && 0xBB == (uint8_t)json[1] && 0xBF == (uint8_t)json[2]) {
759
+ pi.str = json + 3;
760
+ } else {
761
+ pi.str = json;
762
+ }
763
+ pi.s = pi.str;
764
+ doc_init(doc);
765
+ pi.doc = doc;
766
+ #if IS_WINDOWS || !defined(HAVE_GETRLIMIT)
767
+ // assume a 1M stack and give half to ruby
768
+ pi.stack_min = (void *)((char *)&pi - (512L * 1024L));
769
+ #else
770
+ {
771
+ struct rlimit lim;
772
+
773
+ if (0 == getrlimit(RLIMIT_STACK, &lim) && RLIM_INFINITY != lim.rlim_cur) {
774
+ // let 3/4ths of the stack be used only
775
+ pi.stack_min = (void *)((char *)&lim - (lim.rlim_cur / 4 * 3));
776
+ } else {
777
+ pi.stack_min = 0; // indicates not to check stack limit
778
+ }
779
+ }
780
+ #endif
781
+ doc->json = json;
782
+ self = TypedData_Wrap_Struct(clas, &oj_doc_type, doc);
783
+ doc->self = self;
784
+ result = rb_protect(protect_open_proc, (VALUE)&pi, &ex);
785
+ if (given || 0 != ex) {
786
+ DATA_PTR(doc->self) = NULL;
787
+ // TBD is this needed?
788
+ /*
789
+ doc_free(pi.doc);
790
+ if (0 != ex) { // will jump so caller will not free
791
+ OJ_R_FREE(json);
792
+ }
793
+ */
794
+ } else {
795
+ result = doc->self;
796
+ }
797
+ if (0 != ex) {
798
+ rb_jump_tag(ex);
799
+ }
800
+ return result;
801
+ }
802
+
803
+ static Leaf get_doc_leaf(Doc doc, const char *path) {
804
+ Leaf leaf = *doc->where;
805
+
806
+ if (0 != doc->data && 0 != path) {
807
+ Leaf stack[MAX_STACK];
808
+ Leaf *lp;
809
+
810
+ if ('/' == *path) {
811
+ path++;
812
+ *stack = doc->data;
813
+ lp = stack;
814
+ } else if (doc->where == doc->where_path) {
815
+ *stack = doc->data;
816
+ lp = stack;
817
+ } else {
818
+ size_t cnt = doc->where - doc->where_path;
819
+
820
+ if (MAX_STACK <= cnt) {
821
+ rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
822
+ }
823
+ memcpy(stack, doc->where_path, sizeof(Leaf) * (cnt + 1));
824
+ lp = stack + cnt;
825
+ }
826
+ return get_leaf(stack, lp, path);
827
+ }
828
+ return leaf;
829
+ }
830
+
831
+ static const char *next_slash(const char *s) {
832
+ for (; '\0' != *s; s++) {
833
+ if ('\\' == *s) {
834
+ s++;
835
+ if ('\0' == *s) {
836
+ break;
837
+ }
838
+ } else if ('/' == *s) {
839
+ return s;
840
+ }
841
+ }
842
+ return NULL;
843
+ }
844
+
845
+ static bool key_match(const char *pat, const char *key, int plen) {
846
+ for (; 0 < plen; plen--, pat++, key++) {
847
+ if ('\\' == *pat) {
848
+ plen--;
849
+ pat++;
850
+ }
851
+ if (*pat != *key) {
852
+ return false;
853
+ }
854
+ }
855
+ return '\0' == *key;
856
+ }
857
+
858
+ static Leaf get_leaf(Leaf *stack, Leaf *lp, const char *path) {
859
+ Leaf leaf = *lp;
860
+
861
+ if (MAX_STACK <= lp - stack) {
862
+ rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
863
+ }
864
+ if ('\0' != *path) {
865
+ if ('.' == *path && '.' == *(path + 1)) {
866
+ path += 2;
867
+ if ('/' == *path) {
868
+ path++;
869
+ }
870
+ if (stack < lp) {
871
+ leaf = get_leaf(stack, lp - 1, path);
872
+ } else {
873
+ return 0;
874
+ }
875
+ } else if (NULL == leaf->elements) {
876
+ leaf = NULL;
877
+ } else if (STR_VAL == leaf->value_type || RUBY_VAL == leaf->value_type) {
878
+ // We are trying to get a children of a leaf, which
879
+ // doesn't exist.
880
+ leaf = NULL;
881
+ } else if (COL_VAL == leaf->value_type) {
882
+ Leaf first = leaf->elements->next;
883
+ Leaf e = first;
884
+ int type = leaf->rtype;
885
+
886
+ leaf = NULL;
887
+ if (T_ARRAY == type) {
888
+ int cnt = 0;
889
+
890
+ for (; '0' <= *path && *path <= '9'; path++) {
891
+ cnt = cnt * 10 + (*path - '0');
892
+ }
893
+ if ('/' == *path) {
894
+ path++;
895
+ }
896
+ do {
897
+ if (1 >= cnt) {
898
+ lp++;
899
+ *lp = e;
900
+ leaf = get_leaf(stack, lp, path);
901
+ break;
902
+ }
903
+ cnt--;
904
+ e = e->next;
905
+ } while (e != first);
906
+ } else if (T_HASH == type) {
907
+ const char *key = path;
908
+ const char *slash = next_slash(path);
909
+ int klen;
910
+
911
+ leaf = NULL;
912
+ if (0 == slash) {
913
+ klen = (int)strlen(key);
914
+ path += klen;
915
+ } else {
916
+ klen = (int)(slash - key);
917
+ path += klen + 1;
918
+ }
919
+ do {
920
+ if (key_match(key, e->key, klen)) {
921
+ lp++;
922
+ *lp = e;
923
+ leaf = get_leaf(stack, lp, path);
924
+ break;
925
+ }
926
+ e = e->next;
927
+ } while (e != first);
928
+ }
929
+ }
930
+ }
931
+ return leaf;
932
+ }
933
+
934
+ static void each_leaf(Doc doc, VALUE self) {
935
+ if (COL_VAL == (*doc->where)->value_type) {
936
+ if (0 != (*doc->where)->elements) {
937
+ Leaf first = (*doc->where)->elements->next;
938
+ Leaf e = first;
939
+
940
+ doc->where++;
941
+ if (MAX_STACK <= doc->where - doc->where_path) {
942
+ rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
943
+ }
944
+ do {
945
+ *doc->where = e;
946
+ each_leaf(doc, self);
947
+ e = e->next;
948
+ } while (e != first);
949
+ doc->where--;
950
+ }
951
+ } else {
952
+ rb_yield(self);
953
+ }
954
+ }
955
+
956
+ static int move_step(Doc doc, const char *path, int loc) {
957
+ if (MAX_STACK <= doc->where - doc->where_path) {
958
+ rb_raise(rb_const_get_at(Oj, rb_intern("DepthError")), "Path too deep. Limit is %d levels.", MAX_STACK);
959
+ }
960
+ if ('\0' == *path) {
961
+ loc = 0;
962
+ } else {
963
+ Leaf leaf;
964
+
965
+ if (0 == doc->where || 0 == (leaf = *doc->where)) {
966
+ printf("*** Internal error at %s\n", path);
967
+ return loc;
968
+ }
969
+ if ('.' == *path && '.' == *(path + 1)) {
970
+ Leaf init = *doc->where;
971
+
972
+ path += 2;
973
+ if (doc->where == doc->where_path) {
974
+ return loc;
975
+ }
976
+ if ('/' == *path) {
977
+ path++;
978
+ }
979
+ *doc->where = 0;
980
+ doc->where--;
981
+ loc = move_step(doc, path, loc + 1);
982
+ if (0 != loc) {
983
+ *doc->where = init;
984
+ doc->where++;
985
+ }
986
+ } else if (COL_VAL == leaf->value_type && 0 != leaf->elements) {
987
+ Leaf first = leaf->elements->next;
988
+ Leaf e = first;
989
+
990
+ if (T_ARRAY == leaf->rtype) {
991
+ int cnt = 0;
992
+
993
+ for (; '0' <= *path && *path <= '9'; path++) {
994
+ cnt = cnt * 10 + (*path - '0');
995
+ }
996
+ if ('/' == *path) {
997
+ path++;
998
+ } else if ('\0' != *path) {
999
+ return loc;
1000
+ }
1001
+ do {
1002
+ if (1 >= cnt) {
1003
+ doc->where++;
1004
+ *doc->where = e;
1005
+ loc = move_step(doc, path, loc + 1);
1006
+ if (0 != loc) {
1007
+ *doc->where = 0;
1008
+ doc->where--;
1009
+ }
1010
+ break;
1011
+ }
1012
+ cnt--;
1013
+ e = e->next;
1014
+ } while (e != first);
1015
+ } else if (T_HASH == leaf->rtype) {
1016
+ const char *key = path;
1017
+ const char *slash = next_slash(path);
1018
+ int klen;
1019
+
1020
+ if (0 == slash) {
1021
+ klen = (int)strlen(key);
1022
+ path += klen;
1023
+ } else {
1024
+ klen = (int)(slash - key);
1025
+ path += klen + 1;
1026
+ }
1027
+ do {
1028
+ if (key_match(key, e->key, klen)) {
1029
+ doc->where++;
1030
+ *doc->where = e;
1031
+ loc = move_step(doc, path, loc + 1);
1032
+ if (0 != loc) {
1033
+ *doc->where = 0;
1034
+ doc->where--;
1035
+ }
1036
+ break;
1037
+ }
1038
+ e = e->next;
1039
+ } while (e != first);
1040
+ }
1041
+ }
1042
+ }
1043
+ return loc;
1044
+ }
1045
+
1046
+ static void each_value(Doc doc, Leaf leaf) {
1047
+ if (COL_VAL == leaf->value_type) {
1048
+ if (0 != leaf->elements) {
1049
+ Leaf first = leaf->elements->next;
1050
+ Leaf e = first;
1051
+
1052
+ do {
1053
+ each_value(doc, e);
1054
+ e = e->next;
1055
+ } while (e != first);
1056
+ }
1057
+ } else {
1058
+ rb_yield(leaf_value(doc, leaf));
1059
+ }
1060
+ }
1061
+
1062
+ // doc functions
1063
+
1064
+ /* @overload open(json) { |doc| ... } => Object
1065
+ *
1066
+ * Parses a JSON document String and then yields to the provided block if one
1067
+ * is given with an instance of the Oj::Doc as the single yield parameter. If
1068
+ * a block is not given then an Oj::Doc instance is returned and must be
1069
+ * closed with a call to the #close() method when no longer needed.
1070
+ *
1071
+ * @param [String] json JSON document string
1072
+ * @yieldparam [Oj::Doc] doc parsed JSON document
1073
+ * @yieldreturn [Object] returns the result of the yield as the result of the
1074
+ * method call
1075
+ * @example
1076
+ * Oj::Doc.open('[1,2,3]') { |doc| doc.size() } #=> 4
1077
+ * # or as an alternative
1078
+ * doc = Oj::Doc.open('[1,2,3]')
1079
+ * doc.size() #=> 4
1080
+ * doc.close()
1081
+ */
1082
+ static VALUE doc_open(VALUE clas, VALUE str) {
1083
+ char *json;
1084
+ size_t len;
1085
+ volatile VALUE obj;
1086
+ int given = rb_block_given_p();
1087
+
1088
+ Check_Type(str, T_STRING);
1089
+ len = RSTRING_LEN(str) + 1;
1090
+ json = OJ_R_ALLOC_N(char, len);
1091
+
1092
+ memcpy(json, StringValuePtr(str), len);
1093
+ obj = parse_json(clas, json, given);
1094
+ // TBD is this needed
1095
+ /*
1096
+ if (given) {
1097
+ OJ_R_FREE(json);
1098
+ }
1099
+ */
1100
+ return obj;
1101
+ }
1102
+
1103
+ /* @overload open_file(filename) { |doc| ... } => Object
1104
+ *
1105
+ * Parses a JSON document from a file and then yields to the provided block if
1106
+ * one is given with an instance of the Oj::Doc as the single yield
1107
+ * parameter. If a block is not given then an Oj::Doc instance is returned and
1108
+ * must be closed with a call to the #close() method when no longer needed.
1109
+ *
1110
+ * @param [String] filename name of file that contains a JSON document
1111
+ * @yieldparam [Oj::Doc] doc parsed JSON document
1112
+ * @yieldreturn [Object] returns the result of the yield as the result of the
1113
+ * method call
1114
+ * @example
1115
+ * File.open('array.json', 'w') { |f| f.write('[1,2,3]') }
1116
+ * Oj::Doc.open_file(filename) { |doc| doc.size() } #=> 4
1117
+ * # or as an alternative
1118
+ * doc = Oj::Doc.open_file(filename)
1119
+ * doc.size() #=> 4
1120
+ * doc.close()
1121
+ */
1122
+ static VALUE doc_open_file(VALUE clas, VALUE filename) {
1123
+ char *path;
1124
+ char *json;
1125
+ FILE *f;
1126
+ size_t len;
1127
+ volatile VALUE obj;
1128
+ int given = rb_block_given_p();
1129
+
1130
+ path = StringValuePtr(filename);
1131
+ if (0 == (f = fopen(path, "r"))) {
1132
+ rb_raise(rb_eIOError, "%s", strerror(errno));
1133
+ }
1134
+ fseek(f, 0, SEEK_END);
1135
+ len = ftell(f);
1136
+ json = OJ_R_ALLOC_N(char, len + 1);
1137
+
1138
+ fseek(f, 0, SEEK_SET);
1139
+ if (len != fread(json, 1, len, f)) {
1140
+ fclose(f);
1141
+ rb_raise(rb_const_get_at(Oj, rb_intern("LoadError")),
1142
+ "Failed to read %lu bytes from %s.",
1143
+ (unsigned long)len,
1144
+ path);
1145
+ }
1146
+ fclose(f);
1147
+ json[len] = '\0';
1148
+ obj = parse_json(clas, json, given);
1149
+ // TBD is this needed
1150
+ /*
1151
+ if (given) {
1152
+ OJ_R_FREE(json);
1153
+ }
1154
+ */
1155
+ return obj;
1156
+ }
1157
+
1158
+ static int esc_strlen(const char *s) {
1159
+ int cnt = 0;
1160
+
1161
+ for (; '\0' != *s; s++, cnt++) {
1162
+ if ('/' == *s) {
1163
+ cnt++;
1164
+ }
1165
+ }
1166
+ return cnt;
1167
+ }
1168
+
1169
+ static char *append_key(char *p, const char *key) {
1170
+ for (; '\0' != *key; p++, key++) {
1171
+ if ('/' == *key) {
1172
+ *p++ = '\\';
1173
+ }
1174
+ *p = *key;
1175
+ }
1176
+ return p;
1177
+ }
1178
+
1179
+ /* Document-method: parse
1180
+ * @see Oj::Doc.open
1181
+ */
1182
+
1183
+ /* @overload where() => String
1184
+ *
1185
+ * Returns a String that describes the absolute path to the current location
1186
+ * in the JSON document.
1187
+ */
1188
+ static VALUE doc_where(VALUE self) {
1189
+ Doc doc = self_doc(self);
1190
+
1191
+ if (0 == *doc->where_path || doc->where == doc->where_path) {
1192
+ return oj_slash_string;
1193
+ } else {
1194
+ Leaf *lp;
1195
+ Leaf leaf;
1196
+ size_t size = 3; // leading / and terminating \0
1197
+ char *path;
1198
+ char *p;
1199
+
1200
+ for (lp = doc->where_path; lp <= doc->where; lp++) {
1201
+ leaf = *lp;
1202
+ if (T_HASH == leaf->parent_type) {
1203
+ size += esc_strlen((*lp)->key) + 1;
1204
+ } else if (T_ARRAY == leaf->parent_type) {
1205
+ size += ((*lp)->index < 100) ? 3 : 11;
1206
+ }
1207
+ }
1208
+ path = ALLOCA_N(char, size);
1209
+ p = path;
1210
+ for (lp = doc->where_path; lp <= doc->where; lp++) {
1211
+ leaf = *lp;
1212
+ if (T_HASH == leaf->parent_type) {
1213
+ p = append_key(p, (*lp)->key);
1214
+ } else if (T_ARRAY == leaf->parent_type) {
1215
+ p = ulong_fill(p, (*lp)->index);
1216
+ }
1217
+ *p++ = '/';
1218
+ }
1219
+ *--p = '\0';
1220
+
1221
+ return rb_str_new(path, p - path);
1222
+ }
1223
+ }
1224
+
1225
+ /* @overload where?() => String
1226
+ * @deprecated
1227
+ * Returns a String that describes the absolute path to the current location
1228
+ * in the JSON document.
1229
+ */
1230
+ static VALUE doc_where_q(VALUE self) {
1231
+ return doc_where(self);
1232
+ }
1233
+
1234
+ /* @overload path() => String
1235
+ *
1236
+ * Returns a String that describes the absolute path to the current location
1237
+ * in the JSON document.
1238
+ */
1239
+ static VALUE doc_path(VALUE self) {
1240
+ return doc_where(self);
1241
+ }
1242
+
1243
+ /* @overload local_key() => String, Fixnum, nil
1244
+ *
1245
+ * Returns the final key to the current location.
1246
+ * @example
1247
+ * Oj::Doc.open('[1,2,3]') { |doc| doc.move('/2'); doc.local_key() } #=> 2
1248
+ * Oj::Doc.open('{"one":3}') { |doc| doc.move('/one'); doc.local_key() } #=>
1249
+ * "one" Oj::Doc.open('[1,2,3]') { |doc| doc.local_key() }
1250
+ * #=> nil
1251
+ */
1252
+ static VALUE doc_local_key(VALUE self) {
1253
+ Doc doc = self_doc(self);
1254
+ Leaf leaf = *doc->where;
1255
+ volatile VALUE key = Qnil;
1256
+
1257
+ if (T_HASH == leaf->parent_type) {
1258
+ key = rb_utf8_str_new_cstr(leaf->key);
1259
+ } else if (T_ARRAY == leaf->parent_type) {
1260
+ key = LONG2NUM(leaf->index);
1261
+ }
1262
+ return key;
1263
+ }
1264
+
1265
+ /* @overload home() => nil
1266
+ *
1267
+ * Moves the document marker or location to the hoot or home position. The
1268
+ * same operation can be performed with a Oj::Doc.move('/').
1269
+ * @example
1270
+ * Oj::Doc.open('[1,2,3]') { |doc| doc.move('/2'); doc.home(); doc.where? }
1271
+ * #=> '/'
1272
+ */
1273
+ static VALUE doc_home(VALUE self) {
1274
+ Doc doc = self_doc(self);
1275
+
1276
+ *doc->where_path = doc->data;
1277
+ doc->where = doc->where_path;
1278
+
1279
+ return oj_slash_string;
1280
+ }
1281
+
1282
+ /* @overload type(path=nil) => Class
1283
+ *
1284
+ * Returns the Class of the data value at the location identified by the path
1285
+ * or the current location if the path is nil or not provided. This method
1286
+ * does not create the Ruby Object at the location specified so the overhead
1287
+ * is low.
1288
+ * @param [String] path path to the location to get the type of if provided
1289
+ * @example
1290
+ * Oj::Doc.open('[1,2]') { |doc| doc.type() } #=> Array
1291
+ * Oj::Doc.open('[1,2]') { |doc| doc.type('/1') } #=> Fixnum
1292
+ */
1293
+ static VALUE doc_type(int argc, VALUE *argv, VALUE self) {
1294
+ Doc doc = self_doc(self);
1295
+ Leaf leaf;
1296
+ const char *path = 0;
1297
+ VALUE type = Qnil;
1298
+
1299
+ if (1 <= argc) {
1300
+ path = StringValuePtr(*argv);
1301
+ }
1302
+ if (0 != (leaf = get_doc_leaf(doc, path))) {
1303
+ switch (leaf->rtype) {
1304
+ case T_NIL: type = rb_cNilClass; break;
1305
+ case T_TRUE: type = rb_cTrueClass; break;
1306
+ case T_FALSE: type = rb_cFalseClass; break;
1307
+ case T_STRING: type = rb_cString; break;
1308
+ case T_FIXNUM: type = rb_cInteger; break;
1309
+ case T_FLOAT: type = rb_cFloat; break;
1310
+ case T_ARRAY: type = rb_cArray; break;
1311
+ case T_HASH: type = rb_cHash; break;
1312
+ default: break;
1313
+ }
1314
+ }
1315
+ return type;
1316
+ }
1317
+
1318
+ /* @overload fetch(path=nil,default=nil) => nil, true, false, Fixnum, Float, String, Array,
1319
+ * Hash
1320
+ *
1321
+ * Returns the value at the location identified by the path or the current
1322
+ * location if the path is nil or not provided. This method will create and
1323
+ * return an Array or Hash if that is the type of Object at the location
1324
+ * specified. This is more expensive than navigating to the leaves of the JSON
1325
+ * document. If a default is provided that is used if no value if found.
1326
+ * @param [String] path path to the location to get the type of if provided
1327
+ * @example
1328
+ * Oj::Doc.open('[1,2]') { |doc| doc.fetch() } #=> [1, 2]
1329
+ * Oj::Doc.open('[1,2]') { |doc| doc.fetch('/1') } #=> 1
1330
+ */
1331
+ static VALUE doc_fetch(int argc, VALUE *argv, VALUE self) {
1332
+ Doc doc;
1333
+ Leaf leaf;
1334
+ volatile VALUE val = Qnil;
1335
+ const char *path = 0;
1336
+
1337
+ doc = self_doc(self);
1338
+ if (1 <= argc) {
1339
+ path = StringValuePtr(*argv);
1340
+ if (2 == argc) {
1341
+ val = argv[1];
1342
+ }
1343
+ }
1344
+ if (0 != (leaf = get_doc_leaf(doc, path))) {
1345
+ val = leaf_value(doc, leaf);
1346
+ }
1347
+ return val;
1348
+ }
1349
+
1350
+ /* @overload exists?(path) => true, false
1351
+ *
1352
+ * Returns true if the value at the location identified by the path exists.
1353
+ * @param [String] path path to the location
1354
+ * @example
1355
+ * Oj::Doc.open('[1,2]') { |doc| doc.exists?('/1') } #=> true
1356
+ * Oj::Doc.open('[1,2]') { |doc| doc.exists?('/3') } #=> false
1357
+ */
1358
+ static VALUE doc_exists(VALUE self, VALUE str) {
1359
+ Doc doc;
1360
+ Leaf leaf;
1361
+
1362
+ doc = self_doc(self);
1363
+ if (0 != (leaf = get_doc_leaf(doc, StringValuePtr(str)))) {
1364
+ if (NULL != leaf) {
1365
+ return Qtrue;
1366
+ }
1367
+ }
1368
+ return Qfalse;
1369
+ }
1370
+
1371
+ /* @overload each_leaf(path=nil) => nil
1372
+ *
1373
+ * Yields to the provided block for each leaf node with the identified
1374
+ * location of the JSON document as the root. The parameter passed to the
1375
+ * block on yield is the Doc instance after moving to the child location.
1376
+ * @param [String] path if provided it identified the top of the branch to
1377
+ * process the leaves of
1378
+ * @yieldparam [Doc] Doc at the child location
1379
+ * @example
1380
+ * Oj::Doc.open('[3,[2,1]]') { |doc|
1381
+ * result = {}
1382
+ * doc.each_leaf() { |d| result[d.where?] = d.fetch() }
1383
+ * result
1384
+ * }
1385
+ * #=> ["/1" => 3, "/2/1" => 2, "/2/2" => 1]
1386
+ */
1387
+ static VALUE doc_each_leaf(int argc, VALUE *argv, VALUE self) {
1388
+ if (rb_block_given_p()) {
1389
+ Leaf save_path[MAX_STACK];
1390
+ Doc doc = self_doc(self);
1391
+ const char *path = 0;
1392
+ size_t wlen;
1393
+
1394
+ wlen = doc->where - doc->where_path;
1395
+ if (0 < wlen) {
1396
+ memcpy(save_path, doc->where_path, sizeof(Leaf) * (wlen + 1));
1397
+ }
1398
+ if (1 <= argc) {
1399
+ path = StringValuePtr(*argv);
1400
+ if ('/' == *path) {
1401
+ doc->where = doc->where_path;
1402
+ path++;
1403
+ }
1404
+ if (0 != move_step(doc, path, 1)) {
1405
+ if (0 < wlen) {
1406
+ memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
1407
+ }
1408
+ return Qnil;
1409
+ }
1410
+ }
1411
+ each_leaf(doc, self);
1412
+ if (0 < wlen) {
1413
+ memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
1414
+ }
1415
+ }
1416
+ return Qnil;
1417
+ }
1418
+
1419
+ /* @overload move(path) => nil
1420
+ *
1421
+ * Moves the document marker to the path specified. The path can an absolute
1422
+ * path or a relative path.
1423
+ * @param [String] path path to the location to move to
1424
+ * @example
1425
+ * Oj::Doc.open('{"one":[1,2]') { |doc| doc.move('/one/2'); doc.where? } #=>
1426
+ * "/one/2"
1427
+ */
1428
+ static VALUE doc_move(VALUE self, VALUE str) {
1429
+ Doc doc = self_doc(self);
1430
+ const char *path;
1431
+ int loc;
1432
+
1433
+ path = StringValuePtr(str);
1434
+ if ('/' == *path) {
1435
+ doc->where = doc->where_path;
1436
+ path++;
1437
+ }
1438
+ if (0 != (loc = move_step(doc, path, 1))) {
1439
+ rb_raise(rb_eArgError, "Failed to locate element %d of the path %s.", loc, path);
1440
+ }
1441
+ return Qnil;
1442
+ }
1443
+
1444
+ /* @overload each_child(path=nil) { |doc| ... } => nil
1445
+ *
1446
+ * Yields to the provided block for each immediate child node with the
1447
+ * identified location of the JSON document as the root. The parameter passed
1448
+ * to the block on yield is the Doc instance after moving to the child
1449
+ * location.
1450
+ * @param [String] path if provided it identified the top of the branch to
1451
+ * process the children of
1452
+ * @yieldparam [Doc] Doc at the child location
1453
+ * @example
1454
+ * Oj::Doc.open('[3,[2,1]]') { |doc|
1455
+ * result = []
1456
+ * doc.each_value('/2') { |doc| result << doc.where? }
1457
+ * result
1458
+ * }
1459
+ * #=> ["/2/1", "/2/2"]
1460
+ */
1461
+ static VALUE doc_each_child(int argc, VALUE *argv, VALUE self) {
1462
+ if (rb_block_given_p()) {
1463
+ Leaf save_path[MAX_STACK];
1464
+ Doc doc = self_doc(self);
1465
+ const char *path = 0;
1466
+ size_t wlen;
1467
+ Leaf *where_orig = doc->where;
1468
+
1469
+ wlen = doc->where - doc->where_path;
1470
+ if (0 < wlen) {
1471
+ memcpy(save_path, doc->where_path, sizeof(Leaf) * (wlen + 1));
1472
+ }
1473
+ if (1 <= argc) {
1474
+ path = StringValuePtr(*argv);
1475
+ if ('/' == *path) {
1476
+ doc->where = doc->where_path;
1477
+ path++;
1478
+ }
1479
+ if (0 != move_step(doc, path, 1)) {
1480
+ if (0 < wlen) {
1481
+ memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
1482
+ }
1483
+ doc->where = where_orig;
1484
+ return Qnil;
1485
+ }
1486
+ }
1487
+ if (NULL == doc->where || NULL == *doc->where) {
1488
+ return Qnil;
1489
+ }
1490
+ if (COL_VAL == (*doc->where)->value_type && 0 != (*doc->where)->elements) {
1491
+ Leaf first = (*doc->where)->elements->next;
1492
+ Leaf e = first;
1493
+
1494
+ doc->where++;
1495
+ do {
1496
+ *doc->where = e;
1497
+ rb_yield(self);
1498
+ e = e->next;
1499
+ } while (e != first);
1500
+ }
1501
+ if (0 < wlen) {
1502
+ memcpy(doc->where_path, save_path, sizeof(Leaf) * (wlen + 1));
1503
+ }
1504
+ doc->where = where_orig;
1505
+ }
1506
+ return Qnil;
1507
+ }
1508
+
1509
+ /* @overload each_value(path=nil) { |val| ... } => nil
1510
+ *
1511
+ * Yields to the provided block for each leaf value in the identified location
1512
+ * of the JSON document. The parameter passed to the block on yield is the
1513
+ * value of the leaf. Only those leaves below the element specified by the
1514
+ * path parameter are processed.
1515
+ * @param [String] path if provided it identified the top of the branch to
1516
+ * process the leaf values of
1517
+ * @yieldparam [Object] val each leaf value
1518
+ * @example
1519
+ * Oj::Doc.open('[3,[2,1]]') { |doc|
1520
+ * result = []
1521
+ * doc.each_value() { |v| result << v }
1522
+ * result
1523
+ * }
1524
+ * #=> [3, 2, 1]
1525
+ *
1526
+ * Oj::Doc.open('[3,[2,1]]') { |doc|
1527
+ * result = []
1528
+ * doc.each_value('/2') { |v| result << v }
1529
+ * result
1530
+ * }
1531
+ * #=> [2, 1]
1532
+ */
1533
+ static VALUE doc_each_value(int argc, VALUE *argv, VALUE self) {
1534
+ if (rb_block_given_p()) {
1535
+ Doc doc = self_doc(self);
1536
+ const char *path = 0;
1537
+ Leaf leaf;
1538
+
1539
+ if (1 <= argc) {
1540
+ path = StringValuePtr(*argv);
1541
+ }
1542
+ if (0 != (leaf = get_doc_leaf(doc, path))) {
1543
+ each_value(doc, leaf);
1544
+ }
1545
+ }
1546
+ return Qnil;
1547
+ }
1548
+
1549
+ /* @overload dump(path, filename)
1550
+ *
1551
+ * Dumps the document or nodes to a new JSON document. It uses the default
1552
+ * options for generating the JSON.
1553
+ * @param path [String] if provided it identified the top of the branch to
1554
+ * dump to JSON
1555
+ * @param filename [String] if provided it is the filename to write the output
1556
+ * to
1557
+ * @example
1558
+ * Oj::Doc.open('[3,[2,1]]') { |doc|
1559
+ * doc.dump('/2')
1560
+ * }
1561
+ * #=> "[2,1]"
1562
+ */
1563
+ static VALUE doc_dump(int argc, VALUE *argv, VALUE self) {
1564
+ Doc doc = self_doc(self);
1565
+ Leaf leaf;
1566
+ const char *path = 0;
1567
+ const char *filename = 0;
1568
+
1569
+ if (1 <= argc) {
1570
+ if (Qnil != *argv) {
1571
+ path = StringValuePtr(*argv);
1572
+ }
1573
+ if (2 <= argc) {
1574
+ filename = StringValuePtr(argv[1]);
1575
+ }
1576
+ }
1577
+ if (0 != (leaf = get_doc_leaf(doc, path))) {
1578
+ volatile VALUE rjson;
1579
+
1580
+ if (0 == filename) {
1581
+ struct _out out;
1582
+
1583
+ oj_out_init(&out);
1584
+
1585
+ out.omit_nil = oj_default_options.dump_opts.omit_nil;
1586
+ oj_dump_leaf_to_json(leaf, &oj_default_options, &out);
1587
+ rjson = rb_str_new2(out.buf);
1588
+
1589
+ oj_out_free(&out);
1590
+ } else {
1591
+ oj_write_leaf_to_file(leaf, filename, &oj_default_options);
1592
+ rjson = Qnil;
1593
+ }
1594
+ return rjson;
1595
+ }
1596
+ return Qnil;
1597
+ }
1598
+
1599
+ /* @overload size() => Fixnum
1600
+ *
1601
+ * Returns the number of nodes in the JSON document where a node is any one of
1602
+ * the basic JSON components.
1603
+ * @return Returns the size of the JSON document.
1604
+ * @example
1605
+ * Oj::Doc.open('[1,2,3]') { |doc| doc.size() } #=> 4
1606
+ */
1607
+ static VALUE doc_size(VALUE self) {
1608
+ Doc d;
1609
+ TypedData_Get_Struct(self, struct _doc, &oj_doc_type, d);
1610
+ return ULONG2NUM(d->size);
1611
+ }
1612
+
1613
+ /* @overload close() => nil
1614
+ *
1615
+ * Closes an open document. No further calls to the document will be valid
1616
+ * after closing.
1617
+ * @example
1618
+ * doc = Oj::Doc.open('[1,2,3]')
1619
+ * doc.size() #=> 4
1620
+ * doc.close()
1621
+ */
1622
+ static VALUE doc_close(VALUE self) {
1623
+ Doc doc = self_doc(self);
1624
+
1625
+ rb_gc_unregister_address(&doc->self);
1626
+ DATA_PTR(doc->self) = NULL;
1627
+ if (0 != doc) {
1628
+ doc_free(doc);
1629
+ }
1630
+ return Qnil;
1631
+ }
1632
+ #if 0
1633
+ // hack to keep the doc generator happy
1634
+ Oj = rb_define_module("Oj");
1635
+ #endif
1636
+
1637
+ static VALUE doc_not_implemented(VALUE self) {
1638
+ rb_raise(rb_eNotImpError, "Not implemented.");
1639
+ return Qnil;
1640
+ }
1641
+
1642
+ /* Document-class: Oj::Doc
1643
+ *
1644
+ * The Doc class is used to parse and navigate a JSON document. The model it
1645
+ * employs is that of a document that while open can be navigated and values
1646
+ * extracted. Once the document is closed the document can not longer be
1647
+ * accessed. This allows the parsing and data extraction to be extremely fast
1648
+ * compared to other JSON parses.
1649
+ *
1650
+ * An Oj::Doc class is not created directly but the _open()_ class method is
1651
+ * used to open a document and the yield parameter to the block of the #open()
1652
+ * call is the Doc instance. The Doc instance can be moved across, up, and
1653
+ * down the JSON document. At each element the data associated with the
1654
+ * element can be extracted. It is also possible to just provide a path to the
1655
+ * data to be extracted and retrieve the data in that manner.
1656
+ *
1657
+ * For many of the methods a path is used to describe the location of an
1658
+ * element. Paths follow a subset of the XPath syntax. The slash ('/')
1659
+ * character is the separator. Each step in the path identifies the next
1660
+ * branch to take through the document. A JSON object will expect a key string
1661
+ * while an array will expect a positive index. A .. step indicates a move up
1662
+ * the JSON document.
1663
+ *
1664
+ * @example
1665
+ * json = %{[
1666
+ * {
1667
+ * "one" : 1,
1668
+ * "two" : 2
1669
+ * },
1670
+ * {
1671
+ * "three" : 3,
1672
+ * "four" : 4
1673
+ * }
1674
+ * ]}
1675
+ * # move and get value
1676
+ * Oj::Doc.open(json) do |doc|
1677
+ * doc.move('/1/two')
1678
+ * # doc location is now at the 'two' element of the hash that is the first
1679
+ * element of the array. doc.fetch() end
1680
+ * #=> 2
1681
+ *
1682
+ * # Now try again using a path to Oj::Doc.fetch() directly and not using a
1683
+ * block. doc = Oj::Doc.open(json) doc.fetch('/2/three') #=> 3 doc.close()
1684
+ */
1685
+ void oj_init_doc(void) {
1686
+ oj_doc_class = rb_define_class_under(Oj, "Doc", rb_cObject);
1687
+ rb_gc_register_address(&oj_doc_class);
1688
+ rb_undef_alloc_func(oj_doc_class);
1689
+ rb_define_singleton_method(oj_doc_class, "open", doc_open, 1);
1690
+ rb_define_singleton_method(oj_doc_class, "open_file", doc_open_file, 1);
1691
+ rb_define_singleton_method(oj_doc_class, "parse", doc_open, 1);
1692
+ rb_define_method(oj_doc_class, "where?", doc_where_q, 0);
1693
+ rb_define_method(oj_doc_class, "where", doc_where, 0);
1694
+ rb_define_method(oj_doc_class, "path", doc_path, 0);
1695
+ rb_define_method(oj_doc_class, "local_key", doc_local_key, 0);
1696
+ rb_define_method(oj_doc_class, "home", doc_home, 0);
1697
+ rb_define_method(oj_doc_class, "type", doc_type, -1);
1698
+ rb_define_method(oj_doc_class, "fetch", doc_fetch, -1);
1699
+ rb_define_method(oj_doc_class, "exists?", doc_exists, 1);
1700
+ rb_define_method(oj_doc_class, "each_leaf", doc_each_leaf, -1);
1701
+ rb_define_method(oj_doc_class, "move", doc_move, 1);
1702
+ rb_define_method(oj_doc_class, "each_child", doc_each_child, -1);
1703
+ rb_define_method(oj_doc_class, "each_value", doc_each_value, -1);
1704
+ rb_define_method(oj_doc_class, "dump", doc_dump, -1);
1705
+ rb_define_method(oj_doc_class, "size", doc_size, 0);
1706
+ rb_define_method(oj_doc_class, "close", doc_close, 0);
1707
+
1708
+ rb_define_method(oj_doc_class, "clone", doc_not_implemented, 0);
1709
+ rb_define_method(oj_doc_class, "dup", doc_not_implemented, 0);
1710
+ }