oj 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }