oj 3.8.0

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