rage-iodine 1.7.58
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- data/.github/workflows/ruby.yml +42 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +1098 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/LIMITS.md +41 -0
- data/README.md +782 -0
- data/Rakefile +23 -0
- data/SPEC-PubSub-Draft.md +159 -0
- data/SPEC-WebSocket-Draft.md +239 -0
- data/bin/console +22 -0
- data/bin/info.md +353 -0
- data/bin/mustache_bench.rb +100 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/examples/async_task.ru +92 -0
- data/examples/bates/README.md +3 -0
- data/examples/bates/config.ru +342 -0
- data/examples/bates/david+bold.pdf +0 -0
- data/examples/bates/public/drop-pdf.png +0 -0
- data/examples/bates/public/index.html +600 -0
- data/examples/config.ru +59 -0
- data/examples/echo.ru +59 -0
- data/examples/etag.ru +16 -0
- data/examples/hello.ru +29 -0
- data/examples/pubsub_engine.ru +81 -0
- data/examples/rack3.ru +12 -0
- data/examples/redis.ru +70 -0
- data/examples/shootout.ru +73 -0
- data/examples/sub-protocols.ru +90 -0
- data/examples/tcp_client.rb +66 -0
- data/examples/x-sendfile.ru +14 -0
- data/exe/iodine +280 -0
- data/ext/iodine/extconf.rb +110 -0
- data/ext/iodine/fio.c +12096 -0
- data/ext/iodine/fio.h +6390 -0
- data/ext/iodine/fio_cli.c +431 -0
- data/ext/iodine/fio_cli.h +189 -0
- data/ext/iodine/fio_json_parser.h +687 -0
- data/ext/iodine/fio_siphash.c +157 -0
- data/ext/iodine/fio_siphash.h +37 -0
- data/ext/iodine/fio_tls.h +129 -0
- data/ext/iodine/fio_tls_missing.c +649 -0
- data/ext/iodine/fio_tls_openssl.c +1056 -0
- data/ext/iodine/fio_tmpfile.h +50 -0
- data/ext/iodine/fiobj.h +44 -0
- data/ext/iodine/fiobj4fio.h +21 -0
- data/ext/iodine/fiobj_ary.c +333 -0
- data/ext/iodine/fiobj_ary.h +139 -0
- data/ext/iodine/fiobj_data.c +1185 -0
- data/ext/iodine/fiobj_data.h +167 -0
- data/ext/iodine/fiobj_hash.c +409 -0
- data/ext/iodine/fiobj_hash.h +176 -0
- data/ext/iodine/fiobj_json.c +622 -0
- data/ext/iodine/fiobj_json.h +68 -0
- data/ext/iodine/fiobj_mem.h +71 -0
- data/ext/iodine/fiobj_mustache.c +317 -0
- data/ext/iodine/fiobj_mustache.h +62 -0
- data/ext/iodine/fiobj_numbers.c +344 -0
- data/ext/iodine/fiobj_numbers.h +127 -0
- data/ext/iodine/fiobj_str.c +433 -0
- data/ext/iodine/fiobj_str.h +172 -0
- data/ext/iodine/fiobject.c +620 -0
- data/ext/iodine/fiobject.h +654 -0
- data/ext/iodine/hpack.h +1923 -0
- data/ext/iodine/http.c +2736 -0
- data/ext/iodine/http.h +1019 -0
- data/ext/iodine/http1.c +825 -0
- data/ext/iodine/http1.h +29 -0
- data/ext/iodine/http1_parser.h +1835 -0
- data/ext/iodine/http_internal.c +1279 -0
- data/ext/iodine/http_internal.h +248 -0
- data/ext/iodine/http_mime_parser.h +350 -0
- data/ext/iodine/iodine.c +1433 -0
- data/ext/iodine/iodine.h +64 -0
- data/ext/iodine/iodine_caller.c +218 -0
- data/ext/iodine/iodine_caller.h +27 -0
- data/ext/iodine/iodine_connection.c +941 -0
- data/ext/iodine/iodine_connection.h +55 -0
- data/ext/iodine/iodine_defer.c +420 -0
- data/ext/iodine/iodine_defer.h +6 -0
- data/ext/iodine/iodine_fiobj2rb.h +120 -0
- data/ext/iodine/iodine_helpers.c +282 -0
- data/ext/iodine/iodine_helpers.h +12 -0
- data/ext/iodine/iodine_http.c +1280 -0
- data/ext/iodine/iodine_http.h +23 -0
- data/ext/iodine/iodine_json.c +302 -0
- data/ext/iodine/iodine_json.h +6 -0
- data/ext/iodine/iodine_mustache.c +567 -0
- data/ext/iodine/iodine_mustache.h +6 -0
- data/ext/iodine/iodine_pubsub.c +580 -0
- data/ext/iodine/iodine_pubsub.h +26 -0
- data/ext/iodine/iodine_rack_io.c +273 -0
- data/ext/iodine/iodine_rack_io.h +20 -0
- data/ext/iodine/iodine_store.c +142 -0
- data/ext/iodine/iodine_store.h +20 -0
- data/ext/iodine/iodine_tcp.c +346 -0
- data/ext/iodine/iodine_tcp.h +13 -0
- data/ext/iodine/iodine_tls.c +261 -0
- data/ext/iodine/iodine_tls.h +13 -0
- data/ext/iodine/mustache_parser.h +1546 -0
- data/ext/iodine/redis_engine.c +957 -0
- data/ext/iodine/redis_engine.h +79 -0
- data/ext/iodine/resp_parser.h +317 -0
- data/ext/iodine/scheduler.c +173 -0
- data/ext/iodine/scheduler.h +6 -0
- data/ext/iodine/websocket_parser.h +506 -0
- data/ext/iodine/websockets.c +752 -0
- data/ext/iodine/websockets.h +185 -0
- data/iodine.gemspec +50 -0
- data/lib/iodine/connection.rb +61 -0
- data/lib/iodine/json.rb +42 -0
- data/lib/iodine/mustache.rb +113 -0
- data/lib/iodine/pubsub.rb +55 -0
- data/lib/iodine/rack_utils.rb +43 -0
- data/lib/iodine/tls.rb +16 -0
- data/lib/iodine/version.rb +3 -0
- data/lib/iodine.rb +274 -0
- data/lib/rack/handler/iodine.rb +33 -0
- data/logo.png +0 -0
- metadata +284 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
#ifndef H_IODINE_HTTP_H
|
2
|
+
#define H_IODINE_HTTP_H
|
3
|
+
/*
|
4
|
+
Copyright: Boaz segev, 2016-2018
|
5
|
+
License: MIT
|
6
|
+
|
7
|
+
Feel free to copy, use and enjoy according to the license provided.
|
8
|
+
*/
|
9
|
+
#include "iodine.h"
|
10
|
+
|
11
|
+
/* these three are used also by rb-rack-io.c */
|
12
|
+
extern VALUE IODINE_R_INPUT;
|
13
|
+
extern VALUE IODINE_R_INPUT_DEFAULT;
|
14
|
+
extern VALUE IODINE_R_HIJACK;
|
15
|
+
extern VALUE IODINE_R_HIJACK_IO;
|
16
|
+
extern VALUE IODINE_R_HIJACK_CB;
|
17
|
+
void iodine_init_http(void);
|
18
|
+
|
19
|
+
intptr_t iodine_http_listen(iodine_connection_args_s args);
|
20
|
+
// intptr_t iodine_http_connect(iodine_connection_args_s args); // not yet...
|
21
|
+
intptr_t iodine_ws_connect(iodine_connection_args_s args);
|
22
|
+
|
23
|
+
#endif
|
@@ -0,0 +1,302 @@
|
|
1
|
+
#include "iodine.h"
|
2
|
+
|
3
|
+
#include "fio.h"
|
4
|
+
|
5
|
+
#include "fio_json_parser.h"
|
6
|
+
#include "fiobj.h"
|
7
|
+
#include "iodine_fiobj2rb.h"
|
8
|
+
#include "iodine_store.h"
|
9
|
+
|
10
|
+
static VALUE max_nesting;
|
11
|
+
static VALUE allow_nan;
|
12
|
+
static VALUE symbolize_names;
|
13
|
+
static VALUE create_additions;
|
14
|
+
static VALUE object_class;
|
15
|
+
static VALUE array_class;
|
16
|
+
|
17
|
+
#define FIO_ARY_NAME fio_json_stack
|
18
|
+
#define FIO_ARY_TYPE VALUE
|
19
|
+
#include "fio.h"
|
20
|
+
|
21
|
+
/* *****************************************************************************
|
22
|
+
JSON Callacks - these must be implemented in the C file that uses the parser
|
23
|
+
***************************************************************************** */
|
24
|
+
|
25
|
+
/* *****************************************************************************
|
26
|
+
JSON Parser handling (practically copied from the FIOBJ library)
|
27
|
+
***************************************************************************** */
|
28
|
+
|
29
|
+
typedef struct {
|
30
|
+
json_parser_s p;
|
31
|
+
VALUE key;
|
32
|
+
VALUE top;
|
33
|
+
VALUE target;
|
34
|
+
fio_json_stack_s stack;
|
35
|
+
uint8_t is_hash;
|
36
|
+
uint8_t symbolize;
|
37
|
+
} iodine_json_parser_s;
|
38
|
+
|
39
|
+
static inline void iodine_json_add2parser(iodine_json_parser_s *p, VALUE o) {
|
40
|
+
if (p->top) {
|
41
|
+
if (p->is_hash) {
|
42
|
+
if (p->key) {
|
43
|
+
rb_hash_aset(p->top, p->key, o);
|
44
|
+
IodineStore.remove(p->key);
|
45
|
+
p->key = (VALUE)0;
|
46
|
+
} else {
|
47
|
+
p->key = o;
|
48
|
+
IodineStore.add(o);
|
49
|
+
}
|
50
|
+
} else {
|
51
|
+
rb_ary_push(p->top, o);
|
52
|
+
}
|
53
|
+
} else {
|
54
|
+
IodineStore.add(o);
|
55
|
+
p->top = o;
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
/** a NULL object was detected */
|
60
|
+
static void fio_json_on_null(json_parser_s *p) {
|
61
|
+
iodine_json_add2parser((iodine_json_parser_s *)p, Qnil);
|
62
|
+
}
|
63
|
+
/** a TRUE object was detected */
|
64
|
+
static void fio_json_on_true(json_parser_s *p) {
|
65
|
+
iodine_json_add2parser((iodine_json_parser_s *)p, Qtrue);
|
66
|
+
}
|
67
|
+
/** a FALSE object was detected */
|
68
|
+
static void fio_json_on_false(json_parser_s *p) {
|
69
|
+
iodine_json_add2parser((iodine_json_parser_s *)p, Qfalse);
|
70
|
+
}
|
71
|
+
/** a Numberl was detected (long long). */
|
72
|
+
static void fio_json_on_number(json_parser_s *p, long long i) {
|
73
|
+
iodine_json_add2parser((iodine_json_parser_s *)p, LONG2NUM(i));
|
74
|
+
}
|
75
|
+
/** a Float was detected (double). */
|
76
|
+
static void fio_json_on_float(json_parser_s *p, double f) {
|
77
|
+
iodine_json_add2parser((iodine_json_parser_s *)p, DBL2NUM(f));
|
78
|
+
}
|
79
|
+
/** a String was detected (int / float). update `pos` to point at ending */
|
80
|
+
static void fio_json_on_string(json_parser_s *p, void *start, size_t length) {
|
81
|
+
/* Ruby overhead for a rb_str_buf_new is very high. Double copy is faster. */
|
82
|
+
char *tmp = fio_malloc(length);
|
83
|
+
size_t new_len = fio_json_unescape_str(tmp, start, length);
|
84
|
+
VALUE buf;
|
85
|
+
if (((iodine_json_parser_s *)p)->symbolize &&
|
86
|
+
((iodine_json_parser_s *)p)->is_hash &&
|
87
|
+
!((iodine_json_parser_s *)p)->key) {
|
88
|
+
ID id = rb_intern2(tmp, new_len);
|
89
|
+
buf = rb_id2sym(id);
|
90
|
+
} else {
|
91
|
+
buf = rb_str_new(tmp, new_len);
|
92
|
+
}
|
93
|
+
iodine_json_add2parser((iodine_json_parser_s *)p, buf);
|
94
|
+
fio_free(tmp);
|
95
|
+
}
|
96
|
+
/** a dictionary object was detected, should return 0 unless error occurred. */
|
97
|
+
static int fio_json_on_start_object(json_parser_s *p) {
|
98
|
+
iodine_json_parser_s *pr = (iodine_json_parser_s *)p;
|
99
|
+
if (pr->target) {
|
100
|
+
/* push NULL, don't free the objects */
|
101
|
+
fio_json_stack_push(&pr->stack, pr->top);
|
102
|
+
pr->top = pr->target;
|
103
|
+
pr->target = 0;
|
104
|
+
} else {
|
105
|
+
VALUE h = rb_hash_new();
|
106
|
+
iodine_json_add2parser(pr, h);
|
107
|
+
fio_json_stack_push(&pr->stack, pr->top);
|
108
|
+
pr->top = h;
|
109
|
+
}
|
110
|
+
pr->is_hash = 1;
|
111
|
+
return 0;
|
112
|
+
}
|
113
|
+
/** a dictionary object closure detected */
|
114
|
+
static void fio_json_on_end_object(json_parser_s *p) {
|
115
|
+
iodine_json_parser_s *pr = (iodine_json_parser_s *)p;
|
116
|
+
if (pr->key) {
|
117
|
+
FIO_LOG_WARNING("(JSON parsing) malformed JSON, "
|
118
|
+
"ignoring dangling Hash key.");
|
119
|
+
IodineStore.remove(pr->key);
|
120
|
+
pr->key = (VALUE)0;
|
121
|
+
}
|
122
|
+
fio_json_stack_pop(&pr->stack, &pr->top);
|
123
|
+
pr->is_hash = (TYPE(pr->top) == T_HASH);
|
124
|
+
}
|
125
|
+
/** an array object was detected */
|
126
|
+
static int fio_json_on_start_array(json_parser_s *p) {
|
127
|
+
iodine_json_parser_s *pr = (iodine_json_parser_s *)p;
|
128
|
+
if (pr->target)
|
129
|
+
return -1;
|
130
|
+
VALUE ary = rb_ary_new();
|
131
|
+
iodine_json_add2parser(pr, ary);
|
132
|
+
fio_json_stack_push(&pr->stack, pr->top);
|
133
|
+
pr->top = ary;
|
134
|
+
pr->is_hash = 0;
|
135
|
+
return 0;
|
136
|
+
}
|
137
|
+
/** an array closure was detected */
|
138
|
+
static void fio_json_on_end_array(json_parser_s *p) {
|
139
|
+
iodine_json_parser_s *pr = (iodine_json_parser_s *)p;
|
140
|
+
fio_json_stack_pop(&pr->stack, &pr->top);
|
141
|
+
pr->is_hash = (TYPE(pr->top) == T_HASH);
|
142
|
+
}
|
143
|
+
/** the JSON parsing is complete */
|
144
|
+
static void fio_json_on_json(json_parser_s *p) { (void)p; /* do nothing */ }
|
145
|
+
/** the JSON parsing is complete */
|
146
|
+
static void fio_json_on_error(json_parser_s *p) {
|
147
|
+
iodine_json_parser_s *pr = (iodine_json_parser_s *)p;
|
148
|
+
#if DEBUG
|
149
|
+
FIO_LOG_ERROR("JSON on error called.");
|
150
|
+
#endif
|
151
|
+
IodineStore.remove((VALUE)fio_json_stack_get(&pr->stack, 0));
|
152
|
+
IodineStore.remove(pr->key);
|
153
|
+
fio_json_stack_free(&pr->stack);
|
154
|
+
*pr = (iodine_json_parser_s){.top = 0};
|
155
|
+
}
|
156
|
+
|
157
|
+
/* *****************************************************************************
|
158
|
+
Iodine JSON Implementation
|
159
|
+
***************************************************************************** */
|
160
|
+
|
161
|
+
static inline VALUE iodine_json_convert(VALUE str, fiobj2rb_settings_s s) {
|
162
|
+
|
163
|
+
iodine_json_parser_s p = {.top = 0, .symbolize = s.str2sym};
|
164
|
+
size_t consumed = fio_json_parse(&p.p, RSTRING_PTR(str), RSTRING_LEN(str));
|
165
|
+
if (!consumed || p.p.depth) {
|
166
|
+
IodineStore.remove((VALUE)fio_json_stack_get(&p.stack, 0));
|
167
|
+
p.top = FIOBJ_INVALID;
|
168
|
+
}
|
169
|
+
fio_json_stack_free(&p.stack);
|
170
|
+
if (p.key) {
|
171
|
+
IodineStore.remove((VALUE)p.key);
|
172
|
+
}
|
173
|
+
if (!p.top) {
|
174
|
+
rb_raise(rb_eEncodingError, "Malformed JSON format.");
|
175
|
+
}
|
176
|
+
IodineStore.remove(p.top);
|
177
|
+
return p.top;
|
178
|
+
}
|
179
|
+
|
180
|
+
// static inline VALUE iodine_json_convert2(VALUE str, fiobj2rb_settings_s s) {
|
181
|
+
// FIOBJ json;
|
182
|
+
// if (!fiobj_json2obj(&json, RSTRING_PTR(str), RSTRING_LEN(str)) || !json) {
|
183
|
+
// rb_raise(rb_eRuntimeError, "JSON parsing failed. Not JSON?");
|
184
|
+
// return Qnil;
|
185
|
+
// }
|
186
|
+
// VALUE ret = fiobj2rb_deep(json, s.str2sym);
|
187
|
+
// fiobj_free(json);
|
188
|
+
// IodineStore.remove(ret);
|
189
|
+
// return ret;
|
190
|
+
// }
|
191
|
+
|
192
|
+
static inline void iodine_json_update_settings(VALUE h,
|
193
|
+
fiobj2rb_settings_s *s) {
|
194
|
+
VALUE tmp;
|
195
|
+
if (rb_hash_aref(h, max_nesting) != Qnil)
|
196
|
+
FIO_LOG_WARNING("max_nesting ignored on this JSON implementation.");
|
197
|
+
if (rb_hash_aref(h, allow_nan) != Qnil)
|
198
|
+
fprintf(stderr, "WARNING: allow_nan ignored on this JSON implementation. "
|
199
|
+
"NaN always allowed.\n");
|
200
|
+
if (rb_hash_aref(h, create_additions) != Qnil)
|
201
|
+
FIO_LOG_WARNING("create_additions ignored on this JSON implementation.");
|
202
|
+
if (rb_hash_aref(h, object_class) != Qnil)
|
203
|
+
FIO_LOG_WARNING("object_class ignored on this JSON implementation.");
|
204
|
+
if (rb_hash_aref(h, array_class) != Qnil)
|
205
|
+
FIO_LOG_WARNING("array_class ignored on this JSON implementation.");
|
206
|
+
if ((tmp = rb_hash_aref(h, symbolize_names)) != Qnil) {
|
207
|
+
if (tmp == Qtrue)
|
208
|
+
s->str2sym = 1;
|
209
|
+
else if (tmp == Qfalse)
|
210
|
+
s->str2sym = 0;
|
211
|
+
}
|
212
|
+
}
|
213
|
+
|
214
|
+
/**
|
215
|
+
Parse a JSON string using the iodine lenient parser (it's also faster).
|
216
|
+
*/
|
217
|
+
static VALUE iodine_json_parse(int argc, VALUE *argv, VALUE self) {
|
218
|
+
fiobj2rb_settings_s s = {.str2sym = 0};
|
219
|
+
if (argc > 2)
|
220
|
+
rb_raise(rb_eTypeError, "function requires supports up to two arguments.");
|
221
|
+
if (argc == 2) {
|
222
|
+
Check_Type(argv[1], T_HASH);
|
223
|
+
iodine_json_update_settings(argv[1], &s);
|
224
|
+
}
|
225
|
+
if (argc >= 1)
|
226
|
+
Check_Type(argv[0], T_STRING);
|
227
|
+
else
|
228
|
+
rb_raise(rb_eTypeError, "function requires at least one argument.");
|
229
|
+
return iodine_json_convert(argv[0], s);
|
230
|
+
(void)self;
|
231
|
+
}
|
232
|
+
|
233
|
+
/**
|
234
|
+
Parse a JSON string using the iodine lenient parser with a default Symbol
|
235
|
+
rather than String key (this is often faster than the regular
|
236
|
+
{Iodine::JSON.parse} function).
|
237
|
+
*/
|
238
|
+
static VALUE iodine_json_parse_bang(int argc, VALUE *argv, VALUE self) {
|
239
|
+
fiobj2rb_settings_s s = {.str2sym = 0};
|
240
|
+
if (argc > 2)
|
241
|
+
rb_raise(rb_eTypeError, "function requires supports up to two arguments.");
|
242
|
+
if (argc == 2) {
|
243
|
+
Check_Type(argv[1], T_HASH);
|
244
|
+
iodine_json_update_settings(argv[1], &s);
|
245
|
+
}
|
246
|
+
if (argc >= 1)
|
247
|
+
Check_Type(argv[0], T_STRING);
|
248
|
+
else
|
249
|
+
rb_raise(rb_eTypeError, "function requires at least one argument.");
|
250
|
+
return iodine_json_convert(argv[0], s);
|
251
|
+
(void)self;
|
252
|
+
}
|
253
|
+
|
254
|
+
void iodine_init_json(void) {
|
255
|
+
/**
|
256
|
+
Iodine::JSON offers a fast(er) JSON parser that is also lenient and supports
|
257
|
+
some JSON extensions such as Hex number recognition and comments.
|
258
|
+
|
259
|
+
You can test the parser using:
|
260
|
+
|
261
|
+
JSON_FILENAME="foo.json"
|
262
|
+
|
263
|
+
require 'json'
|
264
|
+
require 'iodine'
|
265
|
+
TIMES = 100
|
266
|
+
STR = IO.binread(JSON_FILENAME); nil
|
267
|
+
|
268
|
+
JSON.parse(STR) == Iodine::JSON.parse(STR) # => true
|
269
|
+
JSON.parse(STR,
|
270
|
+
symbolize_names: true) == Iodine::JSON.parse(STR,
|
271
|
+
symbolize_names: true) # => true
|
272
|
+
JSON.parse!(STR) == Iodine::JSON.parse!(STR) # => true/false (unknown)
|
273
|
+
|
274
|
+
# warm-up
|
275
|
+
TIMES.times { JSON.parse STR }
|
276
|
+
TIMES.times { Iodine::JSON.parse STR }
|
277
|
+
|
278
|
+
Benchmark.bm do |b|
|
279
|
+
sys = b.report("system") { TIMES.times { JSON.parse STR } }
|
280
|
+
sys_sym = b.report("system sym") { TIMES.times { JSON.parse STR,
|
281
|
+
symbolize_names: true } }
|
282
|
+
iodine = b.report("iodine") { TIMES.times { Iodine::JSON.parse STR } }
|
283
|
+
iodine_sym = b.report("iodine sym") do
|
284
|
+
TIMES.times { Iodine::JSON.parse STR,
|
285
|
+
symbolize_names: true }
|
286
|
+
end
|
287
|
+
puts "System / Iodine: #{sys/iodine}"
|
288
|
+
puts "System-sym/Iodine-sym: #{sys_sym/iodine_sym}"
|
289
|
+
end; nil
|
290
|
+
|
291
|
+
|
292
|
+
*/
|
293
|
+
VALUE tmp = rb_define_module_under(IodineModule, "JSON");
|
294
|
+
max_nesting = ID2SYM(rb_intern("max_nesting"));
|
295
|
+
allow_nan = ID2SYM(rb_intern("allow_nan"));
|
296
|
+
symbolize_names = ID2SYM(rb_intern("symbolize_names"));
|
297
|
+
create_additions = ID2SYM(rb_intern("create_additions"));
|
298
|
+
object_class = ID2SYM(rb_intern("object_class"));
|
299
|
+
array_class = ID2SYM(rb_intern("array_class"));
|
300
|
+
rb_define_module_function(tmp, "parse", iodine_json_parse, -1);
|
301
|
+
rb_define_module_function(tmp, "parse!", iodine_json_parse_bang, -1);
|
302
|
+
}
|